获取整个文件/复杂代码的AST

3

Julia手册中指出:

Every Julia program starts life as a string:

julia> prog = "1 + 1"
"1 + 1"

借助quote/code_* 或使用Meta.parse/Meta.show_sexpr,我可以轻松地获得简单表达式或甚至函数的AST,如果我有一个字符串中的表达式。

问题是:是否有任何方法可以获取代码块的整个AST,可能包括多个原子表达式?就像读取源文件并将其转换为AST那样?


你所说的“整个AST”究竟是什么意思?:(1 + 1)已经是最完整的了。你是想递归地展开+到子AST吗?(如果是,那并不是很可行)。 - phipsgabler
@phipsgabler 是的,我指的是从互联网上获取随机文件并将其AST转储。你能指导我一些......嗯资源吗?解释一下为什么它实际上不起作用? - Aleksei Matiushkin
如果你正在谈论文件和互联网,我认为我们在谈论不同的事情。我的意思是,你不能通过AST从1 + 1Core.add_int(1, 1),因为AST存在于类型推断之前,只有在类型推断之后,才知道将调用哪个+方法。 - phipsgabler
啊,你想在一个包含多个表达式的源文件上调用名为“parse”的东西吗? - phipsgabler
一个令人惊讶的难题 :) 在FemtoLisp中有jl-parse-file,你可以在julia --lisp中调用它,但我不知道如何从Julia内部执行它。也许可以在C中编写jl_parse_eval_all的变体。 - phipsgabler
显示剩余3条评论
2个回答

5

如果你想使用Julia而不是FemtoLisp来完成这个操作,你可以这样做

function parse_file(path::AbstractString)
    code = read(path, String)
    Meta.parse("begin $code end")
end

这个函数接收一个文件路径,读取并解析成一个可求值的大表达式。
这个函数来自@NHDaly在这里的回答: https://dev59.com/BqXja4cB1Zd3GeqPQWR6#54317201 如果你已经有了文件字符串,不想再次读取文件,则可以使用以下方式:
parse_all(code::AbstractString) = Meta.parse("begin $code end")

Nathan Daly和Taine Zhao在Slack上指出,这段代码无法用于模块:

julia> eval(parse_all("module M x = 1 end"))
ERROR: syntax: "module" expression not at top level
Stacktrace:
 [1] top-level scope at REPL[50]:1
 [2] eval at ./boot.jl:331 [inlined]
 [3] eval(::Expr) at ./client.jl:449
 [4] |>(::Expr, ::typeof(eval)) at ./operators.jl:823
 [5] top-level scope at REPL[50]:1

这可以通过以下方式修复:

julia> eval_all(ex::Expr) = ex.head == :block ? for e in ex eval_all(e) end : eval(e);

julia> eval_all(ex::Expr) = ex.head == :block ? eval.(ex.args) : eval(e);

julia> eval_all(parse_all("module M x = 1 end"));

julia> M.x
1

由于问题提出者不相信上述代码能够生成树形结构,因此这里展示了parse_all的输出结果的图形表示,清晰地显示了树形结构。

enter image description here

如果你好奇的话,那些标记为#= none:1 =#的叶子节点是行号节点,表示每个后续表达式所在的行。

正如评论中建议的那样,人们还可以将Meta.show_sexpr应用于Expr对象,以获得AST更加“lispy”的表示形式,而不需要默认情况下Julia进行的所有漂亮打印:

julia> (Meta.show_sexpr ∘ Meta.parse)("begin x = 1\n y = 2\n z = √(x^2 + y^2)\n end")
(:block,
  :(#= none:1 =#),
  (:(=), :x, 1),
  :(#= none:2 =#),
  (:(=), :y, 2),
  :(#= none:3 =#),
  (:(=), :z, (:call, :√, (:call, :+, (:call, :^, :x, 2), (:call, :^, :y, 2))))
)

1
这是一个包含您文件中所有代码的Expr。这是Julia的AST。 - Mason
2
那个结构不是树的哪一方面? - Mason
1
我必须在这里支持Mason。 Meta.parse(“begin \ nimport LinearAlgebra \ nx = 1 \n f(x)= sin(x)+ x \ nend”)|> Meta.show_sexpr - Expr显然是树形结构,只是以漂亮的方式打印出来。 - phipsgabler
1
实际上,这与Lisp所提供的相同:(jl-parse-all "begin\nimport LinearAlgebra\nx = 1\n f(x) = sin(x) + x\nend" "dummy"),除了包装器。 - phipsgabler
1
是的,@AlekseiMatiushkin,Julia的AST是一个由Expr和字面量组成的树: “前端AST几乎完全由Expr和原子(例如符号、数字)组成。” https://docs.julialang.org/en/v1/devdocs/ast/#Surface-syntax-AST-1 - NHDaly
显示剩余4条评论

1
在Julia解析器的FemtoLisp实现中有jl-parse-file。你可以从Lisp REPL (julia --lisp)中调用它,它会返回整个文件的S表达式。由于Julia的Expr与Lisp S表达式相差不大,这可能已经足够满足您的需求。
我仍然想知道如何从Julia中访问此结果。如果我理解正确,Lisp函数是未从libjulia导出的,因此没有直接使用ccall的方法。但是可能可以实现jl_parse_eval_all的变体。

1
在我的使用案例中,我不会从 Julia 中访问结果。我正在考虑在 ErlangVM 中运行 Julia,最自然的方式不是转译源代码,而是映射 AST。因此,我的目的是将 Julia 源代码→AST 仅限于写入。我仍然记得双向映射的想法,所以如果您找到了合理的解决方案,请回复我。再次感谢。 - Aleksei Matiushkin

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