-module(parser_utils). -export([ read_file/1, get_namespace/0, get_namespace/2, get_blackboard/4, get_decorator/2, get_decorators/2, get_context/3, parse_args/2, throw_error_if_args_and_params_dont_match/3 ]). -include("../bt_node_behaviours/nodes.hrl"). %% convert yaml file to dictionary %% TODO: parse in detailed mode so better errors can be reported, which include line numbers read_file(YAMLFilePath) -> [YAMLList] = yamerl_constr:file(YAMLFilePath), maps:from_list(YAMLList). %% root namespace get_namespace() -> []. get_namespace(ParentNamespace, ChildName) -> ParentNamespace ++ [ChildName]. get_blackboard([], _Namespace, _Context, Blackboard) -> Blackboard; %% if reference is to an arg, then replace it with that. otherwise just whatever constant is their %% TODO: should probably do a better check for whether its an arg or some constant get_blackboard([BBKey | RestBBKeyList], Namespace, Context, Blackboard) -> ResolvedKey = blackboard:get_bb_key(Namespace, BBKey), NewBlackboard = maps:put(ResolvedKey, null, Blackboard), get_blackboard(RestBBKeyList, Namespace, Context, NewBlackboard). %% merge local bb keys with rest of context %% local bb key => get_bb_key(local bb key) get_context(Context, _Namespace, []) -> Context; get_context(PartialContext, Namespace, [LocalBBKey | Rest]) -> NewPartialContext = maps:put(LocalBBKey, blackboard:get_bb_key(Namespace, LocalBBKey), PartialContext), get_context(NewPartialContext, Namespace, Rest). get_decorator(DecoratorList, Context) -> Module = proplists:get_value("module", DecoratorList), UnparsedArgs = proplists:get_value("args", DecoratorList), Name = proplists:get_value("name", DecoratorList, Module), % Params = proplists:get_value("params", DecoratorList, []), ModuleAtom = list_to_atom(Module), Params = ModuleAtom:get_params(), parser_utils:throw_error_if_args_and_params_dont_match(Module, UnparsedArgs, Params), ArgsAsMap = parse_args(UnparsedArgs, Context), #decorator{ name = Name, module = ModuleAtom, args = ArgsAsMap }. get_decorators(undefined, _Context) -> []; get_decorators(DecoratorsList, Context) -> [get_decorator(DecoratorList, Context) || DecoratorList <- DecoratorsList]. parse_args(Args, Context) -> parse_args(Args, #{}, Context). parse_args([], NewContext, _Context) -> NewContext; parse_args([{ArgKey, ArgVal} | Rest], NewContext, Context) -> %% check if arg val is in context; if so, then replace. ValToPut = parse_argval(ArgVal, Context), NewNewContext = maps:put(ArgKey, ValToPut, NewContext), parse_args(Rest, NewNewContext, Context). parse_argval(ArgVal, Context) -> case maps:is_key(ArgVal, Context) of true -> maps:get(ArgVal, Context); false -> parse_argval_basic(ArgVal, Context) end. %% can be deeply nested! %% flawed logic because can't really detect if a list is string or not... but maybe don't need to? parse_argval_basic(ArgVal, _Context) when is_atom(ArgVal); is_number(ArgVal); is_binary(ArgVal) -> ArgVal; parse_argval_basic(ArgVal, Context) when is_tuple(ArgVal) -> %% split L = tuple_to_list(ArgVal), list_to_tuple(parse_argval_basic(L, Context)); parse_argval_basic(ArgVal, Context) when is_list(ArgVal) -> [parse_argval(X, Context) || X <- ArgVal]. throw_error_if_args_and_params_dont_match(NodeName, Args, Params) -> %% args is proplist, params is list ArgKeys = proplists:get_keys(Args), Set1 = ordsets:from_list(ArgKeys), Set2 = ordsets:from_list(Params), case Set1 == Set2 of true -> ok; false -> ErrorReason = lists:flatten(io_lib:format("Args ~p do not match params ~p for node '~s'", [ordsets:to_list(Set1), ordsets:to_list(Set2), NodeName])), erlang:error(ErrorReason) end.