Java Jacc,AST和递归访问者

时间:2019-03-19 13:02:57

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

我正在尝试使用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吗?我会误解访客模式吗?强制转换为具体类型似乎是错误和错误的。

1 个答案:

答案 0 :(得分: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);
    }
}

请注意,现在访问参数 calc Expr而不是类。

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

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

如果要添加新类型的节点,只需实现适当的类,并让解析器了解此类节点,程序的其他部分应保持不变。