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

    parser.ypp

    Qore Programming Language

    Copyright (C) 2003 - 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.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include "qore/Qore.h"
#include "qore/intern/BreakStatement.h"
#include "qore/intern/ContinueStatement.h"
#include "qore/intern/ReturnStatement.h"
#include "qore/intern/RethrowStatement.h"
#include "qore/intern/ThreadExitStatement.h"
#include "qore/intern/ExpressionStatement.h"
#include "qore/intern/DoWhileStatement.h"
#include "qore/intern/SummarizeStatement.h"
#include "qore/intern/ContextStatement.h"
#include "qore/intern/IfStatement.h"
#include "qore/intern/WhileStatement.h"
#include "qore/intern/ForStatement.h"
#include "qore/intern/ForEachStatement.h"
#include "qore/intern/TryStatement.h"
#include "qore/intern/ThrowStatement.h"
#include "qore/intern/StatementBlock.h"
#include "qore/intern/ParserSupport.h"
#include "qore/intern/SwitchStatement.h"
#include "qore/intern/CaseNodeWithOperator.h"
#include "qore/intern/CaseNodeRegex.h"
#include "qore/intern/OnBlockExitStatement.h"
#include "qore/intern/ConstantList.h"
#include "qore/intern/GlobalVariableList.h"
#include "qore/intern/QoreNamespaceIntern.h"
#include "qore/intern/QoreParseHashNode.h"
#include "qore/intern/QoreParseListNode.h"
#include "qore/intern/ModuleInfo.h"

#include "parser.hpp"

#include "qore/intern/QoreClassIntern.h"
#include "qore/intern/QoreParseClass.h"
#include "qore/intern/qore_program_private.h"
#include "qore/intern/typed_hash_decl_private.h"
#include "qore/intern/qore_list_private.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <sstream>
#include <utility>
#include <vector>

#define YYINITDEPTH 300
//#define YYDEBUG 1

#define YYLLOC_DEFAULT(Current, Rhs, N)                         \
    do {                                                        \
        if (N) {                                                \
            (Current).first_line = YYRHSLOC(Rhs, 1).first_line; \
            (Current).first_col  = YYRHSLOC(Rhs, 1).first_col;  \
            (Current).last_line  = YYRHSLOC(Rhs, N).last_line;  \
            (Current).last_col   = YYRHSLOC(Rhs, N).last_col;   \
        } else {                                                \
            (Current).first_line = (Current).last_line =        \
                YYRHSLOC(Rhs, 0).last_line;                     \
            (Current).first_col = (Current).last_col =          \
                YYRHSLOC(Rhs, 0).last_col;                      \
        }                                                       \
    } while (0)

// to fix the start location of an expression
static void fix_start_location(int val, QoreParserLocation& loc, QoreParserLocation& exp_loc) {
    if (!val) {
        loc.first_col = exp_loc.first_col;
        loc.first_line = exp_loc.first_line;
        loc.last_col = exp_loc.last_col;
        loc.last_line = exp_loc.last_line;
    }
}

static void fix_start_location(void* val, QoreParserLocation& loc, QoreParserLocation& exp_loc) {
    fix_start_location(val ? 1 : 0, loc, exp_loc);
}

class HashElement {
public:
    QoreValue key;
    QoreValue value;
    const QoreProgramLocation* loc;

    DLLLOCAL HashElement(QoreValue k, QoreValue v, int sl, int el) : key(k), value(v), loc(qore_program_private::get(*getProgram())->getLocation(sl, el)) {
        //traceout("HashElement::HashElement()");
    }

    DLLLOCAL ~HashElement() {
        key.discard(nullptr);
        value.discard(nullptr);
    }

    DLLLOCAL void addDelete(QoreParseHashNode* h) {
        h->add(key, value, loc);
        key.clear();
        value.clear();
        delete this;
    }
};

// for constant definitions
class ConstNode {
public:
    const QoreProgramLocation* loc;
    NamedScope name;
    QoreValue value;
    bool pub;

    DLLLOCAL ConstNode(const QoreProgramLocation* loc, char* n, QoreValue v, bool p = false) : loc(loc), name(n), value(v), pub(p) {
        // see if constant definitions are allowed
        if (parse_check_parse_option(PO_NO_CONSTANT_DEFS))
            parse_error(*loc, "illegal constant definition \"%s\" (conflicts with parse option PO_NO_CONSTANT_DEFS)", n);
    }
    DLLLOCAL QoreValue takeValue() {
        QoreValue rv = value;
        value.clear();
        return rv;
    }
    DLLLOCAL const char* getName() const {
        return name.getIdentifier();
    }
    DLLLOCAL bool isPublic() const {
        return pub;
    }
};

class ObjClassDef {
public:
    const QoreProgramLocation* loc;
    NamedScope* name;
    QoreClass* oc;

    DLLLOCAL ObjClassDef(const QoreProgramLocation* loc, NamedScope *n, QoreClass* o) : loc(loc), name(n), oc(o) {
        checkName();
        // override class declaration location
        qore_class_private::get(*o)->loc = loc;
    }

    DLLLOCAL ObjClassDef(const QoreProgramLocation* loc, char* n, QoreClass* o) : loc(loc), name(new NamedScope(n)), oc(o) {
        checkName();
        // override class declaration location
        qore_class_private::get(*o)->loc = loc;
    }

    DLLLOCAL ~ObjClassDef() {
        delete name;
    }

    DLLLOCAL void checkName() {
        if (!strcmp(name->getIdentifier(), "auto"))
            parse_error(*loc, "a class may not have the name 'auto'; this name has a special meaning in complex types; please choose another name for your class");
    }
};

class HashDeclDef {
public:
    const QoreProgramLocation* loc;
    NamedScope* name;
    TypedHashDecl* hashdecl;

    DLLLOCAL HashDeclDef(const QoreProgramLocation* loc, NamedScope *n, typed_hash_decl_private* hp) :
            loc(loc), name(n), hashdecl(hp->newTypedHashDecl(name->getIdentifier())) {
        checkName();
    }

    DLLLOCAL HashDeclDef(const QoreProgramLocation* loc, char* n, typed_hash_decl_private* hp) :
            loc(loc), name(new NamedScope(n)), hashdecl(hp->newTypedHashDecl(name->getIdentifier())) {
        checkName();
    }

    DLLLOCAL ~HashDeclDef() {
        delete name;
        if (hashdecl)
            typed_hash_decl_private::get(*hashdecl)->deref();
    }

    DLLLOCAL void checkName() {
        if (!strcmp(name->getIdentifier(), "auto"))
            parse_error(*loc, "a hashdecl may not have the name 'auto'; this name has a special meaning in complex types; please choose another name for your hashdecl");
    }

    DLLLOCAL TypedHashDecl* takeHashDecl() {
        TypedHashDecl* rv = hashdecl;
        hashdecl = nullptr;
        return rv;
    }
};

class ParseUserFunctionBase {
public:
    const QoreProgramLocation* loc;
    UserFunctionVariant* variant;

    DLLLOCAL ParseUserFunctionBase(const QoreProgramLocation* loc, UserFunctionVariant* v) : loc(loc), variant(v) {
    }

    DLLLOCAL ~ParseUserFunctionBase() {
        if (variant)
            variant->deref();
    }

    DLLLOCAL void setPublic() {
        variant->setModulePublic();
    }
};

class ParseUserFunction : public ParseUserFunctionBase {
public:
    char* name;

    DLLLOCAL ParseUserFunction(const QoreProgramLocation* loc, char* n_name, UserFunctionVariant* n_variant) : ParseUserFunctionBase(loc, n_variant), name(n_name) {
    }

    DLLLOCAL ~ParseUserFunction() {
        if (name)
            free(name);
    }

    DLLLOCAL void add(qore_ns_private& ns) {
        qore_root_ns_private::addPendingVariant(ns, name, variant);
        variant = 0;
        // make sure definition was legal
        if (parse_check_parse_option(PO_NO_SUBROUTINE_DEFS)) {
            const char* nsn = ns.name.c_str();
            parse_error(*loc, "function '%s%s%s()' cannot be defined (conflicts with parse option PO_NO_SUBROUTINE_DEFS)", nsn[0] ? nsn : "", nsn[0] ? "::" : "", name);
        }

        delete this;
    }
};

class ParseScopedUserFunction : public ParseUserFunctionBase {
public:
    NamedScope name;

    DLLLOCAL ParseScopedUserFunction(const QoreProgramLocation* loc, char* n_name, UserFunctionVariant* n_variant) : ParseUserFunctionBase(loc, n_variant), name(n_name) {
    }

    DLLLOCAL void add(qore_ns_private& ns) {
        qore_root_ns_private::addPendingVariant(ns, name, variant);
        variant = 0;
        // make sure definition was legal
        if (parse_check_parse_option(PO_NO_SUBROUTINE_DEFS))
            parse_error(*loc, "function '%s()' cannot be defined (conflicts with parse option PO_NO_SUBROUTINE_DEFS)", name.ostr);

        delete this;
    }
};

struct GVarDecl {
    const QoreProgramLocation* loc;
    bool pub;

    DLLLOCAL GVarDecl(const QoreProgramLocation* loc, bool p) : loc(loc), pub(p) {
    }

    DLLLOCAL virtual ~GVarDecl() {
    }

    DLLLOCAL virtual void add(qore_ns_private& ns) = 0;

    DLLLOCAL virtual AbstractQoreNode* makeVar() = 0;
};

struct GVarSingleDecl : public GVarDecl {
    char* name;
    const QoreTypeInfo* typeInfo;
    QoreParseTypeInfo* parseTypeInfo;
    qore_var_t type;

    DLLLOCAL GVarSingleDecl(const QoreProgramLocation* loc, char* n, const QoreTypeInfo* ti, QoreParseTypeInfo* pti,
            bool p, bool bare, qore_var_t type = VT_GLOBAL) : GVarDecl(loc, p), name(n), typeInfo(ti), parseTypeInfo(pti),
            type(type) {
        //printd(5, "GVarSingleDecl::GVarSingleDecl() '%s' %d-%d\n", n, loc.start_line, loc.end_line);
        if (bare) {
            if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "%s variable '%s' declared without '$' prefix, but parse option 'allow-bare-refs' is not set",
                    type == VT_GLOBAL ? "global" : "thread_local", n);
        } else {
            if (parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "reference to %s variable '$%s' with the '$' sign is not allowed when parse option 'allow-bare-refs' is set",
                    type == VT_GLOBAL ? "global" : "thread_local", n);
        }
        if (qore_program_private::get(*getProgram())->expression_mode) {
            parse_error(*loc, "%s variable declaration '%s' not allowed in expressions' is set",
                type == VT_GLOBAL ? "global" : "thread_local", n);
        }
    }

    DLLLOCAL virtual ~GVarSingleDecl() {
        if (name)
            free(name);

        delete parseTypeInfo;
    }

    DLLLOCAL char* takeName() {
        char* rv = name;
        name = 0;
        return rv;
    }

    DLLLOCAL QoreParseTypeInfo* takeParseTypeInfo() {
        QoreParseTypeInfo* rv = parseTypeInfo;
        parseTypeInfo = nullptr;
        return rv;
    }

    DLLLOCAL virtual void add(qore_ns_private& ns) {
        ns.parseAddGlobalVarDecl(loc, takeName(), typeInfo, takeParseTypeInfo(), pub, type);
        delete this;
    }

    DLLLOCAL virtual AbstractQoreNode* makeVar() {
        GlobalVarRefNode* gv = typeInfo
            ? new GlobalVarRefNode(loc, takeName(), typeInfo, type)
            : new GlobalVarRefNode(loc, takeName(), takeParseTypeInfo(), type);
        if (pub)
            gv->setPublic();

        delete this;
        return gv;
    }
};

struct GVarListDecl : public GVarDecl {
    QoreParseListNode* l;
    qore_var_t type;

    DLLLOCAL GVarListDecl(const QoreProgramLocation* loc, QoreParseListNode* list, bool p,
            qore_var_t type = VT_GLOBAL)
            : GVarDecl(loc, p), l(list), type(type) {
        bool bareok = parse_check_parse_option(PO_ALLOW_BARE_REFS);

        QoreParseListNode::nvec_t& vl = l->getValues();
        for (unsigned i = 0; i < vl.size(); ++i) {
            QoreValue n = vl[i];
            qore_type_t t = n.getType();
            if (t == NT_BAREWORD) {
                BarewordNode* b = n.get<BarewordNode>();
                if (!bareok)
                    parse_error(*loc, "variable '%s' in %s variable declaration list declared without '$' " \
                        "prefix, but parse option 'allow-bare-refs' is not set", b->str,
                        type == VT_GLOBAL ? "global" : "thread_local");
            } else if (t == NT_VARREF) {
                VarRefNode* vrn = n.get<VarRefNode>();
                if (vrn->explicitScope()) {
                    if (vrn->getType() == VT_LOCAL)
                        parse_error(*loc, "illegal use of 'my %s' in %s variable declaration list", vrn->getName(),
                            type == VT_GLOBAL ? "global" : "thread_local");
                    else if (vrn->getType() == VT_GLOBAL)
                        parse_error(*loc, "illegal use of 'our %s' in %s variable declaration list", vrn->getName(),
                            type == VT_GLOBAL ? "global" : "thread_local");
                }
            } else
                parse_error(*loc, "element %d in list following '%s' is not a variable reference (%s)", i,
                    type == VT_GLOBAL ? "our" : "thread_local", n.getTypeName());
        }
    }

    DLLLOCAL virtual ~GVarListDecl() {
        if (l) {
            l->deref(nullptr);
        }
    }

    DLLLOCAL QoreParseListNode* takeList() {
        QoreParseListNode* rv = l;
        l = nullptr;
        return rv;
    }

    DLLLOCAL virtual void add(qore_ns_private& ns) {
        QoreParseListNode::nvec_t& vl = l->getValues();
        for (unsigned i = 0; i < vl.size(); ++i) {
            QoreValue n = vl[i];
            qore_type_t t = n.getType();
            if (t == NT_BAREWORD) {
                BarewordNode* b = n.get<BarewordNode>();
                ns.parseAddGlobalVarDecl(loc, b->takeString(), 0, 0, pub, type);
            } else if (t == NT_VARREF) {
                VarRefNode* vrn = n.get<VarRefNode>();
                if (!vrn->explicitScope()) {
                    VarRefDeclNode* vrdn = dynamic_cast<VarRefDeclNode*>(vrn);
                    if (vrdn) {
                        ns.parseAddGlobalVarDecl(loc, vrn->takeName(), vrdn->getTypeInfo(),
                            vrdn->takeParseTypeInfo(), pub, type);
                    } else {
                        ns.parseAddGlobalVarDecl(loc, vrn->takeName(), 0, 0, pub, type);
                    }
                }
            }
        }

        delete this;
    }

    DLLLOCAL virtual AbstractQoreNode* makeVar() {
        l->setVariableList();
        QoreParseListNode::nvec_t& vl = l->getValues();
        for (unsigned i = 0; i < vl.size(); ++i) {
            QoreValue& n = vl[i];
            qore_type_t t = n.getType();
            VarRefNode* vrn = nullptr;
            if (t == NT_BAREWORD) {
                BarewordNode* b = n.get<BarewordNode>();
                GlobalVarRefNode* v = new GlobalVarRefNode(loc, b->takeString(), (const QoreTypeInfo*)nullptr, type);
                b->deref();
                n = vrn = v;
            } else if (t == NT_VARREF) {
                vrn = n.get<VarRefNode>();
                if (!vrn->explicitScope()) {
                    vrn->makeGlobal(type);
                    vrn->setExplicitScope();
                    //printd(5, "GVarListDecl::makeVar() '%s' t: %d set global with explicit scope\n",
                    //  vrn ? vrn->getName() : "n/a", type);
                }
            }

            if (vrn && pub)
                vrn->setPublic();
        }

        QoreParseListNode* rv = takeList();
        delete this;
        return rv;
    }
};

static int check_method(const QoreProgramLocation* loc, const char* name, BCAList* bcal,
        RetTypeInfo* returnTypeInfo) {
    bool con = !strcmp(name, "constructor");
    bool dst = !con && !strcmp(name, "destructor");

    // see if a return type is given for special methods
    if (returnTypeInfo && (con || dst)) {
        parse_error(*loc, "%s methods may not declare a return type", name);
        return -1;
    }

    if (bcal && !con) {
        parse_error(*loc, "base class constructor lists are only legal when defining constructor() methods");
        return -1;
    }

    return 0;
}

#define OFM_PUBLIC     (1 << 0)
#define OFM_PRIVATE    (1 << 1)
#define OFM_INTERNAL   (1 << 2)
#define OFM_SYNCED     (1 << 3)
#define OFM_STATIC     (1 << 4)
#define OFM_DEPRECATED (1 << 5)
#define OFM_FINAL      (1 << 6)
#define OFM_ABSTRACT   (1 << 7)

static inline MethodVariantBase* new_method_variant(const QoreProgramLocation* loc, const char* name, int mod, int n_sig_first_line, int n_sig_last_line, QoreValue params, BCAList* bcal, RetTypeInfo* returnTypeInfo, StatementBlock *b) {
    //printd(5, "new_method_variant() '%s' b: %p\n", name, b);

    int64 flags = QCF_NO_FLAGS;

    if (get_pop_argv_ref())
        flags |= QCF_USES_EXTRA_ARGS;

    ClassAccess access = Public;
    if (mod & OFM_PRIVATE) {
        if (mod & OFM_INTERNAL)
            parse_error(*loc, "%s() methods cannot be both private:hierarchy and private:internal", name);
        if (mod & OFM_PUBLIC)
            parse_error(*loc, "%s() methods cannot be both public and private", name);
        access = Private;
    } else if (mod & OFM_INTERNAL) {
        if (mod & OFM_PUBLIC)
            parse_error(*loc, "%s() methods cannot be both public and private", name);
        access = Internal;
    }

    if (mod & OFM_DEPRECATED)
        flags |= QCF_DEPRECATED;

    if (!strcmp(name, "constructor")) {
        if (mod & OFM_SYNCED)
            parse_error(*loc, "%s() methods cannot be synchronized", name);
        if (mod & OFM_FINAL)
            parse_error(*loc, "%s() methods cannot be declared final; declare the class final instead", name);
        if (mod & OFM_ABSTRACT)
            parse_error(*loc, "%s() methods cannot be declared abstract", name);
        if (returnTypeInfo) {
            parse_error(*loc, "return type information cannot be defined for %s() methods", name);
            delete returnTypeInfo;
        }
        return new UserConstructorVariant(access, b, n_sig_first_line, n_sig_last_line, params, bcal, flags);
    }
    if (bcal) {
        parse_error(*loc, "only constructors may have base class constructor arguments");
        delete bcal;
        bcal = 0;
    }
    if (!strcmp(name, "destructor")) {
        if (params) {
            parse_error(*loc, "parameters cannot be defined for destructors");
            params.discard(nullptr);
        }
        if (mod & OFM_SYNCED)
            parse_error(*loc, "%s() methods cannot be synchronized", name);
        if (mod & OFM_FINAL)
            parse_error(*loc, "%s() methods cannot be declared final; declare the class final instead", name);
        if (mod & OFM_ABSTRACT)
            parse_error(*loc, "%s() methods cannot be declared abstract", name);
        if (returnTypeInfo) {
            parse_error(*loc, "return type information cannot be defined for %s() methods", name);
            delete returnTypeInfo;
        }
        if (access > Public)
            parse_error(*loc, "destructors cannot be private");
        return new UserDestructorVariant(b, n_sig_first_line, n_sig_last_line);
    }
    if (!strcmp(name, "copy")) {
        if (mod & OFM_FINAL) {
            parse_error(*loc, "%s() methods cannot be declared final; declare the class final instead", name);
        }
        if (mod & OFM_ABSTRACT) {
            parse_error(*loc, "%s() methods cannot be declared abstract", name);
        }
        return new UserCopyVariant(access, b, n_sig_first_line, n_sig_last_line, params, returnTypeInfo, mod & OFM_SYNCED);
    }

    if (!strcmp(name, "methodGate") || !strcmp(name, "memberGate") || !strcmp(name, "memberNotification")) {
        if (mod & OFM_FINAL) {
            parse_error(*loc, "%s() methods cannot be declared final; they are not inherited anyway", name);
        }
    }

    if (mod & OFM_FINAL && mod & OFM_ABSTRACT)
        parse_error(*loc, "method '%s()' is declared both final and abstract", name);

    return new UserMethodVariant(access, mod & OFM_FINAL, b, n_sig_first_line, n_sig_last_line, params, returnTypeInfo, mod & OFM_SYNCED, flags, mod & OFM_ABSTRACT);
}

