Skip to content

Commit

Permalink
[#22] Introduce erlang_ls_tree module
Browse files Browse the repository at this point in the history
  • Loading branch information
robertoaloi committed Aug 1, 2019
1 parent 471b8ef commit 733aa91
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 117 deletions.
2 changes: 1 addition & 1 deletion src/erlang_ls_buffer.erl
Expand Up @@ -144,7 +144,7 @@ do_get_mfa(Text, Line, _Character) ->
do_get_element_at_pos(Text, Line, Column) ->
%% TODO: Cache tree
{ok, Tree} = erlang_ls_parser:parse(Text),
AnnotatedTree = erlang_ls_parser:annotate(Tree),
AnnotatedTree = erlang_ls_tree:annotate(Tree),
erlang_ls_parser:find_poi_by_pos(AnnotatedTree, {Line, Column}).

-spec get_line_text(binary(), integer()) -> binary().
Expand Down
117 changes: 1 addition & 116 deletions src/erlang_ls_parser.erl
@@ -1,17 +1,13 @@
-module(erlang_ls_parser).

-export([ annotate/1
, annotate_node/1
, find_poi_by_info/2
-export([ find_poi_by_info/2
, find_poi_by_info_key/2
, find_poi_by_pos/2
, list_poi/1
, parse/1
, parse_file/1
, postorder_update/2
]).

-type syntax_tree() :: erl_syntax:syntaxTree().
-type line() :: non_neg_integer().
-type column() :: non_neg_integer().
-type pos() :: {line(), column()}.
Expand All @@ -21,7 +17,6 @@

-export_type([ poi/0
, range/0
, syntax_tree/0
]).

%% TODO: Generate random filename
Expand Down Expand Up @@ -52,27 +47,6 @@ parse_file(Path) ->
{error, Error}
end.

-spec annotate(syntax_tree()) -> syntax_tree().
annotate(Tree) ->
postorder_update(fun annotate_node/1, Tree).

%% Create annotations for the points of interest (aka `poi`) in the
%% tree.
-spec annotate_node(syntax_tree()) -> syntax_tree().
annotate_node(Tree) ->
lists:foldl(fun erl_syntax:add_ann/2, Tree, analyze(Tree)).

%% Extracted from the `erl_syntax` documentation.
-spec postorder_update(fun(), syntax_tree()) -> syntax_tree().
postorder_update(F, Tree) ->
F(case erl_syntax:subtrees(Tree) of
[] -> Tree;
List -> erl_syntax:update_tree(Tree,
[[postorder_update(F, Subtree)
|| Subtree <- Group]
|| Group <- List])
end).

-spec get_range(syntax_tree(), pos(), {atom(), any()}) -> range().
get_range({Line, Column}, {application, {M, F, _A}}) ->
CFrom = Column - length(atom_to_list(M)),
Expand Down Expand Up @@ -158,95 +132,6 @@ list_poi(Tree) ->
matches_pos(Pos, #{from := From, to := To}) ->
(From =< Pos) andalso (Pos =< To).

-spec analyze(syntax_tree()) -> [poi()].
analyze(Tree) ->
Type = erl_syntax:type(Tree),
try analyze(Tree, Type)
catch
Class:Reason ->
lager:warning("Could not analyze tree: ~p:~p", [Class, Reason]),
[]
end.

-spec analyze(syntax_tree(), any()) -> [poi()].
analyze(Tree, application) ->
case erl_syntax_lib:analyze_application(Tree) of
{M, {F, A}} ->
%% Remote call
[poi(Tree, {application, {M, F, A}})];
{F, A} ->
case lists:member({F, A}, erlang:module_info(exports)) of
true ->
%% Call to a function from the `erlang` module
[poi(Tree, {application, {erlang, F, A}})];
false ->
%% Local call
[poi(Tree, {application, {F, A}})]
end;
A when is_integer(A) ->
%% If the function is not explicitly named (e.g. a variable is
%% used as the module qualifier or the function name), only the
%% arity A is returned.
%% In the special case where the macro `?MODULE` is used as the
%% module qualifier, we can consider it as a local call.
Operator = erl_syntax:application_operator(Tree),
try { erl_syntax:variable_name(
erl_syntax:macro_name(
erl_syntax:module_qualifier_argument(Operator)))
, erl_syntax:atom_value(
erl_syntax:module_qualifier_body(Operator))
} of
{'MODULE', F} ->
[poi(Tree, {application, {'MODULE', F, A}})]
catch _:_ ->
[]
end
end;
analyze(Tree, attribute) ->
case erl_syntax_lib:analyze_attribute(Tree) of
%% Yes, Erlang allows both British and American spellings for
%% keywords.
{behavior, {behavior, Behaviour}} ->
[poi(Tree, {behaviour, Behaviour})];
{behaviour, {behaviour, Behaviour}} ->
[poi(Tree, {behaviour, Behaviour})];
{export, Exports} ->
[poi(Tree, {exports_entry, {F, A}}) || {F, A} <- Exports];
{module, {Module, _Args}} ->
[poi(Tree, {module, Module})];
{module, Module} ->
[poi(Tree, {module, Module})];
preprocessor ->
Name = erl_syntax:atom_value(erl_syntax:attribute_name(Tree)),
case {Name, erl_syntax:attribute_arguments(Tree)} of
{define, [Define|_]} ->
[poi(Tree, {define, erl_syntax:variable_name(Define)})];
{include, [String]} ->
[poi(Tree, {include, erl_syntax:string_literal(String)})];
{include_lib, [String]} ->
[poi(Tree, {include_lib, erl_syntax:string_literal(String)})];
_ ->
[]
end;
{record, {Record, _Fields}} ->
[poi(Tree, {record, atom_to_list(Record)})];
{spec, Spec} ->
[poi(Tree, {spec, Spec})];
_ ->
[]
end;
analyze(Tree, function) ->
{F, A} = erl_syntax_lib:analyze_function(Tree),
[poi(Tree, {function, {F, A}})];
analyze(Tree, macro) ->
Macro = erl_syntax:variable_name(erl_syntax:macro_name(Tree)),
[poi(Tree, {macro, Macro})];
analyze(Tree, record_expr) ->
Record = erl_syntax:atom_name(erl_syntax:record_expr_type(Tree)),
[poi(Tree, {record_expr, Record})];
analyze(_Tree, _) ->
[].

-spec poi(syntax_tree(), any()) -> poi().
poi(Tree, Info) ->
Pos = erl_syntax:get_pos(Tree),
Expand Down
140 changes: 140 additions & 0 deletions src/erlang_ls_tree.erl
@@ -0,0 +1,140 @@
%%==============================================================================
%% Library to handle syntax trees annotated with points of interest
%%==============================================================================
-module(erlang_ls_tree).

%%==============================================================================
%% Exports
%%==============================================================================
-export([ annotate/1
, annotate_node/1
, postorder_update/2
, points_of_interest/1
]).

%%==============================================================================
%% Types
%%==============================================================================
-type tree() :: erl_syntax:syntaxTree().

-export_type([ tree/0 ]).

%%==============================================================================
%% API
%%==============================================================================
%% @edoc Given a syntax tree, it returns a new one, annotated with all
%% the identified _points of interest_ (a.k.a. _poi_).
-spec annotate(tree()) -> tree().
annotate(Tree) ->
postorder_update(fun annotate_node/1, Tree).

%% @edoc Add an annotation to the root of the given `Tree` for each
%% point of interest found.
-spec annotate_node(tree()) -> tree().
annotate_node(Tree) ->
lists:foldl(fun erl_syntax:add_ann/2, Tree, points_of_interest(Tree)).

%% @edoc Traverse the given `Tree`, applying the function `F` to all
%% nodes in the tree, in post-order. Extracted from the `erl_syntax`
%% documentation.
-spec postorder_update(fun(), syntax_tree()) -> syntax_tree().
postorder_update(F, Tree) ->
F(case erl_syntax:subtrees(Tree) of
[] -> Tree;
List -> erl_syntax:update_tree(Tree,
[[postorder_update(F, Subtree)
|| Subtree <- Group]
|| Group <- List])
end).

%% @edoc Return the list of points of interest for a given `Tree`.
-spec points_of_interest(tree()) -> [poi()].
points_of_interest(Tree) ->
Type = erl_syntax:type(Tree),
try points_of_interest(Tree, Type)
catch
Class:Reason ->
lager:warning("Could not analyze tree: ~p:~p", [Class, Reason]),
[]
end.

%% @edoc Return the list of points of interest of a specific `Type`
%% for a given `Tree`.
-spec points_of_interest(syntax_tree(), any()) -> [poi()].
points_of_interest(Tree, application) ->
case erl_syntax_lib:analyze_application(Tree) of
{M, {F, A}} ->
%% Remote call
[poi(Tree, {application, {M, F, A}})];
{F, A} ->
case lists:member({F, A}, erlang:module_info(exports)) of
true ->
%% Call to a function from the `erlang` module
[poi(Tree, {application, {erlang, F, A}})];
false ->
%% Local call
[poi(Tree, {application, {F, A}})]
end;
A when is_integer(A) ->
%% If the function is not explicitly named (e.g. a variable is
%% used as the module qualifier or the function name), only the
%% arity A is returned.
%% In the special case where the macro `?MODULE` is used as the
%% module qualifier, we can consider it as a local call.
Operator = erl_syntax:application_operator(Tree),
try { erl_syntax:variable_name(
erl_syntax:macro_name(
erl_syntax:module_qualifier_argument(Operator)))
, erl_syntax:atom_value(
erl_syntax:module_qualifier_body(Operator))
} of
{'MODULE', F} ->
[poi(Tree, {application, {'MODULE', F, A}})]
catch _:_ ->
[]
end
end;
points_of_interest(Tree, attribute) ->
case erl_syntax_lib:analyze_attribute(Tree) of
%% Yes, Erlang allows both British and American spellings for
%% keywords.
{behavior, {behavior, Behaviour}} ->
[poi(Tree, {behaviour, Behaviour})];
{behaviour, {behaviour, Behaviour}} ->
[poi(Tree, {behaviour, Behaviour})];
{export, Exports} ->
[poi(Tree, {exports_entry, {F, A}}) || {F, A} <- Exports];
{module, {Module, _Args}} ->
[poi(Tree, {module, Module})];
{module, Module} ->
[poi(Tree, {module, Module})];
preprocessor ->
Name = erl_syntax:atom_value(erl_syntax:attribute_name(Tree)),
case {Name, erl_syntax:attribute_arguments(Tree)} of
{define, [Define|_]} ->
[poi(Tree, {define, erl_syntax:variable_name(Define)})];
{include, [String]} ->
[poi(Tree, {include, erl_syntax:string_literal(String)})];
{include_lib, [String]} ->
[poi(Tree, {include_lib, erl_syntax:string_literal(String)})];
_ ->
[]
end;
{record, {Record, _Fields}} ->
[poi(Tree, {record, atom_to_list(Record)})];
{spec, Spec} ->
[poi(Tree, {spec, Spec})];
_ ->
[]
end;
points_of_interest(Tree, function) ->
{F, A} = erl_syntax_lib:analyze_function(Tree),
[poi(Tree, {function, {F, A}})];
points_of_interest(Tree, macro) ->
Macro = erl_syntax:variable_name(erl_syntax:macro_name(Tree)),
[poi(Tree, {macro, Macro})];
points_of_interest(Tree, record_expr) ->
Record = erl_syntax:atom_name(erl_syntax:record_expr_type(Tree)),
[poi(Tree, {record_expr, Record})];
points_of_interest(_Tree, _) ->
[].

0 comments on commit 733aa91

Please sign in to comment.