【问题标题】:How to turn a string with a valid Erlang expression into an abstract syntax tree (AST)?如何将具有有效 Erlang 表达式的字符串转换为抽象语法树(AST)?
【发布时间】:2020-04-06 11:02:40
【问题描述】:

我想将包含有效 Erlang 表达式的字符串转换为其抽象语法树表示,但目前没有任何成功。

下面是我想做的一个例子。编译后,所有z:z().生成模块zed,通过调用zed:zed().返回在给定列表上应用lists:reverse的结果。

-module(z).
-export([z/0]).

z() ->
  ModuleAST = erl_syntax:attribute(erl_syntax:atom(module),
                                   [erl_syntax:atom("zed")]),

  ExportAST = erl_syntax:attribute(erl_syntax:atom(export),
                                   [erl_syntax:list(
                                    [erl_syntax:arity_qualifier(
                                     erl_syntax:atom("zed"),
                                     erl_syntax:integer(0))])]),

  %ListAST = ?(String),  % This is where I would put my AST
  ListAST = erl_syntax:list([erl_syntax:integer(1), erl_syntax:integer(2)]),

  FunctionAST = erl_syntax:function(erl_syntax:atom("zed"),
                                    [erl_syntax:clause(
                                     [], none,
                                     [erl_syntax:application(
                                        erl_syntax:atom(lists),
                                        erl_syntax:atom(reverse),
                                        [ListAST]
                    )])]),

  Forms = [erl_syntax:revert(AST) || AST <- [ModuleAST, ExportAST, FunctionAST]],

  case compile:forms(Forms) of
    {ok,ModuleName,Binary}           -> code:load_binary(ModuleName, "z", Binary);
    {ok,ModuleName,Binary,_Warnings} -> code:load_binary(ModuleName, "z", Binary)
  end.

String 可以是"[1,2,3].",或"begin A=4, B=2+3, [A,B] end.",或任何类似的东西。

(请注意,这只是我想做的一个例子,所以评估String 对我来说不是一个选项。)


编辑:

如下指定 ListAST 会生成一个巨大的 dict-digraph-error-monster,并显示“lint_module 中的内部错误”。

String = "[1,2,3].",
{ok, Ts, _} = erl_scan:string(String),
{ok, ListAST} = erl_parse:parse_exprs(Ts),

EDIT2:

此解决方案适用于简单的术语:

{ok, Ts, _} = erl_scan:string(String),
{ok, Term} = erl_parse:parse_term(Ts),
ListAST = erl_syntax:abstract(Term),

