Null checking and action code in generated parser
I'm porting the ActionExpr.g4 grammar from Java actions to TypeScript. My grammar contains this:
stat:
expr NEWLINE {console.log($expr.v);}
| ID '=' expr NEWLINE { this.memory[$ID.text] = $expr.v; }
| NEWLINE;
The resulting code in the generated parser reads:
...
this.match(ActionExprParser.NEWLINE);
this.memory[(_localctx._ID != null ? _localctx._ID.text : undefined)] = _localctx._expr.v;
}
the explicit undefined results in errors:
- TS2538 - Type 'undefined' cannot be used as an index type.
- TS2345 -Argument of type 'string | undefined' is not assignable to parameter of type 'string'. Type 'undefined' is not assignable to type 'string'.
Seems to be generated by TokenPropertyRef_text(r) in TypeScript.stg
TokenPropertyRef_text(t) ::= "(<ctx(t)>._<t.label> != null ? <ctx(t)>._<t.label>.text : undefined)"
@sharwell this seems like something you might be interested in.
Do you have ideas for how it could be resolved?
TokenPropertyRef_text(t) ::= "<ctx(t)>._<t.label>!.text"
I that sort of punts on null checking...
An alternative might be to return the empty string if the label is null.
TokenPropertyRef_text(t) ::= "<ctx(t)>._<t.label>?.text ?? ''"
Hmm, because Token declares text: string | undefined, it still needs a final null elimination at the end. So in the grammar, I guess I need a bang following text:
stat:
expr NEWLINE {console.log($expr.v);}
| ID '=' expr NEWLINE { this.memory[$ID.text!] = $expr.v; }
| NEWLINE;
In that case, the original generated code may work OK. TypeScript 3.7 operational chaining has a nicer syntax.
I was a bit confused by another message because of how I had defined member memory = {}, but got that straightened out: I eventually settled on memory: { [property: string]: number } = {};
What about using this in the grammar?
{ this.memory[$ID.text ?? ""] = $expr.v; }
That sounds reasonable.