编译器中的递归下降解析和运算符优先级解析两种技术

递归下降解析

核心概念

递归下降解析是一种自顶向下的解析方法,为语法规则中的每个非终结符编写一个对应的递归函数。它直观地反映了语言的语法结构。

工作原理

// 以简单算术表达式为例:表达式 → 项 (('+' | '-') 项)*
// 对应的递归下降函数:

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;
    }
};

选择指南

  • 递归下降:适合解析语言的整体结构(语句、函数、类等)

  • 运算符优先级:专门用于处理复杂的表达式语法

这两种技术是现代编译器实现的基础,理解它们对于构建任何语言的编译器都至关重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值