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

   parser.ypp

   Qore Programming Language

   Copyright 2003 - 2013 David Nichols

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#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 "parser.hpp"

#include <qore/intern/QoreClassIntern.h>
#include <qore/intern/qore_program_private.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <memory>
#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).last_line    = YYRHSLOC(Rhs, N).last_line;   \
            }                                                          \
            else {                                                     \
                (Current).first_line   = (Current).last_line   =       \
                  YYRHSLOC(Rhs, 0).last_line;                          \
            }                                                          \
          while (0)

class HashElement {
public:
   char* key;
   AbstractQoreNode* value;
 
   DLLLOCAL HashElement(AbstractQoreNode* k, AbstractQoreNode* v) {
      //tracein("HashElement::HashElement()");
      if (!k || k->getType() != NT_STRING) {
	 parse_error("hash member name must be a string value!");
	 key = strdup("");
      }
      else
	 key = strdup(reinterpret_cast<QoreStringNode*>(k)->getBuffer());
      k->deref(0);
      value = v;
      //traceout("HashElement::HashElement()");
   }

   DLLLOCAL HashElement(int tag, char* constant, AbstractQoreNode* v) {
      //tracein("HashElement::HashElement()");
      key = (char* )malloc(sizeof(char) * strlen(constant) + 2);
      key[0] = tag; // mark as constant
      strcpy(key + 1, constant);
      value = v;
      free(constant);
      //traceout("HashElement::HashElement()");
   }

   DLLLOCAL ~HashElement() {
      free(key);
   }
};

static AbstractQoreNode* makeErrorTree(AbstractQoreNode* left, AbstractQoreNode* right) {
   discard(left, 0);
   discard(right, 0);
   return &False;
}

// for constant definitions
class ConstNode {
public:
   NamedScope name;
   AbstractQoreNode* value;
   bool pub;

   DLLLOCAL ConstNode(char* n, AbstractQoreNode* v, bool p = false) : name(n), value(v), pub(p) {
      // see if constant definitions are allowed
      if (checkParseOption(PO_NO_CONSTANT_DEFS))
         parse_error("illegal constant definition \"%s\" (conflicts with parse option NO_CONSTANT_DEFS)", n);
   }
   DLLLOCAL AbstractQoreNode* takeValue() {
      AbstractQoreNode* rv = value;
      value = 0;
      return rv;
   }
   DLLLOCAL const std::string &getName() const {
      return name.getIdentifierStr();
   }
   DLLLOCAL bool isPublic() const {
      return pub;
   }
};

class ObjClassDef {
public:
   NamedScope *name;
   QoreClass *oc;

   DLLLOCAL inline ObjClassDef(NamedScope *n, QoreClass *o) : name(n), oc(o) {}
   DLLLOCAL inline ObjClassDef(char* n, QoreClass *o) : name(new NamedScope(n)), oc(o) {}
   DLLLOCAL inline ~ObjClassDef() { delete name; }
};

class ParseUserFunctionBase {
public:
   UserFunctionVariant* variant;

   DLLLOCAL ParseUserFunctionBase(UserFunctionVariant* v) : variant(v) {
   }

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

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

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

   DLLLOCAL ParseUserFunction(char* n_name, UserFunctionVariant* n_variant) : ParseUserFunctionBase(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 (checkParseOption(PO_NO_SUBROUTINE_DEFS)) {
         const char* nsn = ns.name.c_str();
         parse_error("function '%s%s%s()' cannot be defined (conflicts with parse option NO_SUBROUTINE_DEFS)", nsn[0] ? nsn : "", nsn[0] ? "::" : "", name);
      }

      delete this;
   }
};

class ParseScopedUserFunction : public ParseUserFunctionBase {
public:
   NamedScope name;

   DLLLOCAL ParseScopedUserFunction(char* n_name, UserFunctionVariant* n_variant) : ParseUserFunctionBase(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 (checkParseOption(PO_NO_SUBROUTINE_DEFS))
         parse_error("function '%s()' cannot be defined (conflicts with parse option NO_SUBROUTINE_DEFS)", name.ostr);

      delete this;
   }
};

struct GVarDecl {
   bool pub;

   DLLLOCAL GVarDecl(bool p) : 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;

   DLLLOCAL GVarSingleDecl(char* n, const QoreTypeInfo* ti, QoreParseTypeInfo* pti, bool p, bool bare) : GVarDecl(p), name(n), typeInfo(ti), parseTypeInfo(pti) {
      if (bare) {
         if (!checkParseOption(PO_ALLOW_BARE_REFS))
            parse_error("global variable '%s' declared without '$' prefix, but parse option 'allow-bare-refs' is not set", n);
      }
      else {
         if (checkParseOption(PO_ALLOW_BARE_REFS))
            parse_error("reference to variable '$%s' with the '$' sign is not allowed when parse option 'allow-bare-refs' is set", 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 = 0;
      return rv;
   }

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

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

      delete this;
      return gv;
   }   
};

struct GVarListDecl : public GVarDecl {
   QoreListNode* l;

   DLLLOCAL GVarListDecl(QoreListNode* list, bool p) : GVarDecl(p), l(list) {
      bool bareok = checkParseOption(PO_ALLOW_BARE_REFS);

      ListIterator li(l);
      while (li.next()) {
         AbstractQoreNode* n = li.getValue();
         qore_type_t t = get_node_type(n);
         if (t == NT_BAREWORD) {
            BarewordNode* b = reinterpret_cast<BarewordNode*>(n);
            if (!bareok)
               parse_error("global variable '%s' in global variable declaration list declared without '$' prefix, but parse option 'allow-bare-refs' is not set", b->str);
         }
         else if (t == NT_VARREF) {
            VarRefNode* vrn = reinterpret_cast<VarRefNode*>(n);
            if (vrn->explicitScope()) {
               if (vrn->getType() == VT_LOCAL)
                  parse_error("illegal use of 'my %s' in global variable declaration list", vrn->getName());
               else if (vrn->getType() == VT_GLOBAL)
                  parse_error("illegal use of 'our %s' in global variable declaration list", vrn->getName());
            }
         }
         else
            parse_error("element %d in list following 'our' is not a variable reference (%s)", li.index(), get_node_type(n));
      }
   }

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

   DLLLOCAL QoreListNode* takeList() {
      QoreListNode* rv = l;
      l = 0;
      return rv;
   }

   DLLLOCAL virtual void add(qore_ns_private& ns) {
      ListIterator li(l);
      while (li.next()) {
         AbstractQoreNode* n = li.getValue();
         qore_type_t t = get_node_type(n);
         if (t == NT_BAREWORD) {
            BarewordNode* b = reinterpret_cast<BarewordNode*>(n);
            ns.parseAddGlobalVarDecl(b->takeString(), 0, 0, pub);
         }
         else if (t == NT_VARREF) {
            VarRefNode* vrn = reinterpret_cast<VarRefNode*>(n);
            if (!vrn->explicitScope()) {
               VarRefDeclNode* vrdn = dynamic_cast<VarRefDeclNode*>(vrn);
               if (vrdn)
                  ns.parseAddGlobalVarDecl(vrn->takeName(), vrdn->getTypeInfo(), vrdn->takeParseTypeInfo(), pub);
               else
                  ns.parseAddGlobalVarDecl(vrn->takeName(), 0, 0, pub);
            }
         }
      }

      delete this;
   }

