/*
 * Copyright (c) Mike Lischke. All rights reserved.
 * Licensed under the BSD 3-clause License. See License.txt in the project root for license information.
 */

typescriptTypeInitMap ::= [
    "bool":"false",
    "int":"0",
    "float":"0.0",
    "str":"",
    default:"{}" // anything other than a primitive type is an object
]

// args must be <object-model-object>, <fields-resulting-in-STs>

ParserFile(file, parser, namedActions, contextSuperClass) ::= <<
<fileHeader(file.grammarFileName, file.ANTLRVersion)>

import * as antlr from "antlr4ng";
import { Token } from "antlr4ng";

<if(file.genListener)>
import { <file.grammarName>Listener } from "./<file.grammarName>Listener.js";
<endif>
<if(file.genVisitor)>
import { <file.grammarName>Visitor } from "./<file.grammarName>Visitor.js";
<endif>

// for running tests with parameters, TODO: discuss strategy for typed parameters in CI
// eslint-disable-next-line no-unused-vars
type int = number;

<namedActions.header>
<parser>
>>

ListenerFile(file, header, namedActions) ::= <<
<fileHeader(file.grammarFileName, file.ANTLRVersion)>

import { ErrorNode, ParseTreeListener, ParserRuleContext, TerminalNode } from "antlr4ng";

<header>

<file.listenerNames:{lname | import { <lname; format="cap">Context \} from "./<file.parserName>.js";}; separator="\n">

<namedActions.beforeListener>

/**
 * This interface defines a complete listener for a parse tree produced by
 * `<file.parserName>`.
 */
export class <file.grammarName>Listener implements ParseTreeListener {
    <file.listenerNames:{lname |
/**
<if(file.listenerLabelRuleNames.(lname))>
 * Enter a parse tree produced by the `<lname>`
 * labeled alternative in `<file.parserName>.<file.listenerLabelRuleNames.(lname)>`.
<else>
 * Enter a parse tree produced by `<file.parserName>.<lname>`.
<endif>
 * @param ctx the parse tree
 */
enter<lname; format="cap">?: (ctx: <lname; format="cap">Context) => void;
/**
<if(file.listenerLabelRuleNames.(lname))>
 * Exit a parse tree produced by the `<lname>`
 * labeled alternative in `<file.parserName>.<file.listenerLabelRuleNames.(lname)>`.
<else>
 * Exit a parse tree produced by `<file.parserName>.<lname>`.
<endif>
 * @param ctx the parse tree
 */
exit<lname; format="cap">?: (ctx: <lname; format="cap">Context) => void;}; separator="\n">

    visitTerminal(node: TerminalNode): void {}
    visitErrorNode(node: ErrorNode): void {}
    enterEveryRule(node: ParserRuleContext): void {}
    exitEveryRule(node: ParserRuleContext): void {}
}

<namedActions.afterListener>
>>

VisitorFile(file, header, namedActions) ::= <<
<fileHeader(file.grammarFileName, file.ANTLRVersion)>

import { AbstractParseTreeVisitor } from "antlr4ng";

<header>

<file.visitorNames:{lname | import { <lname; format="cap">Context \} from "./<file.parserName>.js";}; separator="\n">

<namedActions.beforeVisitor>

/**
 * This interface defines a complete generic visitor for a parse tree produced
 * by `<file.parserName>`.
 *
 * @param \<Result> The return type of the visit operation. Use `void` for
 * operations with no return type.
 */
export class <file.grammarName>Visitor\<Result> extends AbstractParseTreeVisitor\<Result> {
    <file.visitorNames:{lname |
/**
<if(file.visitorLabelRuleNames.(lname))>
 * Visit a parse tree produced by the `<lname>`
 * labeled alternative in `<file.parserName>.<file.visitorLabelRuleNames.(lname)>`.
<else>
 * Visit a parse tree produced by `<file.parserName>.<lname>`.
<endif>
 * @param ctx the parse tree
 * @return the visitor result
 */
visit<lname; format="cap">?: (ctx: <lname; format="cap">Context) => Result;}; separator="\n">
}

<namedActions.afterVisitor>
>>

fileHeader(grammarFileName, ANTLRVersion) ::= <<
>>

Parser(parser, funcs, atn, sempredFuncs, superClass, isLexer=false) ::= <<
<Parser_(ctor="parser_ctor", ...)>
>>

Parser_(parser, funcs, atn, sempredFuncs, ctor, superClass) ::= <<

export class <parser.name> extends <superClass; null="antlr.Parser"> {
    <if(parser.tokens)>
    <parser.tokens:{k | public static readonly <k> = <parser.tokens.(k)>;}; separator="\n", wrap, anchor>
    <endif>
    <parser.rules:{r | public static readonly RULE_<r.name> = <r.index>;}; separator="\n", wrap, anchor>

