/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


options
{
   MULTI=true;
   STATIC=false;
   JAVA_TEMPLATE_TYPE="modern";
   VISITOR=true;
   NODE_SCOPE_HOOK=true;
   NODE_CLASS="JexlNode";
   UNICODE_INPUT=true;
   KEEP_LINE_COLUMN=true;
   TRACK_TOKENS=true;
   CACHE_TOKENS=true;
   ERROR_REPORTING=false;
//   DEBUG_PARSER=true;
//   DEBUG_TOKEN_MANAGER=true;
}

PARSER_BEGIN(Parser)

package org.apache.commons.jexl3.parser;

import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;

import org.apache.commons.jexl3.JexlInfo;
import org.apache.commons.jexl3.JexlFeatures;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.internal.Scope;

public final class Parser extends JexlParser
{
        public Parser(JexlParser parser) {
            super(parser);
            jj_input_stream = new SimpleCharStream(new StringProvider(""), 1, 1);
            token_source = new ParserTokenManager(jj_input_stream);
            token = new Token();
            token.next = jj_nt = token_source.getNextToken();
        }

    /**
     * Parses a JEXL source string and returns the corresponding AST.
     * @param jexlInfo the JEXL info to use, may be null
     * @param jexlFeatures the JEXL features to use
     * @param jexlSrc the JEXL source string to parse
     * @param jexlScope the scope to use for parsing
     * @return the AST for the JEXL source string
     * @throws JexlException.Tokenization if there is a tokenization error
     * @throws JexlException.Parsing if there is a parsing error
     */
    public ASTJexlScript parse(JexlInfo jexlInfo, JexlFeatures jexlFeatures, String jexlSrc, Scope jexlScope) {
        JexlFeatures previous = getFeatures();
        try {
            // lets do the 'Unique Init' in here to be safe - it's a pain to remember
            this.info = jexlInfo != null? jexlInfo : new JexlInfo();
            this.source = jexlSrc;
            if (this.parent == null) {
                setFeatures(jexlFeatures);
                this.pragmas = null;
            }
            this.scopeReference.set(jexlScope);
            token_source.comparatorNames = jexlFeatures.supportsComparatorNames();
            ReInit(jexlSrc);
            ASTJexlScript script = jexlFeatures.supportsScript()
                ? JexlScript(jexlScope, jexlFeatures)
                : JexlExpression(jexlScope, jexlFeatures);
            script.jjtSetValue(info.detach());
            script.setPragmas(pragmas != null
                             ? Collections.unmodifiableMap(pragmas)
                             : Collections.emptyMap());
            return script;
        } catch (TokenMgrException xtme) {
            throw new JexlException.Tokenization(info, xtme).clean();
        } catch (ParseException xparse) {
            Token errortok = errorToken(jj_lastpos, jj_scanpos, token.next, token);
            throw new JexlException.Parsing(info.at(errortok.beginLine, errortok.beginColumn), errortok.image).clean();
        } finally {
            token_source.defaultLexState = DEFAULT;
            cleanup(previous);
            jjtree.reset();
        }
    }
}

PARSER_END(Parser)

TOKEN_MGR_DECLS : {
    boolean comparatorNames = false;
    boolean jexl331 = true;
}

/***************************************
 *     Skip & Number literal tokens
 ***************************************/

<*> SKIP : /* WHITE SPACE */
{
      " "
    | "\t"
    | "\n"
    | "\r"
    | "\f"
    | <"##" (~["\n","\r"])* ("\n" | "\r" | "\r\n")? >
    | <"/*" (~["*"])* "*" ("*" | ~["*","/"] (~["*"])* "*")* "/">
    | <"//" (~["\n","\r"])* ("\n" | "\r" | "\r\n")? >
}

<DEFAULT> TOKEN : /* Exception handling. */
{
      < THROW : "throw" >
    | < TRY : "try" >
    | < CATCH : "catch" >
    | < FINALLY : "finally" >
}

<DEFAULT> TOKEN : /* Type check. */
{
      < ISA : "instanceof" >
}

<*> TOKEN : { /* SEPARATORS */
      < LPAREN : "(">
    | < RPAREN : ")">
    | < LCURLY : "{" >
    | < RCURLY : "}" >
    | < LBRACKET : "[" >
    | < QLBRACKET : "?[" >
    | < RBRACKET : "]" >
    | < SEMICOL : ";" >
    | < COLON : ":" >
    | < COMMA : "," >
    | < DOT : "." > : DOT_ID
    | < QDOT : "?." > : DOT_ID
    | < ELIPSIS : "..." >
}

<*> TOKEN : { /* CONDITIONALS */
      < QMARK : "?" >
    | < ELVIS : "?:" >
    | < NULLP : "??" >
    | < AND : "&&" >
    | < OR : "||" >
    | < NISA : "!instanceof">
}

<DEFAULT> TOKEN : { /* CONDITIONALS */
      < _AND :  "and" >
    | < _OR: "or" >
}

