【问题标题】:Java - How to handle parse tree recursion for leaf nodes?Java - 如何处理叶节点的解析树递归?
【发布时间】:2018-06-06 18:43:02
【问题描述】:

首先,抱歉标题不好。我真的不知道该怎么称呼这个问题。

我正在尝试为我的 Turtle 图形程序编写一个解析器(如果您不知道它是什么,那么您基本上创建了一种由用于指导“乌龟”运动的命令组成的小型编程语言。例如,输入“FORW 5. LEFT 45.”将使海龟向前移动 5 步,然后向左旋转 45 度。)

我的 BNF 语法如下所示:

<EXPR>::= <CMD><EXPR> | <EOF>

<CMD>::=
  <FORW><INT><PERIOD>
| <BACK><INT><PERIOD>
| <LEFT><INT><PERIOD>
| <RIGHT><INT><PERIOD>
| <DOWN><PERIOD>
| <UP><PERIOD>
| <COLOR><HEX><PERIOD>
| <REP><INT><CMD>
| <REP><INT><QUOTE><EXPR><QUOTE>

(所有&lt;-.-&gt; 都是终端[令牌],&lt;EXPR&gt;&lt;CMD&gt; 除外。)

REP 命令重复一组命令(引号内的命令)X 次。例如:REP 3 "FORW 5. LEFT 45." 重复命令 FORW 5. LEFT 45. 3 次。

这是我在实现解析器时遇到问题的命令 (REP)(因为它的语法中包含非终结符)。

解析树:

// Ett syntaxträd
abstract class ParseTree {
    abstract public int evaluate();
}

// Ett syntaxträd som representerar ett tal
class ExprNode extends ParseTree {
    ParseTree left, right;
    public ExprNode(ParseTree left, ParseTree right) {
        this.left = left;
        this.right = right;
    }
    public int evaluate() {
        return 0;
    }
}

// Ett syntaxträd som representerar någon av de fyra binära operationerna
class CMDNode extends ParseTree {
    TokenType cmd;
    int num;
    String hex;
    ParseTree child;

    public CMDNode(TokenType cmd) {
        this.cmd = cmd;
    }

    public CMDNode(TokenType cmd, int num) {
        this.cmd = cmd;
        this.num = num;
    }

    public CMDNode(TokenType cmd, String hex) {
        this.cmd = cmd;
        this.hex = hex;
    }

    public CMDNode(TokenType cmd, ParseTree child) {
        this.cmd = cmd;
        this.child = child;
    }

    public int evaluate() {
        return 0;
    }
}

(抱歉 cmets 是瑞典语)

解析器:

/**
 * En rekursiv medåknings-parser för aritmetiska uttryck.
 * Se README för mer info.
 *
 * 
 */
public class Parser {
    private Lexer lexer;

    /** Variabler för att kunna ge pratig förklaring av vad som händer
     * i parsningen.  Om man inte har behov av denna feature kan koden
     * som relaterar till dessa variabler tas bort.
     */
    private boolean verbose;
    private int depth;

    /** Om verbose är satt till sann kommer Parsern att prata en massa
     * medans den gör sitt jobb.
     */
    public Parser(Lexer lexer, boolean verbose) {
        this.lexer = lexer;
        this.verbose = verbose;
    }

    private void talk(String s) {
        if (verbose)
            System.out.printf("%"+(3*depth+1)+"s%s\n", "", s);
    }

    public ParseTree parse() throws SyntaxError {
        // Startsymbol är Expr
        depth = 0;
        talk("Start parse()");
        ++depth;
        ParseTree result = Expr();
        // Borde inte finnas något kvar av indata när vi parsat ett uttryck
        if (lexer.nextToken().getType() != TokenType.EOF) {
            throw new SyntaxError();
        }
        return result;
    }

    private ParseTree Expr() throws SyntaxError {
        //talk("Enter Expr()");
        //++depth;
        ParseTree result = Cmd();
        //talk("[Expr()] Read cmd done");
        while (lexer.peekToken().getType() == TokenType.FORW ||
               lexer.peekToken().getType() == TokenType.BACK||
               lexer.peekToken().getType() == TokenType.LEFT||
               lexer.peekToken().getType() == TokenType.RIGHT||
               lexer.peekToken().getType() == TokenType.UP||
               lexer.peekToken().getType() == TokenType.DOWN||
               lexer.peekToken().getType() == TokenType.COLOR||
               lexer.peekToken().getType() == TokenType.REP) {
            ParseTree expression = Expr();
            //talk("[Expr()] Read operator " + operator);

            //talk("[Expr()] Read term done");
            result = new ExprNode(result, expression);
        }
        //--depth;
        //talk("Leave Expr()");
        return result;
    }