    public static readonly literalNames = [
        <parser.literalNames:{t | <t>}; null="null", separator=", ", wrap, anchor>
    ];

    public static readonly symbolicNames = [
        <parser.symbolicNames:{t | <t>}; null="null", separator=", ", wrap, anchor>
    ];
    public static readonly ruleNames = [
        <parser.ruleNames:{r | "<r>",}; separator=" ", wrap, anchor>
    ];

    public get grammarFileName(): string { return "<parser.grammarFileName>"; }
    public get literalNames(): (string | null)[] { return <parser.name>.literalNames; }
    public get symbolicNames(): (string | null)[] { return <parser.name>.symbolicNames; }
    public get ruleNames(): string[] { return <parser.name>.ruleNames; }
    public get serializedATN(): number[] { return <parser.name>._serializedATN; }

    protected createFailedPredicateException(predicate?: string, message?: string): antlr.FailedPredicateException {
        return new antlr.FailedPredicateException(this, predicate, message);
    }

    <namedActions.members>
    <parser:(ctor)()>
    <funcs; separator="\n">

<if(sempredFuncs)>
    public override sempred(localContext: antlr.ParserRuleContext | null, ruleIndex: number, predIndex: number): boolean {
        switch (ruleIndex) {
        <parser.sempredFuncs.values:{f|
case <f.ruleIndex>:
    return this.<f.name>_sempred(localContext as <f.ctxType>, predIndex);}; separator="\n">
        }
        return true;
    }
    <sempredFuncs.values; separator="\n">
<endif>

    <atn>

    private static readonly vocabulary = new antlr.Vocabulary(<parser.name>.literalNames, <parser.name>.symbolicNames, []);

    public override get vocabulary(): antlr.Vocabulary {
        return <parser.name>.vocabulary;
    }

    private static readonly decisionsToDFA = <parser.name>._ATN.decisionToState.map( (ds: antlr.DecisionState, index: number) => new antlr.DFA(ds, index) );
}

<! The argument must be called currentRule because AltLabelStructDecl looks it up by name. !>
<funcs:{currentRule |
<if(currentRule.ruleCtx)>
<currentRule.ruleCtx>
<endif>
<if(currentRule.altLabelCtxs)>
<currentRule.altLabelCtxs:{l | <currentRule.altLabelCtxs.(l)>}; separator="\n">
<endif>
}; separator="\n\n">
>>

dumpActions(recog, argFuncs, actionFuncs, sempredFuncs) ::= <<
<if(actionFuncs)>
public override action(localContext: antlr.ParserRuleContext | null, ruleIndex: number, actionIndex: number): void {
    switch (ruleIndex) {
    <recog.actionFuncs.values:{f|
case <f.ruleIndex>:
    this.<f.name>_action(<if(!recog.modes)>(<f.ctxType>)<endif>localContext, actionIndex);
    break;}; separator="\n">
    }
}
<actionFuncs.values; separator="\n">
<endif>
<if(sempredFuncs)>
public override sempred(localContext: antlr.ParserRuleContext | null, ruleIndex: number, predIndex: number): boolean {
    switch (ruleIndex) {
    <recog.sempredFuncs.values:{f|
case <f.ruleIndex>:
    return this.<f.name>_sempred(localContext<if(!recog.modes)> as <f.ctxType><endif>, predIndex);}; separator="\n">
    }
    return true;
}
<sempredFuncs.values; separator="\n">
<endif>
>>

parser_ctor(p) ::= <<
public constructor(input: antlr.TokenStream) {
    super(input);
    this.interpreter = new antlr.ParserATNSimulator(this, <p.name>._ATN, <p.name>.decisionsToDFA, new antlr.PredictionContextCache());
}
>>

/* This generates a private method since the actionIndex is generated, making an
 * overriding implementation impossible to maintain.
 */
RuleActionFunction(r, actions) ::= <<
private <r.name>_action(localContext: <r.ctxType> | null, actionIndex: number): void {
    switch (actionIndex) {
    <actions:{index|
case <index>:
    <actions.(index)>
    break;}; separator="\n">
    }
}
>>

/* This generates a private method since the predIndex is generated, making an
 * overriding implementation impossible to maintain.
 */
RuleSempredFunction(r, actions) ::= <<
private <r.name>_sempred(localContext: <r.ctxType> | null, predIndex: number): boolean {
    switch (predIndex) {
    <actions:{index|
case <index>:
    return <actions.(index)>;}; separator="\n">
    }
    return true;
}
>>

