[英]Recursive descent: infix to postfix conversion repeating operators
我们最近在 uni 的编程课程中了解到使用堆栈将中缀转换为后缀。 我有一段时间写我的解析器,所以我决定使用递归下降。 我正在关注: Recursive Descent Theodore Norvell 解析表达式。 这是他使用的语法:
E --> P {B P}
P --> v | "(" E ")" | U P
B --> "+" | "-" | "*" | "/" | "^"
U --> "-" | "+"
我曾尝试在 C 中实现这一点并且它有效。 但是,如果我给它以下输入,操作员像这样一个接一个地尾随:
---+-1-(-(2-1)+3)*-2
它输出这个:
---+-1.00 -2.00 1.00 - 3.00 + - -2.00 *
以下内容似乎是错误的:
- -2.00 *
应该是+ -2 * -
(基于我的堆栈实现) 我得到的另一个特殊结果是我得到的2+(2^4*(7+2^6))
:
2.00 2.00 4.00 ^ 7.00 2.00 + 6.00 ^* +
当我期望得到:
2.00 2.00 4.00 ^ 7.00 2.00 6.00 ^ + * +
我不确定,但我是否需要一个优先级攀爬解析器- 在链接的文章中也有建议。 然而,主要问题是我将如何简化尾随对操作```---+``? 任何帮助将不胜感激。 提前非常感谢。 仍然是这一切的初学者。
这是代码:
#include <stdio.h>
#include <stdlib.h>
void expr();
void term();
void match(int t);
void error();
void parseNumber();
//E --> P {B P}
//P --> v | "(" E ")" | U P
//B --> "+" | "-" | "*" | "/" | "^"
//U --> "-" | "+"
//
// Erecognizer is
// E()
// expect( end )
//
// E is
// P
// while next is a binary operator
// consume
// P
char lookahead;
int main() {
lookahead = getchar();
expr();
return 0;
}
// E is
// P
// while next is a binary operator
// consume
// P
void expr() {
term();
/* optimized by inlining rest() and removing recursive calls */
while (1) {
if (lookahead == '+') {
match('+');
term();
printf(" + ");
} else if (lookahead == '-') {
match('-');
term();
printf(" - ");
}else if (lookahead == '*') {
match('*');
term();
putchar('*');
} else if (lookahead == '/') {
match('/');
term();
putchar('/');
} else if (lookahead == '^') {
match('^');
term();
putchar('^');
}
else break;
}
}
// P is
// if next is a v
// consume
// else if next = "("
// consume
// E
// expect( ")" )
// else if next is a unary operator
// consume
// P
// else
// error
void term() {
if (isdigit(lookahead)) {
parseNumber();
// printf("lookahead at %c",lookahead);
} else if(lookahead =='('){
match('(');
expr();
match(')');
}
else if (lookahead =='-' ||lookahead =='+') {
char sign = lookahead;
match(lookahead);
(sign=='+'?putchar('+'):putchar('-'));
term();
}else {
error();
}
}
void match(int t) {
if (lookahead == t)
lookahead = getchar();
else error();
}
void parseNumber() {
double number = 0;
// TODO consume spaces
if (lookahead == '\0'|| lookahead=='\n') return;
while (lookahead >= '0' && lookahead <= '9') {
number = number * 10 + lookahead - '0';
match(lookahead);
}
if (lookahead == '.') {
match(lookahead);
double weight = 1;
while (lookahead >= '0' && lookahead <= '9') {
weight /= 10;
number = number + (lookahead - '0') * weight;
match(lookahead);
}
}
printf("%.2f ", number);
//printf("\ncurrent look ahead at after exiting parseNumber %c\n",lookahead);
}
void error() {
printf("Syntax error at lookahead %c\n",lookahead);
exit(1);
}
您引用的文章非常清楚地指出,提出的递归下降算法不是解析器:(强调添加)
让我们看一个基于这个语法的递归下降识别器。 我将此算法称为识别器而不是解析器,因为它所做的只是识别输入是否为语法语言。 它不会生成抽象语法树或任何其他形式的 output 表示输入内容。
这是绝对正确的; 该语法仅适用于识别器。 没有提到的是,如果您尝试修改算法以生成某种形式的 output(除了简单的“是”或“否”,表明表达式是否为目标语言),您将在结构上得到一个错误的答案。
那是因为这不是真的:
我们可以将 G 转换为等价的非左递归文法 G1……
或者至少,您需要非常小心“等效”的含义。 新语法是等效的,因为它识别相同的语言。 但它不会以相同的方式解析表达式,而且左递归消除算法会从语法中消除产生正确解析所必需的信息。 (在这种情况下,必要的信息——每个运算符的优先级和关联性——已经从文法中删除,大概是为了简化。但即使文法一开始是准确的,左递归删除也会被删除左结合运算符和右结合运算符之间的区别。)
在本演示文稿的稍后部分,在经典解决方案标题下,Norvell 描述了一个递归下降解析器,它可以正确解析表达式。 [注 1] 这可能是您想要编写的代码。
顺便说一句,您的 output 不是逆波兰表示法(并且没有括号也不是明确的),因为您在操作数之前是 output 一元运算符。 RPN 总是将运算符放在其操作数之后——这使得它在没有括号的情况下明确——并要求每个操作数明确指定它需要的操作数的数量。 这通常意味着以不同的方式编写一元和二元否定,以便可以将它们区分开来,尽管另一种选择是只给 output 一个额外的 0 操作数,并让 RPN 评估器将它们视为二元运算符。
但是 RPN 并不是一个非常有用的 output 从解析器。 来自解析器的常见 output 是抽象语法树,它是描述解析文本的句法结构的图结构。 另一种常见的 output 是所谓的“三地址码”,它是具有无限(或至少非常大)数量的寄存器的假想机器的虚拟机代码。 (并非所有的 VM 操作码都有三个地址,但其中很多都有,包括所有二进制算术运算符,它们命名两个源寄存器和一个目标寄存器。)当然,对于计算器,您可以像 go 一样计算产生任何结构化的表示。
如果 Norvell 选择了一个不那么特殊的优先顺序,也许最好说语法 G2 将正确解析表达式。 我们通常将一元否定运算符放在乘法和取幂之间,而不是放在加法和乘法之间。 只要你只实现乘法和精确除法,Norvell 的优先级选择无关紧要,但如果你实现地板除法或取模(即//
和%
的 Python 语义),你会发现低优先级一元否定会导致意想不到的评估。 这个错误是可能的,因为否定分布在乘法和精确除法上。 但是(-3) // 2
与-(3 // 2)
不同, -3 // 2
的预期结果是第一个,而 Norvell 的优先顺序产生第二个。
我应该补充一点,C 中的 integer 除法是截断除法,而不是地板除法,并且 C 的%
运算符是余数,而不是模数,因此 Z0D61F8370CAD1D412F80B84D1 的问题并不明显。 另一方面,C 缺少指数运算符,因此您可以使用 go 的更简单的解决方案,即给予一元否定比任何二元运算符更高的优先级,这就是 Z0D61F8370CAD1D4D412F80ZB84D1 所做的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.