📜  递归预测下降解析器和非递归预测下降解析器的区别(1)

📅  最后修改于: 2023-12-03 15:12:22.449000             🧑  作者: Mango

递归预测下降解析器和非递归预测下降解析器的区别

在编译原理中,语法分析阶段是将源代码解析成抽象语法树的过程。其中,语法分析的算法有很多种,本文将介绍两种常见的语法分析算法:递归预测下降解析器和非递归预测下降解析器。两者的主要区别在于它们的实现方式和效率。

递归预测下降解析器

递归预测下降解析器是基于递归下降算法的语法分析器。它是一种简单直观的语法分析算法,容易手动实现,并且对于一些小型语言,它可以很好地工作。其实现方式是:将语法规则转化为对应的递归过程,每个过程对应一条语法规则。在执行过程中,按照语法规则的顺序,递归调用对应的过程,直到匹配源代码。以下是一个递归预测下降解析器的简单示例代码:

class Parser {
  private Token lookahead;

  public Parser(Token lookahead) {
    this.lookahead = lookahead;
  }

  public void match(TokenType type) {
    if (lookahead.getType() == type) {
      lookahead = getNextToken();
    } else {
      throw new SyntaxError();
    }
  }

  public void expr() {
    term();
    while (lookahead.getType() == TokenType.PLUS || lookahead.getType() == TokenType.MINUS) {
      Token op = lookahead;
      match(lookahead.getType());
      term();
      // do something with op
    }
  }

  public void term() {
    factor();
    while (lookahead.getType() == TokenType.MULT || lookahead.getType() == TokenType.DIV) {
      Token op = lookahead;
      match(lookahead.getType());
      factor();
      // do something with op
    }
  }

  public void factor() {
    if (lookahead.getType() == TokenType.NUMBER) {
      match(TokenType.NUMBER);
    } else if (lookahead.getType() == TokenType.LPAREN) {
      match(TokenType.LPAREN);
      expr();
      match(TokenType.RPAREN);
    } else {
      throw new SyntaxError();
    }
  }
}

上面的代码是一个简单的四则运算表达式的语法分析器。它依赖于一个 Scanner 类,它能够返回一个 Token 流,这里省略了 Scanner 类的实现。

非递归预测下降解析器

非递归预测下降解析器是一种更加通用的语法分析算法,它是基于 LL(k) 文法的语法分析器。它的实现方式是使用一个预测分析表(predictive parsing table),根据当前的符号和向前看符号(lookahead symbol)来选择对应的产生式。需要注意的是,LL(k) 文法是一类确定性上下文无关文法,即对于每个输入串,文法都有唯一的左推导(leftmost derivation),因此它可以通过一趟扫描完成语法分析。以下是一个非递归预测下降解析器的简单示例代码:

class Parser {
  private Token lookahead;
  private Map<EnumSet<TokenType>, Production> parseTable;

  public Parser(Token lookahead, Map<EnumSet<TokenType>, Production> parseTable) {
    this.lookahead = lookahead;
    this.parseTable = parseTable;
  }

  public void match(TokenType type) {
    if (lookahead.getType() == type) {
      lookahead = getNextToken();
    } else {
      throw new SyntaxError();
    }
  }

  public void parse() {
    Stack<TokenType> stack = new Stack<>();
    stack.push(TokenType.EOF);
    stack.push(ProductionType.START);

    while (!stack.empty()) {
      ProductionType top = stack.pop();

      if (top.isTerminal()) {
        TokenType type = top.getTokenType();
        if (lookahead.getType() == type) {
          lookahead = getNextToken();
        } else {
          throw new SyntaxError();
        }
      } else {
        EnumSet<TokenType> lookaheadSet = getLookaheadSet(top);
        Production production = parseTable.get(lookaheadSet);
        if (production == null) {
          throw new SyntaxError();
        }
        for (int i = production.getRight().size() - 1; i >= 0; i--) {
          stack.push(production.getRight().get(i));
        }
      }
    }
  }

  private EnumSet<TokenType> getLookaheadSet(ProductionType type) {
    // return a set of token types that may follow type
  }
}

上面的代码是一个简单的非终极符 LL(k) 文法的语法分析器。它依赖于一个 Scanner 类,它能够返回一个 Token 流,并且还依赖于一个预测分析表。预测分析表是一个以非终结符和向前看符号为索引,以产生式为值的哈希表,它可以在 LL(k) 文法的解析过程中快速确定下一个产生式。

总结

递归预测下降解析器和非递归预测下降解析器是两种常见的语法分析算法。递归预测下降解析器是基于递归下降算法的语法分析器,它简单直观,容易手动实现。非递归预测下降解析器是更加通用的语法分析算法,它是基于 LL(k) 文法的语法分析器,可以通过预测分析表快速确定下一个产生式。需要注意的是,LL(k) 文法必须是一类确定性上下文无关文法,即对于每个输入串,文法都有唯一的左推导。在实际使用中,可以根据语言的复杂程度和实现效率的需求选择合适的语法分析算法。