<*> TOKEN : { /* COMPARISONS */
      < eq : "==" >
    | < ne : "!=" >
    | < gt : ">" >
    | < ge : ">=" >
    | < lt : "<" >
    | < le : "<=" >
    | < req : "=~" > // regexp equal
    | < rne : "!~" > // regexp not equal
    | < seq : "=^" > // starts equal
    | < eeq : "=$" > // ends equal
    | < sne : "!^" > // start not equal
    | < ene : "!$" > // ends not equal
    | < eqstrict : "===" > // strict equal
    | < neqstrict : "!==" > // strict not equal
}

<NEVER> TOKEN : { /* COMPARISONS */
      < EQ : "eq" >
    | < NE : "ne" >
    | < GT : "gt" >
    | < GE : "ge" >
    | < LT : "lt" >
    | < LE : "le"  >
}

<*> TOKEN : { /* OPERATORS */
      < plus_assign : "+=" >
    | < minus_assign : "-=" >
    | < mult_assign : "*=" >
    | < div_assign : "/=" >
    | < mod_assign : "%=" >
    | < and_assign : "&=" >
    | < or_assign : "|=" >
    | < xor_assign : "^=" >
    | < lshift_assign : "<<=" >
    | < rshiftu_assign : ">>>=" >
    | < rshift_assign : ">>=" >

    | < assign : "=" >
    | < plus : "+" >
    | < plusplus : "++" >
    | < minus : "-" >
    | < minusminus : "--" >
    | < mult : "*" >
    | < div : "/" >
    | < mod : "%"  >
    | < not : "!" >
    | < and : "&" >
    | < or : "|" >
    | < xor : "^" >
    | < lshift : "<<" >
    | < rshiftu : ">>>" >
    | < rshift : ">>" >

    | < tilda : "~" >
    | < range : ".." >
}

<DEFAULT> TOKEN : { /* OPERATORS */
      < NOT : "not" >
    | < DIV : "div" >
    | < MOD : "mod" >
}

<*> TOKEN : /* KEYWORDS */
{
      < LAMBDA : "->" >
    | < FATARROW : "=>" >
    | < PRAGMA : "#pragma" >
}

<DEFAULT> TOKEN : /* KEYWORDS */
{
      < IF : "if" >
    | < ELSE : "else" >
    | < DO : "do" >
    | < WHILE : "while" >
    | < FOR : "for" >
    | < BREAK : "break" >
    | < CONTINUE : "continue" >
    | < FUNCTION : "function" >
    | < RETURN : "return" >
    | < NEW : "new" >
    | < SIZE : "size" >
    | < EMPTY : "empty" >
    | < VAR : "var" >
    | < LET : "let" >
    | < CONST : "const" >
    | < NULL : "null" >
    | < TRUE : "true" >
    | < FALSE : "false" >
    | < SWITCH : "switch" >
    | < CASE : "case" >
    | < CASE_DEFAULT : "default" >
    | < IMPORT : "import" >
}


/***************************************
 *     Identifier & String tokens
 ***************************************/
<DEFAULT> TOKEN : /* NaN */
{
    < NAN_LITERAL : "NaN" >
}

<DEFAULT> TOKEN : /* ANNOTATION */
{
  < ANNOTATION: "@" ( [ "0"-"9", "a"-"z", "A"-"Z", "_", "$" ])+ >
}

<DOT_ID> TOKEN : /* IDENTIFIERS */
{
  < DOT_IDENTIFIER: ( [ "0"-"9", "a"-"z", "A"-"Z", "_", "$", "@" ])+ > : DEFAULT
}

<DEFAULT> TOKEN : /* IDENTIFIERS */
{
  < IDENTIFIER: <LETTER> (<LETTER>|<DIGIT>|<ESCAPE>)* >
  {
      matchedToken.image = StringParser.unescapeIdentifier(matchedToken.image);
      final int length = matchedToken.image.length();
      if (comparatorNames && length == 2) {
          switch (matchedToken.image) {
             case "ne" : matchedToken.kind = NE; break;
             case "eq" : matchedToken.kind = EQ; break;
             case "lt" : matchedToken.kind = LT; break;
             case "le" : matchedToken.kind = LE; break;
             case "gt" : matchedToken.kind = GT; break;
             case "ge" : matchedToken.kind = GE; break;
          }
      } else if (jexl331 && length >= 3 && length <= 10) {
          switch (matchedToken.image) {
             case "try" : matchedToken.kind = TRY; break;
             case "catch" : matchedToken.kind = CATCH; break;
             case "finally" : matchedToken.kind = FINALLY; break;
             case "throw" : matchedToken.kind = THROW; break;
             case "instanceof": matchedToken.kind = ISA; break;
             case "!instanceof": matchedToken.kind = NISA; break;
          }
      }
  }
|
  < #LETTER: [ "a"-"z", "A"-"Z", "_", "$", "@" ] >
|
  < #DIGIT: [ "0"-"9"] >
|
  < #ESCAPE: "\\" [" ", "'", "\"", "\\"] >
}

<DEFAULT> TOKEN :
{
  < REGISTER: "#" (["0"-"9"])+ >
}