【问题讨论】:

  • 现在我查看了代码,显然我混淆了 erl_syntax 和 erl_parse 格式......但仍然无法弄清楚如何做到这一点(典型的太多 bejgli 错误)。
  • 是的,如果您将 ListAST 与 erl_syntax 制作的 ListAST 进行比较,它们看起来并不那么相似 :( 42> ListAST. [{cons,1,{integer,1,1},{cons, 1,{integer,1,2},{nil,1}}}] 43> erl_syntax:list([1, 2, 3], []). {tree,list,{attr,0,[],none },{列表,[1,2,3],[]}} 44>
  • 所以我要么需要一种方法来从字符串中创建一个与erl_syntax 兼容的 AST,要么需要一种方法将一个占位符放在 erl_syntax 的东西上,并在调用 revert() 后替换它。或者我遗漏了一些明显的东西......
  • 添加以下来自 erl_syntax 手册的注释:“这意味着所有 erl_parse 树都是有效的抽象语法树......”
  • 不,我只是认为它没有正确标记,所以你输入 [{cons, 1, ...]] 并且它期望 {tree, something} 与 [{缺点,1,...}] :(

标签: erlang abstract-syntax-tree


【解决方案1】:

在您的编辑示例中:

String = "[1,2,3].",
{ok, Ts, _} = erl_scan:string(String),
{ok, ListAST} = erl_parse:parse_exprs(Ts),

ListAST 实际上是一个 AST:s 列表(因为 parse_exprs,顾名思义,解析多个表达式(每个表达式都以句点结尾)。由于您的字符串包含一个表达式,因此您得到了一个包含一个元素的列表。所有你需要做的就是匹配出来:

{ok, [ListAST]} = erl_parse:parse_exprs(Ts),

所以它与 erl_syntax 无关(它接受所有 erl_parse 树);只是您在 ListAST 周围有一个额外的列表包装器,这导致编译器呕吐。

【讨论】:

  • 谢谢理查德!我想我应该明白这一点... :\
【解决方案2】:

我头顶的一些cmets。

我并没有真正使用过 erl_syntax 库,但我确实认为它们使阅读和“看到”您正在尝试构建的内容变得困难。我可能会导入这些函数或定义我自己的 API 以使其更短、更易读。但是我通常更喜欢较短的函数和变量名称。

erl_syntax 创建的 AST 和 erl_parse 创建并在编译器中使用的“标准”AST 是不同的,不能混用。所以你必须选择其中之一并坚持下去。

第二次编辑中的示例适用于术语,但不适用于更一般的情况:

{ok, Ts, _} = erl_scan:string(String),
{ok, Term} = erl_parse:parse_term(Ts),
ListAST = erl_syntax:abstract(Term),

这是因为 erl_parse:parse_term/1 返回由标记表示的实际术语,而其他 erl_parse 函数 parse_form 和 parse_exprs 返回 AST。将它们放入 erl_syntax:abstract 会做一些有趣的事情。

根据您要执行的操作,实际写出 erlang 文件并编译它实际上可能比直接使用抽象形式更容易。这违背了我根深蒂固的感觉,但生成 erlang AST 并非易事。您打算生成什么类型​​的代码?

&lt;shameless_plug&gt;

如果您不害怕列表,您可以尝试使用 LFE(lisp 风格的 erlang)来生成代码,因为所有 lisp 都没有特殊的抽象形式,它们都是同形的并且更容易使用。

&lt;/shameless_plug&gt;

【讨论】:

  • 谢谢你的回答,罗伯特。与此同时,我从 Richard 那里得到了答案:erl_parse 树可以混合到 erl_syntax 树中。然后调用erl_syntax:revert() 创建一个干净的 erl_parse 树。我唯一的错误是没有注意到 erl_parse:parse_exprs() 的结果包含在列表中...
  • 一开始我也打算将源代码构建在一个临时文件中,并按照通常的方式进行编译。现在我将其更改为构建一个 iolist() ,并在其上使用 parse_forms ,所以一切都在内存中完成。不幸的是,我失去了一些不错的功能,例如 code:get_object_code、beam_lib:get_chunks、hipe:compile,但我可以忍受。
  • 顺便说一句,我只是在玩从模板文件生成模块,同时让自己在模板中使用 Erlang 代码。
【解决方案3】:

佐尔坦

这就是我们获得 AST 的方式:

11> String = "fun() -> io:format(\"blah~n\") end.".
"fun() -> io:format(\"blah~n\") end."
12> {ok, Tokens, _} = erl_scan:string(String).     
{ok,[{'fun',1},
     {'(',1},
     {')',1},
     {'->',1},
     {atom,1,io},
     {':',1},
     {atom,1,format},
     {'(',1},
     {string,1,"blah~n"},
     {')',1},
     {'end',1},
     {dot,1}],
    1}
13> {ok, AbsForm} = erl_parse:parse_exprs(Tokens). 
{ok,[{'fun',1,
            {clauses,[{clause,1,[],[],
                              [{call,1,
                                     {remote,1,{atom,1,io},{atom,1,format}},
                                     [{string,1,"blah~n"}]}]}]}}]}
14> 

【讨论】:

  • 我已经尝试过这样的东西。当我将它放入使用 erl_syntax 构建的 AST 中时,这根本不起作用。它让compile:forms() 呕吐...
  • @Gordon,我扩展了我的问题中的示例。当我使用erl_syntax 创建列表时,它可以完美运行。但不幸的是,用erl_parse 替换它不起作用。
猜你喜欢
  • 2020-07-24
  • 1970-01-01
  • 2018-09-10
  • 2010-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-15
相关资源
最近更新 更多