написание типозащищенного посетителя с помеченными правилами

#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», но у меня все еще есть эта небольшая проблема с бородавками в нескольких местах в моем переводчике, так что это все еще реальный вопрос для меня.