RuleFunction(currentRule,args,code,locals,ruleCtx,altLabelCtxs,namedActions,finallyAction,postamble,exceptions) ::= <<
<if(currentRule.modifiers)><currentRule.modifiers:{f | <f> }><else>public <endif><currentRule.escapedName>(<currentRule.args:{a | <a.escapedName>: <a.type>}; separator=", ">): <currentRule.ctxType> {
    let localContext = new <currentRule.ctxType>(this.context, this.state<currentRule.args:{a | , <a.escapedName>}>);
    this.enterRule(localContext, <currentRule.startState>, <parser.name>.RULE_<currentRule.name>);
    <namedActions.init>
    <locals; separator="\n">
    try {
<if(currentRule.hasLookaheadBlock)>
        let alternative: number;
<endif>
        <code>
        <postamble; separator="\n">
        <namedActions.after>
    }
    <if(exceptions)>
    <exceptions; separator="\n">
    <else>
    catch (re) {
        if (re instanceof antlr.RecognitionException) {
            this.errorHandler.reportError(this, re);
            this.errorHandler.recover(this, re);
        } else {
            throw re;
        }
    }
    <endif>
    finally {
        <finallyAction>
        this.exitRule();
    }
    return localContext;
}
>>

LeftFactoredRuleFunction(currentRule,args,code,locals,namedActions,finallyAction,postamble) ::= <<
<if(currentRule.modifiers)><currentRule.modifiers:{f | <f> }><else>private <endif><currentRule.ctxType> <currentRule.escapedName>(<args; separator=",">) {
    let localContext = new <currentRule.ctxType>(this.context, this.state<currentRule.args:{a | , <a.escapedName>}>);
    this.enterLeftFactoredRule(localContext, <currentRule.startState>, <parser.name>.RULE_<currentRule.variantOf>);
    <namedActions.init>
    <locals; separator="\n">
    try {
<if(currentRule.hasLookaheadBlock)>
        let alternative: number;
<endif>
        <code>
        <postamble; separator="\n">
        <namedActions.after>
    } catch (re) {
        if (re instanceof antlr.RecognitionException) {
            this.errorHandler.reportError(this, re);
            this.errorHandler.recover(this, re);
        } else {
            throw re;
        }
    }
    finally {
        <finallyAction>
        this.exitRule();
    }
    return localContext;
}
>>

// This behaves similar to RuleFunction (enterRule is called, and no adjustments
// are made to the parse tree), but since it's still a variant no context class
// needs to be generated.
LeftUnfactoredRuleFunction(currentRule,args,code,locals,namedActions,finallyAction,postamble) ::= <<
<if(currentRule.modifiers)><currentRule.modifiers:{f | <f> }><else>private <endif><currentRule.ctxType> <currentRule.name>(<args; separator=",">) {
    let localContext = new <currentRule.ctxType>(this.context, this.state<currentRule.args:{a | , <a.name>}>);
    this.enterRule(localContext, <currentRule.startState>, <parser.name>.RULE_<currentRule.variantOf>);
    <namedActions.init>
    <locals; separator="\n">
    try {
<if(currentRule.hasLookaheadBlock)>
        let alternative: number;
<endif>
        <code>
        <postamble; separator="\n">
        <namedActions.after>
    }
    catch (re) {
        if (re instanceof antlr.RecognitionException) {
            this.errorHandler.reportError(this, re);
            this.errorHandler.recover(this, re);
        } else {
            throw re;
        }
    }
    finally {
        <finallyAction>
        this.exitRule();
    }
    return localContext;
}
>>

LeftRecursiveRuleFunction(currentRule,args,code,locals,ruleCtx,altLabelCtxs,
    namedActions,finallyAction,postamble) ::=
<<

<if(currentRule.modifiers)><currentRule.modifiers:{f | <f> }><else>public <endif><currentRule.escapedName>(<args; separator=", ">): <currentRule.ctxType>;
<if(currentRule.modifiers)><currentRule.modifiers:{f | <f> }><else>public <endif><currentRule.escapedName>(<args; separator=", "><if(args)>, <endif>_p: number): <currentRule.ctxType>;
<if(currentRule.modifiers)><currentRule.modifiers:{f | <f> }><else>public <endif><currentRule.escapedName>(<args; separator=", "><if(args)>, <endif>_p?: number): <currentRule.ctxType> {
    if (_p === undefined) {
        _p = 0;
    }

    let parentContext = this.context;
    let parentState = this.state;
    let localContext = new <currentRule.ctxType>(this.context, parentState<currentRule.args:{a | , <a.escapedName>}>);
    let previousContext = localContext;
    let _startState = <currentRule.startState>;
    this.enterRecursionRule(localContext, <currentRule.startState>, <parser.name>.RULE_<currentRule.name>, _p);
    <namedActions.init>
    <locals; separator="\n">
    try {
<if(currentRule.hasLookaheadBlock)>
        let alternative: number;
<endif>
        <code>
        <postamble; separator="\n">
        <namedActions.after>
    }
    catch (re) {
        if (re instanceof antlr.RecognitionException) {
            this.errorHandler.reportError(this, re);
            this.errorHandler.recover(this, re);
        } else {
            throw re;
        }
    }
    finally {
        <finallyAction>
        this.unrollRecursionContexts(parentContext);
    }
    return localContext;
}
>>

