假设我有一个简单的语法:
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
错误的关联性。
任何人都可以解决这个问题吗?一般来说,如何编写一个手写的自上而下的解析器来保留语法中内置的关联性?
答案 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
。如果你有一些右关联运算符(例如取幂),你可以使用递归方法。