struct MethodDef {
    const QoreProgramLocation* loc;
    MethodVariantBase* m;
    NamedScope name;
    bool static_flag;

    DLLLOCAL MethodDef(const QoreProgramLocation* loc, int mod, char* n, int n_sig_first_line, int n_sig_last_line, QoreValue params, BCAList* bcal, RetTypeInfo* returnTypeInfo, StatementBlock *b) : loc(loc), m(0), name(n), static_flag(mod & OFM_STATIC) {
        if (check_method(loc, name.getIdentifier(), bcal, returnTypeInfo)) {
            params.discard(nullptr);
            delete bcal;
            delete b;
            delete returnTypeInfo;
            return;
        }

        m = new_method_variant(loc, name.getIdentifier(), mod, n_sig_first_line, n_sig_last_line, params, bcal, returnTypeInfo, b);
    }

    DLLLOCAL ~MethodDef() {
        delete m;
    }

    DLLLOCAL void parseAddToClassAndDel() {
        if (m) {
            // if there are any errors, the function below will delete memory
            // the following method takes ownership of "m" unconditionally
            qore_root_ns_private::parseAddMethodToClass(loc, name, m, static_flag);
            m = 0;
        }
        delete this;
    }

    DLLLOCAL void add(qore_ns_private& ns) {
        // the following method takes ownership of "m" unconditionally
        ns.parseAddMethodToClass(loc, name, m, static_flag);
        m = 0;
    }
};

static QoreValue parse_hash_map(const QoreProgramLocation* loc, QoreParseHashNode* exp, QoreParseListNode* l) {
    assert(l && (l->size() == 1 || l->size() == 2));
    ReferenceHolder<> exph(exp, nullptr);
    ReferenceHolder<> lh(l, nullptr);

    // check hash expression
    size_t len = exp->size();

    if (len == 0) {
        qore_program_private::makeParseWarning(getProgram(), *loc, QP_WARN_INVALID_OPERATION, "INVALID-OPERATION",
                "empty hash as an argument to the hash map operator, this expression will always evaluate to an empty hash");
        return new QoreHashNode(autoTypeInfo);
    }

    if (len != 1) {
        parse_error(*loc, "invalid initial argument to hash map operator, expected a single key value pair; got %d hash elements instead", len);
        return QoreValue();
    }

    if (l->size() == 1) {
        QoreValue iter = l->shift();
        return new QoreHashMapOperatorNode(loc, exp->takeFirstKey(), exp->takeFirstValue(), iter);
    }

    QoreValue iter = l->shift();
    QoreValue select = l->shift();
    return new QoreHashMapSelectOperatorNode(loc, exp->takeFirstKey(), exp->takeFirstValue(), iter, select);
}

static AbstractQoreNode* parse_map(const QoreProgramLocation* loc, QoreValue exp, QoreParseListNode* l) {
    assert(l && (l->size() == 1 || l->size() == 2));
    ReferenceHolder<> lh(l, 0);

    if (l->size() == 1) {
        QoreValue arg = l->shift();
        return new QoreMapOperatorNode(loc, exp, arg);
    }

    QoreValue iterator_exp = l->shift();
    QoreValue select_exp = l->shift();
    return new QoreMapSelectOperatorNode(loc, exp, iterator_exp, select_exp);
}

#define NSN_NONE     0
#define NSN_OCD      1
#define NSN_HASHDECL 2
#define NSN_CONST    3
#define NSN_NS       4
#define NSN_FUNC     5
#define NSN_SFUNC    6
#define NSN_VAR      7
#define NSN_METH     8

struct NSNode {
    int type;
    union {
        ObjClassDef* ocd;
        HashDeclDef* hashdecl;
        ConstNode* cn;
        QoreNamespace* ns;
        ParseUserFunction* func;
        ParseScopedUserFunction* sfunc;
        GVarDecl* gv;
        MethodDef* meth;
    } n;

    DLLLOCAL NSNode(ObjClassDef* o) { type = NSN_OCD; n.ocd = o; }
    DLLLOCAL NSNode(HashDeclDef* h) { type = NSN_HASHDECL; n.hashdecl = h; }
    DLLLOCAL NSNode(ConstNode* c) { type = NSN_CONST; n.cn = c; }
    DLLLOCAL NSNode(QoreNamespace* s) { type = NSN_NS; n.ns = s; }
    DLLLOCAL NSNode(ParseUserFunction* f) : type(NSN_FUNC) {
        n.func = f;
    }
    DLLLOCAL NSNode(ParseScopedUserFunction* f) : type(NSN_SFUNC) {
        n.sfunc = f;
    }

    DLLLOCAL NSNode(GVarDecl* gv) : type(NSN_VAR) {
        n.gv = gv;
    }

    DLLLOCAL NSNode(MethodDef* m) : type(NSN_METH) {
        n.meth = m;
    }

    DLLLOCAL void deref() {
        switch (type) {
            case NSN_OCD:
                delete n.ocd;
                break;
            case NSN_HASHDECL:
                delete n.hashdecl;
                break;
            case NSN_CONST:
                delete n.cn;
                break;
            case NSN_NS:
                delete n.ns;
                break;
            case NSN_FUNC:
                delete n.func;
                break;
            case NSN_SFUNC:
                delete n.sfunc;
                break;
            case NSN_VAR:
                delete n.gv;
                break;
            case NSN_METH:
                delete n.meth;
                break;
            default:
                assert(false);
                // no break
        }
        delete this;
    }

    DLLLOCAL void add(qore_ns_private& ns) {
        switch (type) {
            case NSN_OCD:
                //printd(5, "ocd: %s %d-%d c: %d-%d\n", n.ocd->oc->getName(), n.ocd->loc.start_line, n.ocd->loc.end_line, qore_class_private::get(*n.ocd->oc)->loc.start_line, qore_class_private::get(*n.ocd->oc)->loc.end_line);
                ns.parseAddPendingClass(n.ocd->loc, *(n.ocd->name), n.ocd->oc);
                delete n.ocd;
                break;
            case NSN_HASHDECL:
                ns.parseAddPendingHashDecl(n.hashdecl->loc, *(n.hashdecl->name), n.hashdecl->takeHashDecl());
                delete n.hashdecl;
                break;
            case NSN_CONST:
                ns.parseAddConstant(n.cn->loc, n.cn->name, n.cn->value, n.cn->pub);
                delete n.cn;
                break;
            case NSN_NS:
                ns.parseAddNamespace(n.ns);
                break;
            case NSN_FUNC:
                n.func->add(ns);
                break;
            case NSN_SFUNC:
                n.sfunc->add(ns);
                break;
            case NSN_VAR:
                n.gv->add(ns);
                break;
            case NSN_METH:
                n.meth->add(ns);
                delete n.meth;
                break;
            default:
                assert(false);
                // no break
        }
        delete this;
    }
};

typedef std::vector<NSNode*>nsnode_list_t;

struct NSNodeList : public nsnode_list_t {
    DLLLOCAL void deref() {
        for (nsnode_list_t::iterator i = begin(), e = end(); i != e; ++i)
            (*i)->deref();

        delete this;
    }

    DLLLOCAL void add(qore_ns_private& ns) {
        for (nsnode_list_t::iterator i = begin(), e = end(); i != e; ++i)
            (*i)->add(ns);
    }
};

static QoreParseListNode* splice_expressions(QoreValue a1, QoreValue a2, const QoreProgramLocation* loc1,
        const QoreProgramLocation* loc2) {
    //tracein("splice_expressions()");
    if (a1.getType() == NT_PARSE_LIST) {
        QoreParseListNode* l = a1.get<QoreParseListNode>();
        if (!l->isFinalized()) {
            //printd(5, "LIST x\n");
            l->add(a2, loc2);
            return l;
        }
    }
    QoreParseListNode* l = new QoreParseListNode(qore_program_private::get(*getProgram())->getLocation(*loc1,
        loc2->start_line, loc2->end_line));
    l->add(a1, loc1);
    l->add(a2, loc2);
    return l;
}

typedef std::pair<char*, QoreMemberInfo*> member_pair_t;
typedef std::pair<char*, QoreVarInfo*> var_pair_t;
typedef std::pair<char*, HashDeclMemberInfo*> hashmem_pair_t;

#define MI_Member 0
#define MI_Constant 1
#define MI_Var 2
#define MI_HashMem 3

struct MemberInfo {
protected:
    DLLLOCAL MemberInfo(const QoreProgramLocation* loc) : loc(loc) {
    }

public:
    const QoreProgramLocation* loc;
    char* name;
    unsigned char type;
    union {
        QoreMemberInfo* memberInfo;
        QoreVarInfo* varInfo;
        HashDeclMemberInfo* hashMemberInfo;
        QoreSimpleValue exp;
    } u;

    DLLLOCAL MemberInfo(char* n, QoreMemberInfo* mi) : loc(mi->loc), name(n), type(MI_Member) {
        u.memberInfo = mi;
        if (!(mi && mi->parseHasTypeInfo()) && parse_check_parse_option(PO_REQUIRE_TYPES))
            parse_error(*mi->loc, "member '%s' declared without type information, but parse options require type information for all declarations", name);
    }

    DLLLOCAL MemberInfo(const QoreProgramLocation* loc, char* n, QoreValue exp) : loc(loc), name(n), type(MI_Constant) {
        u.exp = exp;
    }

    DLLLOCAL MemberInfo(char* n, HashDeclMemberInfo* mi) : loc(mi->loc), name(n), type(MI_HashMem) {
        u.hashMemberInfo = mi;
    }

    DLLLOCAL ~MemberInfo() {
        if (name)
            free(name);
        switch (type) {
            case MI_Member:
                delete u.memberInfo;
                break;
            case MI_Var:
                delete u.varInfo;
                break;
            case MI_HashMem:
                delete u.hashMemberInfo;
                break;
            case MI_Constant:
                u.exp.discard(nullptr);
                break;
        }
    }

    DLLLOCAL member_pair_t getPair() {
        assert(type == MI_Member);
        member_pair_t m = std::make_pair(name, u.memberInfo);
        name = nullptr;
        u.memberInfo = nullptr;
        return m;
    }

    DLLLOCAL var_pair_t getVarPair() {
        assert(type == MI_Var);
        var_pair_t m = std::make_pair(name, u.varInfo);
        name = nullptr;
        u.varInfo = nullptr;
        return m;
    }

    DLLLOCAL hashmem_pair_t getHashMemPair() {
        assert(type == MI_HashMem);
        hashmem_pair_t m = std::make_pair(name, u.hashMemberInfo);
        name = nullptr;
        u.hashMemberInfo = nullptr;
        return m;
    }

    DLLLOCAL QoreValue takeExp() {
        assert(type == MI_Constant);
        QoreValue rv = u.exp;
        u.exp.clear();
        return rv;
    }

    DLLLOCAL void setTransient() {
        assert(type == MI_Member);
        u.memberInfo->setTransient();
    }
};

struct ClassVarInfo : public MemberInfo {
    DLLLOCAL ClassVarInfo(char* n, QoreVarInfo* vi) : MemberInfo(vi->loc) {
        name = n;
        type = MI_Var;
        u.varInfo = vi;
        if (!(vi && vi->parseHasTypeInfo()) && parse_check_parse_option(PO_REQUIRE_TYPES))
            parse_error(*vi->loc, "class static variable '%s' declared without type information, but parse options require type information for all declarations", name);
    }

    DLLLOCAL char* takeName() {
        char* n = name;
        name = nullptr;
        return n;
    }
};

class MemberList {
protected:
    // for new class members
    QoreMemberMap mmap;
    // for new class constants
    ConstantList cmap;
    // for new class static variables
    QoreVarMap vmap;

    DLLLOCAL MemberList() : cmap((qore_class_private*)0) {
    }

public:
    DLLLOCAL MemberList(MemberInfo* member) : cmap((qore_class_private*)0) {
        if (!member)
            return;

        if (member->type == MI_Member)
            mmap.addNoCheck(member->getPair());
        else if (member->type == MI_Constant)
            cmap.parseAdd(member->loc, member->name, member->takeExp());
        else
            vmap.addNoCheck(member->getVarPair());

        delete member;
    }

    DLLLOCAL ~MemberList() {
    }

    // takes over ownership of name and typeInfo
    DLLLOCAL int add(MemberInfo* member) {
        if (!member)
            return -1;

        if (member->type == MI_Member) {
            if (mmap.inList(member->name)) {
                parse_error(*member->loc, "duplicate member declaration '%s'", member->name);
                delete member;
                return -1;
            }

            // add new member to list
            mmap.addNoCheck(member->getPair());
            delete member;
        } else if (member->type == MI_Constant) {
            if (cmap.inList(member->name)) {
                parse_error(*member->loc, "duplicate constant declaration '%s'", member->name);
                delete member;
                return -1;
            }
            if (vmap.find(member->name)) {
                parse_error(*member->loc, "constant declaration collides with static class variable declaration '%s'", member->name);
                delete member;
                return -1;
            }

            // add new constant to list
            cmap.parseAdd(member->loc, member->name, member->takeExp());
            delete member;
        } else {
            assert(member->type == MI_Var);
            if (vmap.find(member->name)) {
                parse_error(*member->loc, "duplicate static class variable declaration '%s'", member->name);
                delete member;
                return -1;
            }
            if (cmap.inList(member->name)) {
                parse_error(*member->loc, "static class variable declaration collides with constant declaration '%s'", member->name);
                delete member;
                return -1;
            }

            // add new member to list
            vmap.addNoCheck(member->getVarPair());
            delete member;
        }

        return 0;
    }

    DLLLOCAL void merge(QoreClass* qc, ClassAccess access) {
        mmap.moveAllTo(qc, access);
        vmap.moveAllTo(qc, access);

        // merge constants
        qore_class_private::get(*qc)->parseAssimilateConstants(cmap, access);
    }
};

class EmptyMemberList : public MemberList {
public:
   DLLLOCAL EmptyMemberList() {
   }
};

struct ParserTypeStruct {
protected:
    DLLLOCAL ParserTypeStruct() {
    }

public:
    const QoreTypeInfo* typeInfo = nullptr;
    QoreParseTypeInfo* parseTypeInfo = nullptr;

    DLLLOCAL ParserTypeStruct(QoreParseTypeInfo* n_parseTypeInfo) : parseTypeInfo(n_parseTypeInfo) {
    }

    DLLLOCAL ParserTypeStruct(char* id, bool or_nothing) : typeInfo(or_nothing ? getBuiltinUserOrNothingTypeInfo(id) : getBuiltinUserTypeInfo(id)) {
        //printd(0, "ParserTypeStruct::ParserTypeStruct('%s', %d) t: %p %s\n", id, or_nothing, typeInfo, QoreTypeInfo::getName(typeInfo));
        if (typeInfo) {
            free(id);
            return;
        }

        parseTypeInfo = new QoreParseTypeInfo(id, or_nothing);
    }

    DLLLOCAL ~ParserTypeStruct() {
        delete parseTypeInfo;
    }

    DLLLOCAL const QoreTypeInfo* getTypeInfo() const {
        return typeInfo;
    }

    // static version of method, checking for null pointer
    DLLLOCAL static const QoreTypeInfo* getTypeInfo(ParserTypeStruct* pts) {
        return pts ? pts->getTypeInfo() : nullptr;
    }

    DLLLOCAL QoreParseTypeInfo* getParseTypeInfo() {
        QoreParseTypeInfo* rv = parseTypeInfo;
        parseTypeInfo = nullptr;
        return rv;
    }

    // static version of method, checking for null pointer
    DLLLOCAL static QoreParseTypeInfo* getParseTypeInfo(ParserTypeStruct* pts) {
        return pts ? pts->getParseTypeInfo() : nullptr;
    }

    DLLLOCAL const char* getClassName() {
        assert(parseTypeInfo);
        return parseTypeInfo->cscope->ostr;
    }

    DLLLOCAL static ParserTypeStruct* getType(const QoreProgramLocation* loc, char* id, bool or_nothing) {
        return new ParserTypeStruct(getParseType(loc, id, or_nothing));
    }

    DLLLOCAL static void getSubTypes(const QoreProgramLocation* loc, const char* str, parse_type_vec_t& subtypes, bool raise_error = true) {
        QoreString buf;
        bool or_nothing = false;
        bool comma = false;

        const char* p = str;
        while (true) {
            if (*p == '\0') {
                if (buf.empty()) {
                    if (comma && raise_error)
                        parse_error(*loc, "empty subtype specification in type");
                }
                else
                    subtypes.push_back(getParseType(loc, buf.giveBuffer(), or_nothing));
                break;
            }
            switch (*p) {
                case '<': {
                    if (buf.empty()) {
                        if (raise_error)
                            parse_error(*loc, "invalid subtype specification in '%s'", str);
                        return;
                    }
                    const char* e = strrchr(str, '>');
                    if (!e || e < p) {
                        if (raise_error)
                            parse_error(*loc, "unbalanced angle brackets in subtype specification in '%s'", str);
                        return;
                    }
                    buf.concat(p, e - p + 1);
                    //printd(5, "ST: '%s' (str: '%s') p: %p '%s'\n", buf.c_str(), str, p, p);
                    subtypes.push_back(getParseType(loc, buf.giveBuffer(), or_nothing));
                    buf.reset();
                    or_nothing = false;
                    //printd(5, "ST: p: %p -> %p\n", p, e);
                    p = e;
                    break;
                }
                case ',':
                    buf.trim();
                    if (buf.empty()) {
                        if (raise_error)
                            parse_error(*loc, "invalid subtype specification in '%s'", str);
                        return;
                    }
                    subtypes.push_back(getParseType(loc, buf.giveBuffer(), or_nothing, raise_error));
                    buf.reset();
                    or_nothing = false;
                    comma = true;
                break;
                case '*': {
                    if (buf.empty())
                        or_nothing = true;
                    else
                        buf.concat('*');
                    if (comma)
                        comma = false;
                    break;
                }
                case ' ':
                case '\t':
                    if (!buf.empty())
                        buf.concat(*p);
                    break;
                default:
                    buf.concat(*p);
                    if (comma)
                        comma = false;
                break;
            }
            ++p;
        }
    }

    DLLLOCAL static QoreParseTypeInfo* getParseType(const QoreProgramLocation* loc, char* id, bool or_nothing, bool raise_error = true) {
        //printd(0, "gPT() '%s'\n", id);
        char* p0 = strchr(id, '<');
        if (p0) {
            char* p1 = strrchr(id, '>');
            if (p1 > (p0 + 1)) {
                // terminate main type string
                *p0 = '\0';
                // terminate type args
                *p1 = '\0';

                parse_type_vec_t subtypes;
                getSubTypes(loc, p0 + 1, subtypes, raise_error);
                return new QoreParseTypeInfo(id, or_nothing, std::move(subtypes));
            }
        }
        return new QoreParseTypeInfo(id, or_nothing);
    }

    DLLLOCAL static const QoreTypeInfo* getRuntimeType(const char* str) {
        assert(str);
        assert(str[0]);
        char* p;
        bool or_nothing;
        if (str[0] == '*') {
            or_nothing = true;
            p = strdup(str + 1);
        } else {
            or_nothing = false;
            p = strdup(str);
        }
        std::unique_ptr<QoreParseTypeInfo> pt(ParserTypeStruct::getParseType(&loc_builtin, p, or_nothing, false));
        return QoreParseTypeInfo::resolveRuntime(pt.get());
    }
};