CodeBlockForOuterMostAlt(currentOuterMostAltCodeBlock, locals, preamble, ops) ::= <<
<if(currentOuterMostAltCodeBlock.altLabel)>localContext = new <currentOuterMostAltCodeBlock.altLabel; format="cap">Context(localContext);<endif>
this.enterOuterAlt(localContext, <currentOuterMostAltCodeBlock.alt.altNum>);
<CodeBlockForAlt(currentAltCodeBlock=currentOuterMostAltCodeBlock, ...)>
>>

CodeBlockForAlt(currentAltCodeBlock, locals, preamble, ops) ::= <<
<if(!(locals || preamble || ops))>
// tslint:disable-next-line:no-empty
<endif>
{
<locals; separator="\n">
<preamble; separator="\n">
<ops; separator="\n">
}
>>

LL1AltBlock(choice, preamble, alts, error) ::= <<
this.state = <choice.stateNumber>;
this.errorHandler.sync(this);
<if(choice.label)><labelref(choice.label)> = this.tokenStream.LT(1);<endif>
<preamble; separator="\n">
switch (this.tokenStream.LA(1)) {
<choice.altLook,alts:{look,alt| <cases(tokens=look)>
    <alt>
    break;}; separator="\n">
default:
    <error>
}
>>

LL1OptionalBlock(choice, alts, error) ::= <<
this.state = <choice.stateNumber>;
this.errorHandler.sync(this);
switch (this.tokenStream.LA(1)) {
<choice.altLook,alts:{look,alt| <cases(tokens=look)>
    <alt>
    break;}; separator="\n">
default:
    break;
}
>>

LL1OptionalBlockSingleAlt(choice, expr, alts, preamble, error, followExpr) ::= <<
this.state = <choice.stateNumber>;
this.errorHandler.sync(this);
<preamble; separator="\n">
if (<expr>) {
    <alts; separator="\n">
}
<!else if (!(<followExpr>)) <error>!>
>>

LL1StarBlockSingleAlt(choice, loopExpr, alts, preamble, iteration) ::= <<
this.state = <choice.stateNumber>;
this.errorHandler.sync(this);
<preamble; separator="\n">
while (<loopExpr>) {
    <alts; separator="\n">
    this.state = <choice.loopBackStateNumber>;
    this.errorHandler.sync(this);
    <iteration>
}
>>

LL1PlusBlockSingleAlt(choice, loopExpr, alts, preamble, iteration) ::= <<
this.state = <choice.blockStartStateNumber>;<! alt block decision !>
this.errorHandler.sync(this);
<preamble; separator="\n">
do {
    <alts; separator="\n">
    this.state = <choice.stateNumber>;<! loopback/exit decision !>
    this.errorHandler.sync(this);
    <iteration>
} while (<loopExpr>);
>>

// LL(*) stuff

AltBlock(choice, preamble, alts, error) ::= <<
this.state = <choice.stateNumber>;
this.errorHandler.sync(this);
<if(choice.label)><labelref(choice.label)> = this.tokenStream.LT(1);<endif>
<preamble; separator="\n">
switch (this.interpreter.adaptivePredict(this.tokenStream, <choice.decision>, this.context) ) {
<alts:{alt |
case <i>:
    <alt>
    break;}; separator="\n">
}
>>

OptionalBlock(choice, alts, error) ::= <<
this.state = <choice.stateNumber>;
this.errorHandler.sync(this);
switch (this.interpreter.adaptivePredict(this.tokenStream, <choice.decision>, this.context) ) {
<alts:{alt |
case <i><if(!choice.ast.greedy)> + 1<endif>:
    <alt>
    break;}; separator="\n">
}
>>

StarBlock(choice, alts, sync, iteration) ::= <<
this.state = <choice.stateNumber>;
this.errorHandler.sync(this);
alternative = this.interpreter.adaptivePredict(this.tokenStream, <choice.decision>, this.context);
while (alternative !== <choice.exitAlt> && alternative !== antlr.ATN.INVALID_ALT_NUMBER) {
    if (alternative === 1<if(!choice.ast.greedy)> + 1<endif>) {
        <iteration>
        <alts><! should only be one !>
    }
    this.state = <choice.loopBackStateNumber>;
    this.errorHandler.sync(this);
    alternative = this.interpreter.adaptivePredict(this.tokenStream, <choice.decision>, this.context);
}
>>

