Current section

Files

Jump to
erlang_behaviour_trees src bt_engine yaml_parser parse_subtree.erl
Raw

src/bt_engine/yaml_parser/parse_subtree.erl

-module(parse_subtree).
-export([
parse/6
]).
-include("../bt_node_behaviours/nodes.hrl").
throw_error_if_subtree_file_not_present(Ref, SubtreeName) ->
case Ref of
undefined ->
ErrorReason = "Could not find definition for subtree '" ++ SubtreeName ++ "'. Please include filename ref for it.",
erlang:error(ErrorReason);
_ -> ok
end.
throw_error_if_subtree_child_is_not_selector_or_sequence(SubtreeName, ChildName) ->
case ChildName of
"selector" -> ok;
"sequence" -> ok;
_ ->
ErrorReason = "Child of subtree '" ++ SubtreeName ++ "' is not a selector or sequence. Please fix.",
erlang:error(ErrorReason)
end.
throw_error_if_subtree_has_name(SubtreeNamme, List) ->
case proplists:get_value("name", List, undefined) of
undefined -> ok;
_ ->
ErrorReason = lists:flatten(io_lib:format("Subtrees cannot have names; instead name their child. Fix tree `~p`~n", [SubtreeName])),
erlang:error(ErrorReason)
end.
% %% yaml dictionary is a map of subtree name => unparsed subtree yaml. this
% %% can be extended by reading more files.
-spec parse(
Subtree :: {string(), list({term(), term()})},
Namespace :: {bb_key, list(), term()},
ParentContext :: map(),
Blackboard :: map(),
OldYAMLDictionary :: term(),
FileDirectory :: string()
) -> {ParsedTree :: term(), Blackboard :: term()}.
parse(_SubTree = {SubtreeName, TempList}, Namespace, ParentContext, Blackboard, OldYAMLDictionary, FileDirectory) ->
%% check if definition in YAML dictionary, otherwise read from file
{YAMLDictionary, NewFileDirectory} = case maps:is_key(SubtreeName, OldYAMLDictionary) of
true -> {OldYAMLDictionary, FileDirectory};
false ->
Ref = proplists:get_value("ref", TempList),
%% throw error if ref doesn't exist
throw_error_if_subtree_file_not_present(Ref, SubtreeName),
FileName = filename:join(FileDirectory, Ref),
Directory = filename:dirname(FileName),
{maps:merge(parser_utils:read_file(FileName), OldYAMLDictionary), Directory}
end,
Decorators = proplists:get_value("decorators", TempList, []),
%% again assume that args and params are matching?
Args = proplists:get_value("args", TempList, []),
List = maps:get(SubtreeName, YAMLDictionary),
ListWithArgs = List ++ [{"args", Args}],
parse({SubtreeName, ListWithArgs}, Decorators, Namespace, ParentContext, Blackboard, YAMLDictionary, NewFileDirectory).
parse(_Subtree = {SubtreeName, List}, [], Namespace, ParentContext, Blackboard, YAMLDictionary, FileDirectory) ->
Params = proplists:get_value("params", List, []),
Args = proplists:get_value("args", List, []),
parser_utils:throw_error_if_args_and_params_dont_match(SubtreeName, Args, Params),
%% parse args to replace things using context
ParsedArgsContext = parser_utils:parse_args(Args, ParentContext),
%% create new context consisting just of params + local bb keys
BBKeysList = proplists:get_value("bb_keys", List, []),
Context = parser_utils:get_context(ParsedArgsContext, Namespace, BBKeysList),
%% add own keys to bb
NewBlackboard = parser_utils:get_blackboard(BBKeysList, Namespace, Context, Blackboard),
[OnlyChildNode = {OnlyChildName, _}] = proplists:get_value("definition", List),
throw_error_if_subtree_child_is_not_selector_or_sequence(SubtreeName, OnlyChildName),
throw_error_if_subtree_has_name(SubtreeName, List),
%% use same namespace since this child is only selector or sequence, no conflict of variable declaration.
parser:parse(OnlyChildNode, Namespace, Context, NewBlackboard, YAMLDictionary, FileDirectory);
%% TODO: consolidate decorator parse logic; right repeated in parse task/sequence/subtree
parse(Subtree = {_SubtreeName, _List}, [Decorator | RestOfDecorators], Namespace, ParentContext, Blackboard, YAMLDictionary, FileDirectory) ->
ParsedDecorator = parser_utils:get_decorator(Decorator, ParentContext),
{Child, NewBlackboard} = parse(Subtree, RestOfDecorators, Namespace, ParentContext, Blackboard, YAMLDictionary, FileDirectory),
{
ParsedDecorator#decorator{
child = Child
},
NewBlackboard
}.