<DEFAULT> TOKEN : /* LITERALS */
{
 <INTEGER_LITERAL:
      <DECIMAL_LITERAL> (<INT_SFX>)?
    | <HEX_LITERAL> (<INT_SFX>)?
    | <OCTAL_LITERAL> (<INT_SFX>)?
 >
    | <#DECIMAL_LITERAL: ["1"-"9"] (["0"-"9"])*>
    | <#HEX_LITERAL: "0" ["x","X"] (["0"-"9","a"-"f","A"-"F"])+>
    | <#OCTAL_LITERAL: "0" (["0"-"7"])*>
    | <#INT_SFX : ["l","L","h","H"]>
|
<FLOAT_LITERAL:
      (["0"-"9"])+ "." (["0"-"9"])+ (<FLT_SFX>)?
    | (["0"-"9"])+ (".")? (<FLT_SFX>)
    | "." (["0"-"9"])+ (<FLT_SFX>)
    | "#NaN"
>
    |  <#EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+>
    |  <#FLT_CLS : ["f","F","d","D","b","B"]>
    |  <#FLT_SFX : <EXPONENT> (<FLT_CLS>)? | <FLT_CLS> >
}

<*> TOKEN :
{
  < STRING_LITERAL:
    "\"" (~["\"","\\","\n","\r","\u2028","\u2029"] | "\\" ~["\n","\r","\u2028","\u2029"])* "\""
  |
    "'" (~["'","\\","\n","\r","\u2028","\u2029"] | "\\" ~["\n","\r","\u2028","\u2029"])* "'"
  > : DEFAULT
}

<*> TOKEN :
{
  < JXLT_LITERAL:
    "`" (~["`","\\","\u2028"-"\u202E","\u2066"-"\u2069"] | "\\" ~["\u0000"])* "`"
  > : DEFAULT
}

<*> TOKEN :
{
  < REGEX_LITERAL:
    "~" "/" (~["/","\n","\r","\t","\f","\b","\u2028","\u2029"] | "\\" "/" )* "/"
  >
}

/***************************************
 *      Statements
 ***************************************/

ASTJexlScript JexlScript(Scope frame, JexlFeatures features) : {
    jjtThis.setScope(frame);
    jjtThis.setFeatures(features);
}
{
   {
        pushUnit(jjtThis);
   }
        ( Import())* ( Pragma() | { controlPragmaAnywhere(); } Statement() )* <EOF>
   {
        popUnit(jjtThis);
        return jjtThis.script();
   }
}

ASTJexlScript JexlExpression(Scope frame, JexlFeatures features) #JexlScript : {
    jjtThis.setScope(frame);
    jjtThis.setFeatures(features);
}
{
   {
        pushUnit(jjtThis);
   }
   ( Pragma() )* { controlPragmaAnywhere(); } ( Expression() )? <EOF>
   {
        popUnit(jjtThis);
        return jjtThis.script();
   }
}

void Annotation() #Annotation :
{
    Token t;
}
{
    t=<ANNOTATION> { jjtThis.setName(t.image); } (LOOKAHEAD(2) Arguments() )?
}

void AnnotatedStatement() #AnnotatedStatement : {}
 {
    ( LOOKAHEAD(2) Annotation() )+
        // curly brackets expressions (set, map) have priority over statements
        ( LOOKAHEAD(<LCURLY> ( <RCURLY>  | <COLON> | ( Expression() ( <RCURLY> | <COLON> | <COMMA>) ) ))
          Expression()
        | Statement() )
 }

void Statement() #void : {}
{
    LOOKAHEAD(3) Var()
    |
    LOOKAHEAD(3) FunctionStatement()
    |
    StatementSink()
}

void StatementSink() #void : {}
{
    <SEMICOL>
    | AnnotatedStatement()
    | IfStatement()
    | ForeachStatement()
    | WhileStatement()
    | DoWhileStatement()
    | ReturnStatement()
    | Continue()
    | Break()
    | ThrowStatement()
    | TryStatement()
    | LOOKAHEAD(3) SwitchStatement()
    | LOOKAHEAD(LambdaLookahead()) Lambda()
    | LOOKAHEAD(Expression()) ExpressionStatement()
    | Block()
    | LOOKAHEAD(<VAR>, { !getFeatures().isLexical()} ) Var()
}

void Block() #Block : {}
{
    <LCURLY> { pushUnit(jjtThis); } ( Pragma() | Statement() )* { popUnit(jjtThis); } <RCURLY>
}

void FunctionStatement() #JexlLambda : {}
{
<FUNCTION> DeclareFunction() { beginLambda(jjtThis); } Parameters() ( LOOKAHEAD(3) Block() | Expression()) { endLambda(jjtThis); }
}

