【问题标题】:Java Jacc, AST and recursive visitorJava Jacc、AST 和递归访问者
【发布时间】:2019-03-19 13:02:57
【问题描述】:

我正在尝试使用 Jacc(解析器生成器)制作一个简单的计算器。我首先需要创建一个 AST 并访问它的节点以制作它的 Graphviz 图,然后对其进行评估。

在我的 Jacc 文件中,我不能使用优先指令,所以我创建了一个左递归语法:

%{

    import java.io.*;
    import java.lang.Math;

%}

%class Parser
%interface ParserTokens

%semantic Expr

%token DOUBLE DIV MOD



%%

Calc : /* empty */
    | AddExpr                   { ast = new Calc($1); }
    ;

AddExpr : ModExpr
    | AddExpr '+' ModExpr       { $$ = new AddExpr($1, $3, "+"); }
    | AddExpr '-'   ModExpr     { $$ = new AddExpr($1, $3, "-"); }
    ;

ModExpr : IntDivExpr
    | ModExpr MOD IntDivExpr    { $$ = new ModExpr($1, $3); }
    ;

IntDivExpr : MultExpr
    | IntDivExpr DIV MultExpr   { $$ = new IntDivExpr($1, $3); }
    ;

MultExpr : UnaryExpr
    | MultExpr '*' UnaryExpr    { $$ = new MultExpr($1, $3, "*"); }
    | MultExpr '/' UnaryExpr    { $$ = new MultExpr($1, $3, "/"); }
    ;

UnaryExpr : ExpExpr
    | '-' UnaryExpr             { $$ = new UnaryExpr($2, "-"); }
    | '+' UnaryExpr             { $$ = new UnaryExpr($2, "+"); }
    ;

ExpExpr : Value                 
    | ExpExpr '^' Value         { $$ = new ExpExpr($1, $3); }
    ;

Value : DoubleLiteral           
    | '(' AddExpr ')'           { $$ = new Value($2); }
    ;

DoubleLiteral : DOUBLE          { $$ = $1; }
    ;


%%

    public Calc ast;

    private Lexer lexer;

    public Parser(Reader reader)
    {
        lexer = new Lexer(reader);
    }

    public void yyerror(String error)
    {
        System.err.println("Error: " + error);
    }

    public static void main(String args[])
    {
        System.out.println("Interactive evaluation:");

        Parser parser = new Parser(
            new InputStreamReader(System.in));

        DotVisitor visitor = new DotVisitor();

        parser.lexer.nextToken();
        parser.parse();

        visitor.visit(parser.ast);
        System.out.println(visitor.getOutput());
    }

Value 产生式可以是 DoubleLiteralAddExpr 以适应带括号的表达式,因此我创建了一个名为 Expr 的接口,使所有 AST 类都“实现”它:

interface Expr 
{
    public void accept(DotVisitor visitor);
}

abstract class BinExpr implements Expr
{
    protected Expr left;
    protected Expr right;
    protected String op;

    public Expr getLeft()
    {
        return left;
    }

    public Expr getRight()
    {
        return right;
    }

    public String getOp()
    {
        return op;
    }

    BinExpr(Expr left, Expr right, String op)
    {
        this.left = left;
        this.right = right;
        this.op = op;
    }

    public void accept(DotVisitor visitor)
    {
        visitor.visit(left);
        visitor.visit(right);
    }
}

class Calc implements Expr
{
    Expr ast;

    public Expr getAst()
    {
        return ast;
    }

    Calc(Expr ast)
    {
        this.ast = ast;
    }

    public void accept(DotVisitor visitor)
    {
        visitor.visit(ast);
    }
}

class AddExpr extends BinExpr
{
    AddExpr(Expr left, Expr right, String op)
    {
        super(left, right, op);
    }
}

class ModExpr extends BinExpr
{
    ModExpr(Expr left, Expr right)
    {
        super(left, right, "Mod");
    }
}

class IntDivExpr extends BinExpr
{
    IntDivExpr(Expr left, Expr right)
    {
        super(left, right, "IntDiv");
    }
}

class MultExpr extends BinExpr
{
    MultExpr(Expr left, Expr right, String op)
    {
        super(left, right, op);
    }
}

