<div dir="ltr"><div dir="ltr"><div dir="ltr">The problem seems to mainly be that you would like to be able to use cerl_trees:fold() to do the traversing for you. But in general, a fold doesn't pass the operator any info about the context of each element in relation to its neighbouring elements, in the traversal order.<div><br></div><div>The representation of Core Erlang syntax trees was specifically designed so that only constructs that have a meaningful semantics on their own will be nodes in the tree. So, you get nodes for literals, calls, tuples, funs, letrecs, etc. (including modules). Many of these have a simple sequence of child nodes, as for a cons cell or a tuple, and in a post-order traversal, you know that you have just seen all the child nodes when you see the tuple node itself. But for some other constructs, like a 'fun (X1,...Xn) -> Body' or a 'case Expr of <Clauses> end' or 'module Name [ <Exports> ... ] attributes [ <Attributes>... ] F1/A1 = fun ... F2/A2 = fun ... end', there is nothing that tells a fold how to separate the subgroups of child nodes, especially when they all can have varying length.</div><div><br></div><div>To fix this, a syntax tree representation can choose to use specific grouping nodes, like {module, [{name, [...]}, {exports, [...]}, {attributes, [...]}, {definitions, [...]} ]}, so that a fold would see "aha, I just got done with a 'definitions' group". I guess that's what you wanted, Chris. It does simplify some things in traversals. The drawback, and the reason we decided to avoid all such nodes, is that it forces all code that works on those syntax trees to understand these grouping nodes, and treat them separately from meaning-carrying nodes. In our experience, that is a big pain. I guess what we should have done but never got around to is write some really good tree walking functions that can tell you the context, choose a traversal order, and such things. As is, the cerl:subtrees() function is your best friend. The cerl_trees:fold() was just intended for simple things like checking for occurrences, counting tree sizes and whatnot.</div><div><div><br></div><div>See the example below for how you can label a Core Erlang tree and traverse it to compute mappings between labels and function names.</div><div>It's written as a core transform, so you can try it on an arbitrary module <font face="monospace, monospace">foo</font> by compiling it with <font face="monospace, monospace">c(foo,[{core_transform,cerl_funcmap}])</font>.</div><div><br><div><div dir="ltr" class="gmail_signature"> /Richard</div></div></div></div><div><br></div><div><div><font face="monospace, monospace">%% =====================================================================</font></div><div><font face="monospace, monospace">%% @doc Core transform example for computing and printing a label mapping</font></div><div><font face="monospace, monospace">%% @author Richard Carlsson <<a href="mailto:carlsson.richard@gmail.com">carlsson.richard@gmail.com</a>></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">-module(cerl_funcmap).</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">-export([core_transform/2]).</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">core_transform(T, _Opts) -></font></div><div><font face="monospace, monospace"> {T1, _MaxL} = cerl_trees:label(T),</font></div><div><font face="monospace, monospace"> %%io:format("~n~s.~n", [cerl_prettypr:format(T1)]),</font></div><div><font face="monospace, monospace"> {Ms, _Ls} = visit(T1),</font></div><div><font face="monospace, monospace"> io:format("Functions to labels:~n~p.~n", [maps:from_list(Ms)]),</font></div><div><font face="monospace, monospace"> Rev = lists:usort(</font></div><div><font face="monospace, monospace"> lists:flatmap(fun ({V, Ls}) -> [{L, V} || L <- Ls] end,</font></div><div><font face="monospace, monospace"> Ms)),</font></div><div><font face="monospace, monospace"> io:format("Labels to functions:~n~p.~n", [maps:from_list(Rev)]),</font></div><div><font face="monospace, monospace"> T. % return original T</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">visit(T) -></font></div><div><font face="monospace, monospace"> case cerl:type(T) of</font></div><div><font face="monospace, monospace"> letrec -></font></div><div><font face="monospace, monospace"> {visit_defs(cerl:letrec_defs(T), []),</font></div><div><font face="monospace, monospace"> get_label(T)};</font></div><div><font face="monospace, monospace"> module -></font></div><div><font face="monospace, monospace"> {visit_defs(cerl:module_defs(T), []),</font></div><div><font face="monospace, monospace"> get_label(T)};</font></div><div><font face="monospace, monospace"> _ -></font></div><div><font face="monospace, monospace"> case cerl:subtrees(T) of</font></div><div><font face="monospace, monospace"> [] -></font></div><div><font face="monospace, monospace"> {[], get_label(T)};</font></div><div><font face="monospace, monospace"> Gs -></font></div><div><font face="monospace, monospace"> {Ms, Ls} = visit_list(lists:flatten(Gs)),</font></div><div><font face="monospace, monospace"> {Ms, get_label(T) ++ Ls}</font></div><div><font face="monospace, monospace"> end</font></div><div><font face="monospace, monospace"> end.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">visit_list(Ts) -></font></div><div><font face="monospace, monospace"> lists:foldl(fun (T, {Ms0, Ls0}) -></font></div><div><font face="monospace, monospace"> {Ms, Ls} = visit(T),</font></div><div><font face="monospace, monospace"> {Ms ++ Ms0, Ls ++ Ls0}</font></div><div><font face="monospace, monospace"> end,</font></div><div><font face="monospace, monospace"> {[], []},</font></div><div><font face="monospace, monospace"> Ts).</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">visit_defs([{V, T} | Ds], Ms0) -></font></div><div><font face="monospace, monospace"> {Ms, Ls} = visit(T),</font></div><div><font face="monospace, monospace"> Ls1 = ordsets:union(get_label(V), ordsets:from_list(Ls)),</font></div><div><font face="monospace, monospace"> visit_defs(Ds, [{cerl:var_name(V), Ls1}] ++ Ms ++ Ms0);</font></div><div><font face="monospace, monospace">visit_defs([], Ms) -></font></div><div><font face="monospace, monospace"> Ms.</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">get_label(T) -></font></div><div><font face="monospace, monospace"> case cerl:get_ann(T) of</font></div><div><font face="monospace, monospace"><span style="white-space:pre"> </span>[{label, L} | _] -> [L];</font></div><div><font face="monospace, monospace"><span style="white-space:pre"> </span>_ -> []</font></div><div><font face="monospace, monospace"> end.</font></div></div><div><br></div><div>%% ============================================================</div><div><br></div></div></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">Den mån 11 mars 2019 kl 15:49 skrev Christopher Meiklejohn <<a href="mailto:christopher.meiklejohn@gmail.com">christopher.meiklejohn@gmail.com</a>>:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">On Sun, Mar 10, 2019 at 1:02 PM Christopher Meiklejohn<br>
<<a href="mailto:christopher.meiklejohn@gmail.com" target="_blank">christopher.meiklejohn@gmail.com</a>> wrote:<br>
><br>
> As part of a static analysis I'm writing, I'm trying to associate<br>
> function names with their function bodies so I can provide information<br>
> about the calling functions to users as part of the completed<br>
> analysis. However, I'm running into difficulty performing this<br>
> mapping.<br>
><br>
> The problem I'm running into is the following:<br>
><br>
> 1.) In the Core Erlang AST, named functions are defined by associating<br>
> a c_var node in the form of {Function, Arity} to a subtree containing<br>
> the c_fun function body.<br>
> 2.) Also, in the Core Erlang AST, calls to functions are also<br>
> represented as c_var nodes in the form of {Function, Arity}.<br>
><br>
> The cerl_trees:fold, mapfold all perform postorder traversals. With a<br>
> postorder traversal, there may be an arbitrary distance in the fold<br>
> between when a function is defined and where it is given a name. With<br>
> reverse postorder, my understanding is that these nodes would be<br>
> directly next to each other, which would make the mapping easier.<br>
><br>
> My initial approach was to attempt to, when a c_var node was found in<br>
> the proper form, store a candidate name for the next function that's<br>
> found and perform the association that way. The problem that arises<br>
> from this approach is that there can be an arbitrary number of c_var<br>
> nodes for function invocations between the definition of the function<br>
> and it's actual body. The same can also happen for any functions that<br>
> occur in between as well.<br>
><br>
> Therefore, my analysis incorrectly associates function bodies to<br>
> function names for any function that calls another function in the<br>
> body -- incorrectly thinking that the nearest c_var node containing a<br>
> function name is it's associated name.<br>
><br>
> I assume this must have been done for a previous analysis performed on<br>
> Erlang, but I haven't found anything. Would a reverse postorder<br>
> traversal make this easier? Is there a better way to identify the<br>
> mapping between c_var nodes and c_funs that are being assigned to<br>
> them? Has this been done before?<br>
<br>
Just for those who are interested, I did figure out one solution to<br>
the problem which I have tried out on several models now that appears<br>
to be working so far.<br>
<br>
Here's the rough outline of my currently-working solution:<br>
<br>
Since there is no node directly connecting the c_var of a variable<br>
assignment along with the body of the function, I had to perform a<br>
traversal where I try to match function bodies that I find along with<br>
the variables they are being assigned to. To do this, I did the<br>
following:<br>
<br>
1.) I first use cerl_trees:fold to perform a postorder traversal of<br>
the tree, building up a list of nodes to explore.<br>
2.) I then lists:foldr over this, allowing me to perform effectively a<br>
reverse postorder traversal of the graph, where, I will encounter<br>
function bodies (c_fun) prior to encountering function variables.<br>
(c_var)<br>
3.) I perform an analysis that allows me to match up names to bodies<br>
using the following heuristic:<br>
<br>
a.) Whenever I encounter a function body, I can determine from the<br>
node whether or not the function body is the body of an anonymous<br>
function or not. Anonymous functions are annotated with a randomly<br>
assigned label which includes the enclosing function and the line<br>
where the anonymous function was generated. This allows me to rule<br>
out the top-level functions from functions that are created from<br>
within a top-level function. When I discover one of these functions,<br>
I keep track of this in a candidate list of potential function body<br>
matches for the function variable I am assigning to.<br>
b.) Whenever I encounter a c_var in the form of {Function, Arity}, I<br>
know that I'm dealing with an application or an assignment.<br>
(Curiously, why is there not a node that directly associates the c_var<br>
to the c_fun -- this would have made things *significantly* easier,<br>
but does not exist in the Core Erlang when performing a traversal via<br>
cerl_trees.) If the function contains an annotation containing a line<br>
number of invocation, then I know it's the *application* of this<br>
function, and not the *abstraction* of this function. (Again, why<br>
does c_var treat LHS assignments the same as RHS use of the function<br>
-- if this was more specific, it would have again, made things<br>
*significantly* easier.) If I'm looking for a candidate match between<br>
body and function variable, then I perform a match based on this.<br>
c.) Otherwise, either the c_var is the use of a function or the c_fun<br>
is the creation of an anonymous function, and it's subsequently<br>
ignored by the matching procedure.<br>
<br>
On the 'analysis' branch of this, I have a prototype of this<br>
implementation that I've tested on several modules that appears to be<br>
working.<br>
<br>
If this could have been done a different, easier way, I'd appreciate<br>
knowing about it.<br>
<br>
Thanks,<br>
Christopher<br>
_______________________________________________<br>
erlang-questions mailing list<br>
<a href="mailto:erlang-questions@erlang.org" target="_blank">erlang-questions@erlang.org</a><br>
<a href="http://erlang.org/mailman/listinfo/erlang-questions" rel="noreferrer" target="_blank">http://erlang.org/mailman/listinfo/erlang-questions</a><br>
</blockquote></div>