【问题标题】:Java: Understanding a Recursive Descent Parser ImplementationJava:了解递归下降解析器实现
【发布时间】:2017-10-23 13:41:25
【问题描述】:

假设我们有一个简单的语法:

  1. 程序 ::= 表达式
  2. 表达式 ::= 数字
  3. ::= - ( 表达式 , 表达式 )

使用这个表达式:-(-(8,3)4)
返回 1。

我的令牌流(我将括号和逗号拼接出来)看起来像这样
(减号 -)
(减号 -)
(整数 8)
(整数 3)
(整数 4)

所以 AST 看起来像这样
. . -
. - 。 4
8..3

我的问题是,关于语法的递归性质。考虑到差异表达式有 2 个评估表达式,java 示例将如何工作。

我尝试将表达式传递给类构造函数,如下所示:

public class DiffExp implements LetLangExp {
  LetLangExp left, right;

  public DiffExp(LetLangExp l, LetLangExp r) {
    left = l;
    right = r;
    eval();
  }
}

这仅适用于 -(number,number) 的差异表达式,但递归它不起作用,因为我似乎无法理解解析它的递归性质。我被这个例子困住了,我在网上看过,但我似乎无法将这种语法与我所见过的任何东西模棱两可。

基本上我如何实现一个递归处理的差异表达式,它可以将差异表达式作为操作数并相应地计算?

编辑:根据 Markspace 的要求,我正在尝试为解析树构建节点结构。这是我现在上的课。

class ExprNode{
String c;
static String operator;
static ExprNode operand1;
static ExprNode operand2;

public ExprNode(String num){
    c = num;
    operand1 = operand2 = null;
}

public static void Expr(String op, ExprNode e1, ExprNode e2){
    operator = op;
    operand1 = e1;
    operand2 = e2;
}
}

【问题讨论】:

  • 在解析所有子节点之前,我看不到如何eval() 表达式,所以我很难看到在ctor 中调用eval() 是个好主意。
  • 这就是我正在努力解决的问题,当我将令牌解析为递归表示时,我不知道如何跟踪它们。如果可能有帮助,我已经使用令牌流输出编辑了我的帖子。但据我所知,在评估它之前,我需要先将其构建为树结构?
  • 我完全不确定你在“挣扎”什么。制作一棵树并将节点/表达式放入其中。你能做到那么多吗?请显示执行此操作的代码。
  • -(-(8,3)4) 根据您的语法不是有效的表达式。是不是少了一个逗号?
  • 规则 3 中有一个逗号,它不是表达式的有效表示。

标签: java parsing recursion recursive-descent


【解决方案1】:

看起来不错,但您需要将树构建和评估分开:

public class DiffExp implements LetLangExp {
  LetLangExp left, right;

  public DiffExp(LetLangExp l, LetLangExp r) {
    left = l;
    right = r;
  }

  public double eval() {
    return left.eval() - right.eval();
  }
}

附言解析应该大致如下:

LetLangExpr parseProgram(LinkedList<String> tokens) {
  return parseExpression(tokens);
}

LetLangExpr parseExpression(LinkedList<String> tokens) {
  if ("-".equals(tokenStream.peekFirst())) {
    return parseDiff(tokens);
  } else {
    return parseNumber(tokens);
  }
}

LetLangExpr parseDiff(LinkedList<String> tokens) {
  tokens.pollFirst();  // Consume "-"
  LetLangExpr left = parseExpression(tokens);
  LetLangExpr right = parseExpression(tokens);
  return new DiffExpr(left, right);
}

LetLangExpr parseNumber(LinkedList<String> tokens) {
  String numberStr = tokens.pollFirs();
  double number = Double.parseDouble(numberStr);
  return new NumberExpr(number);
}

【讨论】:

  • 我已经非常准确地定义了该类。每当我需要通过解析标记来构建树时,问题就出现了,因为它再次获得了 NonTerminds(IE DiffExp) 的表达式。所以,本质上,当我到达内部 diffexp 时,我如何确保我知道我得到的标记是它的内部标记?
  • 通常情况下,令牌流会随着您的使用而被消耗,并且当前位置是其状态的一部分。这能回答问题吗?我已经稍微扩展了答案以涵盖解析,但是没有看到您的 TokenStream 接口,它不可能是准确的。
  • 我现在用的是LinkedList作为token流,供参考。
  • 在返回值上,如果这些规则(?)是 ExprNode 类型,而我的 DiffExp 是 LetLangExp 类型,那么表达式的返回将如何验证节点?
  • 已删除 ExprNode,并没有立即意识到它应该用于与 LetLangExpr / DiffExpr 无关的替代方法。
【解决方案2】:

您应该为语法中的每个规则创建方法,例如:

parseProgram(String program) {
  return parseExpression(program)
}

parseExpression(String expression) {

  if ( isNumber(expression) ) {
    return parseNumber(expression);
  } else 
  if ( isSignedExpression(expression) ) {
    String left = getLeftExpression(expression);
    String right = getRightExpression(expression);

    return parseExpression(left) - parseExpression(right);
  } 

}

parseNumber(String number) {
  parsedNumber = ...
  return parsedNumber;
}

【讨论】:

  • 我在上面编辑了我的帖子以包含我的令牌流,所以在这个例子中,如果我正在解析然后需要调用表达式和评估的令牌,我将如何在递归之后跟踪令牌打电话?
  • 您可以将令牌放入队列中。您查看队列的头部并决定应用哪个规则,删除队列的头部并将队列的其余部分传递给相应规则的方法。您只需按顺序使用令牌,递归会为您处理当前在树中的位置以及如何遍历它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-07
  • 1970-01-01
  • 2012-05-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多