Java Jacc、AST 和递归访问者
Java Jacc, AST and recursive visitor
我正在尝试使用 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(); }
;
AddExpr : ModExpr
| AddExpr '+' ModExpr { $$ = new AddExpr(, , "+"); }
| AddExpr '-' ModExpr { $$ = new AddExpr(, , "-"); }
;
ModExpr : IntDivExpr
| ModExpr MOD IntDivExpr { $$ = new ModExpr(, ); }
;
IntDivExpr : MultExpr
| IntDivExpr DIV MultExpr { $$ = new IntDivExpr(, ); }
;
MultExpr : UnaryExpr
| MultExpr '*' UnaryExpr { $$ = new MultExpr(, , "*"); }
| MultExpr '/' UnaryExpr { $$ = new MultExpr(, , "/"); }
;
UnaryExpr : ExpExpr
| '-' UnaryExpr { $$ = new UnaryExpr(, "-"); }
| '+' UnaryExpr { $$ = new UnaryExpr(, "+"); }
;
ExpExpr : Value
| ExpExpr '^' Value { $$ = new ExpExpr(, ); }
;
Value : DoubleLiteral
| '(' AddExpr ')' { $$ = new Value(); }
;
DoubleLiteral : DOUBLE { $$ = ; }
;
%%
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
产生式可以是 DoubleLiteral
或 AddExpr
以适应带括号的表达式,所以我创建了一个名为 Expr
的接口,使所有 AST 类'implement'它:
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 中的评论所述,class 可以实现多个接口,因此您可以将 Expr
class 设为 Visitor
也是。
这样做,您现在可以将所有方法移动到 DotVisitor
class 中的专用节点内。
请看下面的例子:
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
而不是 class.
这样做你可以摆脱检查和投射对象的访问方法。
顺便说一句,它也应该与方法重载一起工作,但我认为从设计的角度来看,将代码放在适当的 class 附近会更有效。
如果你想添加新类型的节点,你只需要实现正确的 class,并让解析器知道这些节点,程序的其他部分应该保持不变。
我正在尝试使用 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(); }
;
AddExpr : ModExpr
| AddExpr '+' ModExpr { $$ = new AddExpr(, , "+"); }
| AddExpr '-' ModExpr { $$ = new AddExpr(, , "-"); }
;
ModExpr : IntDivExpr
| ModExpr MOD IntDivExpr { $$ = new ModExpr(, ); }
;
IntDivExpr : MultExpr
| IntDivExpr DIV MultExpr { $$ = new IntDivExpr(, ); }
;
MultExpr : UnaryExpr
| MultExpr '*' UnaryExpr { $$ = new MultExpr(, , "*"); }
| MultExpr '/' UnaryExpr { $$ = new MultExpr(, , "/"); }
;
UnaryExpr : ExpExpr
| '-' UnaryExpr { $$ = new UnaryExpr(, "-"); }
| '+' UnaryExpr { $$ = new UnaryExpr(, "+"); }
;
ExpExpr : Value
| ExpExpr '^' Value { $$ = new ExpExpr(, ); }
;
Value : DoubleLiteral
| '(' AddExpr ')' { $$ = new Value(); }
;
DoubleLiteral : DOUBLE { $$ = ; }
;
%%
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
产生式可以是 DoubleLiteral
或 AddExpr
以适应带括号的表达式,所以我创建了一个名为 Expr
的接口,使所有 AST 类'implement'它:
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 中的评论所述,class 可以实现多个接口,因此您可以将 Expr
class 设为 Visitor
也是。
这样做,您现在可以将所有方法移动到 DotVisitor
class 中的专用节点内。
请看下面的例子:
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
而不是 class.
这样做你可以摆脱检查和投射对象的访问方法。
顺便说一句,它也应该与方法重载一起工作,但我认为从设计的角度来看,将代码放在适当的 class 附近会更有效。
如果你想添加新类型的节点,你只需要实现正确的 class,并让解析器知道这些节点,程序的其他部分应该保持不变。