PlusBlock(choice, alts, error) ::= <<
this.state = <choice.blockStartStateNumber>;<! alt block decision !>
this.errorHandler.sync(this);
alternative = 1<if(!choice.ast.greedy)> + 1<endif>;
do {
    switch (alternative) {
    <alts:{alt|
case <i><if(!choice.ast.greedy)> + 1<endif>:
    <alt>
    break;}; separator="\n">
    default:
        <error>
    }
    this.state = <choice.loopBackStateNumber>;<! loopback/exit decision !>
    this.errorHandler.sync(this);
    alternative = this.interpreter.adaptivePredict(this.tokenStream, <choice.decision>, this.context);
} while (alternative !== <choice.exitAlt> && alternative !== antlr.ATN.INVALID_ALT_NUMBER);
>>

Sync(s) ::= "this.sync(<s.expecting.name>);"

ThrowNoViableAlt(t) ::= "throw new antlr.NoViableAltException(this);"

TestSetInline(s) ::= <<
<s.bitsets:{bits | <if(rest(rest(bits.tokens)))><bitsetBitfieldComparison(s, bits)><else><bitsetInlineComparison(s, bits)><endif>}; separator=" || ">
>>

// Javascript language spec - shift operators are 32 bits long max
testShiftInRange(shiftAmount) ::= <<
((<shiftAmount>) & ~0x1F) === 0
>>

// produces smaller bytecode only when bits.ttypes contains more than two items
bitsetBitfieldComparison(s, bits) ::= <%
(<testShiftInRange({<offsetShiftVar(s.varName, bits.shift)>})> && ((1 \<\< <offsetShiftVar(s.varName, bits.shift)>) & <bits.calculated>) !== 0)
%>

isZero ::= [
"0":true,
default:false
]

offsetShiftVar(shiftAmount, offset) ::= <%
<if(!isZero.(offset))>(<shiftAmount> - <offset>)<else><shiftAmount><endif>
%>

offsetShiftType(shiftAmount, offset) ::= <%
<if(!isZero.(offset))>(<parser.name>.<shiftAmount> - <offset>)<else><parser.name>.<shiftAmount><endif>
%>


bitsetInlineComparison(s, bits) ::= <%
<bits.tokens:{t | <s.varName> === <t.type>}; separator=" || ">
%>

cases(tokens) ::= <<
<tokens:{t | case <parser.name>.<t.name>:}; separator="\n">
>>

InvokeRule(r, argExprsChunks) ::= <<
this.state = <r.stateNumber>;
<if(r.labels)><r.labels:{l | <labelref(l)> = }><endif>this.<r.escapedName>(<if(r.ast.options.p)><r.ast.options.p><if(argExprsChunks)>,<endif><endif><argExprsChunks>);
>>

MatchToken(m) ::= <<
this.state = <m.stateNumber>;
<if(m.labels)><m.labels:{l | <labelref(l)> = }><endif>this.match(<parser.name>.<m.name>);
>>

MatchSet(m, expr, capture) ::= "<CommonSetStuff(m, expr, capture, false)>"

MatchNotSet(m, expr, capture) ::= "<CommonSetStuff(m, expr, capture, true)>"

CommonSetStuff(m, expr, capture, invert) ::= <<
this.state = <m.stateNumber>;
<if(m.labels)><m.labels:{l | <labelref(l)> = }>this.tokenStream.LT(1);<endif>
<capture>
<if(invert)>if(<m.varName>\<=0 || <expr>)<else>if(!(<expr>))<endif> {
    <if(m.labels)><m.labels:{l | <labelref(l)> = }><endif>this.errorHandler.recoverInline(this);
}
else {
    this.errorHandler.reportMatch(this);
    this.consume();
}
>>

Wildcard(w) ::= <<
this.state = <w.stateNumber>;
<if(w.labels)><w.labels:{l | <labelref(l)> = }><endif>this.matchWildcard();
>>

// ACTION STUFF

Action(a, foo, chunks) ::= "<chunks>"

ArgAction(a, chunks) ::= "<chunks>"

SemPred(p, chunks, failChunks) ::= <<
this.state = <p.stateNumber>;
if (!(<chunks>)) {
    throw this.createFailedPredicateException(<p.predicate><if(failChunks)>, <failChunks><elseif(p.msg)>, <p.msg><endif>);
}
>>

ExceptionClause(e, catchArg, catchAction) ::= <<
catch (<catchArg>) {
    <catchAction>
}
>>

// lexer actions are not associated with model objects

LexerSkipCommand()  ::= "this.skip();"
LexerMoreCommand()  ::= "this.more();"
LexerPopModeCommand() ::= "this.popMode();"
LexerTypeCommand(arg, grammar)      ::= "this.type = <arg>;"
LexerChannelCommand(arg, grammar)   ::= "this.channel = <arg>;"
LexerModeCommand(arg, grammar)      ::= "this.mode = <arg>;"
LexerPushModeCommand(arg, grammar)  ::= "this.pushMode(<arg>);"

