%{ /* -*- mode: c++; indent-tabs-mode: nil -*-

  ast_parser.ypp

  Qore Programming Language

  Copyright (C) 2017 - 2023 Qore Technologies, s.r.o.

  Permission is hereby granted, free of charge, to any person obtaining a
  copy of this software and associated documentation files (the "Software"),
  to deal in the Software without restriction, including without limitation
  the rights to use, copy, modify, merge, publish, distribute, sublicense,
  and/or sell copies of the Software, and to permit persons to whom the
  Software is furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  DEALINGS IN THE SOFTWARE.
*/

#include <cstdio>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "AstParseErrorLog.h"
#include "ast/AST.h"

#define YYINITDEPTH 300
//#define YYDEBUG 1

#define YYLTYPE ASTParseLocation

#define YYLLOC_DEFAULT(Current, Rhs, N)                            \
    do                                                             \
        if (N) {                                                   \
            (Current).firstLine    = YYRHSLOC(Rhs, 1).firstLine;   \
            (Current).firstCol     = YYRHSLOC(Rhs, 1).firstCol;    \
            (Current).lastLine     = YYRHSLOC(Rhs, N).lastLine;    \
            (Current).lastCol      = YYRHSLOC(Rhs, N).lastCol;     \
        }                                                          \
        else {                                                     \
            (Current).firstLine = (Current).lastLine =             \
                YYRHSLOC(Rhs, 0).lastLine;                         \
            (Current).firstCol = (Current).lastCol =               \
                YYRHSLOC(Rhs, 0).lastCol;                          \
        }                                                          \
    while (0)


// Needed for the lex-param and parse-param Bison options.
typedef void* yyscan_t;
%}

// remove this and use the following line when bison is updated on all build environments enough to support api.pure
%pure-parser
//%define api.pure
%lex-param {yyscan_t yyscanner}
%parse-param {yyscan_t yyscanner}
%parse-param {AstParseErrorLog* errorLog}
%parse-param {ASTTree* parseTree}
%locations
// remove this and use the following line when bison is updated on all build environments enough to support parse.error verbose
%error-verbose
//%define parse.error verbose

%union {
    bool b;
    int i4;
    unsigned int ui4;
    int64_t integer;
    double decimal;
    std::string* String;
    char* string;
    ASTDeclaration* astdecl;
    ASTNode* astnode;
    std::vector<ASTCallExpression*>* bclist;
    ASTCallExpression* callexpr;
    ASTCaseExpression* caseexpr;
    ASTClassDeclaration* classdecl;
    ASTContextModExpression* cmodexpr;
    std::vector<ASTContextModExpression*>* cmodlist;
    ASTConstantDeclaration* constdecl;
    ASTConstrInitExpression* constrinit;
    std::vector<ASTDeclaration*>* decllist;
    ASTExpression* expression;
    std::vector<ASTExpression*>* exprlist;
    ASTFunctionDeclaration* funcdecl;
    ASTHashExpression* hash;
    ASTHashDeclaration* hashdecl;
    ASTHashdeclHashExpression* hashdeclhash;
    ASTHashElementExpression* hashelem;
    ASTHashMemberDeclaration* hashmember;
    std::vector<ASTHashMemberDeclaration*>* hashmemberlist;
    ASTListExpression* list;
    ASTMemberGroupDeclaration* membergroup;
    ASTNameExpression* nameexpr;
    ASTNamespaceDeclaration* nsdecl;
    ASTParseOption* parseopt;
    ASTRegexExpression* regex;
    ASTRegexSubstExpression* regexsubst;
    ASTRegexTransExpression* regextrans;
    ASTStatementBlock *sblock;
    ASTSuperclassDeclaration* sclassdecl;
    std::vector<ASTSuperclassDeclaration*>* sclist;
    ASTStatement* statement;
    ASTSwitchBodyExpression* switchexpr;
}

%{

#define YY_LEX_PARAMETERS YYSTYPE *lvalp, YYLTYPE *loc, yyscan_t yyscanner

int yylex(YY_LEX_PARAMETERS);

void yyerror(YYLTYPE *loc, yyscan_t scanner, AstParseErrorLog* errorLog, ASTTree* parseTree, const char* str) {
    errorLog->reportError(*loc, str);
}

%}

// define string aliases for token names for more user-friendly error reporting
%token TOK_RETURN "return"
%token TOK_RETURNS "returns"
%token TOK_THREAD_LOCAL "thread_local"
%token TOK_MY "my"
%token TOK_DO "do"
%token TOK_TRY "try"
%token TOK_THROW "throw"
%token TOK_CATCH "catch"
%token TOK_WHERE "where"
%token TOK_WHILE "while"
%token TOK_IF "if"
%token TOK_FOR "for"
%token TOK_SUB "sub"
%token TOK_THREAD_EXIT "thread_exit"
%token TOK_BREAK "break"
%token TOK_CONTINUE "continue"
%token TOK_CONTEXT_ROW "%%"
%token TOK_FIND "find"
%token TOK_FOREACH "foreach"
%token TOK_IN "in"
%token TOK_DELETE "delete"
%token TOK_REMOVE "remove"
%token TOK_PRIVATE "private"
%token TOK_PRIVATEINTERNAL "private:internal"
%token TOK_PRIVATEHIERARCHY "private:hierarchy"
%token TOK_PUBLIC "public"
%token TOK_SYNCHRONIZED "synchronized"
%token TOK_DEPRECATED "deprecated"
%token TOK_CONTEXT "context"
%token TOK_SORT_BY "sortBy"
%token TOK_SORT_DESCENDING_BY "sortDescendingBy"
%token TOK_SUB_CONTEXT "subcontext"
%token TOK_CONST "const"
%token TOK_SUMMARIZE "summarize"
%token TOK_BY "by"
%token TOK_OUR "our"
%token TOK_RETHROW "rethrow"
%token TOK_SWITCH "switch"
%token TOK_CASE "case"
%token TOK_DEFAULT "default"
%token TOK_INHERITS "inherits"
%token TOK_ELSE "else"
%token TOK_STATIC "static"
%token TOK_MODULE "module"
%token TOK_FINAL "final"
%token TOK_ABSTRACT "abstract"
%token TOK_HASHDECL "hashdecl"
%token TOK_TRANSIENT "transient"
%token TOK_NULL "NULL"
%token TOK_NOTHING "NOTHING"
%token TOK_TRUE "True"
%token TOK_FALSE "False"

// currently unused tokens
%token TOK_UNREFERENCE "unreference"

// operator tokens
%token P_INCREMENT "++ operator"
%token P_DECREMENT "-- operator"
%token PLUS_EQUALS "+= operator"
%token MINUS_EQUALS "-= operator"
%token AND_EQUALS "&= operator"
%token OR_EQUALS "|= operator"
%token MODULO_EQUALS "%= operator"
%token MULTIPLY_EQUALS "*= operator"
%token DIVIDE_EQUALS "/= operator"
%token XOR_EQUALS "^= operator"
%token SHIFT_LEFT_EQUALS "<<= operator"
%token SHIFT_RIGHT_EQUALS ">>= operator"
%token TOK_UNSHIFT "unshift"
%token TOK_PUSH "push"
%token TOK_POP "pop"
%token TOK_SHIFT "shift"
%token TOK_CHOMP "chomp"
%token TOK_TRIM "trim"
%token LOGICAL_AND "&& operator"
%token LOGICAL_OR "|| operator"
%token BROKEN_LOGICAL_OR "|| operator (compat)"
%token BROKEN_BINARY_OR "| operator (compat)"
%token BROKEN_BINARY_XOR "^ operator (compat)"
%token LOGICAL_EQ "== operator"
%token LOGICAL_NE "!= operator"
%token LOGICAL_LE "<= operator"
%token LOGICAL_GE ">= operator"
%token LOGICAL_CMP "<=> operator"
%token ABSOLUTE_EQ "=== operator"
%token ABSOLUTE_NE "!== operator"
%token REGEX_MATCH "=~ operator"
%token REGEX_NMATCH "!~ operator"
%token TOK_EXISTS "exists"
%token TOK_INSTANCEOF "instanceof"
%token SHIFT_RIGHT ">> operator"
%token SHIFT_LEFT "<< operator"
%token TOK_ELEMENTS "elements"
%token TOK_KEYS "keys"
%token TOK_NEW "new"
%token TOK_BACKGROUND "background"
%token TOK_ON_EXIT "on_exit"
%token TOK_ON_SUCCESS "on_success"
%token TOK_ON_ERROR "on_error"
%token TOK_MAP "map"
%token TOK_FOLDR "foldr"
%token TOK_FOLDL "foldl"
%token TOK_SELECT "select"
%token TOK_SPLICE "splice"
%token TOK_EXTRACT "extract"
%token IMPLICIT_ELEMENT "$# operator"
%token NULL_COALESCING "?? operator"
%token VALUE_COALESCING "?* operator"
%token WEAK_ASSIGNMENT ":= operator"
%token RANGE ".. operator"
%token ELLIPSES "... marker"

// other
%token PARSE_OPTION_BEGIN "parse option beginning"

// scanner errors
%token INTEGER_OVERFLOW_ERROR "'integer overflow error (cannot represent in a 64-bit signed number)'"
%token OPEN_BLK_COMMENT_AT_EOF_ERROR "'open block comment at EOF error'"
%token NO_REGEX_AFTER_MATCH_ERROR "'missing regular expression after =~ error'"
%token NO_REGEX_AFTER_NOTMATCH_ERROR "'missing regular expression after !~ error'"

// operators with white-space inside
%token WS_LOGICAL_LE
%token WS_LOGICAL_GE
%token WS_LOGICAL_NE
%token WS_LOGICAL_EQ
%token WS_ABSOLUTE_EQ
%token WS_ABSOLUTE_NE
%token WS_LOGICAL_CMP
%token WS_LOGICAL_AND
%token WS_LOGICAL_OR
%token WS_BROKEN_LOGICAL_OR
%token WS_SHIFT_RIGHT
%token WS_SHIFT_LEFT
%token WS_PLUS_EQUALS
%token WS_MINUS_EQUALS
%token WS_AND_EQUALS
%token WS_OR_EQUALS
%token WS_MODULO_EQUALS
%token WS_MULTIPLY_EQUALS
%token WS_DIVIDE_EQUALS
%token WS_XOR_EQUALS
%token WS_SHIFT_RIGHT_EQUALS
%token WS_SHIFT_LEFT_EQUALS

 // tokens returning data
%token <integer> INTEGER "integer value"
%token <decimal> QFLOAT "floating-point value"
%token <string> NUMBER "arbitrary-precision number"
%token <string> IDENTIFIER "identifier"
%token <string> HASHDECL_IDENTIFIER_OPENCURLY "typed hash identifier"
%token <string> ANGLE_IDENTIFIER "complex type identifier"
%token <string> VAR_REF "variable reference"
%token <string> BACKQUOTE "backquote expression"
%token <string> SELF_REF "in-object member reference"
%token <string> KW_IDENTIFIER_OPENPAREN "identifier or keyword used as function or method identifier"
%token <string> SCOPED_REF "namespace or class-scoped reference"
%token <string> SCOPED_VREF "namespace-scoped global variable reference"
%token <string> CONTEXT_REF "context reference"
%token <string> COMPLEX_CONTEXT_REF "named context reference"
%token <string> QORE_CAST "cast operator"
%token <string> CLASS_STRING "simple class declaration"
%token <string> BASE_CLASS_CALL "call to base class method"
%token <string> DATETIME "date/time value"
%token <String> QUOTED_WORD "quoted string"
%token <string> BINARY "binary constant value"
%token <string> NAMESPACE "namespace"
%token <regexsubst> REGEX_SUBST "regular expression substitution expression"
%token <regextrans> REGEX_TRANS "transliteration expression"
%token <string> CLASS_SCOPED_REF "scoped class declaration"
%token <regex> REGEX "regular expression"
%token <regex> REGEX_EXTRACT "regular expression extraction expression"
%token <i4> IMPLICIT_ARG_REF "implicit argument reference"
%token <String> DOT_KW_IDENTIFIER "keyword used as hash key or object member reference"
%token <parseopt> PARSE_OPTION_END "parse option end"
%token <parseopt> PARSE_OPTION_FULL "full parse option"

%nonassoc IFX SCOPED_REF SCOPED_VREF
%nonassoc TOK_ELSE