class UnaryExpr implements Expr
{
    private Expr value;
    private String sign;

    public Expr getValue()
    {
        return value;
    }

    UnaryExpr(Expr value, String sign)
    {
        this.value = value;
        this.sign = sign;
    }

    public void accept(DotVisitor visitor)
    {
        visitor.visit(value);
    }
}

class ExpExpr extends BinExpr
{
    ExpExpr(Expr left, Expr right)
    {
        super(left, right, "^");
    }
}

class Value implements Expr
{
    private Expr literal;

    private Expr getLiteral()
    {
        return literal;
    }

    Value(Expr literal)
    {
        this.literal = literal;
    }

    public void accept(DotVisitor visitor)
    {
        visitor.visit(literal);
    }
}

class DoubleLiteral implements Expr
{
    private String value;

    public String getValue()
    {
        return value;
    }

    DoubleLiteral(String value)
    {
        this.value = value;
    }

    public void accept(DotVisitor visitor)
    {
    }
}

因此,在我的访问者中,我需要从 Expr 转换为具体类:

public class DotVisitor implements Visitor
{
    private String output = "";

    public String getOutput()
    {
        return output;
    }

    public void visit(Expr expr)
    {
        if(expr instanceof AddExpr)
        {
            visit((AddExpr) expr);
        }
        else if (expr instanceof MultExpr)
        {
            visit((MultExpr) expr);
        }
        else if (expr instanceof DoubleLiteral)
        {
            visit((DoubleLiteral) expr);
        }
        // ...
    }

    public void visit(Calc calc)
    {
        output += "Calc\n";
        calc.accept(this);
    }

    public void visit(AddExpr expr)
    {
        output += "AddExpr\n";
        expr.accept(this);
    }

    public void visit(ModExpr expr)
    {
        output += "ModExpr\n";
        expr.accept(this);
    }

    public void visit(IntDivExpr expr)
    {
        output += "IntDivExpr\n";
        expr.accept(this);
    }

    public void visit(MultExpr expr)
    {
        output += "MultExpr\n";
        expr.accept(this);
    }

    public void visit(UnaryExpr expr)
    {
        output += "UnaryExpr\n";
        expr.accept(this);
    }

    public void visit(ExpExpr expr)
    {
        output += "ExpExpr\n";
        expr.accept(this);
    }

    public void visit(Value value)
    {
        output += "Value\n";
        value.accept(this);
    }

    public void visit(DoubleLiteral literal)
    {
        output += "DoubleLiteral: " + literal.getValue().toString() + "\n";
    }
}

我是否以错误的方式构建我的 AST?我误解了访客模式吗?强制转换为具体类型看起来既丑陋又错误。

【问题讨论】:

  • Java 中的类可以实现多个接口。您可以在适当的类中移动专门的访问者方法,使它们成为访问者接口的实现。所以你不再需要检查确切的类。
  • @MarioSantini 谢谢!你能用上面的例子扩展一下吗?我不确定我会怎么做。

标签: java parsing abstract-syntax-tree parser-generator visitor-pattern


【解决方案1】:

正如我在 Java 中的评论中提到的,一个类可以实现多个接口,因此您的 Expr 类也可以是 Visitor

这样做,您现在可以在专用节点内的DotVisitor 类中移动所有方法。

请看下面的例子:

class Calc implements Expr, Visitor
{
    Expr ast;

    public Expr getAst()
    {
        return ast;
    }

    Calc(Expr ast)
    {
        this.ast = ast;
    }

    public void accept(DotVisitor visitor)
    {
        visitor.visit(ast);
    }

    public void visit(Expr calc)
    {
        output += "Calc\n";
        calc.accept(this);
    }
}

注意现在访问参数 calcExpr 而不是类。

这样做您可以摆脱检查和投射对象的访问方法。

顺便说一句,它也应该与方法重载一起使用,但我认为从设计的角度来看,将代码放在适当的类附近会更有效。

如果你想添加新类型的节点,你只需要实现适当的类,并让解析器知道这些节点,程序的其他部分应该保持不变。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-25
    • 2016-10-24
    • 1970-01-01
    相关资源
    最近更新 更多