ActionText(t) ::= "<t.text>"
ActionTemplate(t) ::= "<t.st>"
ArgRef(a) ::= "localContext?.<a.escapedName>!"
LocalRef(a) ::= "localContext.<a.escapedName>"
RetValueRef(a) ::= "localContext.<a.escapedName>"
QRetValueRef(a) ::= "<ctx(a)>._<a.dict>!.<a.escapedName>"
/** How to translate $tokenLabel */
TokenRef(t) ::= "<ctx(t)>?._<t.escapedName>"
LabelRef(t) ::= "<ctx(t)>?._<t.escapedName>"
ListLabelRef(t) ::= "<ctx(t)>?._<ListLabelName(t.escapedName)>"
SetAttr(s,rhsChunks) ::= "<ctx(s)>!.<s.escapedName> = <rhsChunks>;"

TokenLabelType()  ::= "<file.TokenLabelType; null={Token}>"
InputSymbolType() ::= "<file.InputSymbolType; null={Token}>"

TokenPropertyRefText(t)    ::= "(<ctx(t)>._<t.label>?.text ?? '')"
TokenPropertyRefType(t)    ::= "(<ctx(t)>._<t.label>?.type ?? 0)"
TokenPropertyRefLine(t)    ::= "(<ctx(t)>._<t.label>?.line ?? 0)"
TokenPropertyRefPos(t)     ::= "(<ctx(t)>._<t.label>?.column ?? 0)"
TokenPropertyRefChannel(t) ::= "(<ctx(t)>._<t.label>?.channel ?? 0)"
TokenPropertyRefIndex(t)   ::= "(<ctx(t)>._<t.label>?.tokenIndex ?? 0)"
TokenPropertyRefInt(t)     ::= "Number(<ctx(t)>._<t.label>?.text ?? '0')"

RulePropertyRefStart(r)  ::= "(<ctx(r)>._<r.label>?.start)"
RulePropertyRefStop(r)   ::= "(<ctx(r)>._<r.label>?.stop)"
RulePropertyRefText(r)   ::= "(<ctx(r)>._<r.label> != null ? this.tokenStream.getTextFromRange(<ctx(r)>._<r.label>.start, <ctx(r)>._<r.label>.stop) : '')"
RulePropertyRefCtx(r)    ::= "<ctx(r)>._<r.label>"
RulePropertyRefParser(r) ::= "this"

ThisRulePropertyRefStart(r)  ::= "localContext.start"
ThisRulePropertyRefStop(r)   ::= "localContext.stop"
ThisRulePropertyRefText(r)   ::= "this.tokenStream.getTextFromRange(localContext.start, this.tokenStream.LT(-1))"
ThisRulePropertyRefCtx(r)    ::= "localContext"
ThisRulePropertyRefParser(r) ::= "this"

NonLocalAttrRef(s)            ::= "(this.getInvokingContext(<s.ruleIndex>) as <s.ruleName; format=\"cap\">Context).<s.escapedName>"
SetNonLocalAttr(s, rhsChunks) ::=
    "(this.getInvokingContext(<s.ruleIndex>) as <s.ruleName; format=\"cap\">Context).<s.escapedName> = <rhsChunks>;"

AddToLabelList(a) ::= "<ctx(a.label)>._<a.listName>.push(<labelref(a.label)>!);"

TokenDecl(t) ::= "_<t.escapedName>?: <TokenLabelType()> | null"
TokenTypeDecl(t) ::= "let <t.escapedName>: number;"
TokenListDecl(t) ::= "_<t.escapedName>: antlr.Token[] = []"
RuleContextDecl(r) ::= "_<r.escapedName>?: <r.ctxName>"
RuleContextListDecl(rdecl) ::= "_<rdecl.escapedName>: <rdecl.ctxName>[] = []"

ContextTokenGetterDecl(t)      ::= <<
public <t.escapedName>(): antlr.TerminalNode<if(t.optional)> | null<endif> {
    return this.getToken(<parser.name>.<t.name>, 0)<if(!t.optional)>!<endif>;
}
>>
ContextTokenListGetterDecl(t)  ::= <<
public <t.name>(): antlr.TerminalNode[];
>>
ContextTokenListIndexedGetterDecl(t)  ::= <<
public <t.name>(i: number): antlr.TerminalNode | null;
public <t.name>(i?: number): antlr.TerminalNode | null | antlr.TerminalNode[] {
	if (i === undefined) {
		return this.getTokens(<parser.name>.<t.name>);
	} else {
		return this.getToken(<parser.name>.<t.name>, i);
	}
}
>>
ContextRuleGetterDecl(r) ::= <<
public <r.name>(): <r.ctxName><if(r.optional)> | null<endif> {
    return this.getRuleContext(0, <r.ctxName>)<if(!r.optional)>!<endif>;
}
>>
ContextRuleListGetterDecl(r) ::= <<
public <r.escapedName>(): <r.ctxName>[];
>>
ContextRuleListIndexedGetterDecl(r) ::= <<
public <r.escapedName>(i: number): <r.ctxName> | null;
public <r.escapedName>(i?: number): <r.ctxName>[] | <r.ctxName> | null {
    if (i === undefined) {
        return this.getRuleContexts(<r.ctxName>);
    }

    return this.getRuleContext(i, <r.ctxName>);
}
>>

