#label #antlr #antlr4 #visitor-pattern
#ярлык #antlr #antlr4 #посетитель-шаблон
Вопрос:
Я переношу свой прототип с прослушивателя на шаблон посетителя. В прототипе у меня есть фрагмент грамматики, подобный этому:
thingList: thing ;
thing
: A aSpec # aRule
| B bSpec # bRule
;
Переходя к шаблону посетителя, я не уверен, как я пишу visitThingList
. Каждый посетитель возвращает специализированный подкласс «Node», и мне бы хотелось как-нибудь, когда можно будет написать что-то подобное, скажем, «список вещей» каким-то образом заботится о первой вещи в списке…
visitThingList(cx: ThingListContext): ast.ThingList {
...
const firstThing = super.visit(cx.thing(0));
Проблема с этим заключается в наборе текста. Каждое посещение возвращает специализированный тип, который является подклассом ast.Node
. Поскольку я использую super.visit
, возвращаемое значение будет базовым классом
моего дерева узлов. Однако я знаю, потому что я смотрю на грамматику
и потому, что я написал оба vistARule
, и visitBRule
что результат посещения будет иметь тип ast.Thing
.
Итак, мы заставляем visitThingList выражать его ожидания с помощью приведения…
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 являются проблемой времени компиляции, я исправляю их в своем редакторе. В этом случае я создаю более хрупкий обход, который будет выявлять хрупкость только во время выполнения, а затем только с определенными входными данными.
Я думаю, что мог бы изменить свою грамматику, чтобы можно было кодировать ожидания типа vistThingList()
путем создания точки vistThing()
входа
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
ast.Thing
относится возврат совпадений visitThing()
. Если я создам новое правило для thing , компилятор заставит меня изменить возвращаемый тип visitThing()
, и если я заставлю его вернуть что-то, что НЕ является thing , visitThingList()
будут показаны ошибки типа.
Это также кажется неправильным, потому что я не чувствую, что мне нужно менять свою грамматику, чтобы посетить его.
Я новичок в ANTLR и задаюсь вопросом, есть ли лучший шаблон или подход к этому.
Ответ №1:
Когда я использовал шаблон слушателя, я написал что-то вроде:
enterThing(cx: ThingContext) { } enterARule(cx : ARuleContext) { } enterBRule(cx : BRuleContext) { }
Не совсем: для помеченного правила like thing
слушатель не будет содержать enterThing(...)
exitThing(...)
методы and . enter...
exit...
Будут созданы только методы и для ярлыков aSpec
и bSpec
.
Как бы я написал обход посетителя без изменения грамматики?
Я не понимаю, зачем вам нужно менять грамматику. Когда вы сохраняете грамматику, как вы упомянули:
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) {
...
}
}
Редактировать
Я не знаю, как, повторяя это, вызывать правильного посетителя для каждого элемента
Вам не нужно знать. Вы можете просто вызвать метод посетителя (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());
Комментарии:
1. Спасибо, эти вещи я знал. Чего я не знаю, так это того, как
visitThingList
он посещает содержащиеся в нем правила, потому что до сих пор, когда я писал посетителя, я всегда брал каждое подправило и вызывал подправило visitor сам явно, чтобы получить результат для составления значения родительского правила. (Как в примере, который я привел с измененной грамматикой) Посколькуctx.thing()
это массивThingContext
объектов, я не знаю, как, повторяя это, вызывать правильного посетителя для каждого элемента.2. «Спасибо, эти вещи, которые я знал», включив в ваш вопрос неправильные вещи, у меня сложилось впечатление, что вы этого не знали. Проверьте мой отредактированный ответ @MichaelToy
3. Привет, @BartKiers — Ваш ответ был фантастическим, он помог мне превратить мое беспокойство в лучший вопрос. Я чувствую, что получил полный «ответ», и теперь я задаю реальный вопрос, который я задавал. Я хочу, чтобы вы получили все заслуги за то, что преодолели мое замешательство и предоставили мне информацию, необходимую для того, чтобы задать лучший вопрос. Пытаясь выяснить, как оставить хороший артефакт в stackoverflow, когда исходный вопрос был «-1″d за плохой вопрос, я отредактировал вопрос. Просто хочу крикнуть, ваши ответы были очень полезны.
4. … и на данный момент у меня есть работающий переводчик, так что я больше не совсем «новичок в ANTLR», но у меня все еще есть эта небольшая проблема с бородавками в нескольких местах в моем переводчике, так что это все еще реальный вопрос для меня.