const QoreTypeInfo* qore_get_type_from_string_intern(const char* str) {
    // issue #3318: does not use ProgramRuntimeParseAccessHelper as it is normally not needed, if this is
    // necessary, then the call to this function must be wrapped using this class
    return ParserTypeStruct::getRuntimeType(str);
}

static AbstractQoreNode* process_dot(int sline, int eline, QoreValue l, QoreValue r) {
    qore_type_t rtype = r.getType();
    const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(sline, eline);
    switch (rtype) {
        case NT_BAREWORD: {
            BarewordNode* b = r.get<BarewordNode>();
            if (!b->isFinalized()) {
                AbstractQoreNode* rv = new QoreHashObjectDereferenceOperatorNode(loc, l, b->makeQoreStringNode());
                b->deref();
                return rv;
            }
            break;
        }

        case NT_FUNCTION_CALL: {
            FunctionCallNode* f = r.get<FunctionCallNode>();
            assert(!f->getFunction());
            if (!f->isFinalized()) {
                MethodCallNode* m = new MethodCallNode(loc, f->takeName(), f->takeParseArgs());
                f->deref();

                return new QoreDotEvalOperatorNode(loc, l, m);
            }
            break;
        }
    }

    return new QoreHashObjectDereferenceOperatorNode(loc, l, r);
}

static int check_strong_encapsulation(const QoreProgramLocation* loc, const char* type, const char* name) {
    if (parse_check_parse_option(PO_STRONG_ENCAPSULATION)) {
        parse_error(*loc, "illegal out of line %s definition \"%s\" (conflicts with parse option PO_STRONG_ENCAPSULATION)", type, name);
        return -1;
    }
    return 0;
}

static int check_case(const QoreProgramLocation* loc, const char* op, QoreValue exp) {
    // ignore if NULL (= NOTHING)
    if (exp.hasEffect()) {
        if (op)
            parse_error(*loc, "case expression with '%s' needs run-time evaluation", op);
        else
            parse_error(*loc, "case expression needs run-time evaluation", op);
        return -1;
    }
    return 0;
}

static void check_funcmod(const QoreProgramLocation* loc, int mod) {
    if (mod & (OFM_PRIVATE|OFM_INTERNAL))
        parse_error(*loc, "illegal 'private' modifier in function declaration");
    if (mod & OFM_STATIC)
        parse_error(*loc, "illegal 'static' modifier in function declaration");
    if (mod & OFM_FINAL)
        parse_error(*loc, "illegal 'final' modifier in function declaration");
    if (mod & OFM_ABSTRACT)
        parse_error(*loc, "illegal 'abstract' modifier in function declaration");
}

static void check_classmod(const QoreProgramLocation* loc, int mod, QoreClass& qc) {
   if (mod & (OFM_PRIVATE|OFM_INTERNAL))
      parse_error(*loc, "illegal 'private' modifier in class declaration");
   if (mod & OFM_SYNCED)
      parse_error(*loc, "illegal 'synchronized' modifier in class declaration");
   if (mod & OFM_STATIC)
      parse_error(*loc, "illegal 'static' modifier in class declaration");
   if (mod & OFM_PUBLIC)
      qore_class_private::setPublic(qc);
   if (mod & OFM_DEPRECATED)
      parse_error(*loc, "illegal 'deprecated' modifier in class declaration");
   if (mod & OFM_FINAL)
      qore_class_private::setFinal(qc);
   if (mod & OFM_ABSTRACT)
      parse_error(*loc, "illegal 'abstract' modifier in class declaration");
}

static void check_hashdeclmod(const QoreProgramLocation* loc, int mod, typed_hash_decl_private& hashdecl) {
   if (mod & (OFM_PRIVATE|OFM_INTERNAL))
      parse_error(*loc, "illegal 'private' modifier in hashdecl");
   if (mod & OFM_SYNCED)
      parse_error(*loc, "illegal 'synchronized' modifier in hashdecl");
   if (mod & OFM_STATIC)
      parse_error(*loc, "illegal 'static' modifier in hashdecl");
   if (mod & OFM_PUBLIC)
      hashdecl.setPublic();
   if (mod & OFM_DEPRECATED)
      parse_error(*loc, "illegal 'deprecated' modifier in hashdecl");
   if (mod & OFM_FINAL)
      parse_error(*loc, "illegal 'final' modifier in hashdecl");
   if (mod & OFM_ABSTRACT)
      parse_error(*loc, "illegal 'abstract' modifier in hashdecl");
}

static void check_nsmod(const QoreProgramLocation* loc, int mod, qore_ns_private& ns) {
   if (mod & (OFM_PRIVATE|OFM_INTERNAL))
      parse_error(*loc, "illegal 'private' modifier in namespace declaration");
   if (mod & OFM_SYNCED)
      parse_error(*loc, "illegal 'synchronized' modifier in namespace declaration");
   if (mod & OFM_STATIC)
      parse_error(*loc, "illegal 'static' modifier in namespace declaration");
   if (mod & OFM_PUBLIC)
      ns.setPublic();
   if (mod & OFM_DEPRECATED)
      parse_error(*loc, "illegal 'deprecated' modifier in namespace declaration");
   if (mod & OFM_FINAL)
      parse_error(*loc, "illegal 'final' modifier in namespace declaration");
   if (mod & OFM_ABSTRACT)
      parse_error(*loc, "illegal 'abstract' modifier in namespace declaration");
}

bool node_has_effect(const AbstractQoreNode* n) {
    if (!n || n->is_value())
        return false;

    if (n->getType() == NT_OPERATOR)
        return reinterpret_cast<const QoreOperatorNode*>(n)->hasEffect();

    const ParseNode* pn = dynamic_cast<const ParseNode*>(n);
    // if pn is a function node it's effect actually cannot be evaluated
    // at this moment because the function flags have not yet been set
    // and so has_effect() always returns 'true'
    return pn ? pn->has_effect() : true;
}

bool node_has_effect_as_root(const AbstractQoreNode* n) {
    if (!n || n->is_value())
        return false;

    if (n->getType() == NT_OPERATOR)
        return reinterpret_cast<const QoreOperatorNode*>(n)->hasEffectAsRoot();

    const ParseNode* pn = dynamic_cast<const ParseNode*>(n);
    // the same comment as in the above function node_has_effect() applies also here
    return pn ? pn->has_effect_as_root() : true;
}

bool value_has_effect_as_root(const QoreValue& n) {
    return node_has_effect_as_root(n.getInternalNode());
}

struct MethodNode {
public:
   // name of method
   char* name;
   // method variant to add to class
   MethodVariantBase *m;
   // static flag
   bool static_flag;

   DLLLOCAL MethodNode(char* mname, MethodVariantBase *v, bool n_static) : name(mname), m(v), static_flag(n_static) {
   }

   DLLLOCAL ~MethodNode() {
      free(name);
      delete m;
   }

   DLLLOCAL void addAndDelete(QoreClass* qc) {
      qore_class_private::addUserMethod(*qc, name, m, static_flag);
      m = 0;
      delete this;
   }

   DLLLOCAL const QoreProgramLocation* getLoc() const {
      return m->getUserVariantBase()->getUserSignature()->getParseLocation();
   }
};

static QoreValue is_rewritable_op(QoreValue n) {
    qore_type_t nt = n.getType();
    if (nt == NT_OPERATOR) {
        QoreBinaryLValueOperatorNode* op = dynamic_cast<QoreBinaryLValueOperatorNode*>(n.getInternalNode());
        if (op)
            return op->getRight();
    }

    return QoreValue();
}

static QoreValue check_rewrite_list(QoreParseListNode* l) {
    if (!parse_check_parse_option(PO_BROKEN_LIST_PARSING))
        return l;

    // rewrite expression if it's a list without parentheses and
    // the first expression has one of the following operators:
    // =, +=, -=
    if (l->isFinalized())
        return l;

    QoreValue e0 = l->get(0);

    QoreValue r = is_rewritable_op(e0);
    if (!r.isNothing()) {
        QoreValue last = e0;
        QoreValue top = e0;
        while (true) {
            QoreValue nr = is_rewritable_op(r);
            if (nr.isNothing()) {
                break;
            }
            last = r;
            r = nr;
        }
        assert(dynamic_cast<QoreBinaryLValueOperatorNode*>(last.getInternalNode()));
        // take first element from list (the tree) and make it the new expression
        QoreBinaryLValueOperatorNode* op = last.get<QoreBinaryLValueOperatorNode>();
        l->swap(0, op->swapRight(l));
        return top;
    }

    return l;
}

// cannot be static
void ignore_return_value(QoreSimpleValue& n) {
    qore_type_t t = n.getType();
    if (t == NT_OPERATOR) {
        n.get<QoreOperatorNode>()->ignoreReturnValue();
    }
}

static qore_var_t get_var_type() {
   return parse_check_parse_option(PO_ASSUME_LOCAL) ? VT_LOCAL : VT_UNRESOLVED;
}

static QoreOperatorNode* process_assignment(int sline, int eline, QoreValue $1, QoreValue $3) {
    const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(sline, eline);
    return ($1.getType() == NT_PARSE_LIST)
        ? static_cast<QoreOperatorNode*>(new QoreListAssignmentOperatorNode(loc, $1, $3))
        : static_cast<QoreOperatorNode*>(new QoreAssignmentOperatorNode(loc, $1, $3));
}

static QoreOperatorNode* process_weak_assignment(const QoreProgramLocation* loc, QoreValue $1, QoreValue $3) {
   if ($1.getType() == NT_LIST) {
       parse_error(*loc, "the ':=' operator is not valid with list assignments");
       return static_cast<QoreOperatorNode*>(new QoreAssignmentOperatorNode(loc, $1, $3));
   }
   return static_cast<QoreOperatorNode*>(new QoreWeakAssignmentOperatorNode(loc, $1, $3));
}

static AbstractQoreNode* processCall(int sline, int eline, QoreValue $1, QoreValue $3, const QoreProgramLocation* argloc) {
    AbstractQoreNode* $$;
    const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(sline, eline);

    //printd(5, "1: %s, 3: %s\n", $1.getTypeName(), $3.getTypeName());
    qore_type_t t = $1.getType();
    if (t == NT_BAREWORD) {
        BarewordNode* b = $1.get<BarewordNode>();
        // take string from node and delete node
        char* str = b->takeString();
        b->deref();
        printd(5, "parsing call %s() args: %s\n", str, $3.getTypeName());
        $$ = new FunctionCallNode(loc, str, make_args(argloc, $3));
    } else if (t == NT_CONSTANT) {
        ScopedRefNode* c = $1.get<ScopedRefNode>();
        // take NamedScope from node and delete node
        NamedScope *ns = c->takeName();
        c->deref();
        assert(ns->size() > 1);
        printd(5, "parsing scoped call (static method, new object call, or program function call) %s()\n", ns->ostr);
        $$ = new StaticMethodCallNode(loc, ns, make_args(argloc, $3));
    } else if (t == NT_SELF_VARREF) {
        SelfVarrefNode* v = $1.get<SelfVarrefNode>();
        // take string from node and delete node
        char* str = v->takeString();
        v->deref();
        //printd(5, "parsing in-object method call %s()\n", str);
        $$ = new SelfFunctionCallNode(loc, str, make_args(argloc, $3));
    } else {
        QoreHashObjectDereferenceOperatorNode* hodon;

        if (t == NT_OPERATOR) {
            hodon = dynamic_cast<QoreHashObjectDereferenceOperatorNode*>($1.getInternalNode());
            if (hodon) {
                QoreValue r = hodon->getRight();
                if (r.getType() != NT_STRING)
                    hodon = nullptr;
            }
        } else
            hodon = nullptr;

        if (hodon) {
            // create an object method call node
            // take the string
            QoreStringNode* str = hodon->swapRight(QoreValue()).get<QoreStringNode>();
            assert(str->getEncoding() == QCS_DEFAULT);
            char* cstr = str->giveBuffer();
            assert(cstr);
            str->deref();

            MethodCallNode* mc = new MethodCallNode(loc, cstr, make_args(argloc, $3));

            // destroy the original operator and create a new operator object
            assert(hodon->is_unique());
            QoreValue l = hodon->swapLeft(QoreValue());
            hodon->deref(nullptr);

            $$ = new QoreDotEvalOperatorNode(loc, l, mc);
        } else {
            $$ = nullptr;
            if ($1.getType() == NT_VARREF) {
                VarRefNode* r = $1.get<VarRefNode>();
                qore_var_t vartype = r->getType();

                if (vartype != VT_UNRESOLVED) {
                    $$ = r->makeNewCall($3);
                    //printd(5, "makeNewCall(%p) returned %p (%s)\n", $3, $$, get_type_name($$));
                    if (!$$) {
                        parseException(*loc, "INVALID-CODE-REFERENCE-CALL", "%s variable '%s' declared as a function reference call", r->getType() == VT_GLOBAL ? "global" : "local", r->getName());
                    }
                }
            }
            if (!$$) {
                $$ = new CallReferenceCallNode(loc, $1, make_args(argloc, $3));
                printd(5, "made new CallReferenceCallNode: %p exp: %s\n", $$, $1.getTypeName());
            }
        }
    }
    return $$;
}

static void check_operator(const QoreProgramLocation* loc, const QoreOperatorNode* n, const char* parent, const char* child) {
    if (parse_check_parse_option(PO_BROKEN_LOGIC_PRECEDENCE)) {
        if (!n->getInParentheses()) {
            qore_program_private::makeParseWarning(getProgram(), *loc, QP_WARN_BROKEN_LOGIC_PRECEDENCE, "BROKEN-LOGIC-PRECEDENCE", "%s gets under %s in the parse tree as an impact of broken-logic-precedence flag being set - please consider using parenthesis to enforce the desired operator precedence", child, parent);
        }
    }
}

static void check_operator_log_or(const QoreProgramLocation* loc, QoreValue n, const char* parent) {
    if (dynamic_cast<const QoreLogicalOrOperatorNode*>(n.getInternalNode())) {
        check_operator(loc, static_cast<const QoreOperatorNode*>(n.getInternalNode()), parent, "logical OR");
    }
}

static void check_operator_bin_or(const QoreProgramLocation* loc, QoreValue n, const char* parent) {
    if (dynamic_cast<const QoreBinaryOrOperatorNode*>(n.getInternalNode())) {
        check_operator(loc, static_cast<const QoreOperatorNode*>(n.getInternalNode()), parent, "binary OR");
    }
}

static void check_operator_bin_xor(const QoreProgramLocation* loc, QoreValue n, const char* parent) {
    if (dynamic_cast<const QoreBinaryXorOperatorNode*>(n.getInternalNode())) {
        check_operator(loc, static_cast<const QoreOperatorNode*>(n.getInternalNode()), parent, "binary XOR");
    }
}

%}

// 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 full
%lex-param {yyscan_t yyscanner}
%parse-param {yyscan_t yyscanner}
%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 {
    QoreSimpleValue qv;
    bool b;
    int i4;
    int64 integer;
    double decimal;
    QoreStringNode* String;
    char* string;
    BinaryNode* binary;
    AbstractQoreNode* node;
    QoreParseHashNode* hash;
    QoreParseListNode* parse_list;
    AbstractStatement* statement;
    struct MemberInfo* memberinfo;
    struct ClassVarInfo* classvarinfo;
    StatementBlock *sblock;
    ContextModList* cmods;
    ContextMod *cmod;
    class HashElement* hashelement;
    QoreFunction* userfunc;
    struct MethodNode* methodnode;
    class MemberList* memberlist;
    QoreClass* qoreclass;
    typed_hash_decl_private* hashdeclpriv;
    class ConstNode* constnode;
    QoreNamespace *ns;
    struct NSNodeList* nsnlist;
    struct NSNode* nsn;
    class ObjClassDef* classdef;
    class HashDeclDef* hashdecldef;
    DateTimeNode* datetime;
    QoreRegexSubst* RegexSubst;
    QoreTransliteration* Trans;
    SwitchStatement* switchstmt;
    CaseNode* casenode;
    BCList* sclist;
    class BCNode* sclnode;
    BCAList* bcalist;
    BCANode* bcanode;
    NamedScope *nscope;
    QoreRegex* Regex;
    QoreImplicitArgumentNode* implicit_arg;
    RetTypeInfo* returnTypeInfo;
    struct ParserTypeStruct* parsertype;
    class ParseUserFunction* parsefunc;
    class ParseScopedUserFunction* sparsefunc;
    struct GVarDecl* gv;
    QoreNumberNode* num;
    TryModuleError* trymod;
    struct MethodDef* methoddef;
}