LexerRuleContext() ::= "antlr.ParserRuleContext"

/** The rule context name is the rule followed by a suffix; e.g.,
 *    r becomes rContext.
 */
RuleContextNameSuffix() ::= "Context"

ImplicitTokenLabel(tokenName) ::= "<tokenName>"
ImplicitRuleLabel(ruleName)   ::= "<ruleName>"
ImplicitSetLabel(id)          ::= "_tset<id>"
ListLabelName(label)          ::= "<label>"

CaptureNextToken(d) ::= "<d.varName> = this.tokenStream.LT(1);"
CaptureNextTokenType(d) ::= "<d.varName> = this.tokenStream.LA(1);"

StructDecl(struct,ctorAttrs,attrs,getters,dispatchMethods,interfaces,extensionMembers,signatures)
    ::= <<
export class <struct.escapedName> extends <if(contextSuperClass)><contextSuperClass><else>antlr.ParserRuleContext<endif><if(interfaces)> implements <interfaces; separator=", "><endif> {
    <attrs:{a | public <a>;}; separator="\n">
    <if(struct.ctorAttrs)>
    public constructor(parent: antlr.ParserRuleContext | null, invokingState: number<struct.ctorAttrs:{a |, <a.escapedName>: <a.type>}>) {
        super(parent, invokingState);
        <struct.ctorAttrs:{a | this.<a.escapedName> = <a.escapedName>;}; separator="\n">
    }
    <else>
    public constructor(parent: antlr.ParserRuleContext | null, invokingState: number) {
        super(parent, invokingState);
    }
    <endif>
    <getters:{g | <g>}; separator="\n">
    public override get ruleIndex(): number {
        return <parser.name>.RULE_<struct.derivedFromName>;
    }
<if(struct.provideCopyFrom)><! don't need copy unless we have subclasses !>
    public override copyFrom(ctx: <struct.name>): void {
        super.copyFrom(ctx);
        <struct.attrs:{a | this.<a.escapedName> = ctx.<a.escapedName>;}; separator="\n">
    }
<endif>
    <dispatchMethods; separator="\n">
    <extensionMembers; separator="\n">
}
>>

AltLabelStructDecl(struct,attrs,getters,dispatchMethods) ::= <<
export class <struct.escapedName> extends <struct.parentRule; format="cap">Context {
    <attrs:{a | public <a>;}; separator="\n">
    public constructor(ctx: <currentRule.currentRule.name; format="cap">Context) {
        super(ctx.parent, ctx.invokingState<currentRule.ruleCtx.ctorAttrs:{a | , ctx.<a.d.name>}>);
        super.copyFrom(ctx);
    }
    <getters:{g | <g>}; separator="\n">
    <dispatchMethods; separator="\n">
}
>>

ListenerDispatchMethod(method) ::= <<
public override <if(method.isEnter)>enter<else>exit<endif>Rule(listener: <parser.grammarName>Listener): void {
    if(listener.<if(method.isEnter)>enter<else>exit<endif><struct.derivedFromName; format="cap">) {
         listener.<if(method.isEnter)>enter<else>exit<endif><struct.derivedFromName; format="cap">(this);
    }
}
>>

VisitorDispatchMethod(method) ::= <<
public override accept\<Result>(visitor: <parser.grammarName>Visitor\<Result>): Result | null {
    if (visitor.visit<struct.derivedFromName; format="cap">) {
        return visitor.visit<struct.derivedFromName; format="cap">(this);
    } else {
        return visitor.visitChildren(this);
    }
}
>>

AttributeDecl(d) ::= "<d.escapedName>: <d.type><if(d.initValue)> = <d.initValue><endif>"

/** If we don't know location of label def x, use this template */
labelref(x) ::= "<if(!x.isLocal)><typedContext(x.ctx)>.<endif>_<x.escapedName>"

/** For any action chunk, what is correctly-typed context struct ptr? */
ctx(actionChunk) ::= "<typedContext(actionChunk.ctx)>"

// only casts localContext to the type when the cast isn't redundant (i.e. to a sub-context for a labeled alt)
typedContext(ctx) ::= "<if(ctx.provideCopyFrom)>(localContext as <ctx.name>)<else>localContext<endif>"

// used for left-recursive rules
recRuleAltPredicate(ruleName,opPrec)  ::= "this.precpred(this.context, <opPrec>)"
recRuleSetReturnAction(src,name)      ::= "$<name>=$<src>.<name>;"
recRuleSetStopToken()                 ::= "this.context!.stop = this.tokenStream.LT(-1);"

