如何将包含有效Erlang表达式的字符串转换为抽象语法树(AST)?

10
我想把一个包含有效 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如下会生成一个巨大的字典-图错误怪物,并显示“lint_module中的内部错误”。

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

编辑2:

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

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

现在我看代码时,显然混淆了erl_syntax和erl_parse格式...但仍然无法弄清楚如何做到这一点(典型的太多bejgli错误)。 - Zed
是的,如果你将你的ListAST与erl_syntax生成的进行比较,它们看起来并不相似 :(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},{list,[1,2,3],[]}} 44> - Gordon Guthrie
所以,我需要一种将字符串转换为“erl_syntax”兼容AST的方法,或者一种在“erl_syntax”中放置占位符并在调用“revert()”后替换它的方法。或者我可能忽略了什么明显的东西... - Zed
请添加来自erl_syntax手册的以下注释:“这意味着所有erl_parse树都是有效的抽象语法树...” - Zed
不,我只是认为它的标签不正确,所以你正在输入 [{cons, 1, ...]],而它期望的是 {tree, something},其中 something 与 [{cons, 1, ...}] 有关 :( - Gordon Guthrie
显示剩余2条评论
3个回答

5
在你的编辑示例中:
String = "[1,2,3].",
{ok, Ts, _} = erl_scan:string(String),
{ok, ListAST} = erl_parse:parse_exprs(Ts),

ListAST实际上是AST的列表(因为parse_exprs(如其名称所示)解析多个表达式(每个以句号结尾)。由于您的字符串只包含一个表达式,因此您得到了一个只有一个元素的列表。您需要做的就是匹配出来:
{ok, [ListAST]} = erl_parse:parse_exprs(Ts),

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


谢谢Richard!我想我应该自己想出来... :\ - Zed

3

我脑海中有一些评论。

我并没有真正使用过 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并不是微不足道的。您打算生成什么类型的代码? 如果您不怕列表,可以尝试使用LFE(lisp flavoured erlang)生成代码。与所有lisp一样,没有特殊的抽象形式,它都是同构的,更容易使用。

感谢您的回答,Robert。与此同时,我已经从Richard那里得到了答案:erl_parse树可以混合到erl_syntax树中。然后调用 erl_syntax:revert() 创建一个干净的erl_parse树混合体。我的唯一错误是没有注意到 erl_parse:parse_exprs() 的结果被包装在列表中... - Zed
起初,我也选择在临时文件中构建源代码,并按照通常的方式进行编译。现在我改为构建一个iolist(),并在其中使用parse_forms,这样一切都在内存中完成。不幸的是,我失去了一些很好的功能,比如code:get_object_code、beam_lib:get_chunks和hipe:compile,但我可以接受这一点。 - Zed
顺便玩一下用模板文件生成模块,并允许在模板中使用 Erlang 代码。 - Zed

2

Zoltan

这是我们获取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()出错... - Zed
@Gordon,我在我的问题中扩展了示例。当我使用erl_syntax创建列表时,它完美地工作。但是不幸的是,用erl_parse替换它并没有起作用。 - Zed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接