I am migrating my prototype from a listener to a visitor pattern. In the prototype, I have a grammar fragment like this:
thingList: thing+ ;
thing
: A aSpec # aRule
| B bSpec # bRule
;
Moving to a visitor pattern, I am not sure how I write visitThingList
. Every visitor returns a specializes subclass of "Node", and I would love somehow when to be able to write something like this, say a "thingList" cares about the first thing in the list some how ...
visitThingList(cx: ThingListContext): ast.ThingList {
...
const firstThing = super.visit(cx.thing(0));
The problem with this is in typing. Each visit returns a specialized type which is a subclass of ast.Node
. Because I am using super.visit
, the return value will be the base class
of my node tree. However, I know because I am looking at the grammar
and because I wrote both vistARule
and visitBRule
that the result of the visit will be of type ast.Thing
.
So we make visitThingList express it's expectation with 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
...
In much of my translator, type problems with ast Nodes are a compile time issue, I fix them in my editor. In this case, I am producing a more fragile walk, which will only reveal the fragility at runtime and then only with certain inputs.
I think I could change my grammar, to make it possible to encode the
type expectations of vistThingList()
by creating a vistThing()
entry point
thingList: thing+ ;
thing: aRule | bRule;
aRule: A aSpec;
bRule: B bSpec;
With vistThing()
typed to match the expectation:
visitThing(cx: ThingContext): ast.Thing { }
visitThingList(cx: ThingListContext) {
const firstThing: ast.Thing = this.visitThing(cx.thing(0));
Now visitThingList
can call this.visitThing()
and the type enforcement of making sure all rules that a thing
matches return ast.Thing
belongs to visitThing()
. If I do create a new rule for thing, the compiler will force me to change the return type of visitThing()
and if I make it return something which is NOT a thing, visitThingList()
will show type errors.
This also seems wrong though, because I don't feel like I should have to change my grammar in order to visit it.
I am new to ANTLR and wondering if there is a better pattern or approach to this.