michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef frontend_ParseNode_h michael@0: #define frontend_ParseNode_h michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: michael@0: #include "frontend/TokenStream.h" michael@0: michael@0: namespace js { michael@0: namespace frontend { michael@0: michael@0: template michael@0: struct ParseContext; michael@0: michael@0: class FullParseHandler; michael@0: class FunctionBox; michael@0: class ObjectBox; michael@0: michael@0: /* michael@0: * Indicates a location in the stack that an upvar value can be retrieved from michael@0: * as a two tuple of (level, slot). michael@0: * michael@0: * Some existing client code uses the level value as a delta, or level "skip" michael@0: * quantity. We could probably document that through use of more types at some michael@0: * point in the future. michael@0: */ michael@0: class UpvarCookie michael@0: { michael@0: uint32_t level_ : SCOPECOORD_HOPS_BITS; michael@0: uint32_t slot_ : SCOPECOORD_SLOT_BITS; michael@0: michael@0: void checkInvariants() { michael@0: static_assert(sizeof(UpvarCookie) == sizeof(uint32_t), michael@0: "Not necessary for correctness, but good for ParseNode memory use"); michael@0: } michael@0: michael@0: public: michael@0: // Steal one value to represent the sentinel value for UpvarCookie. michael@0: static const uint32_t FREE_LEVEL = SCOPECOORD_HOPS_LIMIT - 1; michael@0: bool isFree() const { return level_ == FREE_LEVEL; } michael@0: michael@0: uint32_t level() const { JS_ASSERT(!isFree()); return level_; } michael@0: uint32_t slot() const { JS_ASSERT(!isFree()); return slot_; } michael@0: michael@0: // This fails and issues an error message if newLevel or newSlot are too large. michael@0: bool set(TokenStream &ts, unsigned newLevel, uint32_t newSlot) { michael@0: if (newLevel >= FREE_LEVEL) michael@0: return ts.reportError(JSMSG_TOO_DEEP, js_function_str); michael@0: michael@0: if (newSlot >= SCOPECOORD_SLOT_LIMIT) michael@0: return ts.reportError(JSMSG_TOO_MANY_LOCALS); michael@0: michael@0: level_ = newLevel; michael@0: slot_ = newSlot; michael@0: return true; michael@0: } michael@0: michael@0: void makeFree() { michael@0: level_ = FREE_LEVEL; michael@0: slot_ = 0; // value doesn't matter, won't be used michael@0: JS_ASSERT(isFree()); michael@0: } michael@0: }; michael@0: michael@0: #define FOR_EACH_PARSE_NODE_KIND(F) \ michael@0: F(NOP) \ michael@0: F(SEMI) \ michael@0: F(COMMA) \ michael@0: F(CONDITIONAL) \ michael@0: F(COLON) \ michael@0: F(POS) \ michael@0: F(NEG) \ michael@0: F(PREINCREMENT) \ michael@0: F(POSTINCREMENT) \ michael@0: F(PREDECREMENT) \ michael@0: F(POSTDECREMENT) \ michael@0: F(DOT) \ michael@0: F(ELEM) \ michael@0: F(ARRAY) \ michael@0: F(ELISION) \ michael@0: F(STATEMENTLIST) \ michael@0: F(LABEL) \ michael@0: F(OBJECT) \ michael@0: F(CALL) \ michael@0: F(NAME) \ michael@0: F(NUMBER) \ michael@0: F(STRING) \ michael@0: F(REGEXP) \ michael@0: F(TRUE) \ michael@0: F(FALSE) \ michael@0: F(NULL) \ michael@0: F(THIS) \ michael@0: F(FUNCTION) \ michael@0: F(IF) \ michael@0: F(ELSE) \ michael@0: F(SWITCH) \ michael@0: F(CASE) \ michael@0: F(DEFAULT) \ michael@0: F(WHILE) \ michael@0: F(DOWHILE) \ michael@0: F(FOR) \ michael@0: F(BREAK) \ michael@0: F(CONTINUE) \ michael@0: F(VAR) \ michael@0: F(CONST) \ michael@0: F(WITH) \ michael@0: F(RETURN) \ michael@0: F(NEW) \ michael@0: F(DELETE) \ michael@0: F(TRY) \ michael@0: F(CATCH) \ michael@0: F(CATCHLIST) \ michael@0: F(FINALLY) \ michael@0: F(THROW) \ michael@0: F(DEBUGGER) \ michael@0: F(YIELD) \ michael@0: F(YIELD_STAR) \ michael@0: F(GENEXP) \ michael@0: F(ARRAYCOMP) \ michael@0: F(ARRAYPUSH) \ michael@0: F(LEXICALSCOPE) \ michael@0: F(LET) \ michael@0: F(IMPORT) \ michael@0: F(IMPORT_SPEC_LIST) \ michael@0: F(IMPORT_SPEC) \ michael@0: F(EXPORT) \ michael@0: F(EXPORT_FROM) \ michael@0: F(EXPORT_SPEC_LIST) \ michael@0: F(EXPORT_SPEC) \ michael@0: F(EXPORT_BATCH_SPEC) \ michael@0: F(SEQ) \ michael@0: F(FORIN) \ michael@0: F(FOROF) \ michael@0: F(FORHEAD) \ michael@0: F(ARGSBODY) \ michael@0: F(SPREAD) \ michael@0: \ michael@0: /* Unary operators. */ \ michael@0: F(TYPEOF) \ michael@0: F(VOID) \ michael@0: F(NOT) \ michael@0: F(BITNOT) \ michael@0: \ michael@0: /* \ michael@0: * Binary operators. \ michael@0: * These must be in the same order as TOK_OR and friends in TokenStream.h. \ michael@0: */ \ michael@0: F(OR) \ michael@0: F(AND) \ michael@0: F(BITOR) \ michael@0: F(BITXOR) \ michael@0: F(BITAND) \ michael@0: F(STRICTEQ) \ michael@0: F(EQ) \ michael@0: F(STRICTNE) \ michael@0: F(NE) \ michael@0: F(LT) \ michael@0: F(LE) \ michael@0: F(GT) \ michael@0: F(GE) \ michael@0: F(INSTANCEOF) \ michael@0: F(IN) \ michael@0: F(LSH) \ michael@0: F(RSH) \ michael@0: F(URSH) \ michael@0: F(ADD) \ michael@0: F(SUB) \ michael@0: F(STAR) \ michael@0: F(DIV) \ michael@0: F(MOD) \ michael@0: \ michael@0: /* Assignment operators (= += -= etc.). */ \ michael@0: /* ParseNode::isAssignment assumes all these are consecutive. */ \ michael@0: F(ASSIGN) \ michael@0: F(ADDASSIGN) \ michael@0: F(SUBASSIGN) \ michael@0: F(BITORASSIGN) \ michael@0: F(BITXORASSIGN) \ michael@0: F(BITANDASSIGN) \ michael@0: F(LSHASSIGN) \ michael@0: F(RSHASSIGN) \ michael@0: F(URSHASSIGN) \ michael@0: F(MULASSIGN) \ michael@0: F(DIVASSIGN) \ michael@0: F(MODASSIGN) michael@0: michael@0: /* michael@0: * Parsing builds a tree of nodes that directs code generation. This tree is michael@0: * not a concrete syntax tree in all respects (for example, || and && are left michael@0: * associative, but (A && B && C) translates into the right-associated tree michael@0: * > so that code generation can emit a left-associative branch michael@0: * around when A is false). Nodes are labeled by kind, with a michael@0: * secondary JSOp label when needed. michael@0: * michael@0: * The long comment after this enum block describes the kinds in detail. michael@0: */ michael@0: enum ParseNodeKind michael@0: { michael@0: #define EMIT_ENUM(name) PNK_##name, michael@0: FOR_EACH_PARSE_NODE_KIND(EMIT_ENUM) michael@0: #undef EMIT_ENUM michael@0: PNK_LIMIT, /* domain size */ michael@0: PNK_BINOP_FIRST = PNK_OR, michael@0: PNK_BINOP_LAST = PNK_MOD, michael@0: PNK_ASSIGNMENT_START = PNK_ASSIGN, michael@0: PNK_ASSIGNMENT_LAST = PNK_MODASSIGN michael@0: }; michael@0: michael@0: /* michael@0: * Label Variant Members michael@0: * ----- ------- ------- michael@0: * michael@0: * PNK_FUNCTION name pn_funbox: ptr to js::FunctionBox holding function michael@0: * object containing arg and var properties. We michael@0: * create the function object at parse (not emit) michael@0: * time to specialize arg and var bytecodes early. michael@0: * pn_body: PNK_ARGSBODY, ordinarily; michael@0: * PNK_LEXICALSCOPE for implicit function in genexpr michael@0: * pn_cookie: static level and var index for function michael@0: * pn_dflags: PND_* definition/use flags (see below) michael@0: * pn_blockid: block id number michael@0: * PNK_ARGSBODY list list of formal parameters followed by: michael@0: * PNK_STATEMENTLIST node for function body michael@0: * statements, michael@0: * PNK_RETURN for expression closure, or michael@0: * PNK_SEQ for expression closure with michael@0: * destructured formal parameters michael@0: * pn_count: 1 + number of formal parameters michael@0: * pn_tree: PNK_ARGSBODY or PNK_STATEMENTLIST node michael@0: * PNK_SPREAD unary pn_kid: expression being spread michael@0: * michael@0: * michael@0: * PNK_STATEMENTLIST list pn_head: list of pn_count statements michael@0: * PNK_IF ternary pn_kid1: cond, pn_kid2: then, pn_kid3: else or null. michael@0: * In body of a comprehension or desugared generator michael@0: * expression, pn_kid2 is PNK_YIELD, PNK_ARRAYPUSH, michael@0: * or (if the push was optimized away) empty michael@0: * PNK_STATEMENTLIST. michael@0: * PNK_SWITCH binary pn_left: discriminant michael@0: * pn_right: list of PNK_CASE nodes, with at most one michael@0: * PNK_DEFAULT node, or if there are let bindings michael@0: * in the top level of the switch body's cases, a michael@0: * PNK_LEXICALSCOPE node that contains the list of michael@0: * PNK_CASE nodes. michael@0: * PNK_CASE, binary pn_left: case expr michael@0: * pn_right: PNK_STATEMENTLIST node for this case's michael@0: * statements michael@0: * PNK_DEFAULT binary pn_left: null michael@0: * pn_right: PNK_STATEMENTLIST node for this default's michael@0: * statements michael@0: * pn_val: constant value if lookup or table switch michael@0: * PNK_WHILE binary pn_left: cond, pn_right: body michael@0: * PNK_DOWHILE binary pn_left: body, pn_right: cond michael@0: * PNK_FOR binary pn_left: either PNK_FORIN (for-in statement), michael@0: * PNK_FOROF (for-of) or PNK_FORHEAD (for(;;)) michael@0: * pn_right: body michael@0: * PNK_FORIN ternary pn_kid1: PNK_VAR to left of 'in', or nullptr michael@0: * its pn_xflags may have PNX_POPVAR michael@0: * bit set michael@0: * pn_kid2: PNK_NAME or destructuring expr michael@0: * to left of 'in'; if pn_kid1, then this michael@0: * is a clone of pn_kid1->pn_head michael@0: * pn_kid3: object expr to right of 'in' michael@0: * PNK_FOROF ternary pn_kid1: PNK_VAR to left of 'of', or nullptr michael@0: * its pn_xflags may have PNX_POPVAR michael@0: * bit set michael@0: * pn_kid2: PNK_NAME or destructuring expr michael@0: * to left of 'of'; if pn_kid1, then this michael@0: * is a clone of pn_kid1->pn_head michael@0: * pn_kid3: expr to right of 'of' michael@0: * PNK_FORHEAD ternary pn_kid1: init expr before first ';' or nullptr michael@0: * pn_kid2: cond expr before second ';' or nullptr michael@0: * pn_kid3: update expr after second ';' or nullptr michael@0: * PNK_THROW unary pn_op: JSOP_THROW, pn_kid: exception michael@0: * PNK_TRY ternary pn_kid1: try block michael@0: * pn_kid2: null or PNK_CATCHLIST list of michael@0: * PNK_LEXICALSCOPE nodes, each with pn_expr pointing michael@0: * to a PNK_CATCH node michael@0: * pn_kid3: null or finally block michael@0: * PNK_CATCH ternary pn_kid1: PNK_NAME, PNK_ARRAY, or PNK_OBJECT catch var node michael@0: * (PNK_ARRAY or PNK_OBJECT if destructuring) michael@0: * pn_kid2: null or the catch guard expression michael@0: * pn_kid3: catch block statements michael@0: * PNK_BREAK name pn_atom: label or null michael@0: * PNK_CONTINUE name pn_atom: label or null michael@0: * PNK_WITH binary-obj pn_left: head expr; pn_right: body; pn_binary_obj: StaticWithObject michael@0: * PNK_VAR, list pn_head: list of PNK_NAME or PNK_ASSIGN nodes michael@0: * PNK_CONST each name node has either michael@0: * pn_used: false michael@0: * pn_atom: variable name michael@0: * pn_expr: initializer or null michael@0: * or michael@0: * pn_used: true michael@0: * pn_atom: variable name michael@0: * pn_lexdef: def node michael@0: * each assignment node has michael@0: * pn_left: PNK_NAME with pn_used true and michael@0: * pn_lexdef (NOT pn_expr) set michael@0: * pn_right: initializer michael@0: * PNK_RETURN unary pn_kid: return expr or null michael@0: * PNK_SEMI unary pn_kid: expr or null statement michael@0: * pn_prologue: true if Directive Prologue member michael@0: * in original source, not introduced via michael@0: * constant folding or other tree rewriting michael@0: * PNK_LABEL name pn_atom: label, pn_expr: labeled statement michael@0: * michael@0: * michael@0: * All left-associated binary trees of the same type are optimized into lists michael@0: * to avoid recursion when processing expression chains. michael@0: * PNK_COMMA list pn_head: list of pn_count comma-separated exprs michael@0: * PNK_ASSIGN binary pn_left: lvalue, pn_right: rvalue michael@0: * PNK_ADDASSIGN, binary pn_left: lvalue, pn_right: rvalue michael@0: * PNK_SUBASSIGN, pn_op: JSOP_ADD for +=, etc. michael@0: * PNK_BITORASSIGN, michael@0: * PNK_BITXORASSIGN, michael@0: * PNK_BITANDASSIGN, michael@0: * PNK_LSHASSIGN, michael@0: * PNK_RSHASSIGN, michael@0: * PNK_URSHASSIGN, michael@0: * PNK_MULASSIGN, michael@0: * PNK_DIVASSIGN, michael@0: * PNK_MODASSIGN michael@0: * PNK_CONDITIONAL ternary (cond ? trueExpr : falseExpr) michael@0: * pn_kid1: cond, pn_kid2: then, pn_kid3: else michael@0: * PNK_OR binary pn_left: first in || chain, pn_right: rest of chain michael@0: * PNK_AND binary pn_left: first in && chain, pn_right: rest of chain michael@0: * PNK_BITOR binary pn_left: left-assoc | expr, pn_right: ^ expr michael@0: * PNK_BITXOR binary pn_left: left-assoc ^ expr, pn_right: & expr michael@0: * PNK_BITAND binary pn_left: left-assoc & expr, pn_right: EQ expr michael@0: * michael@0: * PNK_EQ, binary pn_left: left-assoc EQ expr, pn_right: REL expr michael@0: * PNK_NE, michael@0: * PNK_STRICTEQ, michael@0: * PNK_STRICTNE michael@0: * PNK_LT, binary pn_left: left-assoc REL expr, pn_right: SH expr michael@0: * PNK_LE, michael@0: * PNK_GT, michael@0: * PNK_GE michael@0: * PNK_LSH, binary pn_left: left-assoc SH expr, pn_right: ADD expr michael@0: * PNK_RSH, michael@0: * PNK_URSH michael@0: * PNK_ADD binary pn_left: left-assoc ADD expr, pn_right: MUL expr michael@0: * pn_xflags: if a left-associated binary PNK_ADD michael@0: * tree has been flattened into a list (see above michael@0: * under ), pn_xflags will contain michael@0: * PNX_STRCAT if at least one list element is a michael@0: * string literal (PNK_STRING); if such a list has michael@0: * any non-string, non-number term, pn_xflags will michael@0: * contain PNX_CANTFOLD. michael@0: * PNK_SUB binary pn_left: left-assoc SH expr, pn_right: ADD expr michael@0: * PNK_STAR, binary pn_left: left-assoc MUL expr, pn_right: UNARY expr michael@0: * PNK_DIV, pn_op: JSOP_MUL, JSOP_DIV, JSOP_MOD michael@0: * PNK_MOD michael@0: * PNK_POS, unary pn_kid: UNARY expr michael@0: * PNK_NEG michael@0: * PNK_TYPEOF, unary pn_kid: UNARY expr michael@0: * PNK_VOID, michael@0: * PNK_NOT, michael@0: * PNK_BITNOT michael@0: * PNK_PREINCREMENT, unary pn_kid: MEMBER expr michael@0: * PNK_POSTINCREMENT, michael@0: * PNK_PREDECREMENT, michael@0: * PNK_POSTDECREMENT michael@0: * PNK_NEW list pn_head: list of ctor, arg1, arg2, ... argN michael@0: * pn_count: 1 + N (where N is number of args) michael@0: * ctor is a MEMBER expr michael@0: * PNK_DELETE unary pn_kid: MEMBER expr michael@0: * PNK_DOT name pn_expr: MEMBER expr to left of . michael@0: * pn_atom: name to right of . michael@0: * PNK_ELEM binary pn_left: MEMBER expr to left of [ michael@0: * pn_right: expr between [ and ] michael@0: * PNK_CALL list pn_head: list of call, arg1, arg2, ... argN michael@0: * pn_count: 1 + N (where N is number of args) michael@0: * call is a MEMBER expr naming a callable object michael@0: * PNK_GENEXP list Exactly like PNK_CALL, used for the implicit call michael@0: * in the desugaring of a generator-expression. michael@0: * PNK_ARRAY list pn_head: list of pn_count array element exprs michael@0: * [,,] holes are represented by PNK_ELISION nodes michael@0: * pn_xflags: PN_ENDCOMMA if extra comma at end michael@0: * PNK_OBJECT list pn_head: list of pn_count binary PNK_COLON nodes michael@0: * PNK_COLON binary key-value pair in object initializer or michael@0: * destructuring lhs michael@0: * pn_left: property id, pn_right: value michael@0: * var {x} = object destructuring shorthand shares michael@0: * PN_NAME node for x on left and right of PNK_COLON michael@0: * node in PNK_OBJECT's list, has PNX_DESTRUCT flag michael@0: * PNK_NAME, name pn_atom: name, string, or object atom michael@0: * PNK_STRING pn_op: JSOP_NAME, JSOP_STRING, or JSOP_OBJECT michael@0: * If JSOP_NAME, pn_op may be JSOP_*ARG or JSOP_*VAR michael@0: * with pn_cookie telling (staticLevel, slot) (see michael@0: * jsscript.h's UPVAR macros) and pn_dflags telling michael@0: * const-ness and static analysis results michael@0: * PNK_REGEXP nullary pn_objbox: RegExp model object michael@0: * PNK_NAME name If pn_used, PNK_NAME uses the lexdef member instead michael@0: * of the expr member it overlays michael@0: * PNK_NUMBER dval pn_dval: double value of numeric literal michael@0: * PNK_TRUE, nullary pn_op: JSOp bytecode michael@0: * PNK_FALSE, michael@0: * PNK_NULL, michael@0: * PNK_THIS michael@0: * michael@0: * PNK_LEXICALSCOPE name pn_objbox: block object in ObjectBox holder michael@0: * pn_expr: block body michael@0: * PNK_ARRAYCOMP list pn_count: 1 michael@0: * pn_head: list of 1 element, which is block michael@0: * enclosing for loop(s) and optionally michael@0: * if-guarded PNK_ARRAYPUSH michael@0: * PNK_ARRAYPUSH unary pn_op: JSOP_ARRAYCOMP michael@0: * pn_kid: array comprehension expression michael@0: * PNK_NOP nullary michael@0: */ michael@0: enum ParseNodeArity michael@0: { michael@0: PN_NULLARY, /* 0 kids, only pn_atom/pn_dval/etc. */ michael@0: PN_UNARY, /* one kid, plus a couple of scalars */ michael@0: PN_BINARY, /* two kids, plus a couple of scalars */ michael@0: PN_BINARY_OBJ, /* two kids, plus an objbox */ michael@0: PN_TERNARY, /* three kids */ michael@0: PN_CODE, /* module or function definition node */ michael@0: PN_LIST, /* generic singly linked list */ michael@0: PN_NAME /* name use or definition node */ michael@0: }; michael@0: michael@0: struct Definition; michael@0: michael@0: class LabeledStatement; michael@0: class LoopControlStatement; michael@0: class BreakStatement; michael@0: class ContinueStatement; michael@0: class ConditionalExpression; michael@0: class PropertyAccess; michael@0: michael@0: class ParseNode michael@0: { michael@0: uint32_t pn_type : 16, /* PNK_* type */ michael@0: pn_op : 8, /* see JSOp enum and jsopcode.tbl */ michael@0: pn_arity : 5, /* see ParseNodeArity enum */ michael@0: pn_parens : 1, /* this expr was enclosed in parens */ michael@0: pn_used : 1, /* name node is on a use-chain */ michael@0: pn_defn : 1; /* this node is a Definition */ michael@0: michael@0: ParseNode(const ParseNode &other) MOZ_DELETE; michael@0: void operator=(const ParseNode &other) MOZ_DELETE; michael@0: michael@0: public: michael@0: ParseNode(ParseNodeKind kind, JSOp op, ParseNodeArity arity) michael@0: : pn_type(kind), pn_op(op), pn_arity(arity), pn_parens(0), pn_used(0), pn_defn(0), michael@0: pn_pos(0, 0), pn_offset(0), pn_next(nullptr), pn_link(nullptr) michael@0: { michael@0: JS_ASSERT(kind < PNK_LIMIT); michael@0: memset(&pn_u, 0, sizeof pn_u); michael@0: } michael@0: michael@0: ParseNode(ParseNodeKind kind, JSOp op, ParseNodeArity arity, const TokenPos &pos) michael@0: : pn_type(kind), pn_op(op), pn_arity(arity), pn_parens(0), pn_used(0), pn_defn(0), michael@0: pn_pos(pos), pn_offset(0), pn_next(nullptr), pn_link(nullptr) michael@0: { michael@0: JS_ASSERT(kind < PNK_LIMIT); michael@0: memset(&pn_u, 0, sizeof pn_u); michael@0: } michael@0: michael@0: JSOp getOp() const { return JSOp(pn_op); } michael@0: void setOp(JSOp op) { pn_op = op; } michael@0: bool isOp(JSOp op) const { return getOp() == op; } michael@0: michael@0: ParseNodeKind getKind() const { michael@0: JS_ASSERT(pn_type < PNK_LIMIT); michael@0: return ParseNodeKind(pn_type); michael@0: } michael@0: void setKind(ParseNodeKind kind) { michael@0: JS_ASSERT(kind < PNK_LIMIT); michael@0: pn_type = kind; michael@0: } michael@0: bool isKind(ParseNodeKind kind) const { return getKind() == kind; } michael@0: michael@0: ParseNodeArity getArity() const { return ParseNodeArity(pn_arity); } michael@0: bool isArity(ParseNodeArity a) const { return getArity() == a; } michael@0: void setArity(ParseNodeArity a) { pn_arity = a; } michael@0: michael@0: bool isAssignment() const { michael@0: ParseNodeKind kind = getKind(); michael@0: return PNK_ASSIGNMENT_START <= kind && kind <= PNK_ASSIGNMENT_LAST; michael@0: } michael@0: michael@0: /* Boolean attributes. */ michael@0: bool isInParens() const { return pn_parens; } michael@0: void setInParens(bool enabled) { pn_parens = enabled; } michael@0: bool isUsed() const { return pn_used; } michael@0: void setUsed(bool enabled) { pn_used = enabled; } michael@0: bool isDefn() const { return pn_defn; } michael@0: void setDefn(bool enabled) { pn_defn = enabled; } michael@0: michael@0: static const unsigned NumDefinitionFlagBits = 10; michael@0: static const unsigned NumListFlagBits = 10; michael@0: static const unsigned NumBlockIdBits = 22; michael@0: static_assert(NumDefinitionFlagBits == NumListFlagBits, michael@0: "Assumed below to achieve consistent blockid offset"); michael@0: static_assert(NumDefinitionFlagBits + NumBlockIdBits <= 32, michael@0: "This is supposed to fit in a single uint32_t"); michael@0: michael@0: TokenPos pn_pos; /* two 16-bit pairs here, for 64 bits */ michael@0: int32_t pn_offset; /* first generated bytecode offset */ michael@0: ParseNode *pn_next; /* intrinsic link in parent PN_LIST */ michael@0: ParseNode *pn_link; /* def/use link (alignment freebie) */ michael@0: michael@0: union { michael@0: struct { /* list of next-linked nodes */ michael@0: ParseNode *head; /* first node in list */ michael@0: ParseNode **tail; /* ptr to ptr to last node in list */ michael@0: uint32_t count; /* number of nodes in list */ michael@0: uint32_t xflags:NumListFlagBits, /* see PNX_* below */ michael@0: blockid:NumBlockIdBits; /* see name variant below */ michael@0: } list; michael@0: struct { /* ternary: if, for(;;), ?: */ michael@0: ParseNode *kid1; /* condition, discriminant, etc. */ michael@0: ParseNode *kid2; /* then-part, case list, etc. */ michael@0: ParseNode *kid3; /* else-part, default case, etc. */ michael@0: } ternary; michael@0: struct { /* two kids if binary */ michael@0: ParseNode *left; michael@0: ParseNode *right; michael@0: union { michael@0: unsigned iflags; /* JSITER_* flags for PNK_FOR node */ michael@0: ObjectBox *objbox; /* Only for PN_BINARY_OBJ */ michael@0: }; michael@0: } binary; michael@0: struct { /* one kid if unary */ michael@0: ParseNode *kid; michael@0: bool prologue; /* directive prologue member (as michael@0: pn_prologue) */ michael@0: } unary; michael@0: struct { /* name, labeled statement, etc. */ michael@0: union { michael@0: JSAtom *atom; /* lexical name or label atom */ michael@0: ObjectBox *objbox; /* block or regexp object */ michael@0: FunctionBox *funbox; /* function object */ michael@0: }; michael@0: union { michael@0: ParseNode *expr; /* module or function body, var michael@0: initializer, argument default, or michael@0: base object of PNK_DOT */ michael@0: Definition *lexdef; /* lexical definition for this use */ michael@0: }; michael@0: UpvarCookie cookie; /* upvar cookie with absolute frame michael@0: level (not relative skip), possibly michael@0: in current frame */ michael@0: uint32_t dflags:NumDefinitionFlagBits, /* see PND_* below */ michael@0: blockid:NumBlockIdBits; /* block number, for subset dominance michael@0: computation */ michael@0: } name; michael@0: struct { michael@0: double value; /* aligned numeric literal value */ michael@0: DecimalPoint decimalPoint; /* Whether the number has a decimal point */ michael@0: } number; michael@0: class { michael@0: friend class LoopControlStatement; michael@0: PropertyName *label; /* target of break/continue statement */ michael@0: } loopControl; michael@0: } pn_u; michael@0: michael@0: #define pn_modulebox pn_u.name.modulebox michael@0: #define pn_funbox pn_u.name.funbox michael@0: #define pn_body pn_u.name.expr michael@0: #define pn_cookie pn_u.name.cookie michael@0: #define pn_dflags pn_u.name.dflags michael@0: #define pn_blockid pn_u.name.blockid michael@0: #define pn_index pn_u.name.blockid /* reuse as object table index */ michael@0: #define pn_head pn_u.list.head michael@0: #define pn_tail pn_u.list.tail michael@0: #define pn_count pn_u.list.count michael@0: #define pn_xflags pn_u.list.xflags michael@0: #define pn_kid1 pn_u.ternary.kid1 michael@0: #define pn_kid2 pn_u.ternary.kid2 michael@0: #define pn_kid3 pn_u.ternary.kid3 michael@0: #define pn_left pn_u.binary.left michael@0: #define pn_right pn_u.binary.right michael@0: #define pn_pval pn_u.binary.pval michael@0: #define pn_iflags pn_u.binary.iflags michael@0: #define pn_binary_obj pn_u.binary.objbox michael@0: #define pn_kid pn_u.unary.kid michael@0: #define pn_prologue pn_u.unary.prologue michael@0: #define pn_atom pn_u.name.atom michael@0: #define pn_objbox pn_u.name.objbox michael@0: #define pn_expr pn_u.name.expr michael@0: #define pn_lexdef pn_u.name.lexdef michael@0: #define pn_dval pn_u.number.value michael@0: michael@0: protected: michael@0: void init(TokenKind type, JSOp op, ParseNodeArity arity) { michael@0: pn_type = type; michael@0: pn_op = op; michael@0: pn_arity = arity; michael@0: pn_parens = false; michael@0: JS_ASSERT(!pn_used); michael@0: JS_ASSERT(!pn_defn); michael@0: pn_next = pn_link = nullptr; michael@0: } michael@0: michael@0: static ParseNode *create(ParseNodeKind kind, ParseNodeArity arity, FullParseHandler *handler); michael@0: michael@0: public: michael@0: /* michael@0: * Append right to left, forming a list node. |left| must have the given michael@0: * kind and op, and op must be left-associative. michael@0: */ michael@0: static ParseNode * michael@0: append(ParseNodeKind tt, JSOp op, ParseNode *left, ParseNode *right, FullParseHandler *handler); michael@0: michael@0: /* michael@0: * Either append right to left, if left meets the conditions necessary to michael@0: * append (see append), or form a binary node whose children are right and michael@0: * left. michael@0: */ michael@0: static ParseNode * michael@0: newBinaryOrAppend(ParseNodeKind kind, JSOp op, ParseNode *left, ParseNode *right, michael@0: FullParseHandler *handler, ParseContext *pc, michael@0: bool foldConstants); michael@0: michael@0: inline PropertyName *name() const; michael@0: inline JSAtom *atom() const; michael@0: michael@0: /* michael@0: * The pn_expr and lexdef members are arms of an unsafe union. Unless you michael@0: * know exactly what you're doing, use only the following methods to access michael@0: * them. For less overhead and assertions for protection, use pn->expr() michael@0: * and pn->lexdef(). Otherwise, use pn->maybeExpr() and pn->maybeLexDef(). michael@0: */ michael@0: ParseNode *expr() const { michael@0: JS_ASSERT(!pn_used); michael@0: JS_ASSERT(pn_arity == PN_NAME || pn_arity == PN_CODE); michael@0: return pn_expr; michael@0: } michael@0: michael@0: Definition *lexdef() const { michael@0: JS_ASSERT(pn_used || isDeoptimized()); michael@0: JS_ASSERT(pn_arity == PN_NAME); michael@0: return pn_lexdef; michael@0: } michael@0: michael@0: ParseNode *maybeExpr() { return pn_used ? nullptr : expr(); } michael@0: Definition *maybeLexDef() { return pn_used ? lexdef() : nullptr; } michael@0: michael@0: Definition *resolve(); michael@0: michael@0: /* PN_CODE and PN_NAME pn_dflags bits. */ michael@0: #define PND_LET 0x01 /* let (block-scoped) binding */ michael@0: #define PND_CONST 0x02 /* const binding (orthogonal to let) */ michael@0: #define PND_ASSIGNED 0x04 /* set if ever LHS of assignment */ michael@0: #define PND_PLACEHOLDER 0x08 /* placeholder definition for lexdep */ michael@0: #define PND_BOUND 0x10 /* bound to a stack or global slot */ michael@0: #define PND_DEOPTIMIZED 0x20 /* former pn_used name node, pn_lexdef michael@0: still valid, but this use no longer michael@0: optimizable via an upvar opcode */ michael@0: #define PND_CLOSED 0x40 /* variable is closed over */ michael@0: #define PND_DEFAULT 0x80 /* definition is an arg with a default */ michael@0: #define PND_IMPLICITARGUMENTS 0x100 /* the definition is a placeholder for michael@0: 'arguments' that has been converted michael@0: into a definition after the function michael@0: body has been parsed. */ michael@0: #define PND_EMITTEDFUNCTION 0x200 /* hoisted function that was emitted */ michael@0: michael@0: static_assert(PND_EMITTEDFUNCTION < (1 << NumDefinitionFlagBits), "Not enough bits"); michael@0: michael@0: /* Flags to propagate from uses to definition. */ michael@0: #define PND_USE2DEF_FLAGS (PND_ASSIGNED | PND_CLOSED) michael@0: michael@0: /* PN_LIST pn_xflags bits. */ michael@0: #define PNX_POPVAR 0x01 /* PNK_VAR or PNK_CONST last result michael@0: needs popping */ michael@0: #define PNX_GROUPINIT 0x02 /* var [a, b] = [c, d]; unit list */ michael@0: #define PNX_FUNCDEFS 0x04 /* contains top-level function statements */ michael@0: #define PNX_SETCALL 0x08 /* call expression in lvalue context */ michael@0: #define PNX_DESTRUCT 0x10 /* destructuring special cases: michael@0: 1. shorthand syntax used, at present michael@0: object destructuring ({x,y}) only; michael@0: 2. code evaluating destructuring michael@0: arguments occurs before function michael@0: body */ michael@0: #define PNX_SPECIALARRAYINIT 0x20 /* one or more of michael@0: 1. array initialiser has holes michael@0: 2. array initializer has spread node */ michael@0: #define PNX_NONCONST 0x40 /* initialiser has non-constants */ michael@0: michael@0: static_assert(PNX_NONCONST < (1 << NumListFlagBits), "Not enough bits"); michael@0: michael@0: unsigned frameLevel() const { michael@0: JS_ASSERT(pn_arity == PN_CODE || pn_arity == PN_NAME); michael@0: return pn_cookie.level(); michael@0: } michael@0: michael@0: uint32_t frameSlot() const { michael@0: JS_ASSERT(pn_arity == PN_CODE || pn_arity == PN_NAME); michael@0: return pn_cookie.slot(); michael@0: } michael@0: michael@0: bool functionIsHoisted() const { michael@0: JS_ASSERT(pn_arity == PN_CODE && getKind() == PNK_FUNCTION); michael@0: JS_ASSERT(isOp(JSOP_LAMBDA) || // lambda, genexpr michael@0: isOp(JSOP_LAMBDA_ARROW) || // arrow function michael@0: isOp(JSOP_DEFFUN) || // non-body-level function statement michael@0: isOp(JSOP_NOP) || // body-level function stmt in global code michael@0: isOp(JSOP_GETLOCAL) || // body-level function stmt in function code michael@0: isOp(JSOP_GETARG)); // body-level function redeclaring formal michael@0: return !isOp(JSOP_LAMBDA) && !isOp(JSOP_LAMBDA_ARROW) && !isOp(JSOP_DEFFUN); michael@0: } michael@0: michael@0: /* michael@0: * True if this statement node could be a member of a Directive Prologue: an michael@0: * expression statement consisting of a single string literal. michael@0: * michael@0: * This considers only the node and its children, not its context. After michael@0: * parsing, check the node's pn_prologue flag to see if it is indeed part of michael@0: * a directive prologue. michael@0: * michael@0: * Note that a Directive Prologue can contain statements that cannot michael@0: * themselves be directives (string literals that include escape sequences michael@0: * or escaped newlines, say). This member function returns true for such michael@0: * nodes; we use it to determine the extent of the prologue. michael@0: */ michael@0: JSAtom *isStringExprStatement() const { michael@0: if (getKind() == PNK_SEMI) { michael@0: JS_ASSERT(pn_arity == PN_UNARY); michael@0: ParseNode *kid = pn_kid; michael@0: if (kid && kid->getKind() == PNK_STRING && !kid->pn_parens) michael@0: return kid->pn_atom; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: inline bool test(unsigned flag) const; michael@0: michael@0: bool isLet() const { return test(PND_LET); } michael@0: bool isConst() const { return test(PND_CONST); } michael@0: bool isPlaceholder() const { return test(PND_PLACEHOLDER); } michael@0: bool isDeoptimized() const { return test(PND_DEOPTIMIZED); } michael@0: bool isAssigned() const { return test(PND_ASSIGNED); } michael@0: bool isClosed() const { return test(PND_CLOSED); } michael@0: bool isBound() const { return test(PND_BOUND); } michael@0: bool isImplicitArguments() const { return test(PND_IMPLICITARGUMENTS); } michael@0: michael@0: /* True if pn is a parsenode representing a literal constant. */ michael@0: bool isLiteral() const { michael@0: return isKind(PNK_NUMBER) || michael@0: isKind(PNK_STRING) || michael@0: isKind(PNK_TRUE) || michael@0: isKind(PNK_FALSE) || michael@0: isKind(PNK_NULL); michael@0: } michael@0: michael@0: /* Return true if this node appears in a Directive Prologue. */ michael@0: bool isDirectivePrologueMember() const { return pn_prologue; } michael@0: michael@0: #ifdef JS_HAS_GENERATOR_EXPRS michael@0: ParseNode *generatorExpr() const { michael@0: JS_ASSERT(isKind(PNK_GENEXP)); michael@0: ParseNode *callee = this->pn_head; michael@0: ParseNode *body = callee->pn_body; michael@0: JS_ASSERT(body->isKind(PNK_LEXICALSCOPE)); michael@0: return body->pn_expr; michael@0: } michael@0: #endif michael@0: michael@0: inline void markAsAssigned(); michael@0: michael@0: /* michael@0: * Compute a pointer to the last element in a singly-linked list. NB: list michael@0: * must be non-empty for correct PN_LAST usage -- this is asserted! michael@0: */ michael@0: ParseNode *last() const { michael@0: JS_ASSERT(pn_arity == PN_LIST); michael@0: JS_ASSERT(pn_count != 0); michael@0: return (ParseNode *)(uintptr_t(pn_tail) - offsetof(ParseNode, pn_next)); michael@0: } michael@0: michael@0: void initNumber(double value, DecimalPoint decimalPoint) { michael@0: JS_ASSERT(pn_arity == PN_NULLARY); michael@0: JS_ASSERT(getKind() == PNK_NUMBER); michael@0: pn_u.number.value = value; michael@0: pn_u.number.decimalPoint = decimalPoint; michael@0: } michael@0: michael@0: void makeEmpty() { michael@0: JS_ASSERT(pn_arity == PN_LIST); michael@0: pn_head = nullptr; michael@0: pn_tail = &pn_head; michael@0: pn_count = 0; michael@0: pn_xflags = 0; michael@0: pn_blockid = 0; michael@0: } michael@0: michael@0: void initList(ParseNode *pn) { michael@0: JS_ASSERT(pn_arity == PN_LIST); michael@0: if (pn->pn_pos.begin < pn_pos.begin) michael@0: pn_pos.begin = pn->pn_pos.begin; michael@0: pn_pos.end = pn->pn_pos.end; michael@0: pn_head = pn; michael@0: pn_tail = &pn->pn_next; michael@0: pn_count = 1; michael@0: pn_xflags = 0; michael@0: pn_blockid = 0; michael@0: } michael@0: michael@0: void append(ParseNode *pn) { michael@0: JS_ASSERT(pn_arity == PN_LIST); michael@0: JS_ASSERT(pn->pn_pos.begin >= pn_pos.begin); michael@0: pn_pos.end = pn->pn_pos.end; michael@0: *pn_tail = pn; michael@0: pn_tail = &pn->pn_next; michael@0: pn_count++; michael@0: } michael@0: michael@0: void checkListConsistency() michael@0: #ifndef DEBUG michael@0: {} michael@0: #endif michael@0: ; michael@0: michael@0: bool getConstantValue(ExclusiveContext *cx, bool strictChecks, MutableHandleValue vp); michael@0: inline bool isConstant(); michael@0: michael@0: template michael@0: inline bool is() const { michael@0: return NodeType::test(*this); michael@0: } michael@0: michael@0: /* Casting operations. */ michael@0: template michael@0: inline NodeType &as() { michael@0: JS_ASSERT(NodeType::test(*this)); michael@0: return *static_cast(this); michael@0: } michael@0: michael@0: template michael@0: inline const NodeType &as() const { michael@0: JS_ASSERT(NodeType::test(*this)); michael@0: return *static_cast(this); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void dump(); michael@0: void dump(int indent); michael@0: #endif michael@0: }; michael@0: michael@0: struct NullaryNode : public ParseNode michael@0: { michael@0: NullaryNode(ParseNodeKind kind, const TokenPos &pos) michael@0: : ParseNode(kind, JSOP_NOP, PN_NULLARY, pos) {} michael@0: NullaryNode(ParseNodeKind kind, JSOp op, const TokenPos &pos) michael@0: : ParseNode(kind, op, PN_NULLARY, pos) {} michael@0: michael@0: // This constructor is for a few mad uses in the emitter. It populates michael@0: // the pn_atom field even though that field belongs to a branch in pn_u michael@0: // that nullary nodes shouldn't use -- bogus. michael@0: NullaryNode(ParseNodeKind kind, JSOp op, const TokenPos &pos, JSAtom *atom) michael@0: : ParseNode(kind, op, PN_NULLARY, pos) michael@0: { michael@0: pn_atom = atom; michael@0: } michael@0: michael@0: static bool test(const ParseNode &node) { michael@0: return node.isArity(PN_NULLARY); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void dump(); michael@0: #endif michael@0: }; michael@0: michael@0: struct UnaryNode : public ParseNode michael@0: { michael@0: UnaryNode(ParseNodeKind kind, JSOp op, const TokenPos &pos, ParseNode *kid) michael@0: : ParseNode(kind, op, PN_UNARY, pos) michael@0: { michael@0: pn_kid = kid; michael@0: } michael@0: michael@0: static inline UnaryNode *create(ParseNodeKind kind, FullParseHandler *handler) { michael@0: return (UnaryNode *) ParseNode::create(kind, PN_UNARY, handler); michael@0: } michael@0: michael@0: static bool test(const ParseNode &node) { michael@0: return node.isArity(PN_UNARY); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void dump(int indent); michael@0: #endif michael@0: }; michael@0: michael@0: struct BinaryNode : public ParseNode michael@0: { michael@0: BinaryNode(ParseNodeKind kind, JSOp op, const TokenPos &pos, ParseNode *left, ParseNode *right) michael@0: : ParseNode(kind, op, PN_BINARY, pos) michael@0: { michael@0: pn_left = left; michael@0: pn_right = right; michael@0: } michael@0: michael@0: BinaryNode(ParseNodeKind kind, JSOp op, ParseNode *left, ParseNode *right) michael@0: : ParseNode(kind, op, PN_BINARY, TokenPos::box(left->pn_pos, right->pn_pos)) michael@0: { michael@0: pn_left = left; michael@0: pn_right = right; michael@0: } michael@0: michael@0: static inline BinaryNode *create(ParseNodeKind kind, FullParseHandler *handler) { michael@0: return (BinaryNode *) ParseNode::create(kind, PN_BINARY, handler); michael@0: } michael@0: michael@0: static bool test(const ParseNode &node) { michael@0: return node.isArity(PN_BINARY); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void dump(int indent); michael@0: #endif michael@0: }; michael@0: michael@0: struct BinaryObjNode : public ParseNode michael@0: { michael@0: BinaryObjNode(ParseNodeKind kind, JSOp op, const TokenPos &pos, ParseNode *left, ParseNode *right, michael@0: ObjectBox *objbox) michael@0: : ParseNode(kind, op, PN_BINARY_OBJ, pos) michael@0: { michael@0: pn_left = left; michael@0: pn_right = right; michael@0: pn_binary_obj = objbox; michael@0: } michael@0: michael@0: static inline BinaryObjNode *create(ParseNodeKind kind, FullParseHandler *handler) { michael@0: return (BinaryObjNode *) ParseNode::create(kind, PN_BINARY_OBJ, handler); michael@0: } michael@0: michael@0: static bool test(const ParseNode &node) { michael@0: return node.isArity(PN_BINARY_OBJ); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void dump(int indent); michael@0: #endif michael@0: }; michael@0: michael@0: struct TernaryNode : public ParseNode michael@0: { michael@0: TernaryNode(ParseNodeKind kind, JSOp op, ParseNode *kid1, ParseNode *kid2, ParseNode *kid3) michael@0: : ParseNode(kind, op, PN_TERNARY, michael@0: TokenPos((kid1 ? kid1 : kid2 ? kid2 : kid3)->pn_pos.begin, michael@0: (kid3 ? kid3 : kid2 ? kid2 : kid1)->pn_pos.end)) michael@0: { michael@0: pn_kid1 = kid1; michael@0: pn_kid2 = kid2; michael@0: pn_kid3 = kid3; michael@0: } michael@0: michael@0: TernaryNode(ParseNodeKind kind, JSOp op, ParseNode *kid1, ParseNode *kid2, ParseNode *kid3, michael@0: const TokenPos &pos) michael@0: : ParseNode(kind, op, PN_TERNARY, pos) michael@0: { michael@0: pn_kid1 = kid1; michael@0: pn_kid2 = kid2; michael@0: pn_kid3 = kid3; michael@0: } michael@0: michael@0: static inline TernaryNode *create(ParseNodeKind kind, FullParseHandler *handler) { michael@0: return (TernaryNode *) ParseNode::create(kind, PN_TERNARY, handler); michael@0: } michael@0: michael@0: static bool test(const ParseNode &node) { michael@0: return node.isArity(PN_TERNARY); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void dump(int indent); michael@0: #endif michael@0: }; michael@0: michael@0: struct ListNode : public ParseNode michael@0: { michael@0: ListNode(ParseNodeKind kind, const TokenPos &pos) michael@0: : ParseNode(kind, JSOP_NOP, PN_LIST, pos) michael@0: { michael@0: makeEmpty(); michael@0: } michael@0: michael@0: ListNode(ParseNodeKind kind, JSOp op, ParseNode *kid) michael@0: : ParseNode(kind, op, PN_LIST, kid->pn_pos) michael@0: { michael@0: initList(kid); michael@0: } michael@0: michael@0: static inline ListNode *create(ParseNodeKind kind, FullParseHandler *handler) { michael@0: return (ListNode *) ParseNode::create(kind, PN_LIST, handler); michael@0: } michael@0: michael@0: static bool test(const ParseNode &node) { michael@0: return node.isArity(PN_LIST); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void dump(int indent); michael@0: #endif michael@0: }; michael@0: michael@0: struct CodeNode : public ParseNode michael@0: { michael@0: static inline CodeNode *create(ParseNodeKind kind, FullParseHandler *handler) { michael@0: return (CodeNode *) ParseNode::create(kind, PN_CODE, handler); michael@0: } michael@0: michael@0: static bool test(const ParseNode &node) { michael@0: return node.isArity(PN_CODE); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void dump(int indent); michael@0: #endif michael@0: }; michael@0: michael@0: struct NameNode : public ParseNode michael@0: { michael@0: NameNode(ParseNodeKind kind, JSOp op, JSAtom *atom, uint32_t blockid, michael@0: const TokenPos &pos) michael@0: : ParseNode(kind, op, PN_NAME, pos) michael@0: { michael@0: pn_atom = atom; michael@0: pn_expr = nullptr; michael@0: pn_cookie.makeFree(); michael@0: pn_dflags = 0; michael@0: pn_blockid = blockid; michael@0: JS_ASSERT(pn_blockid == blockid); // check for bitfield overflow michael@0: } michael@0: michael@0: static bool test(const ParseNode &node) { michael@0: return node.isArity(PN_NAME); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void dump(int indent); michael@0: #endif michael@0: }; michael@0: michael@0: struct LexicalScopeNode : public ParseNode michael@0: { michael@0: static inline LexicalScopeNode *create(ParseNodeKind kind, FullParseHandler *handler) { michael@0: return (LexicalScopeNode *) ParseNode::create(kind, PN_NAME, handler); michael@0: } michael@0: }; michael@0: michael@0: class LabeledStatement : public ParseNode michael@0: { michael@0: public: michael@0: LabeledStatement(PropertyName *label, ParseNode *stmt, uint32_t begin) michael@0: : ParseNode(PNK_LABEL, JSOP_NOP, PN_NAME, TokenPos(begin, stmt->pn_pos.end)) michael@0: { michael@0: pn_atom = label; michael@0: pn_expr = stmt; michael@0: } michael@0: michael@0: PropertyName *label() const { michael@0: return pn_atom->asPropertyName(); michael@0: } michael@0: michael@0: ParseNode *statement() const { michael@0: return pn_expr; michael@0: } michael@0: michael@0: static bool test(const ParseNode &node) { michael@0: bool match = node.isKind(PNK_LABEL); michael@0: JS_ASSERT_IF(match, node.isArity(PN_NAME)); michael@0: JS_ASSERT_IF(match, node.isOp(JSOP_NOP)); michael@0: return match; michael@0: } michael@0: }; michael@0: michael@0: class LoopControlStatement : public ParseNode michael@0: { michael@0: protected: michael@0: LoopControlStatement(ParseNodeKind kind, PropertyName *label, const TokenPos &pos) michael@0: : ParseNode(kind, JSOP_NOP, PN_NULLARY, pos) michael@0: { michael@0: JS_ASSERT(kind == PNK_BREAK || kind == PNK_CONTINUE); michael@0: pn_u.loopControl.label = label; michael@0: } michael@0: michael@0: public: michael@0: /* Label associated with this break/continue statement, if any. */ michael@0: PropertyName *label() const { michael@0: return pn_u.loopControl.label; michael@0: } michael@0: michael@0: static bool test(const ParseNode &node) { michael@0: bool match = node.isKind(PNK_BREAK) || node.isKind(PNK_CONTINUE); michael@0: JS_ASSERT_IF(match, node.isArity(PN_NULLARY)); michael@0: JS_ASSERT_IF(match, node.isOp(JSOP_NOP)); michael@0: return match; michael@0: } michael@0: }; michael@0: michael@0: class BreakStatement : public LoopControlStatement michael@0: { michael@0: public: michael@0: BreakStatement(PropertyName *label, const TokenPos &pos) michael@0: : LoopControlStatement(PNK_BREAK, label, pos) michael@0: { } michael@0: michael@0: static bool test(const ParseNode &node) { michael@0: bool match = node.isKind(PNK_BREAK); michael@0: JS_ASSERT_IF(match, node.isArity(PN_NULLARY)); michael@0: JS_ASSERT_IF(match, node.isOp(JSOP_NOP)); michael@0: return match; michael@0: } michael@0: }; michael@0: michael@0: class ContinueStatement : public LoopControlStatement michael@0: { michael@0: public: michael@0: ContinueStatement(PropertyName *label, const TokenPos &pos) michael@0: : LoopControlStatement(PNK_CONTINUE, label, pos) michael@0: { } michael@0: michael@0: static bool test(const ParseNode &node) { michael@0: bool match = node.isKind(PNK_CONTINUE); michael@0: JS_ASSERT_IF(match, node.isArity(PN_NULLARY)); michael@0: JS_ASSERT_IF(match, node.isOp(JSOP_NOP)); michael@0: return match; michael@0: } michael@0: }; michael@0: michael@0: class DebuggerStatement : public ParseNode michael@0: { michael@0: public: michael@0: DebuggerStatement(const TokenPos &pos) michael@0: : ParseNode(PNK_DEBUGGER, JSOP_NOP, PN_NULLARY, pos) michael@0: { } michael@0: }; michael@0: michael@0: class ConditionalExpression : public ParseNode michael@0: { michael@0: public: michael@0: ConditionalExpression(ParseNode *condition, ParseNode *thenExpr, ParseNode *elseExpr) michael@0: : ParseNode(PNK_CONDITIONAL, JSOP_NOP, PN_TERNARY, michael@0: TokenPos(condition->pn_pos.begin, elseExpr->pn_pos.end)) michael@0: { michael@0: JS_ASSERT(condition); michael@0: JS_ASSERT(thenExpr); michael@0: JS_ASSERT(elseExpr); michael@0: pn_u.ternary.kid1 = condition; michael@0: pn_u.ternary.kid2 = thenExpr; michael@0: pn_u.ternary.kid3 = elseExpr; michael@0: } michael@0: michael@0: ParseNode &condition() const { michael@0: return *pn_u.ternary.kid1; michael@0: } michael@0: michael@0: ParseNode &thenExpression() const { michael@0: return *pn_u.ternary.kid2; michael@0: } michael@0: michael@0: ParseNode &elseExpression() const { michael@0: return *pn_u.ternary.kid3; michael@0: } michael@0: michael@0: static bool test(const ParseNode &node) { michael@0: bool match = node.isKind(PNK_CONDITIONAL); michael@0: JS_ASSERT_IF(match, node.isArity(PN_TERNARY)); michael@0: JS_ASSERT_IF(match, node.isOp(JSOP_NOP)); michael@0: return match; michael@0: } michael@0: }; michael@0: michael@0: class ThisLiteral : public ParseNode michael@0: { michael@0: public: michael@0: ThisLiteral(const TokenPos &pos) : ParseNode(PNK_THIS, JSOP_THIS, PN_NULLARY, pos) { } michael@0: }; michael@0: michael@0: class NullLiteral : public ParseNode michael@0: { michael@0: public: michael@0: NullLiteral(const TokenPos &pos) : ParseNode(PNK_NULL, JSOP_NULL, PN_NULLARY, pos) { } michael@0: }; michael@0: michael@0: class BooleanLiteral : public ParseNode michael@0: { michael@0: public: michael@0: BooleanLiteral(bool b, const TokenPos &pos) michael@0: : ParseNode(b ? PNK_TRUE : PNK_FALSE, b ? JSOP_TRUE : JSOP_FALSE, PN_NULLARY, pos) michael@0: { } michael@0: }; michael@0: michael@0: class RegExpLiteral : public NullaryNode michael@0: { michael@0: public: michael@0: RegExpLiteral(ObjectBox *reobj, const TokenPos &pos) michael@0: : NullaryNode(PNK_REGEXP, JSOP_REGEXP, pos) michael@0: { michael@0: pn_objbox = reobj; michael@0: } michael@0: michael@0: ObjectBox *objbox() const { return pn_objbox; } michael@0: michael@0: static bool test(const ParseNode &node) { michael@0: bool match = node.isKind(PNK_REGEXP); michael@0: JS_ASSERT_IF(match, node.isArity(PN_NULLARY)); michael@0: JS_ASSERT_IF(match, node.isOp(JSOP_REGEXP)); michael@0: return match; michael@0: } michael@0: }; michael@0: michael@0: class PropertyAccess : public ParseNode michael@0: { michael@0: public: michael@0: PropertyAccess(ParseNode *lhs, PropertyName *name, uint32_t begin, uint32_t end) michael@0: : ParseNode(PNK_DOT, JSOP_NOP, PN_NAME, TokenPos(begin, end)) michael@0: { michael@0: JS_ASSERT(lhs != nullptr); michael@0: JS_ASSERT(name != nullptr); michael@0: pn_u.name.expr = lhs; michael@0: pn_u.name.atom = name; michael@0: } michael@0: michael@0: static bool test(const ParseNode &node) { michael@0: bool match = node.isKind(PNK_DOT); michael@0: JS_ASSERT_IF(match, node.isArity(PN_NAME)); michael@0: return match; michael@0: } michael@0: michael@0: ParseNode &expression() const { michael@0: return *pn_u.name.expr; michael@0: } michael@0: michael@0: PropertyName &name() const { michael@0: return *pn_u.name.atom->asPropertyName(); michael@0: } michael@0: }; michael@0: michael@0: class PropertyByValue : public ParseNode michael@0: { michael@0: public: michael@0: PropertyByValue(ParseNode *lhs, ParseNode *propExpr, uint32_t begin, uint32_t end) michael@0: : ParseNode(PNK_ELEM, JSOP_NOP, PN_BINARY, TokenPos(begin, end)) michael@0: { michael@0: pn_u.binary.left = lhs; michael@0: pn_u.binary.right = propExpr; michael@0: } michael@0: }; michael@0: michael@0: #ifdef DEBUG michael@0: void DumpParseTree(ParseNode *pn, int indent = 0); michael@0: #endif michael@0: michael@0: /* michael@0: * js::Definition is a degenerate subtype of the PN_FUNC and PN_NAME variants michael@0: * of js::ParseNode, allocated only for function, var, const, and let michael@0: * declarations that define truly lexical bindings. This means that a child of michael@0: * a PNK_VAR list may be a Definition as well as a ParseNode. The pn_defn bit michael@0: * is set for all Definitions, clear otherwise. michael@0: * michael@0: * In an upvars list, defn->resolve() is the outermost definition the michael@0: * name may reference. If a with block or a function that calls eval encloses michael@0: * the use, the name may end up referring to something else at runtime. michael@0: * michael@0: * Note that not all var declarations are definitions: JS allows multiple var michael@0: * declarations in a function or script, but only the first creates the hoisted michael@0: * binding. JS programmers do redeclare variables for good refactoring reasons, michael@0: * for example: michael@0: * michael@0: * function foo() { michael@0: * ... michael@0: * for (var i ...) ...; michael@0: * ... michael@0: * for (var i ...) ...; michael@0: * ... michael@0: * } michael@0: * michael@0: * Not all definitions bind lexical variables, alas. In global and eval code michael@0: * var may re-declare a pre-existing property having any attributes, with or michael@0: * without JSPROP_PERMANENT. In eval code, indeed, ECMA-262 Editions 1 through michael@0: * 3 require function and var to bind deletable bindings. Global vars thus are michael@0: * properties of the global object, so they can be aliased even if they can't michael@0: * be deleted. michael@0: * michael@0: * Only bindings within function code may be treated as lexical, of course with michael@0: * the caveat that hoisting means use before initialization is allowed. We deal michael@0: * with use before declaration in one pass as follows (error checking elided): michael@0: * michael@0: * for (each use of unqualified name x in parse order) { michael@0: * if (this use of x is a declaration) { michael@0: * if (x in pc->decls) { // redeclaring michael@0: * pn = allocate a PN_NAME ParseNode; michael@0: * } else { // defining michael@0: * dn = lookup x in pc->lexdeps; michael@0: * if (dn) // use before def michael@0: * remove x from pc->lexdeps; michael@0: * else // def before use michael@0: * dn = allocate a PN_NAME Definition; michael@0: * map x to dn via pc->decls; michael@0: * pn = dn; michael@0: * } michael@0: * insert pn into its parent PNK_VAR/PNK_CONST list; michael@0: * } else { michael@0: * pn = allocate a ParseNode for this reference to x; michael@0: * dn = lookup x in pc's lexical scope chain; michael@0: * if (!dn) { michael@0: * dn = lookup x in pc->lexdeps; michael@0: * if (!dn) { michael@0: * dn = pre-allocate a Definition for x; michael@0: * map x to dn in pc->lexdeps; michael@0: * } michael@0: * } michael@0: * append pn to dn's use chain; michael@0: * } michael@0: * } michael@0: * michael@0: * See frontend/BytecodeEmitter.h for js::ParseContext and its top*Stmt, michael@0: * decls, and lexdeps members. michael@0: * michael@0: * Notes: michael@0: * michael@0: * 0. To avoid bloating ParseNode, we steal a bit from pn_arity for pn_defn michael@0: * and set it on a ParseNode instead of allocating a Definition. michael@0: * michael@0: * 1. Due to hoisting, a definition cannot be eliminated even if its "Variable michael@0: * statement" (ECMA-262 12.2) can be proven to be dead code. RecycleTree in michael@0: * ParseNode.cpp will not recycle a node whose pn_defn bit is set. michael@0: * michael@0: * 2. "lookup x in pc's lexical scope chain" gives up on def/use chaining if a michael@0: * with statement is found along the the scope chain, which includes pc, michael@0: * pc->parent, etc. Thus we eagerly connect an inner function's use of an michael@0: * outer's var x if the var x was parsed before the inner function. michael@0: * michael@0: * 3. A use may be eliminated as dead by the constant folder, which therefore michael@0: * must remove the dead name node from its singly-linked use chain, which michael@0: * would mean hashing to find the definition node and searching to update michael@0: * the pn_link pointing at the use to be removed. This is costly, so as for michael@0: * dead definitions, we do not recycle dead pn_used nodes. michael@0: * michael@0: * At the end of parsing a function body or global or eval program, pc->lexdeps michael@0: * holds the lexical dependencies of the parsed unit. The name to def/use chain michael@0: * mappings are then merged into the parent pc->lexdeps. michael@0: * michael@0: * Thus if a later var x is parsed in the outer function satisfying an earlier michael@0: * inner function's use of x, we will remove dn from pc->lexdeps and re-use it michael@0: * as the new definition node in the outer function's parse tree. michael@0: * michael@0: * When the compiler unwinds from the outermost pc, pc->lexdeps contains the michael@0: * definition nodes with use chains for all free variables. These are either michael@0: * global variables or reference errors. michael@0: */ michael@0: #define dn_uses pn_link michael@0: michael@0: struct Definition : public ParseNode michael@0: { michael@0: bool isFreeVar() const { michael@0: JS_ASSERT(isDefn()); michael@0: return pn_cookie.isFree(); michael@0: } michael@0: michael@0: enum Kind { MISSING = 0, VAR, CONST, LET, ARG, NAMED_LAMBDA, PLACEHOLDER }; michael@0: michael@0: bool canHaveInitializer() { return int(kind()) <= int(ARG); } michael@0: michael@0: static const char *kindString(Kind kind); michael@0: michael@0: Kind kind() { michael@0: if (getKind() == PNK_FUNCTION) { michael@0: if (isOp(JSOP_GETARG)) michael@0: return ARG; michael@0: return VAR; michael@0: } michael@0: JS_ASSERT(getKind() == PNK_NAME); michael@0: if (isOp(JSOP_CALLEE)) michael@0: return NAMED_LAMBDA; michael@0: if (isPlaceholder()) michael@0: return PLACEHOLDER; michael@0: if (isOp(JSOP_GETARG)) michael@0: return ARG; michael@0: if (isConst()) michael@0: return CONST; michael@0: if (isLet()) michael@0: return LET; michael@0: return VAR; michael@0: } michael@0: }; michael@0: michael@0: class ParseNodeAllocator michael@0: { michael@0: public: michael@0: explicit ParseNodeAllocator(ExclusiveContext *cx, LifoAlloc &alloc) michael@0: : cx(cx), alloc(alloc), freelist(nullptr) michael@0: {} michael@0: michael@0: void *allocNode(); michael@0: void freeNode(ParseNode *pn); michael@0: ParseNode *freeTree(ParseNode *pn); michael@0: void prepareNodeForMutation(ParseNode *pn); michael@0: michael@0: private: michael@0: ExclusiveContext *cx; michael@0: LifoAlloc &alloc; michael@0: ParseNode *freelist; michael@0: }; michael@0: michael@0: inline bool michael@0: ParseNode::test(unsigned flag) const michael@0: { michael@0: JS_ASSERT(pn_defn || pn_arity == PN_CODE || pn_arity == PN_NAME); michael@0: #ifdef DEBUG michael@0: if ((flag & PND_ASSIGNED) && pn_defn && !(pn_dflags & flag)) { michael@0: for (ParseNode *pn = ((Definition *) this)->dn_uses; pn; pn = pn->pn_link) { michael@0: JS_ASSERT(!pn->pn_defn); michael@0: JS_ASSERT(!(pn->pn_dflags & flag)); michael@0: } michael@0: } michael@0: #endif michael@0: return !!(pn_dflags & flag); michael@0: } michael@0: michael@0: inline void michael@0: ParseNode::markAsAssigned() michael@0: { michael@0: JS_ASSERT(js_CodeSpec[pn_op].format & JOF_NAME); michael@0: if (isUsed()) michael@0: pn_lexdef->pn_dflags |= PND_ASSIGNED; michael@0: pn_dflags |= PND_ASSIGNED; michael@0: } michael@0: michael@0: inline Definition * michael@0: ParseNode::resolve() michael@0: { michael@0: if (isDefn()) michael@0: return (Definition *)this; michael@0: JS_ASSERT(lexdef()->isDefn()); michael@0: return (Definition *)lexdef(); michael@0: } michael@0: michael@0: inline bool michael@0: ParseNode::isConstant() michael@0: { michael@0: switch (pn_type) { michael@0: case PNK_NUMBER: michael@0: case PNK_STRING: michael@0: case PNK_NULL: michael@0: case PNK_FALSE: michael@0: case PNK_TRUE: michael@0: return true; michael@0: case PNK_ARRAY: michael@0: case PNK_OBJECT: michael@0: JS_ASSERT(isOp(JSOP_NEWINIT)); michael@0: return !(pn_xflags & PNX_NONCONST); michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: class ObjectBox michael@0: { michael@0: public: michael@0: JSObject *object; michael@0: michael@0: ObjectBox(JSObject *object, ObjectBox *traceLink); michael@0: bool isFunctionBox() { return object->is(); } michael@0: FunctionBox *asFunctionBox(); michael@0: void trace(JSTracer *trc); michael@0: michael@0: protected: michael@0: friend struct CGObjectList; michael@0: michael@0: ObjectBox *traceLink; michael@0: ObjectBox *emitLink; michael@0: michael@0: ObjectBox(JSFunction *function, ObjectBox *traceLink); michael@0: }; michael@0: michael@0: enum ParseReportKind michael@0: { michael@0: ParseError, michael@0: ParseWarning, michael@0: ParseExtraWarning, michael@0: ParseStrictError michael@0: }; michael@0: michael@0: enum FunctionSyntaxKind { Expression, Statement, Arrow }; michael@0: michael@0: static inline ParseNode * michael@0: FunctionArgsList(ParseNode *fn, unsigned *numFormals) michael@0: { michael@0: JS_ASSERT(fn->isKind(PNK_FUNCTION)); michael@0: ParseNode *argsBody = fn->pn_body; michael@0: JS_ASSERT(argsBody->isKind(PNK_ARGSBODY)); michael@0: *numFormals = argsBody->pn_count; michael@0: if (*numFormals > 0 && argsBody->last()->isKind(PNK_STATEMENTLIST)) michael@0: (*numFormals)--; michael@0: JS_ASSERT(argsBody->isArity(PN_LIST)); michael@0: return argsBody->pn_head; michael@0: } michael@0: michael@0: } /* namespace frontend */ michael@0: } /* namespace js */ michael@0: michael@0: #endif /* frontend_ParseNode_h */