   DLLLOCAL virtual AbstractQoreNode* makeVar() {
      l->setVariableList();
      ListIterator li(l);
      while (li.next()) {
         AbstractQoreNode* n = li.getValue();
         qore_type_t t = get_node_type(n);
         VarRefNode* vrn = 0;
         if (t == NT_BAREWORD) {
            BarewordNode* b = reinterpret_cast<BarewordNode*>(n);
            GlobalVarRefNode* v = new GlobalVarRefNode(b->takeString());
            b->deref();
            AbstractQoreNode** p = li.getValuePtr();
            *p = vrn = v;
         }
         else if (t == NT_VARREF) {
            vrn = reinterpret_cast<VarRefNode*>(n);
            if (!vrn->explicitScope()) {
               vrn->makeGlobal();
               vrn->setExplicitScope();
            }
         }

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

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

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

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

   DLLLOCAL NSNode(ObjClassDef *o) { type = NSN_OCD; n.ocd = o; }
   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 void deref() {
      switch (type) {
         case NSN_OCD:
            delete n.ocd;
            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;
         default:
            assert(false);
            // no break
      }
      delete this;
   }

   DLLLOCAL void add(qore_ns_private& ns) {
      switch (type) {
         case NSN_OCD:
            ns.parseAddPendingClass(*(n.ocd->name), n.ocd->oc);
            delete n.ocd;
            break;
         case NSN_CONST:
            ns.parseAddConstant(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;
         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 QoreListNode* make_list(AbstractQoreNode* a1, AbstractQoreNode* a2) {
   QoreListNode* l = new QoreListNode;
   l->push(a1);
   l->push(a2);
   return l;
}

static QoreListNode* splice_expressions(AbstractQoreNode* a1, AbstractQoreNode* a2) {
   //tracein("splice_expressions()");
   if (a1 && a1->getType() == NT_LIST) {
      QoreListNode* l = reinterpret_cast<QoreListNode*>(a1);
      if (!l->isFinalized()) {
	 //printd(5, "LIST x\n");
	 l->push(a2);
	 return l;
      }
   }
   return make_list(a1, a2);
}

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

struct MemberInfo {
protected:
   DLLLOCAL MemberInfo() {
   }

public:
   enum mi_e { Member, Constant, Var };

   char* name;
   mi_e type;
   union {
      QoreMemberInfo* memberInfo;
      QoreVarInfo* varInfo;
      AbstractQoreNode* exp;
   } u;

   DLLLOCAL MemberInfo(char* n, QoreMemberInfo* mi) : name(n), type(Member)  {
      u.memberInfo = mi; 
      if (!mi->parseHasTypeInfo() && getProgram()->getParseOptions64() & PO_REQUIRE_TYPES)
	 parse_error("member '%s' declared without type information, but parse options require type information for all declarations", name);
   }
   DLLLOCAL MemberInfo(char* n) : name(n), type(Member)  {
      u.memberInfo = 0; 
      if (getProgram()->getParseOptions64() & PO_REQUIRE_TYPES)
	 parse_error("member '%s' declared without type information, but parse options require type information for all declarations", name);
   }
   DLLLOCAL MemberInfo(char* n, AbstractQoreNode* exp) : name(n), type(Constant) {
      u.exp = exp;
   }
   DLLLOCAL ~MemberInfo() {
      if (name)
	 free(name);
      switch (type) {
	 case Member:
	    delete u.memberInfo;
	    break;
	 case Var:
	    delete u.varInfo;
	    break;
	 case Constant:
	    if (u.exp)
	       u.exp->deref(0);
	    break;
      }
   }
   DLLLOCAL member_pair_t getPair() {
      assert(type == Member);
      member_pair_t m = std::make_pair(name, u.memberInfo);
      name = 0;
      u.memberInfo = 0;
      return m;
   }
   DLLLOCAL var_pair_t getVarPair() {
      assert(type == Var);
      var_pair_t m = std::make_pair(name, u.varInfo);
      name = 0;
      u.varInfo = 0;
      return m;
   }
   DLLLOCAL AbstractQoreNode* takeExp() {
      assert(type == Constant);
      AbstractQoreNode* rv = u.exp;
      u.exp = 0;
      return rv;
   }
};

struct ClassVarInfo : public MemberInfo {
   DLLLOCAL ClassVarInfo(char* n, QoreVarInfo* vi) {
      name = n;
      type = Var;
      u.varInfo = vi; 
      if (!vi->parseHasTypeInfo() && getProgram()->getParseOptions64() & PO_REQUIRE_TYPES)
	 parse_error("class static variable '%s' declared without type information, but parse options require type information for all declarations", name);
   }
/*
   DLLLOCAL ClassVarInfo(char* n) {
      name = n;
      type = Var;
      u.varInfo = 0; 
      if (getProgram()->getParseOptions64() & PO_REQUIRE_TYPES)
	 parse_error("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 = 0;
      return n;
   }
   DLLLOCAL QoreVarInfo* takeVarInfo() {
      assert(u.varInfo);
      QoreVarInfo* rv = u.varInfo;
      u.varInfo = 0;
      return rv;
   }
};

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 == MemberInfo::Member)
	 mmap.insert(member->getPair());
      else if (member->type == MemberInfo::Constant)
	 cmap.parseAdd(member->name, member->takeExp());
      else
	 vmap.insert(member->getVarPair());

      delete member;
   }

   DLLLOCAL ~MemberList() {
   }

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

      std::auto_ptr<MemberInfo> mem(member);

      if (member->type == MemberInfo::Member) {
	 if (mmap.find(member->name) != mmap.end()) {
	    parse_error("duplicate member declaration '%s'", member->name);
	    return -1;
	 }

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

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

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

      return 0;
   }

   DLLLOCAL void mergePrivate(QoreClass *qc) {
      {
	 member_map_t::iterator i;
	 // merge private members
	 while ((i = mmap.begin()) != mmap.end()) {
	    qore_class_private::parseAddPrivateMember(*qc, i->first, i->second);
	    mmap.erase(i);
	 }
      }

      {
	 var_map_t::iterator i;
	 // merge private static class variables
	 while ((i = vmap.begin()) != vmap.end()) {
	    qore_class_private::parseAddPrivateStaticVar(qc, i->first, i->second);
	    vmap.erase(i);
	 }
      }

      // merge private constants
      qc->parseAssimilatePrivateConstants(cmap);
   }

   DLLLOCAL void mergePublic(QoreClass *qc) {
      // merge public members
      if (mmap.empty())
	 qc->parseSetEmptyPublicMemberDeclaration();
      else {
	 member_map_t::iterator i;

	 while ((i = mmap.begin()) != mmap.end()) {
	    qore_class_private::parseAddPublicMember(*qc, i->first, i->second);
	    mmap.erase(i);
	 }
      }

      {
	 var_map_t::iterator i;
	 // merge public static class variables
	 while ((i = vmap.begin()) != vmap.end()) {
	    qore_class_private::parseAddPublicStaticVar(qc, i->first, i->second);
	    vmap.erase(i);
	 }
      }

      // merge public constants
      qc->parseAssimilatePublicConstants(cmap);
   }
};

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

struct ParserTypeStruct {
protected:
   DLLLOCAL ParserTypeStruct(QoreParseTypeInfo* n_parseTypeInfo) : typeInfo(0), parseTypeInfo(n_parseTypeInfo) {
   }

   DLLLOCAL ParserTypeStruct() {
   }

public:
   const QoreTypeInfo* typeInfo;
   QoreParseTypeInfo* parseTypeInfo;

   DLLLOCAL ParserTypeStruct(const QoreTypeInfo* n_typeInfo) : typeInfo(n_typeInfo), parseTypeInfo(0) {
   }

   DLLLOCAL ParserTypeStruct(char* id) {
      const QoreTypeInfo* t = getBuiltinUserTypeInfo(id);
      //printd(0, "ParserTypeStruct::ParserTypeStruct('%s') t: %p %s\n", id, t, t->getName());
      if (t) {
	 free(id);
	 typeInfo = t;
	 parseTypeInfo = 0;
	 return;
      }

      typeInfo = 0;
      parseTypeInfo = new QoreParseTypeInfo(id);
   }

   DLLLOCAL ~ParserTypeStruct() {
      delete parseTypeInfo;
   }

   DLLLOCAL const QoreTypeInfo* getTypeInfo() const {
      if (!this)
	 return 0;

      return typeInfo;
   }

   DLLLOCAL QoreParseTypeInfo* getParseTypeInfo() {
      if (!this)
	 return 0;

      QoreParseTypeInfo* rv = parseTypeInfo;
      parseTypeInfo = 0;
      return rv;
   }

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

struct ParserScopedTypeStruct : public ParserTypeStruct {
   DLLLOCAL ParserScopedTypeStruct(char* sr) : ParserTypeStruct(new QoreParseTypeInfo(sr)) {
   }
};

struct ParserOrNothingTypeStruct : public ParserTypeStruct {
   DLLLOCAL ParserOrNothingTypeStruct(char* id) {
      const QoreTypeInfo* t = getBuiltinUserOrNothingTypeInfo(id);
      if (t) {
	 free(id);
	 typeInfo = t;
	 parseTypeInfo = 0;
	 return;
      }

      typeInfo = 0;
      parseTypeInfo = new QoreParseTypeInfo(id, true);
   }
};

struct ParserScopedOrNothingTypeStruct : public ParserTypeStruct {
   DLLLOCAL ParserScopedOrNothingTypeStruct(char* sr) : ParserTypeStruct(new QoreParseTypeInfo(sr, true)) {
   }
};

static AbstractQoreNode* process_dot(AbstractQoreNode* l, AbstractQoreNode* r) {
   qore_type_t rtype = r->getType();
   if (rtype == NT_BAREWORD) {
      BarewordNode* b = reinterpret_cast<BarewordNode*>(r);
      AbstractQoreNode* rv = new QoreTreeNode(l, OP_OBJECT_REF, b->makeQoreStringNode());
      b->deref();
      return rv;
   }

   if (rtype == NT_FUNCTION_CALL) {
      FunctionCallNode* f = reinterpret_cast<FunctionCallNode*>(r);
      assert(!f->getFunction());
      if (!f->isFinalized()) {
         MethodCallNode* m = new MethodCallNode(f->takeName(), f->take_args());
         f->deref();

         return new QoreDotEvalOperatorNode(l, m);
      }
   }

   return new QoreTreeNode(l, OP_OBJECT_REF, r);
}

// returns true if the node needs run-time evaluation, false if not
static bool needsEval(AbstractQoreNode* n) {
   if (!n)
      return false;

   qore_type_t ntype = n->getType();

   // if it's a constant or a function reference
   if (ntype == NT_BAREWORD || ntype == NT_CONSTANT || ntype == NT_FUNCREF)
      return false;

   if (ntype == NT_LIST) {
      QoreListNode* l = reinterpret_cast<QoreListNode*>(n);
      if (l->needs_eval()) {
	 for (unsigned i = 0; i <l->size(); i++) {
	    if (needsEval(l->retrieve_entry(i)))
	       return true;
	 }
	 // here we set needs_eval to false so the list won't be evaluated again
	 l->clearNeedsEval();
      }
      return false;
   }

   if (ntype == NT_HASH) {
      QoreHashNode* h = reinterpret_cast<QoreHashNode*>(n);
      if (h->needs_eval()) {
	 HashIterator hi(h);
	 while (hi.next())
	    if (needsEval(hi.getValue()))
	       return true;
	 // here we set needs_eval to false so the hash won't be evaluated again
	 h->clearNeedsEval();
      }
      return false;
   }
   
   if (ntype == NT_TREE) {
      QoreTreeNode* tree = reinterpret_cast<QoreTreeNode*>(n);

      if (needsEval(tree->left) || (tree->right && needsEval(tree->right)))
	 return true;
      return tree->getOp()->hasEffect();
   }

   //printd(5, "needsEval() type %s = true\n", n->getTypeName());
   // we don't return needs_eval() here because some node types are not meant to be evaluated directly but are also not values
   return !n->is_value();
}

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

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

static void check_funcmod(int mod) {
   if (mod & OFM_PRIVATE)
      parse_error("illegal 'private' modifier in function declaration");
   if (mod & OFM_STATIC)
      parse_error("illegal 'static' modifier in function declaration");
   if (mod & OFM_FINAL)
      parse_error("illegal 'final' modifier in function declaration");
   if (mod & OFM_ABSTRACT)
      parse_error("illegal 'abstract' modifier in function declaration");
}

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

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

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

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

   ParseNode* pn = dynamic_cast<ParseNode*>(n);
   return pn ? pn->has_effect() : true;
}

static int checkMethod(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("%s methods may not declare a return type", name);
      return -1;
   }

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

   return 0;
}

static inline MethodVariantBase* newMethodVariant(const char* name, int mod, int n_sig_first_line, int n_sig_last_line, AbstractQoreNode* params, BCAList* bcal, RetTypeInfo* returnTypeInfo, StatementBlock *b, int64 flags = QC_NO_FLAGS) {
   //printd(5, "newMethodVariant() '%s' b: %p\n", name, b);

   if (get_pop_argv_ref())
      flags |= QC_USES_EXTRA_ARGS;

   if (!strcmp(name, "constructor")) {
      if (mod & OFM_SYNCED)
         parse_error("%s() methods cannot be synchronized", name);
      if (mod & OFM_FINAL)
         parse_error("%s() methods cannot be declared final; declare the class final instead", name);
      if (mod & OFM_ABSTRACT)
         parse_error("%s() methods cannot be declared abstract", name);
      if (returnTypeInfo) {
         parse_error("return type information cannot be defined for %s() methods", name);
         delete returnTypeInfo;
      }
      return new UserConstructorVariant(mod & OFM_PRIVATE, b, n_sig_first_line, n_sig_last_line, params, bcal, flags);
   }
   if (bcal) {
      parse_error("only constructors may have base class constructor arguments");
      delete bcal;
      bcal = 0;
   }
   if (!strcmp(name, "destructor")) {
      if (params) {
	 parse_error("parameters cannot be defined for destructors");
	 params->deref(0);
      }
      if (mod & OFM_SYNCED)
         parse_error("%s() methods cannot be synchronized", name);
      if (mod & OFM_FINAL)
         parse_error("%s() methods cannot be declared final; declare the class final instead", name);
      if (mod & OFM_ABSTRACT)
         parse_error("%s() methods cannot be declared abstract", name);
      if (returnTypeInfo) {
         parse_error("return type information cannot be defined for %s() methods", name);
         delete returnTypeInfo;
      }
      if (mod & OFM_PRIVATE)
	 parse_error("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("%s() methods cannot be declared final; declare the class final instead", name);
      if (!(mod & OFM_SYNCED))
         return new UserCopyVariant(mod & OFM_PRIVATE, 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("%s() methods cannot be declared final; they are not inherited anyway", name);
      if (mod & OFM_ABSTRACT)
         parse_error("%s() methods cannot be declared abstract; they are not inherited anyway", name);
   }

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

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

static inline void tryAddMethod(int mod, char* n, int n_sig_first_line, int n_sig_last_line, AbstractQoreNode* params, BCAList* bcal, RetTypeInfo* returnTypeInfo, StatementBlock *b) {
   std::auto_ptr<NamedScope> name(new NamedScope(n));

   if (checkMethod(name->getIdentifier(), bcal, returnTypeInfo)) {
      if (params)
	 params->deref(0);
      delete bcal;
      delete b;
      delete returnTypeInfo;
   }
   else {
      MethodVariantBase *m = newMethodVariant(name->getIdentifier(), mod, n_sig_first_line, n_sig_last_line, params, bcal, returnTypeInfo, b, mod & OFM_DEPRECATED);
      // if there are any errors, the function below will delete memory
      qore_root_ns_private::parseAddMethodToClass(*(name.get()), m, mod & OFM_STATIC);
   }
}

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

   DLLLOCAL inline MethodNode(char* mname, MethodVariantBase *v, bool n_static) : name(mname), m(v), static_flag(n_static) {
   }
   DLLLOCAL inline ~MethodNode() {
      free(name);
      delete m;
   }
   DLLLOCAL inline void addAndDelete(QoreClass *qc) {
      qore_class_private::addUserMethod(*qc, name, m, static_flag);
      m = 0;
      delete this;
   }
};

static AbstractQoreNode* is_rewritable_op(AbstractQoreNode* n) {
   qore_type_t nt = get_node_type(n);
   if (nt == NT_OPERATOR) {
      QoreBinaryLValueOperatorNode* op = dynamic_cast<QoreBinaryLValueOperatorNode*>(n);
      if (op)
         return op->getRight();
   }

   return 0;
}

static AbstractQoreNode* checkRewriteList(QoreListNode* 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;

   AbstractQoreNode* e0 = l->retrieve_entry(0);

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

   return l;
}

void ignore_return_value(AbstractQoreNode* n) {
   qore_type_t t = get_node_type(n);
   if (t == NT_TREE)
      reinterpret_cast<QoreTreeNode*>(n)->ignoreReturnValue();
   else if (t == NT_OPERATOR)
      reinterpret_cast<QoreOperatorNode*>(n)->ignoreReturnValue();
}

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

%}

%pure-parser
%lex-param {yyscan_t yyscanner}
%parse-param {yyscan_t yyscanner}
%locations
%error-verbose

%union {
   bool b;
   int i4;
   int64 integer;
   double decimal;
   QoreStringNode* String;
   char* string;
   BinaryNode* binary;
   AbstractQoreNode* node;
   QoreHashNode* hash;
   QoreListNode* 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;
   class ConstNode* constnode;
   QoreNamespace *ns;
   struct NSNodeList* nsnlist;
   struct NSNode* nsn;
   class ObjClassDef *classdef;
   DateTimeNode* datetime;
   RegexSubstNode* RegexSubst;
   RegexTransNode* RegexTrans;
   SwitchStatement *switchstmt;
   CaseNode* casenode;
   BCList* sclist;
   class BCNode* sclnode;
   BCAList* bcalist;
   BCANode* bcanode;
   NamedScope *nscope;
   QoreRegexNode* Regex;
   QoreImplicitArgumentNode* implicit_arg;
   RetTypeInfo* returnTypeInfo;
   struct ParserTypeStruct *parsertype;
   class ParseUserFunction* parsefunc;
   class ParseScopedUserFunction* sparsefunc;
   struct GVarDecl* gv;
   QoreNumberNode* num;
   TryModuleError* trymod;
}

%{

#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: \"%s\"\n", loc->first_line, loc->last_line, str);
   parse_error("%s", str);
}

%}

// define string aliases for token names for more user-friendly error reporting
%token TOK_RETURN "return"
%token TOK_RETURNS "returns"
%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_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"

// currently unused tokens
//%token TOK_FINALLY "finally"
//%token TOK_INTERFACE "interface"
%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 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 "$#"

 // 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> VAR_REF "variable reference"
%token <string> BACKQUOTE "backquote expression"
%token <string> SELF_REF "in-object member reference"
%token <string> KW_IDENTIFIER_OPENPAREN "identifier or keyword used as function or method identifier"
%token <string> SCOPED_REF "namespace or class-scoped reference"
%token <string> SCOPED_VREF "namespace-scoped global variable reference"
%token <string> CONTEXT_REF "context reference"
%token <string> COMPLEX_CONTEXT_REF "named context reference"
%token <string> QORE_CAST "cast operator"
%token <string> CLASS_STRING "simple class declaration"
%token <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 <RegexTrans> 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
%right '='
%right '?' ':'
%left LOGICAL_AND LOGICAL_OR
%left '&' '|' '^'	      // binary and, or, and 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 '%'		      // modula
%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
%right '~' '\\'               // binary not, reference operator
%left '!'		      // logical not
%right TOK_BACKGROUND TOK_DELETE TOK_REMOVE QORE_CAST
%nonassoc TOK_NEW
%nonassoc P_INCREMENT P_DECREMENT
%left '{' '[' '.' '(' DOT_KW_IDENTIFIER  // list and object references, etc, defined for precedence

%type <sblock>         block
%type <sblock>         statement_or_block
%type <sblock>         statements
%type <statement>      statement
%type <statement>      return_statement
%type <statement>      try_statement
%type <node>           exp
%type <node>           myexp
%type <node>           scalar
%type <hash>           hash
%type <list>           list
%type <String>         string
%type <hashelement>    hash_element
%type <cmods>          context_mods
%type <cmod>           context_mod
%type <methodnode>     method_definition
%type <memberlist>     private_member_list
%type <memberlist>     public_member_list
%type <memberlist>     member_list
%type <memberinfo>     member
%type <memberlist>     member_list2
%type <memberinfo>     member2
%type <classvarinfo>   classvardecl
%type <qoreclass>      class_attributes
%type <classdef>       class_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

 // 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 method_definition class_def top_namespace_decl scoped_const_decl unscoped_const_decl switch_statement case_block case_code superclass base_constructor private_member_list public_member_list member_list member_list2 base_constructor_list base_constructors class_attributes return_value member member2 superclass_list inheritance_list qtypedef uncqtypedef classvardecl sub_def scoped_sub_def gvardecl
%destructor { free($$); } IDENTIFIER VAR_REF SELF_REF CONTEXT_REF COMPLEX_CONTEXT_REF BACKQUOTE SCOPED_REF SCOPED_VREF KW_IDENTIFIER_OPENPAREN QORE_CAST CLASS_STRING optname ident_openparen
%destructor { if ($$) $$->deref(); } namespace_decl namespace_decls string QUOTED_WORD DATETIME BINARY IMPLICIT_ARG_REF DOT_KW_IDENTIFIER
%destructor { if ($$) $$->deref(0); } exp myexp scalar hash list

%%
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 {
           $1->add(*(qore_ns_private::get(*(getRootNS()))));
        }
        | class_def {
           qore_root_ns_private::parseAddClass(*($1->name), $1->oc);

	   // see if class definitions are allowed
	   if (checkParseOption(PO_NO_CLASS_DEFS))
	      parse_error("illegal class definition \"%s\" (conflicts with parse option NO_CLASS_DEFS)", $1->oc->getName());
	   delete $1;
	}
	| scoped_const_decl {
           qore_root_ns_private::parseAddConstant(*getRootNS(), $1->name, $1->value, $1->pub);
	   delete $1;
	}
        | unscoped_const_decl {
           qore_root_ns_private::parseAddConstant(*getRootNS(), $1->name, $1->value, $1->pub);
	   delete $1;
	}
        | object_outofline_function_def  // registered directly
	| statement {
	   if ($1) {
	      if ($1->isParseDeclaration())
		 delete $1;
	      else
	         qore_program_private::addStatement(*(getProgram()), $1);
	   }
	}
        | '{' statements '}' {
	   // set line range
	   $2->loc.start_line = @1.first_line;
	   $2->loc.end_line = @2.last_line;
	   qore_program_private::addStatement(*(getProgram()), $2);
        }
        | top_namespace_decl {
           qore_root_ns_private::parseAddNamespace($1); 
	   // see if ns declaration is legal
	   if (checkParseOption(PO_NO_NAMESPACE_DEFS))
	      parse_error("illegal namespace definition \"%s\" (conflicts with parse option NO_NAMESPACE_DEFINITION)", $1->getName());
	}
        | TOK_MODULE '{' module_decls '}' {
           QoreModuleDefContext* qmd = get_module_def_context();
           if (!qmd)
              qore_program_private::makeParseWarning(getProgram(), QP_WARN_MODULE_ONLY, "MODULE-ONLY", "module definitions are ignored when not defining a module");

           // registered internally
        }
        | TRY_MODULE_ERROR {
           bool has_dollar = $1->var[0] == '$';
           if (!has_dollar) {
              if (!checkParseOption(PO_ALLOW_BARE_REFS))
                 parse_error("%%try-module variable '%s' declared without '$' prefix, but parse option 'allow-bare-refs' is not set", $1->var);
           }
           else {
              $1->fixName();
              if (checkParseOption(PO_ALLOW_BARE_REFS))
                 parse_error("%%try-module variable '%s' declared with '$' prefix, but parse option 'allow-bare-refs' is set", $1->var);
           }

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

module_decls:
        module_decl {
        }
        | module_decls module_decl {
        }
        ;

module_decl:
        IDENTIFIER '=' exp ';' {
           QoreModuleDefContext* qmd = get_module_def_context();
           if (!qmd)
              qore_program_private::makeParseWarning(getProgram(), QP_WARN_MODULE_ONLY, "MODULE-ONLY", "cannot add module tag '%s' = <%s> when not defining a module", $1, get_type_name($3));
           else
              qmd->set($1, $3);

           free($1);
           $3->deref(0);
        }
        ;

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

gvardecl:
        ppub TOK_OUR uncqtypedef SCOPED_REF {
	   const QoreTypeInfo* t = $3->getTypeInfo();
           $$ = new GVarSingleDecl($4, t, t ? 0 : $3->getParseTypeInfo(), $1, true);
           delete $3;
	}
        | ppub TOK_OUR SCOPED_REF {
           $$ = new GVarSingleDecl($3, 0, 0, $1, true);
        }
        | ppub TOK_OUR uncqtypedef IDENTIFIER {
	   const QoreTypeInfo* t = $3->getTypeInfo();
           $$ = new GVarSingleDecl($4, t, t ? 0 : $3->getParseTypeInfo(), $1, true);
           delete $3;
	}
        | ppub TOK_OUR IDENTIFIER {
           $$ = new GVarSingleDecl($3, 0, 0, $1, true);
	}
        | ppub TOK_OUR qtypedef SCOPED_VREF {
	   const QoreTypeInfo* t = $3->getTypeInfo();
           $$ = new GVarSingleDecl($4, t, t ? 0 : $3->getParseTypeInfo(), $1, false);
           delete $3;
	}
        | ppub TOK_OUR qtypedef VAR_REF {
	   const QoreTypeInfo* t = $3->getTypeInfo();
           $$ = new GVarSingleDecl($4, t, t ? 0 : $3->getParseTypeInfo(), $1, false);
           delete $3;
	}
        | ppub TOK_OUR '(' list ')' {
           $$ = new GVarListDecl($4, $1);
        }
        ;

top_namespace_decl:
        nsc_modifiers TOK_NAMESPACE '{' namespace_decls '}' {
           qore_ns_private* p = new qore_ns_private;
           check_nsmod($1, *p);
           $4->add(*p);
           delete $4;
           $$ = p->ns;
        }
        | nsc_modifiers TOK_NAMESPACE ';' {
           qore_ns_private* p = new qore_ns_private;
           check_nsmod($1, *p);
           $$ = p->ns;
        }
        | nsc_modifiers TOK_NAMESPACE '{' '}' {
           qore_ns_private* p = new qore_ns_private;
           check_nsmod($1, *p);
           $$ = p->ns;
        }
	;

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

namespace_decl:
        scoped_const_decl {
	   $$ = new NSNode($1);
        }
        | unscoped_const_decl {
	   $$ = new NSNode($1);
	}
	| class_def {
	   $$ = new NSNode($1);
	   // see if class definitions are allowed
	   if (checkParseOption(PO_NO_CLASS_DEFS))
	      parse_error("illegal class definition \"%s\" (conflicts with parse option NO_CLASS_DEFS)", $1->oc->getName());
	}
        | sub_def {
           $$ = new NSNode($1);
        }
        | scoped_sub_def {
           $$ = new NSNode($1);
        }
        | gvardecl ';' {
           $$ = new NSNode($1);
        }
	| top_namespace_decl { 
	   $$ = new NSNode($1); 
	   // see if ns declaration is legal
	   if (checkParseOption(PO_NO_NAMESPACE_DEFS))
	      parse_error("illegal namespace definition \"%s\" (conflicts with parse option NO_NAMESPACE_DEFINITION)", $1->getName());
	}

unscoped_const_decl: 
	TOK_CONST IDENTIFIER '=' exp ';' { 
	   $$ = new ConstNode($2, $4); 
	}
        | TOK_PUBLIC TOK_CONST IDENTIFIER '=' exp ';' { 
	   $$ = new ConstNode($3, $5, true); 
	}
        ;

scoped_const_decl:
	TOK_CONST SCOPED_REF '=' exp ';' {
	   $$ = new ConstNode($2, $4); 
	}
        | TOK_PUBLIC TOK_CONST SCOPED_REF '=' exp ';' {
	   $$ = new ConstNode($3, $5, true); 
	}
        ;

block:
	'{' statements '}'
        { $$ = $2; }
        |
        '{' /* NOTHING */ '}'
        { $$ = 0; }
        ;

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:
	exp ';' {
	   // if the expression has no effect and it's not a variable declaration
	   qore_type_t t = $1 ? $1->getType() : 0;
	   bool ok = false;
	   if (t == NT_LIST) {
	      QoreListNode* l = reinterpret_cast<QoreListNode*>($1);
	      // rewrite expression if it's a list without parentheses under certain conditions
	      $1 = checkRewriteList(l);

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

	   if (!ok && !hasEffect($1) && (t != NT_VARREF || !reinterpret_cast<VarRefNode*>($1)->parseIsDecl()))
	      parse_error("statement has no effect (%s)", $1 ? $1->getTypeName() : "NOTHING");
           ignore_return_value($1);
	   $$ = new ExpressionStatement(@1.first_line, @1.last_line, $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());
	   $$ = new ExpressionStatement(@1.first_line, @1.last_line, new StaticMethodCallNode(ns, makeArgs($3)));
	}
        | try_statement
        { $$ = $1; }
        | TOK_RETHROW ';' {
	   $$ = new RethrowStatement(@1.first_line, @1.last_line);
	}
        | 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 ';' {
	   // see if thread exit is allowed
	   if (checkParseOption(PO_NO_THREAD_CONTROL))
	      parse_error("illegal use of \"thread_exit\" (conflicts with parse option NO_THREAD_CONTROL)");

	   $$ = new ThreadExitStatement(@1.first_line, @1.last_line); 
	}
        | 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;
	   $$->loc.start_line = @1.first_line;
	   $$->loc.end_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
        {
	   check_case(">=", $3);
	   $$ = new CaseNodeWithOperator($3, $5, OP_LOG_GE);
        }
        | TOK_CASE LOGICAL_GE exp ':' // nothing
        {
	   check_case(">=", $3);
	   $$ = new CaseNodeWithOperator($3, 0, OP_LOG_GE);
        }

        | TOK_CASE LOGICAL_LE exp ':' statements
        {
	   check_case("<=", $3);
	   $$ = new CaseNodeWithOperator($3, $5, OP_LOG_LE);
        }
        | TOK_CASE LOGICAL_LE exp ':' // nothing
        {
	   check_case("<=", $3);
	   $$ = new CaseNodeWithOperator($3, 0, OP_LOG_LE);
        }

        | TOK_CASE LOGICAL_EQ exp ':' statements
        {
	   check_case("==", $3);
	   $$ = new CaseNodeWithOperator($3, $5, OP_LOG_EQ);
        }
        | TOK_CASE LOGICAL_EQ exp ':' // nothing
        {
	   check_case("==", $3);
	   $$ = new CaseNodeWithOperator($3, 0, OP_LOG_EQ);
        }

        | TOK_CASE '<' exp ':' statements
        {
	   check_case("<", $3);
	   $$ = new CaseNodeWithOperator($3, $5, OP_LOG_LT);
        }
        | TOK_CASE '<' exp ':' // nothing
        {
	   check_case("<", $3);
	   $$ = new CaseNodeWithOperator($3, 0, OP_LOG_LT);
        }

        | TOK_CASE '>' exp ':' statements
        {
	   check_case(">", $3);
	   $$ = new CaseNodeWithOperator($3, $5, OP_LOG_GT);
        }
        | TOK_CASE '>' exp ':' // nothing
        {
	   check_case(">", $3);
	   $$ = new CaseNodeWithOperator($3, 0, OP_LOG_GT);
        }

	| TOK_CASE REGEX_MATCH REGEX ':' statements
	{
	   $$ = new CaseNodeRegex($3, $5);
	}
	| TOK_CASE REGEX_MATCH REGEX ':' // nothing
	{
	   $$ = new CaseNodeRegex($3, 0);
	}

	| TOK_CASE REGEX_NMATCH REGEX ':' statements
	{
	   $$ = new CaseNodeNegRegex($3, $5);
	}
	| TOK_CASE REGEX_NMATCH REGEX ':' // nothing
	{
	   $$ = new CaseNodeNegRegex($3, 0);
	}

	| TOK_CASE REGEX ':' statements
	{
	   $$ = new CaseNodeRegex($2, $4);
	}
	| TOK_CASE REGEX ':' // nothing
	{
	   $$ = new CaseNodeRegex($2, 0);
	}

        | TOK_CASE exp ':' statements
        {
	   check_case(0, $2);
	   $$ = new CaseNode($2, $4);
	}
        | TOK_CASE exp ':' // nothing
        {
	   check_case(0, $2);
	   $$ = new CaseNode($2, 0);
	}

        | TOK_DEFAULT ':' statements
        {
	   $$ = new CaseNode(0, $3);
	}
        | TOK_DEFAULT ':' // nothing
        {
	   $$ = new CaseNode(0, 0);
	}
        ;

try_statement:
        TOK_TRY statement_or_block TOK_CATCH '(' myexp ')' statement_or_block {
	   char* param = 0;
	   if ($5) {
	      if ($5->getType() == NT_VARREF) 
		 param = reinterpret_cast<VarRefNode*>($5)->takeName();
	      else if ($5->getType() == NT_BAREWORD) {
		 param = reinterpret_cast<BarewordNode*>($5)->takeString();
		 if (!checkParseOption(PO_ALLOW_BARE_REFS))
		    parse_error("local variable '%s' in catch parameter list declared without '$' prefix, but parse option 'allow-bare-refs' is not set", param);
	      }
	      else
		 parse_error("only one parameter accepted in catch block for exception hash");
	      $5->deref(0);
	   }
	   $$ = new TryStatement(@1.first_line, @7.last_line, $2, $7, param);
	}
        ;

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

class_def:
        nsc_modifiers CLASS_STRING inheritance_list '{' class_attributes '}' {
           check_classmod($1, *($5));
           qore_class_private::parseSetBaseClassList(*($5), $3);
	   $$ = new ObjClassDef($2, $5);
	}
        | nsc_modifiers CLASS_SCOPED_REF inheritance_list '{' class_attributes '}' {
           check_classmod($1, *($5));
	   $$ = new ObjClassDef($2, $5);
           qore_class_private::parseSetBaseClassList(*($5), $3);
	}
	| nsc_modifiers CLASS_STRING inheritance_list ';' {
           // class name is set automatically from name saved in scanner
	   QoreClass *qc = new QoreClass;
           check_classmod($1, *qc);
           qore_class_private::parseSetBaseClassList(*qc, $3);
	   $$ = new ObjClassDef($2, qc);
	}
	| nsc_modifiers CLASS_SCOPED_REF inheritance_list ';' {
           // class name is set automatically from name saved in scanner
	   QoreClass *qc = new QoreClass;
           check_classmod($1, *qc);
	   $$ = new ObjClassDef($2, qc);
           qore_class_private::parseSetBaseClassList(*qc, $3);
	}
        | nsc_modifiers CLASS_STRING inheritance_list '{' '}' {
           // class name is set automatically from name saved in scanner
	   QoreClass *qc = new QoreClass;
           check_classmod($1, *qc);
           qore_class_private::parseSetBaseClassList(*qc, $3);
	   $$ = new ObjClassDef($2, qc);
	}
	| nsc_modifiers CLASS_SCOPED_REF inheritance_list '{' '}' {
           // class name is set automatically from name saved in scanner
	   QoreClass *qc = new QoreClass;
           check_classmod($1, *qc);
	   $$ = new ObjClassDef($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 {
	   $$ = new BCNode($1, false);
	}
        | SCOPED_REF {
	   $$ = new BCNode(new NamedScope($1), false);
	}
        | TOK_PUBLIC IDENTIFIER {
	   $$ = new BCNode($2, false);
	}
        | TOK_PUBLIC SCOPED_REF {
	   $$ = new BCNode(new NamedScope($2), false);
	}
        | TOK_PRIVATE IDENTIFIER {
	   $$ = new BCNode($2, true);
	}
        | TOK_PRIVATE SCOPED_REF {
	   $$ = new BCNode(new NamedScope($2), true);
	}
	;

class_attributes:
	method_definition { 
           $$ = new QoreClass;
	   $1->addAndDelete($$);
	}
        | private_member_list {
	   $$ = new QoreClass;
	   $1->mergePrivate($$);
	   delete $1;
	}
        | public_member_list {
	   $$ = new QoreClass;
	   $1->mergePublic($$);
	   delete $1;
	}
        | unscoped_const_decl {
	   $$ = new QoreClass;
	   $$->parseAddPublicConstant($1->getName(), $1->takeValue());
	   delete $1;
	}
	| class_attributes method_definition { 
	   $2->addAndDelete($1);
	   $$ = $1; 
	}
	| class_attributes private_member_list { 
	   $2->mergePrivate($1);
	   $$ = $1;
	   delete $2;
	}
	| class_attributes public_member_list {
	   $2->mergePublic($1);
	   $$ = $1; 
	   delete $2;
	}
        | class_attributes unscoped_const_decl {
	   $$->parseAddPublicConstant($2->getName(), $2->takeValue());
	   $$ = $1;
	   delete $2;
	}
/*
        // causes a shift-reduce conflict with static method declarations
        | classvardecl {
	   $$ = new QoreClass;
	   qore_class_private::parseAddPublicStaticVar($$, $1->takeName(), $1->takeVarInfo());
	   delete $1;
	}
        | class_attributes classvardecl {
	   qore_class_private::parseAddPublicStaticVar($1, $2->takeName(), $2->takeVarInfo());
	   $$ = $1;
	   delete $2;
	}
*/
        ;

uncqtypedef:
        IDENTIFIER {
	   $$ = new ParserTypeStruct($1);
	}
        | SCOPED_REF {
	   $$ = new ParserScopedTypeStruct($1);
        }
        | '*' IDENTIFIER {
	   $$ = new ParserOrNothingTypeStruct($2);
        }
        | '*' SCOPED_REF {
	   $$ = new ParserScopedOrNothingTypeStruct($2);
        }
        ;

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

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

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

classvardecl: 
	TOK_STATIC uncqtypedef IDENTIFIER ';' {
	   $$ = new ClassVarInfo($3, new QoreVarInfo(@2.first_line, @3.last_line, $2->getTypeInfo(), $2->getParseTypeInfo()));
	   delete $2;
	}
	| TOK_STATIC IDENTIFIER ';' {
	   $$ = new ClassVarInfo($2, new QoreVarInfo(@2.first_line, @2.last_line));
	}
        | TOK_STATIC uncqtypedef IDENTIFIER '=' exp ';' {
	   $$ = new ClassVarInfo($3, new QoreVarInfo(@2.first_line, @5.last_line, $2->getTypeInfo(), $2->getParseTypeInfo(), $5));
	   //printd(5, "new QoreVarInfo = %p (empty: %d)\n", $$->u.varInfo, $$->u.varInfo->empty());
	   delete $2;
        }
        | TOK_STATIC IDENTIFIER '=' exp ';' {
	   $$ = new ClassVarInfo($2, new QoreVarInfo(@2.first_line, @4.last_line, $4));
	}
        | TOK_STATIC uncqtypedef IDENTIFIER '(' myexp ')' ';' {
	   const QoreTypeInfo* t = $2->getTypeInfo();
	   ScopedObjectCallNode* new_call = 0;

	   if (t)
	      parse_error("cannot call constructor for non-class base type '%s'", t->getName());
	   else
	      new_call = new ScopedObjectCallNode(new NamedScope(strdup($2->getClassName())), makeArgs($5));

	   $$ = new ClassVarInfo($3, new QoreVarInfo(@2.first_line, @5.last_line, $2->getTypeInfo(), $2->getParseTypeInfo(), new_call));

	   delete $2;
	}

member2: 
        qtypedef SELF_REF ';' {
	   int fl = $1 ? @1.first_line : @2.first_line;
	   $$ = new MemberInfo($2, new QoreMemberInfo(fl, @2.last_line, $1->getTypeInfo(), $1->getParseTypeInfo()));
	   delete $1;
	   if (checkParseOption(PO_ALLOW_BARE_REFS))
	      parse_error("reference to '$.%s' with the '$.' prefix is not allowed when parse option 'allow-bare-refs' is set", $2);
	}
        | qtypedef SELF_REF '=' exp ';' {
	   int fl = $1 ? @1.first_line : @2.first_line;
	   $$ = new MemberInfo($2, new QoreMemberInfo(fl, @4.last_line, $1->getTypeInfo(), $1->getParseTypeInfo(), $4));
	   delete $1;
	   if (checkParseOption(PO_ALLOW_BARE_REFS))
	      parse_error("reference to '$.%s' with the '$.' prefix is not allowed when parse option 'allow-bare-refs' is set", $2);
	}
        | qtypedef SELF_REF '(' myexp ')' ';' {
	   int fl = $1 ? @1.first_line : @2.first_line;
	   const QoreTypeInfo* t = $1->getTypeInfo();
	   ScopedObjectCallNode* new_call = 0;

	   if (!$1)
	      parse_error("cannot call constructor without declaring the class");
	   else if (t)
	      parse_error("cannot call constructor for non-class base type '%s'", t->getName());
	   else
	      new_call = new ScopedObjectCallNode(new NamedScope(strdup($1->getClassName())), makeArgs($4));

	   $$ = new MemberInfo($2, new QoreMemberInfo(fl, @4.last_line, $1->getTypeInfo(), $1->getParseTypeInfo(), new_call));

	   delete $1;
	   if (checkParseOption(PO_ALLOW_BARE_REFS))
	      parse_error("reference to '$.%s' with the '$.' prefix is not allowed when parse option 'allow-bare-refs' is set", $2);
	}
        | uncqtypedef IDENTIFIER ';' {
	   int fl = @1.first_line;
	   $$ = new MemberInfo($2, new QoreMemberInfo(fl, @2.last_line, $1->getTypeInfo(), $1->getParseTypeInfo()));
	   delete $1;
	   if (!checkParseOption(PO_ALLOW_BARE_REFS))
	      parse_error("member '%s' declared without '$.' prefix, but parse option 'allow-bare-refs' is not set", $2);
	}
        | uncqtypedef IDENTIFIER '=' exp ';' {
	   int fl = @1.first_line;
	   $$ = new MemberInfo($2, new QoreMemberInfo(fl, @4.last_line, $1->getTypeInfo(), $1->getParseTypeInfo(), $4));
	   delete $1;
	   if (!checkParseOption(PO_ALLOW_BARE_REFS))
	      parse_error("member '%s' declared without '$.' prefix, but parse option 'allow-bare-refs' is not set", $2);
	}
        | uncqtypedef IDENTIFIER '(' myexp ')' ';' {
	   int fl = @1.first_line;
	   const QoreTypeInfo* t = $1->getTypeInfo();
	   ScopedObjectCallNode* new_call = 0;

	   if (!$1)
	      parse_error("cannot call constructor without declaring the class");
	   else if (t)
	      parse_error("cannot call constructor for non-class base type '%s'", t->getName());
	   else
	      new_call = new ScopedObjectCallNode(new NamedScope(strdup($1->getClassName())), makeArgs($4));

	   $$ = new MemberInfo($2, new QoreMemberInfo(fl, @4.last_line, $1->getTypeInfo(), $1->getParseTypeInfo(), new_call));

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

member:
	SELF_REF {
	   $$ = new MemberInfo($1);
	   if (checkParseOption(PO_ALLOW_BARE_REFS))
	      parse_error("reference to '$.%s' with the '$.' prefix is not allowed when parse option 'allow-bare-refs' is set", $1);
        }
        | SELF_REF SCOPED_REF {
	   $$ = new MemberInfo($1, new QoreMemberInfo(@1.first_line, @2.last_line, $2));
	   if (checkParseOption(PO_ALLOW_BARE_REFS))
	      parse_error("reference to '$.%s' with the '$.' prefix is not allowed when parse option 'allow-bare-refs' is set", $1);
	}
        ;

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

member_list2:
        member2 {
	   $$ = new MemberList($1);
        }
        | member_list2 member2 {
	   $1->add($2);
	   $$ = $1;
	}
	;

ident_openparen:
        IDENTIFIER '(' {
	   $$ = $1;
        }
        | KW_IDENTIFIER_OPENPAREN {
	   $$ = $1;
        }
        ;

method_definition:
        nsc_modifiers ident_openparen myexp ')' base_constructor_list return_value block {
           if ($1 & OFM_ABSTRACT) {
              if ($7)
                 parse_error("'abstract' can only be used with no implementation (remove the body and terminate the declaration with ';' or remove the 'abstract')");
              else
                 parse_error("'abstract' can only be used with no implementation (terminate the declaration with ';' instead of '{}')");
           }
	   checkMethod($2, $5, $6);
	   $$ = new MethodNode($2, newMethodVariant($2, $1, @3.first_line, @3.last_line, $3, $5, $6, $7, $1 & OFM_DEPRECATED), $1 & OFM_STATIC);
	}
        | nsc_modifiers uncqtypedef ident_openparen myexp ')' base_constructor_list block {
           if ($1 & OFM_ABSTRACT) {
              if ($7)
                 parse_error("'abstract' can only be used with no implementation (remove the body and terminate the declaration with ';' or remove the 'abstract')");
              else
                 parse_error("'abstract' can only be used with no implementation (terminate the declaration with ';' instead of '{}')");
           }
	   RetTypeInfo* rt = new RetTypeInfo($2->getParseTypeInfo(), $2->getTypeInfo());
	   delete $2;
	   
	   checkMethod($3, $6, rt);

	   $$ = new MethodNode($3, newMethodVariant($3, $1, @4.first_line, @4.last_line, $4, $6, rt, $7, $1 & OFM_DEPRECATED), $1 & OFM_STATIC);
	}
        | nsc_modifiers ident_openparen myexp ')' base_constructor_list return_value ';' {
           if (!($1 & OFM_ABSTRACT))
              parse_error("non-abstract method declared without any body; either declare the method 'abstract' or declare an empty method body with '{}'");
           checkMethod($2, $5, $6);
           $$ = new MethodNode($2, newMethodVariant($2, $1, @3.first_line, @3.last_line, $3, $5, $6, 0, $1 & OFM_DEPRECATED), $1 & OFM_STATIC);
        }
        | nsc_modifiers uncqtypedef ident_openparen myexp ')' base_constructor_list ';' {
           if (!($1 & OFM_ABSTRACT))
              parse_error("non-abstract method declared without any body; either declare the method 'abstract' or declare an empty method body with '{}'");
           RetTypeInfo* rt = new RetTypeInfo($2->getParseTypeInfo(), $2->getTypeInfo());
           delete $2;

           checkMethod($3, $6, rt);

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

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

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

           tryAddMethod($1, $3, @5.first_line, @5.last_line, $5, $7, rt, 0);
        }
	| SCOPED_REF '(' myexp ')' base_constructor_list return_value block {
	   tryAddMethod(0, $1, @3.first_line, @3.last_line, $3, $5, $6, $7);
	}
	| uncqtypedef SCOPED_REF '(' myexp ')' base_constructor_list block {
	   RetTypeInfo* rt = new RetTypeInfo($1->getParseTypeInfo(), $1->getTypeInfo());
	   delete $1;

	   tryAddMethod(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 ')' {
	   $$ = new BCANode($1, makeArgs($3));
	}
	| SCOPED_REF '(' myexp ')' {
	   $$ = new BCANode(new NamedScope($1), makeArgs($3));
	}
	;

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

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

method_modifier:
	TOK_PRIVATE { $$ = OFM_PRIVATE; }
        | 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 {
	   if (!$2) {
	      parse_error("missing type declaration after 'returns'");
	      $$ = 0;
	   }
	   else {
	      $$ = new RetTypeInfo($2->getParseTypeInfo(), $2->getTypeInfo());
	      delete $2;
	   }
	}
        | /* nothing */
        {
	   $$ = 0;
	}

sub_def:
	TOK_SUB ident_openparen myexp ')' return_value block {
	   int ar = get_pop_argv_ref();
	   int64 flags = ar > 0 ? QC_USES_EXTRA_ARGS : QC_NO_FLAGS;
           $$ = new ParseUserFunction($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 ? QC_USES_EXTRA_ARGS : QC_NO_FLAGS;

	   RetTypeInfo* rt = new RetTypeInfo($1->getParseTypeInfo(), $1->getTypeInfo());
	   delete $1;

	   $$ = new ParseUserFunction($3, new UserFunctionVariant($6, @4.first_line, @4.last_line, $4, rt, false, flags));
	}
	| method_modifiers TOK_SUB ident_openparen myexp ')' return_value block {
	   check_funcmod($1);
	   int ar = get_pop_argv_ref();
	   int64 flags = ar > 0 ? QC_USES_EXTRA_ARGS : QC_NO_FLAGS;
	   if ($1 & OFM_DEPRECATED)
	      flags |= QC_DEPRECATED;
           
	   $$ = new ParseUserFunction($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 {
	   check_funcmod($1);
	   int ar = get_pop_argv_ref();
	   int64 flags = ar > 0 ? QC_USES_EXTRA_ARGS : QC_NO_FLAGS;
	   if ($1 & OFM_DEPRECATED)
	      flags |= QC_DEPRECATED;

	   RetTypeInfo* rt = new RetTypeInfo($2->getParseTypeInfo(), $2->getTypeInfo());
	   delete $2;

	   $$ = new ParseUserFunction($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 ? QC_USES_EXTRA_ARGS : QC_NO_FLAGS;
           $$ = new ParseScopedUserFunction($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 ? QC_USES_EXTRA_ARGS : QC_NO_FLAGS;

	   RetTypeInfo* rt = new RetTypeInfo($1->getParseTypeInfo(), $1->getTypeInfo());
	   delete $1;

	   $$ = new ParseScopedUserFunction($3, new UserFunctionVariant($7, @5.first_line, @5.last_line, $5, rt, false, flags));
	}
	| method_modifiers TOK_SUB SCOPED_REF '(' myexp ')' return_value block {
	   check_funcmod($1);
	   int ar = get_pop_argv_ref();
	   int64 flags = ar > 0 ? QC_USES_EXTRA_ARGS : QC_NO_FLAGS;
	   if ($1 & OFM_DEPRECATED)
	      flags |= QC_DEPRECATED;
           
	   $$ = new ParseScopedUserFunction($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 {
	   check_funcmod($1);
	   int ar = get_pop_argv_ref();
	   int64 flags = ar > 0 ? QC_USES_EXTRA_ARGS : QC_NO_FLAGS;
	   if ($1 & OFM_DEPRECATED)
	      flags |= QC_DEPRECATED;

	   RetTypeInfo* rt = new RetTypeInfo($2->getParseTypeInfo(), $2->getTypeInfo());
	   delete $2;

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

list:
	exp ',' exp
        { $$ = splice_expressions($1, $3); }
        | exp ',' {
	   QoreListNode* l;
	   if ($1 && $1->getType() == NT_LIST) 
	      l = reinterpret_cast<QoreListNode*>($1);
	   else {
	      l = new QoreListNode;
	      l->push($1);
	      // parse_error("problem in parsing ',' in list: left side of comma is not a list (type: '%s')", $1 ? $1->getTypeName() : "NOTHING");
	      // so we don't insert null values in the parse tree
	   }
	   $$ = l;
        }
        ;

hash:
        hash_element {
	   $$ = new QoreHashNode;
	   $$->setKeyValue($1->key, $1->value, 0);
	   delete $1;
	}
	| hash ',' hash_element	{
	   if ($1->existsKey($3->key))
	      QoreHashNode::doDuplicateKeyWarning($3->key);
	   $1->setKeyValue($3->key, $3->value, 0);
	   delete $3;
	   $$ = $1;
	}
        | hash ','
	{ /* empty ',' on end of hash */ $$=$1; }
	;

hash_element:
	scalar ':' exp
	{ $$ = new HashElement($1, $3); }
        | IDENTIFIER ':' exp     // for constants
        { $$ = new HashElement(HE_TAG_CONST, $1, $3); }
        | SCOPED_REF ':' exp     // for scoped constants
        { $$ = new HashElement(HE_TAG_SCOPED_CONST, $1, $3); }
	;

exp:    scalar
        { $$ = $1; }
        | BINARY
        { $$ = $1; }
        | list
        { $$ = $1; }
	| '(' hash ')'
	{ $$ = $2; }
        | SCOPED_REF
        { $$ = new ScopedRefNode($1); }
        | qtypedef VAR_REF {
	   if (!$1 && !strcmp($2, "argv"))
	      inc_argv_ref();

	   if ($1)
	      $$ = new VarRefDeclNode(@1.first_line, @2.last_line, $2, get_var_type(), $1->getTypeInfo(), $1->getParseTypeInfo());
	   else
	      $$ = new VarRefNode($2, get_var_type());

	   delete $1;
	   if (checkParseOption(PO_ALLOW_BARE_REFS))
	      parse_error("reference to variable '%s' with the '$' sign is not allowed when parse option 'allow-bare-refs' is set", $2);
	}
        | qtypedef SCOPED_VREF {
	   if (!$1 && !strcmp($2, "argv"))
	      inc_argv_ref();

	   if ($1)
	      $$ = new VarRefDeclNode(@1.first_line, @2.last_line, $2, get_var_type(), $1->getTypeInfo(), $1->getParseTypeInfo());
	   else
	      $$ = new VarRefNode($2, get_var_type());

	   delete $1;
	   if (checkParseOption(PO_ALLOW_BARE_REFS))
	      parse_error("reference to variable '%s' with the '$' sign is not allowed when parse option 'allow-bare-refs' is set", $2);
	}
        | uncqtypedef IDENTIFIER {
	   $$ = new VarRefDeclNode(@1.first_line, @2.last_line, $2, get_var_type(), $1->getTypeInfo(), $1->getParseTypeInfo());
	   delete $1;
	}
        | TOK_MY uncqtypedef IDENTIFIER {
	   VarRefDeclNode* v = new VarRefDeclNode(@2.first_line, @3.last_line, $3, VT_LOCAL, $2->getTypeInfo(), $2->getParseTypeInfo());
	   v->setExplicitScope();
	   $$ = v;
	   delete $2;
	   if (!checkParseOption(PO_ALLOW_BARE_REFS))
	      parse_error("local variable '%s' declared without '$' prefix, but parse option 'allow-bare-refs' is not set", $3);
	}
        | TOK_MY IDENTIFIER {
	   // see if types are required
	   if (checkParseOption(PO_REQUIRE_TYPES))
	      parse_error("local variable '%s' declared without type information, but parse options require all declarations to have type information", $2);
	   VarRefNode* v = new VarRefNode($2, VT_LOCAL);
	   v->setExplicitScope();
	   $$ = v;
	   if (!checkParseOption(PO_ALLOW_BARE_REFS))
	      parse_error("local variable '%s' declared without '$' prefix, but parse option 'allow-bare-refs' is not set", $2);
	}
        | TOK_MY qtypedef VAR_REF {
	   VarRefNode* v;
	   if ($2) {
	      v = new VarRefDeclNode(@3.first_line, @3.last_line, $3, VT_LOCAL, $2->getTypeInfo(), $2->getParseTypeInfo());
	      delete $2;
	   }
	   else {
	      // see if types are required
	      if (checkParseOption(PO_REQUIRE_TYPES))
		 parse_error("local variable '%s' declared without type information, but parse options require all declarations to have type information", $3);
	      v = new VarRefNode($3, VT_LOCAL);
	   }
	   v->setExplicitScope();
	   $$ = v;
	   if (checkParseOption(PO_ALLOW_BARE_REFS))
	      parse_error("reference to variable '$%s' with the '$' sign is not allowed when parse option 'allow-bare-refs' is set", $3);
	}
        | TOK_MY '(' list ')' {
	   $3->setVariableList();
	   ListIterator li($3);
	   while (li.next()) {
	      AbstractQoreNode* n = li.getValue();
	      qore_type_t t = get_node_type(n);
	      if (t == NT_BAREWORD) {
		 BarewordNode* b = reinterpret_cast<BarewordNode*>(n);
		 if (checkParseOption(PO_REQUIRE_TYPES))
		    parse_error("local variable '%s' declared without type information, but parse options require all declarations to have type information", b->str);
		 if (!checkParseOption(PO_ALLOW_BARE_REFS))
		    parse_error("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(b->takeString(), VT_LOCAL);
		 v->setExplicitScope();
		 b->deref();
		 AbstractQoreNode** p = li.getValuePtr();
		 *p = v;
	      }
	      else if (t == NT_VARREF) {
		 VarRefNode* v = reinterpret_cast<VarRefNode*>(n);
		 // see if types are required
		 if (checkParseOption(PO_REQUIRE_TYPES) && !v->isDecl())
		    parse_error("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("illegal use of 'my %s' in local variable declaration list", v->getName());
		    else if (v->getType() == VT_GLOBAL)
		       parse_error("illegal use of 'our %s' in local variable declaration list", v->getName());
		 }
		 else
		    v->makeLocal();
		 v->setExplicitScope();
	      }
	      else
		 parse_error("element %d in list following 'my' is not a variable reference (%s)", li.index(), get_type_name(n));
	   }
	   $$ = $3;
        }
        | gvardecl {
           $$ = $1->makeVar();
        }
	| IDENTIFIER
        {
	   if (checkParseOption(PO_ASSUME_LOCAL & PO_ALLOW_BARE_REFS) && !strcmp($1, "argv"))
	      inc_argv_ref();

	   $$ = new BarewordNode($1);
	}
	| CONTEXT_REF
        { $$ = new ContextrefNode($1); }
        | TOK_CONTEXT_ROW
        { $$ = new ContextRowNode; }
        | COMPLEX_CONTEXT_REF
        { $$ = new ComplexContextrefNode($1); } 
        | TOK_FIND exp TOK_IN exp TOK_WHERE '(' exp ')'
        { $$ = new FindNode($2, $4, $7); }
	| exp PLUS_EQUALS exp {
           $$ = new QorePlusEqualsOperatorNode($1, $3);
	}
        | exp MINUS_EQUALS exp {
           $$ = new QoreMinusEqualsOperatorNode($1, $3);
	}
        | exp AND_EQUALS exp {
           $$ = new QoreAndEqualsOperatorNode($1, $3);
	}
        | exp OR_EQUALS exp {
           $$ = new QoreOrEqualsOperatorNode($1, $3);
	}
        | exp MODULA_EQUALS exp {
           $$ = new QoreModulaEqualsOperatorNode($1, $3);
	}
        | exp MULTIPLY_EQUALS exp {
           $$ = new QoreMultiplyEqualsOperatorNode($1, $3);
	}
        | exp DIVIDE_EQUALS exp {
           $$ = new QoreDivideEqualsOperatorNode($1, $3);
	}
        | exp XOR_EQUALS exp {
           $$ = new QoreXorEqualsOperatorNode($1, $3);
	}
        | exp SHIFT_LEFT_EQUALS exp {
           $$ = new QoreShiftLeftEqualsOperatorNode($1, $3);
	}
        | exp SHIFT_RIGHT_EQUALS exp {
           $$ = new QoreShiftRightEqualsOperatorNode($1, $3);
	}
	| exp '=' exp {
	   if ($1 && $1->getType() == NT_LIST) {
	      $$ = new QoreTreeNode($1, OP_LIST_ASSIGNMENT, $3);
	   }
	   else {
	      $$ = new QoreAssignmentOperatorNode($1, $3);
	   }
	   //print_tree($1, 0);
	}
        | TOK_EXISTS exp
        { $$ = new QoreTreeNode($2, OP_EXISTS, 0); }
        | TOK_ELEMENTS exp
        { $$ = new QoreTreeNode($2, OP_ELEMENTS, 0); }
        | exp TOK_INSTANCEOF IDENTIFIER {
	   $$ = new QoreTreeNode($1, OP_INSTANCEOF, new ClassRefNode($3));
	}
        | exp TOK_INSTANCEOF SCOPED_REF {
	   $$ = new QoreTreeNode($1, OP_INSTANCEOF, new ClassRefNode($3));
	}
        | TOK_KEYS exp
        { $$ = new QoreTreeNode($2, OP_KEYS, 0); }
        | TOK_UNSHIFT exp {  // unshift list, element
	   QoreListNode* l = $2 && $2->getType() == NT_LIST ? reinterpret_cast<QoreListNode*>($2) : 0;
	   if (!l || l->size() != 2) {
	      parse_error("invalid arguments to unshift, expected: lvalue, expression (%s)", $2->getTypeName());
	      $$ = makeErrorTree($2, 0);
	   }
	   else {
	      AbstractQoreNode* lv = l->shift();
	      $$ = new QoreTreeNode(lv, OP_UNSHIFT, l->shift());
	      $2->deref(0);
	   }
	}
	| TOK_SHIFT exp {
	   $$ = new QoreTreeNode($2, OP_SHIFT, 0); 
	}
        | TOK_PUSH exp {  // push lvalue-list, element
	   QoreListNode* l = $2 && $2->getType() == NT_LIST ? reinterpret_cast<QoreListNode*>($2) : 0;
	   if (!l || l->size() != 2) {
	      parse_error("invalid arguments to push, expected: lvalue, expression (%s)", $2->getTypeName());
	      $$ = makeErrorTree($2, 0);
	   }
	   else {
	      AbstractQoreNode* lv = l->shift();
	      $$ = new QoreTreeNode(lv, OP_PUSH, l->shift());
	      $2->deref(0);
	   }
	}
        | TOK_POP exp {
	   $$ = new QoreTreeNode($2, OP_POP, 0); 
	}
	| TOK_CHOMP exp {
	   $$ = new QoreTreeNode($2, OP_CHOMP, 0); 
	}
	| TOK_TRIM exp {
	   $$ = new QoreTreeNode($2, OP_TRIM, 0); 
	}
        | TOK_SPLICE exp {  // splice lvalue-list, offset, [length, list]
	   QoreListNode* l = $2 && $2->getType() == NT_LIST ? reinterpret_cast<QoreListNode*>($2) : 0;
	   if (!l) {
	      parse_error("invalid arguments to splice, expected: lvalue, offset exp[, length exp[, list exp]] (got %s)", get_type_name($2));
	      $$ = makeErrorTree($2, 0);
	   }
	   else if (l->size() < 2) {
	      parse_error("too few arguments to splice, expected at least 2 arguments as follows: lvalue, offset exp[, length exp[, list exp]] (got %d arguments)", l->size());
	      $$ = makeErrorTree($2, 0);
	   }
	   else if (l->size() > 4) {
	      parse_error("too many arguments to splice, expected maximum 4 arguments as follows: lvalue, offset exp[, length exp[, list exp]] (got %d arguments)", l->size());
	      $$ = makeErrorTree($2, 0);
	   }
	   else {
	      AbstractQoreNode* lv = l->shift();
	      $$ = new QoreSpliceOperatorNode(lv, l->shift(), l->shift(), l->shift());
	      discard(l, 0);
	   }
	}
        | TOK_EXTRACT exp {  // extract lvalue-list, offset, [length, list]
	   QoreListNode* l = $2 && $2->getType() == NT_LIST ? reinterpret_cast<QoreListNode*>($2) : 0;
	   if (!l) {
	      parse_error("invalid arguments to extract, expected: lvalue, offset exp[, length exp[, list exp]] (got %s)", get_type_name($2));
	      $$ = makeErrorTree($2, 0);
	   }
	   else if (l->size() < 2) {
	      parse_error("too few arguments to extract, expected at least 2 arguments as follows: lvalue, offset exp[, length exp[, list exp]] (got %d arguments)", l->size());
	      $$ = makeErrorTree($2, 0);
	   }
	   else if (l->size() > 4) {
	      parse_error("too many arguments to extract, expected maximum 4 arguments as follows: lvalue, offset exp[, length exp[, list exp]] (got %d arguments)", l->size());
	      $$ = makeErrorTree($2, 0);
	   }
	   else {
	      AbstractQoreNode* lv = l->shift();
	      $$ = new QoreExtractOperatorNode(lv, l->shift(), l->shift(), l->shift());
	      discard(l, 0);
	   }
	}
        | TOK_MAP exp {
	   pop_ignore_numeric_argv_ref();
	   QoreListNode* l = $2 && $2->getType() == NT_LIST ? reinterpret_cast<QoreListNode*>($2) : 0;
	   int len = l ? (int)l->size() : 0;
	   if (!l || len < 2 || len > 3) {
	      parse_error("invalid arguments to map operator, expected: 2 or 3 element list (code expression, list argument, [select expression]), got: '%s'", get_type_name($2));
	      $$ = makeErrorTree($2, 0);
	   }
	   else if (len == 2) {
	      AbstractQoreNode* map_exp = l->shift();
	      AbstractQoreNode* arg = l->shift();
	      $$ = new QoreTreeNode(map_exp, OP_MAP, arg);
	      $2->deref(0);
	   }
	   else {
	      AbstractQoreNode* map_exp = l->shift();
	      $$ = new QoreTreeNode(map_exp, OP_MAP_SELECT, l);
	   }
	}
        | TOK_FOLDR exp {
	   pop_ignore_numeric_argv_ref();
	   QoreListNode* l = $2 && $2->getType() == NT_LIST ? reinterpret_cast<QoreListNode*>($2) : 0;
	   if (!l || l->size() != 2) {
	      parse_error("invalid arguments to foldr operator, expected: 2-element list expected: 2-element list (fold expression and list expression), got: '%s'", get_type_name($2));
	      $$ = makeErrorTree($2, 0);
	   }
	   else {
	      AbstractQoreNode* code_exp = l->shift();
	      AbstractQoreNode* arg = l->shift();
	      $$ = new QoreTreeNode(code_exp, OP_FOLDR, arg);
	      $2->deref(0);
	   }
	}
        | TOK_FOLDL exp {
	   pop_ignore_numeric_argv_ref();
	   QoreListNode* l = $2 && $2->getType() == NT_LIST ? reinterpret_cast<QoreListNode*>($2) : 0;
	   if (!l || l->size() != 2) {
	      parse_error("invalid arguments to foldl operator, expected: 2-element list (fold expression and list expression), got: '%s'", get_type_name($2));
	      $$ = makeErrorTree($2, 0);
	   }
	   else {
	      AbstractQoreNode* code_exp = l->shift();
	      AbstractQoreNode* arg = l->shift();
	      $$ = new QoreTreeNode(code_exp, OP_FOLDL, arg);
	      $2->deref(0);
	   }
	}
        | TOK_SELECT exp {
	   pop_ignore_numeric_argv_ref();
	   QoreListNode* l = $2 && $2->getType() == NT_LIST ? reinterpret_cast<QoreListNode*>($2) : 0;
	   if (!l || l->size() != 2) {
	      parse_error("invalid arguments to select operator, expected: 2-element list (list expression and select expression) got: '%s'", get_type_name($2));
	      $$ = makeErrorTree($2, 0);
	   }
	   else {
	      AbstractQoreNode* arg = l->shift();
	      AbstractQoreNode* select_exp = l->shift();
	      $$ = new QoreTreeNode(arg, OP_SELECT, select_exp);
	      $2->deref(0);
	   }
	}
        | exp '?' exp ':' exp
        { $$ = new QoreTreeNode($1, OP_QUESTION_MARK, make_list($3, $5)); } 
        | P_INCREMENT exp {  // pre-increment
           $$ = new QorePreIncrementOperatorNode($2);
        }
        | exp P_INCREMENT {  // post-increment
           $$ = new QorePostIncrementOperatorNode($1);
        }
        | P_DECREMENT exp {  // pre-decrement
           $$ = new QorePreDecrementOperatorNode($2);
        }
        | exp P_DECREMENT {   // post-decrement
           $$ = new QorePostDecrementOperatorNode($1);
        }
	| exp '(' myexp ')' {
	   //printd(5, "1=%s (%p), 3=%s (%p)\n", $1->getTypeName(), $1, $3 ? $3->getTypeName() : "n/a", $3); 
	   qore_type_t t = get_node_type($1);
	   if (t == NT_BAREWORD) {
	      BarewordNode* b = reinterpret_cast<BarewordNode*>($1);
	      // take string from node and delete node
	      char* str = b->takeString();
	      b->deref();
	      printd(5, "parsing call %s() args=%p %s\n", str, $3, $3 ? $3->getTypeName() : "n/a");
	      $$ = new FunctionCallNode(str, makeArgs($3));
	   }
	   else if (t == NT_CONSTANT) {
	      ScopedRefNode* c = reinterpret_cast<ScopedRefNode*>($1);
	      // 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(ns, makeArgs($3));
	   }
	   else if (t == NT_SELF_VARREF) {
	      SelfVarrefNode* v = reinterpret_cast<SelfVarrefNode*>($1);
	      // 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(str, makeArgs($3));
	   }
	   else {
	      QoreTreeNode* tree;
	      
	      if (t == NT_TREE) {
		 tree = reinterpret_cast<QoreTreeNode*>($1);
		 if (!(tree->getOp() == OP_OBJECT_REF && tree->right && tree->right->getType() == NT_STRING))
		    tree = 0;
	      }
	      else
		 tree = 0;

	      if (tree) {
		 // create an object method call node
		 // take the string
		 QoreStringNode* str = reinterpret_cast<QoreStringNode*>(tree->right);
		 char* cstr = str->giveBuffer();
		 str->deref();
		 
		 //printd(5, "method call to %s: tree=%s, args=%p %s\n", cstr, tree->left->getTypeName(), $3, $3 ? $3->getTypeName() : "n/a");

		 MethodCallNode* mc = new MethodCallNode(cstr, makeArgs($3));
                 
                 // destroy the tree and create a new operator object
                 assert(tree->is_unique());
                 AbstractQoreNode* l = tree->left;
                 tree->right = 0;
                 tree->left = 0;
                 tree->deref(0);

		 $$ = new QoreDotEvalOperatorNode(l, mc);
	      }
	      else {
		 $$ = 0;
		 if ($1 && $1->getType() == NT_VARREF) {
		    VarRefNode* r = reinterpret_cast<VarRefNode*>($1);
		    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("INVALID-CODE-REFERENCE-CALL", "%s variable '%s' declared as a function reference call", r->getType() == VT_GLOBAL ? "global" : "local", r->getName());
		    }
		 }
		 if (!$$) {
		    $$ = new CallReferenceCallNode($1, makeArgs($3));
		    printd(5, "made new CallReferenceCallNode: %p exp=%p %s\n", $$, $1, get_type_name($1));
		 }
	      }
	   }
	}
        | BASE_CLASS_CALL '(' myexp ')' {
	   printd(5, "parsing in-object base class method call %s()\n", $1->ostr);
	   if (!strcmp($1->getIdentifier(), "copy"))
	      parse_error("illegal call to base class copy method '%s'", $1->ostr);

	   $$ = new SelfFunctionCallNode($1, makeArgs($3));
	}
        | KW_IDENTIFIER_OPENPAREN myexp ')' {
	   printd(5, "parsing call %s()\n", $1);
	   $$ = new FunctionCallNode($1, makeArgs($2));
        }
        | SELF_REF
	{
	   $$ = new SelfVarrefNode($1); 
	   if (checkParseOption(PO_ALLOW_BARE_REFS))
	      parse_error("reference to '$.%s' with the '$.' prefix is not allowed when parse option 'allow-bare-refs' is set", $1);
	}
        | exp LOGICAL_AND exp	     { $$ = new QoreTreeNode($1, OP_LOG_AND, $3); }
        | exp LOGICAL_OR exp	     { $$ = new QoreTreeNode($1, OP_LOG_OR, $3); }
        | exp '|' exp		     { $$ = new QoreTreeNode($1, OP_BIN_OR, $3); }
        | exp '&' exp		     { $$ = new QoreTreeNode($1, OP_BIN_AND, $3); }
        | exp '^' exp		     { $$ = new QoreTreeNode($1, OP_BIN_XOR, $3); }
        | exp REGEX_MATCH REGEX {
	   $$ = new QoreTreeNode($1, OP_REGEX_MATCH, $3);
	}
        | exp REGEX_NMATCH REGEX {
	   $$ = new QoreTreeNode($1, OP_REGEX_NMATCH, $3);
	}
        | exp REGEX_MATCH REGEX_SUBST {
	   //printf("REGEX_SUBST: '%s'\n", $3->getPattern()->getBuffer());
	   $$ = new QoreTreeNode($1, OP_REGEX_SUBST, $3);
	}
        | exp REGEX_MATCH REGEX_TRANS {
	   $$ = new QoreTreeNode($1, OP_REGEX_TRANS, $3);
	}
        | exp REGEX_MATCH REGEX_EXTRACT
        { $$ = new QoreTreeNode($1, OP_REGEX_EXTRACT, $3); }
        | exp '>' exp		     { $$ = new QoreLogicalGreaterThanOperatorNode($1, $3); }
        | exp '<' exp                { $$ = new QoreLogicalLessThanOperatorNode($1, $3); }
        | exp LOGICAL_CMP exp	     { $$ = new QoreTreeNode($1, OP_LOG_CMP, $3); }
        | exp LOGICAL_EQ exp	     { $$ = new QoreLogicalEqualsOperatorNode($1, $3); }
        | exp ABSOLUTE_EQ exp        { $$ = new QoreTreeNode($1, OP_ABSOLUTE_EQ, $3); }
        | exp ABSOLUTE_NE exp        { $$ = new QoreTreeNode($1, OP_ABSOLUTE_NE, $3); }
        | exp LOGICAL_NE exp	     { $$ = new QoreLogicalNotEqualsOperatorNode($1, $3); }
        | exp LOGICAL_LE exp	     { $$ = new QoreLogicalLessThanOrEqualsOperatorNode($1, $3); }
        | exp LOGICAL_GE exp	     { $$ = new QoreLogicalGreaterThanOrEqualsOperatorNode($1, $3); }
        | exp SHIFT_LEFT exp	     { $$ = new QoreTreeNode($1, OP_SHIFT_LEFT, $3); }
        | exp SHIFT_RIGHT exp	     { $$ = new QoreTreeNode($1, OP_SHIFT_RIGHT, $3); }
        | exp '+' exp		     { $$ = new QoreTreeNode($1, OP_PLUS, $3); }
        | exp '-' exp		     { $$ = new QoreTreeNode($1, OP_MINUS, $3); }
        | exp '%' exp		     { $$ = new QoreTreeNode($1, OP_MODULA, $3); }
        | exp '/' exp		     { $$ = new QoreTreeNode($1, OP_DIV, $3); }
        | exp '*' exp		     { $$ = new QoreTreeNode($1, OP_MULT, $3); }
        | '-' 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
	   $$ = QoreUnaryMinusOperatorNode::makeNode($2);
	}
        | '~' exp		     { $$ = new QoreTreeNode($2, OP_BIN_NOT, 0); }
        | '!' exp                    { $$ = new QoreTreeNode($2, OP_NOT, 0); }
        | '\\' exp {
	   qore_type_t t = $2 ? $2->getType() : 0;
	   //printd(5, "backslash exp line %d, type %s\n", @2.first_line, $2->getTypeName());

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

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

	      if (make_ref) {
		 //printd(5, "type=%s\n", $2->getTypeName());
		 $$ = new ParseReferenceNode($2);
	      }
	   }
	}
	| TOK_NEW IDENTIFIER '(' myexp ')' {
	   $$ = new ScopedObjectCallNode(new NamedScope($2), makeArgs($4));
	   if (checkParseOption(PO_NO_NEW))
	      parse_error("illegal use of the 'new' operator (conflicts with parse option NO_NEW)");
        }
	| TOK_NEW SCOPED_REF '(' myexp ')' {
	   $$ = new ScopedObjectCallNode(new NamedScope($2), makeArgs($4));
	   if (checkParseOption(PO_NO_NEW))
	      parse_error("illegal use of the 'new' operator (conflicts with parse option NO_NEW)");
        }
        | TOK_BACKGROUND exp {
	   $$ = new QoreTreeNode($2, OP_BACKGROUND, 0);
	   // check to see if the expression is legal
	   if (checkParseOption(PO_NO_THREAD_CONTROL))
	      parse_error("illegal use of \"background\" operator (conflicts with parse option NO_THREAD_CONTROL)");
	   else if (!hasEffect($2))
	      parse_error("argument to background operator (%s) has no effect", get_type_name($2));
	}
        | BACKQUOTE {
	   $$ = new BackquoteNode($1);
	   if (checkParseOption(PO_NO_EXTERNAL_PROCESS))
	      parse_error("illegal use of backquote operator (conflicts with parse option NO_EXTERNAL_PROCESS)");
	}
        | exp '[' exp ']'            { $$ = new QoreTreeNode($1, OP_LIST_REF, $3); }
        | exp '{' exp '}'            { $$ = new QoreTreeNode($1, OP_OBJECT_REF, $3); }
        | exp '.' exp {
	   $$ = process_dot($1, $3);
	}
        | exp DOT_KW_IDENTIFIER {
	   $$ = new QoreTreeNode($1, OP_OBJECT_REF, $2);
	}
	| '(' exp ')' { 
	   $$ = $2;
	   if ($2) {
              switch ($2->getType()) {
                 case NT_LIST:
                    reinterpret_cast<QoreListNode*>($2)->setFinalized();
                    break;
                 case NT_FUNCTION_CALL:
                    reinterpret_cast<FunctionCallNode*>($2)->setFinalized();
                    break;
              }
           }
	}
        | '(' ')' { QoreListNode* l = new QoreListNode; l->setFinalized(); $$ = l; }
        | TOK_SUB '(' myexp ')' return_value block {
	   int ar = get_pop_argv_ref();
	   $$ = new QoreClosureParseNode(new UserClosureFunction($6, @3.first_line, @3.last_line, $3, $5, false, ar > 0 ? QC_USES_EXTRA_ARGS : QC_NO_FLAGS));
	}
        | uncqtypedef TOK_SUB '(' myexp ')' block {
	   RetTypeInfo* rt = new RetTypeInfo($1->getParseTypeInfo(), $1->getTypeInfo());
	   delete $1;

	   int ar = get_pop_argv_ref();
	   $$ = new QoreClosureParseNode(new UserClosureFunction($6, @4.first_line, @4.last_line, $4, rt, false, ar > 0 ? QC_USES_EXTRA_ARGS : QC_NO_FLAGS));
	}
	| method_modifiers TOK_SUB '(' myexp ')' return_value block {
	   check_funcmod($1);
	   int ar = get_pop_argv_ref();
	   int64 flags = ar > 0 ? QC_USES_EXTRA_ARGS : QC_NO_FLAGS;
	   if ($1 & OFM_DEPRECATED)
	      flags |= QC_DEPRECATED;
	   $$ = new QoreClosureParseNode(new UserClosureFunction($7, @4.first_line, @4.last_line, $4, $6, $1 | OFM_SYNCED, flags));
	}
	| method_modifiers uncqtypedef TOK_SUB '(' myexp ')' block {
	   check_funcmod($1);

	   RetTypeInfo* rt = new RetTypeInfo($2->getParseTypeInfo(), $2->getTypeInfo());
	   delete $2;

	   int ar = get_pop_argv_ref();
	   int64 flags = ar > 0 ? QC_USES_EXTRA_ARGS : QC_NO_FLAGS;
	   if ($1 & OFM_DEPRECATED)
	      flags |= QC_DEPRECATED;
	   $$ = new QoreClosureParseNode(new UserClosureFunction($7, @5.first_line, @5.last_line, $5, rt, $1 | OFM_SYNCED, flags));
	}
        | IMPLICIT_ARG_REF { 
	   $$ = $1;
	   inc_numeric_argv_ref();
	}
        | IMPLICIT_ELEMENT {
	   $$ = new QoreImplicitElementNode;
	}
        | TOK_DELETE exp {
	   $$ = new QoreDeleteOperatorNode($2);
	}
        | TOK_REMOVE exp {
	   $$ = new QoreRemoveOperatorNode($2);
	}
        | QORE_CAST '(' exp ')' {
           $$ = new QoreCastOperatorNode($1, $3);
	}
	;

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

scalar:
	QFLOAT        { $$ = new QoreFloatNode($1); }
        | INTEGER     { $$ = new QoreBigIntNode($1); }
        | string      { $$ = $1; }
        | DATETIME    { $$ = $1; }
        | NUMBER      { $$ = $1; }
	;

%%