// FIXME: check precedence
%nonassoc TOK_UNSHIFT TOK_PUSH TOK_SPLICE TOK_EXTRACT TOK_MAP TOK_FOLDR TOK_FOLDL TOK_SELECT
%left ','
%right PLUS_EQUALS WS_PLUS_EQUALS MINUS_EQUALS WS_MINUS_EQUALS AND_EQUALS WS_AND_EQUALS OR_EQUALS WS_OR_EQUALS MODULO_EQUALS WS_MODULO_EQUALS MULTIPLY_EQUALS WS_MULTIPLY_EQUALS DIVIDE_EQUALS WS_DIVIDE_EQUALS XOR_EQUALS WS_XOR_EQUALS SHIFT_LEFT_EQUALS WS_SHIFT_LEFT_EQUALS SHIFT_RIGHT_EQUALS WS_SHIFT_RIGHT_EQUALS '=' WEAK_ASSIGNMENT
%right '?' ':'
%nonassoc RANGE
%right NULL_COALESCING VALUE_COALESCING
%left LOGICAL_OR WS_LOGICAL_OR
%left LOGICAL_AND WS_LOGICAL_AND BROKEN_LOGICAL_OR WS_BROKEN_LOGICAL_OR
%left '|'
%left '^'
%left '&' BROKEN_BINARY_OR BROKEN_BINARY_XOR
%left '<' '>' LOGICAL_EQ WS_LOGICAL_EQ LOGICAL_NE WS_LOGICAL_NE LOGICAL_LE WS_LOGICAL_LE LOGICAL_GE WS_LOGICAL_GE LOGICAL_CMP WS_LOGICAL_CMP ABSOLUTE_EQ WS_ABSOLUTE_EQ ABSOLUTE_NE WS_ABSOLUTE_NE REGEX_MATCH REGEX_NMATCH
%right TOK_EXISTS TOK_INSTANCEOF
%left SHIFT_RIGHT WS_SHIFT_RIGHT SHIFT_LEFT WS_SHIFT_LEFT    // binary shift right and left
%left '+' '-'                   // arithmetic plus and minus
%left '%'                       // modulo
%left '*' '/'                   // arithmetic multiply and divide
%right TOK_ELEMENTS TOK_KEYS
%nonassoc TOK_SHIFT TOK_POP TOK_CHOMP TOK_TRIM
%left NEG                     // unary minus, defined for precedence
%left UPLUS           // unary plus, defined for precedence
%right '~' '\\'               // binary not, reference operator
%left '!'                     // logical not
%right TOK_BACKGROUND TOK_DELETE TOK_REMOVE QORE_CAST
%nonassoc TOK_NEW
%nonassoc P_INCREMENT P_DECREMENT
%left '{' '[' '.' '(' DOT_KW_IDENTIFIER  // list and object references, etc, defined for precedence

%type <hash>           alt_hash
%type <callexpr>       base_constructor
%type <bclist>         base_constructors
%type <constrinit>     base_constructor_list
%type <sblock>         block
%type <expression>     classvardecl
%type <decllist>       class_attributes
%type <classdecl>      class_def
%type <cmodexpr>       context_mod
%type <cmodlist>       context_mods
%type <switchexpr>     case_block
%type <caseexpr>       case_code
%type <expression>     exp
%type <expression>     exp_c
%type <expression>     exp_n
%type <astdecl>        gvardecl
%type <hash>           hash
%type <hashelem>       hash_element
%type <hashmemberlist> hashdecl_attributes
%type <hashdecl>       hashdecl_def
%type <hashmember>     hashdecl_member
%type <string>         ident_openparen
%type <hashdeclhash>   immediate_typed_hash
%type <sclist>         inheritance_list
%type <funcdecl>       inline_methoddef
%type <membergroup>    internal_member_list
%type <list>           list
%type <list>           list_n
%type <expression>     member
%type <expression>     member2
%type <expression>     transient_member
%type <expression>     transient_member2
%type <exprlist>       member_list
%type <exprlist>       member_list2
%type <ui4>            method_modifier
%type <ui4>            method_modifiers
%type <expression>     myexp
%type <astdecl>        namespace_decl
%type <decllist>       namespace_decls
%type <ui4>            nsc_modifiers
%type <nameexpr>       optname
%type <funcdecl>       outofline_methoddef
%type <parseopt>       parse_option
%type <b>              ppub
%type <membergroup>    private_member_list
%type <membergroup>    public_member_list
%type <String>         qtypedef
%type <statement>      return_statement
%type <expression>     return_value
%type <expression>     scalar
%type <constdecl>      scoped_const_decl
%type <funcdecl>       scoped_sub_def
%type <statement>      statement
%type <sblock>         statements
%type <sblock>         statement_or_block
%type <String>         string
%type <funcdecl>       sub_def
%type <sclassdecl>     superclass
%type <sclist>         superclass_list
%type <statement>      switch_statement
%type <astnode>        top_level_command
%type <nsdecl>         top_namespace_decl
%type <statement>      try_statement
%type <String>         uncqtypedef
%type <constdecl>      unscoped_const_decl

 // destructor actions for elements that need deleting when parse errors occur
%destructor { delete $$; $$ = nullptr; } DOT_KW_IDENTIFIER PARSE_OPTION_END PARSE_OPTION_FULL QUOTED_WORD REGEX REGEX_EXTRACT REGEX_SUBST REGEX_TRANS alt_hash base_constructor base_constructor_list block case_block case_code classvardecl class_def context_mod exp exp_c exp_n gvardecl hash hash_element hashdecl_def hashdecl_member immediate_typed_hash inline_methoddef internal_member_list list list_n member member2 myexp namespace_decl optname outofline_methoddef parse_option private_member_list public_member_list qtypedef return_statement return_value scalar scoped_const_decl scoped_sub_def statement statements statement_or_block string sub_def superclass switch_statement top_level_command top_namespace_decl transient_member transient_member2 try_statement uncqtypedef unscoped_const_decl
%destructor { free($$); $$ = nullptr; } ANGLE_IDENTIFIER BACKQUOTE BASE_CLASS_CALL BINARY CLASS_SCOPED_REF CLASS_STRING COMPLEX_CONTEXT_REF CONTEXT_REF DATETIME HASHDECL_IDENTIFIER_OPENCURLY IDENTIFIER KW_IDENTIFIER_OPENPAREN NAMESPACE NUMBER QORE_CAST SCOPED_REF SCOPED_VREF SELF_REF VAR_REF ident_openparen
%destructor { if ($$) { for (int i = 0, count = $$->size(); i < count; i++) {delete (*$$)[i];} delete $$; } $$ = nullptr; } base_constructors class_attributes context_mods hashdecl_attributes inheritance_list member_list member_list2 namespace_decls superclass_list

%%
top_level_commands:
        top_level_command {
            if ($1)
                parseTree->addNode($1);
        }
        | top_level_commands top_level_command {
            if ($2)
                parseTree->addNode($2);
        }
        ;

top_level_command:
        sub_def {
            $$ = $1;
        }
        | scoped_sub_def {
            $$ = $1;
        }
        | class_def {
            $$ = $1;
        }
        | hashdecl_def {
            $$ = $1;
        }
        | scoped_const_decl {
            $$ = $1;
        }
        | unscoped_const_decl {
            $$ = $1;
        }
        | outofline_methoddef {
            $$ = $1;
        }
        | statement {
            $$ = $1;
        }
        | '{' '}' {
            $$ = nullptr;
        }
        | '{' statements '}' {
            $$ = $2;
        }
        | top_namespace_decl {
            $$ = $1;
        }
        | TOK_MODULE '{' module_decls '}' { $$ = nullptr; }
        | parse_option {
            $$ = $1;
        }
        ;

parse_option:
        PARSE_OPTION_FULL {
            $$ = $1;
            $$->loc = @1;
        }
        | PARSE_OPTION_BEGIN PARSE_OPTION_END {
            $$ = $2;
            $$->loc.set(@1, @2);
        }
        ;

module_decls:
        module_decl {
        }
        | module_decls module_decl {
        }
        ;

module_decl:
        IDENTIFIER '=' exp ';' {
            free($1);
            delete $3;
        }
        ;

ppub:
        /* empty */
        { $$ = false; }
        | TOK_PUBLIC {
            $$ = true;
        }
        ;

