在Dragon-book的附录中,给出了一个LL(1)前端作为例子。我认为这非常有帮助。但是,我发现对于下面的上下文无关语法,至少需要一个LL(2)解析器。
statement : variable ':=' expression
| functionCall
functionCall : ID'(' (expression ( ',' expression )*)? ')'
;
variable : ID
| ID'.'variable
| ID '[' expression ']'
;
我如何调整LL(1)解析器的词法分析器以支持k向前看令牌? 有一些优雅的方式吗?
我知道我可以为令牌添加一些缓冲区。我想讨论编程的一些细节。
这是Parser:
class Parser
{
private Lexer lex;
private Token look;
public Parser(Lexer l)
{
lex = l;
move();
}
private void move()
{
look = lex.scan();
}
}
并且Lexer.scan()
返回流中的下一个标记。
答案 0 :(得分:1)
实际上,您需要缓冲k
前瞻标记才能进行LL(k)
解析。如果k
为2,那么您只需要扩展当前方法,该方法使用另一个私有成员look
或其他类似的方式在look2
中缓冲一个标记。对于较大的k
,您可以使用环形缓冲区。
在实践中,你并不需要一直充分的前瞻。大多数时候,单一令牌前瞻就足够了。您应该将代码构造为决策树,只有在必要时才能查询未来的令牌以解决歧义。 (提供特殊的令牌类型通常很有用," unknown&#34 ;,可以将其分配给缓冲的令牌列表,以指示前瞻尚未到达该点。或者,您可以始终保持k
前瞻标记;对于手工构建的解析器,可以更简单。)
或者,您可以使用回退结构,只需尝试一种替代方案,如果不起作用,而不是报告语法错误,请将解析器和词法分析器的状态恢复到下一个替代方案。在此模型中,词法分析器将当前输入缓冲区位置作为显式参数,并且输入缓冲区需要可重新循环。但是,您可以使用前瞻缓冲区来有效地记忆词法分析器功能,这可以避免重绕和重新扫描。 (扫描通常足够快,偶尔重新扫描不重要,因此您可能希望推迟添加代码复杂性,直到您的分析表明它有用。)
两个注释:
1)我对这条规则持怀疑态度:
functionCall : ID'(' (expression ( ',' expression )*)* ')'
;
这将允许,例如:
function(a[3], b[2] c[x] d[y], e.foo)
对我来说并不合适。通常,您可以将()
的内容标记为可选而不是可重复,例如。使用可选标记?而不是第二个Kleene星 * :
functionCall : ID'(' (expression ( ',' expression )*)? ')'
;
2)在我看来,你真的应该考虑对表达式语言使用自下而上的解析,无论是生成的LR(1)
解析器还是手工构建的Pratt解析器。 LL(1)
很少足够。当然,如果你正在使用解析器生成器,你可以使用像ANTLR这样有效实现LL(∞)
的工具;这将照顾你的先行。