手写自上而下的解析器,保留语法关联性/优先级?

时间:2013-03-06 18:59:35

标签: parsing

假设我有一个简单的语法:

X -> number
T -> X
T -> T + X

因此,例如3 + 4 + 5将解析为:

     +
    / \ 
   +   5
 /  \
3    4

左右关联性+“内置于”语法中。

它很简单LR(1),但是假设我想用手写自上而下的解析它。

我不能这样做,因为它是递归的,所以让我们留下因素:

X  -> number
T  -> X T'
T' -> + X T'
T' -> e    // empty

如果我现在为它编写一个解析器(伪代码):

parse_X:
    if lookahead is number
        return pop_lookahead

parse_T:
    return (parse_X, parse_T')

parse_T':
    if lookahead is +
         pop_lookahead
         return (parse_X, parse_T')
    else
         return ();

然后当我在parse_T的输入上调用3 + 4 + 5时,我得到了一条跟踪:

parse_T
(parse_X, parse_T')
(3, parse_T')
(3, (parse_X, parse_T'))
(3, (4, parse_T'))
(3, (4, (parse_X, parse_T')))
(3, (4, (5, ())))

查看解析是如何“向后”的。从这样的解析中“天真地”构造的树看起来像:

     +
    / \ 
   3   +
      / \
     4    5

错误的关联性。

任何人都可以解决这个问题吗?一般来说,如何编写一个手写的自上而下的解析器来保留语法中内置的关联性?

1 个答案:

答案 0 :(得分:2)

一种策略是使用T上的迭代替换X上的递归(一般来说,通过迭代超过下一个最高优先级运算符替换运算符上的递归)。它有助于使用EBNF风格的表示法,在这种情况下

T -> X {+ X}

因为那时所需的迭代变得很明显:

parse_T:
  val = parse_X
  while lookahead is +
     pop_lookahead
     val = PLUS(val, parse_X)
  return val

其中PLUS()表示您为评估附加表达式所做的任何操作,例如构造一个树节点。

如果你将它应用于所有操作符,你最终会得到一个与EXPRESSION相对应的函数,它只在处理

时进行递归
PRIMARY -> '(' EXPRESSION ')'

这种方法导致了一个相当快的表达式解析器;使用朴素递归下降来解析表达式的一个常见异议是,如果你有几个级别的运算符优先级,你很容易需要20个左右的嵌套函数调用来解析每个PRIMARY,这可能会相当慢。使用迭代方法,它通常只需要一个函数调用PRIMARY。如果你有一些右关联运算符(例如取幂),你可以使用递归方法。