gvardecl:
        ppub TOK_OUR uncqtypedef SCOPED_REF {
            ASTModifiers mods(ASTModifier::AM_Our);
            if ($1)
                mods.add(ASTModifier::AM_Public);
            mods.loc.set(@1, @2);
            ASTName typeName($3, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @3;
            delete $3;
            ASTName name($4, ASTNameKind::ANK_ScopedRef);
            name.loc = @4;
            free($4);

            $$ = new ASTVariableDeclaration(mods, std::move(typeName), std::move(name));
            $$->loc.set(@1, @4);
        }
        | ppub TOK_OUR SCOPED_REF {
            ASTModifiers mods(ASTModifier::AM_Our);
            if ($1)
                mods.add(ASTModifier::AM_Public);
            mods.loc.set(@1, @2);
            ASTName name($3, ASTNameKind::ANK_ScopedRef);
            name.loc = @3;
            free($3);

            $$ = new ASTVariableDeclaration(mods, std::move(name));
            $$->loc.set(@1, @3);
        }
        | ppub TOK_OUR uncqtypedef IDENTIFIER {
            ASTModifiers mods(ASTModifier::AM_Our);
            if ($1)
                mods.add(ASTModifier::AM_Public);
            mods.loc.set(@1, @2);
            ASTName typeName($3, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @3;
            delete $3;
            ASTName name($4, ASTNameKind::ANK_Identifier);
            name.loc = @4;
            free($4);

            $$ = new ASTVariableDeclaration(mods, std::move(typeName), std::move(name));
            $$->loc.set(@1, @4);
        }
        | ppub TOK_OUR IDENTIFIER {
            ASTModifiers mods(ASTModifier::AM_Our);
            if ($1)
                mods.add(ASTModifier::AM_Public);
            mods.loc.set(@1, @2);
            ASTName name($3, ASTNameKind::ANK_Identifier);
            name.loc = @3;
            free($3);

            $$ = new ASTVariableDeclaration(mods, std::move(name));
            $$->loc.set(@1, @3);
        }
        | ppub TOK_OUR qtypedef SCOPED_VREF {
            ASTModifiers mods(ASTModifier::AM_Our);
            if ($1)
                mods.add(ASTModifier::AM_Public);
            mods.loc.set(@1, @2);
            ASTName typeName(ASTNameKind::ANK_QTypedef);
            if ($3) {
                typeName.name = *($3);
                typeName.loc = @3;
                delete $3;
            }
            ASTName name($4, ASTNameKind::ANK_ScopedVref);
            name.loc = @4;
            free($4);

            $$ = new ASTVariableDeclaration(mods, std::move(typeName), std::move(name));
            $$->loc.set(@1, @4);
        }
        | ppub TOK_OUR qtypedef VAR_REF {
            ASTModifiers mods(ASTModifier::AM_Our);
            if ($1)
                mods.add(ASTModifier::AM_Public);
            mods.loc.set(@1, @2);
            ASTName typeName(ASTNameKind::ANK_QTypedef);
            if ($3) {
                typeName.name = *($3);
                typeName.loc = @3;
                delete $3;
            }
            ASTName name($4, ASTNameKind::ANK_VarRef);
            name.loc = @4;
            free($4);

            $$ = new ASTVariableDeclaration(mods, std::move(typeName), std::move(name));
            $$->loc.set(@1, @4);
        }
        | ppub TOK_OUR '(' list ')' {
            ASTModifiers mods(ASTModifier::AM_Our);
            $$ = new ASTVarListDeclaration(mods, $4);
            $$->loc.set(@1, @5);
        }
        | ppub TOK_THREAD_LOCAL uncqtypedef SCOPED_REF {
            ASTModifiers mods(ASTModifier::AM_ThreadLocal);
            if ($1)
                mods.add(ASTModifier::AM_Public);
            mods.loc.set(@1, @2);
            ASTName typeName($3, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @3;
            delete $3;
            ASTName name($4, ASTNameKind::ANK_ScopedRef);
            name.loc = @4;
            free($4);

            $$ = new ASTVariableDeclaration(mods, std::move(typeName), std::move(name));
            $$->loc.set(@1, @4);
        }
        | ppub TOK_THREAD_LOCAL SCOPED_REF {
            ASTModifiers mods(ASTModifier::AM_ThreadLocal);
            if ($1)
                mods.add(ASTModifier::AM_Public);
            mods.loc.set(@1, @2);
            ASTName name($3, ASTNameKind::ANK_ScopedRef);
            name.loc = @3;
            free($3);

            $$ = new ASTVariableDeclaration(mods, std::move(name));
            $$->loc.set(@1, @3);
        }
        | ppub TOK_THREAD_LOCAL uncqtypedef IDENTIFIER {
            ASTModifiers mods(ASTModifier::AM_ThreadLocal);
            if ($1)
                mods.add(ASTModifier::AM_Public);
            mods.loc.set(@1, @2);
            ASTName typeName($3, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @3;
            delete $3;
            ASTName name($4, ASTNameKind::ANK_Identifier);
            name.loc = @4;
            free($4);

            $$ = new ASTVariableDeclaration(mods, std::move(typeName), std::move(name));
            $$->loc.set(@1, @4);
        }
        | ppub TOK_THREAD_LOCAL IDENTIFIER {
            ASTModifiers mods(ASTModifier::AM_ThreadLocal);
            if ($1)
                mods.add(ASTModifier::AM_Public);
            mods.loc.set(@1, @2);
            ASTName name($3, ASTNameKind::ANK_Identifier);
            name.loc = @3;
            free($3);

            $$ = new ASTVariableDeclaration(mods, std::move(name));
            $$->loc.set(@1, @3);
        }
        | ppub TOK_THREAD_LOCAL qtypedef SCOPED_VREF {
            ASTModifiers mods(ASTModifier::AM_ThreadLocal);
            if ($1)
                mods.add(ASTModifier::AM_Public);
            mods.loc.set(@1, @2);
            ASTName typeName(ASTNameKind::ANK_QTypedef);
            if ($3) {
                typeName.name = *($3);
                typeName.loc = @3;
                delete $3;
            }
            ASTName name($4, ASTNameKind::ANK_ScopedVref);
            name.loc = @4;
            free($4);

            $$ = new ASTVariableDeclaration(mods, std::move(typeName), std::move(name));
            $$->loc.set(@1, @4);
        }
        | ppub TOK_THREAD_LOCAL qtypedef VAR_REF {
            ASTModifiers mods(ASTModifier::AM_ThreadLocal);
            if ($1)
                mods.add(ASTModifier::AM_Public);
            mods.loc.set(@1, @2);
            ASTName typeName(ASTNameKind::ANK_QTypedef);
            if ($3) {
                typeName.name = *($3);
                typeName.loc = @3;
                delete $3;
            }
            ASTName name($4, ASTNameKind::ANK_VarRef);
            name.loc = @4;
            free($4);

            $$ = new ASTVariableDeclaration(mods, std::move(typeName), std::move(name));
            $$->loc.set(@1, @4);
        }
        | ppub TOK_THREAD_LOCAL '(' list ')' {
            ASTModifiers mods(ASTModifier::AM_ThreadLocal);
            $$ = new ASTVarListDeclaration(mods, $4);
            $$->loc.set(@1, @5);
        }
        ;

top_namespace_decl:
        nsc_modifiers NAMESPACE '{' namespace_decls '}' {
            ASTName name($2, ASTNameKind::ANK_Namespace);
            name.loc = @2;
            free($2);

            $$ = new ASTNamespaceDeclaration($1, std::move(name), $4);
            $$->loc.set(@1, @5);
            delete $4;
        }
        | nsc_modifiers NAMESPACE ';' {
            ASTName name($2, ASTNameKind::ANK_Namespace);
            name.loc = @2;
            free($2);

            $$ = new ASTNamespaceDeclaration($1, std::move(name));
            $$->loc.set(@1, @3);
        }
        | nsc_modifiers NAMESPACE '{' '}' {
            ASTName name($2, ASTNameKind::ANK_Namespace);
            name.loc = @2;
            free($2);

            $$ = new ASTNamespaceDeclaration($1, std::move(name));
            $$->loc.set(@1, @4);
        }
        ;

namespace_decls:
        namespace_decl {
            $$ = new std::vector<ASTDeclaration*>;
            if ($1)
                $$->push_back($1);
        }
        | namespace_decls namespace_decl {
            $$ = $1;
            if ($2)
                $$->push_back($2);
        }
        ;

namespace_decl:
        scoped_const_decl { $$ = $1; }
        | unscoped_const_decl { $$ = $1; }
        | class_def { $$ = $1; }
        | hashdecl_def { $$ = $1; }
        | sub_def { $$ = $1; }
        | scoped_sub_def { $$ = $1; }
        | gvardecl ';' { $$ = $1; }
        | top_namespace_decl { $$ = $1; }
        | outofline_methoddef { $$ = $1; }
        ;

unscoped_const_decl:
        TOK_CONST IDENTIFIER '=' exp ';' {
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            $$ = new ASTConstantDeclaration(std::move(name), $4);
            $$->loc.set(@1, @5);
        }
        | TOK_PUBLIC TOK_CONST IDENTIFIER '=' exp ';' {
            ASTModifiers mods(ASTModifier::AM_Public);
            ASTName name($3, ASTNameKind::ANK_Identifier);
            name.loc = @3;
            free($3);

            $$ = new ASTConstantDeclaration(mods, std::move(name), $5);
            $$->loc.set(@1, @6);
        }
        ;

scoped_const_decl:
        TOK_CONST SCOPED_REF '=' exp ';' {
            ASTName name($2, ASTNameKind::ANK_ScopedRef);
            name.loc = @2;
            free($2);

            $$ = new ASTConstantDeclaration(std::move(name), $4);
            $$->loc.set(@1, @5);
        }
        | TOK_PUBLIC TOK_CONST SCOPED_REF '=' exp ';' {
            ASTModifiers mods(ASTModifier::AM_Public);
            ASTName name($3, ASTNameKind::ANK_ScopedRef);
            name.loc = @3;
            free($3);

            $$ = new ASTConstantDeclaration(mods, std::move(name), $5);
            $$->loc.set(@1, @6);
        }
        ;

block:
        '{' statements '}'
        { $$ = $2; }
        |
        '{' /* NOTHING */ '}' {
            $$ = new ASTStatementBlock;
            $$->loc.set(@1, @2);
        }
        ;

statement_or_block:
        statement {
            $$ = new ASTStatementBlock;
            $$->loc = @1;
            $$->add($1);
        }
        |
        block
        { $$ = $1; }
        ;

statements:
        statement {
            $$ = new ASTStatementBlock;
            $$->loc = @1;
            $$->add($1);
        }
        | block {
            $$ = new ASTStatementBlock;
            $$->loc = @1;
            $$->add($1);
        }
        | statements block {
            $1->add($2);
            $1->loc.setLast(@2);
            $$ = $1;
        }
        | statements statement {
            $1->add($2);
            $1->loc.setLast(@2);
            $$ = $1;
        }
        ;

optname:
        /* empty */
        { $$ = 0; }
        | IDENTIFIER {
            ASTName name($1, ASTNameKind::ANK_Identifier);
            name.loc = @1;
            free($1);
            $$ = new ASTNameExpression(std::move(name));
        }
        ;

statement:
        ';' {
            $$ = new ASTStatementBlock;
            $$->loc = @1;
        }
        | exp_n ';' {
            $$ = new ASTExpressionStatement($1);
            $$->loc.setLast(@2);
        }
        // this should be covered as an expression, but for some reason it isn't...
        | SCOPED_REF '(' myexp ')' ';' {
            ASTName name($1, ASTNameKind::ANK_ScopedRef);
            name.loc = @1;
            free($1);

            $$ = new ASTCallStatement(new ASTCallExpression(new ASTNameExpression(std::move(name)), $3));
            $$->loc.set(@1, @5);
        }
        | try_statement { $$ = $1; }
        | TOK_RETHROW ';' {
            $$ = new ASTRethrowStatement;
            $$->loc.set(@1, @2);
        }
        | TOK_RETHROW exp ';' {
            $$ = new ASTRethrowStatement($2);
            $$->loc.set(@1, @3);
        }
        | TOK_THROW exp ';' {
            $$ = new ASTThrowStatement($2);
            $$->loc.set(@1, @3);
        }
        | TOK_ON_EXIT statement_or_block {
            $$ = new ASTOnBlockExitStatement($2, ASTOnBlockExitStatement::Condition::AOBEC_Exit);
            $$->loc.set(@1, @2);
        }
        | TOK_ON_SUCCESS statement_or_block {
            $$ = new ASTOnBlockExitStatement($2, ASTOnBlockExitStatement::Condition::AOBEC_Success);
            $$->loc.set(@1, @2);
        }
        | TOK_ON_ERROR statement_or_block {
            $$ = new ASTOnBlockExitStatement($2, ASTOnBlockExitStatement::Condition::AOBEC_Error);
            $$->loc.set(@1, @2);
        }
        | TOK_SUB_CONTEXT context_mods statement_or_block {
            $$ = new ASTContextStatement(nullptr, nullptr, $2, $3, true);
            $$->loc.set(@1, @3);
            delete $2;
        }
        | TOK_SUMMARIZE optname '(' exp ')' TOK_BY '(' exp ')' context_mods statement_or_block {
            $$ = new ASTSummarizeStatement($2, $4, $8, $10, $11);
            $$->loc.set(@1, @11);
            delete $10;
        }
        | TOK_CONTEXT optname '(' exp ')' context_mods statement_or_block {
            $$ = new ASTContextStatement($2, $4, $6, $7);
            $$->loc.set(@1, @7);
            delete $6;
        }
        | TOK_IF '(' exp ')' statement_or_block %prec IFX {
            $$ = new ASTIfStatement($3, $5, nullptr);
            $$->loc.set(@1, @5);
        }
        | TOK_IF '(' exp ')' statement_or_block TOK_ELSE statement_or_block {
            $$ = new ASTIfStatement($3, $5, $7);
            $$->loc.set(@1, @7);
        }
        | TOK_WHILE '(' exp ')' statement_or_block {
            $$ = new ASTWhileStatement($3, $5);
            $$->loc.set(@1, @5);
        }
        | TOK_DO statement_or_block TOK_WHILE '(' exp ')' ';' {
            $$ = new ASTDoWhileStatement($5, $2);
            $$->loc.set(@1, @7);
        }
        | TOK_FOR '(' myexp ';' myexp ';' myexp ')' statement_or_block {
            $$ = new ASTForStatement($3, $5, $7, $9);
            $$->loc.set(@1, @9);
        }
        | TOK_FOREACH exp TOK_IN '(' exp ')' statement_or_block {
            $$ = new ASTForeachStatement($2, $5, $7);
            $$->loc.set(@1, @7);
        }
        | return_statement ';' {
            $$ = $1;
            $$->loc.setLast(@2);
        }
        | TOK_THREAD_EXIT ';' {
            $$ = new ASTThreadExitStatement;
            $$->loc.set(@1, @2);
        }
        | TOK_BREAK ';' {
            $$ = new ASTBreakStatement;
            $$->loc.set(@1, @2);
        }
        | TOK_CONTINUE ';' {
            $$ = new ASTContinueStatement;
            $$->loc.set(@1, @2);
        }
        | switch_statement { $$ = $1; }
        | error ';'        { $$ = 0; }
        ;

context_mods:
        // empty
        { $$ = 0; }
        | context_mods context_mod {
            if (!$1)
                $$ = new std::vector<ASTContextModExpression*>;
            else
                $$ = $1;
            $$->push_back($2);
        }
        ;

context_mod:
        TOK_WHERE '(' exp ')' {
            $$ = new ASTContextModExpression(ACMEKind::ACMEK_Where, $3);
            $$->loc.setFirst(@1);
        }
        | TOK_SORT_BY '(' exp ')' {
            $$ = new ASTContextModExpression(ACMEKind::ACMEK_SortBy, $3);
            $$->loc.setFirst(@1);
        }
        | TOK_SORT_DESCENDING_BY '(' exp ')' {
            $$ = new ASTContextModExpression(ACMEKind::ACMEK_SortDescBy, $3);
            $$->loc.setFirst(@1);
        }
        ;

return_statement:
        TOK_RETURN {
            $$ = new ASTReturnStatement;
            $$->loc = @1;
        }
        |
        TOK_RETURN exp {
            $$ = new ASTReturnStatement($2);
            $$->loc.set(@1, @2);
        }
        ;

switch_statement:
        TOK_SWITCH '(' exp ')' '{' case_block '}' {
            $$ = new ASTSwitchStatement($3, $6);
            $$->loc.set(@1, @7);
        }
        ;

case_block:
        case_code {
            $$ = new ASTSwitchBodyExpression;
            $$->cases.push_back($1);
            $$->loc = @1;
        }
        | case_block case_code {
            $1->cases.push_back($2);
            $1->loc.setLast(@2);
            $$ = $1;
        }
        ;

case_code:
        TOK_CASE LOGICAL_GE exp ':' statements {
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_GreaterThanOrEquals, $3, $5);
            $$->loc.set(@1, @5);
        }
        | TOK_CASE WS_LOGICAL_GE exp ':' statements {
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_WS_LOGICAL_GE, $3, $5);
            $$->loc.set(@1, @5);
        }
        | TOK_CASE LOGICAL_GE exp ':' { // nothing
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_GreaterThanOrEquals, $3, 0);
            $$->loc.set(@1, @4);
        }
        | TOK_CASE WS_LOGICAL_GE exp ':' { // nothing
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_WS_LOGICAL_GE, $3, 0);
            $$->loc.set(@1, @4);
        }
        | TOK_CASE LOGICAL_LE exp ':' statements {
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_LessThanOrEquals, $3, $5);
            $$->loc.set(@1, @5);
        }
        | TOK_CASE WS_LOGICAL_LE exp ':' statements {
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_WS_LOGICAL_LE, $3, $5);
            $$->loc.set(@1, @5);
        }
        | TOK_CASE LOGICAL_LE exp ':' { // nothing
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_LessThanOrEquals, $3, 0);
            $$->loc.set(@1, @4);
        }
        | TOK_CASE WS_LOGICAL_LE exp ':' { // nothing
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_WS_LOGICAL_LE, $3, 0);
            $$->loc.set(@1, @4);
        }
        | TOK_CASE LOGICAL_EQ exp ':' statements {
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_Equals, $3, $5);
            $$->loc.set(@1, @5);
        }
        | TOK_CASE WS_LOGICAL_EQ exp ':' statements {
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_WS_LOGICAL_EQ, $3, $5);
            $$->loc.set(@1, @5);
        }
        | TOK_CASE LOGICAL_EQ exp ':' { // nothing
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_Equals, $3, 0);
            $$->loc.set(@1, @4);
        }
        | TOK_CASE WS_LOGICAL_EQ exp ':' { // nothing
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_WS_LOGICAL_EQ, $3, 0);
            $$->loc.set(@1, @4);
        }
        | TOK_CASE '<' exp ':' statements {
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_LessThan, $3, $5);
            $$->loc.set(@1, @5);
        }
        | TOK_CASE '<' exp ':' { // nothing
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_LessThan, $3, 0);
            $$->loc.set(@1, @4);
        }
        | TOK_CASE '>' exp ':' statements {
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_GreaterThan, $3, $5);
            $$->loc.set(@1, @5);
        }
        | TOK_CASE '>' exp ':' { // nothing
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_GreaterThan, $3, 0);
            $$->loc.set(@1, @4);
        }
        | TOK_CASE REGEX_MATCH REGEX ':' statements {
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_RegexMatch, $3, $5);
            $$->loc.set(@1, @5);
        }
        | TOK_CASE REGEX_MATCH REGEX ':' { // nothing
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_RegexMatch, $3, 0);
            $$->loc.set(@1, @4);
        }
        | TOK_CASE REGEX_NMATCH REGEX ':' statements {
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_RegexNotMatch, $3, $5);
            $$->loc.set(@1, @5);
        }
        | TOK_CASE REGEX_NMATCH REGEX ':' { // nothing
            $$ = new ASTCaseExpression(ASTOperatorKind::AOK_RegexNotMatch, $3, 0);
            $$->loc.set(@1, @4);
        }
        | TOK_CASE REGEX ':' statements {
            $$ = new ASTCaseExpression($2, $4);
            $$->loc.set(@1, @4);
        }
        | TOK_CASE REGEX ':' { // nothing
            $$ = new ASTCaseExpression($2, 0);
            $$->loc.set(@1, @3);
        }
        | TOK_CASE exp ':' statements {
            $$ = new ASTCaseExpression($2, $4);
            $$->loc.set(@1, @4);
        }
        | TOK_CASE exp ':' { // nothing
            $$ = new ASTCaseExpression($2, 0);
            $$->loc.set(@1, @3);
        }
        | TOK_DEFAULT ':' statements {
            $$ = new ASTCaseExpression(0, $3, true);
            $$->loc.set(@1, @3);
        }
        | TOK_DEFAULT ':' { // nothing
            $$ = new ASTCaseExpression(0, 0, true);
            $$->loc.set(@1, @2);
        }
        ;