void ExpressionStatement() #void : {}
{
    Expression() (LOOKAHEAD(2, { isAmbiguousStatement(SEMICOL) } ) Expression() #Ambiguous(1))* (LOOKAHEAD(1) <SEMICOL>)?
}


void IfStatement() : {}
{
    <IF> <LPAREN> Expression() <RPAREN> (LOOKAHEAD(1) Block() | StatementSink())
    ( LOOKAHEAD(2) <ELSE> <IF> <LPAREN> Expression() <RPAREN> (LOOKAHEAD(1) Block() | StatementSink()) )*
    ( LOOKAHEAD(1) <ELSE>  (LOOKAHEAD(1) Block() | StatementSink()) )?
}

void TryStatement() : {}
{
     <TRY> ( TryResources() | Block())
     ( <CATCH> { pushUnit(jjtThis); } <LPAREN> InlineVar() <RPAREN> Block() { jjtThis.catchClause(); popUnit(jjtThis);})?
     ( <FINALLY>  Block() { jjtThis.finallyClause(); } )?
}

void TryResources() : {}
{
    {
        pushUnit(jjtThis);
    }
<LPAREN>
    TryResource() ( LOOKAHEAD(2) <SEMICOL> TryResource() )* (<SEMICOL>)?
<RPAREN>
    Block()
    {
        popUnit(jjtThis);
    }
}

void TryResource() #void : {}
{
LOOKAHEAD(2) Var() | Identifier(true)
}

void WhileStatement() : {}
{
    <WHILE> <LPAREN> Expression() <RPAREN>  { loopCount.incrementAndGet(); }  (LOOKAHEAD(1) Block() | StatementSink()) { loopCount.decrementAndGet(); }
}

void DoWhileStatement() : {}
{
    <DO> { loopCount.incrementAndGet(); } (LOOKAHEAD(1) Block() | StatementSink()) <WHILE> <LPAREN> Expression() <RPAREN> { loopCount.decrementAndGet(); }
}

void ReturnStatement() : {
    this.autoSemicolon = false;
 }
{
    <RETURN> (LOOKAHEAD(2) ExpressionStatement() )? { this.autoSemicolon = true; }
}

void ThrowStatement() : {
    this.autoSemicolon = false;
 }
{
    <THROW> (LOOKAHEAD(2) ExpressionStatement() )? { this.autoSemicolon = true; }
}

void Continue() #Continue : {
    Token t;
}
{
    t=<CONTINUE> { if (loopCount.get() == 0) { throwParsingException(t); } }
}

void Break() #Break : {
    Token t;
}
{
    t=<BREAK> { if (loopCount.get() == 0) { throwParsingException(t); } }
}

void ForeachStatement() : {
 int loopForm = 0;
}
{
    {
        pushUnit(jjtThis);
    }
    <FOR> <LPAREN>
    (
        LOOKAHEAD(3) InlineVar() <COLON> Expression() { loopForm = 0; }
    |
        ((LOOKAHEAD(1) Var() | Expression()) { loopForm = 1; })? <SEMICOL>
        (Expression() { loopForm |= 2; })? <SEMICOL>
        (Expression() { loopForm |= 4; })? { loopForm |= 8; }
    )
    <RPAREN>
    {
        loopCount.incrementAndGet();
    }
        (LOOKAHEAD(1) Block() | StatementSink() )
    {
        loopCount.decrementAndGet();
        jjtThis.setLoopForm(loopForm);
        popUnit(jjtThis);
    }
}

void InlineVar() #Reference : {}
{
    <VAR> DeclareVar(false, false)
|
    <LET> DeclareVar(true, false)
|
    <CONST> DeclareVar(true, true)
|
    Identifier(true)
}

