递归下降解析
核心概念
递归下降解析是一种自顶向下的解析方法,为语法规则中的每个非终结符编写一个对应的递归函数。它直观地反映了语言的语法结构。
工作原理
// 以简单算术表达式为例:表达式 → 项 (('+' | '-') 项)*
// 对应的递归下降函数:
class RecursiveDescentParser {
Token currentToken;
Lexer lexer;
public:
// 表达式解析
double parseExpression() {
double result = parseTerm(); // 先解析项
while (currentToken.type == PLUS || currentToken.type == MINUS) {
Token op = currentToken;
getNextToken(); // 消费运算符
double right = parseTerm();
if (op.type == PLUS)
result += right;
else
result -= right;
}
return result;
}
// 项解析
double parseTerm() {
double result = parseFactor(); // 先解析因子
while (currentToken.type == MUL || currentToken.type == DIV) {
Token op = currentToken;
getNextToken(); // 消费运算符
double right = parseFactor();
if (op.type == MUL)
result *= right;
else
result /= right;
}
return result;
}
// 因子解析(数字或括号表达式)
double parseFactor() {
if (currentToken.type == NUMBER) {
double value = currentToken.value;
getNextToken();
return value;
}
else if (currentToken.type == LPAREN) {
getNextToken(); // 消费 '('
double result = parseExpression(); // 递归解析括号内的表达式
if (currentToken.type != RPAREN)
throw "期望 ')'";
getNextToken(); // 消费 ')'
return result;
}
else {
throw "期望数字或 '('";
}
}
};
解析过程示例
输入:2 + 3 * (4 - 1)
调用栈:
parseExpression()
→ parseTerm()
→ parseFactor() // 返回 2
→ 遇到 '+',继续 parseTerm()
→ parseFactor() // 返回 3
→ 遇到 '*',继续 parseFactor()
→ 遇到 '(',调用 parseExpression()
→ parseTerm() → parseFactor() // 返回 4
→ 遇到 '-',继续 parseTerm() → parseFactor() // 返回 1
→ 返回 4 - 1 = 3
→ 返回 3 * 3 = 9
→ 返回 2 + 9 = 11
优缺点
优点:
-
实现简单直观,代码易读
-
错误检测和恢复相对容易
-
天然支持上下文相关语法
-
适合手写编译器
缺点:
-
左递归文法会导致无限递归
-
可能需要回溯,效率较低
-
对某些复杂文法实现繁琐
运算符优先级解析
核心概念
专门为表达式解析设计的自底向上技术,通过比较运算符的优先级和结合性来决定规约顺序。
工作原理
class PrattParser {
Token currentToken;
Lexer lexer;
// 定义运算符优先级
unordered_map<TokenType, int> precedence = {
{PLUS, 10}, {MINUS, 10},
{MUL, 20}, {DIV, 20},
{POWER, 30} // 幂运算优先级最高
};
public:
double parseExpression(int minPrecedence = 0) {
// 解析左操作数
double left = parsePrimary();
while (true) {
Token op = currentToken;
int opPrec = getPrecedence(op.type);
// 如果当前运算符优先级不足,停止
if (opPrec < minPrecedence) break;
getNextToken(); // 消费运算符
// 处理右结合性(如幂运算)
int nextMinPrec = opPrec;
if (isRightAssociative(op.type)) {
nextMinPrec = opPrec - 1;
}
// 递归解析右操作数
double right = parseExpression(nextMinPrec);
// 应用运算
left = applyOperator(op, left, right);
}
return left;
}
double parsePrimary() {
if (currentToken.type == NUMBER) {
double value = currentToken.value;
getNextToken();
return value;
}
else if (currentToken.type == LPAREN) {
getNextToken(); // 消费 '('
double result = parseExpression(0);
if (currentToken.type != RPAREN)
throw "期望 ')'";
getNextToken(); // 消费 ')'
return result;
}
else {
throw "期望数字或 '('";
}
}
int getPrecedence(TokenType type) {
auto it = precedence.find(type);
return it != precedence.end() ? it->second : -1;
}
bool isRightAssociative(TokenType type) {
return type == POWER; // 只有幂运算是右结合
}
double applyOperator(Token op, double left, double right) {
switch (op.type) {
case PLUS: return left + right;
case MINUS: return left - right;
case MUL: return left * right;
case DIV: return left / right;
case POWER: return pow(left, right);
default: throw "未知运算符";
}
}
};
解析过程示例
输入:2 + 3 * 4 ^ 2 ^ 3
解析步骤:
初始: left = 2, 遇到 '+' (优先级10)
解析右侧: left = 3, 遇到 '*' (优先级20 > 10)
解析右侧: left = 4, 遇到 '^' (优先级30 > 20)
解析右侧: left = 2, 遇到 '^' (优先级30,右结合)
解析右侧: left = 3
计算: 2^3 = 8
计算: 4^8 = 65536
计算: 3*65536 = 196608
计算: 2+196608 = 196610
优缺点
优点:
-
高效处理复杂的运算符优先级和结合性
-
无需担心左递归问题
-
代码简洁,易于扩展新运算符
-
天然支持一元运算符
缺点:
-
主要适用于表达式解析
-
对非表达式语法支持有限
-
错误处理相对复杂
两种技术的对比
| 特性 | 递归下降 | 运算符优先级 |
|---|---|---|
| 解析方向 | 自顶向下 | 自底向上 |
| 适用场景 | 整个语言语法 | 主要表达式 |
| 左递归处理 | 需要改写文法 | 天然支持 |
| 优先级处理 | 通过嵌套函数调用 | 通过优先级表 |
| 实现复杂度 | 中等 | 简单(仅表达式) |
| 扩展性 | 好 | 很好(添加新运算符容易) |
实际应用建议
结合使用
在实际编译器中,通常结合使用两种技术:
class CombinedParser {
PrattParser exprParser; // 用于表达式
public:
// 使用递归下降解析语句结构
ASTNode* parseStatement() {
if (currentToken.type == IF)
return parseIfStatement();
else if (currentToken.type == WHILE)
return parseWhileStatement();
else
return parseExpressionStatement(); // 这里调用运算符优先级解析
}
// 表达式语句使用运算符优先级解析
ASTNode* parseExpressionStatement() {
ASTNode* expr = exprParser.parseExpression();
// ... 处理语句结束
return expr;
}
};
选择指南
-
递归下降:适合解析语言的整体结构(语句、函数、类等)
-
运算符优先级:专门用于处理复杂的表达式语法
这两种技术是现代编译器实现的基础,理解它们对于构建任何语言的编译器都至关重要。
1412

被折叠的 条评论
为什么被折叠?