%{
#define LEX_PARAMETERS YYSTYPE *lvalp, YYLTYPE *loc, yyscan_t scanner

DLLLOCAL int yylex(LEX_PARAMETERS);

DLLLOCAL void yyerror(YYLTYPE* loc, yyscan_t scanner, const char* str) {
    //printd(5, "yyerror() location: %d:%d - %d:%d \"%s\"\n", loc->first_line, loc->first_col, loc->last_line, loc->last_col, str);
    const QoreProgramLocation* qloc = qore_program_private::get(*getProgram())->getLocation(loc->first_line, loc->last_line);
    parse_error(*qloc, "%s", 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_NAMESPACE "namespace"
%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 MODULA_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"

 // tokens returning data
%token <integer> INTEGER "integer value"
%token <decimal> QFLOAT "floating-point value"
%token <num> 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> QORE_OR_NOTHING_CAST "cast (or nothing) operator"
%token <string> CLASS_STRING "simple class declaration"
%token <datetime> DATETIME "date/time value"
%token <String> QUOTED_WORD "quoted string"
%token <binary> BINARY "binary constant value"
%token <RegexSubst> REGEX_SUBST "regular expression substitution expression"
%token <Trans> REGEX_TRANS "transliteration expression"
%token <nscope> CLASS_SCOPED_REF "scoped class declaration"
%token <nscope> BASE_CLASS_CALL "call to base class method"
%token <Regex> REGEX "regular expression"
%token <Regex> REGEX_EXTRACT "regular expression extraction expression"
%token <implicit_arg> IMPLICIT_ARG_REF "implicit argument reference"
%token <String> DOT_KW_IDENTIFIER "keyword used as hash key or object member reference"
%token <trymod> TRY_MODULE_ERROR "%try-module error token"

%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 MINUS_EQUALS AND_EQUALS OR_EQUALS MODULA_EQUALS MULTIPLY_EQUALS DIVIDE_EQUALS XOR_EQUALS SHIFT_LEFT_EQUALS SHIFT_RIGHT_EQUALS '=' WEAK_ASSIGNMENT
%right '?' ':'
%nonassoc RANGE ELLIPSES
%right NULL_COALESCING VALUE_COALESCING
%left LOGICAL_OR
%left LOGICAL_AND BROKEN_LOGICAL_OR
%left '|'
%left '^'
%left '&' BROKEN_BINARY_OR BROKEN_BINARY_XOR
%left '<' '>' LOGICAL_EQ LOGICAL_NE LOGICAL_LE LOGICAL_GE LOGICAL_CMP ABSOLUTE_EQ ABSOLUTE_NE REGEX_MATCH REGEX_NMATCH
%right TOK_EXISTS TOK_INSTANCEOF
%left SHIFT_RIGHT 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 QORE_OR_NOTHING_CAST
%nonassoc TOK_NEW
%nonassoc P_INCREMENT P_DECREMENT
%left '{' '[' '.' '(' DOT_KW_IDENTIFIER  // list and object references, etc, defined for precedence

%type <sblock>         block
%type <sblock>         statement_or_block
%type <sblock>         statements
%type <statement>      statement
%type <statement>      return_statement
%type <statement>      try_statement
%type <qv>             exp
%type <qv>             exp_n
%type <qv>             exp_c
%type <qv>             myexp
%type <qv>             scalar
%type <hash>           hash
%type <hash>           alt_hash
%type <node>           immediate_typed_hash
%type <parse_list>     list
%type <parse_list>     list_n
%type <String>         string
%type <hashelement>    hash_element
%type <cmods>          context_mods
%type <cmod>           context_mod
%type <methodnode>     inline_methoddef
%type <memberlist>     internal_member_list
%type <memberlist>     private_member_list
%type <memberlist>     public_member_list
%type <memberlist>     member_list
%type <memberinfo>     member
%type <memberinfo>     transient_member
%type <memberlist>     member_list2
%type <memberinfo>     member2
%type <memberinfo>     transient_member2
%type <memberinfo>     hashdecl_member
%type <classvarinfo>   classvardecl
%type <qoreclass>      class_attributes
%type <hashdeclpriv>   hashdecl_attrs
%type <classdef>       class_def
%type <hashdecldef>    hashdecl_def
%type <ns>             top_namespace_decl
%type <nsnlist>        namespace_decls
%type <nsn>            namespace_decl
%type <constnode>      scoped_const_decl
%type <constnode>      unscoped_const_decl
%type <i4>             method_modifiers
%type <i4>             method_modifier
%type <i4>             nsc_modifiers
%type <returnTypeInfo> return_value
%type <string>         optname
%type <statement>      switch_statement
%type <switchstmt>     case_block
%type <casenode>       case_code
%type <sclist>         superclass_list
%type <sclist>         inheritance_list
%type <sclnode>        superclass
%type <bcalist>        base_constructor_list
%type <bcalist>        base_constructors
%type <bcanode>        base_constructor
%type <string>         ident_openparen
%type <parsertype>     qtypedef
%type <parsertype>     uncqtypedef
%type <parsefunc>      sub_def
%type <sparsefunc>     scoped_sub_def
%type <b>              ppub
%type <gv>             gvardecl
%type <methoddef>      outofline_methoddef

 // destructor actions for elements that need deleting when parse errors occur
%destructor { delete $$; } TRY_MODULE_ERROR REGEX REGEX_SUBST REGEX_EXTRACT REGEX_TRANS BASE_CLASS_CALL CLASS_SCOPED_REF block statement_or_block statements statement return_statement try_statement hash_element context_mods context_mod inline_methoddef class_def hashdecl_def top_namespace_decl scoped_const_decl unscoped_const_decl switch_statement case_block case_code superclass base_constructor internal_member_list private_member_list public_member_list member_list member_list2 base_constructor_list base_constructors return_value member member2 transient_member transient_member2 hashdecl_member superclass_list inheritance_list qtypedef uncqtypedef classvardecl sub_def scoped_sub_def gvardecl outofline_methoddef hash alt_hash
%destructor { qore_class_private::get(*$$)->deref(true, true); } class_attributes
%destructor { free($$); } IDENTIFIER ANGLE_IDENTIFIER VAR_REF SELF_REF CONTEXT_REF COMPLEX_CONTEXT_REF BACKQUOTE SCOPED_REF SCOPED_VREF KW_IDENTIFIER_OPENPAREN QORE_CAST QORE_OR_NOTHING_CAST CLASS_STRING optname ident_openparen HASHDECL_IDENTIFIER_OPENCURLY
%destructor { if ($$) $$->deref(); } namespace_decl namespace_decls string QUOTED_WORD DATETIME BINARY IMPLICIT_ARG_REF DOT_KW_IDENTIFIER hashdecl_attrs list list_n
%destructor { if ($$) $$->deref(nullptr); } immediate_typed_hash
%destructor { $$.discard(nullptr); } scalar exp exp_n exp_c myexp
%%
top_level_commands:
        top_level_command
        | top_level_commands top_level_command
        ;

top_level_command:
        sub_def {
           $1->add(*(qore_ns_private::get(*(getRootNS()))));
        }
        | scoped_sub_def {
           // see if strong encapsulation is enabled
           check_strong_encapsulation($1->loc, "function", $1->name.ostr);
           $1->add(*(qore_ns_private::get(*(getRootNS()))));
        }
        | class_def {
           qore_root_ns_private::parseAddClass($1->loc, *($1->name), $1->oc);

           // see if class definitions are allowed
           if (parse_check_parse_option(PO_NO_CLASS_DEFS))
              parse_error(*$1->loc, "illegal class definition \"%s\" (conflicts with parse option NO_CLASS_DEFS)", $1->oc->getName());
           delete $1;
        }
        | hashdecl_def {
           qore_root_ns_private::parseAddHashDecl($1->loc, *($1->name), $1->takeHashDecl());
           delete $1;
        }
        | scoped_const_decl {
           // see if strong encapsulation is enabled
           check_strong_encapsulation($1->loc, "constant", $1->name.ostr);
           qore_root_ns_private::parseAddConstant($1->loc, *getRootNS(), $1->name, $1->value, $1->pub);
           delete $1;
        }
        | unscoped_const_decl {
           qore_root_ns_private::parseAddConstant($1->loc, *getRootNS(), $1->name, $1->value, $1->pub);
           delete $1;
        }
        | outofline_methoddef {
           // see if strong encapsulation is enabled
           check_strong_encapsulation($1->loc, "method", $1->name.ostr);
           $1->parseAddToClassAndDel();
        }
        | statement {
           if ($1) {
              if ($1->isParseDeclaration())
                 delete $1;
              else
                 qore_program_private::addStatement(*(getProgram()), $1);
           }
        }
        | '{' '}'
        | '{' statements '}' {
            $2->finalizeBlock(@1.first_line, @2.last_line);
            qore_program_private::addStatement(*(getProgram()), $2);
        }
        | top_namespace_decl {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            qore_root_ns_private::parseAddNamespace($1);
            // see if ns declaration is legal
            if (parse_check_parse_option(PO_NO_NAMESPACE_DEFS))
                parse_error(*loc, "illegal namespace definition \"%s\" (conflicts with parse option PO_NO_NAMESPACE_DEFINITION)", $1->getName());
        }
        | TOK_MODULE '{' module_decls '}' {
            QoreModuleDefContext* qmd = get_module_def_context();
            if (!qmd)
                qore_program_private::makeParseWarning(getProgram(), *qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line), QP_WARN_MODULE_ONLY, "MODULE-ONLY", "module definitions are ignored when not defining a module");
            else {
                QoreUserModuleDefContextHelper* uqmd = static_cast<QoreUserModuleDefContextHelper*>(qmd);
                uqmd->close();
            }
            // registered internally
        }
        | TRY_MODULE_ERROR {
            bool has_dollar = $1->var[0] == '$';
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            if (!has_dollar) {
                if (!parse_check_parse_option(PO_ALLOW_BARE_REFS)) {
                    parse_error(*loc, "%%try-module variable '%s' declared without '$' prefix, but parse option 'allow-bare-refs' is not set", $1->var);
                }
            } else {
                $1->fixName();
                if (parse_check_parse_option(PO_ALLOW_BARE_REFS))
                    parse_error(*loc, "%%try-module variable '%s' declared with '$' prefix, but parse option 'allow-bare-refs' is set", $1->var);
            }

            if (parse_check_parse_option(PO_NO_TOP_LEVEL_STATEMENTS)) {
                parse_error(*loc, "'%%try-module' with exception variable cannot be used with parse option NO_TOP_LEVEL_STATEMENTS; use %%try-module without an exception variable instead");
            } else {
                VarRefTryModuleErrorNode* left = new VarRefTryModuleErrorNode(loc, $1->takeName());
                QoreAssignmentOperatorNode* exp = new QoreAssignmentOperatorNode(loc, left, $1->takeExceptionHash());
                qore_program_private::addStatement(*(getProgram()), new ExpressionStatement(loc, exp));
            }
            delete $1;
        }
        ;

module_decls:
        module_decl {
        }
        | module_decls module_decl {
        }
        ;

module_decl:
        IDENTIFIER '=' exp ';' {
            QoreModuleDefContext* qmd = get_module_def_context();
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
            if (!qmd)
                qore_program_private::makeParseWarning(getProgram(), *loc, QP_WARN_MODULE_ONLY, "MODULE-ONLY", "cannot add module tag '%s' = <%s> when not defining a module", $1, $3.getTypeName());
            else
                qmd->set(loc, $1, $3);

            free($1);
            $3.discard(nullptr);
        }
        ;

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

gvardecl:
        ppub TOK_OUR uncqtypedef SCOPED_REF {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @4.last_line);
            // see if strong encapsulation is enabled
            check_strong_encapsulation(loc, "global variable", $4);
            const QoreTypeInfo* t = ParserTypeStruct::getTypeInfo($3);
            $$ = new GVarSingleDecl(loc, $4, t, t ? 0 : ParserTypeStruct::getParseTypeInfo($3), $1, true);
            delete $3;
        }
        | ppub TOK_OUR SCOPED_REF {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @3.last_line);
            // see if strong encapsulation is enabled
            check_strong_encapsulation(loc, "global variable", $3);
            $$ = new GVarSingleDecl(loc, $3, 0, 0, $1, true);
        }
        | ppub TOK_OUR uncqtypedef IDENTIFIER {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @4.last_line);
            const QoreTypeInfo* t = ParserTypeStruct::getTypeInfo($3);
            $$ = new GVarSingleDecl(loc, $4, t, t ? 0 : ParserTypeStruct::getParseTypeInfo($3), $1, true);
            delete $3;
        }
        | ppub TOK_OUR IDENTIFIER {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @3.last_line);
            $$ = new GVarSingleDecl(loc, $3, 0, 0, $1, true);
        }
        | ppub TOK_OUR qtypedef SCOPED_VREF {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @4.last_line);
            // see if strong encapsulation is enabled
            check_strong_encapsulation(loc, "global variable", $4);
            const QoreTypeInfo* t = ParserTypeStruct::getTypeInfo($3);
            $$ = new GVarSingleDecl(loc, $4, t, t ? 0 : ParserTypeStruct::getParseTypeInfo($3), $1, false);
            delete $3;
        }
        | ppub TOK_OUR qtypedef VAR_REF {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @4.last_line);
            const QoreTypeInfo* t = ParserTypeStruct::getTypeInfo($3);
            $$ = new GVarSingleDecl(loc, $4, t, t ? 0 : ParserTypeStruct::getParseTypeInfo($3), $1, false);
            delete $3;
        }
        | ppub TOK_OUR '(' list ')' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @5.last_line);
            $$ = new GVarListDecl(loc, $4, $1);
        }
        | ppub TOK_THREAD_LOCAL uncqtypedef SCOPED_REF {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @4.last_line);
            // see if strong encapsulation is enabled
            check_strong_encapsulation(loc, "thread_local variable", $4);
            const QoreTypeInfo* t = ParserTypeStruct::getTypeInfo($3);
            $$ = new GVarSingleDecl(loc, $4, t, t ? 0 : ParserTypeStruct::getParseTypeInfo($3), $1, true, VT_THREAD_LOCAL);
            delete $3;
        }
        | ppub TOK_THREAD_LOCAL SCOPED_REF {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @3.last_line);
            // see if strong encapsulation is enabled
            check_strong_encapsulation(loc, "thread_local variable", $3);
            $$ = new GVarSingleDecl(loc, $3, 0, 0, $1, true, VT_THREAD_LOCAL);
        }
        | ppub TOK_THREAD_LOCAL uncqtypedef IDENTIFIER {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @4.last_line);
            const QoreTypeInfo* t = ParserTypeStruct::getTypeInfo($3);
            $$ = new GVarSingleDecl(loc, $4, t, t ? 0 : ParserTypeStruct::getParseTypeInfo($3), $1, true, VT_THREAD_LOCAL);
            delete $3;
        }
        | ppub TOK_THREAD_LOCAL IDENTIFIER {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @3.last_line);
            $$ = new GVarSingleDecl(loc, $3, 0, 0, $1, true, VT_THREAD_LOCAL);
        }
        | ppub TOK_THREAD_LOCAL qtypedef SCOPED_VREF {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @4.last_line);
            // see if strong encapsulation is enabled
            check_strong_encapsulation(loc, "thread_local variable", $4);
            const QoreTypeInfo* t = ParserTypeStruct::getTypeInfo($3);
            $$ = new GVarSingleDecl(loc, $4, t, t ? 0 : ParserTypeStruct::getParseTypeInfo($3), $1, false, VT_THREAD_LOCAL);
            delete $3;
        }
        | ppub TOK_THREAD_LOCAL qtypedef VAR_REF {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @4.last_line);
            const QoreTypeInfo* t = ParserTypeStruct::getTypeInfo($3);
            $$ = new GVarSingleDecl(loc, $4, t, t ? 0 : ParserTypeStruct::getParseTypeInfo($3), $1, false, VT_THREAD_LOCAL);
            delete $3;
        }
        | ppub TOK_THREAD_LOCAL '(' list ')' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @5.last_line);
            $$ = new GVarListDecl(loc, $4, $1, VT_THREAD_LOCAL);
        }
        ;

top_namespace_decl:
        nsc_modifiers TOK_NAMESPACE '{' namespace_decls '}' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @5.last_line);
            qore_ns_private* p = new qore_ns_private(loc);
            check_nsmod(loc, $1, *p);
            $4->add(*p);
            delete $4;
            $$ = p->ns;
        }
        | nsc_modifiers TOK_NAMESPACE ';' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @2.last_line);
            qore_ns_private* p = new qore_ns_private(loc);
            check_nsmod(loc, $1, *p);
            $$ = p->ns;
        }
        | nsc_modifiers TOK_NAMESPACE '{' '}' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @4.last_line);
            qore_ns_private* p = new qore_ns_private(loc);
            check_nsmod(loc, $1, *p);
            $$ = p->ns;
        }
        ;

namespace_decls:
        namespace_decl {
           $$ = new NSNodeList;
           if ($1)
              $$->push_back($1);
        }
        | namespace_decls namespace_decl {
           $$ = $1;
           if ($2)
              $$->push_back($2);
        }
        ;

namespace_decl:
        scoped_const_decl {
           // see if strong encapsulation is enabled
           check_strong_encapsulation($1->loc, "constant", $1->name.ostr);
           $$ = new NSNode($1);
        }
        | unscoped_const_decl {
           $$ = new NSNode($1);
        }
        | class_def {
            $$ = new NSNode($1);
            // see if class definitions are allowed
            if (parse_check_parse_option(PO_NO_CLASS_DEFS)) {
                const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
                parse_error(*loc, "illegal class definition \"%s\" (conflicts with parse option PO_NO_CLASS_DEFS)", $1->oc->getName());
            }
        }
        | hashdecl_def {
           $$ = new NSNode($1);
        }
        | sub_def {
           $$ = new NSNode($1);
        }
        | scoped_sub_def {
           // see if strong encapsulation is enabled
           check_strong_encapsulation($1->loc, "function", $1->name.ostr);
           $$ = new NSNode($1);
        }
        | gvardecl ';' {
           $$ = new NSNode($1);
        }
        | top_namespace_decl {
            $$ = new NSNode($1);
            // see if ns declaration is legal
            if (parse_check_parse_option(PO_NO_NAMESPACE_DEFS)) {
                const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
                parse_error(*loc, "illegal namespace definition \"%s\" (conflicts with parse option PO_NO_NAMESPACE_DEFINITION)", $1->getName());
            }
        }
        | outofline_methoddef {
           $$ = new NSNode($1);
        }
        ;

unscoped_const_decl:
        TOK_CONST IDENTIFIER '=' exp ';' {
           $$ = new ConstNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line), $2, $4);
        }
        | TOK_PUBLIC TOK_CONST IDENTIFIER '=' exp ';' {
           $$ = new ConstNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line), $3, $5, true);
        }
        ;

scoped_const_decl:
        TOK_CONST SCOPED_REF '=' exp ';' {
           $$ = new ConstNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line), $2, $4);
        }
        | TOK_PUBLIC TOK_CONST SCOPED_REF '=' exp ';' {
           $$ = new ConstNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line), $3, $5, true);
        }
        ;

block:
        '{' statements '}'
        { $$ = $2; }
        |
        '{' /* NOTHING */ '}'
        { $$ = new StatementBlock(@1.first_line, @1.last_line); }
        ;

statement_or_block:
        statement
        { $$ = new StatementBlock(@1.first_line, @1.last_line, $1); }
        |
        block
        { $$ = $1; }
        ;

statements:
        statement
        { $$ = new StatementBlock(@1.first_line, @1.last_line, $1); }
        | block
        { $$ = new StatementBlock(@1.first_line, @1.last_line, $1); }
        | statements block
        { $1->addStatement($2); $$ = $1; }
        | statements statement
        { $1->addStatement($2); $$ = $1; }
        ;

optname:
        /* empty */
        { $$ = 0; }
        | IDENTIFIER { $$ = $1; }
        ;

statement:
        ';' { $$ = new StatementBlock(@1.first_line, @1.last_line); }
        | exp_n ';' {
            qore_program_private* pgm = qore_program_private::get(*getProgram());
            // if the expression has no effect and it's not a variable declaration
            qore_type_t t = $1.getType();
            bool ok = false;
            if (t == NT_PARSE_LIST) {
                QoreParseListNode* l = $1.get<QoreParseListNode>();
                // rewrite expression if it's a list without parentheses under certain conditions
                $1.set(check_rewrite_list(l));

                if (l->isVariableList())
                    ok = true;
            }

            const QoreProgramLocation* loc = pgm->getLocation(@1.first_line, @1.last_line);

            if (!ok) {
                if (!value_has_effect_as_root($1) && (t != NT_VARREF || !$1.get<VarRefNode>()->parseIsDecl())) {
                    if (!parse_check_parse_option(PO_ALLOW_STATEMENT_NO_EFFECT)) {
                        parse_error(*loc, "expression has no effect as a statement (%s)", $1.getTypeName());
                    }
                } else if (!$1.hasEffect() && (t != NT_VARREF || !$1.get<VarRefNode>()->parseIsDecl())) {
                    parse_error(*loc, "statement has no effect (%s)", $1.getTypeName());
                }
            }

            ignore_return_value($1);
            $$ = new ExpressionStatement(loc, $1);
        }
        // this should be covered as an expression, but for some reason it isn't...
        | SCOPED_REF '(' myexp ')' ';' {
            NamedScope *ns = new NamedScope($1);
            assert(ns->size() > 1);
            printd(5, "statement: parsing static method call: %s() size=%d\n", ns->ostr, ns->size());
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            $$ = new ExpressionStatement(loc, new StaticMethodCallNode(loc, ns, make_args(qore_program_private::get(*getProgram())->getLocation(@3.first_line, @3.last_line), $3)));
        }
        | try_statement
        { $$ = $1; }
        | TOK_RETHROW ';' {
           $$ = new RethrowStatement(@1.first_line, @1.last_line);
        }
        | TOK_RETHROW exp ';' {
           $$ = new RethrowStatement(@1.first_line, @1.last_line, $2);
        }
        | TOK_THROW exp ';' {
           $$ = new ThrowStatement(@1.first_line, @2.last_line, $2);
        }
        | TOK_ON_EXIT statement_or_block {
           $$ = new OnBlockExitStatement(@1.first_line, @2.last_line, $2, OBE_Unconditional);
        }
        | TOK_ON_SUCCESS statement_or_block {
           $$ = new OnBlockExitStatement(@1.first_line, @2.last_line, $2, OBE_Success);
        }
        | TOK_ON_ERROR statement_or_block {
           $$ = new OnBlockExitStatement(@1.first_line, @2.last_line, $2, OBE_Error);
        }
        | TOK_SUB_CONTEXT context_mods statement_or_block {
           $$ = new ContextStatement(@1.first_line, @3.last_line, 0, 0, $2, $3);
        }
        | TOK_SUMMARIZE optname '(' exp ')' TOK_BY '(' exp ')' context_mods statement_or_block {
           $$ = new SummarizeStatement(@1.first_line, @11.last_line, $2, $4, $10, $11, $8);
        }
        | TOK_CONTEXT optname '(' exp ')' context_mods statement_or_block {
           $$ = new ContextStatement(@1.first_line, @7.last_line, $2, $4, $6, $7);
        }
        | TOK_IF '(' exp ')' statement_or_block %prec IFX {
           $$ = new IfStatement(@1.first_line, @5.last_line, $3, $5);
        }
        | TOK_IF '(' exp ')' statement_or_block TOK_ELSE statement_or_block {
           $$ = new IfStatement(@1.first_line, @7.last_line, $3, $5, $7);
        }
        | TOK_WHILE '(' exp ')' statement_or_block {
           $$ = new WhileStatement(@1.first_line, @5.last_line, $3, $5);
        }
        | TOK_DO statement_or_block TOK_WHILE '(' exp ')' ';' {
           $$ = new DoWhileStatement(@1.first_line, @5.last_line, $5, $2);
        }
        | TOK_FOR '(' myexp ';' myexp ';' myexp ')' statement_or_block {
           $$ = new ForStatement(@1.first_line, @9.last_line, $3, $5, $7, $9);
        }
        | TOK_FOREACH exp TOK_IN '(' exp ')' statement_or_block {
           $$ = new ForEachStatement(@1.first_line, @7.last_line, $2, $5, $7);
        }
        | return_statement ';' { $$ = $1; }
        | TOK_THREAD_EXIT ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            // see if thread exit is allowed
            if (parse_check_parse_option(PO_NO_THREAD_CONTROL)) {
                parse_error(*loc, "illegal use of \"thread_exit\" (conflicts with parse option PO_NO_THREAD_CONTROL)");
            }
            $$ = new ThreadExitStatement(loc);
        }
        | TOK_BREAK ';' {
            $$ = new BreakStatement(@1.first_line, @1.last_line);
        }
        | TOK_CONTINUE ';' {
            $$ = new ContinueStatement(@1.first_line, @1.last_line);
        }
        | switch_statement { $$ = $1; }
        | error ';'        { $$ = 0; }
        ;

