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