如何手动构建AST?

hor*_*guy 8 ruby parsing abstract-syntax-tree lexer ll-grammar

我目前正在学习解析,但我对如何生成AST感到有点困惑.我编写了一个解析器,可以正确地验证表达式是否符合语法(当表达式符合时它是静默的,而当表达式不符合时引发异常).我从哪里开始构建AST?我发现了很多关于构建我的LL(1)解析器的信息,但是很少有人继续构建AST.

我的当前代码(用非常简单的Ruby编写,包括词法分析器和解析器)可以在github上找到:https://gist.github.com/e9d4081b7d3409e30a57

有人能解释我如何从目前的AST到AST吗?

或者,如果您不熟悉Ruby,但知道C,您能告诉我如何在递归下降解析维基百科文章中为C代码构建AST .

请注意,我不想使用像yacc或antlr这样的解析器生成器为我做的工作,我想从头开始做所有事情.

谢谢!

d11*_*wtq 6

您需要将与匹配的每个符号与构造树的一小部分的回调相关联.例如,让我们采用一个相当常见的结构:嵌套函数调用.

a(b())
Run Code Online (Sandbox Code Playgroud)

你的终端令牌在这里是这样的:

  • L_PAREN ='(''
  • R_PAREN =')'
  • IDENTIFIER = [az] +

而你的非终结符号是这样的:

  • FUNCTION_CALL = IDENTIFIER,L_PAREN,R_PAREN
  • 要么;
  • FUNCTION_CALL = IDENTIFIER,L_PAREN,FUNCTION_CALL,R_PAREN

显然,规则的上述第二种选择FUNCTION_CALL是递归的.

您已经有一个解析器知道它找到了一个有效的符号.您缺少的一点是将回调附加到规则,该规则接收其组件作为输入并返回表示AST中该节点的值(通常).

想象一下,如果我们FUNCTION_CALL上面的规则中的第一个替代方案有回调:

Proc.new do |id_tok, l_paren_tok, r_paren_tok|
  { item: :function_call, name: id_tok, args: [] }
end
Run Code Online (Sandbox Code Playgroud)

这意味着匹配产生的AST:

a()
Run Code Online (Sandbox Code Playgroud)

将会:

{
  item: :function_call,
  name: "a",
  args: []
}
Run Code Online (Sandbox Code Playgroud)

现在将其推断为更复杂的a(b()).因为解析器是递归的,所以它将识别第b()一个,回调从中返回我们上面的内容,但是使用"b"而不是"a".

现在让我们定义附加到与第二个备选项匹配的规则的回调.它非常相似,除了它还处理它传递的参数:

Proc.new do |id_tok, l_paren_tok, func_call_item, r_paren_tok|
  { item: :function_call, name: id_tok, args: [ func_call_item ] }
end
Run Code Online (Sandbox Code Playgroud)

因为解析器已经识别b()并且从回调中返回了AST的一部分,所以生成的树现在是:

{
  item: :function_call,
  name: "a",
  args: [
    {
      item: :function_call,
      name: "b",
      args: []
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

希望这会给你一些思考的食物.将您匹配的所有令牌传递给构建AST非常小的部分的例程.