LL(1)解析器用堆栈实现:如何构建AST?

时间:2013-11-22 19:42:32

标签: parsing implementation context-free-grammar ll

我目前正在手工构建解析器。它是LL(1)解析器。目前,它是一个很好的识别器:它的函数解析(List tokens)决定令牌是否是该语言的成员。

现在,我想为该输入构建相应的AST。但是,我知道如何以递归下降的方式实现它(已经做到了)。也就是说,对于挑战,我使用经典算法的堆栈实现我的堆栈:

next <- first token of the input
stack <- START_SYMBOL
do {
    top <- stack.pop()
    if (top is a terminal and top == next) {
        next <- next token of the input
    } else if (top is a non terminal and PARSING_TABLE[top, next] exists) {
        stack.push(PARSING_TABLE[top, next]);
    } else {
         return invalid input;
    }
} while (stack is not empty);
return valid input;

其中PARSING_TABLE是LL(1)表。但是,我想知道如何在这样的配置中实现构建AST的部分。我不要求完整的实现,更多的是实现的想法。

谢谢!

2 个答案:

答案 0 :(得分:1)

您的堆栈可以注释,以便它包含AST条目引用(即规则号+规则中的位置+目标数据存储位置)+(终端/非终端)

注释您的initial stack <- START_SYMBOL将其结果存储在AST根目录中。

基本上,您的pop()选择当前的AST构造。然后next <- next token保存AST中的值。 stack.push(PARSING_TABLE[top, next]);打开一个新的AST列表并将其写入与top对应的构造中,并在堆栈的每个条目中生成“规则编号+位置+目标列表”。

解析完成后,您将拥有整个树。

在精确的AST中,您可能想忽略一些令牌。这可以通过push()部分期间堆栈集中的适当注释来完成。典型的方法是将一些元信息附加到您的每个规则(A - > B C),例如,要保留的内容以及结果的性质。

答案 1 :(得分:0)

出现困难是因为用匹配规则的rhs替换堆栈上的非终结符的常用方法在其预测的时刻有效地忘记了语法结构。但要生成AST,您需要在完成规则解析后再使用该结构。

不是用匹配规则的rhs符号替换非终结符,而是将其保留在原位并将匹配的符号作为列表对象推送。这样,堆栈保留了语法的层次结构。

解析消耗最顶层列表中的符号。列表的耗尽对应于规则的完成。非规则在规则完成时从堆栈中删除,而不是在预测时。

当堆栈被消耗时,构建一个推论AST结构,该结构记住相关规则并存储解析的标记。因此,堆栈就像预测的AST一样流入解析的AST。

您可以将此视为模拟递归下降解析器的调用层次结构,并将符号列表堆栈作为一组调用帧。

在红宝石中:

# g is the grammar; a list of rules
# s is a terminal sequence to parse
# note, this code does not tokenize EOF

def parse(g, s)

 tab = gen_table(g)
 stack = [[g.start_sym]]
 # intermediate ast node format: [rule-action, symbols...]
 ast = [[->(rhs){[:_S, rhs[0]]}]]

 loop do
  puts "PARSE\n #{s}\n #{stack}\n #{ast}"

  if stack.first.empty?
   raise "extraneous input" if not s.empty?
   break
  end

  if stack.last.empty? # rule complete
   stack.pop
   node = ast.pop
   # transform the node (eg to a class) using the associated rule-action
   node = node.first.(node.drop(1))
   ast.last.push(node)
   stack.last.shift # rm sym from stack after completing it
   next
  end

  raise "incomplete input" if s.empty?
  tok = s.first
  topsym = stack.last.first

  if topsym.is_a? String # terminal
   raise "mismatch #{tok} != #{topsym}" if tok != topsym
   stack.last.shift
   ast.last.push(s.shift)

  elsif topsym.is_a? Symbol # nonterminal
   ri = tab[topsym][tok]
   raise "no rule for #{topsym}, #{tok}" if ri.nil?
   stack.push(g[ri].rhs.clone)
   ast.push([g[ri].action])
  end

 end

 node = ast.first
 node.first.(node.drop(1))
end