void Var() #void : {}
{
    (<VAR> DefineVar() (<COMMA> DefineVar())*) #DefineVars(>1)
    |
    (<LET> DefineLet() (<COMMA> DefineLet())*) #DefineVars(>1)
    |
    (<CONST> DefineConst() (<COMMA> DefineConst())*) #DefineVars(>1)
}

 void DefineVar() #void : {}
 {
    DeclareVar(false, false) (LOOKAHEAD(1) <assign> Expression() #Assignment(2))?
 }

void DefineLet() #void : {}
{
    DeclareVar(true, false) (LOOKAHEAD(1) <assign> Expression() #Assignment(2))?
}

void DefineConst() #void : {}
{
    DeclareVar(true, true) <assign> Expression() #Assignment(2)
}

void DeclareVar(boolean lexical, boolean constant) #Var :
{
    Token t;
}
{
    t=<IDENTIFIER> { declareVariable(jjtThis, t, lexical, constant); }
}

void DeclareFunction() #Var :
{
    Token t;
}
{
    t=<IDENTIFIER> { declareFunction(jjtThis, t); }
}

void SwitchStatement() #SwitchStatement :
{
    ASTSwitchStatement.Helper helper = new ASTSwitchStatement.Helper();
    SwitchSet cases = switchSet();
}
{
    <SWITCH> <LPAREN> Expression() <RPAREN> <LCURLY>
      ( LOOKAHEAD(SwitchStatementLookahead()) ( SwitchStatementCase(cases) { helper.defineCase(cases); } )*
        |
        { jjtThis.isStatement = false; pushLoop(); } ( SwitchExpressionCase(cases) { helper.defineCase(cases); } )*) { popLoop(); }
    <RCURLY>
{
    helper.defineSwitch(jjtThis);
}
}

void SwitchStatementLookahead() #void : {}
{
    <CASE> constLiteral() <COLON> | <CASE_DEFAULT> <COLON>
}

void SwitchStatementCase(SwitchSet cases) #CaseStatement :
{
    Object constant;
    List<Object> constants;
    loopCount.incrementAndGet();
}
{
    ( ( LOOKAHEAD(2) <CASE> constant=constLiteral() <COLON>
        { constants = Collections.singletonList(constant); jjtThis.setValues(constants); cases.addAll(constants); } )+
        |
        <CASE_DEFAULT> <COLON> )
    ( LOOKAHEAD(<LCURLY>) Block() | (StatementSink() )+ )?
{
    loopCount.decrementAndGet();
}
}

void SwitchExpression() #SwitchExpression :
{
    ASTSwitchStatement.Helper helper = new ASTSwitchStatement.Helper();
    SwitchSet cases = switchSet();
    pushLoop();
}
{
    <SWITCH> <LPAREN> Expression() <RPAREN> <LCURLY>
      ( SwitchExpressionCase(cases) { helper.defineCase(cases); } )*
    <RCURLY>
{
    helper.defineSwitch(jjtThis);
    popLoop();
}
}

void SwitchExpressionCase(SwitchSet cases) #CaseExpression :
{
    List<Object> constants = new ArrayList<Object>(1);
}
{
   ( LOOKAHEAD(2) <CASE> ConstLiterals(constants) <LAMBDA> { cases.addAll(constants); jjtThis.setValues(constants); }
    |
    <CASE_DEFAULT> <LAMBDA> )
    ( LOOKAHEAD(3) Block() | Expression() (<SEMICOL>)? )
}

void ConstLiterals(List<Object> constants) #void :
{
    Object constant;
}
{
    constant=constLiteral() { constants.add(constant); } (LOOKAHEAD(2) <COMMA> constant=constLiteral() { constants.add(constant); })*
}


Object constLiteral() #void :
{
    Token s = null;
    Token v;
    Object result;
    LinkedList<String> lstr = new LinkedList<String>();
}
{
  (
      LOOKAHEAD(2) (s=<plus>|s=<minus>)? v=<INTEGER_LITERAL> { result = NumberParser.parseInteger(s, v); }
    | LOOKAHEAD(2) (s=<plus>|s=<minus>)? v=<FLOAT_LITERAL> { result = NumberParser.parseDouble(s, v); }
    | LOOKAHEAD(1) v=<STRING_LITERAL> { result = Parser.buildString(v.image, true); }
    | LOOKAHEAD(1) <TRUE> { result = true; }
    | LOOKAHEAD(1) <FALSE> { result = false; }
    | LOOKAHEAD(1) <NULL> { result = null; }
    | LOOKAHEAD(1) <NAN_LITERAL> { result = Double.NaN; }
    | LOOKAHEAD(1) propertyKey(lstr) { result = resolveConstant(stringify(lstr)); }
  )
  {
    return result;
  }
}

void Import() #void :
{
    List<String> imports = new ArrayList<String>();
}
{
    <IMPORT> propertyKey(imports) (LOOKAHEAD(1) <SEMICOL>)?
    {
       addImport(stringify(imports));
    }
}

void Pragma() #void :
{
    LinkedList<String> lstr = new LinkedList<String>();
    Object value;
}
{
<PRAGMA> propertyKey(lstr)  value=pragmaValue() { declarePragma(stringify(lstr), value); }
}

void propertyKey(List<String> lstr) #void :
{
    Token t;
}
{
    t=<IDENTIFIER>  { lstr.add(t.image); } ( LOOKAHEAD(<DOT>) propertyKey(lstr) )*
   |
    <DOT> t=<DOT_IDENTIFIER> { lstr.add(t.image); }
}

Object pragmaValue() #void :
{
Token s = null;
Token v;
LinkedList<String> lstr = new LinkedList<String>();
Object result;
}
{
  (
      LOOKAHEAD(2) (s=<plus>|s=<minus>)? v=<INTEGER_LITERAL> { result = NumberParser.parseInteger(s, v); }
    | LOOKAHEAD(2) (s=<plus>|s=<minus>)? v=<FLOAT_LITERAL> { result = NumberParser.parseDouble(s, v); }
    | LOOKAHEAD(1) v=<STRING_LITERAL> { result = Parser.buildString(v.image, true); }
    | LOOKAHEAD(1)  propertyKey(lstr) { result = stringify(lstr); }
    | LOOKAHEAD(1) <TRUE> { result = true; }
    | LOOKAHEAD(1) <FALSE> { result = false; }
    | LOOKAHEAD(1) <NULL> { result = null; }
    | LOOKAHEAD(1) <NAN_LITERAL> { result = Double.NaN; }
  )
  {
    return result;
  }
}


/***************************************
 *      Expression syntax
 ***************************************/

void Expression() #void : {}
{
      AssignmentExpression()
}

void AssignmentExpression() #void : {}
{
  ConditionalExpression()
  ( LOOKAHEAD(2) (
    <plus_assign>  Expression() #SetAddNode(2)
  |
    <mult_assign>  Expression() #SetMultNode(2)
  |
    <div_assign>  Expression() #SetDivNode(2)
  |
    <mod_assign>  Expression() #SetModNode(2)
  |
    <and_assign>  Expression() #SetAndNode(2)
  |
    <or_assign>  Expression() #SetOrNode(2)
  |
    <xor_assign> Expression() #SetXorNode(2)
  |
    <minus_assign>  Expression() #SetSubNode(2)
  |
    <lshift_assign>  Expression() #SetShiftLeftNode(2)
  |
    <rshift_assign>  Expression() #SetShiftRightNode(2)
  |
    <rshiftu_assign>  Expression() #SetShiftRightUnsignedNode(2)
  |
    <assign> Expression() #Assignment(2)
  ) )?
}

/***************************************
 *      Conditional & relational
 ***************************************/

void ConditionalExpression() #void : {}
{
  ConditionalOrExpression()
  ( LOOKAHEAD(2) (
    <QMARK> Expression() <COLON> Expression() #TernaryNode(3)
     |
    <ELVIS> Expression() #TernaryNode(2)
  |
    <NULLP> Expression() #NullpNode(2)
  ) )?
}

void ConditionalOrExpression() #void : {}
{
  ( ConditionalAndExpression()
  ( LOOKAHEAD(2) ( (<OR>|<_OR>) ConditionalAndExpression()  ) )* ) #OrNode(>1)
}

void ConditionalAndExpression() #void : {}
{
  ( InclusiveOrExpression()
  ( LOOKAHEAD(2) ( (<AND>|<_AND>) InclusiveOrExpression()  ) )* ) #AndNode(>1)
}

void InclusiveOrExpression() #void : {}
{
  ExclusiveOrExpression()
  ( LOOKAHEAD(2) ( <or> ExclusiveOrExpression() #BitwiseOrNode(2) ) )*
}

void ExclusiveOrExpression() #void : {}
{
  AndExpression()
  ( LOOKAHEAD(2) ( <xor> AndExpression() #BitwiseXorNode(2) ) )*
}

void AndExpression() #void : {}
{
  EqualityExpression()
  ( LOOKAHEAD(2) (<and> EqualityExpression() #BitwiseAndNode(2) ) )*
}

void EqualityExpression() #void : {}
{
  RelationalExpression()
  ( LOOKAHEAD(2) (
     (<eq> | <EQ>) RelationalExpression() #EQNode(2)
   |
     (<ne> | <NE>) RelationalExpression() #NENode(2)
   |
     <eqstrict> RelationalExpression() #EQSNode(2)
   |
     <neqstrict> RelationalExpression() #NESNode(2)
   |
     <range> RelationalExpression() #RangeNode(2) // range
  ) )?
}

void RelationalExpression() #void : {}
{
  ShiftExpression()
  ( LOOKAHEAD(2) (
    (<lt> |<LT>)  ShiftExpression() #LTNode(2)
   |
    (<gt> | <GT>) ShiftExpression() #GTNode(2)
   |
    (<le> | <LE>) ShiftExpression() #LENode(2)
   |
    (<ge> | <GE>) ShiftExpression() #GENode(2)
   |
    <req> ShiftExpression() #ERNode(2) // equals regexp
   |
    <rne> ShiftExpression() #NRNode(2) // not equals regexp
   |
    <seq> ShiftExpression() #SWNode(2) // starts with
   |
    <sne> ShiftExpression() #NSWNode(2) // not starts with
   |
    <eeq> ShiftExpression() #EWNode(2) // ends with
   |
    <ene> ShiftExpression() #NEWNode(2) // not ends with
   |
    <ISA> ShiftExpression() #InstanceOf(2) // instanceof
   |
    <NISA> ShiftExpression() #NotInstanceOf(2) // not instanceof
  ) )?
}

/***************************************
 *      Arithmetic
 ***************************************/
void ShiftExpression() #void : {}
{
    AdditiveExpression()
    ( LOOKAHEAD(2) (
      <lshift> AdditiveExpression() #ShiftLeftNode(2) // left shift
    |
      <rshift> AdditiveExpression() #ShiftRightNode(2) // right shift
    |
      <rshiftu> AdditiveExpression() #ShiftRightUnsignedNode(2) // right shift unsigned
    ) )*
}

void AdditiveExpression() #void : {}
{
  MultiplicativeExpression()
  ( LOOKAHEAD(2) (
    <plus> MultiplicativeExpression() #AddNode(2)
  |
    <minus> MultiplicativeExpression() #SubNode(2)
  ) )*
}

void MultiplicativeExpression() #void : {}
{
  UnaryExpression()
  ( LOOKAHEAD(2) (
    <mult> UnaryExpression() #MulNode(2)
  |
    (<div>|<DIV>) UnaryExpression() #DivNode(2)
  |
    (<mod>|<MOD>) UnaryExpression() #ModNode(2)
  ) )*
}

void UnaryExpression() #void : {}
{
    <minus> UnaryExpression() #UnaryMinusNode(1)
  |
    <plus> UnaryExpression() #UnaryPlusNode(1)
  |
    <tilda> UnaryExpression() #BitwiseComplNode(1)
  |
    (<not>|<NOT>) UnaryExpression() #NotNode(1)
  |
    <EMPTY> UnaryExpression() #EmptyFunction(1)
  |
    <SIZE> UnaryExpression() #SizeFunction(1)
  |
   <minusminus> UnaryExpression() #DecrementGetNode(1)
  |
   <plusplus> UnaryExpression() #IncrementGetNode(1)
  |
    PostfixExpression()
}

void PostfixOperator() #void : {}
{
    <plusplus> #GetIncrementNode(1)
  |
    <minusminus> #GetDecrementNode(1)
}

void PostfixExpression() #void : {}
{
   ValueExpression() [ LOOKAHEAD(1) PostfixOperator() ]
}

/***************************************
 *      Identifier & Literals
 ***************************************/

void Identifier(boolean top) :
{
    Token t;
}
{
    t=<IDENTIFIER> { jjtThis.setSymbol(top? checkVariable(jjtThis, t.image) : t.image); }
    |
    t=<REGISTER> { if (!getFeatures().supportsRegister()) throwParsingException(t); jjtThis.setSymbol(t.image); }
}


void NamespaceIdentifier()  #NamespaceIdentifier :
{
    Token ns;
    Token id;
}
{
   ns=<IDENTIFIER> <COLON> id=<IDENTIFIER> { jjtThis.setNamespace(ns.image, id.image); }
}

void Literal() #void :
{
   Token t;
}
{
  IntegerLiteral()
|
  FloatLiteral()
|
  BooleanLiteral()
|
  JxltLiteral()
|
  StringLiteral()
|
  RegexLiteral()
|
  NullLiteral()
|
  NaNLiteral()
}

void NaNLiteral() #NumberLiteral :
{}
{
    <NAN_LITERAL> { jjtThis.setReal("NaN"); }
}

void NullLiteral() : {}
{
    <NULL>
}

void BooleanLiteral() #void :
{}
{
  <TRUE> #TrueNode
|
  <FALSE> #FalseNode
}

void IntegerLiteral() #NumberLiteral :
{
  Token t;
}
{
  t=<INTEGER_LITERAL>
  { jjtThis.setNatural(t.image); }
}


void FloatLiteral() #NumberLiteral:
{
  Token t;
}
{
  t=<FLOAT_LITERAL>
  { jjtThis.setReal(t.image); }
}

void StringLiteral() :
{
   Token t;
}
{
  t=<STRING_LITERAL>
  { jjtThis.setLiteral(Parser.buildString(t.image, true)); }
}


void JxltLiteral() #JxltLiteral :
{
   Token t;
   String src;
}
{
   t=<JXLT_LITERAL>
   { jjtThis.setLiteral(src = Parser.buildString(t.image, true), getScope()); }
}

void RegexLiteral() :
{
   Token t;
}
{
  t=<REGEX_LITERAL>
  { jjtThis.setLiteral(Parser.buildRegex(t.image)); }
}


void ExtendedLiteral() #ExtendedLiteral : {}
{
   <ELIPSIS>
}

void ArrayLiteral() : {}
{
   <LBRACKET>
   (
        (<COMMA>)? ExtendedLiteral()
    |
        (Expression() (LOOKAHEAD(2) <COMMA> Expression())*
            (<COMMA> (ExtendedLiteral() { jjtThis.setExtended(true); })? )? )?
   )
   <RBRACKET>
}

void MapLiteral() : {}
{
    <LCURLY>
    (
        MapEntry() (LOOKAHEAD(2) <COMMA> MapEntry() )*
            (<COMMA> (ExtendedLiteral(){ jjtThis.setExtended(true); })? )?
    |
        <COLON>
    ) <RCURLY>
}

void MapEntry() : {}
{
    LOOKAHEAD(2) Identifier(true) <COLON> Expression()
    |
    Expression() <COLON> Expression()
}

void SetLiteral() : {}
{
    <LCURLY>
    (
    Expression() (LOOKAHEAD(2) <COMMA> Expression())*
        (<COMMA> (ExtendedLiteral() { jjtThis.setExtended(true); } )? )?
    )? <RCURLY>
}


/***************************************
 *      Functions & Methods
 ***************************************/

void Arguments() #Arguments : {}
{
     <LPAREN> (Expression() (<COMMA> Expression())* )? <RPAREN>
}

void FunctionCallLookahead() #void : {}
{
    LOOKAHEAD(4, <IDENTIFIER> <COLON> <IDENTIFIER> <LPAREN>,  { isNamespaceFuncall(getToken(1), getToken(2), getToken(3), getToken(4)) }) <IDENTIFIER> <COLON> <IDENTIFIER> <LPAREN>
    |
    LOOKAHEAD(2) <IDENTIFIER> <LPAREN>
    |
    LOOKAHEAD(2) <REGISTER> <LPAREN>
}

void FunctionCall() #void : {}
{
    LOOKAHEAD(4, <IDENTIFIER> <COLON> <IDENTIFIER> <LPAREN>,  { isNamespaceFuncall(getToken(1), getToken(2), getToken(3), getToken(4)) })  NamespaceIdentifier() Arguments() #FunctionNode(2)
    |
    LOOKAHEAD(<IDENTIFIER> <LPAREN>) Identifier(true) Arguments() #FunctionNode(2)
}

void QualifiedIdentifier() #QualifiedIdentifier : {
    LinkedList<String> lstr = new LinkedList<String>();
}
{
    propertyKey(lstr) { jjtThis.setName(stringify(lstr));}
}

void Constructor() #ConstructorNode : {}
{
  LOOKAHEAD(2) <NEW> <LPAREN> Expression() ( <COMMA> Expression() )* <RPAREN>
  |
  <NEW> QualifiedIdentifier()  <LPAREN> [ Expression() ( <COMMA> Expression() )* ] <RPAREN>
}

void Parameter() #void :
{
    Token t;
}
{
    (<VAR>)? t=<IDENTIFIER> { declareParameter(t, false, false); }
    |
    <LET> t=<IDENTIFIER> { declareParameter(t, true, false); }
    |
    <CONST> t=<IDENTIFIER> { declareParameter(t, true, true); }
}

void Parameters() #void : {}
{
     <LPAREN> [Parameter() (<COMMA> Parameter())* ] <RPAREN>
}

void ParametersLookahead() #void : {}
{
     <LPAREN> [(<VAR>|<LET>|<CONST>)? <IDENTIFIER> (<COMMA> ((<VAR>|<LET>|<CONST>)? <IDENTIFIER>))*] <RPAREN>
}

void LambdaLookahead() #void : {}
{
  <FUNCTION> ParametersLookahead()
  |
  ParametersLookahead() (<LAMBDA> | <FATARROW>)
  |
  <IDENTIFIER> (<LAMBDA> | <FATARROW>)
}

void Lambda() #JexlLambda :
{
   Token arrow;
}
{
  <FUNCTION> (LOOKAHEAD(<IDENTIFIER>) DeclareFunction())? {
   beginLambda(jjtThis); } Parameters() ( LOOKAHEAD(3) Block() | Expression()) { endLambda(jjtThis); }
  |
  { beginLambda(jjtThis); } Parameters() (arrow=<LAMBDA> | arrow=<FATARROW>) ( LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); endLambda(jjtThis); }
  |
  { beginLambda(jjtThis); } Parameter() (arrow=<LAMBDA> | arrow=<FATARROW>)( LOOKAHEAD(3) Block() | Expression()) { checkLambda(arrow); endLambda(jjtThis); }
}



/***************************************
 *     References
 ***************************************/

void IdentifierAccess() #void :
{
    Token t;
}
{
    <DOT> (
        t=<DOT_IDENTIFIER> { jjtThis.setIdentifier(t.image); } #IdentifierAccess
    |
        t=<STRING_LITERAL> { jjtThis.setIdentifier(Parser.buildString(t.image, true)); } #IdentifierAccess
    |
        t=<JXLT_LITERAL> { jjtThis.setIdentifier(Parser.buildString(t.image, true), getScope()); } #IdentifierAccessJxlt
    )
    |
    <QDOT> (
        t=<DOT_IDENTIFIER> { jjtThis.setIdentifier(t.image); } #IdentifierAccessSafe
    |
        t=<STRING_LITERAL> { jjtThis.setIdentifier(Parser.buildString(t.image, true)); } #IdentifierAccessSafe
    |
        t=<JXLT_LITERAL> { jjtThis.setIdentifier(Parser.buildString(t.image, true), getScope()); } #IdentifierAccessSafeJxlt
    )
}

void ArrayAccess() : {
 long safe = 0L;
 int s = 0;
 }
{
    (LOOKAHEAD(2) (<LBRACKET>|<QLBRACKET> { safe |= (1L << s++); }) Expression() <RBRACKET>)+ { jjtThis.setSafe(safe); }
}

void MemberAccess() #void : {}
{
    LOOKAHEAD(<LBRACKET>|<QLBRACKET>) ArrayAccess()
    |
    LOOKAHEAD(<DOT>) IdentifierAccess()
    |
    LOOKAHEAD(<QDOT>) IdentifierAccess()
}

void ReferenceExpression() #MethodNode(>1) : {}
{
    <LPAREN> Expression() <RPAREN> #ReferenceExpression(1) ( LOOKAHEAD(<LPAREN>) Arguments() )*
}

void PrimaryExpression() #void : {}
{
       LOOKAHEAD( LambdaLookahead() ) Lambda()
    |
       LOOKAHEAD( <LPAREN> ) ReferenceExpression()
    |
       LOOKAHEAD( <LCURLY> Expression() <COLON>) MapLiteral()
    |
       LOOKAHEAD( <LCURLY> <COLON>) MapLiteral()
    |
       LOOKAHEAD( <LCURLY> Expression() (<COMMA> | <RCURLY>)) SetLiteral()
    |
       LOOKAHEAD( <LCURLY> <RCURLY> ) SetLiteral()
    |
       LOOKAHEAD( <LBRACKET> ) ArrayLiteral()
    |
       LOOKAHEAD( <NEW> ) Constructor()
    |
       LOOKAHEAD( <SWITCH> ) SwitchExpression()
    |
       LOOKAHEAD( FunctionCallLookahead() ) FunctionCall()
    |
       Identifier(true)
    |
       Literal()
}

void MethodCall() #void : {}
{
    (MemberAccess() (LOOKAHEAD(<LPAREN>) Arguments())+) #MethodNode(>1)
}


void MethodCallLookahead() #void : {}
{
    MemberAccess() <LPAREN>
}


void MemberExpression() #void : {}
{
    LOOKAHEAD(MethodCallLookahead()) MethodCall() | MemberAccess()
}

void ValueExpression() #void : {}
{
    ( PrimaryExpression() ( LOOKAHEAD(2) MemberExpression() )*) #Reference(>1)
}