try_statement:
        TOK_TRY statement_or_block TOK_CATCH '(' myexp ')' statement_or_block {
            $$ = new ASTTryStatement($2, $5, $7);
            $$->loc.set(@1, @7);
        }
        ;

myexp:
        /* empty */  { $$ = 0; }
        | exp        { $$ = $1; }
        | error      { $$ = 0; }
        ;

hashdecl_def:
        nsc_modifiers TOK_HASHDECL IDENTIFIER '{' hashdecl_attributes '}' {
            ASTModifiers mods($1);
            ASTName name($3, ASTNameKind::ANK_Identifier);
            name.loc = @3;
            free($3);

            $$ = new ASTHashDeclaration(mods, std::move(name), $5);
            $$->loc.set(@1, @6);
            delete $5;
        }
        | nsc_modifiers TOK_HASHDECL SCOPED_REF '{' hashdecl_attributes '}' {
            ASTModifiers mods($1);
            ASTName name($3, ASTNameKind::ANK_ScopedRef);
            name.loc = @3;
            free($3);

            $$ = new ASTHashDeclaration(mods, std::move(name), $5);
            $$->loc.set(@1, @6);
            delete $5;
        }
        ;

class_def:
        nsc_modifiers CLASS_STRING inheritance_list '{' class_attributes '}' {
            ASTModifiers mods($1);
            ASTName name($2, ASTNameKind::ANK_ClassString);
            name.loc = @2;
            free($2);

            $$ = new ASTClassDeclaration(mods, std::move(name), $3, $5);
            $$->loc.set(@1, @6);
            delete $3;
            delete $5;
        }
        | nsc_modifiers CLASS_SCOPED_REF inheritance_list '{' class_attributes '}' {
            ASTModifiers mods($1);
            ASTName name($2, ASTNameKind::ANK_ClassScopedRef);
            name.loc = @2;
            free($2);

            $$ = new ASTClassDeclaration(mods, std::move(name), $3, $5);
            $$->loc.set(@1, @6);
            delete $3;
            delete $5;
        }
        | nsc_modifiers CLASS_STRING inheritance_list ';' {
            ASTModifiers mods($1);
            ASTName name($2, ASTNameKind::ANK_ClassString);
            name.loc = @2;
            free($2);

            $$ = new ASTClassDeclaration(mods, std::move(name), $3);
            $$->loc.set(@1, @4);
            delete $3;
        }
        | nsc_modifiers CLASS_SCOPED_REF inheritance_list ';' {
            ASTModifiers mods($1);
            ASTName name($2, ASTNameKind::ANK_ClassScopedRef);
            name.loc = @2;
            free($2);

            $$ = new ASTClassDeclaration(mods, std::move(name), $3);
            $$->loc.set(@1, @4);
            delete $3;
        }
        | nsc_modifiers CLASS_STRING inheritance_list '{' '}' {
            ASTModifiers mods($1);
            ASTName name($2, ASTNameKind::ANK_ClassString);
            name.loc = @2;
            free($2);

            $$ = new ASTClassDeclaration(mods, std::move(name), $3);
            $$->loc.set(@1, @5);
            delete $3;
        }
        | nsc_modifiers CLASS_SCOPED_REF inheritance_list '{' '}' {
            ASTModifiers mods($1);
            ASTName name($2, ASTNameKind::ANK_ClassScopedRef);
            name.loc = @2;
            free($2);

            $$ = new ASTClassDeclaration(mods, std::move(name), $3);
            $$->loc.set(@1, @5);
            delete $3;
        }
        ;

inheritance_list:
        TOK_INHERITS superclass_list {
            $$ = $2;
        }
        | { // NOTHING
            $$ = 0;
        }
        ;

superclass_list:
        superclass {
            $$ = new std::vector<ASTSuperclassDeclaration*>;
            $$->push_back($1);
        }
        | superclass_list ',' superclass {
            $1->push_back($3);
            $$ = $1;
        }
        ;

superclass:
        IDENTIFIER {
            ASTName name($1, ASTNameKind::ANK_Identifier);
            name.loc = @1;
            free($1);
            $$ = new ASTSuperclassDeclaration(std::move(name));
        }
        | SCOPED_REF {
            ASTName name($1, ASTNameKind::ANK_ScopedRef);
            name.loc = @1;
            free($1);
            $$ = new ASTSuperclassDeclaration(std::move(name));
        }
        | TOK_PUBLIC IDENTIFIER {
            ASTModifiers mods(ASTModifier::AM_Public);
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            $$ = new ASTSuperclassDeclaration(mods, std::move(name));
            $$->loc.setFirst(@1);
        }
        | TOK_PUBLIC SCOPED_REF {
            ASTModifiers mods(ASTModifier::AM_Public);
            ASTName name($2, ASTNameKind::ANK_ScopedRef);
            name.loc = @2;
            free($2);

            $$ = new ASTSuperclassDeclaration(mods, std::move(name));
            $$->loc.setFirst(@1);
        }
        | TOK_PRIVATE IDENTIFIER {
            ASTModifiers mods(ASTModifier::AM_Private);
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            $$ = new ASTSuperclassDeclaration(mods, std::move(name));
            $$->loc.setFirst(@1);
        }
        | TOK_PRIVATE SCOPED_REF {
            ASTModifiers mods(ASTModifier::AM_Private);
            ASTName name($2, ASTNameKind::ANK_ScopedRef);
            name.loc = @2;
            free($2);

            $$ = new ASTSuperclassDeclaration(mods, std::move(name));
            $$->loc.setFirst(@1);
        }
        | TOK_PRIVATEHIERARCHY IDENTIFIER {
            ASTModifiers mods(ASTModifier::AM_PrivateHierarchy);
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            $$ = new ASTSuperclassDeclaration(mods, std::move(name));
            $$->loc.setFirst(@1);
        }
        | TOK_PRIVATEHIERARCHY SCOPED_REF {
            ASTModifiers mods(ASTModifier::AM_PrivateHierarchy);
            ASTName name($2, ASTNameKind::ANK_ScopedRef);
            name.loc = @2;
            free($2);

            $$ = new ASTSuperclassDeclaration(mods, std::move(name));
            $$->loc.setFirst(@1);
        }
        | TOK_PRIVATEINTERNAL IDENTIFIER {
            ASTModifiers mods(ASTModifier::AM_PrivateInternal);
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            $$ = new ASTSuperclassDeclaration(mods, std::move(name));
            $$->loc.setFirst(@1);
        }
        | TOK_PRIVATEINTERNAL SCOPED_REF {
            ASTModifiers mods(ASTModifier::AM_PrivateInternal);
            ASTName name($2, ASTNameKind::ANK_ScopedRef);
            name.loc = @2;
            free($2);

            $$ = new ASTSuperclassDeclaration(mods, std::move(name));
            $$->loc.setFirst(@1);
        }
        ;