context_mods:
        // empty
        { $$ = 0; }
        | context_mods context_mod {
           if (!$1)
              $$ = new ContextModList($2);
           else {
              $1->addContextMod($2);
              $$ = $1;
           }
        }
        ;

context_mod:
        TOK_WHERE '(' exp ')'
        { $$ = new ContextMod(CM_WHERE_NODE, $3); }
        | TOK_SORT_BY '(' exp ')'
        { $$ = new ContextMod(CM_SORT_ASCENDING, $3); }
        | TOK_SORT_DESCENDING_BY '(' exp ')'
        { $$ = new ContextMod(CM_SORT_DESCENDING, $3); }
        ;

return_statement:
        TOK_RETURN     { $$ = new ReturnStatement(@1.first_line, @1.last_line); }
        |
        TOK_RETURN exp { $$ = new ReturnStatement(@1.first_line, @2.last_line, $2); }
        ;

switch_statement:
        TOK_SWITCH '(' exp ')' '{' case_block '}'
        {
           $6->setSwitch($3);
           $$ = $6;
           $$->finalizeBlock(@1.first_line, @7.last_line);
        }
        ;

case_block:
        case_code
        {
           $$ = new SwitchStatement($1);
        }
        | case_block case_code
        {
           $1->addCase($2);
           $$ = $1;
        }
        ;

case_code:
        TOK_CASE LOGICAL_GE exp ':' statements
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line);
            check_case(loc, ">=", $3);
            $$ = new CaseNodeWithOperator(loc, $3, $5, QoreLogicalGreaterThanOrEqualsOperatorNode::doGreaterThanOrEquals);
        }
        | TOK_CASE LOGICAL_GE exp ':' // nothing
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            check_case(loc, ">=", $3);
            $$ = new CaseNodeWithOperator(loc, $3, nullptr, QoreLogicalGreaterThanOrEqualsOperatorNode::doGreaterThanOrEquals);
        }
        | TOK_CASE LOGICAL_LE exp ':' statements
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line);
            check_case(loc, "<=", $3);
            $$ = new CaseNodeWithOperator(loc, $3, $5, QoreLogicalLessThanOrEqualsOperatorNode::doLessThanOrEquals);
        }
        | TOK_CASE LOGICAL_LE exp ':' // nothing
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            check_case(loc, "<=", $3);
            $$ = new CaseNodeWithOperator(loc, $3, nullptr, QoreLogicalLessThanOrEqualsOperatorNode::doLessThanOrEquals);
        }
        | TOK_CASE LOGICAL_EQ exp ':' statements
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line);
            check_case(loc, "==", $3);
            $$ = new CaseNodeWithOperator(loc, $3, $5, QoreLogicalEqualsOperatorNode::softEqual);
        }
        | TOK_CASE LOGICAL_EQ exp ':' // nothing
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            check_case(loc, "==", $3);
            $$ = new CaseNodeWithOperator(loc, $3, nullptr, QoreLogicalEqualsOperatorNode::softEqual);
        }
        | TOK_CASE '<' exp ':' statements
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line);
            check_case(loc, "<", $3);
            $$ = new CaseNodeWithOperator(loc, $3, $5, QoreLogicalLessThanOperatorNode::doLessThan);
        }
        | TOK_CASE '<' exp ':' // nothing
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            check_case(loc, "<", $3);
            $$ = new CaseNodeWithOperator(loc, $3, nullptr, QoreLogicalLessThanOperatorNode::doLessThan);
        }
        | TOK_CASE '>' exp ':' statements
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line);
            check_case(loc, ">", $3);
            $$ = new CaseNodeWithOperator(loc, $3, $5, QoreLogicalGreaterThanOperatorNode::doGreaterThan);
        }
        | TOK_CASE '>' exp ':' // nothing
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            check_case(loc, ">", $3);
            $$ = new CaseNodeWithOperator(loc, $3, nullptr, QoreLogicalGreaterThanOperatorNode::doGreaterThan);
        }
        | TOK_CASE REGEX_MATCH REGEX ':' statements
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line);
            $$ = new CaseNodeRegex(loc, $3, $5);
        }
        | TOK_CASE REGEX_MATCH REGEX ':' // nothing
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            $$ = new CaseNodeRegex(loc, $3, nullptr);
        }
        | TOK_CASE REGEX_NMATCH REGEX ':' statements
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line);
            $$ = new CaseNodeNegRegex(loc, $3, $5);
        }
        | TOK_CASE REGEX_NMATCH REGEX ':' // nothing
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            $$ = new CaseNodeNegRegex(loc, $3, nullptr);
        }
        | TOK_CASE REGEX ':' statements
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            $$ = new CaseNodeRegex(loc, $2, $4);
        }
        | TOK_CASE REGEX ':' // nothing
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
            $$ = new CaseNodeRegex(loc, $2, nullptr);
        }
        | TOK_CASE exp ':' statements
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            check_case(loc, 0, $2);
            $$ = new CaseNode(loc, $2, $4);
        }
        | TOK_CASE exp ':' // nothing
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
            check_case(loc, 0, $2);
            $$ = new CaseNode(loc, $2, nullptr);
        }
        | TOK_DEFAULT ':' statements
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
            $$ = new CaseNode(loc, QoreValue(), $3, true);
        }
        | TOK_DEFAULT ':' // nothing
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$ = new CaseNode(loc, QoreValue(), nullptr, true);
        }
        ;

try_statement:
        TOK_TRY statement_or_block TOK_CATCH '(' myexp ')' statement_or_block {
            char* param = 0;
            const QoreTypeInfo* typeInfo = nullptr;
            QoreParseTypeInfo* parseTypeInfo = nullptr;
            const QoreProgramLocation* loc =
                qore_program_private::get(*getProgram())->getLocation(@1.first_line, @7.last_line);
            bool missing_type_decl = true;
            if ($5.getType() == NT_VARREF) {
                VarRefNode* varRefNode = $5.get<VarRefNode>();
                param = varRefNode->takeName();
                if (varRefNode->isDecl()) {
                    VarRefDeclNode* varRefDeclNode = static_cast<VarRefDeclNode*>(varRefNode);
                    parseTypeInfo = varRefDeclNode->takeParseTypeInfo();
                    if (!parseTypeInfo)
                        typeInfo = varRefDeclNode->getTypeInfo();
                    missing_type_decl = false;
                }
            } else if ($5.getType() == NT_BAREWORD) {
                param = $5.get<BarewordNode>()->takeString();
                if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                    parse_error(*loc, "local variable '%s' in catch parameter list declared without '$' prefix, but " \
                        "parse option 'allow-bare-refs' is not set", param);
            } else if ($5) {
                parse_error(*loc, "only one parameter accepted in catch block for exception hash");
                missing_type_decl = false;
            }
            if ($5 && missing_type_decl) {
                int64 po = parse_get_parse_options();
                if (po & (PO_STRICT_TYPES|PO_REQUIRE_TYPES)) {
                    SimpleRefHolder<QoreStringNode> desc(new QoreStringNodeMaker("the catch block is missing a " \
                        "type declaration; the exception argument should be declared as 'hash<ExceptionInfo>' or " \
                        "with a compatible type"));
                    // issue #2943: raise an error for mixing string and non-scalar values with %strict-types
                    if (po & PO_STRICT_TYPES) {
                        desc->concat("; this is an error when %strict-types is in effect");
                        qore_program_private::makeParseException(getProgram(), *loc, "PARSE-TYPE-ERROR", desc.release());
                    } else {
                        qore_program_private::makeParseWarning(getProgram(), *loc, QP_WARN_INVALID_CATCH,
                            "INVALID-CATCH", desc.release());
                    }
                }
            }
            $5.discard(nullptr);
            const QoreProgramLocation* vloc = qore_program_private::get(*getProgram())->getLocation($5 ? @5.first_line : 0, $5 ? @5.last_line : 0);
            $$ = new TryStatement(loc, $2, $7, param, typeInfo, parseTypeInfo, vloc);
        }
        ;

myexp:  /* empty */  { $$.set(nullptr); }
        | exp        { $$.set($1); }
        | error      { $$.set(nullptr); }
        ;

hashdecl_def:
        nsc_modifiers TOK_HASHDECL IDENTIFIER '{' hashdecl_attrs '}' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @6.last_line);
            check_hashdeclmod(loc, $1, *($5));
            $$ = new HashDeclDef(loc, $3, $5);
        }
        | nsc_modifiers TOK_HASHDECL SCOPED_REF '{' hashdecl_attrs '}' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @6.last_line);
            check_hashdeclmod(loc, $1, *($5));
            $$ = new HashDeclDef(loc, $3, $5);
        }
        ;

class_def:
        nsc_modifiers CLASS_STRING inheritance_list '{' class_attributes '}' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @6.last_line);
            check_classmod(loc, $1, *($5));
            qore_class_private::parseSetBaseClassList(*($5), $3);
            $$ = new ObjClassDef(loc, $2, $5);
        }
        | nsc_modifiers CLASS_SCOPED_REF inheritance_list '{' class_attributes '}' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @6.last_line);
            // see if strong encapsulation is enabled
            check_strong_encapsulation(loc, "class", $2->ostr);
            check_classmod(loc, $1, *($5));
            $$ = new ObjClassDef(loc, $2, $5);
            qore_class_private::parseSetBaseClassList(*($5), $3);
        }
        | nsc_modifiers CLASS_STRING inheritance_list ';' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @3.last_line);
            // class name is set automatically from name saved in scanner
            QoreClass* qc = new QoreParseClass(loc);
            check_classmod(loc, $1, *qc);
            qore_class_private::parseSetBaseClassList(*qc, $3);
            $$ = new ObjClassDef(loc, $2, qc);
        }
        | nsc_modifiers CLASS_SCOPED_REF inheritance_list ';' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @3.last_line);
            // see if strong encapsulation is enabled
            check_strong_encapsulation(loc, "class", $2->ostr);
            // class name is set automatically from name saved in scanner
            QoreClass* qc = new QoreParseClass(loc);
            check_classmod(loc, $1, *qc);
            $$ = new ObjClassDef(loc, $2, qc);
            qore_class_private::parseSetBaseClassList(*qc, $3);
        }
        | nsc_modifiers CLASS_STRING inheritance_list '{' '}' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @5.last_line);
            // class name is set automatically from name saved in scanner
            QoreClass* qc = new QoreParseClass(loc);
            check_classmod(loc, $1, *qc);
            qore_class_private::parseSetBaseClassList(*qc, $3);
            $$ = new ObjClassDef(loc, $2, qc);
        }
        | nsc_modifiers CLASS_SCOPED_REF inheritance_list '{' '}' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @5.last_line);
            // see if strong encapsulation is enabled
            check_strong_encapsulation(loc, "class", $2->ostr);
            // class name is set automatically from name saved in scanner
            QoreClass* qc = new QoreParseClass(loc);
            check_classmod(loc, $1, *qc);
            $$ = new ObjClassDef(loc, $2, qc);
            qore_class_private::parseSetBaseClassList(*qc, $3);
        }
        ;

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

superclass_list:
        superclass {
           $$ = new BCList($1);
        }
        | superclass_list ',' superclass {
           $1->push_back($3);
           $$ = $1;
        }
        ;

superclass:
        IDENTIFIER {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$ = new BCNode(loc, $1, Public);
        }
        | SCOPED_REF {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$ = new BCNode(loc, new NamedScope($1), Public);
        }
        | TOK_PUBLIC IDENTIFIER {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$ = new BCNode(loc, $2, Public);
        }
        | TOK_PUBLIC SCOPED_REF {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$ = new BCNode(loc, new NamedScope($2), Public);
        }
        | TOK_PRIVATE IDENTIFIER {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$ = new BCNode(loc, $2, Private);
        }
        | TOK_PRIVATE SCOPED_REF {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$ = new BCNode(loc, new NamedScope($2), Private);
        }
        | TOK_PRIVATEHIERARCHY IDENTIFIER {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$ = new BCNode(loc, $2, Private);
        }
        | TOK_PRIVATEHIERARCHY SCOPED_REF {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$ = new BCNode(loc, new NamedScope($2), Private);
        }
        | TOK_PRIVATEINTERNAL IDENTIFIER {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$ = new BCNode(loc, $2, Internal);
        }
        | TOK_PRIVATEINTERNAL SCOPED_REF {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$ = new BCNode(loc, new NamedScope($2), Internal);
        }
        ;

hashdecl_attrs:
        hashdecl_member {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$ = new typed_hash_decl_private(loc);
            if ($1) {
                $$->parseAdd($1->getHashMemPair());
                delete $1;
            }
        }
        | hashdecl_attrs hashdecl_member {
            if ($2) {
                if ($1->hasMember($2->name)) {
                    parse_error(*$2->loc, "duplicate hashdecl member declaration '%s'", $2->name);
                } else {
                    // add new member to list
                    $1->parseAdd($2->getHashMemPair());
                }
                delete $2;
            }

            $$ = $1;
        }
        ;

hashdecl_member:
        uncqtypedef IDENTIFIER ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$ = new MemberInfo($2, new HashDeclMemberInfo(loc, ParserTypeStruct::getTypeInfo($1), ParserTypeStruct::getParseTypeInfo($1)));
            delete $1;
        }
        | uncqtypedef IDENTIFIER '=' exp ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            $$ = new MemberInfo($2, new HashDeclMemberInfo(loc, ParserTypeStruct::getTypeInfo($1), ParserTypeStruct::getParseTypeInfo($1), $4));
            delete $1;
        }
        | uncqtypedef IDENTIFIER '(' myexp ')' ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @5.last_line);
            const QoreTypeInfo* t = ParserTypeStruct::getTypeInfo($1);
            AbstractQoreNode* new_call = nullptr;
            QoreParseTypeInfo* pti = ParserTypeStruct::getParseTypeInfo($1);

            if (!$1)
                parse_error(*loc, "cannot call constructor without declaring the class");
            else if (t)
                parse_error(*loc, "cannot call constructor for non-class base type '%s'", QoreTypeInfo::getName(t));
            else {
                const QoreProgramLocation* aloc = qore_program_private::get(*getProgram())->getLocation(@4.first_line, @4.last_line);
                if (pti->subtypes.size()) {
                    new_call = new ParseNewComplexTypeNode(loc, new QoreParseTypeInfo(*pti), make_args(aloc, $4));
                }
                else {
                    new_call = new ScopedObjectCallNode(loc, new NamedScope(strdup(pti->cscope->ostr)), make_args(aloc, $4));
                }
            }

            $$ = new MemberInfo($2, new HashDeclMemberInfo(loc, t, pti, new_call));

            delete $1;
        }
        ;

class_attributes:
        inline_methoddef {
           $$ = new QoreParseClass($1->getLoc());
           $1->addAndDelete($$);
        }
        | internal_member_list {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$ = new QoreParseClass(loc);
            $1->merge($$, Internal);
            delete $1;
        }
        | private_member_list {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$ = new QoreParseClass(loc);
            $1->merge($$, Private);
            delete $1;
        }
        | public_member_list {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$ = new QoreParseClass(loc);
            $1->merge($$, Public);
            delete $1;
        }
        | unscoped_const_decl {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$ = new QoreParseClass(loc);
            qore_class_private::parseAddConstant(*($$), loc, $1->getName(), $1->takeValue(), Public);
            delete $1;
        }
        | class_attributes inline_methoddef {
            $2->addAndDelete($1);
            $$ = $1;
        }
        | class_attributes internal_member_list {
            $2->merge($1, Internal);
            $$ = $1;
            delete $2;
        }
        | class_attributes private_member_list {
            $2->merge($1, Private);
            $$ = $1;
            delete $2;
        }
        | class_attributes public_member_list {
            $2->merge($1, Public);
            $$ = $1;
            delete $2;
        }
        | class_attributes unscoped_const_decl {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@2.first_line, @2.last_line);
            qore_class_private::parseAddConstant(*($$), loc, $2->getName(), $2->takeValue(), Public);
            $$ = $1;
            delete $2;
        }
        ;

uncqtypedef:
        IDENTIFIER {
            $$ = new ParserTypeStruct($1, false);
        }
        | SCOPED_REF {
            $$ = new ParserTypeStruct(new QoreParseTypeInfo($1, false));
        }
        | ANGLE_IDENTIFIER {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$ = ParserTypeStruct::getType(loc, $1, false);
        }
        | '*' IDENTIFIER {
            $$ = new ParserTypeStruct($2, true);
        }
        | '*' SCOPED_REF {
            $$ = new ParserTypeStruct(new QoreParseTypeInfo($2, true));
        }
        | '*' ANGLE_IDENTIFIER {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$ = ParserTypeStruct::getType(loc, $2, true);
        }
        ;

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

internal_member_list
    : TOK_PRIVATEINTERNAL member_list ';'                 { $$ = $2; }
    | TOK_PRIVATEINTERNAL '{' member_list2 '}'            { $$ = $3; }
    | TOK_PRIVATEINTERNAL '{' '}'                         { $$ = new EmptyMemberList; }
    ;

private_member_list
    : TOK_PRIVATE member_list ';'                 { $$ = $2; }
    | TOK_PRIVATE '{' member_list2 '}'            { $$ = $3; }
    | TOK_PRIVATE '{' '}'                         { $$ = new EmptyMemberList; }
    | TOK_PRIVATEHIERARCHY member_list ';'        { $$ = $2; }
    | TOK_PRIVATEHIERARCHY '{' member_list2 '}'   { $$ = $3; }
    | TOK_PRIVATEHIERARCHY '{' '}'                { $$ = new EmptyMemberList; }
    ;

public_member_list
    : TOK_PUBLIC member_list ';'            { $$ = $2; }
    | TOK_PUBLIC '{' member_list2 '}'       { $$ = $3; }
    | TOK_PUBLIC '{' '}'                    { $$ = new EmptyMemberList; }
    ;

