tunnelvisionlabs / antlr4ts

Optimized TypeScript target for ANTLR 4

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ParseTreeWalker doesn't understand the generated listeners

BurtHarris opened this issue · comments

In TypeScript we can declare listeners as interfaces which specify, and implement them using object literal syntax and lambda's. E.g:

const parser = new ExprParser(tokens);
parser.addParseListener({
	enterProg: ctx => console.log("statments:"),
	exitStat: ctx => console.log("  - " + ctx.text.trim()),
	exitProg: ctx => console.log("\nStringTree:", ctx.toStringTree(parser))
}).prog(); 

This is much more compact that creating a listener class implementing interface ExprParseListener, but just as typesafe. Typescript understands the correct type of each ctx, in the lambda's without explicit typing. It also checks the implemented enter/exit function member names, and catches any misspellings.

This unfortunately we don't get these benefits when using class ParseTreeWalker because it isn't customized by the code generation. A solution to this might be to implement a correctly typed walk(listener: ExprListener) method on the generated parse context/tree classes, which under the covers just creates a ParseTreeWalker and calls walk(listener, this) on it. The difference would be that the context/tree class. Similar processing to above could happen after the parse using:

const parser = new ExprParser(tokens);
const tree = parser.prog();
tree.walk({
	enterProg: ctx => console.log("statments:"),
	exitStat: ctx => console.log("  - " + ctx.text.trim()),
	exitProg: ctx => console.log("\nStringTree:", ctx.toStringTree(parser))
});

I found a good approach for this by making the ParseTreeListener class as a template parameter to both Parser and ParserRuleContext. This way there is zero runtime overhead. The updated classes become:

export abstract class Parser<T extends ParseTreeListener = ParseTreeListener> ...

and

export class ParserRuleContext<T extends ParseTreeListener = ParseTreeListener> 
           extends RuleContext {
...
	public walk(listener: T): this {
		const walker = new ParseTreeWalker();
		walker.walk(listener, this);
		return this;
	}
}

The generated code then substitutes the right listener type in when creating the custom-built ParserRuleContexts.