使用标记规则编写类型安全的访问者
writing a typesafe visitor with labeled rules
我正在将我的原型从侦听器模式迁移到访问者模式。在原型中,我有这样一个语法片段:
thingList: thing+ ;
thing
: A aSpec # aRule
| B bSpec # bRule
;
转向访客模式,我不确定我是怎么写的visitThingList
。每个访问者 return 都是“节点”的专门子class,我会喜欢什么时候能够写这样的东西,比如说“thingList”关心列表中的第一件事...
visitThingList(cx: ThingListContext): ast.ThingList {
...
const firstThing = super.visit(cx.thing(0));
问题出在打字上。每次访问 return 都是一种特殊类型,它是 ast.Node
的子 class。因为我使用的是 super.visit
,return 值将是基础 class
我的节点树。但是,我知道因为我正在看语法
因为我写了 vistARule
和 visitBRule
访问的结果将是类型 ast.Thing
.
所以我们让 visitThingList 表达它对 cast 的期望...
visitThingList(cx: ThingListContext): ast.ThingList {
const firstThing = super.visit(cx.thing(0));
if (!firstThing instanceof ast.Thing) {
throw "no matching visitor for thing";
}
// firstThing is now known to be of type ast.Thing
...
在我的大部分翻译器中,ast 节点的类型问题是一个编译时问题,我在我的编辑器中修复它们。在这种情况下,我正在生成一个更脆弱的步行,它只会在运行时揭示脆弱性,然后仅在特定输入下显示。
我想我可以改变我的语法,使编码成为可能
通过创建 vistThing()
入口点
输入对 vistThingList()
的期望
thingList: thing+ ;
thing: aRule | bRule;
aRule: A aSpec;
bRule: B bSpec;
输入 vistThing()
以符合预期:
visitThing(cx: ThingContext): ast.Thing { }
visitThingList(cx: ThingListContext) {
const firstThing: ast.Thing = this.visitThing(cx.thing(0));
现在visitThingList
可以调用this.visitThing()
并且确保thing
匹配return的所有规则的类型强制ast.Thing
属于visitThing()
.如果我确实为事物创建了一个新规则,编译器将强制我更改 visitThing()
的 return 类型,如果我将它设为 return 不是事物的东西,visitThingList()
将显示类型错误。
虽然这似乎也是错误的,因为我觉得我不应该为了访问它而必须更改我的语法。
我是 ANTLR 的新手,想知道是否有更好的模式或方法。
When I was using the listener pattern, I wrote something like:
enterThing(cx: ThingContext) { }
enterARule(cx : ARuleContext) { }
enterBRule(cx : BRuleContext) { }
不完全是:对于像 thing
这样的标记规则,侦听器将不包含 enterThing(...)
和 exitThing(...)
方法。只会创建标签 aSpec
和 bSpec
的 enter...
和 exit...
方法。
How would I write the visitor walk without changing the grammar?
我不明白你为什么需要更改语法。当你保持你提到的语法时:
thingList: thing+ ;
thing
: A aSpec # aRule
| B bSpec # bRule
;
然后可以使用以下访问者(同样,没有 visitThing(...)
方法!):
public class TestVisitor extends TBaseVisitor<Object> {
@Override
public Object visitThingList(TParser.ThingListContext ctx) {
...
}
@Override
public Object visitARule(TParser.ARuleContext ctx) {
...
}
@Override
public Object visitBRule(TParser.BRuleContext ctx) {
...
}
@Override
public Object visitASpec(TParser.ASpecContext ctx) {
...
}
@Override
public Object visitBSpec(TParser.BSpecContext ctx) {
...
}
}
编辑
I do not know how, as i iterate over that, to call the correct visitor for each element
你不需要知道。您可以简单地调用访问者的 (super) visit(...)
方法,将调用正确的方法:
class TestVisitor extends TBaseVisitor<Object> {
@Override
public Object visitThingList(TParser.ThingListContext ctx) {
for (TParser.ThingContext child : ctx.thing()) {
super.visit(child);
}
return null;
}
...
}
而且您甚至不需要实现所有方法。你没有实现的那些,将有一个默认的visitChildren(ctx)
,导致(顾名思义)它们下面的所有子节点都被遍历。
在您的情况下,以下访问者已经导致 visitASpec
和 visitBSpec
被调用:
class TestVisitor extends TBaseVisitor<Object> {
@Override
public Object visitASpec(TParser.ASpecContext ctx) {
System.out.println("visitASpec");
return null;
}
@Override
public Object visitBSpec(TParser.BSpecContext ctx) {
System.out.println("visitBSpec");
return null;
}
}
您可以这样测试(在 Java 中):
String source = "... your input here ...";
TLexer lexer = new TLexer(CharStreams.fromString(source));
TParser parser = new TParser(new CommonTokenStream(lexer));
TestVisitor visitor = new TestVisitor();
visitor.visit(parser.thingList());
我正在将我的原型从侦听器模式迁移到访问者模式。在原型中,我有这样一个语法片段:
thingList: thing+ ;
thing
: A aSpec # aRule
| B bSpec # bRule
;
转向访客模式,我不确定我是怎么写的visitThingList
。每个访问者 return 都是“节点”的专门子class,我会喜欢什么时候能够写这样的东西,比如说“thingList”关心列表中的第一件事...
visitThingList(cx: ThingListContext): ast.ThingList {
...
const firstThing = super.visit(cx.thing(0));
问题出在打字上。每次访问 return 都是一种特殊类型,它是 ast.Node
的子 class。因为我使用的是 super.visit
,return 值将是基础 class
我的节点树。但是,我知道因为我正在看语法
因为我写了 vistARule
和 visitBRule
访问的结果将是类型 ast.Thing
.
所以我们让 visitThingList 表达它对 cast 的期望...
visitThingList(cx: ThingListContext): ast.ThingList {
const firstThing = super.visit(cx.thing(0));
if (!firstThing instanceof ast.Thing) {
throw "no matching visitor for thing";
}
// firstThing is now known to be of type ast.Thing
...
在我的大部分翻译器中,ast 节点的类型问题是一个编译时问题,我在我的编辑器中修复它们。在这种情况下,我正在生成一个更脆弱的步行,它只会在运行时揭示脆弱性,然后仅在特定输入下显示。
我想我可以改变我的语法,使编码成为可能
通过创建 vistThing()
入口点
vistThingList()
的期望
thingList: thing+ ;
thing: aRule | bRule;
aRule: A aSpec;
bRule: B bSpec;
输入 vistThing()
以符合预期:
visitThing(cx: ThingContext): ast.Thing { }
visitThingList(cx: ThingListContext) {
const firstThing: ast.Thing = this.visitThing(cx.thing(0));
现在visitThingList
可以调用this.visitThing()
并且确保thing
匹配return的所有规则的类型强制ast.Thing
属于visitThing()
.如果我确实为事物创建了一个新规则,编译器将强制我更改 visitThing()
的 return 类型,如果我将它设为 return 不是事物的东西,visitThingList()
将显示类型错误。
虽然这似乎也是错误的,因为我觉得我不应该为了访问它而必须更改我的语法。
我是 ANTLR 的新手,想知道是否有更好的模式或方法。
When I was using the listener pattern, I wrote something like:
enterThing(cx: ThingContext) { } enterARule(cx : ARuleContext) { } enterBRule(cx : BRuleContext) { }
不完全是:对于像 thing
这样的标记规则,侦听器将不包含 enterThing(...)
和 exitThing(...)
方法。只会创建标签 aSpec
和 bSpec
的 enter...
和 exit...
方法。
How would I write the visitor walk without changing the grammar?
我不明白你为什么需要更改语法。当你保持你提到的语法时:
thingList: thing+ ;
thing
: A aSpec # aRule
| B bSpec # bRule
;
然后可以使用以下访问者(同样,没有 visitThing(...)
方法!):
public class TestVisitor extends TBaseVisitor<Object> {
@Override
public Object visitThingList(TParser.ThingListContext ctx) {
...
}
@Override
public Object visitARule(TParser.ARuleContext ctx) {
...
}
@Override
public Object visitBRule(TParser.BRuleContext ctx) {
...
}
@Override
public Object visitASpec(TParser.ASpecContext ctx) {
...
}
@Override
public Object visitBSpec(TParser.BSpecContext ctx) {
...
}
}
编辑
I do not know how, as i iterate over that, to call the correct visitor for each element
你不需要知道。您可以简单地调用访问者的 (super) visit(...)
方法,将调用正确的方法:
class TestVisitor extends TBaseVisitor<Object> {
@Override
public Object visitThingList(TParser.ThingListContext ctx) {
for (TParser.ThingContext child : ctx.thing()) {
super.visit(child);
}
return null;
}
...
}
而且您甚至不需要实现所有方法。你没有实现的那些,将有一个默认的visitChildren(ctx)
,导致(顾名思义)它们下面的所有子节点都被遍历。
在您的情况下,以下访问者已经导致 visitASpec
和 visitBSpec
被调用:
class TestVisitor extends TBaseVisitor<Object> {
@Override
public Object visitASpec(TParser.ASpecContext ctx) {
System.out.println("visitASpec");
return null;
}
@Override
public Object visitBSpec(TParser.BSpecContext ctx) {
System.out.println("visitBSpec");
return null;
}
}
您可以这样测试(在 Java 中):
String source = "... your input here ...";
TLexer lexer = new TLexer(CharStreams.fromString(source));
TParser parser = new TParser(new CommonTokenStream(lexer));
TestVisitor visitor = new TestVisitor();
visitor.visit(parser.thingList());