classvardecl:
        TOK_STATIC uncqtypedef IDENTIFIER ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
            $$ = new ClassVarInfo($3, new QoreVarInfo(loc, ParserTypeStruct::getTypeInfo($2), ParserTypeStruct::getParseTypeInfo($2)));
            delete $2;
        }
        | TOK_STATIC IDENTIFIER ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$ = new ClassVarInfo($2, new QoreVarInfo(loc));
        }
        | TOK_STATIC uncqtypedef IDENTIFIER '=' exp ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line);
            $$ = new ClassVarInfo($3, new QoreVarInfo(loc, ParserTypeStruct::getTypeInfo($2), ParserTypeStruct::getParseTypeInfo($2), $5));
            //printd(5, "new QoreVarInfo = %p (empty: %d)\n", $$->u.varInfo, $$->u.varInfo->empty());
            delete $2;
        }
        | TOK_STATIC IDENTIFIER '=' exp ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            $$ = new ClassVarInfo($2, new QoreVarInfo(loc, nullptr, nullptr, $4));
        }
        | TOK_STATIC uncqtypedef IDENTIFIER '(' myexp ')' ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @6.last_line);
            const QoreTypeInfo* t = ParserTypeStruct::getTypeInfo($2);
            AbstractQoreNode* new_call = nullptr;
            QoreParseTypeInfo* pti = ParserTypeStruct::getParseTypeInfo($2);

            if (t)
                parse_error(*loc, "cannot call constructor for non-class base type '%s'", QoreTypeInfo::getName(t));
            else {
                const QoreProgramLocation* argsloc = qore_program_private::get(*getProgram())->getLocation(@5.first_line, @5.last_line);
                if (pti->subtypes.size())
                    new_call = new ParseNewComplexTypeNode(loc, new QoreParseTypeInfo(*pti), make_args(argsloc, $5));
                else
                    new_call = new ScopedObjectCallNode(loc, new NamedScope(strdup(pti->cscope->ostr)), make_args(argsloc, $5));
            }

            $$ = new ClassVarInfo($3, new QoreVarInfo(loc, t, pti, new_call));

            delete $2;
        }
        ;

member2:
        qtypedef SELF_REF ';' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @2.last_line);
            $$ = new MemberInfo($2, new QoreMemberInfo(loc, ParserTypeStruct::getTypeInfo($1), ParserTypeStruct::getParseTypeInfo($1)));
            delete $1;
            if (parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "reference to '$.%s' with the '$.' prefix is not allowed when parse option 'allow-bare-refs' is set", $2);
        }
        | qtypedef SELF_REF '=' exp ';' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @4.last_line);
            $$ = new MemberInfo($2, new QoreMemberInfo(loc, ParserTypeStruct::getTypeInfo($1), ParserTypeStruct::getParseTypeInfo($1), $4));
            delete $1;
            if (parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "reference to '$.%s' with the '$.' prefix is not allowed when parse option 'allow-bare-refs' is set", $2);
        }
        | qtypedef SELF_REF '(' myexp ')' ';' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @5.last_line);
            const QoreTypeInfo* t = ParserTypeStruct::getTypeInfo($1);
            AbstractQoreNode* new_call = nullptr;
            QoreParseTypeInfo* pti = $1 ? ParserTypeStruct::getParseTypeInfo($1) : nullptr;

            if (!$1)
                parse_error(*loc, "cannot call constructor without declaring the class");
            else if (t)
                parse_error(*loc, "cannot call constructor for non-class base type '%s'", QoreTypeInfo::getName(t));
            else {
                const QoreProgramLocation* argsloc = qore_program_private::get(*getProgram())->getLocation(@4.first_line, @4.last_line);
                if (pti && pti->subtypes.size())
                    new_call = new ParseNewComplexTypeNode(loc, new QoreParseTypeInfo(*pti), make_args(argsloc, $4));
                else
                    new_call = new ScopedObjectCallNode(loc, new NamedScope(strdup(pti->cscope->ostr)), make_args(argsloc, $4));
            }

            $$ = new MemberInfo($2, new QoreMemberInfo(loc, t, pti, new_call));

            delete $1;
            if (parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "reference to '$.%s' with the '$.' prefix is not allowed when parse option 'allow-bare-refs' is set", $2);
        }
        | uncqtypedef IDENTIFIER ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$ = new MemberInfo($2, new QoreMemberInfo(loc, ParserTypeStruct::getTypeInfo($1), ParserTypeStruct::getParseTypeInfo($1)));
            delete $1;
            if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "member '%s' declared without '$.' prefix, but parse option 'allow-bare-refs' is not set", $2);
        }
        | uncqtypedef IDENTIFIER '=' exp ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            $$ = new MemberInfo($2, new QoreMemberInfo(loc, ParserTypeStruct::getTypeInfo($1), ParserTypeStruct::getParseTypeInfo($1), $4));
            delete $1;
            if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "member '%s' declared without '$.' prefix, but parse option 'allow-bare-refs' is not set", $2);
        }
        | uncqtypedef IDENTIFIER '(' myexp ')' ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line);
            const QoreTypeInfo* t = ParserTypeStruct::getTypeInfo($1);
            AbstractQoreNode* new_call = nullptr;
            QoreParseTypeInfo* pti = ParserTypeStruct::getParseTypeInfo($1);

            if (!$1)
                parse_error(*loc, "cannot call constructor without declaring the class");
            else if (t)
                parse_error(*loc, "cannot call constructor for non-class base type '%s'", QoreTypeInfo::getName(t));
            else {
                const QoreProgramLocation* argsloc = qore_program_private::get(*getProgram())->getLocation(@4.first_line, @4.last_line);
                if (pti->subtypes.size())
                    new_call = new ParseNewComplexTypeNode(loc, new QoreParseTypeInfo(*pti), make_args(argsloc, $4));
                else
                    new_call = new ScopedObjectCallNode(loc, new NamedScope(strdup(pti->cscope->ostr)), make_args(argsloc, $4));
            }

            $$ = new MemberInfo($2, new QoreMemberInfo(loc, t, pti, new_call));

            delete $1;
            if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "member '%s' declared without '$.' prefix, but parse option 'allow-bare-refs' is not set", $2);
        }
        | IDENTIFIER ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$ = new MemberInfo($1, new QoreMemberInfo(loc));
            if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "member '%s' declared without '$.' prefix, but parse option 'allow-bare-refs' is not set", $1);
        }
        | IDENTIFIER '=' exp ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
            $$ = new MemberInfo($1, new QoreMemberInfo(loc, nullptr, nullptr, $3));
            if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "member '%s' declared without '$.' prefix, but parse option 'allow-bare-refs' is not set", $1);
        }
        | classvardecl {
            $$ = $1;
        }
        | unscoped_const_decl {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            if ($1->pub)
                parse_error(*loc, "cannot change class constant scope for constant '%s' within a public or private declaration block", $1->name.ostr);
            $$ = new MemberInfo(loc, $1->name.takeName(), $1->value);
            delete $1;
        }
        ;

transient_member2:
        TOK_TRANSIENT member2 {
            $$ = $2;
            $$->setTransient();
        }
        ;

member:
        SELF_REF {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$ = new MemberInfo($1, new QoreMemberInfo(loc));
            if (parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "reference to '$.%s' with the '$.' prefix is not allowed when parse option 'allow-bare-refs' is set", $1);
        }
        | SELF_REF SCOPED_REF {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$ = new MemberInfo($1, new QoreMemberInfo(loc, nullptr, new QoreParseTypeInfo($2)));
            if (parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "reference to '$.%s' with the '$.' prefix is not allowed when parse option 'allow-bare-refs' is set", $1);
        }
        ;

transient_member:
        TOK_TRANSIENT member {
            $$ = $2;
            $$->setTransient();
        }
        ;

member_list:
        member {
           $$ = new MemberList($1);
        }
        | transient_member {
           $$ = new MemberList($1);
        }
        | member_list ',' member {
           $1->add($3);
           $$ = $1;
        }
        | member_list ',' transient_member {
           $1->add($3);
           $$ = $1;
        }
        ;

member_list2:
        member2 {
           $$ = new MemberList($1);
        }
        | transient_member2 {
           $$ = new MemberList($1);
        }
        | member_list2 member2 {
           $1->add($2);
           $$ = $1;
        }
        | member_list2 transient_member2 {
           $1->add($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 {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @7.last_line);
            if ($1 & OFM_ABSTRACT) {
                if ($7)
                    parse_error(*loc, "'abstract' can only be used with no implementation (remove the body and terminate the declaration with ';' or remove the 'abstract')");
                else
                    parse_error(*loc, "'abstract' can only be used with no implementation (terminate the declaration with ';' instead of '{}')");
            }
            check_method(loc, $2, $5, $6);
            $$ = new MethodNode($2, new_method_variant(loc, $2, $1, @3.first_line, @3.last_line, $3, $5, $6, $7), $1 & OFM_STATIC);
        }
        | nsc_modifiers uncqtypedef ident_openparen myexp ')' base_constructor_list block {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @7.last_line);
            if ($1 & OFM_ABSTRACT) {
                if ($7)
                    parse_error(*loc, "'abstract' can only be used with no implementation (remove the body and terminate the declaration with ';' or remove the 'abstract')");
                else
                    parse_error(*loc, "'abstract' can only be used with no implementation (terminate the declaration with ';' instead of '{}')");
            }
            RetTypeInfo* rt = new RetTypeInfo(ParserTypeStruct::getParseTypeInfo($2), ParserTypeStruct::getTypeInfo($2));
            delete $2;

            check_method(loc, $3, $6, rt);

            $$ = new MethodNode($3, new_method_variant(loc, $3, $1, @4.first_line, @4.last_line, $4, $6, rt, $7), $1 & OFM_STATIC);
        }
        | nsc_modifiers ident_openparen myexp ')' base_constructor_list return_value ';' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @6.last_line);
            if (!($1 & OFM_ABSTRACT))
                parse_error(*loc, "non-abstract method declared without any body; either declare the method 'abstract' or declare an empty method body with '{}'");
            check_method(loc, $2, $5, $6);
            $$ = new MethodNode($2, new_method_variant(loc, $2, $1, @3.first_line, @3.last_line, $3, $5, $6, 0), $1 & OFM_STATIC);
        }
        | nsc_modifiers uncqtypedef ident_openparen myexp ')' base_constructor_list ';' {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @6.last_line);
            if (!($1 & OFM_ABSTRACT))
                parse_error(*loc, "non-abstract method declared without any body; either declare the method 'abstract' or declare an empty method body with '{}'");
            RetTypeInfo* rt = new RetTypeInfo(ParserTypeStruct::getParseTypeInfo($2), ParserTypeStruct::getTypeInfo($2));
            delete $2;

            check_method(loc, $3, $6, rt);

            $$ = new MethodNode($3, new_method_variant(loc, $3, $1, @4.first_line, @4.last_line, $4, $6, rt, 0), $1 & OFM_STATIC);
        }
        ;

outofline_methoddef:
        method_modifiers SCOPED_REF '(' myexp ')' base_constructor_list return_value block {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @8.last_line);
            if ($1 & OFM_ABSTRACT) {
                if ($8)
                    parse_error(*loc, "'abstract' can only be used with no implementation (remove the body and terminate the declaration with ';' or remove the 'abstract')");
                else
                    parse_error(*loc, "'abstract' can only be used with no implementation (terminate the declaration with ';' instead of '{}')");
            }

            $$ = new MethodDef(loc, $1, $2, @4.first_line, @4.last_line, $4, $6, $7, $8);
        }
        | method_modifiers uncqtypedef SCOPED_REF '(' myexp ')' base_constructor_list block {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @8.last_line);
            if ($1 & OFM_ABSTRACT) {
                if ($8)
                    parse_error(*loc, "'abstract' can only be used with no implementation (remove the body and terminate the declaration with ';' or remove the 'abstract')");
                else
                    parse_error(*loc, "'abstract' can only be used with no implementation (terminate the declaration with ';' instead of '{}')");
            }
            RetTypeInfo* rt = new RetTypeInfo(ParserTypeStruct::getParseTypeInfo($2), ParserTypeStruct::getTypeInfo($2));
            delete $2;

            $$ = new MethodDef(loc, $1, $3, @5.first_line, @5.last_line, $5, $7, rt, $8);
        }
        | method_modifiers SCOPED_REF '(' myexp ')' base_constructor_list return_value ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @7.last_line);
            if (!($1 & OFM_ABSTRACT))
                parse_error(*loc, "non-abstract method declared without any body; either declare the method 'abstract' or declare an empty method body with '{}'");
            $$ = new MethodDef(loc, $1, $2, @4.first_line, @4.last_line, $4, $6, $7, 0);
        }
        | method_modifiers uncqtypedef SCOPED_REF '(' myexp ')' base_constructor_list ';' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @7.last_line);
            if (!($1 & OFM_ABSTRACT))
                parse_error(*loc, "non-abstract method declared without any body; either declare the method 'abstract' or declare an empty method body with '{}'");
            RetTypeInfo* rt = new RetTypeInfo(ParserTypeStruct::getParseTypeInfo($2), ParserTypeStruct::getTypeInfo($2));
            delete $2;

            $$ = new MethodDef(loc, $1, $3, @5.first_line, @5.last_line, $5, $7, rt, 0);
        }
        | SCOPED_REF '(' myexp ')' base_constructor_list return_value block {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @7.last_line);
            $$ = new MethodDef(loc, 0, $1, @3.first_line, @3.last_line, $3, $5, $6, $7);
        }
        | uncqtypedef SCOPED_REF '(' myexp ')' base_constructor_list block {
            RetTypeInfo* rt = new RetTypeInfo(ParserTypeStruct::getParseTypeInfo($1), ParserTypeStruct::getTypeInfo($1));
            delete $1;

            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @7.last_line);
            $$ = new MethodDef(loc, 0, $2, @4.first_line, @4.last_line, $4, $6, rt, $7);
        }
        ;

base_constructor_list:
        ':' base_constructors {
           $$ = $2;
        }
        | { // nothing
           $$ = 0;
        }
        ;

base_constructors:
        base_constructor {
           $$ = new BCAList($1);
        }
        | base_constructors ',' base_constructor {
           $1->push_back($3);
           $$ = $1;
        }
        ;

base_constructor:
        IDENTIFIER '(' myexp ')' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            $$ = new BCANode($1, make_args(loc, $3), loc);
        }
        | SCOPED_REF '(' myexp ')' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            $$ = new BCANode(new NamedScope($1), make_args(loc, $3), loc);
        }
        ;

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

method_modifiers:
        method_modifier { $$ = $1; }
        | method_modifiers method_modifier {
            if (($1 | $2) == $1) {
                const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
                parse_error(*loc, "modifier given twice");
            }
            $$ = $1 | $2;
        }
        ;

method_modifier:
        TOK_PRIVATE { $$ = OFM_PRIVATE; }
        | TOK_PRIVATEHIERARCHY { $$ = OFM_PRIVATE; }
        | TOK_PRIVATEINTERNAL { $$ = OFM_INTERNAL; }
        | TOK_STATIC { $$ = OFM_STATIC; }
        | TOK_SYNCHRONIZED { $$ = OFM_SYNCED; }
        | TOK_DEPRECATED { $$ = OFM_DEPRECATED; }
        | TOK_PUBLIC { $$ = OFM_PUBLIC; }
        | TOK_FINAL { $$ = OFM_FINAL; }
        | TOK_ABSTRACT { $$ = OFM_ABSTRACT; }
        ;

return_value:
        TOK_RETURNS qtypedef {
            // see if the deprecated "returns" keyword can be used
            if (!parse_check_parse_option(PO_ALLOW_RETURNS)) {
                const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
                parse_error(*loc, "illegal use of the deprecated \"returns\" keyword (enable by setting parse option PO_ALLOW_RETURNS)");
            }

            if (!$2) {
                const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
                parse_error(*loc, "missing type declaration after 'returns'");
                $$ = 0;
            } else {
                $$ = new RetTypeInfo(ParserTypeStruct::getParseTypeInfo($2), ParserTypeStruct::getTypeInfo($2));
                delete $2;
            }
        }
        | /* nothing */
        {
            $$ = 0;
        }
        ;

sub_def:
        TOK_SUB ident_openparen myexp ')' return_value block {
            int ar = get_pop_argv_ref();
            int64 flags = ar > 0 ? QCF_USES_EXTRA_ARGS : QCF_NO_FLAGS;
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @6.last_line);
            $$ = new ParseUserFunction(loc, $2, new UserFunctionVariant($6, @3.first_line, @3.last_line, $3, $5, false, flags));
        }
        | uncqtypedef TOK_SUB ident_openparen myexp ')' block {
            int ar = get_pop_argv_ref();
            int64 flags = ar > 0 ? QCF_USES_EXTRA_ARGS : QCF_NO_FLAGS;

            RetTypeInfo* rt = new RetTypeInfo(ParserTypeStruct::getParseTypeInfo($1), ParserTypeStruct::getTypeInfo($1));
            delete $1;

            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @6.last_line);
            $$ = new ParseUserFunction(loc, $3, new UserFunctionVariant($6, @4.first_line, @4.last_line, $4, rt, false, flags));
        }
        | method_modifiers TOK_SUB ident_openparen myexp ')' return_value block {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @7.last_line);
            check_funcmod(loc, $1);
            int ar = get_pop_argv_ref();
            int64 flags = ar > 0 ? QCF_USES_EXTRA_ARGS : QCF_NO_FLAGS;
            if ($1 & OFM_DEPRECATED)
                flags |= QCF_DEPRECATED;

            $$ = new ParseUserFunction(loc, $3, new UserFunctionVariant($7, @4.first_line, @4.last_line, $4, $6, $1 & OFM_SYNCED, flags));
            if ($1 & OFM_PUBLIC)
                $$->setPublic();
        }
        | method_modifiers uncqtypedef TOK_SUB ident_openparen myexp ')' block {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @7.last_line);
            check_funcmod(loc, $1);
            int ar = get_pop_argv_ref();
            int64 flags = ar > 0 ? QCF_USES_EXTRA_ARGS : QCF_NO_FLAGS;
            if ($1 & OFM_DEPRECATED)
                flags |= QCF_DEPRECATED;

            RetTypeInfo* rt = new RetTypeInfo(ParserTypeStruct::getParseTypeInfo($2), ParserTypeStruct::getTypeInfo($2));
            delete $2;

            $$ = new ParseUserFunction(loc, $4, new UserFunctionVariant($7, @5.first_line, @5.last_line, $5, rt, $1 & OFM_SYNCED, flags));
            if ($1 & OFM_PUBLIC)
                $$->setPublic();
        }
        ;

scoped_sub_def:
        TOK_SUB SCOPED_REF '(' myexp ')' return_value block {
            int ar = get_pop_argv_ref();
            int64 flags = ar > 0 ? QCF_USES_EXTRA_ARGS : QCF_NO_FLAGS;
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @7.last_line);
            $$ = new ParseScopedUserFunction(loc, $2, new UserFunctionVariant($7, @4.first_line, @4.last_line, $4, $6, false, flags));
        }
        | uncqtypedef TOK_SUB SCOPED_REF '(' myexp ')' block {
            int ar = get_pop_argv_ref();
            int64 flags = ar > 0 ? QCF_USES_EXTRA_ARGS : QCF_NO_FLAGS;

            RetTypeInfo* rt = new RetTypeInfo(ParserTypeStruct::getParseTypeInfo($1), ParserTypeStruct::getTypeInfo($1));
            delete $1;

            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @7.last_line);
            $$ = new ParseScopedUserFunction(loc, $3, new UserFunctionVariant($7, @5.first_line, @5.last_line, $5, rt, false, flags));
        }
        | method_modifiers TOK_SUB SCOPED_REF '(' myexp ')' return_value block {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @8.last_line);
            check_funcmod(loc, $1);
            int ar = get_pop_argv_ref();
            int64 flags = ar > 0 ? QCF_USES_EXTRA_ARGS : QCF_NO_FLAGS;
            if ($1 & OFM_DEPRECATED)
                flags |= QCF_DEPRECATED;

            $$ = new ParseScopedUserFunction(loc, $3, new UserFunctionVariant($8, @5.first_line, @5.last_line, $5, $7, $1 & OFM_SYNCED, flags));
            if ($1 & OFM_PUBLIC)
                $$->setPublic();
        }
        | method_modifiers uncqtypedef TOK_SUB SCOPED_REF '(' myexp ')' block {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @8.last_line);
            check_funcmod(loc, $1);
            int ar = get_pop_argv_ref();
            int64 flags = ar > 0 ? QCF_USES_EXTRA_ARGS : QCF_NO_FLAGS;
            if ($1 & OFM_DEPRECATED)
                flags |= QCF_DEPRECATED;

            RetTypeInfo* rt = new RetTypeInfo(ParserTypeStruct::getParseTypeInfo($2), ParserTypeStruct::getTypeInfo($2));
            delete $2;

            $$ = new ParseScopedUserFunction(loc, $4, new UserFunctionVariant($8, @6.first_line, @6.last_line, $6, rt, $1 & OFM_SYNCED, flags));
            if ($1 & OFM_PUBLIC)
                $$->setPublic();
        }
        ;

