Current section
Files
Jump to
Current section
Files
src/bt_engine/yaml_parser/parser_utils.erl
-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/bt_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 = bt_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, bt_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.