hashdecl_attributes:
        hashdecl_member {
            $$ = new std::vector<ASTHashMemberDeclaration*>;
            $$->push_back($1);
        }
        | hashdecl_attributes hashdecl_member {
            $$ = $1;
            $1->push_back($2);
        }
        ;

 hashdecl_member:
        uncqtypedef IDENTIFIER ';' {
            ASTName typeName($1, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @1;
            delete $1;
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            $$ = new ASTHashMemberDeclaration(std::move(typeName), std::move(name));
            $$->loc.set(@1, @2);
        }
        | uncqtypedef IDENTIFIER '=' exp ';' {
            ASTName typeName($1, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @1;
            delete $1;
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            $$ = new ASTHashMemberDeclaration(std::move(typeName), std::move(name), $4);
            $$->loc.set(@1, @4);
        }
        | uncqtypedef IDENTIFIER '(' myexp ')' ';' {
            ASTName typeName($1, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @1;
            delete $1;
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            $$ = new ASTHashMemberDeclaration(std::move(typeName), std::move(name), $4, true);
            $$->loc.set(@1, @5);
        }
        ;

class_attributes:
        inline_methoddef {
            $$ = new std::vector<ASTDeclaration*>;
            $$->push_back($1);
        }
        | internal_member_list {
            $$ = new std::vector<ASTDeclaration*>;
            $$->push_back($1);
        }
        | private_member_list {
            $$ = new std::vector<ASTDeclaration*>;
            $$->push_back($1);
        }
        | public_member_list {
            $$ = new std::vector<ASTDeclaration*>;
            $$->push_back($1);
        }
        | unscoped_const_decl {
            $$ = new std::vector<ASTDeclaration*>;
            $$->push_back($1);
        }
        | class_attributes inline_methoddef {
            $$ = $1;
            $1->push_back($2);
        }
        | class_attributes internal_member_list {
            $$ = $1;
            $1->push_back($2);
        }
        | class_attributes private_member_list {
            $$ = $1;
            $1->push_back($2);
        }
        | class_attributes public_member_list {
            $$ = $1;
            $1->push_back($2);
        }
        | class_attributes unscoped_const_decl {
            $$ = $1;
            $1->push_back($2);
        }
        ;

uncqtypedef:
        IDENTIFIER {
            $$ = new std::string($1);
            free($1);
        }
        | SCOPED_REF {
            $$ = new std::string($1);
            free($1);
        }
        | ANGLE_IDENTIFIER {
            $$ = new std::string($1);
            free($1);
        }
        | '*' IDENTIFIER {
            $$ = new std::string("*");
            $$->append($2);
            free($2);
        }
        | '*' SCOPED_REF {
            $$ = new std::string("*");
            $$->append($2);
            free($2);
        }
        | '*' ANGLE_IDENTIFIER {
            $$ = new std::string("*");
            $$->append($2);
            free($2);
        }
        ;

qtypedef:
        /* empty */
        { $$ = 0; }
        | uncqtypedef {
            $$ = $1;
        }
        ;

internal_member_list:
        TOK_PRIVATEINTERNAL member_list ';' {
            ASTModifiers mods(ASTModifier::AM_PrivateInternal);
            $$ = new ASTMemberGroupDeclaration(mods, $2);
            $$->loc.set(@1, @3);
            delete $2;
        }
        | TOK_PRIVATEINTERNAL '{' member_list2 '}' {
            ASTModifiers mods(ASTModifier::AM_PrivateInternal);
            $$ = new ASTMemberGroupDeclaration(mods, $3);
            $$->loc.set(@1, @4);
            delete $3;
        }
        | TOK_PRIVATEINTERNAL '{' '}' {
            ASTModifiers mods(ASTModifier::AM_PrivateInternal);
            $$ = new ASTMemberGroupDeclaration(mods);
            $$->loc.set(@1, @3);
        }
        ;

private_member_list:
        TOK_PRIVATE member_list ';' {
            ASTModifiers mods(ASTModifier::AM_Private);
            $$ = new ASTMemberGroupDeclaration(mods, $2);
            $$->loc.set(@1, @3);
            delete $2;
        }
        | TOK_PRIVATE '{' member_list2 '}' {
            ASTModifiers mods(ASTModifier::AM_Private);
            $$ = new ASTMemberGroupDeclaration(mods, $3);
            $$->loc.set(@1, @4);
            delete $3;
        }
        | TOK_PRIVATE '{' '}' {
            ASTModifiers mods(ASTModifier::AM_Private);
            $$ = new ASTMemberGroupDeclaration(mods);
            $$->loc.set(@1, @3);
        }
        | TOK_PRIVATEHIERARCHY member_list ';' {
            ASTModifiers mods(ASTModifier::AM_PrivateHierarchy);
            $$ = new ASTMemberGroupDeclaration(mods, $2);
            $$->loc.set(@1, @3);
            delete $2;
        }
        | TOK_PRIVATEHIERARCHY '{' member_list2 '}' {
            ASTModifiers mods(ASTModifier::AM_PrivateHierarchy);
            $$ = new ASTMemberGroupDeclaration(mods, $3);
            $$->loc.set(@1, @4);
            delete $3;
        }
        | TOK_PRIVATEHIERARCHY '{' '}' {
            ASTModifiers mods(ASTModifier::AM_PrivateHierarchy);
            $$ = new ASTMemberGroupDeclaration(mods);
            $$->loc.set(@1, @3);
        }
        ;

public_member_list:
        TOK_PUBLIC member_list ';' {
            ASTModifiers mods(ASTModifier::AM_Public);
            $$ = new ASTMemberGroupDeclaration(mods, $2);
            $$->loc.set(@1, @3);
            delete $2;
        }
        | TOK_PUBLIC '{' member_list2 '}' {
            ASTModifiers mods(ASTModifier::AM_Public);
            $$ = new ASTMemberGroupDeclaration(mods, $3);
            $$->loc.set(@1, @4);
            delete $3;
        }
        | TOK_PUBLIC '{' '}' {
            ASTModifiers mods(ASTModifier::AM_Public);
            $$ = new ASTMemberGroupDeclaration(mods);
            $$->loc.set(@1, @3);
        }
        ;

classvardecl:
        TOK_STATIC uncqtypedef IDENTIFIER ';' {
            ASTModifiers mods(ASTModifier::AM_Static);
            ASTName typeName($2, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @2;
            delete $2;
            ASTName name($3, ASTNameKind::ANK_Identifier);
            name.loc = @3;
            free($3);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(mods, std::move(typeName), std::move(name)));
            vd->loc.set(@1, @3);
            $$ = new ASTDeclExpression(vd.release());
        }
        | TOK_STATIC IDENTIFIER ';' {
            ASTModifiers mods(ASTModifier::AM_Static);
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(mods, std::move(name)));
            vd->loc.set(@1, @2);
            $$ = new ASTDeclExpression(vd.release());
        }
        | TOK_STATIC uncqtypedef IDENTIFIER '=' exp ';' {
            ASTModifiers mods(ASTModifier::AM_Static);
            ASTName typeName($2, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @2;
            delete $2;
            ASTName name($3, ASTNameKind::ANK_Identifier);
            name.loc = @3;
            free($3);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(mods, std::move(typeName), std::move(name)));
            vd->loc.set(@1, @3);
            $$ = new ASTAssignmentExpression(new ASTDeclExpression(vd.release()), $5);
        }
        | TOK_STATIC IDENTIFIER '=' exp ';' {
            ASTModifiers mods(ASTModifier::AM_Static);
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(mods, std::move(name)));
            vd->loc.set(@1, @2);
            $$ = new ASTAssignmentExpression(new ASTDeclExpression(vd.release()), $4);
        }
        | TOK_STATIC uncqtypedef IDENTIFIER '(' myexp ')' ';' {
            ASTModifiers mods(ASTModifier::AM_Static);
            ASTName typeName($2, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @2;
            delete $2;
            ASTName name($3, ASTNameKind::ANK_Identifier);
            name.loc = @3;
            free($3);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(mods, std::move(typeName), std::move(name)));
            vd->loc.set(@1, @3);
            $$ = new ASTCallExpression(new ASTDeclExpression(vd.release()), $5);
            $$->loc.setLast(@7);
        }
        ;

member2:
        qtypedef SELF_REF ';' {
            ASTName typeName($1, ASTNameKind::ANK_QTypedef);
            typeName.loc = @1;
            delete $1;
            ASTName name($2, ASTNameKind::ANK_SelfRef);
            name.loc = @2;
            free($2);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(std::move(typeName), std::move(name)));
            vd->loc.set(@1, @2);
            $$ = new ASTDeclExpression(vd.release());
            /*if (parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error("reference to '$.%s' with the '$.' prefix is not allowed when parse option 'allow-bare-refs' is set", $2);*/
        }
        | qtypedef SELF_REF '=' exp ';' {
            ASTName typeName($1, ASTNameKind::ANK_QTypedef);
            typeName.loc = @1;
            delete $1;
            ASTName name($2, ASTNameKind::ANK_SelfRef);
            name.loc = @2;
            free($2);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(std::move(typeName), std::move(name)));
            vd->loc.set(@1, @2);
            $$ = new ASTAssignmentExpression(new ASTDeclExpression(vd.release()), $4);
            /*if (parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error("reference to '$.%s' with the '$.' prefix is not allowed when parse option 'allow-bare-refs' is set", $2);*/
        }
        | qtypedef SELF_REF '(' myexp ')' ';' {
            ASTName typeName($1, ASTNameKind::ANK_QTypedef);
            typeName.loc = @1;
            delete $1;
            ASTName name($2, ASTNameKind::ANK_SelfRef);
            name.loc = @2;
            free($2);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(std::move(typeName), std::move(name)));
            $$ = new ASTCallExpression(new ASTDeclExpression(vd.release()), $4);
            $$->loc.setLast(@6);
            /*if (!$1)
                parse_error("cannot call constructor without declaring the class");*/
            /*if (parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error("reference to '$.%s' with the '$.' prefix is not allowed when parse option 'allow-bare-refs' is set", $2);*/
        }
        | uncqtypedef IDENTIFIER ';' {
            ASTName typeName($1, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @1;
            delete $1;
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(std::move(typeName), std::move(name)));
            $$ = new ASTDeclExpression(vd.release());
            /*if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error("member '%s' declared without '$.' prefix, but parse option 'allow-bare-refs' is not set", $2);*/
        }
        | uncqtypedef IDENTIFIER '=' exp ';' {
            ASTName typeName($1, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @1;
            delete $1;
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(std::move(typeName), std::move(name)));
            $$ = new ASTAssignmentExpression(new ASTDeclExpression(vd.release()), $4);
            /*if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error("member '%s' declared without '$.' prefix, but parse option 'allow-bare-refs' is not set", $2);*/
        }
        | uncqtypedef IDENTIFIER '(' myexp ')' ';' {
            ASTName typeName($1, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @1;
            delete $1;
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(std::move(typeName), std::move(name)));
            $$ = new ASTCallExpression(new ASTDeclExpression(vd.release()), $4);
            $$->loc.setLast(@6);
            /*if (!$1)
                parse_error("cannot call constructor without declaring the class");
            if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error("member '%s' declared without '$.' prefix, but parse option 'allow-bare-refs' is not set", $2);*/
        }
        | IDENTIFIER ';' {
            ASTName name($1, ASTNameKind::ANK_Identifier);
            name.loc = @1;
            free($1);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(std::move(name)));
            vd->loc = @1;
            $$ = new ASTDeclExpression(vd.release());
            /*if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error("member '%s' declared without '$.' prefix, but parse option 'allow-bare-refs' is not set", $1);*/
        }
        | IDENTIFIER '=' exp ';' {
            ASTName name($1, ASTNameKind::ANK_Identifier);
            name.loc = @1;
            free($1);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(std::move(name)));
            vd->loc = @1;
            $$ = new ASTAssignmentExpression(new ASTDeclExpression(vd.release()), $3);
            /*if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error("member '%s' declared without '$.' prefix, but parse option 'allow-bare-refs' is not set", $1);*/
        }
        | classvardecl {
            $$ = $1;
        }
        | unscoped_const_decl {
            $$ = new ASTDeclExpression($1);
        }
        ;

member:
        SELF_REF {
            ASTName name($1, ASTNameKind::ANK_SelfRef);
            name.loc = @1;
            free($1);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(std::move(name)));
            vd->loc = @1;
            $$ = new ASTDeclExpression(vd.release());
        }
        | SELF_REF SCOPED_REF {
            ASTName name($1, ASTNameKind::ANK_SelfAndScopedRef);
            name.name += $2;
            name.loc.set(@1, @2);
            free($1);
            free($2);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(std::move(name)));
            vd->loc.set(@1, @2);
            $$ = new ASTDeclExpression(vd.release());
        }
        ;

transient_member2:
        TOK_TRANSIENT member2 {
            switch ($2->getKind()) {
                case ASTExpressionKind::AEK_Assignment: {
                    ASTAssignmentExpression* e = static_cast<ASTAssignmentExpression*>($2);
                    e->loc.setFirst(@1);
                    ASTDeclExpression* de = static_cast<ASTDeclExpression*>(e->left.get());
                    de->loc.setFirst(@1);
                    ASTVariableDeclaration* d = static_cast<ASTVariableDeclaration*>(de->declaration.get());
                    d->loc.setFirst(@1);
                    d->modifiers.add(AM_Transient);
                    break;
                }
                case ASTExpressionKind::AEK_Call: {
                    ASTCallExpression* e = static_cast<ASTCallExpression*>($2);
                    e->loc.setFirst(@1);
                    ASTDeclExpression* de = static_cast<ASTDeclExpression*>(e->target.get());
                    de->loc.setFirst(@1);
                    ASTVariableDeclaration* d = static_cast<ASTVariableDeclaration*>(de->declaration.get());
                    d->loc.setFirst(@1);
                    d->modifiers.add(AM_Transient);
                    break;
                }
                case ASTExpressionKind::AEK_Decl: {
                    ASTDeclExpression* e = static_cast<ASTDeclExpression*>($2);
                    e->loc.setFirst(@1);
                    ASTVariableDeclaration* d = static_cast<ASTVariableDeclaration*>(e->declaration.get());
                    d->loc.setFirst(@1);
                    d->modifiers.add(AM_Transient);
                    break;
                }
                default: {
                    yyerror(&@2, yyscanner, errorLog, parseTree, "invalid member expression");
                    YYERROR;
                }
            }
            $$ = $2;
        }
        ;

transient_member:
        TOK_TRANSIENT member {
            switch ($2->getKind()) {
                case ASTExpressionKind::AEK_Decl: {
                    ASTDeclExpression* e = static_cast<ASTDeclExpression*>($2);
                    e->loc.setFirst(@1);
                    ASTVariableDeclaration* d = static_cast<ASTVariableDeclaration*>(e->declaration.get());
                    d->loc.setFirst(@1);
                    d->modifiers.add(AM_Transient);
                    break;
                }
                default: {
                    yyerror(&@2, yyscanner, errorLog, parseTree, "invalid member expression");
                    YYERROR;
                }
            }
            $$ = $2;
        }
        ;

member_list:
        member {
            $$ = new std::vector<ASTExpression*>;
            $$->push_back($1);
        }
        | transient_member {
            $$ = new std::vector<ASTExpression*>;
            $$->push_back($1);
        }
        | member_list ',' member {
            $1->push_back($3);
            $$ = $1;
        }
        | member_list ',' transient_member {
            $1->push_back($3);
            $$ = $1;
        }
        ;

member_list2:
        member2 {
            $$ = new std::vector<ASTExpression*>;
            $$->push_back($1);
        }
        | transient_member2 {
            $$ = new std::vector<ASTExpression*>;
            $$->push_back($1);
        }
        | member_list2 member2 {
            $1->push_back($2);
            $$ = $1;
        }
        | member_list2 transient_member2 {
            $1->push_back($2);
            $$ = $1;
        }
        ;

ident_openparen:
        IDENTIFIER '(' {
            $$ = $1;
        }
        | TOK_KEYS '(' {
            $$ = strdup("keys");
        }
        | KW_IDENTIFIER_OPENPAREN {
            $$ = $1;
        }
        ;

