tunnelvisionlabs / antlr4ts

Optimized TypeScript target for ANTLR 4
Other
636 stars 109 forks source link

Invalid code generated for grammars attempting to provide overridden constructor #444

Open BurtHarris opened 4 years ago

BurtHarris commented 4 years ago

Let's port this simple example from The Definitive Antlr 4 Reference Rows.g4 grammar:

grammar Rows;

@parser::members {
    int col;
    public RowsParser(TokenStream input, int col) {
        this(input);
        this.col = col;
    }
}
...

How does one port this grammar to generate typescript? Here's a stab at it:

grammar Rows;

@parser::members {
    col: number;
    constructor (input: TokenStream, col: number) {
        super(input);
        this.col = col;
        }
}
...

With the current version of antlr4ts-cli, this generates flawed typescript. When processed by the typescript compiler, it complains saying: Multiple constructor implementations are not allowed. ts(2392). In relevant part the resulting RowsParser.ts reads:

    col: number;
    constructor (input: TokenStream, col: number) {
        super(input);
        this.col = col;
        }

    constructor(input: TokenStream) {
        super(input);
        this._interp = new ParserATNSimulator(RowsParser._ATN, this);
    }
...

That doesn't work because TypeScript doesn't handle overloads like Java... There can only be one body for the constructor, but it may have several declarations preceding it.

BurtHarris commented 4 years ago

I think this calls for the code generation tool to detect (or be told) not to build a constructor itself if one is being provided in the grammar file. I suppose the simplest approach would be to make it an option that can be set in the grammar file. Comments?

Would this make sense?

grammar Rows;

options { SuppressParserConstructor = true }

@parser::members {
    col: number;
    constructor (input: TokenStream, col: number) {
        super(input);
    this._interp = new ParserATNSimulator(RowsParser._ATN, this);
        this.col = col;
        }
}
...

The fragile part of this might be that someone writing (or customizing) a grammar for antlr4ts would need to remember to set the _interp member in the manually generated constructor. I suppose that could be addressed by making it a mandatory parameter of the abstract Parser constructor's constructor, either as a ParserATNSimulator, or a serialized string. Leading to:

grammar Rows;

options { SuppressParserConstructor = true }

@parser::members {
    col: number;
    constructor (input: TokenStream, col: number) {
        super(input, new ParserATNSimulator(RowsParser._ATN, this););
        this.col = col;
        }
}
...
BurtHarris commented 4 years ago

Dealing with generated overloads, particularly overloaded constructors, seems like a difficult problem. @sharwell thoughts?

One thought is to build a post-processor which can detect attempts in generated code at overloading and combine into a single function. A postprocessor might also address my desire to generate only a single output .ts file, as described in #195. See also stackoverflow/antlr4-target-file-names

There's now an ANTLR grammar for typescript.