最简单的解决方案是C的设计者实际采用的解决方案,因此也受到各种C衍生物的影响:将赋值视为另一个操作符,而不将它限制在语句的顶层。因此,在C,以下是没有问题的:
while ((ch = getchar()) != EOF) { ... }
并不是所有人都会认为,良好的作风,但肯定是常见的(特别是在for
声明的条款,其语法或多或少需要分配成为一个表达)。
有两个小的并发症,这是比较容易做到:
逻辑上,而不像大多数运营商,转让联营权,使a = b = 0
被解析为a = (b = 0)
,而不是(a = b) = 0
(这将是非常意外)。它也非常弱,至少在正确的方面。
不同的意见应该如何紧密结合到左边。在C中,大多数情况下遵循严格的优先模型,因此a = 2 + b = 3
被解析为a = ((2 + b) = 3)
,因此被拒绝。 a = 2 + b = 3
看似可怕的风格,但也考虑a < b ? (x = a) : (y = a)
。在三元运算符的结果可以作为参考的C++中,可以将其写为(a < b ? x : y) = a
,即使认为赋值的优先级低于三元运算符,也需要括号。
虽然这些选项在语法中很难实现。
在许多语言中,作业的左侧具有受限制的语法。在具有参考价值的C++中,限制可以被认为是语义上的,我相信它通常是通过语义检查来实现的,但在许多C中,衍生词可以用语法来定义。这样的定义是明确的,但它们通常不适合用自顶向下的语法进行解析,即使对于自下而上的语法,它们也会产生复杂性。做检查后分析总是一个简单的解决方案。
如果你真的想从表达式语句区分赋值语句,然后你,如果你使用自上而下的分析技术,如递归下降这样的确碰上预测失败(不歧义)的问题。由于语法不是模棱两可的,因此一个简单的解决方案是使用LALR(1)解析器生成器,如bison/yacc,解析这样的语法没有问题,因为它不需要及早决定哪种类型的语句解析。总的来说,使用LALR(1)甚至GLR解析器生成器可以简化语法分析器的实现,它允许您指定一个容易阅读并对应于语法分析的形式的语法。 (例如,LALR(1)解析器可以自然处理左联合算子,而LL(1)语法只能生成右联合解析,因此需要对语法树进行某种重构。)
A递归下降解析器是一种计算机程序,而不是语法,因此它的表达性不受LL(1)语法的形式约束的限制。这既是一个优点,也是一个弱点:其优点是你可以找到不受LL(1)语法限制的解决方案;它的弱点在于它提取关于语言的精确语法的明确陈述要复杂得多(甚至有时是不可能的)。例如,这种权力允许递归下降语法以或多或少自然的方式处理左结合性,尽管上述限制。
如果你想走这条路,那么解决方案就够简单了。你将有某种功能:
/* This function parses and returns a single expression */
Node expr() {
Node left = value();
while (true) {
switch (lookahead) {
/* handle each possible operator token. I left out
* the detail of handling operator precedence since it's
* not relevant here
*/
case OP_PLUS: {
accept(lookahead);
left = MakeNode(OP_PLUS, left, value());
break;
}
/* If no operator found, return the current expression */
default:
return left;
}
}
}
这很容易被修改为能够解析表达式和语句。首先,重构函数,使其解析表达式的“休息”,给定第一个运算符。 (唯一的变化是一个新的原型,并在身体的第一行删除)
/* This function parses and returns a single expression
* after the first value has been parsed. The value must be
* passed as an argument.
*/
Node expr_rest(Node left) {
while (true) {
switch (lookahead) {
/* handle each possible operator token. I left out
* the detail of handling operator precedence since it's
* not relevant here
*/
case OP_PLUS: {
accept(lookahead);
left = MakeNode(OP_PLUS, left, value());
break;
}
/* If no operator found, return the current expression */
default:
return left;
}
}
}
有了到位,它是简单的同时实现expr
和stmt
:
Node expr() {
return expr_rest(value());
}
Node stmt() {
/* Check lookahead for statements which start with
* a keyword. Omitted for simplicity.
*/
/* either first value in an expr or target of assignment */
Node left = value();
switch (lookahead) {
case OP_ASSIGN:
accept(lookahead);
return MakeAssignment(left, expr())
}
/* Handle += and other mutating assignments if desired */
default: {
/* Not an assignment, just an expression */
return MakeExpressionStatement(expr_rest(left));
}
}
}
你的问题缺少一个重要的细节:你在寻找什么样的语法?你用什么工具来生成解析器? –
那么我不知道什么类型的语法(我们只讨论了EBNF,上下文无关)。我不会为此使用自动化工具。 – Dave
好吧,那么,你在为上下文无关语法实现什么样的解析器? –