inline_methoddef:
        nsc_modifiers ident_openparen myexp ')' base_constructor_list return_value block {
            ASTModifiers mods($1);
            ASTName name($2, ASTNameKind::ANK_IdentOpenParen);
            name.loc = @2;
            free($2);

            $$ = new ASTFunctionDeclaration(mods, std::move(name), AFDKind::AFDK_Inline, $6, $3, $5, $7);
            $$->loc.set(@1, @7);
        }
        | nsc_modifiers uncqtypedef ident_openparen myexp ')' base_constructor_list block {
            ASTModifiers mods($1);
            ASTName typeName($2, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @2;
            delete $2;
            ASTName name($3, ASTNameKind::ANK_IdentOpenParen);
            name.loc = @3;
            free($3);

            $$ = new ASTFunctionDeclaration(
                mods,
                std::move(name),
                AFDKind::AFDK_Inline,
                new ASTNameExpression(std::move(typeName)),
                $4, $6, $7
            );
            $$->loc.set(@1, @7);
        }
        | nsc_modifiers ident_openparen myexp ')' base_constructor_list return_value ';' {
            ASTModifiers mods($1);
            ASTName name($2, ASTNameKind::ANK_IdentOpenParen);
            name.loc = @2;
            free($2);

            $$ = new ASTFunctionDeclaration(mods, std::move(name), AFDKind::AFDK_Inline, $6, $3, $5);
            $$->loc.set(@1, @7);
        }
        | nsc_modifiers uncqtypedef ident_openparen myexp ')' base_constructor_list ';' {
            ASTModifiers mods($1);
            ASTName typeName($2, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @2;
            delete $2;
            ASTName name($3, ASTNameKind::ANK_IdentOpenParen);
            name.loc = @3;
            free($3);

            $$ = new ASTFunctionDeclaration(
                mods,
                std::move(name),
                AFDKind::AFDK_Inline,
                new ASTNameExpression(std::move(typeName)),
                $4, $6
            );
            $$->loc.set(@1, @7);
        }
        ;

outofline_methoddef:
        method_modifiers SCOPED_REF '(' myexp ')' base_constructor_list return_value block {
            ASTModifiers mods($1);
            ASTName name($2, ASTNameKind::ANK_ScopedRef);
            name.loc = @2;
            free($2);

            $$ = new ASTFunctionDeclaration(mods, std::move(name), AFDKind::AFDK_Outofline, $7, $4, $6, $8);
            $$->loc.set(@1, @8);
        }
        | method_modifiers uncqtypedef SCOPED_REF '(' myexp ')' base_constructor_list block {
            ASTModifiers mods($1);
            ASTName typeName($2, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @2;
            delete $2;
            ASTName name($3, ASTNameKind::ANK_ScopedRef);
            name.loc = @3;
            free($3);

            $$ = new ASTFunctionDeclaration(
                mods,
                std::move(name),
                AFDKind::AFDK_Outofline,
                new ASTNameExpression(std::move(typeName)),
                $5, $7, $8
            );
            $$->loc.set(@1, @8);
        }
        | method_modifiers SCOPED_REF '(' myexp ')' base_constructor_list return_value ';' {
            ASTModifiers mods($1);
            ASTName name($2, ASTNameKind::ANK_ScopedRef);
            name.loc = @2;
            free($2);

            $$ = new ASTFunctionDeclaration(mods, std::move(name), AFDKind::AFDK_Outofline, $7, $4, $6);
            $$->loc.set(@1, @8);
        }
        | method_modifiers uncqtypedef SCOPED_REF '(' myexp ')' base_constructor_list ';' {
            ASTModifiers mods($1);
            ASTName typeName($2, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @2;
            delete $2;
            ASTName name($3, ASTNameKind::ANK_ScopedRef);
            name.loc = @3;
            free($3);

            $$ = new ASTFunctionDeclaration(
                mods,
                std::move(name),
                AFDKind::AFDK_Outofline,
                new ASTNameExpression(std::move(typeName)),
                $5, $7
            );
            $$->loc.set(@1, @8);
        }
        | SCOPED_REF '(' myexp ')' base_constructor_list return_value block {
            ASTName name($1, ASTNameKind::ANK_ScopedRef);
            name.loc = @1;
            free($1);

            $$ = new ASTFunctionDeclaration(std::move(name), AFDKind::AFDK_Outofline, $6, $3, $5, $7);
            $$->loc.set(@1, @7);
        }
        | uncqtypedef SCOPED_REF '(' myexp ')' base_constructor_list block {
            ASTName typeName($1, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @1;
            delete $1;
            ASTName name($2, ASTNameKind::ANK_ScopedRef);
            name.loc = @2;
            free($2);

            $$ = new ASTFunctionDeclaration(
                std::move(name),
                AFDKind::AFDK_Outofline,
                new ASTNameExpression(std::move(typeName)),
                $4, $6, $7
            );
            $$->loc.set(@1, @7);
        }
        ;

base_constructor_list:
        ':' base_constructors {
            $$ = new ASTConstrInitExpression($2);
            $$->loc = @2;
            delete $2;
        }
        | { // nothing
            $$ = 0;
        }
        ;

base_constructors:
        base_constructor {
            $$ = new std::vector<ASTCallExpression*>;
            $$->push_back($1);
        }
        | base_constructors ',' base_constructor {
            $$ = $1;
            $1->push_back($3);
        }
        ;

base_constructor:
        IDENTIFIER '(' myexp ')' {
            ASTName name($1, ASTNameKind::ANK_Identifier);
            name.loc = @1;
            free($1);
            $$ = new ASTCallExpression(new ASTNameExpression(std::move(name)), $3);
        }
        | SCOPED_REF '(' myexp ')' {
            ASTName name($1, ASTNameKind::ANK_ScopedRef);
            name.loc = @1;
            free($1);
            $$ = new ASTCallExpression(new ASTNameExpression(std::move(name)), $3);
        }
        ;

nsc_modifiers:
        // nothing
        { $$ = 0; }
        | method_modifiers {
            $$ = $1;
        }

method_modifiers:
        method_modifier { $$ = $1; }
        | method_modifiers method_modifier {
            /*if (($1 | $2) == $1)
                parse_error("modifier given twice");*/
            $$ = $1 | $2;
        }
        ;

method_modifier:
        TOK_PRIVATE { $$ = ASTModifier::AM_Private; }
        | TOK_PRIVATEHIERARCHY { $$ = ASTModifier::AM_PrivateHierarchy; }
        | TOK_PRIVATEINTERNAL { $$ = ASTModifier::AM_PrivateInternal; }
        | TOK_STATIC { $$ = ASTModifier::AM_Static; }
        | TOK_SYNCHRONIZED { $$ = ASTModifier::AM_Synchronized; }
        | TOK_DEPRECATED { $$ = ASTModifier::AM_Deprecated; }
        | TOK_PUBLIC { $$ = ASTModifier::AM_Public; }
        | TOK_FINAL { $$ = ASTModifier::AM_Final; }
        | TOK_ABSTRACT { $$ = ASTModifier::AM_Abstract; }
        ;

return_value:
        TOK_RETURNS qtypedef {
            if ($2) {
                ASTName typeName($2, ASTNameKind::ANK_QTypedef);
                typeName.loc = @2;
                delete $2;
                $$ = new ASTReturnsExpression(new ASTNameExpression(std::move(typeName)));
            }
            else {
                $$ = new ASTReturnsExpression();
            }
            $$->loc.set(@1, @2);
        }
        | /* nothing */
        { $$ = 0; }

sub_def:
        TOK_SUB ident_openparen myexp ')' return_value block {
            ASTName name($2, ASTNameKind::ANK_IdentOpenParen);
            name.loc = @2;
            free($2);

            $$ = new ASTFunctionDeclaration(std::move(name), AFDKind::AFDK_Sub, $5, $3, nullptr, $6);
            $$->loc.set(@1, @6);
        }
        | uncqtypedef TOK_SUB ident_openparen myexp ')' block {
            ASTName typeName($1, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @1;
            delete $1;
            ASTName name($3, ASTNameKind::ANK_IdentOpenParen);
            name.loc = @3;
            free($3);

            $$ = new ASTFunctionDeclaration(
                std::move(name),
                AFDKind::AFDK_Sub,
                new ASTNameExpression(std::move(typeName)),
                $4, nullptr, $6
            );
            $$->loc.set(@1, @6);
        }
        | method_modifiers TOK_SUB ident_openparen myexp ')' return_value block {
            ASTModifiers mods($1);
            ASTName name($3, ASTNameKind::ANK_IdentOpenParen);
            name.loc = @3;
            free($3);

            $$ = new ASTFunctionDeclaration(mods, std::move(name), AFDKind::AFDK_Sub, $6, $4, nullptr, $7);
            $$->loc.set(@1, @7);
        }
        | method_modifiers uncqtypedef TOK_SUB ident_openparen myexp ')' block {
            ASTModifiers mods($1);
            ASTName typeName($2, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @2;
            delete $2;
            ASTName name($4, ASTNameKind::ANK_IdentOpenParen);
            name.loc = @4;
            free($4);

            $$ = new ASTFunctionDeclaration(
                mods,
                std::move(name),
                AFDKind::AFDK_Sub,
                new ASTNameExpression(std::move(typeName)),
                $5, nullptr, $7
            );
            $$->loc.set(@1, @7);
        }

scoped_sub_def:
        TOK_SUB SCOPED_REF '(' myexp ')' return_value block {
            ASTName name($2, ASTNameKind::ANK_ScopedRef);
            name.loc = @2;
            free($2);

            $$ = new ASTFunctionDeclaration(std::move(name), AFDKind::AFDK_ScopedSub, $6, $4, nullptr, $7);
            $$->loc.set(@1, @7);
        }
        | uncqtypedef TOK_SUB SCOPED_REF '(' myexp ')' block {
            ASTName typeName($1, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @1;
            delete $1;
            ASTName name($3, ASTNameKind::ANK_ScopedRef);
            name.loc = @3;
            free($3);

            $$ = new ASTFunctionDeclaration(
                std::move(name),
                AFDKind::AFDK_ScopedSub,
                new ASTNameExpression(std::move(typeName)),
                $5, nullptr, $7
            );
            $$->loc.set(@1, @7);
        }
        | method_modifiers TOK_SUB SCOPED_REF '(' myexp ')' return_value block {
            ASTModifiers mods($1);
            ASTName name($3, ASTNameKind::ANK_ScopedRef);
            name.loc = @3;
            free($3);

            $$ = new ASTFunctionDeclaration(mods, std::move(name), AFDKind::AFDK_ScopedSub, $7, $5, nullptr, $8);
            $$->loc.set(@1, @8);
        }
        | method_modifiers uncqtypedef TOK_SUB SCOPED_REF '(' myexp ')' block {
            ASTModifiers mods($1);
            ASTName typeName($2, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @2;
            delete $2;
            ASTName name($4, ASTNameKind::ANK_ScopedRef);
            name.loc = @4;
            free($4);

            $$ = new ASTFunctionDeclaration(
                mods,
                std::move(name),
                AFDKind::AFDK_ScopedSub,
                new ASTNameExpression(std::move(typeName)),
                $6, nullptr, $8
            );
            $$->loc.set(@1, @8);
        }
        ;

list:
        exp ',' exp {
            $$ = new ASTListExpression;
            $$->elements.push_back($1);
            $$->elements.push_back($3);
            $$->loc.set(@1, @3);
        }
        | exp ',' {
            if ($1 && $1->getKind() == ASTExpressionKind::AEK_List) {
                $$ = static_cast<ASTListExpression*>($1);
            }
            else {
                $$ = new ASTListExpression;
                $$->elements.push_back($1);
                $$->loc = @1;
            }
        }
        ;

list_n:
        exp_n ',' exp {
            $$ = new ASTListExpression;
            $$->elements.push_back($1);
            $$->elements.push_back($3);
            $$->loc.set(@1, @3);
        }
        | exp_n ',' {
            if ($1 && $1->getKind() == ASTExpressionKind::AEK_List) {
                $$ = static_cast<ASTListExpression*>($1);
            }
            else {
                $$ = new ASTListExpression;
                $$->elements.push_back($1);
                $$->loc = @1;
            }
        }
        ;

hash:
        hash_element {
            $$ = new ASTHashExpression;
            $$->elements.push_back($1);
            $$->loc = @1;
        }
        | hash ',' hash_element {
            $$ = $1;
            $$->elements.push_back($3);
            $$->loc.setLast(@3);
        }
        | hash ','
        { /* empty ',' on end of hash */ $$ = $1; }
        ;

hash_element:
        exp ':' exp
        { $$ = new ASTHashElementExpression($1, $3); }
        ;

alt_hash:
        '{' hash '}' {
            $$ = $2;
        }
        ;

immediate_typed_hash:
        HASHDECL_IDENTIFIER_OPENCURLY hash '}' {
            ASTName hashdecl($1, ASTNameKind::ANK_HashdeclHash);
            hashdecl.loc = @1;
            free($1);

            $$ = new ASTHashdeclHashExpression(std::move(hashdecl), $2);
            $$->loc.set(@1, @3);
        }
        | HASHDECL_IDENTIFIER_OPENCURLY '}' {
            ASTName hashdecl($1, ASTNameKind::ANK_HashdeclHash);
            hashdecl.loc = @1;
            free($1);

            $$ = new ASTHashdeclHashExpression(std::move(hashdecl));
            $$->loc.set(@1, @2);
        }
        ;

exp_c:
        scalar
        { $$ = $1; }
        | BINARY
        { $$ = new ASTLiteralExpression(ALEKind::ALEK_Binary, $1); $$->loc = @1; }
        | '(' hash ')'
        { $$ = $2; }
        | SCOPED_REF {
            ASTName name($1, ASTNameKind::ANK_ScopedRef);
            name.loc = @1;
            free($1);
            $$ = new ASTNameExpression(std::move(name));
        }
        | qtypedef VAR_REF {
            if ($1) {
                ASTName typeName($1, ASTNameKind::ANK_QTypedef);
                typeName.loc = @1;
                delete $1;
                ASTName name($2, ASTNameKind::ANK_VarRef);
                name.loc = @2;
                free($2);

                ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(std::move(typeName), std::move(name)));
                vd->loc.set(@1, @2);
                $$ = new ASTDeclExpression(vd.release());
            }
            else {
                ASTName name($2, ASTNameKind::ANK_VarRef);
                name.loc = @2;
                free($2);
                $$ = new ASTNameExpression(std::move(name));
            }
        }
        | qtypedef SCOPED_VREF {
            if ($1) {
                ASTName typeName($1, ASTNameKind::ANK_QTypedef);
                typeName.loc = @1;
                delete $1;
                ASTName name($2, ASTNameKind::ANK_ScopedVref);
                name.loc = @2;
                free($2);

                ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(std::move(typeName), std::move(name)));
                vd->loc.set(@1, @2);
                $$ = new ASTDeclExpression(vd.release());
            }
            else {
                ASTName name($2, ASTNameKind::ANK_ScopedVref);
                name.loc = @2;
                free($2);
                $$ = new ASTNameExpression(std::move(name));
            }
        }
        | uncqtypedef IDENTIFIER {
            ASTName typeName($1, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @1;
            delete $1;
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(std::move(typeName), std::move(name)));
            vd->loc.set(@1, @2);
            $$ = new ASTDeclExpression(vd.release());
        }
        | TOK_MY uncqtypedef IDENTIFIER {
            ASTModifiers mods(ASTModifier::AM_My);
            ASTName typeName($2, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @2;
            delete $2;
            ASTName name($3, ASTNameKind::ANK_Identifier);
            name.loc = @3;
            free($3);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(mods, std::move(typeName), std::move(name)));
            vd->loc.set(@1, @3);
            $$ = new ASTDeclExpression(vd.release());
        }
        | TOK_MY IDENTIFIER {
            ASTModifiers mods(ASTModifier::AM_My);
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(mods, std::move(name)));
            vd->loc.set(@1, @2);
            $$ = new ASTDeclExpression(vd.release());
        }
        | TOK_MY qtypedef VAR_REF {
            ASTModifiers mods(ASTModifier::AM_My);
            ASTName typeName(ASTNameKind::ANK_QTypedef);
            if ($2) {
                typeName.name = *($2);
                typeName.loc = @2;
                delete $2;
            }
            ASTName name($3, ASTNameKind::ANK_VarRef);
            name.loc = @3;
            free($3);

            ASTVariableDeclaration::Ptr vd(new ASTVariableDeclaration(mods, std::move(typeName), std::move(name)));
            vd->loc.set(@1, @3);
            $$ = new ASTDeclExpression(vd.release());
        }
        | TOK_MY '(' list ')' {
            ASTModifiers mods(ASTModifier::AM_My);
            ASTVarListDeclaration::Ptr vld(new ASTVarListDeclaration(mods, $3));
            vld->loc.set(@1, @4);
            $$ = new ASTDeclExpression(vld.release());
        }
        | gvardecl {
            $$ = new ASTDeclExpression($1);
        }
        | IDENTIFIER {
            ASTName name($1, ASTNameKind::ANK_Identifier);
            name.loc = @1;
            free($1);
            $$ = new ASTNameExpression(std::move(name));
        }
        | CONTEXT_REF {
            ASTName name($1, ASTNameKind::ANK_ContextRef);
            name.loc = @1;
            free($1);
            $$ = new ASTNameExpression(std::move(name));
        }
        | TOK_CONTEXT_ROW {
            $$ = new ASTContextRowExpression;
            $$->loc = @1;
        }
        | COMPLEX_CONTEXT_REF {
            ASTName name($1, ASTNameKind::ANK_ComplexContextRef);
            name.loc = @1;
            free($1);
            $$ = new ASTNameExpression(std::move(name));
        }
        | TOK_FIND exp TOK_IN exp TOK_WHERE '(' exp ')' {
            $$ = new ASTFindExpression($2, $4, $7);
            $$->loc.set(@1, @8);
        }
        | TOK_EXISTS exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Exists);
            $$->loc.setFirst(@1);
        }
        | TOK_ELEMENTS exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Elements);
            $$->loc.setFirst(@1);
        }
        | TOK_KEYS exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Keys);
            $$->loc.setFirst(@1);
        }
        | TOK_UNSHIFT exp {  // unshift list, element
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Unshift);
            $$->loc.setFirst(@1);
        }
        | TOK_SHIFT exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Shift);
            $$->loc.setFirst(@1);
        }
        | TOK_PUSH exp {  // push lvalue-list, element
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Push);
            $$->loc.setFirst(@1);
        }
        | TOK_POP exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Pop);
            $$->loc.setFirst(@1);
        }
        | TOK_CHOMP exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Chomp);
            $$->loc.setFirst(@1);
        }
        | TOK_TRIM exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Trim);
            $$->loc.setFirst(@1);
        }
        | TOK_SPLICE exp {  // splice lvalue-list, offset, [length, list]
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Splice);
            $$->loc.setFirst(@1);
        }
        | TOK_EXTRACT exp {  // extract lvalue-list, offset, [length, list]
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Extract);
            $$->loc.setFirst(@1);
        }
        | TOK_MAP exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Map);
            $$->loc.setFirst(@1);
        }
        | TOK_FOLDR exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Foldr);
            $$->loc.setFirst(@1);
        }
        | TOK_FOLDL exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Foldl);
            $$->loc.setFirst(@1);
        }
        | TOK_SELECT exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Select);
            $$->loc.setFirst(@1);
        }
        | P_INCREMENT exp {  // pre-increment
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_PreIncrement);
            $$->loc.setFirst(@1);
        }
        | P_DECREMENT exp {  // pre-decrement
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_PreDecrement);
            $$->loc.setFirst(@1);
        }
        | BASE_CLASS_CALL '(' myexp ')' {
            ASTName name($1, ASTNameKind::ANK_BaseClassCall);
            name.loc = @1;
            free($1);
            $$ = new ASTCallExpression(new ASTNameExpression(std::move(name)), $3);
        }
        | KW_IDENTIFIER_OPENPAREN myexp ')' {
            ASTName name($1, ASTNameKind::ANK_KWIdentifier);
            name.loc.setFirst(@1);
            name.loc.lastLine = @1.lastLine - 1;
            name.loc.lastCol = @1.lastCol - 1;
            free($1);
            $$ = new ASTCallExpression(new ASTNameExpression(std::move(name)), $2);
        }
        | SELF_REF {
            ASTName name($1, ASTNameKind::ANK_SelfRef);
            name.loc = @1;
            free($1);
            $$ = new ASTNameExpression(std::move(name));
            /*if (parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error("reference to '$.%s' with the '$.' prefix is not allowed when parse option 'allow-bare-refs' is set", $1);*/
        }
        | '-' exp %prec NEG {
            // FIXME: need to expand constant expression checking to determine
            //        if the expression can throw an exception at run-time
            // first check for constant expressions
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_UnaryMinus);
            $$->loc.setFirst(@1);
        }
        | '+' exp %prec UPLUS {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_UnaryPlus);
            $$->loc.setFirst(@1);
        }
        | '~' exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_BinaryNot);
            $$->loc.setFirst(@1);
        }
        | '!' exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_LogicalNot);
            $$->loc.setFirst(@1);
        }
        | '\\' exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Reference);
            $$->loc.setFirst(@1);
        }
        | TOK_NEW IDENTIFIER '(' myexp ')' {
            ASTName name($2, ASTNameKind::ANK_Identifier);
            name.loc = @2;
            free($2);

            ASTCallExpression::Ptr ce(new ASTCallExpression(new ASTNameExpression(std::move(name)), $4));
            $$ = new ASTUnaryExpression(ce.release(), ASTOperatorKind::AOK_New);
        }
        | TOK_NEW SCOPED_REF '(' myexp ')' {
            ASTName name($2, ASTNameKind::ANK_ScopedRef);
            name.loc = @2;
            free($2);

            ASTCallExpression::Ptr ce(new ASTCallExpression(new ASTNameExpression(std::move(name)), $4));
            $$ = new ASTUnaryExpression(ce.release(), ASTOperatorKind::AOK_New);
            $$->loc.setFirst(@1);
        }
        | TOK_NEW ANGLE_IDENTIFIER '(' myexp ')' {
            ASTName name($2, ASTNameKind::ANK_AngleIdentifier);
            name.loc = @2;
            free($2);

            ASTCallExpression::Ptr ce(new ASTCallExpression(new ASTNameExpression(std::move(name)), $4));
            $$ = new ASTUnaryExpression(ce.release(), ASTOperatorKind::AOK_New);
            $$->loc.setFirst(@1);
        }
        | TOK_BACKGROUND exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Background);
            $$->loc.setFirst(@1);
        }
        | BACKQUOTE {
            $$ = new ASTBackquoteExpression($1);
            $$->loc = @1;
            free($1);
        }
        | '(' exp ')' {
            $$ = $2;
        }
        | '(' ')' { $$ = new ASTListExpression; $$->loc.set(@1, @2); }
        | TOK_SUB '(' myexp ')' return_value block {
            ASTClosureDeclaration::Ptr cd(new ASTClosureDeclaration($5, $3, $6));
            cd->loc.set(@1, @6);
            $$ = new ASTClosureExpression(cd.release());
        }
        | uncqtypedef TOK_SUB '(' myexp ')' block {
            ASTName typeName($1, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @1;
            delete $1;

            ASTClosureDeclaration::Ptr cd(new ASTClosureDeclaration(new ASTNameExpression(std::move(typeName)), $4, $6));
            cd->loc.set(@1, @6);
            $$ = new ASTClosureExpression(cd.release());
        }
        | method_modifiers TOK_SUB '(' myexp ')' return_value block {
            ASTModifiers mods($1);
            ASTClosureDeclaration::Ptr cd(new ASTClosureDeclaration(mods, $6, $4, $7));
            cd->loc.set(@1, @7);
            $$ = new ASTClosureExpression(cd.release());
        }
        | method_modifiers uncqtypedef TOK_SUB '(' myexp ')' block {
            ASTModifiers mods($1);
            ASTName typeName($2, ASTNameKind::ANK_UncQTypedef);
            typeName.loc = @2;
            delete $2;

            ASTClosureDeclaration::Ptr cd(new ASTClosureDeclaration(mods, new ASTNameExpression(std::move(typeName)), $5, $7));
            cd->loc.set(@1, @7);
            $$ = new ASTClosureExpression(cd.release());
        }
        | IMPLICIT_ARG_REF {
            $$ = new ASTImplicitArgExpression($1);
            $$->loc = @1;
        }
        | IMPLICIT_ELEMENT {
            $$ = new ASTImplicitElemExpression;
            $$->loc = @1;
        }
        | TOK_DELETE exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Delete);
            $$->loc.setFirst(@1);
        }
        | TOK_REMOVE exp {
            $$ = new ASTUnaryExpression($2, ASTOperatorKind::AOK_Remove);
            $$->loc.setFirst(@1);
        }
        | QORE_CAST '(' exp ')' {
            ASTName castType($1, ASTNameKind::ANK_CastType);
            castType.loc = @1;
            free($1);
            $$ = new ASTCastExpression(castType, $3);
            $$->loc.setLast(@4);
        }
        ;

