mirror of
https://github.com/roytam1/palemoon26.git
synced 2026-05-26 05:44:31 +00:00
8d816ab963
bug886205, bug870103, bug886611, bug886241, bug878142, bug857334, bug886128, bug885596, bug887068, bug868302, bug886575, bug809969, bug860782, bug886285, bug848592, bug877748, bug886647, bug879079, bug886827, bug884648, bug866638, bug885463, bug826124, bug844805, bug886551, bug884407, bug884369, bug887002, bug886632, 572efc8fea86, bug886230, bug886689, bug884124
1442 lines
50 KiB
C++
1442 lines
50 KiB
C++
/* -*- 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;
|
|
|
|
class ParseNode
|
|
{
|
|
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_NOP, 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_NOP, 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 */
|