recRuleAltStartAction(ruleName, ctxName, label, isListLabel) ::= <<
localContext = new <ctxName>Context(parentContext, parentState);
<if(label)>
<if(isListLabel)>
localContext._<label>.push(previousContext);
<else>
localContext._<label> = previousContext;
<endif>
<endif>
this.pushNewRecursionContext(localContext, _startState, <parser.name>.RULE_<ruleName>);
>>

recRuleLabeledAltStartAction(ruleName, currentAltLabel, label, isListLabel) ::= <<
localContext = new <currentAltLabel; format="cap">Context(new <ruleName; format="cap">Context(parentContext, parentState));
<if(label)>
<if(isListLabel)>
(localContext as <currentAltLabel; format="cap">Context)._<label>.push(previousContext);
<else>
(localContext as <currentAltLabel; format="cap">Context)._<label> = previousContext;
<endif>
<endif>
this.pushNewRecursionContext(localContext, _startState, <parser.name>.RULE_<ruleName>);
>>

recRuleReplaceContext(ctxName) ::= <<
localContext = new <ctxName>Context(localContext);
this.context = localContext;
previousContext = localContext;
>>

recRuleSetPrevCtx() ::= <<
if (this.parseListeners != null) {
    this.triggerExitRuleEvent();
}
previousContext = localContext;
>>


LexerFile(lexerFile, lexer, namedActions) ::= <<
<fileHeader(lexerFile.grammarFileName, lexerFile.ANTLRVersion)>

import * as antlr from "antlr4ng";
import { Token } from "antlr4ng";

<namedActions.header>
<lexer>
>>

Lexer(lexer, atn, actionFuncs, sempredFuncs, superClass, isLexer=true) ::= <<

export class <lexer.name> extends <superClass; null="antlr.Lexer"> {
    <lexer.tokens:{k | public static readonly <k> = <lexer.tokens.(k)>;}; separator="\n">
    <rest(lexer.modes):{m | public static readonly <m> = <i>;}; separator="\n">

    public static readonly channelNames = [
        "DEFAULT_TOKEN_CHANNEL", "HIDDEN"<if (lexer.channelNames)>, <lexer.channelNames:{c| "<c>"}; separator=", ", wrap, anchor><endif>
    ];

    public static readonly literalNames = [
        <lexer.literalNames:{t | <t>}; null="null", separator=", ", wrap, anchor>
    ];

    public static readonly symbolicNames = [
        <lexer.symbolicNames:{t | <t>}; null="null", separator=", ", wrap, anchor>
    ];

    public static readonly modeNames = [
        <lexer.modes:{m| "<m>",}; separator=" ", wrap, anchor>
    ];

    public static readonly ruleNames = [
        <lexer.ruleNames:{r | "<r>",}; separator=" ", wrap, anchor>
    ];

    <namedActions.members>

    public constructor(input: antlr.CharStream) {
        super(input);
        this.interpreter = new antlr.LexerATNSimulator(this, <lexer.name>._ATN, <lexer.name>.decisionsToDFA, new antlr.PredictionContextCache());
    }

    public get grammarFileName(): string { return "<lexer.grammarFileName>"; }

    public get literalNames(): (string | null)[] { return <lexer.name>.literalNames; }
    public get symbolicNames(): (string | null)[] { return <lexer.name>.symbolicNames; }
    public get ruleNames(): string[] { return <lexer.name>.ruleNames; }

    public get serializedATN(): number[] { return <lexer.name>._serializedATN; }

    public get channelNames(): string[] { return <lexer.name>.channelNames; }

    public get modeNames(): string[] { return <lexer.name>.modeNames; }

    <dumpActions(lexer, "", actionFuncs, sempredFuncs)>
    <atn>

    private static readonly vocabulary = new antlr.Vocabulary(<lexer.name>.literalNames, <lexer.name>.symbolicNames, []);

    public override get vocabulary(): antlr.Vocabulary {
        return <lexer.name>.vocabulary;
    }

    private static readonly decisionsToDFA = <lexer.name>._ATN.decisionToState.map( (ds: antlr.DecisionState, index: number) => new antlr.DFA(ds, index) );
}
>>

SerializedATN(model, className={<if(isLexer)><lexer.name><else><parser.name><endif>}) ::= <<
public static readonly _serializedATN: number[] = [
    <model.serialized: {s | <s>}; separator=",", wrap>
];

private static __ATN: antlr.ATN;
public static get _ATN(): antlr.ATN {
    if (!<className>.__ATN) {
        <className>.__ATN = new antlr.ATNDeserializer().deserialize(<className>._serializedATN);
    }

    return <className>.__ATN;
}

>>

/** Using a type to init value map, try to init a type; if not in table
 *    must be an object, default value is "null".
 */
initValue(typeName) ::= <<
<typescriptTypeInitMap.(typeName)>
>>

codeFileExtension() ::= ".ts"