exp:
        exp_c
        | list                              { $$ = $1; }
        | alt_hash                          { $$ = $1; }
        | immediate_typed_hash              { $$ = $1; }
        | '{' '}'                           { $$ = new ASTHashExpression; $$->loc.set(@1, @2); }
        | exp PLUS_EQUALS exp               { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_PlusEquals, $3); }
        | exp MINUS_EQUALS exp              { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_MinusEquals, $3); }
        | exp AND_EQUALS exp                { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_AndEquals, $3); }
        | exp OR_EQUALS exp                 { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_OrEquals, $3); }
        | exp MODULO_EQUALS exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_ModuloEquals, $3); }
        | exp MULTIPLY_EQUALS exp           { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_MultiplyEquals, $3); }
        | exp DIVIDE_EQUALS exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_DivideEquals, $3); }
        | exp XOR_EQUALS exp                { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_XorEquals, $3); }
        | exp SHIFT_LEFT_EQUALS exp         { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_ShiftLeftEquals, $3); }
        | exp SHIFT_RIGHT_EQUALS exp        { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_ShiftRightEquals, $3); }
        | exp WS_PLUS_EQUALS exp            { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_PLUS_EQUALS, $3); }
        | exp WS_MINUS_EQUALS exp           { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_MINUS_EQUALS, $3); }
        | exp WS_AND_EQUALS exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_AND_EQUALS, $3); }
        | exp WS_OR_EQUALS exp              { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_OR_EQUALS, $3); }
        | exp WS_MODULO_EQUALS exp          { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_MODULO_EQUALS, $3); }
        | exp WS_MULTIPLY_EQUALS exp        { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_MULTIPLY_EQUALS, $3); }
        | exp WS_DIVIDE_EQUALS exp          { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_DIVIDE_EQUALS, $3); }
        | exp WS_XOR_EQUALS exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_XOR_EQUALS, $3); }
        | exp WS_SHIFT_LEFT_EQUALS exp      { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_SHIFT_LEFT_EQUALS, $3); }
        | exp WS_SHIFT_RIGHT_EQUALS exp     { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_SHIFT_RIGHT_EQUALS, $3); }
        | exp '=' exp                       { $$ = new ASTAssignmentExpression($1, $3); }
        | exp WEAK_ASSIGNMENT exp           { $$ = new ASTAssignmentExpression($1, $3, true); }
        | exp TOK_INSTANCEOF uncqtypedef    {
            ASTName name($3, ASTNameKind::ANK_UncQTypedef);
            name.loc = @3;
            delete $3;
            $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Instanceof, new ASTNameExpression(std::move(name)));
        }
        | exp NULL_COALESCING exp           { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_NullCoalesce, $3); }
        | exp VALUE_COALESCING exp          { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_ValueCoalesce, $3); }
        | exp '?' exp ':' exp               { $$ = new ASTTernaryExpression($1, $3, $5); }
        | exp P_INCREMENT                   { // post-increment
            $$ = new ASTUnaryExpression($1, ASTOperatorKind::AOK_PostIncrement);
            $$->loc.setLast(@2);
        }
        | exp P_DECREMENT                   { // post-decrement
            $$ = new ASTUnaryExpression($1, ASTOperatorKind::AOK_PostDecrement);
            $$->loc.setLast(@2);
        }
        | exp '(' myexp ')'                 {
            $$ = new ASTCallExpression($1, $3);
        }
        | exp LOGICAL_AND exp               { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_LogicalAnd, $3); }
        | exp LOGICAL_OR exp                { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_LogicalOr, $3); }
        | exp BROKEN_LOGICAL_OR exp         { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_BrokenLogicalOr, $3); }
        | exp BROKEN_BINARY_OR exp          { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_BrokenBinaryOr, $3); }
        | exp BROKEN_BINARY_XOR exp         { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_BrokenBinaryXor, $3); }
        | exp '|' exp                       { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_BinaryOr, $3); }
        | exp '&' exp                       { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_BinaryAnd, $3); }
        | exp '^' exp                       { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_BinaryXor, $3); }
        | exp REGEX_MATCH REGEX             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_RegexMatch, $3); }
        | exp REGEX_NMATCH REGEX            { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_RegexNotMatch, $3); }
        | exp REGEX_MATCH REGEX_SUBST       { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_RegexMatch, $3); }
        | exp REGEX_MATCH REGEX_TRANS       { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_RegexMatch, $3); }
        | exp REGEX_MATCH REGEX_EXTRACT     { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_RegexMatch, $3); }
        | exp '>' exp                       { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_GreaterThan, $3); }
        | exp '<' exp                       { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_LessThan, $3); }
        | exp LOGICAL_CMP exp               { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Comparison, $3); }
        | exp LOGICAL_EQ exp                { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Equals, $3); }
        | exp ABSOLUTE_EQ exp               { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_AbsoluteEquals, $3); }
        | exp ABSOLUTE_NE exp               { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_AbsoluteNotEquals, $3); }
        | exp LOGICAL_NE exp                { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_NotEquals, $3); }
        | exp LOGICAL_LE exp                { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_LessThanOrEquals, $3); }
        | exp LOGICAL_GE exp                { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_GreaterThanOrEquals, $3); }
        | exp SHIFT_LEFT exp                { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_ShiftLeft, $3); }
        | exp SHIFT_RIGHT exp               { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_ShiftRight, $3); }
        | exp WS_LOGICAL_AND exp            { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_LOGICAL_AND, $3); }
        | exp WS_LOGICAL_OR exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_LOGICAL_OR, $3); }
        | exp WS_BROKEN_LOGICAL_OR exp      { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_BROKEN_LOGICAL_OR, $3); }
        | exp WS_LOGICAL_CMP exp            { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_LOGICAL_CMP, $3); }
        | exp WS_LOGICAL_EQ exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_LOGICAL_EQ, $3); }
        | exp WS_ABSOLUTE_EQ exp            { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_ABSOLUTE_EQ, $3); }
        | exp WS_ABSOLUTE_NE exp            { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_ABSOLUTE_NE, $3); }
        | exp WS_LOGICAL_NE exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_LOGICAL_NE, $3); }
        | exp WS_LOGICAL_LE exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_LOGICAL_LE, $3); }
        | exp WS_LOGICAL_GE exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_LOGICAL_GE, $3); }
        | exp WS_SHIFT_LEFT exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_SHIFT_LEFT, $3); }
        | exp WS_SHIFT_RIGHT exp            { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_SHIFT_RIGHT, $3); }
        | exp '+' exp                       { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Plus, $3); }
        | exp '-' exp                       { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Minus, $3); }
        | exp '%' exp                       { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Modulo, $3); }
        | exp '/' exp                       { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Divide, $3); }
        | exp '*' exp                       { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Multiply, $3); }
        | exp '[' exp RANGE ']'             {
            ASTRangeExpression::Ptr re(new ASTRangeExpression($3, nullptr));
            re->loc.setLast(@4);
            $$ = new ASTIndexExpression($1, re.release(), ASTIndexExpression::IndexKind::AIE_SquareBrackets);
        }
        | exp '[' RANGE exp ']'             {
            ASTRangeExpression::Ptr re(new ASTRangeExpression(nullptr, $4));
            re->loc.setFirst(@3);
            $$ = new ASTIndexExpression($1, re.release(), ASTIndexExpression::IndexKind::AIE_SquareBrackets);
        }
        | exp '[' RANGE ']'                 {
            yynerrs++;
            yyerror(&@3, yyscanner, errorLog, parseTree, "illegal range without start or end expressions; at least start or end must be given for a range");
            ASTRangeExpression::Ptr re(new ASTRangeExpression(nullptr, nullptr));
            re->loc = @3;
            $$ = new ASTIndexExpression($1, re.release(), ASTIndexExpression::IndexKind::AIE_SquareBrackets);
        }
        | exp '[' exp ']'                   {
            $3->loc.set(@2, @4);
            $$ = new ASTIndexExpression($1, $3, ASTIndexExpression::IndexKind::AIE_SquareBrackets);
        }
        | exp '{' exp '}'                   {
            $3->loc.set(@2, @4);
            $$ = new ASTIndexExpression($1, $3, ASTIndexExpression::IndexKind::AIE_CurlyBrackets);
        }
        | exp RANGE exp                     { $$ = new ASTRangeExpression($1, $3); }
        | exp '.' exp                       { $$ = new ASTAccessExpression($1, $3); }
        | exp DOT_KW_IDENTIFIER             {
            ASTName name($2, ASTNameKind::ANK_KWIdentifier);
            name.loc = @2;
            delete $2;
            $$ = new ASTAccessExpression($1, new ASTNameExpression(std::move(name)));
        }
        ;

