困难的出现是因为用匹配规则的 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