list:
        exp ',' exp {
            const QoreProgramLocation* loc1 = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            const QoreProgramLocation* loc3 = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$ = splice_expressions($1, $3, loc1, loc3);
        }
        | exp ',' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            QoreParseListNode* l;
            if ($1.getType() == NT_PARSE_LIST) {
                l = $1.get<QoreParseListNode>();
                if (l->isFinalized()) {
                    QoreParseListNode* nl = new QoreParseListNode(loc);
                    nl->add(l, loc);
                    l = nl;
                }
            } else {
                l = new QoreParseListNode(loc);
                l->add($1, loc);
            }
            $$ = l;
        }
        ;

list_n:
        exp_n ',' exp {
            const QoreProgramLocation* loc1 = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            const QoreProgramLocation* loc3 = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$ = splice_expressions($1, $3, loc1, loc3);
        }
        | exp_n ',' {
            QoreParseListNode* l;
            if ($1.getType() == NT_PARSE_LIST)
                l = $1.get<QoreParseListNode>();
            else {
                const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
                l = new QoreParseListNode(loc);
                l->add($1, loc);
            }
            $$ = l;
        }
        ;

hash:
        hash_element {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$ = new QoreParseHashNode(loc);
            $1->addDelete($$);
        }
        | hash ',' hash_element {
            $3->addDelete($1);
            $$ = $1;
        }
        | hash ','
        { /* empty ',' on end of hash */ $$ = $1; }
        ;

hash_element:
        exp ':' exp
        { $$ = new HashElement($1, $3, @1.first_line, @3.last_line); }
        ;

alt_hash:
        '{' hash '}' {
            // mark for the curly-bracket version of a literal parse hash expression for the hash version of the map operator
            $2->setCurly();
            $2->finalizeBlock(@1.first_line, @3.last_line);

            $$ = $2;
        }
        ;

immediate_typed_hash:
        HASHDECL_IDENTIFIER_OPENCURLY hash '}' {
            // mark for the curly-bracket version of a literal parse hash expression for the hash version of the map operator
            $2->setCurly();

            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
            QoreStringMaker fake_cast("hash<%s>", $1);
            free($1);
            $$ = new QoreParseCastOperatorNode(loc, ParserTypeStruct::getParseType(loc, fake_cast.giveBuffer(), false), $2);
        }
        | HASHDECL_IDENTIFIER_OPENCURLY '}' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            QoreStringMaker fake_cast("hash<%s>", $1);
            free($1);
            $$ = new QoreParseCastOperatorNode(loc, ParserTypeStruct::getParseType(loc, fake_cast.giveBuffer(), false), new QoreParseHashNode(loc, true));
        }
        ;

exp_c:
        scalar
        { $$.set($1); }
        | BINARY
        { $$.set($1); }
        | '(' hash ')'
        { $$.set($2); }
        | SCOPED_REF
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$.set(new ScopedRefNode(loc, $1));
        }
        | qtypedef VAR_REF {
            fix_start_location($1, @$, @2);
            if (!$1 && !strcmp($2, "argv"))
                inc_argv_ref();

            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @2.last_line);
            if ($1)
                $$.set(new VarRefDeclNode(loc, $2, get_var_type(), ParserTypeStruct::getTypeInfo($1), ParserTypeStruct::getParseTypeInfo($1)));
            else
                $$.set(new VarRefNode(loc, $2, get_var_type()));

            delete $1;
            if (parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "reference to variable '%s' with the '$' sign is not allowed when parse option 'allow-bare-refs' is set", $2);
        }
        | qtypedef SCOPED_VREF {
            fix_start_location($1, @$, @2);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation($1 ? @1.first_line : @2.first_line, @2.last_line);
            if ($1)
                $$.set(new VarRefDeclNode(loc, $2, get_var_type(), ParserTypeStruct::getTypeInfo($1), ParserTypeStruct::getParseTypeInfo($1)));
            else
                $$.set(new VarRefNode(loc, $2, get_var_type()));

            delete $1;
            if (parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "reference to variable '%s' with the '$' sign is not allowed when parse option 'allow-bare-refs' is set", $2);
        }
        | uncqtypedef IDENTIFIER {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new VarRefDeclNode(loc, $2, get_var_type(), ParserTypeStruct::getTypeInfo($1), ParserTypeStruct::getParseTypeInfo($1)));
            delete $1;
            if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "local variable '%s' declared without '$' prefix, but parse option 'allow-bare-refs' is not set", $2);
        }
        | TOK_MY uncqtypedef IDENTIFIER {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
            VarRefDeclNode* v = new VarRefDeclNode(loc, $3, VT_LOCAL, ParserTypeStruct::getTypeInfo($2), ParserTypeStruct::getParseTypeInfo($2));
            v->setExplicitScope();
            $$.set(v);
            delete $2;
            if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "local variable '%s' declared without '$' prefix, but parse option 'allow-bare-refs' is not set", $3);
        }
        | TOK_MY IDENTIFIER {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            // see if types are required
            if (parse_check_parse_option(PO_REQUIRE_TYPES))
                parse_error(*loc, "local variable '%s' declared without type information, but parse options require all declarations to have type information", $2);
            VarRefNode* v = new VarRefNode(loc, $2, VT_LOCAL);
            v->setExplicitScope();
            $$.set(v);
            if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "local variable '%s' declared without '$' prefix, but parse option 'allow-bare-refs' is not set", $2);
        }
        | TOK_MY qtypedef VAR_REF {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
            VarRefNode* v;
            if ($2) {
                v = new VarRefDeclNode(loc, $3, VT_LOCAL, ParserTypeStruct::getTypeInfo($2), ParserTypeStruct::getParseTypeInfo($2));
                delete $2;
            } else {
                // see if types are required
                if (parse_check_parse_option(PO_REQUIRE_TYPES))
                    parse_error(*loc, "local variable '%s' declared without type information, but parse options require all declarations to have type information", $3);
                v = new VarRefNode(loc, $3, VT_LOCAL);
            }
            v->setExplicitScope();
            $$.set(v);
            if (parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "reference to variable '$%s' with the '$' sign is not allowed when parse option 'allow-bare-refs' is set", $3);
        }
        | TOK_MY '(' list ')' {
            $3->setVariableList();
            QoreParseListNode::nvec_t& vl = $3->getValues();
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            for (unsigned i = 0; i < vl.size(); ++i) {
                QoreValue& n = vl[i];
                qore_type_t t = n.getType();
                if (t == NT_BAREWORD) {
                    BarewordNode* b = n.get<BarewordNode>();
                    if (parse_check_parse_option(PO_REQUIRE_TYPES))
                        parse_error(*loc, "local variable '%s' declared without type information, but parse options require all declarations to have type information", b->str);
                    if (!parse_check_parse_option(PO_ALLOW_BARE_REFS))
                        parse_error(*loc, "local variable '%s' in local variable declaration list declared without '$' prefix, but parse option 'allow-bare-refs' is not set", b->str);
                    VarRefNode* v = new VarRefNode(loc, b->takeString(), VT_LOCAL);
                    v->setExplicitScope();
                    b->deref();
                    n = v;
                } else if (t == NT_VARREF) {
                    VarRefNode* v = n.get<VarRefNode>();
                    // see if types are required
                    if (parse_check_parse_option(PO_REQUIRE_TYPES) && !v->isDecl())
                        parse_error(*loc, "local variable '%s' declared without type information, but parse options require all declarations to have type information", v->getName());
                    if (v->explicitScope()) {
                        if (v->getType() == VT_LOCAL)
                            parse_error(*loc, "illegal use of 'my %s' in local variable declaration list", v->getName());
                        else if (v->getType() == VT_GLOBAL)
                            parse_error(*loc, "illegal use of 'our %s' in local variable declaration list", v->getName());
                    } else
                        v->makeLocal();
                    v->setExplicitScope();
                } else
                    parse_error(*loc, "element %d in list following 'my' is not a variable reference (%s)", i, n.getTypeName());
            }
            $$.set($3);
        }
        | gvardecl {
            $$.set($1->makeVar());
        }
        | IDENTIFIER
        {
            if (parse_check_parse_option(PO_ASSUME_LOCAL & PO_ALLOW_BARE_REFS) && !strcmp($1, "argv"))
                inc_argv_ref();

            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$.set(new BarewordNode(loc, $1));
        }
        | CONTEXT_REF
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$.set(new ContextrefNode(loc, $1));
        }
        | TOK_CONTEXT_ROW
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$.set(new ContextRowNode(loc));
        }
        | COMPLEX_CONTEXT_REF
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$.set(new ComplexContextrefNode(loc, $1));
        }
        | TOK_FIND exp TOK_IN exp TOK_WHERE '(' exp ')'
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @8.last_line);
            $$.set(new FindNode(loc, $2, $4, $7));
        }
        | TOK_EXISTS exp
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QoreExistsOperatorNode(loc, $2));
        }
        | TOK_ELEMENTS exp
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QoreElementsOperatorNode(loc, $2));
        }
        | TOK_KEYS exp {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QoreKeysOperatorNode(loc, $2));
        }
        | TOK_UNSHIFT exp {  // unshift list, element
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            QoreParseListNode* l = $2.getType() == NT_PARSE_LIST ? $2.get<QoreParseListNode>() : nullptr;
            if (!l || l->size() != 2) {
                parse_error(*loc, "invalid arguments to unshift, expected: lvalue, expression (%s)", $2.getTypeName());
                $2.discard(nullptr);
                $$.set(nullptr);
            }
            else {
                QoreValue lv = l->shift();
                $$.set(new QoreUnshiftOperatorNode(loc, lv, l->shift()));
                $2.discard(nullptr);
            }
        }
        | TOK_SHIFT exp {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QoreShiftOperatorNode(loc, $2));
        }
        | TOK_PUSH exp {  // push lvalue-list, element
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            QoreParseListNode* l = $2.getType() == NT_PARSE_LIST ? $2.get<QoreParseListNode>() : nullptr;
            if (!l || l->size() != 2) {
                parse_error(*loc, "invalid arguments to push, expected: lvalue, expression (%s)", $2.getTypeName());
                $2.discard(nullptr);
                $$.set(nullptr);
            }
            else {
                QoreValue lv = l->shift();
                $$.set(new QorePushOperatorNode(loc, lv, l->shift()));
                $2.discard(nullptr);
            }
        }
        | TOK_POP exp {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QorePopOperatorNode(loc, $2));
        }
        | TOK_CHOMP exp {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QoreChompOperatorNode(loc, $2));
        }
        | TOK_TRIM exp {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QoreTrimOperatorNode(loc, $2));
        }
        | TOK_SPLICE exp {  // splice lvalue-list, offset, [length, list]
            QoreParseListNode* l = $2.getType() == NT_PARSE_LIST ? $2.get<QoreParseListNode>() : nullptr;
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            if (!l) {
                parse_error(*loc, "invalid arguments to splice, expected: lvalue, offset exp[, length exp[, list exp]] (got %s)", $2.getTypeName());
                $2.discard(nullptr);
                $$.set(nullptr);
            } else if (l->size() < 2) {
                parse_error(*loc, "too few arguments to splice, expected at least 2 arguments as follows: lvalue, offset exp[, length exp[, list exp]] (got %d arguments)", l->size());
                $2.discard(nullptr);
                $$.set(nullptr);
            } else if (l->size() > 4) {
                parse_error(*loc, "too many arguments to splice, expected maximum 4 arguments as follows: lvalue, offset exp[, length exp[, list exp]] (got %d arguments)", l->size());
                $2.discard(nullptr);
                $$.set(nullptr);
            } else {
                QoreValue lv = l->shift();
                QoreValue offset = l->shift();
                QoreValue length = l->shift();
                $$.set(new QoreSpliceOperatorNode(loc, lv, offset, length, l->shift()));
                discard(l, 0);
            }
        }
        | TOK_EXTRACT exp {  // extract lvalue-list, offset, [length, list]
            QoreParseListNode* l = $2.getType() == NT_PARSE_LIST ? $2.get<QoreParseListNode>() : nullptr;
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            if (!l) {
                parse_error(*loc, "invalid arguments to extract, expected: lvalue, offset exp[, length exp[, list exp]] (got %s)", $2.getTypeName());
                $2.discard(nullptr);
                $$.set(nullptr);
            }
            else if (l->size() < 2) {
                parse_error(*loc, "too few arguments to extract, expected at least 2 arguments as follows: lvalue, offset exp[, length exp[, list exp]] (got %d arguments)", l->size());
                $2.discard(nullptr);
                $$.set(nullptr);
            }
            else if (l->size() > 4) {
                parse_error(*loc, "too many arguments to extract, expected maximum 4 arguments as follows: lvalue, offset exp[, length exp[, list exp]] (got %d arguments)", l->size());
                $2.discard(nullptr);
                $$.set(nullptr);
            }
            else {
                QoreValue lv = l->shift();
                QoreValue offset = l->shift();
                QoreValue length = l->shift();
                $$.set(new QoreExtractOperatorNode(loc, lv, offset, length, l->shift()));
                discard(l, 0);
            }
        }
        | TOK_MAP exp {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            pop_ignore_numeric_argv_ref();
            QoreParseListNode* l = $2.getType() == NT_PARSE_LIST ? $2.get<QoreParseListNode>() : nullptr;
            int len = l ? (int)l->size() : 0;
            if (!l || len < 2 || len > 3) {
                parse_error(*loc, "invalid arguments to map operator, expected: 2 or 3 element list (code expression, list argument, [select expression]), got: '%s'", $2.getTypeName());
                $2.discard(nullptr);
                $$.set(nullptr);
            } else {
                // get first expression from list
                QoreValue exp = l->shift();
                qore_type_t t = exp.getType();
                $$.set(nullptr);
                bool is_curly = false;
                if (t == NT_PARSE_HASH) {
                    QoreParseHashNode* phn = exp.get<QoreParseHashNode>();
                    if (phn->isCurly()) {
                        is_curly = true;
                        $$.set(parse_hash_map(loc, phn, l));
                    }
                }

                if (!$$ && !is_curly) {
                    $$.set(parse_map(loc, exp, l));
                }
            }
        }
        | TOK_FOLDR exp {
            pop_ignore_numeric_argv_ref();
            QoreParseListNode* l = $2.getType() == NT_PARSE_LIST ? $2.get<QoreParseListNode>() : nullptr;
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            if (!l || l->size() != 2) {
                parse_error(*loc, "invalid arguments to foldr operator, expected: 2-element list expected: 2-element list (fold expression and list expression), got: '%s'", $2.getTypeName());
                $2.discard(nullptr);
                $$.set(nullptr);
            }
            else {
                QoreValue code_exp = l->shift();
                QoreValue arg = l->shift();
                $$.set(new QoreFoldrOperatorNode(loc, code_exp, arg));
                $2.discard(nullptr);
            }
        }
        | TOK_FOLDL exp {
            pop_ignore_numeric_argv_ref();
            QoreParseListNode* l = $2.getType() == NT_PARSE_LIST ? $2.get<QoreParseListNode>() : nullptr;
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            if (!l || l->size() != 2) {
                parse_error(*loc, "invalid arguments to foldl operator, expected: 2-element list (fold expression and list expression), got: '%s'", $2.getTypeName());
                $2.discard(nullptr);
                $$.set(nullptr);
            }
            else {
                QoreValue code_exp = l->shift();
                QoreValue arg = l->shift();
                $$.set(new QoreFoldlOperatorNode(loc, code_exp, arg));
                $2.discard(nullptr);
            }
        }
        | TOK_SELECT exp {
            pop_ignore_numeric_argv_ref();
            QoreParseListNode* l = $2.getType() == NT_PARSE_LIST ? $2.get<QoreParseListNode>() : nullptr;
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            if (!l || l->size() != 2) {
                parse_error(*loc, "invalid arguments to select operator, expected: 2-element list (list expression and select expression) got: '%s'", $2.getTypeName());
                $2.discard(nullptr);
                $$.set(nullptr);
            }
            else {
                QoreValue arg = l->shift();
                QoreValue select_exp = l->shift();
                $$.set(new QoreSelectOperatorNode(loc, arg, select_exp));
                $2.discard(nullptr);
            }
        }
        | P_INCREMENT exp {  // pre-increment
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QorePreIncrementOperatorNode(loc, $2));
        }
        | P_DECREMENT exp {  // pre-decrement
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QorePreDecrementOperatorNode(loc, $2));
        }
        | BASE_CLASS_CALL '(' myexp ')' {
            printd(5, "parsing in-object base class method call %s()\n", $1->ostr);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            if (!strcmp($1->getIdentifier(), "copy"))
                parse_error(*loc, "illegal call to base class copy method '%s'", $1->ostr);

            $$.set(new SelfFunctionCallNode(loc, $1, make_args(qore_program_private::get(*getProgram())->getLocation(@3.first_line, @3.last_line), $3)));
        }
        | KW_IDENTIFIER_OPENPAREN myexp ')' {
            printd(5, "parsing call %s()\n", $1);
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
            const QoreProgramLocation* argsloc = qore_program_private::get(*getProgram())->getLocation(@2.first_line, @2.last_line);
            $$.set(new FunctionCallNode(loc, $1, make_args(argsloc, $2)));
        }
        | SELF_REF
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$.set(new SelfVarrefNode(loc, $1));
            if (parse_check_parse_option(PO_ALLOW_BARE_REFS))
                parse_error(*loc, "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
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QoreUnaryMinusOperatorNode(loc, $2));
        }
        | '+' exp %prec UPLUS
        {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QoreUnaryPlusOperatorNode(loc, $2));
        }
        | '~' exp {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QoreBinaryNotOperatorNode(loc, $2));
        }
        | '!' exp {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QoreLogicalNotOperatorNode(loc, $2));
        }
        | '\\' exp {
            qore_type_t t = $2.getType();
            //printd(5, "backslash exp line %d, type %s\n", @2.first_line, $2.getTypeName());

            if (t == NT_FUNCTION_CALL || t == NT_PROGRAM_FUNC_CALL) {
                $$.set($2.get<FunctionCallNode>()->makeReferenceNodeAndDeref());
            }
            else if (t == NT_SELF_CALL) {
                $$.set($2.get<SelfFunctionCallNode>()->makeReferenceNodeAndDeref());
            }
            else if (t == NT_STATIC_METHOD_CALL) {
                $$.set($2.get<StaticMethodCallNode>()->makeReferenceNodeAndDeref());
            }
            else {
                bool make_ref = true;

                if (t == NT_OPERATOR) {
                    QoreDotEvalOperatorNode* deon = dynamic_cast<QoreDotEvalOperatorNode*>($2.getInternalNode());
                    if (deon) {
                        $$.set(deon->makeCallReference());
                        make_ref = false;
                    }
                }

                if (make_ref) {
                    const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
                    //printd(5, "type=%s\n", $2.getTypeName());
                    $$.set(new ParseReferenceNode(loc, $2));
                }
            }
        }
        | TOK_NEW IDENTIFIER '(' myexp ')' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line);
            const QoreProgramLocation* argsloc = qore_program_private::get(*getProgram())->getLocation(@4.first_line, @4.last_line);
            $$.set(new ScopedObjectCallNode(loc, new NamedScope($2), make_args(argsloc, $4)));
            if (parse_check_parse_option(PO_NO_NEW))
                parse_error(*loc, "illegal use of the 'new' operator (conflicts with parse option PO_NO_NEW)");
        }
        | TOK_NEW SCOPED_REF '(' myexp ')' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line);
            const QoreProgramLocation* argsloc = qore_program_private::get(*getProgram())->getLocation(@4.first_line, @4.last_line);
            $$.set(new ScopedObjectCallNode(loc, new NamedScope($2), make_args(argsloc, $4)));
            if (parse_check_parse_option(PO_NO_NEW))
                parse_error(*loc, "illegal use of the 'new' operator (conflicts with parse option PO_NO_NEW)");
        }
        | TOK_NEW ANGLE_IDENTIFIER '(' myexp ')' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line);
            const QoreProgramLocation* argsloc = qore_program_private::get(*getProgram())->getLocation(@4.first_line, @4.last_line);
            $$.set(new ParseNewComplexTypeNode(loc, ParserTypeStruct::getParseType(loc, $2, false), make_args(argsloc, $4)));
            if (parse_check_parse_option(PO_NO_NEW))
                parse_error(*loc, "illegal use of the 'new' operator (conflicts with parse option PO_NO_NEW)");
        }
        | TOK_BACKGROUND exp {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QoreBackgroundOperatorNode(loc, $2));
            // check to see if the expression is legal
            if (parse_check_parse_option(PO_NO_THREAD_CONTROL))
                parse_error(*loc, "illegal use of 'background' operator (conflicts with parse option PO_NO_THREAD_CONTROL)");
            else if (!value_has_effect_as_root($2)) {
                if (!parse_check_parse_option(PO_ALLOW_STATEMENT_NO_EFFECT)) {
                    parse_error(*loc, "argument to background operator (%s) has no effect", $2.getTypeName());
                }
            } else if (!$2.hasEffect()) {
                parse_error(*loc, "argument to background operator (%s) has no effect", $2.getTypeName());
            }
        }
        | BACKQUOTE {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$.set(new BackquoteNode(loc, $1));
            if (parse_check_parse_option(PO_NO_EXTERNAL_PROCESS))
                parse_error(*loc, "illegal use of backquote operator (conflicts with parse option PO_NO_EXTERNAL_PROCESS)");
        }
        | '(' exp ')' {
            $$.set($2);
            switch ($2.getType()) {
                case NT_PARSE_LIST:
                    $2.get<QoreParseListNode>()->setFinalized();
                    break;
                case NT_FUNCTION_CALL:
                    $2.get<FunctionCallNode>()->setFinalized();
                    break;
                case NT_BAREWORD:
                    $2.get<BarewordNode>()->setFinalized();
                    break;
                case NT_OPERATOR:
                    $2.get<QoreOperatorNode>()->setInParentheses();
                    break;
            }
        }
        | '(' ')' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            QoreParseListNode* l = new QoreParseListNode(loc);
            l->setFinalized();
            $$.set(l);
        }
        | TOK_SUB '(' myexp ')' return_value block {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @6.last_line);
            int ar = get_pop_argv_ref();
            $$.set(new QoreClosureParseNode(loc, new UserClosureFunction($6, @3.first_line, @3.last_line, $3, $5, false, ar > 0 ? QCF_USES_EXTRA_ARGS : QCF_NO_FLAGS)));
        }
        | uncqtypedef TOK_SUB '(' myexp ')' block {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @6.last_line);
            RetTypeInfo* rt = new RetTypeInfo(ParserTypeStruct::getParseTypeInfo($1), ParserTypeStruct::getTypeInfo($1));
            delete $1;

            int ar = get_pop_argv_ref();
            $$.set(new QoreClosureParseNode(loc, new UserClosureFunction($6, @4.first_line, @4.last_line, $4, rt, false, ar > 0 ? QCF_USES_EXTRA_ARGS : QCF_NO_FLAGS)));
        }
        | method_modifiers TOK_SUB '(' myexp ')' return_value block {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @7.last_line);
            check_funcmod(loc, $1);
            int ar = get_pop_argv_ref();
            int64 flags = ar > 0 ? QCF_USES_EXTRA_ARGS : QCF_NO_FLAGS;
            if ($1 & OFM_DEPRECATED)
                flags |= QCF_DEPRECATED;
            $$.set(new QoreClosureParseNode(loc, new UserClosureFunction($7, @4.first_line, @4.last_line, $4, $6, $1 | OFM_SYNCED, flags)));
        }
        | method_modifiers uncqtypedef TOK_SUB '(' myexp ')' block {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @7.last_line);
            check_funcmod(loc, $1);

            RetTypeInfo* rt = new RetTypeInfo(ParserTypeStruct::getParseTypeInfo($2), ParserTypeStruct::getTypeInfo($2));
            delete $2;

            int ar = get_pop_argv_ref();
            int64 flags = ar > 0 ? QCF_USES_EXTRA_ARGS : QCF_NO_FLAGS;
            if ($1 & OFM_DEPRECATED)
                flags |= QCF_DEPRECATED;
            $$.set(new QoreClosureParseNode(loc, new UserClosureFunction($7, @5.first_line, @5.last_line, $5, rt, $1 | OFM_SYNCED, flags)));
        }
        | IMPLICIT_ARG_REF {
            $$.set($1);
            inc_numeric_argv_ref();
        }
        | IMPLICIT_ELEMENT {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line);
            $$.set(new QoreImplicitElementNode(loc));
        }
        | TOK_DELETE exp {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QoreDeleteOperatorNode(loc, $2));
        }
        | TOK_REMOVE exp {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line);
            $$.set(new QoreRemoveOperatorNode(loc, $2));
        }
        | QORE_CAST '(' exp ')' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            $$.set(new QoreParseCastOperatorNode(loc, ParserTypeStruct::getParseType(loc, $1, false), $3));
        }
        | QORE_OR_NOTHING_CAST '(' exp ')' {
            const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
            $$.set(new QoreParseCastOperatorNode(loc, ParserTypeStruct::getParseType(loc, $1, true), $3));
        }
        ;