exp_n:
        exp_c
        | list_n                            { $$ = $1; }
        | exp_n PLUS_EQUALS exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_PlusEquals, $3); }
        | exp_n MINUS_EQUALS exp            { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_MinusEquals, $3); }
        | exp_n AND_EQUALS exp              { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_AndEquals, $3); }
        | exp_n OR_EQUALS exp               { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_OrEquals, $3); }
        | exp_n MODULO_EQUALS exp           { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_ModuloEquals, $3); }
        | exp_n MULTIPLY_EQUALS exp         { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_MultiplyEquals, $3); }
        | exp_n DIVIDE_EQUALS exp           { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_DivideEquals, $3); }
        | exp_n XOR_EQUALS exp              { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_XorEquals, $3); }
        | exp_n SHIFT_LEFT_EQUALS exp       { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_ShiftLeftEquals, $3); }
        | exp_n SHIFT_RIGHT_EQUALS exp      { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_ShiftRightEquals, $3); }
        | exp_n WS_PLUS_EQUALS exp          { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_PLUS_EQUALS, $3); }
        | exp_n WS_MINUS_EQUALS exp         { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_MINUS_EQUALS, $3); }
        | exp_n WS_AND_EQUALS exp           { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_AND_EQUALS, $3); }
        | exp_n WS_OR_EQUALS exp            { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_OR_EQUALS, $3); }
        | exp_n WS_MODULO_EQUALS exp        { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_MODULO_EQUALS, $3); }
        | exp_n WS_MULTIPLY_EQUALS exp      { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_MULTIPLY_EQUALS, $3); }
        | exp_n WS_DIVIDE_EQUALS exp        { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_DIVIDE_EQUALS, $3); }
        | exp_n WS_XOR_EQUALS exp           { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_XOR_EQUALS, $3); }
        | exp_n WS_SHIFT_LEFT_EQUALS exp    { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_SHIFT_LEFT_EQUALS, $3); }
        | exp_n WS_SHIFT_RIGHT_EQUALS exp   { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_SHIFT_RIGHT_EQUALS, $3); }
        | exp_n '=' exp                     { $$ = new ASTAssignmentExpression($1, $3); }
        | exp_n WEAK_ASSIGNMENT exp         { $$ = new ASTAssignmentExpression($1, $3, true); }
        | exp_n TOK_INSTANCEOF uncqtypedef  {
            ASTName name($3, ASTNameKind::ANK_UncQTypedef);
            name.loc = @3;
            delete $3;
            $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Instanceof, new ASTNameExpression(std::move(name)));
        }
        | exp_n NULL_COALESCING exp         { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_NullCoalesce, $3); }
        | exp_n VALUE_COALESCING exp        { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_ValueCoalesce, $3); }
        | exp_n '?' exp ':' exp             { $$ = new ASTTernaryExpression($1, $3, $5); }
        | exp_n P_INCREMENT                 { // post-increment
            $$ = new ASTUnaryExpression($1, ASTOperatorKind::AOK_PostIncrement);
            $$->loc.setLast(@2);
        }
        | exp_n P_DECREMENT                 { // post-decrement
            $$ = new ASTUnaryExpression($1, ASTOperatorKind::AOK_PostDecrement);
            $$->loc.setLast(@2);
        }
        | exp_n '(' myexp ')'               {
            $$ = new ASTCallExpression($1, $3);
        }
        | exp_n LOGICAL_AND exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_LogicalAnd, $3); }
        | exp_n LOGICAL_OR exp              { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_LogicalOr, $3); }
        | exp_n BROKEN_LOGICAL_OR exp       { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_BrokenLogicalOr, $3); }
        | exp_n BROKEN_BINARY_OR exp        { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_BrokenBinaryOr, $3); }
        | exp_n BROKEN_BINARY_XOR exp       { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_BrokenBinaryXor, $3); }
        | exp_n '|' exp                     { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_BinaryOr, $3); }
        | exp_n '&' exp                     { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_BinaryAnd, $3); }
        | exp_n '^' exp                     { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_BinaryXor, $3); }
        | exp_n REGEX_MATCH REGEX           { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_RegexMatch, $3); }
        | exp_n REGEX_NMATCH REGEX          { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_RegexNotMatch, $3); }
        | exp_n REGEX_MATCH REGEX_SUBST     { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_RegexMatch, $3); }
        | exp_n REGEX_MATCH REGEX_TRANS     { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_RegexMatch, $3); }
        | exp_n REGEX_MATCH REGEX_EXTRACT   { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_RegexMatch, $3); }
        | exp_n '>' exp                     { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_GreaterThan, $3); }
        | exp_n '<' exp                     { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_LessThan, $3); }
        | exp_n LOGICAL_CMP exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Comparison, $3); }
        | exp_n LOGICAL_EQ exp              { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Equals, $3); }
        | exp_n ABSOLUTE_EQ exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_AbsoluteEquals, $3); }
        | exp_n ABSOLUTE_NE exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_AbsoluteNotEquals, $3); }
        | exp_n LOGICAL_NE exp              { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_NotEquals, $3); }
        | exp_n LOGICAL_LE exp              { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_LessThanOrEquals, $3); }
        | exp_n LOGICAL_GE exp              { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_GreaterThanOrEquals, $3); }
        | exp_n SHIFT_LEFT exp              { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_ShiftLeft, $3); }
        | exp_n SHIFT_RIGHT exp             { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_ShiftRight, $3); }
        | exp_n WS_LOGICAL_AND exp          { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_LOGICAL_AND, $3); }
        | exp_n WS_LOGICAL_OR exp           { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_LOGICAL_OR, $3); }
        | exp_n WS_BROKEN_LOGICAL_OR exp    { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_BROKEN_LOGICAL_OR, $3); }
        | exp_n WS_LOGICAL_CMP exp          { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_LOGICAL_CMP, $3); }
        | exp_n WS_LOGICAL_EQ exp           { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_LOGICAL_EQ, $3); }
        | exp_n WS_ABSOLUTE_EQ exp          { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_ABSOLUTE_EQ, $3); }
        | exp_n WS_ABSOLUTE_NE exp          { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_ABSOLUTE_NE, $3); }
        | exp_n WS_LOGICAL_NE exp           { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_LOGICAL_NE, $3); }
        | exp_n WS_LOGICAL_LE exp           { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_LOGICAL_LE, $3); }
        | exp_n WS_LOGICAL_GE exp           { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_LOGICAL_GE, $3); }
        | exp_n WS_SHIFT_LEFT exp           { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_SHIFT_LEFT, $3); }
        | exp_n WS_SHIFT_RIGHT exp          { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_WS_SHIFT_RIGHT, $3); }
        | exp_n '+' exp                     { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Plus, $3); }
        | exp_n '-' exp                     { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Minus, $3); }
        | exp_n '%' exp                     { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Modulo, $3); }
        | exp_n '/' exp                     { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Divide, $3); }
        | exp_n '*' exp                     { $$ = new ASTBinaryExpression($1, ASTOperatorKind::AOK_Multiply, $3); }
        | exp_n '[' exp RANGE ']'           {
            ASTRangeExpression::Ptr re(new ASTRangeExpression($3, nullptr));
            re->loc.setLast(@4);
            $$ = new ASTIndexExpression($1, re.release(), ASTIndexExpression::IndexKind::AIE_SquareBrackets);
        }
        | exp_n '[' RANGE exp ']'           {
            ASTRangeExpression::Ptr re(new ASTRangeExpression(nullptr, $4));
            re->loc.setFirst(@3);
            $$ = new ASTIndexExpression($1, re.release(), ASTIndexExpression::IndexKind::AIE_SquareBrackets);
        }
        | exp_n '[' RANGE ']'               {
            yynerrs++;
            yyerror(&@3, yyscanner, errorLog, parseTree, "illegal range without start or end expressions; at least start or end must be given for a range");
            ASTRangeExpression::Ptr re(new ASTRangeExpression(nullptr, nullptr));
            re->loc = @3;
            $$ = new ASTIndexExpression($1, re.release(), ASTIndexExpression::IndexKind::AIE_SquareBrackets);
        }
        | exp_n '[' exp ']'                 {
            $3->loc.set(@2, @4);
            $$ = new ASTIndexExpression($1, $3, ASTIndexExpression::IndexKind::AIE_SquareBrackets);
        }
        | exp_n '{' exp '}'                 {
            $3->loc.set(@2, @4);
            $$ = new ASTIndexExpression($1, $3, ASTIndexExpression::IndexKind::AIE_CurlyBrackets);
        }
        | exp_n RANGE exp                   { $$ = new ASTRangeExpression($1, $3); }
        | exp_n '.' exp                     { $$ = new ASTAccessExpression($1, $3); }
        | exp_n DOT_KW_IDENTIFIER           {
            ASTName name($2, ASTNameKind::ANK_KWIdentifier);
            name.loc = @2;
            delete $2;
            $$ = new ASTAccessExpression($1, new ASTNameExpression(std::move(name)));
        }
        ;

string:
        QUOTED_WORD {
            $$ = $1;
        }
        | QUOTED_WORD string {
            $$ = $1;
            $$->append(*($2));
            delete $2;
        }
        ;

scalar:
        QFLOAT        { $$ = new ASTLiteralExpression(ALEKind::ALEK_Float, $1); $$->loc = @1; }
        | INTEGER     { $$ = new ASTLiteralExpression(ALEKind::ALEK_Int, $1); $$->loc = @1; }
        | string      { $$ = new ASTLiteralExpression(ALEKind::ALEK_String, $1); $$->loc = @1; }
        | DATETIME    { $$ = new ASTLiteralExpression(ALEKind::ALEK_Date, $1); $$->loc = @1; }
        | NUMBER      { $$ = new ASTLiteralExpression(ALEKind::ALEK_Number, $1); $$->loc = @1; }
        | TOK_NOTHING { $$ = new ASTNothing; $$->loc = @1; }
        | TOK_NULL    { $$ = new ASTNull; $$->loc = @1; }
        | TOK_TRUE    { $$ = new ASTLiteralExpression(true); $$->loc = @1; }
        | TOK_FALSE   { $$ = new ASTLiteralExpression(false); $$->loc = @1; }
        | ELLIPSES    { $$ = new ASTLiteralExpression(ALEKind::ALEK_Ellipses); $$->loc = @1; }
        ;

%%