    private ParseTree Cmd() throws SyntaxError  {
        Token t = lexer.nextToken();
        if(t.getType() == TokenType.FORW || t.getType() == TokenType.BACK || t.getType() == TokenType.LEFT || t.getType() == TokenType.RIGHT) {
            Token num = lexer.nextToken();
            if(num.getType() != TokenType.DECIMAL) {
                throw new SyntaxError();
            }
            if(lexer.nextToken().getType() != TokenType.PERIOD) {
                throw new SyntaxError();
            }
            return new CMDNode(t.getType(), (Integer)num.getData());
        }
        else if(t.getType() == TokenType.UP || t.getType() == TokenType.DOWN) {
            if(lexer.nextToken().getType() != TokenType.PERIOD) {
                throw new SyntaxError();
            }
            return new CMDNode(t.getType());
        }
        else if(t.getType() == TokenType.COLOR) {
            Token hex = lexer.nextToken();
            if(hex.getType() != TokenType.HEX) {
                throw new SyntaxError();
            }
            if(lexer.nextToken().getType() != TokenType.PERIOD) {
                throw new SyntaxError();
            }
            return new CMDNode(t.getType(), (String)hex.getData());
        }
        else if(t.getType() == TokenType.REP) {
            Token num = lexer.nextToken();
            if(num.getType() != TokenType.DECIMAL) {
                throw new SyntaxError();
            }
            if(lexer.peekToken().getType() == TokenType.QUOTE) {
                Expr();
            }
            else {
                Cmd();
            }
        }
        else {
            throw new SyntaxError();
        }
        return null;

    }

}

我添加了所有代码,但最重要的部分(我正在努力处理的部分)在我尝试处理 REP 令牌时位于解析器代码的底部。

else if(t.getType() == TokenType.REP) {
            Token num = lexer.nextToken();
            if(num.getType() != TokenType.DECIMAL) {
                throw new SyntaxError();
            }
            if(lexer.peekToken().getType() == TokenType.QUOTE) {
                Expr();
            }
            else {
                Cmd();
            }

根据语法,REP 可以有两个“结果”。它可以仅重复一个命令 X 次或一组命令 X 次(我在 QUOTE 令牌的帮助下识别该集合)。但我不知道在第二个ifelse 语句中该做什么或写什么。我目前只是添加了对Expr() 函数或Cmd() 函数的递归调用,但我认为这是错误的。我认为这将创建第二个 Parse 树,而不是连接到当前 CMDNode 的节点。我不知道如何解决这个问题。

对于冗长而蹩脚的解释很抱歉,但我希望您已经理解问题所在。

顺便说一句,我之前没有太多使用树结构的经验,所以这个问题对你来说可能很愚蠢:)

谢谢!

【问题讨论】:

    标签: java parsing recursion syntax tree


    【解决方案1】:

    这是未经测试的伪代码,但应该足以帮助您继续开发。

            if(lexer.peekToken().getType() == TokenType.QUOTE) {
                // Here we know there will be one or more children and that the sequence starts and ends with a "quote command"
                List<ParseTree> children = new ArrayList<>();
                ParseTree child = Cmd(); // The initial "quote command" - just ignore
                while ((child = Cmd()) != TokenType.QUOTE) {
                    // Will stop looping when the second quote is found
                    children.add(child);
                }
                return new CMDNode(t.getType(), children); // Yes, you need to create a new constructor
            }
            else {
                // Here we know there will only one child
                ParseTree child = Cmd();
                return new CMDNode(t.getType(), child);
            }
    

    您还需要添加一个新的“引用命令”:

    // Existing code
    else if(t.getType() == TokenType.REP) {
         ...
    }
    // New code starts here
    else if(t.getType() == TokenType.QUOTE) {
        // This "command" is only used when parsing the input string.
        return new CMDNode(t.getType());
    }
    // Existing code again
    else {
        throw new SyntaxError();
    }
    

    【讨论】:

      猜你喜欢
      • 2021-08-15
      • 1970-01-01
      • 2018-12-31
      • 1970-01-01
      • 1970-01-01
      • 2017-02-24
      • 1970-01-01
      • 1970-01-01
      • 2016-08-07
      相关资源
      最近更新 更多