exp:
    exp_c                               { $$.set($1); }
    | list                              { $$.set($1); }
    | alt_hash                          { $$.set($1); }
    | immediate_typed_hash              { $$.set($1); }
    | '{' '}'                           { $$.set(new QoreParseHashNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line), true)); }
    | exp PLUS_EQUALS exp               { $$.set(new QorePlusEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp MINUS_EQUALS exp              { $$.set(new QoreMinusEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp AND_EQUALS exp                { $$.set(new QoreAndEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp OR_EQUALS exp                 { $$.set(new QoreOrEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp MODULA_EQUALS exp             { $$.set(new QoreModuloEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp MULTIPLY_EQUALS exp           { $$.set(new QoreMultiplyEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp DIVIDE_EQUALS exp             { $$.set(new QoreDivideEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp XOR_EQUALS exp                { $$.set(new QoreXorEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp SHIFT_LEFT_EQUALS exp         { $$.set(new QoreShiftLeftEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp SHIFT_RIGHT_EQUALS exp        { $$.set(new QoreShiftRightEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp '=' exp                       {
        $$.set(process_assignment(@1.first_line, @3.last_line, $1, $3));
    }
    | exp WEAK_ASSIGNMENT exp           {
        const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
        // see if the use of the weak assignment operator is allowed
        if (!parse_check_parse_option(PO_ALLOW_WEAK_REFERENCES))
            parse_error(*loc, "unauthorized use of the weak reference assignment operator \":=\" (parse option PO_ALLOW_WEAK_REFERENCES is not set)");

        $$.set(process_weak_assignment(loc, $1, $3));
    }
    | exp TOK_INSTANCEOF uncqtypedef    {
        const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
        $$.set($3->getTypeInfo()
            ? new QoreInstanceOfOperatorNode(loc, $1, $3->getTypeInfo())
            : new QoreInstanceOfOperatorNode(loc, $1, $3->getParseTypeInfo()));
        delete $3;
    }
    | exp NULL_COALESCING exp           { $$.set(new QoreNullCoalescingOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp VALUE_COALESCING exp          { $$.set(new QoreValueCoalescingOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp '?' exp ':' exp               { $$.set(new QoreQuestionMarkOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line), $1, $3, $5)); }
    | exp P_INCREMENT                   { $$.set(new QorePostIncrementOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line), $1)); } // post-increment
    | exp P_DECREMENT                   { $$.set(new QorePostDecrementOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line), $1)); } // post-decrement
    | exp '(' myexp ')'                 { $$.set(processCall(@1.first_line, @4.last_line, $1, $3, qore_program_private::get(*getProgram())->getLocation(@3.first_line, @3.last_line))); }
    | exp LOGICAL_AND exp               {
        const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
        check_operator_log_or(loc, $1, "logical AND");
        $$.set(new QoreLogicalAndOperatorNode(loc, $1, $3));
    }
    | exp LOGICAL_OR exp                { $$.set(new QoreLogicalOrOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp BROKEN_LOGICAL_OR exp         { $$.set(new QoreLogicalOrOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp BROKEN_BINARY_OR exp          { $$.set(new QoreBinaryOrOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp BROKEN_BINARY_XOR exp         {
        const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
        check_operator_bin_or(loc, $1, "binary XOR");
        $$.set(new QoreBinaryXorOperatorNode(loc, $1, $3));
    }
    | exp '|' exp                       { $$.set(new QoreBinaryOrOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp '&' exp                       {
        const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
        check_operator_bin_or(loc, $1, "binary AND");
        check_operator_bin_xor(loc, $1, "binary AND");
        $$.set(new QoreBinaryAndOperatorNode(loc, $1, $3));
    }
    | exp '^' exp                       { $$.set(new QoreBinaryXorOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp REGEX_MATCH REGEX             { $$.set(new QoreRegexMatchOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp REGEX_NMATCH REGEX            { $$.set(new QoreRegexNMatchOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp REGEX_MATCH REGEX_SUBST       { $$.set(new QoreRegexSubstOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp REGEX_MATCH REGEX_TRANS       { $$.set(new QoreTransliterationOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp REGEX_MATCH REGEX_EXTRACT     { $$.set(new QoreRegexExtractOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp '>' exp                       { $$.set(new QoreLogicalGreaterThanOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp '<' exp                       { $$.set(new QoreLogicalLessThanOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp LOGICAL_CMP exp               { $$.set(new QoreLogicalComparisonOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp LOGICAL_EQ exp                { $$.set(new QoreLogicalEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp ABSOLUTE_EQ exp               { $$.set(new QoreLogicalAbsoluteEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp ABSOLUTE_NE exp               { $$.set(new QoreLogicalAbsoluteNotEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp LOGICAL_NE exp                { $$.set(new QoreLogicalNotEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp LOGICAL_LE exp                { $$.set(new QoreLogicalLessThanOrEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp LOGICAL_GE exp                { $$.set(new QoreLogicalGreaterThanOrEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp SHIFT_LEFT exp                { $$.set(new QoreShiftLeftOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp SHIFT_RIGHT exp               { $$.set(new QoreShiftRightOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp '+' exp                       { $$.set(new QorePlusOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp '-' exp                       { $$.set(new QoreMinusOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp '%' exp                       { $$.set(new QoreModuloOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp '/' exp                       { $$.set(new QoreDivisionOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp '*' exp                       { $$.set(new QoreMultiplicationOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp '[' exp RANGE ']'             { $$.set(new QoreSquareBracketsRangeOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line), $1, $3, QoreValue())); }
    | exp '[' RANGE exp ']'             { $$.set(new QoreSquareBracketsRangeOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line), $1, QoreValue(), $4)); }
    | exp '[' RANGE ']'                 {
        parse_error(*qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line), "illegal range without start or end expressions; at least start or end must be given for a range");
        $$.set(new QoreSquareBracketsRangeOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line), $1, QoreValue(), QoreValue()));
    }
    | exp '[' exp ']'                   {
        $$.set(nullptr);
        // issue #2198: if we have a range between square brackets, we need to return a QoreSquareBracketsRangeOperatorNode
        if ($3.getType() == NT_OPERATOR) {
            QoreRangeOperatorNode* ron = dynamic_cast<QoreRangeOperatorNode*>($3.getInternalNode());
            if (ron) {
                std::unique_ptr<QoreRangeOperatorNode> holder(ron);
                $$.set(new QoreSquareBracketsRangeOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line), $1, ron->swapLeft(QoreValue()), ron->swapRight(QoreValue())));
            }
        }
        if (!$$) {
            $$.set(new QoreSquareBracketsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3));
        }
    }
    | exp '{' exp '}'                   { $$.set(new QoreHashObjectDereferenceOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp RANGE exp                     { $$.set(new QoreRangeOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp '.' exp                       { $$.set(process_dot(@1.first_line, @3.last_line, $1, $3)); }
    | exp DOT_KW_IDENTIFIER             { $$.set(new QoreHashObjectDereferenceOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line), $1, $2)); }
    ;

exp_n:
    exp_c                                 { $$.set($1); }
    | list_n                              { $$.set($1); }
    | exp_n PLUS_EQUALS exp               { $$.set(new QorePlusEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n MINUS_EQUALS exp              { $$.set(new QoreMinusEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n AND_EQUALS exp                { $$.set(new QoreAndEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n OR_EQUALS exp                 { $$.set(new QoreOrEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n MODULA_EQUALS exp             { $$.set(new QoreModuloEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n MULTIPLY_EQUALS exp           { $$.set(new QoreMultiplyEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n DIVIDE_EQUALS exp             { $$.set(new QoreDivideEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n XOR_EQUALS exp                { $$.set(new QoreXorEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n SHIFT_LEFT_EQUALS exp         { $$.set(new QoreShiftLeftEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n SHIFT_RIGHT_EQUALS exp        { $$.set(new QoreShiftRightEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n '=' exp                       {
        $$.set(process_assignment(@1.first_line, @3.last_line, $1, $3));
    }
    | exp_n WEAK_ASSIGNMENT exp           {
        const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
        // see if the use of the weak assignment operator is allowed
        if (!parse_check_parse_option(PO_ALLOW_WEAK_REFERENCES))
            parse_error(*loc, "unauthorized use of the weak reference assignment operator \":=\" (parse option PO_ALLOW_WEAK_REFERENCES is not set)");

        $$.set(process_weak_assignment(loc, $1, $3));
    }
    | exp_n TOK_INSTANCEOF uncqtypedef    {
        const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
        $$.set($3->getTypeInfo()
            ? new QoreInstanceOfOperatorNode(loc, $1, $3->getTypeInfo())
            : new QoreInstanceOfOperatorNode(loc, $1, $3->getParseTypeInfo()));
        delete $3;
    }
    | exp_n NULL_COALESCING exp           { $$.set(new QoreNullCoalescingOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n VALUE_COALESCING exp          { $$.set(new QoreValueCoalescingOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n '?' exp ':' exp               { $$.set(new QoreQuestionMarkOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line), $1, $3, $5)); }
    | exp_n P_INCREMENT                   { $$.set(new QorePostIncrementOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line), $1)); } // post-increment
    | exp_n P_DECREMENT                   { $$.set(new QorePostDecrementOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line), $1)); } // post-decrement
    | exp_n '(' myexp ')'                 { $$.set(processCall(@1.first_line, @4.last_line, $1, $3, qore_program_private::get(*getProgram())->getLocation(@3.first_line, @3.last_line))); }
    | exp_n LOGICAL_AND exp               {
        const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
        check_operator_log_or(loc, $1, "logical AND");
        $$.set(new QoreLogicalAndOperatorNode(loc, $1, $3));
    }
    | exp_n LOGICAL_OR exp                { $$.set(new QoreLogicalOrOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n BROKEN_LOGICAL_OR exp         { $$.set(new QoreLogicalOrOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n BROKEN_BINARY_OR exp          { $$.set(new QoreBinaryOrOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n BROKEN_BINARY_XOR exp         {
        const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
        check_operator_bin_or(loc, $1, "binary XOR");
        $$.set(new QoreBinaryXorOperatorNode(loc, $1, $3));
    }
    | exp_n '|' exp                       { $$.set(new QoreBinaryOrOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n '&' exp                       {
        const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line);
        check_operator_bin_or(loc, $1, "binary AND");
        check_operator_bin_xor(loc, $1, "binary AND");
        $$.set(new QoreBinaryAndOperatorNode(loc, $1, $3));
    }
    | exp_n '^' exp                       { $$.set(new QoreBinaryXorOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n REGEX_MATCH REGEX             { $$.set(new QoreRegexMatchOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n REGEX_NMATCH REGEX            { $$.set(new QoreRegexNMatchOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n REGEX_MATCH REGEX_SUBST       { $$.set(new QoreRegexSubstOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n REGEX_MATCH REGEX_TRANS       { $$.set(new QoreTransliterationOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n REGEX_MATCH REGEX_EXTRACT     { $$.set(new QoreRegexExtractOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n '>' exp                       { $$.set(new QoreLogicalGreaterThanOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n '<' exp                       { $$.set(new QoreLogicalLessThanOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n LOGICAL_CMP exp               { $$.set(new QoreLogicalComparisonOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n LOGICAL_EQ exp                { $$.set(new QoreLogicalEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n ABSOLUTE_EQ exp               { $$.set(new QoreLogicalAbsoluteEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n ABSOLUTE_NE exp               { $$.set(new QoreLogicalAbsoluteNotEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n LOGICAL_NE exp                { $$.set(new QoreLogicalNotEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n LOGICAL_LE exp                { $$.set(new QoreLogicalLessThanOrEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n LOGICAL_GE exp                { $$.set(new QoreLogicalGreaterThanOrEqualsOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n SHIFT_LEFT exp                { $$.set(new QoreShiftLeftOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n SHIFT_RIGHT exp               { $$.set(new QoreShiftRightOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n '+' exp                       { $$.set(new QorePlusOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n '-' exp                       { $$.set(new QoreMinusOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n '%' exp                       { $$.set(new QoreModuloOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n '/' exp                       { $$.set(new QoreDivisionOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n '*' exp                       { $$.set(new QoreMultiplicationOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n '[' exp RANGE ']'             { $$.set(new QoreSquareBracketsRangeOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line), $1, $3, QoreValue())); }
    | exp_n '[' RANGE exp ']'             { $$.set(new QoreSquareBracketsRangeOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @5.last_line), $1, QoreValue(), $4)); }
    | exp_n '[' RANGE ']'                 {
        const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
        parse_error(*loc, "illegal range without start or end expressions; at least start or end must be given for a range");
        $$.set(new QoreSquareBracketsRangeOperatorNode(loc, $1, QoreValue(), QoreValue()));
    }
    | exp_n '[' exp ']'                   {
        const QoreProgramLocation* loc = qore_program_private::get(*getProgram())->getLocation(@1.first_line, @4.last_line);
        $$.set(nullptr);
        // issue #2198: if we have a range between square brackets, we need to return a QoreSquareBracketsRangeOperatorNode
        if ($3.getType() == NT_OPERATOR) {
            QoreRangeOperatorNode* ron = dynamic_cast<QoreRangeOperatorNode*>($3.getInternalNode());
            if (ron) {
                std::unique_ptr<QoreRangeOperatorNode> holder(ron);
                $$.set(new QoreSquareBracketsRangeOperatorNode(loc, $1, ron->swapLeft(QoreValue()), ron->swapRight(QoreValue())));
            }
        }
        if (!$$) {
            $$.set(new QoreSquareBracketsOperatorNode(loc, $1, $3));
        }
    }
    | exp_n '{' exp '}'                   { $$.set(new QoreHashObjectDereferenceOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n RANGE exp                     { $$.set(new QoreRangeOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @3.last_line), $1, $3)); }
    | exp_n '.' exp                       { $$.set(process_dot(@1.first_line, @3.last_line, $1, $3)); }
    | exp_n DOT_KW_IDENTIFIER             { $$.set(new QoreHashObjectDereferenceOperatorNode(qore_program_private::get(*getProgram())->getLocation(@1.first_line, @2.last_line), $1, $2)); }
    ;

string:
        QUOTED_WORD {
           $$ = $1;
        }
        | QUOTED_WORD string {
            $$ = $1;
            $$->concat($2);
            $2->deref();
        }
        ;

scalar:
        QFLOAT        { $$.set($1); }
        | INTEGER     { $$.set($1); }
        | string      { $$.set($1); }
        | DATETIME    { $$.set($1); }
        | NUMBER      { $$.set($1); }
        | TOK_NOTHING { $$.set(nullptr); }
        | TOK_NULL    { $$.set(&Null); }
        | TOK_TRUE    { $$.set(true); }
        | TOK_FALSE   { $$.set(false); }
        | ELLIPSES    {
            $$.set(new QoreEllipsesNode(
                qore_program_private::get(*getProgram())->getLocation(@1.first_line, @1.last_line))
            );
        }
        ;

%%
