summaryrefslogtreecommitdiff
path: root/src/third_party/js-1.7/jsparse.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/third_party/js-1.7/jsparse.c')
-rw-r--r--src/third_party/js-1.7/jsparse.c6547
1 files changed, 6547 insertions, 0 deletions
diff --git a/src/third_party/js-1.7/jsparse.c b/src/third_party/js-1.7/jsparse.c
new file mode 100644
index 00000000000..132e2ad26b7
--- /dev/null
+++ b/src/third_party/js-1.7/jsparse.c
@@ -0,0 +1,6547 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et tw=78:
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Communicator client code, released
+ * March 31, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * JS parser.
+ *
+ * This is a recursive-descent parser for the JavaScript language specified by
+ * "The JavaScript 1.5 Language Specification". It uses lexical and semantic
+ * feedback to disambiguate non-LL(1) structures. It generates trees of nodes
+ * induced by the recursive parsing (not precise syntax trees, see jsparse.h).
+ * After tree construction, it rewrites trees to fold constants and evaluate
+ * compile-time expressions. Finally, it calls js_EmitTree (see jsemit.h) to
+ * generate bytecode.
+ *
+ * This parser attempts no error recovery.
+ */
+#include "jsstddef.h"
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "jstypes.h"
+#include "jsarena.h" /* Added by JSIFY */
+#include "jsutil.h" /* Added by JSIFY */
+#include "jsapi.h"
+#include "jsarray.h"
+#include "jsatom.h"
+#include "jscntxt.h"
+#include "jsconfig.h"
+#include "jsemit.h"
+#include "jsfun.h"
+#include "jsinterp.h"
+#include "jslock.h"
+#include "jsnum.h"
+#include "jsobj.h"
+#include "jsopcode.h"
+#include "jsparse.h"
+#include "jsscan.h"
+#include "jsscope.h"
+#include "jsscript.h"
+#include "jsstr.h"
+
+#if JS_HAS_XML_SUPPORT
+#include "jsxml.h"
+#endif
+
+#if JS_HAS_DESTRUCTURING
+#include "jsdhash.h"
+#endif
+
+/*
+ * JS parsers, from lowest to highest precedence.
+ *
+ * Each parser takes a context, a token stream, and a tree context struct.
+ * Each returns a parse node tree or null on error.
+ */
+
+typedef JSParseNode *
+JSParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc);
+
+typedef JSParseNode *
+JSMemberParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
+ JSBool allowCallSyntax);
+
+typedef JSParseNode *
+JSPrimaryParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
+ JSTokenType tt, JSBool afterDot);
+
+static JSParser FunctionStmt;
+static JSParser FunctionExpr;
+static JSParser Statements;
+static JSParser Statement;
+static JSParser Variables;
+static JSParser Expr;
+static JSParser AssignExpr;
+static JSParser CondExpr;
+static JSParser OrExpr;
+static JSParser AndExpr;
+static JSParser BitOrExpr;
+static JSParser BitXorExpr;
+static JSParser BitAndExpr;
+static JSParser EqExpr;
+static JSParser RelExpr;
+static JSParser ShiftExpr;
+static JSParser AddExpr;
+static JSParser MulExpr;
+static JSParser UnaryExpr;
+static JSMemberParser MemberExpr;
+static JSPrimaryParser PrimaryExpr;
+
+/*
+ * Insist that the next token be of type tt, or report errno and return null.
+ * NB: this macro uses cx and ts from its lexical environment.
+ */
+#define MUST_MATCH_TOKEN(tt, errno) \
+ JS_BEGIN_MACRO \
+ if (js_GetToken(cx, ts) != tt) { \
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR, \
+ errno); \
+ return NULL; \
+ } \
+ JS_END_MACRO
+
+#define CHECK_RECURSION() \
+ JS_BEGIN_MACRO \
+ int stackDummy; \
+ if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) { \
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR, \
+ JSMSG_OVER_RECURSED); \
+ return NULL; \
+ } \
+ JS_END_MACRO
+
+#ifdef METER_PARSENODES
+static uint32 parsenodes = 0;
+static uint32 maxparsenodes = 0;
+static uint32 recyclednodes = 0;
+#endif
+
+static JSParseNode *
+RecycleTree(JSParseNode *pn, JSTreeContext *tc)
+{
+ JSParseNode *next;
+
+ if (!pn)
+ return NULL;
+ JS_ASSERT(pn != tc->nodeList); /* catch back-to-back dup recycles */
+ next = pn->pn_next;
+ pn->pn_next = tc->nodeList;
+ tc->nodeList = pn;
+#ifdef METER_PARSENODES
+ recyclednodes++;
+#endif
+ return next;
+}
+
+static JSParseNode *
+NewOrRecycledNode(JSContext *cx, JSTreeContext *tc)
+{
+ JSParseNode *pn;
+
+ pn = tc->nodeList;
+ if (!pn) {
+ JS_ARENA_ALLOCATE_TYPE(pn, JSParseNode, &cx->tempPool);
+ if (!pn)
+ JS_ReportOutOfMemory(cx);
+ } else {
+ tc->nodeList = pn->pn_next;
+
+ /* Recycle immediate descendents only, to save work and working set. */
+ switch (pn->pn_arity) {
+ case PN_FUNC:
+ RecycleTree(pn->pn_body, tc);
+ break;
+ case PN_LIST:
+ if (pn->pn_head) {
+ /* XXX check for dup recycles in the list */
+ *pn->pn_tail = tc->nodeList;
+ tc->nodeList = pn->pn_head;
+#ifdef METER_PARSENODES
+ recyclednodes += pn->pn_count;
+#endif
+ }
+ break;
+ case PN_TERNARY:
+ RecycleTree(pn->pn_kid1, tc);
+ RecycleTree(pn->pn_kid2, tc);
+ RecycleTree(pn->pn_kid3, tc);
+ break;
+ case PN_BINARY:
+ RecycleTree(pn->pn_left, tc);
+ RecycleTree(pn->pn_right, tc);
+ break;
+ case PN_UNARY:
+ RecycleTree(pn->pn_kid, tc);
+ break;
+ case PN_NAME:
+ RecycleTree(pn->pn_expr, tc);
+ break;
+ case PN_NULLARY:
+ break;
+ }
+ }
+#ifdef METER_PARSENODES
+ if (pn) {
+ parsenodes++;
+ if (parsenodes - recyclednodes > maxparsenodes)
+ maxparsenodes = parsenodes - recyclednodes;
+ }
+#endif
+ return pn;
+}
+
+/*
+ * Allocate a JSParseNode from cx's temporary arena.
+ */
+static JSParseNode *
+NewParseNode(JSContext *cx, JSTokenStream *ts, JSParseNodeArity arity,
+ JSTreeContext *tc)
+{
+ JSParseNode *pn;
+ JSToken *tp;
+
+ pn = NewOrRecycledNode(cx, tc);
+ if (!pn)
+ return NULL;
+ tp = &CURRENT_TOKEN(ts);
+ pn->pn_type = tp->type;
+ pn->pn_pos = tp->pos;
+ pn->pn_op = JSOP_NOP;
+ pn->pn_arity = arity;
+ pn->pn_next = NULL;
+ pn->pn_ts = ts;
+ pn->pn_source = NULL;
+ return pn;
+}
+
+static JSParseNode *
+NewBinary(JSContext *cx, JSTokenType tt,
+ JSOp op, JSParseNode *left, JSParseNode *right,
+ JSTreeContext *tc)
+{
+ JSParseNode *pn, *pn1, *pn2;
+
+ if (!left || !right)
+ return NULL;
+
+ /*
+ * Flatten a left-associative (left-heavy) tree of a given operator into
+ * a list, to reduce js_FoldConstants and js_EmitTree recursion.
+ */
+ if (left->pn_type == tt &&
+ left->pn_op == op &&
+ (js_CodeSpec[op].format & JOF_LEFTASSOC)) {
+ if (left->pn_arity != PN_LIST) {
+ pn1 = left->pn_left, pn2 = left->pn_right;
+ left->pn_arity = PN_LIST;
+ PN_INIT_LIST_1(left, pn1);
+ PN_APPEND(left, pn2);
+ if (tt == TOK_PLUS) {
+ if (pn1->pn_type == TOK_STRING)
+ left->pn_extra |= PNX_STRCAT;
+ else if (pn1->pn_type != TOK_NUMBER)
+ left->pn_extra |= PNX_CANTFOLD;
+ if (pn2->pn_type == TOK_STRING)
+ left->pn_extra |= PNX_STRCAT;
+ else if (pn2->pn_type != TOK_NUMBER)
+ left->pn_extra |= PNX_CANTFOLD;
+ }
+ }
+ PN_APPEND(left, right);
+ left->pn_pos.end = right->pn_pos.end;
+ if (tt == TOK_PLUS) {
+ if (right->pn_type == TOK_STRING)
+ left->pn_extra |= PNX_STRCAT;
+ else if (right->pn_type != TOK_NUMBER)
+ left->pn_extra |= PNX_CANTFOLD;
+ }
+ return left;
+ }
+
+ /*
+ * Fold constant addition immediately, to conserve node space and, what's
+ * more, so js_FoldConstants never sees mixed addition and concatenation
+ * operations with more than one leading non-string operand in a PN_LIST
+ * generated for expressions such as 1 + 2 + "pt" (which should evaluate
+ * to "3pt", not "12pt").
+ */
+ if (tt == TOK_PLUS &&
+ left->pn_type == TOK_NUMBER &&
+ right->pn_type == TOK_NUMBER) {
+ left->pn_dval += right->pn_dval;
+ left->pn_pos.end = right->pn_pos.end;
+ RecycleTree(right, tc);
+ return left;
+ }
+
+ pn = NewOrRecycledNode(cx, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_type = tt;
+ pn->pn_pos.begin = left->pn_pos.begin;
+ pn->pn_pos.end = right->pn_pos.end;
+ pn->pn_op = op;
+ pn->pn_arity = PN_BINARY;
+ pn->pn_left = left;
+ pn->pn_right = right;
+ pn->pn_next = NULL;
+ pn->pn_ts = NULL;
+ pn->pn_source = NULL;
+ return pn;
+}
+
+#if JS_HAS_GETTER_SETTER
+static JSTokenType
+CheckGetterOrSetter(JSContext *cx, JSTokenStream *ts, JSTokenType tt)
+{
+ JSAtom *atom;
+ JSRuntime *rt;
+ JSOp op;
+ const char *name;
+
+ JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_NAME);
+ atom = CURRENT_TOKEN(ts).t_atom;
+ rt = cx->runtime;
+ if (atom == rt->atomState.getterAtom)
+ op = JSOP_GETTER;
+ else if (atom == rt->atomState.setterAtom)
+ op = JSOP_SETTER;
+ else
+ return TOK_NAME;
+ if (js_PeekTokenSameLine(cx, ts) != tt)
+ return TOK_NAME;
+ (void) js_GetToken(cx, ts);
+ if (CURRENT_TOKEN(ts).t_op != JSOP_NOP) {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_GETTER_OR_SETTER,
+ (op == JSOP_GETTER)
+ ? js_getter_str
+ : js_setter_str);
+ return TOK_ERROR;
+ }
+ CURRENT_TOKEN(ts).t_op = op;
+ if (JS_HAS_STRICT_OPTION(cx)) {
+ name = js_AtomToPrintableString(cx, atom);
+ if (!name ||
+ !js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS |
+ JSREPORT_WARNING |
+ JSREPORT_STRICT,
+ JSMSG_DEPRECATED_USAGE,
+ name)) {
+ return TOK_ERROR;
+ }
+ }
+ return tt;
+}
+#endif
+
+static void
+MaybeSetupFrame(JSContext *cx, JSObject *chain, JSStackFrame *oldfp,
+ JSStackFrame *newfp)
+{
+ /*
+ * Always push a new frame if the current frame is special, so that
+ * Variables gets the correct variables object: the one from the special
+ * frame's caller.
+ */
+ if (oldfp &&
+ oldfp->varobj &&
+ oldfp->scopeChain == chain &&
+ !(oldfp->flags & JSFRAME_SPECIAL)) {
+ return;
+ }
+
+ memset(newfp, 0, sizeof *newfp);
+
+ /* Default to sharing the same variables object and scope chain. */
+ newfp->varobj = newfp->scopeChain = chain;
+ if (cx->options & JSOPTION_VAROBJFIX) {
+ while ((chain = JS_GetParent(cx, chain)) != NULL)
+ newfp->varobj = chain;
+ }
+ newfp->down = oldfp;
+ if (oldfp) {
+ /*
+ * In the case of eval and debugger frames, we need to dig down and find
+ * the real variables objects and function that our new stack frame is
+ * going to use.
+ */
+ newfp->flags = oldfp->flags & (JSFRAME_SPECIAL | JSFRAME_COMPILE_N_GO |
+ JSFRAME_SCRIPT_OBJECT);
+ while (oldfp->flags & JSFRAME_SPECIAL) {
+ oldfp = oldfp->down;
+ if (!oldfp)
+ break;
+ }
+ if (oldfp && (newfp->flags & JSFRAME_SPECIAL)) {
+ newfp->varobj = oldfp->varobj;
+ newfp->vars = oldfp->vars;
+ newfp->fun = oldfp->fun;
+ }
+ }
+ cx->fp = newfp;
+}
+
+/*
+ * Parse a top-level JS script.
+ */
+JS_FRIEND_API(JSParseNode *)
+js_ParseTokenStream(JSContext *cx, JSObject *chain, JSTokenStream *ts)
+{
+ JSStackFrame *fp, frame;
+ JSTreeContext tc;
+ JSParseNode *pn;
+
+ /*
+ * Push a compiler frame if we have no frames, or if the top frame is a
+ * lightweight function activation, or if its scope chain doesn't match
+ * the one passed to us.
+ */
+ fp = cx->fp;
+ MaybeSetupFrame(cx, chain, fp, &frame);
+
+ /*
+ * Protect atoms from being collected by a GC activation, which might
+ * - nest on this thread due to out of memory (the so-called "last ditch"
+ * GC attempted within js_NewGCThing), or
+ * - run for any reason on another thread if this thread is suspended on
+ * an object lock before it finishes generating bytecode into a script
+ * protected from the GC by a root or a stack frame reference.
+ */
+ JS_KEEP_ATOMS(cx->runtime);
+ TREE_CONTEXT_INIT(&tc);
+ pn = Statements(cx, ts, &tc);
+ if (pn) {
+ if (!js_MatchToken(cx, ts, TOK_EOF)) {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_SYNTAX_ERROR);
+ pn = NULL;
+ } else {
+ pn->pn_type = TOK_LC;
+ if (!js_FoldConstants(cx, pn, &tc))
+ pn = NULL;
+ }
+ }
+
+ TREE_CONTEXT_FINISH(&tc);
+ JS_UNKEEP_ATOMS(cx->runtime);
+ cx->fp = fp;
+ return pn;
+}
+
+/*
+ * Compile a top-level script.
+ */
+JS_FRIEND_API(JSBool)
+js_CompileTokenStream(JSContext *cx, JSObject *chain, JSTokenStream *ts,
+ JSCodeGenerator *cg)
+{
+ JSStackFrame *fp, frame;
+ uint32 flags;
+ JSParseNode *pn;
+ JSBool ok;
+#ifdef METER_PARSENODES
+ void *sbrk(ptrdiff_t), *before = sbrk(0);
+#endif
+
+ /*
+ * Push a compiler frame if we have no frames, or if the top frame is a
+ * lightweight function activation, or if its scope chain doesn't match
+ * the one passed to us.
+ */
+ fp = cx->fp;
+ MaybeSetupFrame(cx, chain, fp, &frame);
+ flags = cx->fp->flags;
+ cx->fp->flags = flags |
+ (JS_HAS_COMPILE_N_GO_OPTION(cx)
+ ? JSFRAME_COMPILING | JSFRAME_COMPILE_N_GO
+ : JSFRAME_COMPILING);
+
+ /* Prevent GC activation while compiling. */
+ JS_KEEP_ATOMS(cx->runtime);
+
+ pn = Statements(cx, ts, &cg->treeContext);
+ if (!pn) {
+ ok = JS_FALSE;
+ } else if (!js_MatchToken(cx, ts, TOK_EOF)) {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_SYNTAX_ERROR);
+ ok = JS_FALSE;
+ } else {
+#ifdef METER_PARSENODES
+ printf("Parser growth: %d (%u nodes, %u max, %u unrecycled)\n",
+ (char *)sbrk(0) - (char *)before,
+ parsenodes,
+ maxparsenodes,
+ parsenodes - recyclednodes);
+ before = sbrk(0);
+#endif
+
+ /*
+ * No need to emit bytecode here -- Statements already has, for each
+ * statement in turn. Search for TCF_COMPILING in Statements, below.
+ * That flag is set for every tc == &cg->treeContext, and it implies
+ * that the tc can be downcast to a cg and used to emit code during
+ * parsing, rather than at the end of the parse phase.
+ *
+ * Nowadays the threaded interpreter needs a stop instruction, so we
+ * do have to emit that here.
+ */
+ JS_ASSERT(cg->treeContext.flags & TCF_COMPILING);
+ ok = js_Emit1(cx, cg, JSOP_STOP) >= 0;
+ }
+
+#ifdef METER_PARSENODES
+ printf("Code-gen growth: %d (%u bytecodes, %u srcnotes)\n",
+ (char *)sbrk(0) - (char *)before, CG_OFFSET(cg), cg->noteCount);
+#endif
+#ifdef JS_ARENAMETER
+ JS_DumpArenaStats(stdout);
+#endif
+ JS_UNKEEP_ATOMS(cx->runtime);
+ cx->fp->flags = flags;
+ cx->fp = fp;
+ return ok;
+}
+
+/*
+ * Insist on a final return before control flows out of pn. Try to be a bit
+ * smart about loops: do {...; return e2;} while(0) at the end of a function
+ * that contains an early return e1 will get a strict warning. Similarly for
+ * iloops: while (true){...} is treated as though ... returns.
+ */
+#define ENDS_IN_OTHER 0
+#define ENDS_IN_RETURN 1
+#define ENDS_IN_BREAK 2
+
+static int
+HasFinalReturn(JSParseNode *pn)
+{
+ JSParseNode *pn2, *pn3;
+ uintN rv, rv2, hasDefault;
+
+ switch (pn->pn_type) {
+ case TOK_LC:
+ if (!pn->pn_head)
+ return ENDS_IN_OTHER;
+ return HasFinalReturn(PN_LAST(pn));
+
+ case TOK_IF:
+ if (!pn->pn_kid3)
+ return ENDS_IN_OTHER;
+ return HasFinalReturn(pn->pn_kid2) & HasFinalReturn(pn->pn_kid3);
+
+ case TOK_WHILE:
+ pn2 = pn->pn_left;
+ if (pn2->pn_type == TOK_PRIMARY && pn2->pn_op == JSOP_TRUE)
+ return ENDS_IN_RETURN;
+ if (pn2->pn_type == TOK_NUMBER && pn2->pn_dval)
+ return ENDS_IN_RETURN;
+ return ENDS_IN_OTHER;
+
+ case TOK_DO:
+ pn2 = pn->pn_right;
+ if (pn2->pn_type == TOK_PRIMARY) {
+ if (pn2->pn_op == JSOP_FALSE)
+ return HasFinalReturn(pn->pn_left);
+ if (pn2->pn_op == JSOP_TRUE)
+ return ENDS_IN_RETURN;
+ }
+ if (pn2->pn_type == TOK_NUMBER) {
+ if (pn2->pn_dval == 0)
+ return HasFinalReturn(pn->pn_left);
+ return ENDS_IN_RETURN;
+ }
+ return ENDS_IN_OTHER;
+
+ case TOK_FOR:
+ pn2 = pn->pn_left;
+ if (pn2->pn_arity == PN_TERNARY && !pn2->pn_kid2)
+ return ENDS_IN_RETURN;
+ return ENDS_IN_OTHER;
+
+ case TOK_SWITCH:
+ rv = ENDS_IN_RETURN;
+ hasDefault = ENDS_IN_OTHER;
+ pn2 = pn->pn_right;
+ if (pn2->pn_type == TOK_LEXICALSCOPE)
+ pn2 = pn2->pn_expr;
+ for (pn2 = pn2->pn_head; rv && pn2; pn2 = pn2->pn_next) {
+ if (pn2->pn_type == TOK_DEFAULT)
+ hasDefault = ENDS_IN_RETURN;
+ pn3 = pn2->pn_right;
+ JS_ASSERT(pn3->pn_type == TOK_LC);
+ if (pn3->pn_head) {
+ rv2 = HasFinalReturn(PN_LAST(pn3));
+ if (rv2 == ENDS_IN_OTHER && pn2->pn_next)
+ /* Falling through to next case or default. */;
+ else
+ rv &= rv2;
+ }
+ }
+ /* If a final switch has no default case, we judge it harshly. */
+ rv &= hasDefault;
+ return rv;
+
+ case TOK_BREAK:
+ return ENDS_IN_BREAK;
+
+ case TOK_WITH:
+ return HasFinalReturn(pn->pn_right);
+
+ case TOK_RETURN:
+ return ENDS_IN_RETURN;
+
+ case TOK_COLON:
+ case TOK_LEXICALSCOPE:
+ return HasFinalReturn(pn->pn_expr);
+
+ case TOK_THROW:
+ return ENDS_IN_RETURN;
+
+ case TOK_TRY:
+ /* If we have a finally block that returns, we are done. */
+ if (pn->pn_kid3) {
+ rv = HasFinalReturn(pn->pn_kid3);
+ if (rv == ENDS_IN_RETURN)
+ return rv;
+ }
+
+ /* Else check the try block and any and all catch statements. */
+ rv = HasFinalReturn(pn->pn_kid1);
+ if (pn->pn_kid2) {
+ JS_ASSERT(pn->pn_kid2->pn_arity == PN_LIST);
+ for (pn2 = pn->pn_kid2->pn_head; pn2; pn2 = pn2->pn_next)
+ rv &= HasFinalReturn(pn2);
+ }
+ return rv;
+
+ case TOK_CATCH:
+ /* Check this catch block's body. */
+ return HasFinalReturn(pn->pn_kid3);
+
+ case TOK_LET:
+ /* Non-binary let statements are let declarations. */
+ if (pn->pn_arity != PN_BINARY)
+ return ENDS_IN_OTHER;
+ return HasFinalReturn(pn->pn_right);
+
+ default:
+ return ENDS_IN_OTHER;
+ }
+}
+
+static JSBool
+ReportBadReturn(JSContext *cx, JSTokenStream *ts, uintN flags, uintN errnum,
+ uintN anonerrnum)
+{
+ JSFunction *fun;
+ const char *name;
+
+ fun = cx->fp->fun;
+ if (fun->atom) {
+ name = js_AtomToPrintableString(cx, fun->atom);
+ } else {
+ errnum = anonerrnum;
+ name = NULL;
+ }
+ return js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | flags, errnum,
+ name);
+}
+
+static JSBool
+CheckFinalReturn(JSContext *cx, JSTokenStream *ts, JSParseNode *pn)
+{
+ return HasFinalReturn(pn) == ENDS_IN_RETURN ||
+ ReportBadReturn(cx, ts, JSREPORT_WARNING | JSREPORT_STRICT,
+ JSMSG_NO_RETURN_VALUE, JSMSG_ANON_NO_RETURN_VALUE);
+}
+
+static JSParseNode *
+FunctionBody(JSContext *cx, JSTokenStream *ts, JSFunction *fun,
+ JSTreeContext *tc)
+{
+ JSStackFrame *fp, frame;
+ JSObject *funobj;
+ JSStmtInfo stmtInfo;
+ uintN oldflags, firstLine;
+ JSParseNode *pn;
+
+ fp = cx->fp;
+ funobj = fun->object;
+ if (!fp || fp->fun != fun || fp->varobj != funobj ||
+ fp->scopeChain != funobj) {
+ memset(&frame, 0, sizeof frame);
+ frame.fun = fun;
+ frame.varobj = frame.scopeChain = funobj;
+ frame.down = fp;
+ if (fp)
+ frame.flags = fp->flags & JSFRAME_COMPILE_N_GO;
+ cx->fp = &frame;
+ }
+
+ /*
+ * Set interpreted early so js_EmitTree can test it to decide whether to
+ * eliminate useless expressions.
+ */
+ fun->flags |= JSFUN_INTERPRETED;
+
+ js_PushStatement(tc, &stmtInfo, STMT_BLOCK, -1);
+ stmtInfo.flags = SIF_BODY_BLOCK;
+
+ oldflags = tc->flags;
+ tc->flags &= ~(TCF_RETURN_EXPR | TCF_RETURN_VOID);
+ tc->flags |= TCF_IN_FUNCTION;
+
+ /*
+ * Save the body's first line, and store it in pn->pn_pos.begin.lineno
+ * later, because we may have not peeked in ts yet, so Statements won't
+ * acquire a valid pn->pn_pos.begin from the current token.
+ */
+ firstLine = ts->lineno;
+ pn = Statements(cx, ts, tc);
+
+ js_PopStatement(tc);
+
+ /* Check for falling off the end of a function that returns a value. */
+ if (pn && JS_HAS_STRICT_OPTION(cx) && (tc->flags & TCF_RETURN_EXPR)) {
+ if (!CheckFinalReturn(cx, ts, pn))
+ pn = NULL;
+ }
+
+ /*
+ * If we have a parse tree in pn and a code generator in tc, emit this
+ * function's code. We must do this here, not in js_CompileFunctionBody,
+ * in order to detect TCF_IN_FUNCTION among tc->flags.
+ */
+ if (pn) {
+ pn->pn_pos.begin.lineno = firstLine;
+ if ((tc->flags & TCF_COMPILING)) {
+ JSCodeGenerator *cg = (JSCodeGenerator *) tc;
+
+ if (!js_FoldConstants(cx, pn, tc) ||
+ !js_EmitFunctionBytecode(cx, cg, pn)) {
+ pn = NULL;
+ }
+ }
+ }
+
+ cx->fp = fp;
+ tc->flags = oldflags | (tc->flags & (TCF_FUN_FLAGS | TCF_HAS_DEFXMLNS));
+ return pn;
+}
+
+/*
+ * Compile a JS function body, which might appear as the value of an event
+ * handler attribute in an HTML <INPUT> tag.
+ */
+JSBool
+js_CompileFunctionBody(JSContext *cx, JSTokenStream *ts, JSFunction *fun)
+{
+ JSArenaPool codePool, notePool;
+ JSCodeGenerator funcg;
+ JSStackFrame *fp, frame;
+ JSObject *funobj;
+ JSParseNode *pn;
+
+ JS_InitArenaPool(&codePool, "code", 1024, sizeof(jsbytecode));
+ JS_InitArenaPool(&notePool, "note", 1024, sizeof(jssrcnote));
+ if (!js_InitCodeGenerator(cx, &funcg, &codePool, &notePool,
+ ts->filename, ts->lineno,
+ ts->principals)) {
+ return JS_FALSE;
+ }
+
+ /* Prevent GC activation while compiling. */
+ JS_KEEP_ATOMS(cx->runtime);
+
+ /* Push a JSStackFrame for use by FunctionBody. */
+ fp = cx->fp;
+ funobj = fun->object;
+ JS_ASSERT(!fp || (fp->fun != fun && fp->varobj != funobj &&
+ fp->scopeChain != funobj));
+ memset(&frame, 0, sizeof frame);
+ frame.fun = fun;
+ frame.varobj = frame.scopeChain = funobj;
+ frame.down = fp;
+ frame.flags = JS_HAS_COMPILE_N_GO_OPTION(cx)
+ ? JSFRAME_COMPILING | JSFRAME_COMPILE_N_GO
+ : JSFRAME_COMPILING;
+ cx->fp = &frame;
+
+ /*
+ * Farble the body so that it looks like a block statement to js_EmitTree,
+ * which is called beneath FunctionBody; see Statements, further below in
+ * this file. FunctionBody pushes a STMT_BLOCK record around its call to
+ * Statements, so Statements will not compile each statement as it loops
+ * to save JSParseNode space -- it will not compile at all, only build a
+ * JSParseNode tree.
+ *
+ * Therefore we must fold constants, allocate try notes, and generate code
+ * for this function, including a stop opcode at the end.
+ */
+ CURRENT_TOKEN(ts).type = TOK_LC;
+ pn = FunctionBody(cx, ts, fun, &funcg.treeContext);
+ if (pn && !js_NewScriptFromCG(cx, &funcg, fun))
+ pn = NULL;
+
+ /* Restore saved state and release code generation arenas. */
+ cx->fp = fp;
+ JS_UNKEEP_ATOMS(cx->runtime);
+ js_FinishCodeGenerator(cx, &funcg);
+ JS_FinishArenaPool(&codePool);
+ JS_FinishArenaPool(&notePool);
+ return pn != NULL;
+}
+
+/*
+ * Parameter block types for the several Binder functions. We use a common
+ * helper function signature in order to share code among destructuring and
+ * simple variable declaration parsers. In the destructuring case, the binder
+ * function is called indirectly from the variable declaration parser by way
+ * of CheckDestructuring and its friends.
+ */
+typedef struct BindData BindData;
+
+typedef JSBool
+(*Binder)(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc);
+
+struct BindData {
+ JSParseNode *pn; /* error source coordinate */
+ JSTokenStream *ts; /* fallback if pn is null */
+ JSObject *obj; /* the variable object */
+ JSOp op; /* prolog bytecode or nop */
+ Binder binder; /* binder, discriminates u */
+ union {
+ struct {
+ JSFunction *fun; /* must come first! see next */
+ } arg;
+ struct {
+ JSFunction *fun; /* this overlays u.arg.fun */
+ JSClass *clasp;
+ JSPropertyOp getter;
+ JSPropertyOp setter;
+ uintN attrs;
+ } var;
+ struct {
+ jsuint index;
+ uintN overflow;
+ } let;
+ } u;
+};
+
+/*
+ * Given BindData *data and JSREPORT_* flags, expand to the second and third
+ * actual parameters to js_ReportCompileErrorNumber. Prefer reporting via pn
+ * to reporting via ts, for better destructuring error pointers.
+ */
+#define BIND_DATA_REPORT_ARGS(data, flags) \
+ (data)->pn ? (void *)(data)->pn : (void *)(data)->ts, \
+ ((data)->pn ? JSREPORT_PN : JSREPORT_TS) | (flags)
+
+static JSBool
+BumpFormalCount(JSContext *cx, JSFunction *fun)
+{
+ if (fun->nargs == JS_BITMASK(16)) {
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+ JSMSG_TOO_MANY_FUN_ARGS);
+ return JS_FALSE;
+ }
+ fun->nargs++;
+ return JS_TRUE;
+}
+
+static JSBool
+BindArg(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc)
+{
+ JSObject *obj, *pobj;
+ JSProperty *prop;
+ JSBool ok;
+ uintN dupflag;
+ JSFunction *fun;
+ const char *name;
+
+ obj = data->obj;
+ ok = js_LookupHiddenProperty(cx, obj, ATOM_TO_JSID(atom), &pobj, &prop);
+ if (!ok)
+ return JS_FALSE;
+
+ dupflag = 0;
+ if (prop) {
+ JS_ASSERT(pobj == obj);
+ name = js_AtomToPrintableString(cx, atom);
+
+ /*
+ * A duplicate parameter name, a "feature" required by ECMA-262.
+ * We force a duplicate node on the SCOPE_LAST_PROP(scope) list
+ * with the same id, distinguished by the SPROP_IS_DUPLICATE flag,
+ * and not mapped by an entry in scope.
+ */
+ ok = name &&
+ js_ReportCompileErrorNumber(cx,
+ BIND_DATA_REPORT_ARGS(data,
+ JSREPORT_WARNING |
+ JSREPORT_STRICT),
+ JSMSG_DUPLICATE_FORMAL,
+ name);
+
+ OBJ_DROP_PROPERTY(cx, pobj, prop);
+ if (!ok)
+ return JS_FALSE;
+
+ dupflag = SPROP_IS_DUPLICATE;
+ }
+
+ fun = data->u.arg.fun;
+ if (!js_AddHiddenProperty(cx, data->obj, ATOM_TO_JSID(atom),
+ js_GetArgument, js_SetArgument,
+ SPROP_INVALID_SLOT,
+ JSPROP_PERMANENT | JSPROP_SHARED,
+ dupflag | SPROP_HAS_SHORTID,
+ fun->nargs)) {
+ return JS_FALSE;
+ }
+
+ return BumpFormalCount(cx, fun);
+}
+
+static JSBool
+BindLocalVariable(JSContext *cx, BindData *data, JSAtom *atom)
+{
+ JSFunction *fun;
+
+ /*
+ * Can't increase fun->nvars in an active frame, so insist that getter is
+ * js_GetLocalVariable, not js_GetCallVariable or anything else.
+ */
+ if (data->u.var.getter != js_GetLocalVariable)
+ return JS_TRUE;
+
+ /*
+ * Don't bind a variable with the hidden name 'arguments', per ECMA-262.
+ * Instead 'var arguments' always restates the predefined property of the
+ * activation objects with unhidden name 'arguments'. Assignment to such
+ * a variable must be handled specially.
+ */
+ if (atom == cx->runtime->atomState.argumentsAtom)
+ return JS_TRUE;
+
+ fun = data->u.var.fun;
+ if (!js_AddHiddenProperty(cx, data->obj, ATOM_TO_JSID(atom),
+ data->u.var.getter, data->u.var.setter,
+ SPROP_INVALID_SLOT,
+ data->u.var.attrs | JSPROP_SHARED,
+ SPROP_HAS_SHORTID, fun->u.i.nvars)) {
+ return JS_FALSE;
+ }
+ if (fun->u.i.nvars == JS_BITMASK(16)) {
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+ JSMSG_TOO_MANY_FUN_VARS);
+ return JS_FALSE;
+ }
+ fun->u.i.nvars++;
+ return JS_TRUE;
+}
+
+#if JS_HAS_DESTRUCTURING
+/*
+ * Forward declaration to maintain top-down presentation.
+ */
+static JSParseNode *
+DestructuringExpr(JSContext *cx, BindData *data, JSTreeContext *tc,
+ JSTokenType tt);
+
+static JSBool
+BindDestructuringArg(JSContext *cx, BindData *data, JSAtom *atom,
+ JSTreeContext *tc)
+{
+ JSAtomListElement *ale;
+ JSFunction *fun;
+ JSObject *obj, *pobj;
+ JSProperty *prop;
+ const char *name;
+
+ ATOM_LIST_SEARCH(ale, &tc->decls, atom);
+ if (!ale) {
+ ale = js_IndexAtom(cx, atom, &tc->decls);
+ if (!ale)
+ return JS_FALSE;
+ ALE_SET_JSOP(ale, data->op);
+ }
+
+ fun = data->u.var.fun;
+ obj = data->obj;
+ if (!js_LookupHiddenProperty(cx, obj, ATOM_TO_JSID(atom), &pobj, &prop))
+ return JS_FALSE;
+
+ if (prop) {
+ JS_ASSERT(pobj == obj && OBJ_IS_NATIVE(pobj));
+ name = js_AtomToPrintableString(cx, atom);
+ if (!name ||
+ !js_ReportCompileErrorNumber(cx,
+ BIND_DATA_REPORT_ARGS(data,
+ JSREPORT_WARNING |
+ JSREPORT_STRICT),
+ JSMSG_DUPLICATE_FORMAL,
+ name)) {
+ return JS_FALSE;
+ }
+ OBJ_DROP_PROPERTY(cx, pobj, prop);
+ } else {
+ if (!BindLocalVariable(cx, data, atom))
+ return JS_FALSE;
+ }
+ return JS_TRUE;
+}
+#endif /* JS_HAS_DESTRUCTURING */
+
+static JSParseNode *
+FunctionDef(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
+ JSBool lambda)
+{
+ JSOp op, prevop;
+ JSParseNode *pn, *body, *result;
+ JSTokenType tt;
+ JSAtom *funAtom, *objAtom;
+ JSStackFrame *fp;
+ JSObject *varobj, *pobj;
+ JSAtomListElement *ale;
+ JSProperty *prop;
+ JSFunction *fun;
+ JSTreeContext funtc;
+#if JS_HAS_DESTRUCTURING
+ JSParseNode *item, *list = NULL;
+#endif
+
+ /* Make a TOK_FUNCTION node. */
+#if JS_HAS_GETTER_SETTER
+ op = CURRENT_TOKEN(ts).t_op;
+#endif
+ pn = NewParseNode(cx, ts, PN_FUNC, tc);
+ if (!pn)
+ return NULL;
+
+ /* Scan the optional function name into funAtom. */
+ ts->flags |= TSF_KEYWORD_IS_NAME;
+ tt = js_GetToken(cx, ts);
+ ts->flags &= ~TSF_KEYWORD_IS_NAME;
+ if (tt == TOK_NAME) {
+ funAtom = CURRENT_TOKEN(ts).t_atom;
+ } else {
+ funAtom = NULL;
+ js_UngetToken(ts);
+ }
+
+ /* Find the nearest variable-declaring scope and use it as our parent. */
+ fp = cx->fp;
+ varobj = fp->varobj;
+
+ /*
+ * Record names for function statements in tc->decls so we know when to
+ * avoid optimizing variable references that might name a function.
+ */
+ if (!lambda && funAtom) {
+ ATOM_LIST_SEARCH(ale, &tc->decls, funAtom);
+ if (ale) {
+ prevop = ALE_JSOP(ale);
+ if (JS_HAS_STRICT_OPTION(cx) || prevop == JSOP_DEFCONST) {
+ const char *name = js_AtomToPrintableString(cx, funAtom);
+ if (!name ||
+ !js_ReportCompileErrorNumber(cx, ts,
+ (prevop != JSOP_DEFCONST)
+ ? JSREPORT_TS |
+ JSREPORT_WARNING |
+ JSREPORT_STRICT
+ : JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_REDECLARED_VAR,
+ (prevop == JSOP_DEFFUN ||
+ prevop == JSOP_CLOSURE)
+ ? js_function_str
+ : (prevop == JSOP_DEFCONST)
+ ? js_const_str
+ : js_var_str,
+ name)) {
+ return NULL;
+ }
+ }
+ if (!AT_TOP_LEVEL(tc) && prevop == JSOP_DEFVAR)
+ tc->flags |= TCF_FUN_CLOSURE_VS_VAR;
+ } else {
+ ale = js_IndexAtom(cx, funAtom, &tc->decls);
+ if (!ale)
+ return NULL;
+ }
+ ALE_SET_JSOP(ale, AT_TOP_LEVEL(tc) ? JSOP_DEFFUN : JSOP_CLOSURE);
+
+ /*
+ * A function nested at top level inside another's body needs only a
+ * local variable to bind its name to its value, and not an activation
+ * object property (it might also need the activation property, if the
+ * outer function contains with statements, e.g., but the stack slot
+ * wins when jsemit.c's BindNameToSlot can optimize a JSOP_NAME into a
+ * JSOP_GETVAR bytecode).
+ */
+ if (AT_TOP_LEVEL(tc) && (tc->flags & TCF_IN_FUNCTION)) {
+ JSScopeProperty *sprop;
+
+ /*
+ * Define a property on the outer function so that BindNameToSlot
+ * can properly optimize accesses.
+ */
+ JS_ASSERT(OBJ_GET_CLASS(cx, varobj) == &js_FunctionClass);
+ JS_ASSERT(fp->fun == (JSFunction *) JS_GetPrivate(cx, varobj));
+ if (!js_LookupHiddenProperty(cx, varobj, ATOM_TO_JSID(funAtom),
+ &pobj, &prop)) {
+ return NULL;
+ }
+ if (prop)
+ OBJ_DROP_PROPERTY(cx, pobj, prop);
+ sprop = NULL;
+ if (!prop ||
+ pobj != varobj ||
+ (sprop = (JSScopeProperty *)prop,
+ sprop->getter != js_GetLocalVariable)) {
+ uintN sflags;
+
+ /*
+ * Use SPROP_IS_DUPLICATE if there is a formal argument of the
+ * same name, so the decompiler can find the parameter name.
+ */
+ sflags = (sprop && sprop->getter == js_GetArgument)
+ ? SPROP_IS_DUPLICATE | SPROP_HAS_SHORTID
+ : SPROP_HAS_SHORTID;
+ if (!js_AddHiddenProperty(cx, varobj, ATOM_TO_JSID(funAtom),
+ js_GetLocalVariable,
+ js_SetLocalVariable,
+ SPROP_INVALID_SLOT,
+ JSPROP_PERMANENT | JSPROP_SHARED,
+ sflags, fp->fun->u.i.nvars)) {
+ return NULL;
+ }
+ if (fp->fun->u.i.nvars == JS_BITMASK(16)) {
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+ JSMSG_TOO_MANY_FUN_VARS);
+ return NULL;
+ }
+ fp->fun->u.i.nvars++;
+ }
+ }
+ }
+
+ fun = js_NewFunction(cx, NULL, NULL, 0, lambda ? JSFUN_LAMBDA : 0, varobj,
+ funAtom);
+ if (!fun)
+ return NULL;
+#if JS_HAS_GETTER_SETTER
+ if (op != JSOP_NOP)
+ fun->flags |= (op == JSOP_GETTER) ? JSPROP_GETTER : JSPROP_SETTER;
+#endif
+
+ /*
+ * Atomize fun->object early to protect against a last-ditch GC under
+ * js_LookupHiddenProperty.
+ *
+ * Absent use of the new scoped local GC roots API around compiler calls,
+ * we need to atomize here to protect against a GC activation. Atoms are
+ * protected from GC during compilation by the JS_FRIEND_API entry points
+ * in this file. There doesn't seem to be any gain in switching from the
+ * atom-keeping method to the bulkier, slower scoped local roots method.
+ */
+ objAtom = js_AtomizeObject(cx, fun->object, 0);
+ if (!objAtom)
+ return NULL;
+
+ /* Initialize early for possible flags mutation via DestructuringExpr. */
+ TREE_CONTEXT_INIT(&funtc);
+
+ /* Now parse formal argument list and compute fun->nargs. */
+ MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_FORMAL);
+ if (!js_MatchToken(cx, ts, TOK_RP)) {
+ BindData data;
+
+ data.pn = NULL;
+ data.ts = ts;
+ data.obj = fun->object;
+ data.op = JSOP_NOP;
+ data.binder = BindArg;
+ data.u.arg.fun = fun;
+
+ do {
+ tt = js_GetToken(cx, ts);
+ switch (tt) {
+#if JS_HAS_DESTRUCTURING
+ case TOK_LB:
+ case TOK_LC:
+ {
+ JSParseNode *lhs, *rhs;
+ jsint slot;
+
+ /*
+ * A destructuring formal parameter turns into one or more
+ * local variables initialized from properties of a single
+ * anonymous positional parameter, so here we must tweak our
+ * binder and its data.
+ */
+ data.op = JSOP_DEFVAR;
+ data.binder = BindDestructuringArg;
+ data.u.var.clasp = &js_FunctionClass;
+ data.u.var.getter = js_GetLocalVariable;
+ data.u.var.setter = js_SetLocalVariable;
+ data.u.var.attrs = JSPROP_PERMANENT;
+
+ /*
+ * Temporarily transfer the owneship of the recycle list to
+ * funtc. See bug 313967.
+ */
+ funtc.nodeList = tc->nodeList;
+ tc->nodeList = NULL;
+ lhs = DestructuringExpr(cx, &data, &funtc, tt);
+ tc->nodeList = funtc.nodeList;
+ funtc.nodeList = NULL;
+ if (!lhs)
+ return NULL;
+
+ /*
+ * Restore the formal parameter binder in case there are more
+ * non-destructuring formals in the parameter list.
+ */
+ data.binder = BindArg;
+
+ /*
+ * Adjust fun->nargs to count the single anonymous positional
+ * parameter that is to be destructured.
+ */
+ slot = fun->nargs;
+ if (!BumpFormalCount(cx, fun))
+ return NULL;
+
+ /*
+ * Synthesize a destructuring assignment from the single
+ * anonymous positional parameter into the destructuring
+ * left-hand-side expression and accumulate it in list.
+ */
+ rhs = NewParseNode(cx, ts, PN_NAME, tc);
+ if (!rhs)
+ return NULL;
+ rhs->pn_type = TOK_NAME;
+ rhs->pn_op = JSOP_GETARG;
+ rhs->pn_atom = cx->runtime->atomState.emptyAtom;
+ rhs->pn_expr = NULL;
+ rhs->pn_slot = slot;
+ rhs->pn_attrs = 0;
+
+ item = NewBinary(cx, TOK_ASSIGN, JSOP_NOP, lhs, rhs, tc);
+ if (!item)
+ return NULL;
+ if (!list) {
+ list = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!list)
+ return NULL;
+ list->pn_type = TOK_COMMA;
+ PN_INIT_LIST(list);
+ }
+ PN_APPEND(list, item);
+ break;
+ }
+#endif /* JS_HAS_DESTRUCTURING */
+
+ case TOK_NAME:
+ if (!data.binder(cx, &data, CURRENT_TOKEN(ts).t_atom, tc))
+ return NULL;
+ break;
+
+ default:
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_MISSING_FORMAL);
+ return NULL;
+ }
+ } while (js_MatchToken(cx, ts, TOK_COMMA));
+
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FORMAL);
+ }
+
+ MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_BODY);
+ pn->pn_pos.begin = CURRENT_TOKEN(ts).pos.begin;
+
+ /*
+ * Temporarily transfer the owneship of the recycle list to funtc.
+ * See bug 313967.
+ */
+ funtc.nodeList = tc->nodeList;
+ tc->nodeList = NULL;
+ body = FunctionBody(cx, ts, fun, &funtc);
+ tc->nodeList = funtc.nodeList;
+ funtc.nodeList = NULL;
+
+ if (!body)
+ return NULL;
+
+ MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_BODY);
+ pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
+
+#if JS_HAS_DESTRUCTURING
+ /*
+ * If there were destructuring formal parameters, prepend the initializing
+ * comma expression that we synthesized to body. If the body is a lexical
+ * scope node, we must make a special TOK_BODY node, to prepend the formal
+ * parameter destructuring code without bracing the decompilation of the
+ * function body's lexical scope.
+ */
+ if (list) {
+ if (body->pn_arity != PN_LIST) {
+ JSParseNode *block;
+
+ JS_ASSERT(body->pn_type == TOK_LEXICALSCOPE);
+ JS_ASSERT(body->pn_arity == PN_NAME);
+
+ block = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!block)
+ return NULL;
+ block->pn_type = TOK_BODY;
+ block->pn_pos = body->pn_pos;
+ PN_INIT_LIST_1(block, body);
+
+ body = block;
+ }
+
+ item = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!item)
+ return NULL;
+
+ item->pn_type = TOK_SEMI;
+ item->pn_pos.begin = item->pn_pos.end = body->pn_pos.begin;
+ item->pn_kid = list;
+ item->pn_next = body->pn_head;
+ body->pn_head = item;
+ if (body->pn_tail == &body->pn_head)
+ body->pn_tail = &item->pn_next;
+ ++body->pn_count;
+ }
+#endif
+
+ /*
+ * If we collected flags that indicate nested heavyweight functions, or
+ * this function contains heavyweight-making statements (references to
+ * __parent__ or __proto__; use of with, eval, import, or export; and
+ * assignment to arguments), flag the function as heavyweight (requiring
+ * a call object per invocation).
+ */
+ if (funtc.flags & TCF_FUN_HEAVYWEIGHT) {
+ fun->flags |= JSFUN_HEAVYWEIGHT;
+ tc->flags |= TCF_FUN_HEAVYWEIGHT;
+ } else {
+ /*
+ * If this function is a named statement function not at top-level
+ * (i.e. a JSOP_CLOSURE, not a function definiton or expression), then
+ * our enclosing function, if any, must be heavyweight.
+ *
+ * The TCF_FUN_USES_NONLOCALS flag is set only by the code generator,
+ * so it won't be set here. Assert that it's not. We have to check
+ * it later, in js_EmitTree, after js_EmitFunctionBody has traversed
+ * the function's body
+ */
+ JS_ASSERT(!(funtc.flags & TCF_FUN_USES_NONLOCALS));
+ if (!lambda && funAtom && !AT_TOP_LEVEL(tc))
+ tc->flags |= TCF_FUN_HEAVYWEIGHT;
+ }
+
+ result = pn;
+ if (lambda) {
+ /*
+ * ECMA ed. 3 standard: function expression, possibly anonymous.
+ */
+ op = funAtom ? JSOP_NAMEDFUNOBJ : JSOP_ANONFUNOBJ;
+ } else if (!funAtom) {
+ /*
+ * If this anonymous function definition is *not* embedded within a
+ * larger expression, we treat it as an expression statement, not as
+ * a function declaration -- and not as a syntax error (as ECMA-262
+ * Edition 3 would have it). Backward compatibility trumps all.
+ */
+ result = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!result)
+ return NULL;
+ result->pn_type = TOK_SEMI;
+ result->pn_pos = pn->pn_pos;
+ result->pn_kid = pn;
+ op = JSOP_ANONFUNOBJ;
+ } else if (!AT_TOP_LEVEL(tc)) {
+ /*
+ * ECMA ed. 3 extension: a function expression statement not at the
+ * top level, e.g., in a compound statement such as the "then" part
+ * of an "if" statement, binds a closure only if control reaches that
+ * sub-statement.
+ */
+ op = JSOP_CLOSURE;
+ } else {
+ op = JSOP_NOP;
+ }
+
+ pn->pn_funAtom = objAtom;
+ pn->pn_op = op;
+ pn->pn_body = body;
+ pn->pn_flags = funtc.flags & (TCF_FUN_FLAGS | TCF_HAS_DEFXMLNS);
+ pn->pn_tryCount = funtc.tryCount;
+ TREE_CONTEXT_FINISH(&funtc);
+ return result;
+}
+
+static JSParseNode *
+FunctionStmt(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ return FunctionDef(cx, ts, tc, JS_FALSE);
+}
+
+static JSParseNode *
+FunctionExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ return FunctionDef(cx, ts, tc, JS_TRUE);
+}
+
+/*
+ * Parse the statements in a block, creating a TOK_LC node that lists the
+ * statements' trees. If called from block-parsing code, the caller must
+ * match { before and } after.
+ */
+static JSParseNode *
+Statements(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn, *pn2, *saveBlock;
+ JSTokenType tt;
+
+ CHECK_RECURSION();
+
+ pn = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!pn)
+ return NULL;
+ saveBlock = tc->blockNode;
+ tc->blockNode = pn;
+ PN_INIT_LIST(pn);
+
+ ts->flags |= TSF_OPERAND;
+ while ((tt = js_PeekToken(cx, ts)) > TOK_EOF && tt != TOK_RC) {
+ ts->flags &= ~TSF_OPERAND;
+ pn2 = Statement(cx, ts, tc);
+ if (!pn2) {
+ if (ts->flags & TSF_EOF)
+ ts->flags |= TSF_UNEXPECTED_EOF;
+ return NULL;
+ }
+ ts->flags |= TSF_OPERAND;
+
+ /* Detect a function statement for the TOK_LC case in Statement. */
+ if (pn2->pn_type == TOK_FUNCTION && !AT_TOP_LEVEL(tc))
+ tc->flags |= TCF_HAS_FUNCTION_STMT;
+
+ /* If compiling top-level statements, emit as we go to save space. */
+ if (!tc->topStmt && (tc->flags & TCF_COMPILING)) {
+ if (cx->fp->fun &&
+ JS_HAS_STRICT_OPTION(cx) &&
+ (tc->flags & TCF_RETURN_EXPR)) {
+ /*
+ * Check pn2 for lack of a final return statement if it is the
+ * last statement in the block.
+ */
+ tt = js_PeekToken(cx, ts);
+ if ((tt == TOK_EOF || tt == TOK_RC) &&
+ !CheckFinalReturn(cx, ts, pn2)) {
+ tt = TOK_ERROR;
+ break;
+ }
+
+ /*
+ * Clear TCF_RETURN_EXPR so FunctionBody doesn't try to
+ * CheckFinalReturn again.
+ */
+ tc->flags &= ~TCF_RETURN_EXPR;
+ }
+ if (!js_FoldConstants(cx, pn2, tc) ||
+ !js_AllocTryNotes(cx, (JSCodeGenerator *)tc) ||
+ !js_EmitTree(cx, (JSCodeGenerator *)tc, pn2)) {
+ tt = TOK_ERROR;
+ break;
+ }
+ RecycleTree(pn2, tc);
+ } else {
+ PN_APPEND(pn, pn2);
+ }
+ }
+
+ /*
+ * Handle the case where there was a let declaration under this block. If
+ * it replaced tc->blockNode with a new block node then we must refresh pn
+ * and then restore tc->blockNode.
+ */
+ if (tc->blockNode != pn)
+ pn = tc->blockNode;
+ tc->blockNode = saveBlock;
+
+ ts->flags &= ~TSF_OPERAND;
+ if (tt == TOK_ERROR)
+ return NULL;
+
+ pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
+ return pn;
+}
+
+static JSParseNode *
+Condition(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn, *pn2;
+
+ MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND);
+ pn = Expr(cx, ts, tc);
+ if (!pn)
+ return NULL;
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND);
+
+ /*
+ * Check for (a = b) and "correct" it to (a == b) iff b's operator has
+ * greater precedence than ==.
+ * XXX not ECMA, but documented in several books -- now a strict warning.
+ */
+ if (pn->pn_type == TOK_ASSIGN &&
+ pn->pn_op == JSOP_NOP &&
+ pn->pn_right->pn_type > TOK_EQOP)
+ {
+ JSBool rewrite = !JS_VERSION_IS_ECMA(cx);
+ if (!js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS |
+ JSREPORT_WARNING |
+ JSREPORT_STRICT,
+ JSMSG_EQUAL_AS_ASSIGN,
+ rewrite
+ ? "\nAssuming equality test"
+ : "")) {
+ return NULL;
+ }
+ if (rewrite) {
+ pn->pn_type = TOK_EQOP;
+ pn->pn_op = (JSOp)cx->jsop_eq;
+ pn2 = pn->pn_left;
+ switch (pn2->pn_op) {
+ case JSOP_SETNAME:
+ pn2->pn_op = JSOP_NAME;
+ break;
+ case JSOP_SETPROP:
+ pn2->pn_op = JSOP_GETPROP;
+ break;
+ case JSOP_SETELEM:
+ pn2->pn_op = JSOP_GETELEM;
+ break;
+ default:
+ JS_ASSERT(0);
+ }
+ }
+ }
+ return pn;
+}
+
+static JSBool
+MatchLabel(JSContext *cx, JSTokenStream *ts, JSParseNode *pn)
+{
+ JSAtom *label;
+ JSTokenType tt;
+
+ tt = js_PeekTokenSameLine(cx, ts);
+ if (tt == TOK_ERROR)
+ return JS_FALSE;
+ if (tt == TOK_NAME) {
+ (void) js_GetToken(cx, ts);
+ label = CURRENT_TOKEN(ts).t_atom;
+ } else {
+ label = NULL;
+ }
+ pn->pn_atom = label;
+ return JS_TRUE;
+}
+
+#if JS_HAS_EXPORT_IMPORT
+static JSParseNode *
+ImportExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn, *pn2;
+ JSTokenType tt;
+
+ MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_IMPORT_NAME);
+ pn = NewParseNode(cx, ts, PN_NAME, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_op = JSOP_NAME;
+ pn->pn_atom = CURRENT_TOKEN(ts).t_atom;
+ pn->pn_expr = NULL;
+ pn->pn_slot = -1;
+ pn->pn_attrs = 0;
+
+ ts->flags |= TSF_OPERAND;
+ while ((tt = js_GetToken(cx, ts)) == TOK_DOT || tt == TOK_LB) {
+ ts->flags &= ~TSF_OPERAND;
+ if (pn->pn_op == JSOP_IMPORTALL)
+ goto bad_import;
+
+ if (tt == TOK_DOT) {
+ pn2 = NewParseNode(cx, ts, PN_NAME, tc);
+ if (!pn2)
+ return NULL;
+ ts->flags |= TSF_KEYWORD_IS_NAME;
+ if (js_MatchToken(cx, ts, TOK_STAR)) {
+ pn2->pn_op = JSOP_IMPORTALL;
+ pn2->pn_atom = NULL;
+ pn2->pn_slot = -1;
+ pn2->pn_attrs = 0;
+ } else {
+ MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NAME_AFTER_DOT);
+ pn2->pn_op = JSOP_GETPROP;
+ pn2->pn_atom = CURRENT_TOKEN(ts).t_atom;
+ pn2->pn_slot = -1;
+ pn2->pn_attrs = 0;
+ }
+ ts->flags &= ~TSF_KEYWORD_IS_NAME;
+ pn2->pn_expr = pn;
+ pn2->pn_pos.begin = pn->pn_pos.begin;
+ pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
+ } else {
+ /* Make a TOK_LB binary node. */
+ pn2 = NewBinary(cx, tt, JSOP_GETELEM, pn, Expr(cx, ts, tc), tc);
+ if (!pn2)
+ return NULL;
+
+ MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX);
+ }
+
+ pn = pn2;
+ ts->flags |= TSF_OPERAND;
+ }
+ ts->flags &= ~TSF_OPERAND;
+ if (tt == TOK_ERROR)
+ return NULL;
+ js_UngetToken(ts);
+
+ switch (pn->pn_op) {
+ case JSOP_GETPROP:
+ pn->pn_op = JSOP_IMPORTPROP;
+ break;
+ case JSOP_GETELEM:
+ pn->pn_op = JSOP_IMPORTELEM;
+ break;
+ case JSOP_IMPORTALL:
+ break;
+ default:
+ goto bad_import;
+ }
+ return pn;
+
+ bad_import:
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_IMPORT);
+ return NULL;
+}
+#endif /* JS_HAS_EXPORT_IMPORT */
+
+static JSBool
+BindLet(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc)
+{
+ JSObject *blockObj;
+ JSScopeProperty *sprop;
+ JSAtomListElement *ale;
+
+ blockObj = data->obj;
+ sprop = SCOPE_GET_PROPERTY(OBJ_SCOPE(blockObj), ATOM_TO_JSID(atom));
+ ATOM_LIST_SEARCH(ale, &tc->decls, atom);
+ if (sprop || (ale && ALE_JSOP(ale) == JSOP_DEFCONST)) {
+ const char *name;
+
+ if (sprop) {
+ JS_ASSERT(sprop->flags & SPROP_HAS_SHORTID);
+ JS_ASSERT((uint16)sprop->shortid < data->u.let.index);
+ }
+
+ name = js_AtomToPrintableString(cx, atom);
+ if (name) {
+ js_ReportCompileErrorNumber(cx,
+ BIND_DATA_REPORT_ARGS(data,
+ JSREPORT_ERROR),
+ JSMSG_REDECLARED_VAR,
+ (ale && ALE_JSOP(ale) == JSOP_DEFCONST)
+ ? js_const_str
+ : "variable",
+ name);
+ }
+ return JS_FALSE;
+ }
+
+ if (data->u.let.index == JS_BIT(16)) {
+ js_ReportCompileErrorNumber(cx,
+ BIND_DATA_REPORT_ARGS(data, JSREPORT_ERROR),
+ data->u.let.overflow);
+ return JS_FALSE;
+ }
+
+ /* Use JSPROP_ENUMERATE to aid the disassembler. */
+ return js_DefineNativeProperty(cx, blockObj, ATOM_TO_JSID(atom),
+ JSVAL_VOID, NULL, NULL,
+ JSPROP_ENUMERATE | JSPROP_PERMANENT,
+ SPROP_HAS_SHORTID,
+ (intN)data->u.let.index++,
+ NULL);
+}
+
+static JSBool
+BindVarOrConst(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc)
+{
+ JSStmtInfo *stmt;
+ JSAtomListElement *ale;
+ JSOp op, prevop;
+ const char *name;
+ JSFunction *fun;
+ JSObject *obj, *pobj;
+ JSProperty *prop;
+ JSBool ok;
+ JSPropertyOp getter, setter;
+ JSScopeProperty *sprop;
+
+ stmt = js_LexicalLookup(tc, atom, NULL, JS_FALSE);
+ ATOM_LIST_SEARCH(ale, &tc->decls, atom);
+ op = data->op;
+ if ((stmt && stmt->type != STMT_WITH) || ale) {
+ prevop = ale ? ALE_JSOP(ale) : JSOP_DEFVAR;
+ if (JS_HAS_STRICT_OPTION(cx)
+ ? op != JSOP_DEFVAR || prevop != JSOP_DEFVAR
+ : op == JSOP_DEFCONST || prevop == JSOP_DEFCONST) {
+ name = js_AtomToPrintableString(cx, atom);
+ if (!name ||
+ !js_ReportCompileErrorNumber(cx,
+ BIND_DATA_REPORT_ARGS(data,
+ (op != JSOP_DEFCONST &&
+ prevop != JSOP_DEFCONST)
+ ? JSREPORT_WARNING |
+ JSREPORT_STRICT
+ : JSREPORT_ERROR),
+ JSMSG_REDECLARED_VAR,
+ (prevop == JSOP_DEFFUN ||
+ prevop == JSOP_CLOSURE)
+ ? js_function_str
+ : (prevop == JSOP_DEFCONST)
+ ? js_const_str
+ : js_var_str,
+ name)) {
+ return JS_FALSE;
+ }
+ }
+ if (op == JSOP_DEFVAR && prevop == JSOP_CLOSURE)
+ tc->flags |= TCF_FUN_CLOSURE_VS_VAR;
+ }
+ if (!ale) {
+ ale = js_IndexAtom(cx, atom, &tc->decls);
+ if (!ale)
+ return JS_FALSE;
+ }
+ ALE_SET_JSOP(ale, op);
+
+ fun = data->u.var.fun;
+ obj = data->obj;
+ if (!fun) {
+ /* Don't lookup global variables at compile time. */
+ prop = NULL;
+ } else {
+ JS_ASSERT(OBJ_IS_NATIVE(obj));
+ if (!js_LookupHiddenProperty(cx, obj, ATOM_TO_JSID(atom),
+ &pobj, &prop)) {
+ return JS_FALSE;
+ }
+ }
+
+ ok = JS_TRUE;
+ getter = data->u.var.getter;
+ setter = data->u.var.setter;
+
+ if (prop && pobj == obj && OBJ_IS_NATIVE(pobj)) {
+ sprop = (JSScopeProperty *)prop;
+ if (sprop->getter == js_GetArgument) {
+ name = js_AtomToPrintableString(cx, atom);
+ if (!name) {
+ ok = JS_FALSE;
+ } else if (op == JSOP_DEFCONST) {
+ js_ReportCompileErrorNumber(cx,
+ BIND_DATA_REPORT_ARGS(data,
+ JSREPORT_ERROR),
+ JSMSG_REDECLARED_PARAM,
+ name);
+ ok = JS_FALSE;
+ } else {
+ getter = js_GetArgument;
+ setter = js_SetArgument;
+ ok = js_ReportCompileErrorNumber(cx,
+ BIND_DATA_REPORT_ARGS(data,
+ JSREPORT_WARNING |
+ JSREPORT_STRICT),
+ JSMSG_VAR_HIDES_ARG,
+ name);
+ }
+ } else {
+ JS_ASSERT(getter == js_GetLocalVariable);
+
+ if (fun) {
+ /* Not an argument, must be a redeclared local var. */
+ if (data->u.var.clasp == &js_FunctionClass) {
+ JS_ASSERT(sprop->getter == js_GetLocalVariable);
+ JS_ASSERT((sprop->flags & SPROP_HAS_SHORTID) &&
+ (uint16) sprop->shortid < fun->u.i.nvars);
+ } else if (data->u.var.clasp == &js_CallClass) {
+ if (sprop->getter == js_GetCallVariable) {
+ /*
+ * Referencing a name introduced by a var statement in
+ * the enclosing function. Check that the slot number
+ * we have is in range.
+ */
+ JS_ASSERT((sprop->flags & SPROP_HAS_SHORTID) &&
+ (uint16) sprop->shortid < fun->u.i.nvars);
+ } else {
+ /*
+ * A variable introduced through another eval: don't
+ * use the special getters and setters since we can't
+ * allocate a slot in the frame.
+ */
+ getter = sprop->getter;
+ setter = sprop->setter;
+ }
+ }
+
+ /* Override the old getter and setter, to handle eval. */
+ sprop = js_ChangeNativePropertyAttrs(cx, obj, sprop,
+ 0, sprop->attrs,
+ getter, setter);
+ if (!sprop)
+ ok = JS_FALSE;
+ }
+ }
+ if (prop)
+ OBJ_DROP_PROPERTY(cx, pobj, prop);
+ } else {
+ /*
+ * Property not found in current variable scope: we have not seen this
+ * variable before. Define a new local variable by adding a property
+ * to the function's scope, allocating one slot in the function's vars
+ * frame. Global variables and any locals declared in with statement
+ * bodies are handled at runtime, by script prolog JSOP_DEFVAR opcodes
+ * generated for slot-less vars.
+ */
+ sprop = NULL;
+ if (prop) {
+ OBJ_DROP_PROPERTY(cx, pobj, prop);
+ prop = NULL;
+ }
+
+ if (cx->fp->scopeChain == obj &&
+ !js_InWithStatement(tc) &&
+ !BindLocalVariable(cx, data, atom)) {
+ return JS_FALSE;
+ }
+ }
+ return ok;
+}
+
+#if JS_HAS_DESTRUCTURING
+
+static JSBool
+BindDestructuringVar(JSContext *cx, BindData *data, JSParseNode *pn,
+ JSTreeContext *tc)
+{
+ JSAtom *atom;
+
+ /*
+ * Destructuring is a form of assignment, so just as for an initialized
+ * simple variable, we must check for assignment to 'arguments' and flag
+ * the enclosing function (if any) as heavyweight.
+ */
+ JS_ASSERT(pn->pn_type == TOK_NAME);
+ atom = pn->pn_atom;
+ if (atom == cx->runtime->atomState.argumentsAtom)
+ tc->flags |= TCF_FUN_HEAVYWEIGHT;
+
+ data->pn = pn;
+ if (!data->binder(cx, data, atom, tc))
+ return JS_FALSE;
+ data->pn = NULL;
+
+ /*
+ * Select the appropriate name-setting opcode, which may be specialized
+ * further for local variable and argument slot optimizations. At this
+ * point, we can't select the optimal final opcode, yet we must preserve
+ * the CONST bit and convey "set", not "get".
+ */
+ pn->pn_op = (data->op == JSOP_DEFCONST)
+ ? JSOP_SETCONST
+ : JSOP_SETNAME;
+ pn->pn_attrs = data->u.var.attrs;
+ return JS_TRUE;
+}
+
+/*
+ * Here, we are destructuring {... P: Q, ...} = R, where P is any id, Q is any
+ * LHS expression except a destructuring initialiser, and R is on the stack.
+ * Because R is already evaluated, the usual LHS-specialized bytecodes won't
+ * work. After pushing R[P] we need to evaluate Q's "reference base" QB and
+ * then push its property name QN. At this point the stack looks like
+ *
+ * [... R, R[P], QB, QN]
+ *
+ * We need to set QB[QN] = R[P]. This is a job for JSOP_ENUMELEM, which takes
+ * its operands with left-hand side above right-hand side:
+ *
+ * [rval, lval, xval]
+ *
+ * and pops all three values, setting lval[xval] = rval. But we cannot select
+ * JSOP_ENUMELEM yet, because the LHS may turn out to be an arg or local var,
+ * which can be optimized further. So we select JSOP_SETNAME.
+ */
+static JSBool
+BindDestructuringLHS(JSContext *cx, JSParseNode *pn, JSTreeContext *tc)
+{
+ while (pn->pn_type == TOK_RP)
+ pn = pn->pn_kid;
+
+ switch (pn->pn_type) {
+ case TOK_NAME:
+ if (pn->pn_atom == cx->runtime->atomState.argumentsAtom)
+ tc->flags |= TCF_FUN_HEAVYWEIGHT;
+ /* FALL THROUGH */
+ case TOK_DOT:
+ case TOK_LB:
+ pn->pn_op = JSOP_SETNAME;
+ break;
+
+#if JS_HAS_LVALUE_RETURN
+ case TOK_LP:
+ JS_ASSERT(pn->pn_op == JSOP_CALL || pn->pn_op == JSOP_EVAL);
+ pn->pn_op = JSOP_SETCALL;
+ break;
+#endif
+
+#if JS_HAS_XML_SUPPORT
+ case TOK_UNARYOP:
+ if (pn->pn_op == JSOP_XMLNAME) {
+ pn->pn_op = JSOP_BINDXMLNAME;
+ break;
+ }
+ /* FALL THROUGH */
+#endif
+
+ default:
+ js_ReportCompileErrorNumber(cx, pn, JSREPORT_PN | JSREPORT_ERROR,
+ JSMSG_BAD_LEFTSIDE_OF_ASS);
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+typedef struct FindPropValData {
+ uint32 numvars; /* # of destructuring vars in left side */
+ uint32 maxstep; /* max # of steps searching right side */
+ JSDHashTable table; /* hash table for O(1) right side search */
+} FindPropValData;
+
+typedef struct FindPropValEntry {
+ JSDHashEntryHdr hdr;
+ JSParseNode *pnkey;
+ JSParseNode *pnval;
+} FindPropValEntry;
+
+#define ASSERT_VALID_PROPERTY_KEY(pnkey) \
+ JS_ASSERT((pnkey)->pn_arity == PN_NULLARY && \
+ ((pnkey)->pn_type == TOK_NUMBER || \
+ (pnkey)->pn_type == TOK_STRING || \
+ (pnkey)->pn_type == TOK_NAME))
+
+JS_STATIC_DLL_CALLBACK(JSDHashNumber)
+HashFindPropValKey(JSDHashTable *table, const void *key)
+{
+ const JSParseNode *pnkey = (const JSParseNode *)key;
+
+ ASSERT_VALID_PROPERTY_KEY(pnkey);
+ return (pnkey->pn_type == TOK_NUMBER)
+ ? (JSDHashNumber) (JSDOUBLE_HI32(pnkey->pn_dval) ^
+ JSDOUBLE_LO32(pnkey->pn_dval))
+ : (JSDHashNumber) pnkey->pn_atom->number;
+}
+
+JS_STATIC_DLL_CALLBACK(JSBool)
+MatchFindPropValEntry(JSDHashTable *table,
+ const JSDHashEntryHdr *entry,
+ const void *key)
+{
+ const FindPropValEntry *fpve = (const FindPropValEntry *)entry;
+ const JSParseNode *pnkey = (const JSParseNode *)key;
+
+ ASSERT_VALID_PROPERTY_KEY(pnkey);
+ return pnkey->pn_type == fpve->pnkey->pn_type &&
+ ((pnkey->pn_type == TOK_NUMBER)
+ ? pnkey->pn_dval == fpve->pnkey->pn_dval
+ : pnkey->pn_atom == fpve->pnkey->pn_atom);
+}
+
+static const JSDHashTableOps FindPropValOps = {
+ JS_DHashAllocTable,
+ JS_DHashFreeTable,
+ JS_DHashGetKeyStub,
+ HashFindPropValKey,
+ MatchFindPropValEntry,
+ JS_DHashMoveEntryStub,
+ JS_DHashClearEntryStub,
+ JS_DHashFinalizeStub,
+ NULL
+};
+
+#define STEP_HASH_THRESHOLD 10
+#define BIG_DESTRUCTURING 5
+#define BIG_OBJECT_INIT 20
+
+static JSParseNode *
+FindPropertyValue(JSParseNode *pn, JSParseNode *pnid, FindPropValData *data)
+{
+ FindPropValEntry *entry;
+ JSParseNode *pnhit, *pnprop, *pnkey;
+ uint32 step;
+
+ /* If we have a hash table, use it as the sole source of truth. */
+ if (data->table.ops) {
+ entry = (FindPropValEntry *)
+ JS_DHashTableOperate(&data->table, pnid, JS_DHASH_LOOKUP);
+ return JS_DHASH_ENTRY_IS_BUSY(&entry->hdr) ? entry->pnval : NULL;
+ }
+
+ /* If pn is not an object initialiser node, we can't do anything here. */
+ if (pn->pn_type != TOK_RC)
+ return NULL;
+
+ /*
+ * We must search all the way through pn's list, to handle the case of an
+ * id duplicated for two or more property initialisers.
+ */
+ pnhit = NULL;
+ step = 0;
+ ASSERT_VALID_PROPERTY_KEY(pnid);
+ if (pnid->pn_type == TOK_NUMBER) {
+ for (pnprop = pn->pn_head; pnprop; pnprop = pnprop->pn_next) {
+ JS_ASSERT(pnprop->pn_type == TOK_COLON);
+ if (pnprop->pn_op == JSOP_NOP) {
+ pnkey = pnprop->pn_left;
+ ASSERT_VALID_PROPERTY_KEY(pnkey);
+ if (pnkey->pn_type == TOK_NUMBER &&
+ pnkey->pn_dval == pnid->pn_dval) {
+ pnhit = pnprop;
+ }
+ ++step;
+ }
+ }
+ } else {
+ for (pnprop = pn->pn_head; pnprop; pnprop = pnprop->pn_next) {
+ JS_ASSERT(pnprop->pn_type == TOK_COLON);
+ if (pnprop->pn_op == JSOP_NOP) {
+ pnkey = pnprop->pn_left;
+ ASSERT_VALID_PROPERTY_KEY(pnkey);
+ if (pnkey->pn_type == pnid->pn_type &&
+ pnkey->pn_atom == pnid->pn_atom) {
+ pnhit = pnprop;
+ }
+ ++step;
+ }
+ }
+ }
+ if (!pnhit)
+ return NULL;
+
+ /* Hit via full search -- see whether it's time to create the hash table. */
+ JS_ASSERT(!data->table.ops);
+ if (step > data->maxstep) {
+ data->maxstep = step;
+ if (step >= STEP_HASH_THRESHOLD &&
+ data->numvars >= BIG_DESTRUCTURING &&
+ pn->pn_count >= BIG_OBJECT_INIT &&
+ JS_DHashTableInit(&data->table, &FindPropValOps, pn,
+ sizeof(FindPropValEntry), pn->pn_count)) {
+
+ for (pn = pn->pn_head; pn; pn = pn->pn_next) {
+ ASSERT_VALID_PROPERTY_KEY(pn->pn_left);
+ entry = (FindPropValEntry *)
+ JS_DHashTableOperate(&data->table, pn->pn_left,
+ JS_DHASH_ADD);
+ entry->pnval = pn->pn_right;
+ }
+ }
+ }
+ return pnhit->pn_right;
+}
+
+/*
+ * If data is null, the caller is AssignExpr and instead of binding variables,
+ * we specialize lvalues in the propery value positions of the left-hand side.
+ * If right is null, just check for well-formed lvalues.
+ */
+static JSBool
+CheckDestructuring(JSContext *cx, BindData *data,
+ JSParseNode *left, JSParseNode *right,
+ JSTreeContext *tc)
+{
+ JSBool ok;
+ FindPropValData fpvd;
+ JSParseNode *lhs, *rhs, *pn, *pn2;
+
+ if (left->pn_type == TOK_ARRAYCOMP) {
+ js_ReportCompileErrorNumber(cx, left, JSREPORT_PN | JSREPORT_ERROR,
+ JSMSG_ARRAY_COMP_LEFTSIDE);
+ return JS_FALSE;
+ }
+
+ ok = JS_TRUE;
+ fpvd.table.ops = NULL;
+ lhs = left->pn_head;
+ if (lhs && lhs->pn_type == TOK_DEFSHARP) {
+ pn = lhs;
+ goto no_var_name;
+ }
+
+ if (left->pn_type == TOK_RB) {
+ rhs = (right && right->pn_type == left->pn_type)
+ ? right->pn_head
+ : NULL;
+
+ while (lhs) {
+ pn = lhs, pn2 = rhs;
+ if (!data) {
+ /* Skip parenthesization if not in a variable declaration. */
+ while (pn->pn_type == TOK_RP)
+ pn = pn->pn_kid;
+ if (pn2) {
+ while (pn2->pn_type == TOK_RP)
+ pn2 = pn2->pn_kid;
+ }
+ }
+
+ /* Nullary comma is an elision; binary comma is an expression.*/
+ if (pn->pn_type != TOK_COMMA || pn->pn_arity != PN_NULLARY) {
+ if (pn->pn_type == TOK_RB || pn->pn_type == TOK_RC) {
+ ok = CheckDestructuring(cx, data, pn, pn2, tc);
+ } else {
+ if (data) {
+ if (pn->pn_type != TOK_NAME)
+ goto no_var_name;
+
+ ok = BindDestructuringVar(cx, data, pn, tc);
+ } else {
+ ok = BindDestructuringLHS(cx, pn, tc);
+ }
+ }
+ if (!ok)
+ goto out;
+ }
+
+ lhs = lhs->pn_next;
+ if (rhs)
+ rhs = rhs->pn_next;
+ }
+ } else {
+ JS_ASSERT(left->pn_type == TOK_RC);
+ fpvd.numvars = left->pn_count;
+ fpvd.maxstep = 0;
+ rhs = NULL;
+
+ while (lhs) {
+ JS_ASSERT(lhs->pn_type == TOK_COLON);
+ pn = lhs->pn_right;
+ if (!data) {
+ /* Skip parenthesization if not in a variable declaration. */
+ while (pn->pn_type == TOK_RP)
+ pn = pn->pn_kid;
+ }
+
+ if (pn->pn_type == TOK_RB || pn->pn_type == TOK_RC) {
+ if (right) {
+ rhs = FindPropertyValue(right, lhs->pn_left, &fpvd);
+ if (rhs && !data) {
+ while (rhs->pn_type == TOK_RP)
+ rhs = rhs->pn_kid;
+ }
+ }
+
+ ok = CheckDestructuring(cx, data, pn, rhs, tc);
+ } else if (data) {
+ if (pn->pn_type != TOK_NAME)
+ goto no_var_name;
+
+ ok = BindDestructuringVar(cx, data, pn, tc);
+ } else {
+ ok = BindDestructuringLHS(cx, pn, tc);
+ }
+ if (!ok)
+ goto out;
+
+ lhs = lhs->pn_next;
+ }
+ }
+
+out:
+ if (fpvd.table.ops)
+ JS_DHashTableFinish(&fpvd.table);
+ return ok;
+
+no_var_name:
+ js_ReportCompileErrorNumber(cx, pn, JSREPORT_PN | JSREPORT_ERROR,
+ JSMSG_NO_VARIABLE_NAME);
+ ok = JS_FALSE;
+ goto out;
+}
+
+static JSParseNode *
+DestructuringExpr(JSContext *cx, BindData *data, JSTreeContext *tc,
+ JSTokenType tt)
+{
+ JSParseNode *pn;
+
+ pn = PrimaryExpr(cx, data->ts, tc, tt, JS_FALSE);
+ if (!pn)
+ return NULL;
+ if (!CheckDestructuring(cx, data, pn, NULL, tc))
+ return NULL;
+ return pn;
+}
+
+#endif /* JS_HAS_DESTRUCTURING */
+
+extern const char js_with_statement_str[];
+
+static JSParseNode *
+ContainsStmt(JSParseNode *pn, JSTokenType tt)
+{
+ JSParseNode *pn2, *pnt;
+
+ if (!pn)
+ return NULL;
+ if (pn->pn_type == tt)
+ return pn;
+ switch (pn->pn_arity) {
+ case PN_LIST:
+ for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
+ pnt = ContainsStmt(pn2, tt);
+ if (pnt)
+ return pnt;
+ }
+ break;
+ case PN_TERNARY:
+ pnt = ContainsStmt(pn->pn_kid1, tt);
+ if (pnt)
+ return pnt;
+ pnt = ContainsStmt(pn->pn_kid2, tt);
+ if (pnt)
+ return pnt;
+ return ContainsStmt(pn->pn_kid3, tt);
+ case PN_BINARY:
+ /*
+ * Limit recursion if pn is a binary expression, which can't contain a
+ * var statement.
+ */
+ if (pn->pn_op != JSOP_NOP)
+ return NULL;
+ pnt = ContainsStmt(pn->pn_left, tt);
+ if (pnt)
+ return pnt;
+ return ContainsStmt(pn->pn_right, tt);
+ case PN_UNARY:
+ if (pn->pn_op != JSOP_NOP)
+ return NULL;
+ return ContainsStmt(pn->pn_kid, tt);
+ case PN_NAME:
+ return ContainsStmt(pn->pn_expr, tt);
+ default:;
+ }
+ return NULL;
+}
+
+static JSParseNode *
+ReturnOrYield(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
+ JSParser operandParser)
+{
+ JSTokenType tt, tt2;
+ JSParseNode *pn, *pn2;
+
+ tt = CURRENT_TOKEN(ts).type;
+ if (!(tc->flags & TCF_IN_FUNCTION)) {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_RETURN_OR_YIELD,
+#if JS_HAS_GENERATORS
+ (tt == TOK_YIELD) ? js_yield_str :
+#endif
+ js_return_str);
+ return NULL;
+ }
+
+ pn = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!pn)
+ return NULL;
+
+#if JS_HAS_GENERATORS
+ if (tt == TOK_YIELD)
+ tc->flags |= TCF_FUN_IS_GENERATOR;
+#endif
+
+ /* This is ugly, but we don't want to require a semicolon. */
+ ts->flags |= TSF_OPERAND;
+ tt2 = js_PeekTokenSameLine(cx, ts);
+ ts->flags &= ~TSF_OPERAND;
+ if (tt2 == TOK_ERROR)
+ return NULL;
+
+ if (tt2 != TOK_EOF && tt2 != TOK_EOL && tt2 != TOK_SEMI && tt2 != TOK_RC
+#if JS_HAS_GENERATORS
+ && (tt != TOK_YIELD || (tt2 != tt && tt2 != TOK_RB && tt2 != TOK_RP))
+#endif
+ ) {
+ pn2 = operandParser(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+#if JS_HAS_GENERATORS
+ if (tt == TOK_RETURN)
+#endif
+ tc->flags |= TCF_RETURN_EXPR;
+ pn->pn_pos.end = pn2->pn_pos.end;
+ pn->pn_kid = pn2;
+ } else {
+#if JS_HAS_GENERATORS
+ if (tt == TOK_RETURN)
+#endif
+ tc->flags |= TCF_RETURN_VOID;
+ pn->pn_kid = NULL;
+ }
+
+ if ((~tc->flags & (TCF_RETURN_EXPR | TCF_FUN_IS_GENERATOR)) == 0) {
+ /* As in Python (see PEP-255), disallow return v; in generators. */
+ ReportBadReturn(cx, ts, JSREPORT_ERROR,
+ JSMSG_BAD_GENERATOR_RETURN,
+ JSMSG_BAD_ANON_GENERATOR_RETURN);
+ return NULL;
+ }
+
+ if (JS_HAS_STRICT_OPTION(cx) &&
+ (~tc->flags & (TCF_RETURN_EXPR | TCF_RETURN_VOID)) == 0 &&
+ !ReportBadReturn(cx, ts, JSREPORT_WARNING | JSREPORT_STRICT,
+ JSMSG_NO_RETURN_VALUE,
+ JSMSG_ANON_NO_RETURN_VALUE)) {
+ return NULL;
+ }
+
+ return pn;
+}
+
+static JSParseNode *
+PushLexicalScope(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
+ JSStmtInfo *stmtInfo)
+{
+ JSParseNode *pn;
+ JSObject *obj;
+ JSAtom *atom;
+
+ pn = NewParseNode(cx, ts, PN_NAME, tc);
+ if (!pn)
+ return NULL;
+
+ obj = js_NewBlockObject(cx);
+ if (!obj)
+ return NULL;
+
+ atom = js_AtomizeObject(cx, obj, 0);
+ if (!atom)
+ return NULL;
+
+ js_PushBlockScope(tc, stmtInfo, atom, -1);
+ pn->pn_type = TOK_LEXICALSCOPE;
+ pn->pn_op = JSOP_LEAVEBLOCK;
+ pn->pn_atom = atom;
+ pn->pn_expr = NULL;
+ pn->pn_slot = -1;
+ pn->pn_attrs = 0;
+ return pn;
+}
+
+#if JS_HAS_BLOCK_SCOPE
+
+static JSParseNode *
+LetBlock(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, JSBool statement)
+{
+ JSParseNode *pn, *pnblock, *pnlet;
+ JSStmtInfo stmtInfo;
+
+ JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_LET);
+
+ /* Create the let binary node. */
+ pnlet = NewParseNode(cx, ts, PN_BINARY, tc);
+ if (!pnlet)
+ return NULL;
+
+ MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_LET);
+
+ /* This is a let block or expression of the form: let (a, b, c) .... */
+ pnblock = PushLexicalScope(cx, ts, tc, &stmtInfo);
+ if (!pnblock)
+ return NULL;
+ pn = pnblock;
+ pn->pn_expr = pnlet;
+
+ pnlet->pn_left = Variables(cx, ts, tc);
+ if (!pnlet->pn_left)
+ return NULL;
+ pnlet->pn_left->pn_extra = PNX_POPVAR;
+
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_LET);
+
+ ts->flags |= TSF_OPERAND;
+ if (statement && !js_MatchToken(cx, ts, TOK_LC)) {
+ /*
+ * If this is really an expression in let statement guise, then we
+ * need to wrap the TOK_LET node in a TOK_SEMI node so that we pop
+ * the return value of the expression.
+ */
+ pn = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_type = TOK_SEMI;
+ pn->pn_num = -1;
+ pn->pn_kid = pnblock;
+
+ statement = JS_FALSE;
+ }
+ ts->flags &= ~TSF_OPERAND;
+
+ if (statement) {
+ pnlet->pn_right = Statements(cx, ts, tc);
+ if (!pnlet->pn_right)
+ return NULL;
+ MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_LET);
+ } else {
+ /*
+ * Change pnblock's opcode to the variant that propagates the last
+ * result down after popping the block, and clear statement.
+ */
+ pnblock->pn_op = JSOP_LEAVEBLOCKEXPR;
+ pnlet->pn_right = Expr(cx, ts, tc);
+ if (!pnlet->pn_right)
+ return NULL;
+ }
+
+ js_PopStatement(tc);
+ return pn;
+}
+
+#endif /* JS_HAS_BLOCK_SCOPE */
+
+static JSParseNode *
+Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSTokenType tt;
+ JSParseNode *pn, *pn1, *pn2, *pn3, *pn4;
+ JSStmtInfo stmtInfo, *stmt, *stmt2;
+ JSAtom *label;
+
+ CHECK_RECURSION();
+
+ ts->flags |= TSF_OPERAND;
+ tt = js_GetToken(cx, ts);
+ ts->flags &= ~TSF_OPERAND;
+
+#if JS_HAS_GETTER_SETTER
+ if (tt == TOK_NAME) {
+ tt = CheckGetterOrSetter(cx, ts, TOK_FUNCTION);
+ if (tt == TOK_ERROR)
+ return NULL;
+ }
+#endif
+
+ switch (tt) {
+#if JS_HAS_EXPORT_IMPORT
+ case TOK_EXPORT:
+ pn = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!pn)
+ return NULL;
+ PN_INIT_LIST(pn);
+ if (js_MatchToken(cx, ts, TOK_STAR)) {
+ pn2 = NewParseNode(cx, ts, PN_NULLARY, tc);
+ if (!pn2)
+ return NULL;
+ PN_APPEND(pn, pn2);
+ } else {
+ do {
+ MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_EXPORT_NAME);
+ pn2 = NewParseNode(cx, ts, PN_NAME, tc);
+ if (!pn2)
+ return NULL;
+ pn2->pn_op = JSOP_NAME;
+ pn2->pn_atom = CURRENT_TOKEN(ts).t_atom;
+ pn2->pn_expr = NULL;
+ pn2->pn_slot = -1;
+ pn2->pn_attrs = 0;
+ PN_APPEND(pn, pn2);
+ } while (js_MatchToken(cx, ts, TOK_COMMA));
+ }
+ pn->pn_pos.end = PN_LAST(pn)->pn_pos.end;
+ tc->flags |= TCF_FUN_HEAVYWEIGHT;
+ break;
+
+ case TOK_IMPORT:
+ pn = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!pn)
+ return NULL;
+ PN_INIT_LIST(pn);
+ do {
+ pn2 = ImportExpr(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ PN_APPEND(pn, pn2);
+ } while (js_MatchToken(cx, ts, TOK_COMMA));
+ pn->pn_pos.end = PN_LAST(pn)->pn_pos.end;
+ tc->flags |= TCF_FUN_HEAVYWEIGHT;
+ break;
+#endif /* JS_HAS_EXPORT_IMPORT */
+
+ case TOK_FUNCTION:
+#if JS_HAS_XML_SUPPORT
+ ts->flags |= TSF_KEYWORD_IS_NAME;
+ tt = js_PeekToken(cx, ts);
+ ts->flags &= ~TSF_KEYWORD_IS_NAME;
+ if (tt == TOK_DBLCOLON)
+ goto expression;
+#endif
+ return FunctionStmt(cx, ts, tc);
+
+ case TOK_IF:
+ /* An IF node has three kids: condition, then, and optional else. */
+ pn = NewParseNode(cx, ts, PN_TERNARY, tc);
+ if (!pn)
+ return NULL;
+ pn1 = Condition(cx, ts, tc);
+ if (!pn1)
+ return NULL;
+ js_PushStatement(tc, &stmtInfo, STMT_IF, -1);
+ pn2 = Statement(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ ts->flags |= TSF_OPERAND;
+ if (js_MatchToken(cx, ts, TOK_ELSE)) {
+ ts->flags &= ~TSF_OPERAND;
+ stmtInfo.type = STMT_ELSE;
+ pn3 = Statement(cx, ts, tc);
+ if (!pn3)
+ return NULL;
+ pn->pn_pos.end = pn3->pn_pos.end;
+ } else {
+ ts->flags &= ~TSF_OPERAND;
+ pn3 = NULL;
+ pn->pn_pos.end = pn2->pn_pos.end;
+ }
+ js_PopStatement(tc);
+ pn->pn_kid1 = pn1;
+ pn->pn_kid2 = pn2;
+ pn->pn_kid3 = pn3;
+ return pn;
+
+ case TOK_SWITCH:
+ {
+ JSParseNode *pn5, *saveBlock;
+ JSBool seenDefault = JS_FALSE;
+
+ pn = NewParseNode(cx, ts, PN_BINARY, tc);
+ if (!pn)
+ return NULL;
+ MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_SWITCH);
+
+ /* pn1 points to the switch's discriminant. */
+ pn1 = Expr(cx, ts, tc);
+ if (!pn1)
+ return NULL;
+
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_SWITCH);
+ MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_SWITCH);
+
+ /* pn2 is a list of case nodes. The default case has pn_left == NULL */
+ pn2 = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!pn2)
+ return NULL;
+ saveBlock = tc->blockNode;
+ tc->blockNode = pn2;
+ PN_INIT_LIST(pn2);
+
+ js_PushStatement(tc, &stmtInfo, STMT_SWITCH, -1);
+
+ while ((tt = js_GetToken(cx, ts)) != TOK_RC) {
+ switch (tt) {
+ case TOK_DEFAULT:
+ if (seenDefault) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_TOO_MANY_DEFAULTS);
+ return NULL;
+ }
+ seenDefault = JS_TRUE;
+ /* fall through */
+
+ case TOK_CASE:
+ pn3 = NewParseNode(cx, ts, PN_BINARY, tc);
+ if (!pn3)
+ return NULL;
+ if (tt == TOK_DEFAULT) {
+ pn3->pn_left = NULL;
+ } else {
+ pn3->pn_left = Expr(cx, ts, tc);
+ if (!pn3->pn_left)
+ return NULL;
+ }
+ PN_APPEND(pn2, pn3);
+ if (pn2->pn_count == JS_BIT(16)) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_TOO_MANY_CASES);
+ return NULL;
+ }
+ break;
+
+ case TOK_ERROR:
+ return NULL;
+
+ default:
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_SWITCH);
+ return NULL;
+ }
+ MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_AFTER_CASE);
+
+ pn4 = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!pn4)
+ return NULL;
+ pn4->pn_type = TOK_LC;
+ PN_INIT_LIST(pn4);
+ ts->flags |= TSF_OPERAND;
+ while ((tt = js_PeekToken(cx, ts)) != TOK_RC &&
+ tt != TOK_CASE && tt != TOK_DEFAULT) {
+ ts->flags &= ~TSF_OPERAND;
+ if (tt == TOK_ERROR)
+ return NULL;
+ pn5 = Statement(cx, ts, tc);
+ if (!pn5)
+ return NULL;
+ pn4->pn_pos.end = pn5->pn_pos.end;
+ PN_APPEND(pn4, pn5);
+ ts->flags |= TSF_OPERAND;
+ }
+ ts->flags &= ~TSF_OPERAND;
+
+ /* Fix the PN_LIST so it doesn't begin at the TOK_COLON. */
+ if (pn4->pn_head)
+ pn4->pn_pos.begin = pn4->pn_head->pn_pos.begin;
+ pn3->pn_pos.end = pn4->pn_pos.end;
+ pn3->pn_right = pn4;
+ }
+
+ /*
+ * Handle the case where there was a let declaration in any case in
+ * the switch body, but not within an inner block. If it replaced
+ * tc->blockNode with a new block node then we must refresh pn2 and
+ * then restore tc->blockNode.
+ */
+ if (tc->blockNode != pn2)
+ pn2 = tc->blockNode;
+ tc->blockNode = saveBlock;
+ js_PopStatement(tc);
+
+ pn->pn_pos.end = pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
+ pn->pn_left = pn1;
+ pn->pn_right = pn2;
+ return pn;
+ }
+
+ case TOK_WHILE:
+ pn = NewParseNode(cx, ts, PN_BINARY, tc);
+ if (!pn)
+ return NULL;
+ js_PushStatement(tc, &stmtInfo, STMT_WHILE_LOOP, -1);
+ pn2 = Condition(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ pn->pn_left = pn2;
+ pn2 = Statement(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ js_PopStatement(tc);
+ pn->pn_pos.end = pn2->pn_pos.end;
+ pn->pn_right = pn2;
+ return pn;
+
+ case TOK_DO:
+ pn = NewParseNode(cx, ts, PN_BINARY, tc);
+ if (!pn)
+ return NULL;
+ js_PushStatement(tc, &stmtInfo, STMT_DO_LOOP, -1);
+ pn2 = Statement(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ pn->pn_left = pn2;
+ MUST_MATCH_TOKEN(TOK_WHILE, JSMSG_WHILE_AFTER_DO);
+ pn2 = Condition(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ js_PopStatement(tc);
+ pn->pn_pos.end = pn2->pn_pos.end;
+ pn->pn_right = pn2;
+ if ((cx->version & JSVERSION_MASK) != JSVERSION_ECMA_3) {
+ /*
+ * All legacy and extended versions must do automatic semicolon
+ * insertion after do-while. See the testcase and discussion in
+ * http://bugzilla.mozilla.org/show_bug.cgi?id=238945.
+ */
+ (void) js_MatchToken(cx, ts, TOK_SEMI);
+ return pn;
+ }
+ break;
+
+ case TOK_FOR:
+ {
+#if JS_HAS_BLOCK_SCOPE
+ JSParseNode *pnlet;
+ JSStmtInfo blockInfo;
+
+ pnlet = NULL;
+#endif
+
+ /* A FOR node is binary, left is loop control and right is the body. */
+ pn = NewParseNode(cx, ts, PN_BINARY, tc);
+ if (!pn)
+ return NULL;
+ js_PushStatement(tc, &stmtInfo, STMT_FOR_LOOP, -1);
+
+ pn->pn_op = JSOP_FORIN;
+ if (js_MatchToken(cx, ts, TOK_NAME)) {
+ if (CURRENT_TOKEN(ts).t_atom == cx->runtime->atomState.eachAtom)
+ pn->pn_op = JSOP_FOREACH;
+ else
+ js_UngetToken(ts);
+ }
+
+ MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR);
+ ts->flags |= TSF_OPERAND;
+ tt = js_PeekToken(cx, ts);
+ ts->flags &= ~TSF_OPERAND;
+ if (tt == TOK_SEMI) {
+ if (pn->pn_op == JSOP_FOREACH)
+ goto bad_for_each;
+
+ /* No initializer -- set first kid of left sub-node to null. */
+ pn1 = NULL;
+ } else {
+ /*
+ * Set pn1 to a var list or an initializing expression.
+ *
+ * Set the TCF_IN_FOR_INIT flag during parsing of the first clause
+ * of the for statement. This flag will be used by the RelExpr
+ * production; if it is set, then the 'in' keyword will not be
+ * recognized as an operator, leaving it available to be parsed as
+ * part of a for/in loop.
+ *
+ * A side effect of this restriction is that (unparenthesized)
+ * expressions involving an 'in' operator are illegal in the init
+ * clause of an ordinary for loop.
+ */
+ tc->flags |= TCF_IN_FOR_INIT;
+ if (tt == TOK_VAR) {
+ (void) js_GetToken(cx, ts);
+ pn1 = Variables(cx, ts, tc);
+#if JS_HAS_BLOCK_SCOPE
+ } else if (tt == TOK_LET) {
+ (void) js_GetToken(cx, ts);
+ if (js_PeekToken(cx, ts) == TOK_LP) {
+ pn1 = LetBlock(cx, ts, tc, JS_FALSE);
+ tt = TOK_LEXICALSCOPE;
+ } else {
+ pnlet = PushLexicalScope(cx, ts, tc, &blockInfo);
+ if (!pnlet)
+ return NULL;
+ pn1 = Variables(cx, ts, tc);
+ }
+#endif
+ } else {
+ pn1 = Expr(cx, ts, tc);
+ if (pn1) {
+ while (pn1->pn_type == TOK_RP)
+ pn1 = pn1->pn_kid;
+ }
+ }
+ tc->flags &= ~TCF_IN_FOR_INIT;
+ if (!pn1)
+ return NULL;
+ }
+
+ /*
+ * We can be sure that it's a for/in loop if there's still an 'in'
+ * keyword here, even if JavaScript recognizes 'in' as an operator,
+ * as we've excluded 'in' from being parsed in RelExpr by setting
+ * the TCF_IN_FOR_INIT flag in our JSTreeContext.
+ */
+ if (pn1 && js_MatchToken(cx, ts, TOK_IN)) {
+ stmtInfo.type = STMT_FOR_IN_LOOP;
+
+ /* Check that the left side of the 'in' is valid. */
+ JS_ASSERT(!TOKEN_TYPE_IS_DECL(tt) || pn1->pn_type == tt);
+ if (TOKEN_TYPE_IS_DECL(tt)
+ ? (pn1->pn_count > 1 || pn1->pn_op == JSOP_DEFCONST
+#if JS_HAS_DESTRUCTURING
+ || (pn->pn_op == JSOP_FORIN &&
+ (pn1->pn_head->pn_type == TOK_RC ||
+ (pn1->pn_head->pn_type == TOK_RB &&
+ pn1->pn_head->pn_count != 2) ||
+ (pn1->pn_head->pn_type == TOK_ASSIGN &&
+ (pn1->pn_head->pn_left->pn_type != TOK_RB ||
+ pn1->pn_head->pn_left->pn_count != 2))))
+#endif
+ )
+ : (pn1->pn_type != TOK_NAME &&
+ pn1->pn_type != TOK_DOT &&
+#if JS_HAS_DESTRUCTURING
+ ((pn->pn_op == JSOP_FORIN)
+ ? (pn1->pn_type != TOK_RB || pn1->pn_count != 2)
+ : (pn1->pn_type != TOK_RB && pn1->pn_type != TOK_RC)) &&
+#endif
+#if JS_HAS_LVALUE_RETURN
+ pn1->pn_type != TOK_LP &&
+#endif
+#if JS_HAS_XML_SUPPORT
+ (pn1->pn_type != TOK_UNARYOP ||
+ pn1->pn_op != JSOP_XMLNAME) &&
+#endif
+ pn1->pn_type != TOK_LB)) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_FOR_LEFTSIDE);
+ return NULL;
+ }
+
+ if (TOKEN_TYPE_IS_DECL(tt)) {
+ /* Tell js_EmitTree(TOK_VAR) that pn1 is part of a for/in. */
+ pn1->pn_extra |= PNX_FORINVAR;
+
+ /*
+ * Generate a final POP only if the variable is a simple name
+ * (which means it is not a destructuring left-hand side) and
+ * it has an initializer.
+ */
+ pn2 = pn1->pn_head;
+ if (pn2->pn_type == TOK_NAME && pn2->pn_expr)
+ pn1->pn_extra |= PNX_POPVAR;
+ } else {
+ pn2 = pn1;
+#if JS_HAS_LVALUE_RETURN
+ if (pn2->pn_type == TOK_LP)
+ pn2->pn_op = JSOP_SETCALL;
+#endif
+#if JS_HAS_XML_SUPPORT
+ if (pn2->pn_type == TOK_UNARYOP)
+ pn2->pn_op = JSOP_BINDXMLNAME;
+#endif
+ }
+
+ switch (pn2->pn_type) {
+ case TOK_NAME:
+ /* Beware 'for (arguments in ...)' with or without a 'var'. */
+ if (pn2->pn_atom == cx->runtime->atomState.argumentsAtom)
+ tc->flags |= TCF_FUN_HEAVYWEIGHT;
+ break;
+
+#if JS_HAS_DESTRUCTURING
+ case TOK_ASSIGN:
+ pn2 = pn2->pn_left;
+ JS_ASSERT(pn2->pn_type == TOK_RB || pn2->pn_type == TOK_RC);
+ /* FALL THROUGH */
+ case TOK_RB:
+ case TOK_RC:
+ /* Check for valid lvalues in var-less destructuring for-in. */
+ if (pn1 == pn2 && !CheckDestructuring(cx, NULL, pn2, NULL, tc))
+ return NULL;
+
+ /* Destructuring for-in requires [key, value] enumeration. */
+ if (pn->pn_op != JSOP_FOREACH)
+ pn->pn_op = JSOP_FOREACHKEYVAL;
+ break;
+#endif
+
+ default:;
+ }
+
+ /* Parse the object expression as the right operand of 'in'. */
+ pn2 = NewBinary(cx, TOK_IN, JSOP_NOP, pn1, Expr(cx, ts, tc), tc);
+ if (!pn2)
+ return NULL;
+ pn->pn_left = pn2;
+ } else {
+ if (pn->pn_op == JSOP_FOREACH)
+ goto bad_for_each;
+ pn->pn_op = JSOP_NOP;
+
+ /* Parse the loop condition or null into pn2. */
+ MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_INIT);
+ ts->flags |= TSF_OPERAND;
+ tt = js_PeekToken(cx, ts);
+ ts->flags &= ~TSF_OPERAND;
+ if (tt == TOK_SEMI) {
+ pn2 = NULL;
+ } else {
+ pn2 = Expr(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ }
+
+ /* Parse the update expression or null into pn3. */
+ MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_COND);
+ ts->flags |= TSF_OPERAND;
+ tt = js_PeekToken(cx, ts);
+ ts->flags &= ~TSF_OPERAND;
+ if (tt == TOK_RP) {
+ pn3 = NULL;
+ } else {
+ pn3 = Expr(cx, ts, tc);
+ if (!pn3)
+ return NULL;
+ }
+
+ /* Build the RESERVED node to use as the left kid of pn. */
+ pn4 = NewParseNode(cx, ts, PN_TERNARY, tc);
+ if (!pn4)
+ return NULL;
+ pn4->pn_type = TOK_RESERVED;
+ pn4->pn_op = JSOP_NOP;
+ pn4->pn_kid1 = pn1;
+ pn4->pn_kid2 = pn2;
+ pn4->pn_kid3 = pn3;
+ pn->pn_left = pn4;
+ }
+
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL);
+
+ /* Parse the loop body into pn->pn_right. */
+ pn2 = Statement(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ pn->pn_right = pn2;
+
+ /* Record the absolute line number for source note emission. */
+ pn->pn_pos.end = pn2->pn_pos.end;
+
+#if JS_HAS_BLOCK_SCOPE
+ if (pnlet) {
+ js_PopStatement(tc);
+ pnlet->pn_expr = pn;
+ pn = pnlet;
+ }
+#endif
+ js_PopStatement(tc);
+ return pn;
+
+ bad_for_each:
+ js_ReportCompileErrorNumber(cx, pn,
+ JSREPORT_PN | JSREPORT_ERROR,
+ JSMSG_BAD_FOR_EACH_LOOP);
+ return NULL;
+ }
+
+ case TOK_TRY: {
+ JSParseNode *catchList, *lastCatch;
+
+ /*
+ * try nodes are ternary.
+ * kid1 is the try Statement
+ * kid2 is the catch node list or null
+ * kid3 is the finally Statement
+ *
+ * catch nodes are ternary.
+ * kid1 is the lvalue (TOK_NAME, TOK_LB, or TOK_LC)
+ * kid2 is the catch guard or null if no guard
+ * kid3 is the catch block
+ *
+ * catch lvalue nodes are either:
+ * TOK_NAME for a single identifier
+ * TOK_RB or TOK_RC for a destructuring left-hand side
+ *
+ * finally nodes are TOK_LC Statement lists.
+ */
+ pn = NewParseNode(cx, ts, PN_TERNARY, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_op = JSOP_NOP;
+
+ MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_TRY);
+ js_PushStatement(tc, &stmtInfo, STMT_TRY, -1);
+ pn->pn_kid1 = Statements(cx, ts, tc);
+ if (!pn->pn_kid1)
+ return NULL;
+ MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_TRY);
+ js_PopStatement(tc);
+
+ catchList = NULL;
+ tt = js_GetToken(cx, ts);
+ if (tt == TOK_CATCH) {
+ catchList = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!catchList)
+ return NULL;
+ catchList->pn_type = TOK_RESERVED;
+ PN_INIT_LIST(catchList);
+ lastCatch = NULL;
+
+ do {
+ JSParseNode *pnblock;
+ BindData data;
+
+ /* Check for another catch after unconditional catch. */
+ if (lastCatch && !lastCatch->pn_kid2) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_CATCH_AFTER_GENERAL);
+ return NULL;
+ }
+
+ /*
+ * Create a lexical scope node around the whole catch clause,
+ * including the head.
+ */
+ pnblock = PushLexicalScope(cx, ts, tc, &stmtInfo);
+ if (!pnblock)
+ return NULL;
+ stmtInfo.type = STMT_CATCH;
+
+ /*
+ * Legal catch forms are:
+ * catch (lhs)
+ * catch (lhs if <boolean_expression>)
+ * where lhs is a name or a destructuring left-hand side.
+ * (the latter is legal only #ifdef JS_HAS_CATCH_GUARD)
+ */
+ pn2 = NewParseNode(cx, ts, PN_TERNARY, tc);
+ if (!pn2)
+ return NULL;
+ pnblock->pn_expr = pn2;
+ MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH);
+
+ /*
+ * Contrary to ECMA Ed. 3, the catch variable is lexically
+ * scoped, not a property of a new Object instance. This is
+ * an intentional change that anticipates ECMA Ed. 4.
+ */
+ data.pn = NULL;
+ data.ts = ts;
+ data.obj = tc->blockChain;
+ data.op = JSOP_NOP;
+ data.binder = BindLet;
+ data.u.let.index = 0;
+ data.u.let.overflow = JSMSG_TOO_MANY_CATCH_VARS;
+
+ tt = js_GetToken(cx, ts);
+ switch (tt) {
+#if JS_HAS_DESTRUCTURING
+ case TOK_LB:
+ case TOK_LC:
+ pn3 = DestructuringExpr(cx, &data, tc, tt);
+ if (!pn3)
+ return NULL;
+ break;
+#endif
+
+ case TOK_NAME:
+ label = CURRENT_TOKEN(ts).t_atom;
+ if (!data.binder(cx, &data, label, tc))
+ return NULL;
+
+ pn3 = NewParseNode(cx, ts, PN_NAME, tc);
+ if (!pn3)
+ return NULL;
+ pn3->pn_atom = label;
+ pn3->pn_expr = NULL;
+ pn3->pn_slot = 0;
+ pn3->pn_attrs = 0;
+ break;
+
+ default:
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_CATCH_IDENTIFIER);
+ return NULL;
+ }
+
+ pn2->pn_kid1 = pn3;
+ pn2->pn_kid2 = NULL;
+#if JS_HAS_CATCH_GUARD
+ /*
+ * We use 'catch (x if x === 5)' (not 'catch (x : x === 5)')
+ * to avoid conflicting with the JS2/ECMAv4 type annotation
+ * catchguard syntax.
+ */
+ if (js_MatchToken(cx, ts, TOK_IF)) {
+ pn2->pn_kid2 = Expr(cx, ts, tc);
+ if (!pn2->pn_kid2)
+ return NULL;
+ }
+#endif
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_CATCH);
+
+ MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CATCH);
+ pn2->pn_kid3 = Statements(cx, ts, tc);
+ if (!pn2->pn_kid3)
+ return NULL;
+ MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_CATCH);
+ js_PopStatement(tc);
+
+ PN_APPEND(catchList, pnblock);
+ lastCatch = pn2;
+ ts->flags |= TSF_OPERAND;
+ tt = js_GetToken(cx, ts);
+ ts->flags &= ~TSF_OPERAND;
+ } while (tt == TOK_CATCH);
+ }
+ pn->pn_kid2 = catchList;
+
+ if (tt == TOK_FINALLY) {
+ tc->tryCount++;
+ MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_FINALLY);
+ js_PushStatement(tc, &stmtInfo, STMT_FINALLY, -1);
+ pn->pn_kid3 = Statements(cx, ts, tc);
+ if (!pn->pn_kid3)
+ return NULL;
+ MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_FINALLY);
+ js_PopStatement(tc);
+ } else {
+ js_UngetToken(ts);
+ pn->pn_kid3 = NULL;
+ }
+ if (!catchList && !pn->pn_kid3) {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_CATCH_OR_FINALLY);
+ return NULL;
+ }
+ tc->tryCount++;
+ return pn;
+ }
+
+ case TOK_THROW:
+ pn = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!pn)
+ return NULL;
+
+ /* ECMA-262 Edition 3 says 'throw [no LineTerminator here] Expr'. */
+ ts->flags |= TSF_OPERAND;
+ tt = js_PeekTokenSameLine(cx, ts);
+ ts->flags &= ~TSF_OPERAND;
+ if (tt == TOK_ERROR)
+ return NULL;
+ if (tt == TOK_EOF || tt == TOK_EOL || tt == TOK_SEMI || tt == TOK_RC) {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_SYNTAX_ERROR);
+ return NULL;
+ }
+
+ pn2 = Expr(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ pn->pn_pos.end = pn2->pn_pos.end;
+ pn->pn_op = JSOP_THROW;
+ pn->pn_kid = pn2;
+ break;
+
+ /* TOK_CATCH and TOK_FINALLY are both handled in the TOK_TRY case */
+ case TOK_CATCH:
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_CATCH_WITHOUT_TRY);
+ return NULL;
+
+ case TOK_FINALLY:
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_FINALLY_WITHOUT_TRY);
+ return NULL;
+
+ case TOK_BREAK:
+ pn = NewParseNode(cx, ts, PN_NULLARY, tc);
+ if (!pn)
+ return NULL;
+ if (!MatchLabel(cx, ts, pn))
+ return NULL;
+ stmt = tc->topStmt;
+ label = pn->pn_atom;
+ if (label) {
+ for (; ; stmt = stmt->down) {
+ if (!stmt) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_LABEL_NOT_FOUND);
+ return NULL;
+ }
+ if (stmt->type == STMT_LABEL && stmt->atom == label)
+ break;
+ }
+ } else {
+ for (; ; stmt = stmt->down) {
+ if (!stmt) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_TOUGH_BREAK);
+ return NULL;
+ }
+ if (STMT_IS_LOOP(stmt) || stmt->type == STMT_SWITCH)
+ break;
+ }
+ }
+ if (label)
+ pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
+ break;
+
+ case TOK_CONTINUE:
+ pn = NewParseNode(cx, ts, PN_NULLARY, tc);
+ if (!pn)
+ return NULL;
+ if (!MatchLabel(cx, ts, pn))
+ return NULL;
+ stmt = tc->topStmt;
+ label = pn->pn_atom;
+ if (label) {
+ for (stmt2 = NULL; ; stmt = stmt->down) {
+ if (!stmt) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_LABEL_NOT_FOUND);
+ return NULL;
+ }
+ if (stmt->type == STMT_LABEL) {
+ if (stmt->atom == label) {
+ if (!stmt2 || !STMT_IS_LOOP(stmt2)) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS |
+ JSREPORT_ERROR,
+ JSMSG_BAD_CONTINUE);
+ return NULL;
+ }
+ break;
+ }
+ } else {
+ stmt2 = stmt;
+ }
+ }
+ } else {
+ for (; ; stmt = stmt->down) {
+ if (!stmt) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_CONTINUE);
+ return NULL;
+ }
+ if (STMT_IS_LOOP(stmt))
+ break;
+ }
+ }
+ if (label)
+ pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
+ break;
+
+ case TOK_WITH:
+ pn = NewParseNode(cx, ts, PN_BINARY, tc);
+ if (!pn)
+ return NULL;
+ MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH);
+ pn2 = Expr(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_WITH);
+ pn->pn_left = pn2;
+
+ js_PushStatement(tc, &stmtInfo, STMT_WITH, -1);
+ pn2 = Statement(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ js_PopStatement(tc);
+
+ pn->pn_pos.end = pn2->pn_pos.end;
+ pn->pn_right = pn2;
+ tc->flags |= TCF_FUN_HEAVYWEIGHT;
+ return pn;
+
+ case TOK_VAR:
+ pn = Variables(cx, ts, tc);
+ if (!pn)
+ return NULL;
+
+ /* Tell js_EmitTree to generate a final POP. */
+ pn->pn_extra |= PNX_POPVAR;
+ break;
+
+#if JS_HAS_BLOCK_SCOPE
+ case TOK_LET:
+ {
+ JSStmtInfo **sip;
+ JSObject *obj;
+ JSAtom *atom;
+
+ /* Check for a let statement or let expression. */
+ if (js_PeekToken(cx, ts) == TOK_LP) {
+ pn = LetBlock(cx, ts, tc, JS_TRUE);
+ if (!pn || pn->pn_op == JSOP_LEAVEBLOCK)
+ return pn;
+
+ /* Let expressions require automatic semicolon insertion. */
+ JS_ASSERT(pn->pn_type == TOK_SEMI ||
+ pn->pn_op == JSOP_LEAVEBLOCKEXPR);
+ break;
+ }
+
+ /*
+ * This is a let declaration. We must convert the nearest JSStmtInfo
+ * that is a block or a switch body to be our scope statement. Further
+ * let declarations in this block will find this scope statement and
+ * use the same block object. If we are the first let declaration in
+ * this block (i.e., when the nearest maybe-scope JSStmtInfo isn't a
+ * scope statement) then we also need to set tc->blockNode to be our
+ * TOK_LEXICALSCOPE.
+ */
+ sip = &tc->topScopeStmt;
+ for (stmt = tc->topStmt; stmt; stmt = stmt->down) {
+ if (STMT_MAYBE_SCOPE(stmt))
+ break;
+ if (stmt == *sip)
+ sip = &stmt->downScope;
+ }
+
+ if (stmt && (stmt->flags & SIF_SCOPE)) {
+ JS_ASSERT(tc->blockChain == ATOM_TO_OBJECT(stmt->atom));
+ obj = tc->blockChain;
+ } else {
+ if (!stmt) {
+ /*
+ * FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=346749
+ *
+ * This is a hard case that requires more work. In particular,
+ * in many cases, we're trying to emit code as we go. However,
+ * this means that we haven't necessarily finished processing
+ * all let declarations in the implicit top-level block when
+ * we emit a reference to one of them. For now, punt on this
+ * and pretend this is a var declaration.
+ */
+ CURRENT_TOKEN(ts).type = TOK_VAR;
+ CURRENT_TOKEN(ts).t_op = JSOP_DEFVAR;
+
+ pn = Variables(cx, ts, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_extra |= PNX_POPVAR;
+ break;
+ }
+
+ /* Convert the block statement into a scope statement. */
+ obj = js_NewBlockObject(cx);
+ if (!obj)
+ return NULL;
+ atom = js_AtomizeObject(cx, obj, 0);
+ if (!atom)
+ return NULL;
+
+ /*
+ * Insert stmt on the tc->topScopeStmt/stmtInfo.downScope linked
+ * list stack, if it isn't already there. If it is there, but it
+ * lacks the SIF_SCOPE flag, it must be a try, catch, or finally
+ * block.
+ */
+ JS_ASSERT(!(stmt->flags & SIF_SCOPE));
+ stmt->flags |= SIF_SCOPE;
+ if (stmt != *sip) {
+ JS_ASSERT(!stmt->downScope);
+ JS_ASSERT(stmt->type == STMT_BLOCK ||
+ stmt->type == STMT_SWITCH ||
+ stmt->type == STMT_TRY ||
+ stmt->type == STMT_FINALLY);
+ stmt->downScope = *sip;
+ *sip = stmt;
+ } else {
+ JS_ASSERT(stmt->type == STMT_CATCH);
+ JS_ASSERT(stmt->downScope);
+ }
+
+ obj->slots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(tc->blockChain);
+ tc->blockChain = obj;
+ stmt->atom = atom;
+
+#ifdef DEBUG
+ pn1 = tc->blockNode;
+ JS_ASSERT(!pn1 || pn1->pn_type != TOK_LEXICALSCOPE);
+#endif
+
+ /* Create a new lexical scope node for these statements. */
+ pn1 = NewParseNode(cx, ts, PN_NAME, tc);
+ if (!pn1)
+ return NULL;
+
+ pn1->pn_type = TOK_LEXICALSCOPE;
+ pn1->pn_op = JSOP_LEAVEBLOCK;
+ pn1->pn_pos = tc->blockNode->pn_pos;
+ pn1->pn_atom = atom;
+ pn1->pn_expr = tc->blockNode;
+ pn1->pn_slot = -1;
+ pn1->pn_attrs = 0;
+ tc->blockNode = pn1;
+ }
+
+ pn = Variables(cx, ts, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_extra = PNX_POPVAR;
+ break;
+ }
+#endif /* JS_HAS_BLOCK_SCOPE */
+
+ case TOK_RETURN:
+ pn = ReturnOrYield(cx, ts, tc, Expr);
+ if (!pn)
+ return NULL;
+ break;
+
+ case TOK_LC:
+ {
+ uintN oldflags;
+
+ oldflags = tc->flags;
+ tc->flags = oldflags & ~TCF_HAS_FUNCTION_STMT;
+ js_PushStatement(tc, &stmtInfo, STMT_BLOCK, -1);
+ pn = Statements(cx, ts, tc);
+ if (!pn)
+ return NULL;
+
+ MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_IN_COMPOUND);
+ js_PopStatement(tc);
+
+ /*
+ * If we contain a function statement and our container is top-level
+ * or another block, flag pn to preserve braces when decompiling.
+ */
+ if ((tc->flags & TCF_HAS_FUNCTION_STMT) &&
+ (!tc->topStmt || tc->topStmt->type == STMT_BLOCK)) {
+ pn->pn_extra |= PNX_NEEDBRACES;
+ }
+ tc->flags = oldflags | (tc->flags & (TCF_FUN_FLAGS | TCF_RETURN_FLAGS));
+ return pn;
+ }
+
+ case TOK_EOL:
+ case TOK_SEMI:
+ pn = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_type = TOK_SEMI;
+ pn->pn_kid = NULL;
+ return pn;
+
+#if JS_HAS_DEBUGGER_KEYWORD
+ case TOK_DEBUGGER:
+ pn = NewParseNode(cx, ts, PN_NULLARY, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_type = TOK_DEBUGGER;
+ tc->flags |= TCF_FUN_HEAVYWEIGHT;
+ break;
+#endif /* JS_HAS_DEBUGGER_KEYWORD */
+
+#if JS_HAS_XML_SUPPORT
+ case TOK_DEFAULT:
+ pn = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!pn)
+ return NULL;
+ if (!js_MatchToken(cx, ts, TOK_NAME) ||
+ CURRENT_TOKEN(ts).t_atom != cx->runtime->atomState.xmlAtom ||
+ !js_MatchToken(cx, ts, TOK_NAME) ||
+ CURRENT_TOKEN(ts).t_atom != cx->runtime->atomState.namespaceAtom ||
+ !js_MatchToken(cx, ts, TOK_ASSIGN) ||
+ CURRENT_TOKEN(ts).t_op != JSOP_NOP) {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_DEFAULT_XML_NAMESPACE);
+ return NULL;
+ }
+ pn2 = Expr(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ pn->pn_op = JSOP_DEFXMLNS;
+ pn->pn_pos.end = pn2->pn_pos.end;
+ pn->pn_kid = pn2;
+ tc->flags |= TCF_HAS_DEFXMLNS;
+ break;
+#endif
+
+ case TOK_ERROR:
+ return NULL;
+
+ default:
+#if JS_HAS_XML_SUPPORT
+ expression:
+#endif
+ js_UngetToken(ts);
+ pn2 = Expr(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+
+ if (js_PeekToken(cx, ts) == TOK_COLON) {
+ if (pn2->pn_type != TOK_NAME) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_LABEL);
+ return NULL;
+ }
+ label = pn2->pn_atom;
+ for (stmt = tc->topStmt; stmt; stmt = stmt->down) {
+ if (stmt->type == STMT_LABEL && stmt->atom == label) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_DUPLICATE_LABEL);
+ return NULL;
+ }
+ }
+ (void) js_GetToken(cx, ts);
+
+ /* Push a label struct and parse the statement. */
+ js_PushStatement(tc, &stmtInfo, STMT_LABEL, -1);
+ stmtInfo.atom = label;
+ pn = Statement(cx, ts, tc);
+ if (!pn)
+ return NULL;
+
+ /* Normalize empty statement to empty block for the decompiler. */
+ if (pn->pn_type == TOK_SEMI && !pn->pn_kid) {
+ pn->pn_type = TOK_LC;
+ pn->pn_arity = PN_LIST;
+ PN_INIT_LIST(pn);
+ }
+
+ /* Pop the label, set pn_expr, and return early. */
+ js_PopStatement(tc);
+ pn2->pn_type = TOK_COLON;
+ pn2->pn_pos.end = pn->pn_pos.end;
+ pn2->pn_expr = pn;
+ return pn2;
+ }
+
+ pn = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_type = TOK_SEMI;
+ pn->pn_pos = pn2->pn_pos;
+ pn->pn_kid = pn2;
+ break;
+ }
+
+ /* Check termination of this primitive statement. */
+ if (ON_CURRENT_LINE(ts, pn->pn_pos)) {
+ ts->flags |= TSF_OPERAND;
+ tt = js_PeekTokenSameLine(cx, ts);
+ ts->flags &= ~TSF_OPERAND;
+ if (tt == TOK_ERROR)
+ return NULL;
+ if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_SEMI_BEFORE_STMNT);
+ return NULL;
+ }
+ }
+
+ (void) js_MatchToken(cx, ts, TOK_SEMI);
+ return pn;
+}
+
+static JSParseNode *
+Variables(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSTokenType tt;
+ JSBool let;
+ JSStmtInfo *scopeStmt;
+ BindData data;
+ JSParseNode *pn, *pn2;
+ JSStackFrame *fp;
+ JSAtom *atom;
+
+ /*
+ * The three options here are:
+ * - TOK_LET: We are parsing a let declaration.
+ * - TOK_LP: We are parsing the head of a let block.
+ * - Otherwise, we're parsing var declarations.
+ */
+ tt = CURRENT_TOKEN(ts).type;
+ let = (tt == TOK_LET || tt == TOK_LP);
+ JS_ASSERT(let || tt == TOK_VAR);
+
+ /* Make sure that Statement set the tree context up correctly. */
+ scopeStmt = tc->topScopeStmt;
+ if (let) {
+ while (scopeStmt && !(scopeStmt->flags & SIF_SCOPE)) {
+ JS_ASSERT(!STMT_MAYBE_SCOPE(scopeStmt));
+ scopeStmt = scopeStmt->downScope;
+ }
+ JS_ASSERT(scopeStmt);
+ }
+
+ data.pn = NULL;
+ data.ts = ts;
+ data.op = let ? JSOP_NOP : CURRENT_TOKEN(ts).t_op;
+ data.binder = let ? BindLet : BindVarOrConst;
+ pn = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_op = data.op;
+ PN_INIT_LIST(pn);
+
+ /*
+ * The tricky part of this code is to create special parsenode opcodes for
+ * getting and setting variables (which will be stored as special slots in
+ * the frame). The most complicated case is an eval() inside a function.
+ * If the evaluated string references variables in the enclosing function,
+ * then we need to generate the special variable opcodes. We determine
+ * this by looking up the variable's id in the current variable object.
+ * Fortunately, we can avoid doing this for let declared variables.
+ */
+ fp = cx->fp;
+ if (let) {
+ JS_ASSERT(tc->blockChain == ATOM_TO_OBJECT(scopeStmt->atom));
+ data.obj = tc->blockChain;
+ data.u.let.index = OBJ_BLOCK_COUNT(cx, data.obj);
+ data.u.let.overflow = JSMSG_TOO_MANY_FUN_VARS;
+ } else {
+ data.obj = fp->varobj;
+ data.u.var.fun = fp->fun;
+ data.u.var.clasp = OBJ_GET_CLASS(cx, data.obj);
+ if (data.u.var.fun && data.u.var.clasp == &js_FunctionClass) {
+ /* We are compiling code inside a function */
+ data.u.var.getter = js_GetLocalVariable;
+ data.u.var.setter = js_SetLocalVariable;
+ } else if (data.u.var.fun && data.u.var.clasp == &js_CallClass) {
+ /* We are compiling code from an eval inside a function */
+ data.u.var.getter = js_GetCallVariable;
+ data.u.var.setter = js_SetCallVariable;
+ } else {
+ data.u.var.getter = data.u.var.clasp->getProperty;
+ data.u.var.setter = data.u.var.clasp->setProperty;
+ }
+
+ data.u.var.attrs = (data.op == JSOP_DEFCONST)
+ ? JSPROP_PERMANENT | JSPROP_READONLY
+ : JSPROP_PERMANENT;
+ }
+
+ do {
+ tt = js_GetToken(cx, ts);
+#if JS_HAS_DESTRUCTURING
+ if (tt == TOK_LB || tt == TOK_LC) {
+ pn2 = PrimaryExpr(cx, ts, tc, tt, JS_FALSE);
+ if (!pn2)
+ return NULL;
+
+ if ((tc->flags & TCF_IN_FOR_INIT) &&
+ js_PeekToken(cx, ts) == TOK_IN) {
+ if (!CheckDestructuring(cx, &data, pn2, NULL, tc))
+ return NULL;
+ PN_APPEND(pn, pn2);
+ continue;
+ }
+
+ MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_BAD_DESTRUCT_DECL);
+ if (CURRENT_TOKEN(ts).t_op != JSOP_NOP)
+ goto bad_var_init;
+
+ pn2 = NewBinary(cx, TOK_ASSIGN, JSOP_NOP,
+ pn2, AssignExpr(cx, ts, tc),
+ tc);
+ if (!pn2 ||
+ !CheckDestructuring(cx, &data,
+ pn2->pn_left, pn2->pn_right,
+ tc)) {
+ return NULL;
+ }
+ PN_APPEND(pn, pn2);
+ continue;
+ }
+#endif
+
+ if (tt != TOK_NAME) {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_NO_VARIABLE_NAME);
+ return NULL;
+ }
+ atom = CURRENT_TOKEN(ts).t_atom;
+ if (!data.binder(cx, &data, atom, tc))
+ return NULL;
+
+ pn2 = NewParseNode(cx, ts, PN_NAME, tc);
+ if (!pn2)
+ return NULL;
+ pn2->pn_op = JSOP_NAME;
+ pn2->pn_atom = atom;
+ pn2->pn_expr = NULL;
+ pn2->pn_slot = -1;
+ pn2->pn_attrs = let ? 0 : data.u.var.attrs;
+ PN_APPEND(pn, pn2);
+
+ if (js_MatchToken(cx, ts, TOK_ASSIGN)) {
+ if (CURRENT_TOKEN(ts).t_op != JSOP_NOP)
+ goto bad_var_init;
+
+ pn2->pn_expr = AssignExpr(cx, ts, tc);
+ if (!pn2->pn_expr)
+ return NULL;
+ pn2->pn_op = (!let && data.op == JSOP_DEFCONST)
+ ? JSOP_SETCONST
+ : JSOP_SETNAME;
+ if (!let && atom == cx->runtime->atomState.argumentsAtom)
+ tc->flags |= TCF_FUN_HEAVYWEIGHT;
+ }
+ } while (js_MatchToken(cx, ts, TOK_COMMA));
+
+ pn->pn_pos.end = PN_LAST(pn)->pn_pos.end;
+ return pn;
+
+bad_var_init:
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_VAR_INIT);
+ return NULL;
+}
+
+static JSParseNode *
+Expr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn, *pn2;
+
+ pn = AssignExpr(cx, ts, tc);
+ if (pn && js_MatchToken(cx, ts, TOK_COMMA)) {
+ pn2 = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!pn2)
+ return NULL;
+ pn2->pn_pos.begin = pn->pn_pos.begin;
+ PN_INIT_LIST_1(pn2, pn);
+ pn = pn2;
+ do {
+#if JS_HAS_GENERATORS
+ pn2 = PN_LAST(pn);
+ if (pn2->pn_type == TOK_YIELD) {
+ js_ReportCompileErrorNumber(cx, pn2,
+ JSREPORT_PN | JSREPORT_ERROR,
+ JSMSG_BAD_YIELD_SYNTAX);
+ return NULL;
+ }
+#endif
+ pn2 = AssignExpr(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ PN_APPEND(pn, pn2);
+ } while (js_MatchToken(cx, ts, TOK_COMMA));
+ pn->pn_pos.end = PN_LAST(pn)->pn_pos.end;
+ }
+ return pn;
+}
+
+static JSParseNode *
+AssignExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn, *pn2;
+ JSTokenType tt;
+ JSOp op;
+
+ CHECK_RECURSION();
+
+#if JS_HAS_GENERATORS
+ ts->flags |= TSF_OPERAND;
+ if (js_MatchToken(cx, ts, TOK_YIELD)) {
+ ts->flags &= ~TSF_OPERAND;
+ return ReturnOrYield(cx, ts, tc, AssignExpr);
+ }
+ ts->flags &= ~TSF_OPERAND;
+#endif
+
+ pn = CondExpr(cx, ts, tc);
+ if (!pn)
+ return NULL;
+
+ tt = js_GetToken(cx, ts);
+#if JS_HAS_GETTER_SETTER
+ if (tt == TOK_NAME) {
+ tt = CheckGetterOrSetter(cx, ts, TOK_ASSIGN);
+ if (tt == TOK_ERROR)
+ return NULL;
+ }
+#endif
+ if (tt != TOK_ASSIGN) {
+ js_UngetToken(ts);
+ return pn;
+ }
+
+ op = CURRENT_TOKEN(ts).t_op;
+ for (pn2 = pn; pn2->pn_type == TOK_RP; pn2 = pn2->pn_kid)
+ continue;
+ switch (pn2->pn_type) {
+ case TOK_NAME:
+ pn2->pn_op = JSOP_SETNAME;
+ if (pn2->pn_atom == cx->runtime->atomState.argumentsAtom)
+ tc->flags |= TCF_FUN_HEAVYWEIGHT;
+ break;
+ case TOK_DOT:
+ pn2->pn_op = (pn2->pn_op == JSOP_GETMETHOD)
+ ? JSOP_SETMETHOD
+ : JSOP_SETPROP;
+ break;
+ case TOK_LB:
+ pn2->pn_op = JSOP_SETELEM;
+ break;
+#if JS_HAS_DESTRUCTURING
+ case TOK_RB:
+ case TOK_RC:
+ if (op != JSOP_NOP) {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_DESTRUCT_ASS);
+ return NULL;
+ }
+ pn = AssignExpr(cx, ts, tc);
+ if (!pn || !CheckDestructuring(cx, NULL, pn2, pn, tc))
+ return NULL;
+ return NewBinary(cx, TOK_ASSIGN, op, pn2, pn, tc);
+#endif
+#if JS_HAS_LVALUE_RETURN
+ case TOK_LP:
+ JS_ASSERT(pn2->pn_op == JSOP_CALL || pn2->pn_op == JSOP_EVAL);
+ pn2->pn_op = JSOP_SETCALL;
+ break;
+#endif
+#if JS_HAS_XML_SUPPORT
+ case TOK_UNARYOP:
+ if (pn2->pn_op == JSOP_XMLNAME) {
+ pn2->pn_op = JSOP_SETXMLNAME;
+ break;
+ }
+ /* FALL THROUGH */
+#endif
+ default:
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_LEFTSIDE_OF_ASS);
+ return NULL;
+ }
+
+ return NewBinary(cx, TOK_ASSIGN, op, pn2, AssignExpr(cx, ts, tc), tc);
+}
+
+static JSParseNode *
+CondExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn, *pn1, *pn2, *pn3;
+ uintN oldflags;
+
+ pn = OrExpr(cx, ts, tc);
+ if (pn && js_MatchToken(cx, ts, TOK_HOOK)) {
+ pn1 = pn;
+ pn = NewParseNode(cx, ts, PN_TERNARY, tc);
+ if (!pn)
+ return NULL;
+ /*
+ * Always accept the 'in' operator in the middle clause of a ternary,
+ * where it's unambiguous, even if we might be parsing the init of a
+ * for statement.
+ */
+ oldflags = tc->flags;
+ tc->flags &= ~TCF_IN_FOR_INIT;
+ pn2 = AssignExpr(cx, ts, tc);
+ tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS);
+
+ if (!pn2)
+ return NULL;
+ MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_IN_COND);
+ pn3 = AssignExpr(cx, ts, tc);
+ if (!pn3)
+ return NULL;
+ pn->pn_pos.begin = pn1->pn_pos.begin;
+ pn->pn_pos.end = pn3->pn_pos.end;
+ pn->pn_kid1 = pn1;
+ pn->pn_kid2 = pn2;
+ pn->pn_kid3 = pn3;
+ }
+ return pn;
+}
+
+static JSParseNode *
+OrExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn;
+
+ pn = AndExpr(cx, ts, tc);
+ if (pn && js_MatchToken(cx, ts, TOK_OR))
+ pn = NewBinary(cx, TOK_OR, JSOP_OR, pn, OrExpr(cx, ts, tc), tc);
+ return pn;
+}
+
+static JSParseNode *
+AndExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn;
+
+ pn = BitOrExpr(cx, ts, tc);
+ if (pn && js_MatchToken(cx, ts, TOK_AND))
+ pn = NewBinary(cx, TOK_AND, JSOP_AND, pn, AndExpr(cx, ts, tc), tc);
+ return pn;
+}
+
+static JSParseNode *
+BitOrExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn;
+
+ pn = BitXorExpr(cx, ts, tc);
+ while (pn && js_MatchToken(cx, ts, TOK_BITOR)) {
+ pn = NewBinary(cx, TOK_BITOR, JSOP_BITOR, pn, BitXorExpr(cx, ts, tc),
+ tc);
+ }
+ return pn;
+}
+
+static JSParseNode *
+BitXorExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn;
+
+ pn = BitAndExpr(cx, ts, tc);
+ while (pn && js_MatchToken(cx, ts, TOK_BITXOR)) {
+ pn = NewBinary(cx, TOK_BITXOR, JSOP_BITXOR, pn, BitAndExpr(cx, ts, tc),
+ tc);
+ }
+ return pn;
+}
+
+static JSParseNode *
+BitAndExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn;
+
+ pn = EqExpr(cx, ts, tc);
+ while (pn && js_MatchToken(cx, ts, TOK_BITAND))
+ pn = NewBinary(cx, TOK_BITAND, JSOP_BITAND, pn, EqExpr(cx, ts, tc), tc);
+ return pn;
+}
+
+static JSParseNode *
+EqExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn;
+ JSOp op;
+
+ pn = RelExpr(cx, ts, tc);
+ while (pn && js_MatchToken(cx, ts, TOK_EQOP)) {
+ op = CURRENT_TOKEN(ts).t_op;
+ pn = NewBinary(cx, TOK_EQOP, op, pn, RelExpr(cx, ts, tc), tc);
+ }
+ return pn;
+}
+
+static JSParseNode *
+RelExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn;
+ JSTokenType tt;
+ JSOp op;
+ uintN inForInitFlag = tc->flags & TCF_IN_FOR_INIT;
+
+ /*
+ * Uses of the in operator in ShiftExprs are always unambiguous,
+ * so unset the flag that prohibits recognizing it.
+ */
+ tc->flags &= ~TCF_IN_FOR_INIT;
+
+ pn = ShiftExpr(cx, ts, tc);
+ while (pn &&
+ (js_MatchToken(cx, ts, TOK_RELOP) ||
+ /*
+ * Recognize the 'in' token as an operator only if we're not
+ * currently in the init expr of a for loop.
+ */
+ (inForInitFlag == 0 && js_MatchToken(cx, ts, TOK_IN)) ||
+ js_MatchToken(cx, ts, TOK_INSTANCEOF))) {
+ tt = CURRENT_TOKEN(ts).type;
+ op = CURRENT_TOKEN(ts).t_op;
+ pn = NewBinary(cx, tt, op, pn, ShiftExpr(cx, ts, tc), tc);
+ }
+ /* Restore previous state of inForInit flag. */
+ tc->flags |= inForInitFlag;
+
+ return pn;
+}
+
+static JSParseNode *
+ShiftExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn;
+ JSOp op;
+
+ pn = AddExpr(cx, ts, tc);
+ while (pn && js_MatchToken(cx, ts, TOK_SHOP)) {
+ op = CURRENT_TOKEN(ts).t_op;
+ pn = NewBinary(cx, TOK_SHOP, op, pn, AddExpr(cx, ts, tc), tc);
+ }
+ return pn;
+}
+
+static JSParseNode *
+AddExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn;
+ JSTokenType tt;
+ JSOp op;
+
+ pn = MulExpr(cx, ts, tc);
+ while (pn &&
+ (js_MatchToken(cx, ts, TOK_PLUS) ||
+ js_MatchToken(cx, ts, TOK_MINUS))) {
+ tt = CURRENT_TOKEN(ts).type;
+ op = (tt == TOK_PLUS) ? JSOP_ADD : JSOP_SUB;
+ pn = NewBinary(cx, tt, op, pn, MulExpr(cx, ts, tc), tc);
+ }
+ return pn;
+}
+
+static JSParseNode *
+MulExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn;
+ JSTokenType tt;
+ JSOp op;
+
+ pn = UnaryExpr(cx, ts, tc);
+ while (pn &&
+ (js_MatchToken(cx, ts, TOK_STAR) ||
+ js_MatchToken(cx, ts, TOK_DIVOP))) {
+ tt = CURRENT_TOKEN(ts).type;
+ op = CURRENT_TOKEN(ts).t_op;
+ pn = NewBinary(cx, tt, op, pn, UnaryExpr(cx, ts, tc), tc);
+ }
+ return pn;
+}
+
+static JSParseNode *
+SetLvalKid(JSContext *cx, JSTokenStream *ts, JSParseNode *pn, JSParseNode *kid,
+ const char *name)
+{
+ while (kid->pn_type == TOK_RP)
+ kid = kid->pn_kid;
+ if (kid->pn_type != TOK_NAME &&
+ kid->pn_type != TOK_DOT &&
+#if JS_HAS_LVALUE_RETURN
+ (kid->pn_type != TOK_LP || kid->pn_op != JSOP_CALL) &&
+#endif
+#if JS_HAS_XML_SUPPORT
+ (kid->pn_type != TOK_UNARYOP || kid->pn_op != JSOP_XMLNAME) &&
+#endif
+ kid->pn_type != TOK_LB) {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_OPERAND, name);
+ return NULL;
+ }
+ pn->pn_kid = kid;
+ return kid;
+}
+
+static const char incop_name_str[][10] = {"increment", "decrement"};
+
+static JSBool
+SetIncOpKid(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
+ JSParseNode *pn, JSParseNode *kid,
+ JSTokenType tt, JSBool preorder)
+{
+ JSOp op;
+
+ kid = SetLvalKid(cx, ts, pn, kid, incop_name_str[tt == TOK_DEC]);
+ if (!kid)
+ return JS_FALSE;
+ switch (kid->pn_type) {
+ case TOK_NAME:
+ op = (tt == TOK_INC)
+ ? (preorder ? JSOP_INCNAME : JSOP_NAMEINC)
+ : (preorder ? JSOP_DECNAME : JSOP_NAMEDEC);
+ if (kid->pn_atom == cx->runtime->atomState.argumentsAtom)
+ tc->flags |= TCF_FUN_HEAVYWEIGHT;
+ break;
+
+ case TOK_DOT:
+ op = (tt == TOK_INC)
+ ? (preorder ? JSOP_INCPROP : JSOP_PROPINC)
+ : (preorder ? JSOP_DECPROP : JSOP_PROPDEC);
+ break;
+
+#if JS_HAS_LVALUE_RETURN
+ case TOK_LP:
+ JS_ASSERT(kid->pn_op == JSOP_CALL);
+ kid->pn_op = JSOP_SETCALL;
+ /* FALL THROUGH */
+#endif
+#if JS_HAS_XML_SUPPORT
+ case TOK_UNARYOP:
+ if (kid->pn_op == JSOP_XMLNAME)
+ kid->pn_op = JSOP_SETXMLNAME;
+ /* FALL THROUGH */
+#endif
+ case TOK_LB:
+ op = (tt == TOK_INC)
+ ? (preorder ? JSOP_INCELEM : JSOP_ELEMINC)
+ : (preorder ? JSOP_DECELEM : JSOP_ELEMDEC);
+ break;
+
+ default:
+ JS_ASSERT(0);
+ op = JSOP_NOP;
+ }
+ pn->pn_op = op;
+ return JS_TRUE;
+}
+
+static JSParseNode *
+UnaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSTokenType tt;
+ JSParseNode *pn, *pn2;
+
+ CHECK_RECURSION();
+
+ ts->flags |= TSF_OPERAND;
+ tt = js_GetToken(cx, ts);
+ ts->flags &= ~TSF_OPERAND;
+
+ switch (tt) {
+ case TOK_UNARYOP:
+ case TOK_PLUS:
+ case TOK_MINUS:
+ pn = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_type = TOK_UNARYOP; /* PLUS and MINUS are binary */
+ pn->pn_op = CURRENT_TOKEN(ts).t_op;
+ pn2 = UnaryExpr(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ pn->pn_pos.end = pn2->pn_pos.end;
+ pn->pn_kid = pn2;
+ break;
+
+ case TOK_INC:
+ case TOK_DEC:
+ pn = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!pn)
+ return NULL;
+ pn2 = MemberExpr(cx, ts, tc, JS_TRUE);
+ if (!pn2)
+ return NULL;
+ if (!SetIncOpKid(cx, ts, tc, pn, pn2, tt, JS_TRUE))
+ return NULL;
+ pn->pn_pos.end = pn2->pn_pos.end;
+ break;
+
+ case TOK_DELETE:
+ pn = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!pn)
+ return NULL;
+ pn2 = UnaryExpr(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ pn->pn_pos.end = pn2->pn_pos.end;
+
+ /*
+ * Under ECMA3, deleting any unary expression is valid -- it simply
+ * returns true. Here we strip off any parentheses.
+ */
+ while (pn2->pn_type == TOK_RP)
+ pn2 = pn2->pn_kid;
+ pn->pn_kid = pn2;
+ break;
+
+ case TOK_ERROR:
+ return NULL;
+
+ default:
+ js_UngetToken(ts);
+ pn = MemberExpr(cx, ts, tc, JS_TRUE);
+ if (!pn)
+ return NULL;
+
+ /* Don't look across a newline boundary for a postfix incop. */
+ if (ON_CURRENT_LINE(ts, pn->pn_pos)) {
+ ts->flags |= TSF_OPERAND;
+ tt = js_PeekTokenSameLine(cx, ts);
+ ts->flags &= ~TSF_OPERAND;
+ if (tt == TOK_INC || tt == TOK_DEC) {
+ (void) js_GetToken(cx, ts);
+ pn2 = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!pn2)
+ return NULL;
+ if (!SetIncOpKid(cx, ts, tc, pn2, pn, tt, JS_FALSE))
+ return NULL;
+ pn2->pn_pos.begin = pn->pn_pos.begin;
+ pn = pn2;
+ }
+ }
+ break;
+ }
+ return pn;
+}
+
+static JSBool
+ArgumentList(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
+ JSParseNode *listNode)
+{
+ JSBool matched;
+
+ ts->flags |= TSF_OPERAND;
+ matched = js_MatchToken(cx, ts, TOK_RP);
+ ts->flags &= ~TSF_OPERAND;
+ if (!matched) {
+ do {
+ JSParseNode *argNode = AssignExpr(cx, ts, tc);
+ if (!argNode)
+ return JS_FALSE;
+#if JS_HAS_GENERATORS
+ if (argNode->pn_type == TOK_YIELD) {
+ js_ReportCompileErrorNumber(cx, argNode,
+ JSREPORT_PN | JSREPORT_ERROR,
+ JSMSG_BAD_YIELD_SYNTAX);
+ return JS_FALSE;
+ }
+#endif
+ PN_APPEND(listNode, argNode);
+ } while (js_MatchToken(cx, ts, TOK_COMMA));
+
+ if (js_GetToken(cx, ts) != TOK_RP) {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_PAREN_AFTER_ARGS);
+ return JS_FALSE;
+ }
+ }
+ return JS_TRUE;
+}
+
+static JSParseNode *
+MemberExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
+ JSBool allowCallSyntax)
+{
+ JSParseNode *pn, *pn2, *pn3;
+ JSTokenType tt;
+
+ CHECK_RECURSION();
+
+ /* Check for new expression first. */
+ ts->flags |= TSF_OPERAND;
+ tt = js_GetToken(cx, ts);
+ ts->flags &= ~TSF_OPERAND;
+ if (tt == TOK_NEW) {
+ pn = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!pn)
+ return NULL;
+ pn2 = MemberExpr(cx, ts, tc, JS_FALSE);
+ if (!pn2)
+ return NULL;
+ pn->pn_op = JSOP_NEW;
+ PN_INIT_LIST_1(pn, pn2);
+ pn->pn_pos.begin = pn2->pn_pos.begin;
+
+ if (js_MatchToken(cx, ts, TOK_LP) && !ArgumentList(cx, ts, tc, pn))
+ return NULL;
+ if (pn->pn_count > ARGC_LIMIT) {
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+ JSMSG_TOO_MANY_CON_ARGS);
+ return NULL;
+ }
+ pn->pn_pos.end = PN_LAST(pn)->pn_pos.end;
+ } else {
+ pn = PrimaryExpr(cx, ts, tc, tt, JS_FALSE);
+ if (!pn)
+ return NULL;
+
+ if (pn->pn_type == TOK_ANYNAME ||
+ pn->pn_type == TOK_AT ||
+ pn->pn_type == TOK_DBLCOLON) {
+ pn2 = NewOrRecycledNode(cx, tc);
+ if (!pn2)
+ return NULL;
+ pn2->pn_type = TOK_UNARYOP;
+ pn2->pn_pos = pn->pn_pos;
+ pn2->pn_op = JSOP_XMLNAME;
+ pn2->pn_arity = PN_UNARY;
+ pn2->pn_kid = pn;
+ pn2->pn_next = NULL;
+ pn2->pn_ts = ts;
+ pn2->pn_source = NULL;
+ pn = pn2;
+ }
+ }
+
+ while ((tt = js_GetToken(cx, ts)) > TOK_EOF) {
+ if (tt == TOK_DOT) {
+ pn2 = NewParseNode(cx, ts, PN_NAME, tc);
+ if (!pn2)
+ return NULL;
+ pn2->pn_slot = -1;
+ pn2->pn_attrs = 0;
+#if JS_HAS_XML_SUPPORT
+ ts->flags |= TSF_OPERAND | TSF_KEYWORD_IS_NAME;
+ tt = js_GetToken(cx, ts);
+ ts->flags &= ~(TSF_OPERAND | TSF_KEYWORD_IS_NAME);
+ pn3 = PrimaryExpr(cx, ts, tc, tt, JS_TRUE);
+ if (!pn3)
+ return NULL;
+ tt = pn3->pn_type;
+ if (tt == TOK_NAME ||
+ (tt == TOK_DBLCOLON &&
+ pn3->pn_arity == PN_NAME &&
+ pn3->pn_expr->pn_type == TOK_FUNCTION)) {
+ pn2->pn_op = (tt == TOK_NAME) ? JSOP_GETPROP : JSOP_GETMETHOD;
+ pn2->pn_expr = pn;
+ pn2->pn_atom = pn3->pn_atom;
+ RecycleTree(pn3, tc);
+ } else {
+ if (TOKEN_TYPE_IS_XML(tt)) {
+ pn2->pn_type = TOK_LB;
+ pn2->pn_op = JSOP_GETELEM;
+ } else if (tt == TOK_RP) {
+ JSParseNode *group = pn3;
+
+ /* Recycle the useless TOK_RP/JSOP_GROUP node. */
+ pn3 = group->pn_kid;
+ group->pn_kid = NULL;
+ RecycleTree(group, tc);
+ pn2->pn_type = TOK_FILTER;
+ pn2->pn_op = JSOP_FILTER;
+
+ /* A filtering predicate is like a with statement. */
+ tc->flags |= TCF_FUN_HEAVYWEIGHT;
+ } else {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_NAME_AFTER_DOT);
+ return NULL;
+ }
+ pn2->pn_arity = PN_BINARY;
+ pn2->pn_left = pn;
+ pn2->pn_right = pn3;
+ }
+#else
+ ts->flags |= TSF_KEYWORD_IS_NAME;
+ MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NAME_AFTER_DOT);
+ ts->flags &= ~TSF_KEYWORD_IS_NAME;
+ pn2->pn_op = JSOP_GETPROP;
+ pn2->pn_expr = pn;
+ pn2->pn_atom = CURRENT_TOKEN(ts).t_atom;
+#endif
+ pn2->pn_pos.begin = pn->pn_pos.begin;
+ pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
+#if JS_HAS_XML_SUPPORT
+ } else if (tt == TOK_DBLDOT) {
+ pn2 = NewParseNode(cx, ts, PN_BINARY, tc);
+ if (!pn2)
+ return NULL;
+ ts->flags |= TSF_OPERAND | TSF_KEYWORD_IS_NAME;
+ tt = js_GetToken(cx, ts);
+ ts->flags &= ~(TSF_OPERAND | TSF_KEYWORD_IS_NAME);
+ pn3 = PrimaryExpr(cx, ts, tc, tt, JS_TRUE);
+ if (!pn3)
+ return NULL;
+ tt = pn3->pn_type;
+ if (tt == TOK_NAME) {
+ pn3->pn_type = TOK_STRING;
+ pn3->pn_arity = PN_NULLARY;
+ pn3->pn_op = JSOP_QNAMEPART;
+ } else if (!TOKEN_TYPE_IS_XML(tt)) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_NAME_AFTER_DOT);
+ return NULL;
+ }
+ pn2->pn_op = JSOP_DESCENDANTS;
+ pn2->pn_left = pn;
+ pn2->pn_right = pn3;
+ pn2->pn_pos.begin = pn->pn_pos.begin;
+ pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
+#endif
+ } else if (tt == TOK_LB) {
+ pn2 = NewParseNode(cx, ts, PN_BINARY, tc);
+ if (!pn2)
+ return NULL;
+ pn3 = Expr(cx, ts, tc);
+ if (!pn3)
+ return NULL;
+
+ MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX);
+ pn2->pn_pos.begin = pn->pn_pos.begin;
+ pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
+
+ /* Optimize o['p'] to o.p by rewriting pn2. */
+ if (pn3->pn_type == TOK_STRING) {
+ pn2->pn_type = TOK_DOT;
+ pn2->pn_op = JSOP_GETPROP;
+ pn2->pn_arity = PN_NAME;
+ pn2->pn_expr = pn;
+ pn2->pn_atom = pn3->pn_atom;
+ } else {
+ pn2->pn_op = JSOP_GETELEM;
+ pn2->pn_left = pn;
+ pn2->pn_right = pn3;
+ }
+ } else if (allowCallSyntax && tt == TOK_LP) {
+ pn2 = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!pn2)
+ return NULL;
+
+ /* Pick JSOP_EVAL and flag tc as heavyweight if eval(...). */
+ pn2->pn_op = JSOP_CALL;
+ if (pn->pn_op == JSOP_NAME &&
+ pn->pn_atom == cx->runtime->atomState.evalAtom) {
+ pn2->pn_op = JSOP_EVAL;
+ tc->flags |= TCF_FUN_HEAVYWEIGHT;
+ }
+
+ PN_INIT_LIST_1(pn2, pn);
+ pn2->pn_pos.begin = pn->pn_pos.begin;
+
+ if (!ArgumentList(cx, ts, tc, pn2))
+ return NULL;
+ if (pn2->pn_count > ARGC_LIMIT) {
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+ JSMSG_TOO_MANY_FUN_ARGS);
+ return NULL;
+ }
+ pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
+ } else {
+ js_UngetToken(ts);
+ return pn;
+ }
+
+ pn = pn2;
+ }
+ if (tt == TOK_ERROR)
+ return NULL;
+ return pn;
+}
+
+static JSParseNode *
+BracketedExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ uintN oldflags;
+ JSParseNode *pn;
+
+ /*
+ * Always accept the 'in' operator in a parenthesized expression,
+ * where it's unambiguous, even if we might be parsing the init of a
+ * for statement.
+ */
+ oldflags = tc->flags;
+ tc->flags &= ~TCF_IN_FOR_INIT;
+ pn = Expr(cx, ts, tc);
+ tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS);
+ return pn;
+}
+
+#if JS_HAS_XML_SUPPORT
+
+static JSParseNode *
+EndBracketedExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn;
+
+ pn = BracketedExpr(cx, ts, tc);
+ if (!pn)
+ return NULL;
+
+ MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_ATTR_EXPR);
+ return pn;
+}
+
+/*
+ * From the ECMA-357 grammar in 11.1.1 and 11.1.2:
+ *
+ * AttributeIdentifier:
+ * @ PropertySelector
+ * @ QualifiedIdentifier
+ * @ [ Expression ]
+ *
+ * PropertySelector:
+ * Identifier
+ * *
+ *
+ * QualifiedIdentifier:
+ * PropertySelector :: PropertySelector
+ * PropertySelector :: [ Expression ]
+ *
+ * We adapt AttributeIdentifier and QualifiedIdentier to be LL(1), like so:
+ *
+ * AttributeIdentifier:
+ * @ QualifiedIdentifier
+ * @ [ Expression ]
+ *
+ * PropertySelector:
+ * Identifier
+ * *
+ *
+ * QualifiedIdentifier:
+ * PropertySelector :: PropertySelector
+ * PropertySelector :: [ Expression ]
+ * PropertySelector
+ *
+ * As PrimaryExpression: Identifier is in ECMA-262 and we want the semantics
+ * for that rule to result in a name node, but ECMA-357 extends the grammar
+ * to include PrimaryExpression: QualifiedIdentifier, we must factor further:
+ *
+ * QualifiedIdentifier:
+ * PropertySelector QualifiedSuffix
+ *
+ * QualifiedSuffix:
+ * :: PropertySelector
+ * :: [ Expression ]
+ * /nothing/
+ *
+ * And use this production instead of PrimaryExpression: QualifiedIdentifier:
+ *
+ * PrimaryExpression:
+ * Identifier QualifiedSuffix
+ *
+ * We hoist the :: match into callers of QualifiedSuffix, in order to tweak
+ * PropertySelector vs. Identifier pn_arity, pn_op, and other members.
+ */
+static JSParseNode *
+PropertySelector(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn;
+
+ pn = NewParseNode(cx, ts, PN_NULLARY, tc);
+ if (!pn)
+ return NULL;
+ if (pn->pn_type == TOK_STAR) {
+ pn->pn_type = TOK_ANYNAME;
+ pn->pn_op = JSOP_ANYNAME;
+ pn->pn_atom = cx->runtime->atomState.starAtom;
+ } else {
+ JS_ASSERT(pn->pn_type == TOK_NAME);
+ pn->pn_op = JSOP_QNAMEPART;
+ pn->pn_arity = PN_NAME;
+ pn->pn_atom = CURRENT_TOKEN(ts).t_atom;
+ pn->pn_expr = NULL;
+ pn->pn_slot = -1;
+ pn->pn_attrs = 0;
+ }
+ return pn;
+}
+
+static JSParseNode *
+QualifiedSuffix(JSContext *cx, JSTokenStream *ts, JSParseNode *pn,
+ JSTreeContext *tc)
+{
+ JSParseNode *pn2, *pn3;
+ JSTokenType tt;
+
+ JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_DBLCOLON);
+ pn2 = NewParseNode(cx, ts, PN_NAME, tc);
+ if (!pn2)
+ return NULL;
+
+ /* Left operand of :: must be evaluated if it is an identifier. */
+ if (pn->pn_op == JSOP_QNAMEPART)
+ pn->pn_op = JSOP_NAME;
+
+ ts->flags |= TSF_KEYWORD_IS_NAME;
+ tt = js_GetToken(cx, ts);
+ ts->flags &= ~TSF_KEYWORD_IS_NAME;
+ if (tt == TOK_STAR || tt == TOK_NAME) {
+ /* Inline and specialize PropertySelector for JSOP_QNAMECONST. */
+ pn2->pn_op = JSOP_QNAMECONST;
+ pn2->pn_atom = (tt == TOK_STAR)
+ ? cx->runtime->atomState.starAtom
+ : CURRENT_TOKEN(ts).t_atom;
+ pn2->pn_expr = pn;
+ pn2->pn_slot = -1;
+ pn2->pn_attrs = 0;
+ return pn2;
+ }
+
+ if (tt != TOK_LB) {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_SYNTAX_ERROR);
+ return NULL;
+ }
+ pn3 = EndBracketedExpr(cx, ts, tc);
+ if (!pn3)
+ return NULL;
+
+ pn2->pn_op = JSOP_QNAME;
+ pn2->pn_arity = PN_BINARY;
+ pn2->pn_left = pn;
+ pn2->pn_right = pn3;
+ return pn2;
+}
+
+static JSParseNode *
+QualifiedIdentifier(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn;
+
+ pn = PropertySelector(cx, ts, tc);
+ if (!pn)
+ return NULL;
+ if (js_MatchToken(cx, ts, TOK_DBLCOLON))
+ pn = QualifiedSuffix(cx, ts, pn, tc);
+ return pn;
+}
+
+static JSParseNode *
+AttributeIdentifier(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn, *pn2;
+ JSTokenType tt;
+
+ JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_AT);
+ pn = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_op = JSOP_TOATTRNAME;
+ ts->flags |= TSF_KEYWORD_IS_NAME;
+ tt = js_GetToken(cx, ts);
+ ts->flags &= ~TSF_KEYWORD_IS_NAME;
+ if (tt == TOK_STAR || tt == TOK_NAME) {
+ pn2 = QualifiedIdentifier(cx, ts, tc);
+ } else if (tt == TOK_LB) {
+ pn2 = EndBracketedExpr(cx, ts, tc);
+ } else {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_SYNTAX_ERROR);
+ return NULL;
+ }
+ if (!pn2)
+ return NULL;
+ pn->pn_kid = pn2;
+ return pn;
+}
+
+/*
+ * Make a TOK_LC unary node whose pn_kid is an expression.
+ */
+static JSParseNode *
+XMLExpr(JSContext *cx, JSTokenStream *ts, JSBool inTag, JSTreeContext *tc)
+{
+ JSParseNode *pn, *pn2;
+ uintN oldflags;
+
+ JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_LC);
+ pn = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!pn)
+ return NULL;
+
+ /*
+ * Turn off XML tag mode, but don't restore it after parsing this braced
+ * expression. Instead, simply restore ts's old flags. This is required
+ * because XMLExpr is called both from within a tag, and from within text
+ * contained in an element, but outside of any start, end, or point tag.
+ */
+ oldflags = ts->flags;
+ ts->flags = oldflags & ~TSF_XMLTAGMODE;
+ pn2 = Expr(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+
+ MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_IN_XML_EXPR);
+ ts->flags = oldflags;
+ pn->pn_kid = pn2;
+ pn->pn_op = inTag ? JSOP_XMLTAGEXPR : JSOP_XMLELTEXPR;
+ return pn;
+}
+
+/*
+ * Make a terminal node for one of TOK_XMLNAME, TOK_XMLATTR, TOK_XMLSPACE,
+ * TOK_XMLTEXT, TOK_XMLCDATA, TOK_XMLCOMMENT, or TOK_XMLPI. When converting
+ * parse tree to XML, we preserve a TOK_XMLSPACE node only if it's the sole
+ * child of a container tag.
+ */
+static JSParseNode *
+XMLAtomNode(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn;
+ JSToken *tp;
+
+ pn = NewParseNode(cx, ts, PN_NULLARY, tc);
+ if (!pn)
+ return NULL;
+ tp = &CURRENT_TOKEN(ts);
+ pn->pn_op = tp->t_op;
+ pn->pn_atom = tp->t_atom;
+ if (tp->type == TOK_XMLPI)
+ pn->pn_atom2 = tp->t_atom2;
+ return pn;
+}
+
+/*
+ * Parse the productions:
+ *
+ * XMLNameExpr:
+ * XMLName XMLNameExpr?
+ * { Expr } XMLNameExpr?
+ *
+ * Return a PN_LIST, PN_UNARY, or PN_NULLARY according as XMLNameExpr produces
+ * a list of names and/or expressions, a single expression, or a single name.
+ * If PN_LIST or PN_NULLARY, pn_type will be TOK_XMLNAME; if PN_UNARY, pn_type
+ * will be TOK_LC.
+ */
+static JSParseNode *
+XMLNameExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
+{
+ JSParseNode *pn, *pn2, *list;
+ JSTokenType tt;
+
+ pn = list = NULL;
+ do {
+ tt = CURRENT_TOKEN(ts).type;
+ if (tt == TOK_LC) {
+ pn2 = XMLExpr(cx, ts, JS_TRUE, tc);
+ if (!pn2)
+ return NULL;
+ } else {
+ JS_ASSERT(tt == TOK_XMLNAME);
+ pn2 = XMLAtomNode(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ }
+
+ if (!pn) {
+ pn = pn2;
+ } else {
+ if (!list) {
+ list = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!list)
+ return NULL;
+ list->pn_type = TOK_XMLNAME;
+ list->pn_pos.begin = pn->pn_pos.begin;
+ PN_INIT_LIST_1(list, pn);
+ list->pn_extra = PNX_CANTFOLD;
+ pn = list;
+ }
+ pn->pn_pos.end = pn2->pn_pos.end;
+ PN_APPEND(pn, pn2);
+ }
+ } while ((tt = js_GetToken(cx, ts)) == TOK_XMLNAME || tt == TOK_LC);
+
+ js_UngetToken(ts);
+ return pn;
+}
+
+/*
+ * Macro to test whether an XMLNameExpr or XMLTagContent node can be folded
+ * at compile time into a JSXML tree.
+ */
+#define XML_FOLDABLE(pn) ((pn)->pn_arity == PN_LIST \
+ ? ((pn)->pn_extra & PNX_CANTFOLD) == 0 \
+ : (pn)->pn_type != TOK_LC)
+
+/*
+ * Parse the productions:
+ *
+ * XMLTagContent:
+ * XMLNameExpr
+ * XMLTagContent S XMLNameExpr S? = S? XMLAttr
+ * XMLTagContent S XMLNameExpr S? = S? { Expr }
+ *
+ * Return a PN_LIST, PN_UNARY, or PN_NULLARY according to how XMLTagContent
+ * produces a list of name and attribute values and/or braced expressions, a
+ * single expression, or a single name.
+ *
+ * If PN_LIST or PN_NULLARY, pn_type will be TOK_XMLNAME for the case where
+ * XMLTagContent: XMLNameExpr. If pn_type is not TOK_XMLNAME but pn_arity is
+ * PN_LIST, pn_type will be tagtype. If PN_UNARY, pn_type will be TOK_LC and
+ * we parsed exactly one expression.
+ */
+static JSParseNode *
+XMLTagContent(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
+ JSTokenType tagtype, JSAtom **namep)
+{
+ JSParseNode *pn, *pn2, *list;
+ JSTokenType tt;
+
+ pn = XMLNameExpr(cx, ts, tc);
+ if (!pn)
+ return NULL;
+ *namep = (pn->pn_arity == PN_NULLARY) ? pn->pn_atom : NULL;
+ list = NULL;
+
+ while (js_MatchToken(cx, ts, TOK_XMLSPACE)) {
+ tt = js_GetToken(cx, ts);
+ if (tt != TOK_XMLNAME && tt != TOK_LC) {
+ js_UngetToken(ts);
+ break;
+ }
+
+ pn2 = XMLNameExpr(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+ if (!list) {
+ list = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!list)
+ return NULL;
+ list->pn_type = tagtype;
+ list->pn_pos.begin = pn->pn_pos.begin;
+ PN_INIT_LIST_1(list, pn);
+ pn = list;
+ }
+ PN_APPEND(pn, pn2);
+ if (!XML_FOLDABLE(pn2))
+ pn->pn_extra |= PNX_CANTFOLD;
+
+ js_MatchToken(cx, ts, TOK_XMLSPACE);
+ MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_NO_ASSIGN_IN_XML_ATTR);
+ js_MatchToken(cx, ts, TOK_XMLSPACE);
+
+ tt = js_GetToken(cx, ts);
+ if (tt == TOK_XMLATTR) {
+ pn2 = XMLAtomNode(cx, ts, tc);
+ } else if (tt == TOK_LC) {
+ pn2 = XMLExpr(cx, ts, JS_TRUE, tc);
+ pn->pn_extra |= PNX_CANTFOLD;
+ } else {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_XML_ATTR_VALUE);
+ return NULL;
+ }
+ if (!pn2)
+ return NULL;
+ pn->pn_pos.end = pn2->pn_pos.end;
+ PN_APPEND(pn, pn2);
+ }
+
+ return pn;
+}
+
+#define XML_CHECK_FOR_ERROR_AND_EOF(tt,result) \
+ JS_BEGIN_MACRO \
+ if ((tt) <= TOK_EOF) { \
+ if ((tt) == TOK_EOF) { \
+ js_ReportCompileErrorNumber(cx, ts, \
+ JSREPORT_TS | JSREPORT_ERROR, \
+ JSMSG_END_OF_XML_SOURCE); \
+ } \
+ return result; \
+ } \
+ JS_END_MACRO
+
+static JSParseNode *
+XMLElementOrList(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
+ JSBool allowList);
+
+/*
+ * Consume XML element tag content, including the TOK_XMLETAGO (</) sequence
+ * that opens the end tag for the container.
+ */
+static JSBool
+XMLElementContent(JSContext *cx, JSTokenStream *ts, JSParseNode *pn,
+ JSTreeContext *tc)
+{
+ JSTokenType tt;
+ JSParseNode *pn2;
+ JSAtom *textAtom;
+
+ ts->flags &= ~TSF_XMLTAGMODE;
+ for (;;) {
+ ts->flags |= TSF_XMLTEXTMODE;
+ tt = js_GetToken(cx, ts);
+ ts->flags &= ~TSF_XMLTEXTMODE;
+ XML_CHECK_FOR_ERROR_AND_EOF(tt, JS_FALSE);
+
+ JS_ASSERT(tt == TOK_XMLSPACE || tt == TOK_XMLTEXT);
+ textAtom = CURRENT_TOKEN(ts).t_atom;
+ if (textAtom) {
+ /* Non-zero-length XML text scanned. */
+ pn2 = XMLAtomNode(cx, ts, tc);
+ if (!pn2)
+ return JS_FALSE;
+ pn->pn_pos.end = pn2->pn_pos.end;
+ PN_APPEND(pn, pn2);
+ }
+
+ ts->flags |= TSF_OPERAND;
+ tt = js_GetToken(cx, ts);
+ ts->flags &= ~TSF_OPERAND;
+ XML_CHECK_FOR_ERROR_AND_EOF(tt, JS_FALSE);
+ if (tt == TOK_XMLETAGO)
+ break;
+
+ if (tt == TOK_LC) {
+ pn2 = XMLExpr(cx, ts, JS_FALSE, tc);
+ pn->pn_extra |= PNX_CANTFOLD;
+ } else if (tt == TOK_XMLSTAGO) {
+ pn2 = XMLElementOrList(cx, ts, tc, JS_FALSE);
+ if (pn2) {
+ pn2->pn_extra &= ~PNX_XMLROOT;
+ pn->pn_extra |= pn2->pn_extra;
+ }
+ } else {
+ JS_ASSERT(tt == TOK_XMLCDATA || tt == TOK_XMLCOMMENT ||
+ tt == TOK_XMLPI);
+ pn2 = XMLAtomNode(cx, ts, tc);
+ }
+ if (!pn2)
+ return JS_FALSE;
+ pn->pn_pos.end = pn2->pn_pos.end;
+ PN_APPEND(pn, pn2);
+ }
+
+ JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_XMLETAGO);
+ ts->flags |= TSF_XMLTAGMODE;
+ return JS_TRUE;
+}
+
+/*
+ * Return a PN_LIST node containing an XML or XMLList Initialiser.
+ */
+static JSParseNode *
+XMLElementOrList(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
+ JSBool allowList)
+{
+ JSParseNode *pn, *pn2, *list;
+ JSBool hadSpace;
+ JSTokenType tt;
+ JSAtom *startAtom, *endAtom;
+
+ CHECK_RECURSION();
+
+ JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_XMLSTAGO);
+ pn = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!pn)
+ return NULL;
+
+ ts->flags |= TSF_XMLTAGMODE;
+ hadSpace = js_MatchToken(cx, ts, TOK_XMLSPACE);
+ tt = js_GetToken(cx, ts);
+ if (tt == TOK_ERROR)
+ return NULL;
+
+ if (tt == TOK_XMLNAME || tt == TOK_LC) {
+ /*
+ * XMLElement. Append the tag and its contents, if any, to pn.
+ */
+ pn2 = XMLTagContent(cx, ts, tc, TOK_XMLSTAGO, &startAtom);
+ if (!pn2)
+ return NULL;
+ js_MatchToken(cx, ts, TOK_XMLSPACE);
+
+ tt = js_GetToken(cx, ts);
+ if (tt == TOK_XMLPTAGC) {
+ /* Point tag (/>): recycle pn if pn2 is a list of tag contents. */
+ if (pn2->pn_type == TOK_XMLSTAGO) {
+ PN_INIT_LIST(pn);
+ RecycleTree(pn, tc);
+ pn = pn2;
+ } else {
+ JS_ASSERT(pn2->pn_type == TOK_XMLNAME ||
+ pn2->pn_type == TOK_LC);
+ PN_INIT_LIST_1(pn, pn2);
+ if (!XML_FOLDABLE(pn2))
+ pn->pn_extra |= PNX_CANTFOLD;
+ }
+ pn->pn_type = TOK_XMLPTAGC;
+ pn->pn_extra |= PNX_XMLROOT;
+ } else {
+ /* We had better have a tag-close (>) at this point. */
+ if (tt != TOK_XMLTAGC) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_XML_TAG_SYNTAX);
+ return NULL;
+ }
+ pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
+
+ /* Make sure pn2 is a TOK_XMLSTAGO list containing tag contents. */
+ if (pn2->pn_type != TOK_XMLSTAGO) {
+ PN_INIT_LIST_1(pn, pn2);
+ if (!XML_FOLDABLE(pn2))
+ pn->pn_extra |= PNX_CANTFOLD;
+ pn2 = pn;
+ pn = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!pn)
+ return NULL;
+ }
+
+ /* Now make pn a nominal-root TOK_XMLELEM list containing pn2. */
+ pn->pn_type = TOK_XMLELEM;
+ PN_INIT_LIST_1(pn, pn2);
+ if (!XML_FOLDABLE(pn2))
+ pn->pn_extra |= PNX_CANTFOLD;
+ pn->pn_extra |= PNX_XMLROOT;
+
+ /* Get element contents and delimiting end-tag-open sequence. */
+ if (!XMLElementContent(cx, ts, pn, tc))
+ return NULL;
+
+ js_MatchToken(cx, ts, TOK_XMLSPACE);
+ tt = js_GetToken(cx, ts);
+ XML_CHECK_FOR_ERROR_AND_EOF(tt, NULL);
+ if (tt != TOK_XMLNAME && tt != TOK_LC) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_XML_TAG_SYNTAX);
+ return NULL;
+ }
+
+ /* Parse end tag; check mismatch at compile-time if we can. */
+ pn2 = XMLTagContent(cx, ts, tc, TOK_XMLETAGO, &endAtom);
+ if (!pn2)
+ return NULL;
+ if (pn2->pn_type == TOK_XMLETAGO) {
+ /* Oops, end tag has attributes! */
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_XML_TAG_SYNTAX);
+ return NULL;
+ }
+ if (endAtom && startAtom && endAtom != startAtom) {
+ JSString *str = ATOM_TO_STRING(startAtom);
+
+ /* End vs. start tag name mismatch: point to the tag name. */
+ js_ReportCompileErrorNumberUC(cx, pn2,
+ JSREPORT_PN | JSREPORT_ERROR,
+ JSMSG_XML_TAG_NAME_MISMATCH,
+ JSSTRING_CHARS(str));
+ return NULL;
+ }
+
+ /* Make a TOK_XMLETAGO list with pn2 as its single child. */
+ JS_ASSERT(pn2->pn_type == TOK_XMLNAME || pn2->pn_type == TOK_LC);
+ list = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!list)
+ return NULL;
+ list->pn_type = TOK_XMLETAGO;
+ PN_INIT_LIST_1(list, pn2);
+ PN_APPEND(pn, list);
+ if (!XML_FOLDABLE(pn2)) {
+ list->pn_extra |= PNX_CANTFOLD;
+ pn->pn_extra |= PNX_CANTFOLD;
+ }
+
+ js_MatchToken(cx, ts, TOK_XMLSPACE);
+ MUST_MATCH_TOKEN(TOK_XMLTAGC, JSMSG_BAD_XML_TAG_SYNTAX);
+ }
+
+ /* Set pn_op now that pn has been updated to its final value. */
+ pn->pn_op = JSOP_TOXML;
+ } else if (!hadSpace && allowList && tt == TOK_XMLTAGC) {
+ /* XMLList Initialiser. */
+ pn->pn_type = TOK_XMLLIST;
+ pn->pn_op = JSOP_TOXMLLIST;
+ PN_INIT_LIST(pn);
+ pn->pn_extra |= PNX_XMLROOT;
+ if (!XMLElementContent(cx, ts, pn, tc))
+ return NULL;
+
+ MUST_MATCH_TOKEN(TOK_XMLTAGC, JSMSG_BAD_XML_LIST_SYNTAX);
+ } else {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_XML_NAME_SYNTAX);
+ return NULL;
+ }
+
+ pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
+ ts->flags &= ~TSF_XMLTAGMODE;
+ return pn;
+}
+
+static JSParseNode *
+XMLElementOrListRoot(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
+ JSBool allowList)
+{
+ uint32 oldopts;
+ JSParseNode *pn;
+
+ /*
+ * Force XML support to be enabled so that comments and CDATA literals
+ * are recognized, instead of <! followed by -- starting an HTML comment
+ * to end of line (used in script tags to hide content from old browsers
+ * that don't recognize <script>).
+ */
+ oldopts = JS_SetOptions(cx, cx->options | JSOPTION_XML);
+ pn = XMLElementOrList(cx, ts, tc, allowList);
+ JS_SetOptions(cx, oldopts);
+ return pn;
+}
+
+JS_FRIEND_API(JSParseNode *)
+js_ParseXMLTokenStream(JSContext *cx, JSObject *chain, JSTokenStream *ts,
+ JSBool allowList)
+{
+ JSStackFrame *fp, frame;
+ JSParseNode *pn;
+ JSTreeContext tc;
+ JSTokenType tt;
+
+ /*
+ * Push a compiler frame if we have no frames, or if the top frame is a
+ * lightweight function activation, or if its scope chain doesn't match
+ * the one passed to us.
+ */
+ fp = cx->fp;
+ MaybeSetupFrame(cx, chain, fp, &frame);
+ JS_KEEP_ATOMS(cx->runtime);
+ TREE_CONTEXT_INIT(&tc);
+
+ /* Set XML-only mode to turn off special treatment of {expr} in XML. */
+ ts->flags |= TSF_OPERAND | TSF_XMLONLYMODE;
+ tt = js_GetToken(cx, ts);
+ ts->flags &= ~TSF_OPERAND;
+
+ if (tt != TOK_XMLSTAGO) {
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_XML_MARKUP);
+ pn = NULL;
+ } else {
+ pn = XMLElementOrListRoot(cx, ts, &tc, allowList);
+ }
+
+ ts->flags &= ~TSF_XMLONLYMODE;
+ TREE_CONTEXT_FINISH(&tc);
+ JS_UNKEEP_ATOMS(cx->runtime);
+ cx->fp = fp;
+ return pn;
+}
+
+#endif /* JS_HAS_XMLSUPPORT */
+
+static JSParseNode *
+PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
+ JSTokenType tt, JSBool afterDot)
+{
+ JSParseNode *pn, *pn2, *pn3;
+ JSOp op;
+
+#if JS_HAS_SHARP_VARS
+ JSParseNode *defsharp;
+ JSBool notsharp;
+
+ defsharp = NULL;
+ notsharp = JS_FALSE;
+ again:
+ /*
+ * Control flows here after #n= is scanned. If the following primary is
+ * not valid after such a "sharp variable" definition, the tt switch case
+ * should set notsharp.
+ */
+#endif
+
+ CHECK_RECURSION();
+
+#if JS_HAS_GETTER_SETTER
+ if (tt == TOK_NAME) {
+ tt = CheckGetterOrSetter(cx, ts, TOK_FUNCTION);
+ if (tt == TOK_ERROR)
+ return NULL;
+ }
+#endif
+
+ switch (tt) {
+ case TOK_FUNCTION:
+#if JS_HAS_XML_SUPPORT
+ ts->flags |= TSF_KEYWORD_IS_NAME;
+ if (js_MatchToken(cx, ts, TOK_DBLCOLON)) {
+ ts->flags &= ~TSF_KEYWORD_IS_NAME;
+ pn2 = NewParseNode(cx, ts, PN_NULLARY, tc);
+ if (!pn2)
+ return NULL;
+ pn2->pn_type = TOK_FUNCTION;
+ pn = QualifiedSuffix(cx, ts, pn2, tc);
+ if (!pn)
+ return NULL;
+ break;
+ }
+ ts->flags &= ~TSF_KEYWORD_IS_NAME;
+#endif
+ pn = FunctionExpr(cx, ts, tc);
+ if (!pn)
+ return NULL;
+ break;
+
+ case TOK_LB:
+ {
+ JSBool matched;
+ jsuint index;
+
+ pn = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_type = TOK_RB;
+
+#if JS_HAS_SHARP_VARS
+ if (defsharp) {
+ PN_INIT_LIST_1(pn, defsharp);
+ defsharp = NULL;
+ } else
+#endif
+ PN_INIT_LIST(pn);
+
+ ts->flags |= TSF_OPERAND;
+ matched = js_MatchToken(cx, ts, TOK_RB);
+ ts->flags &= ~TSF_OPERAND;
+ if (!matched) {
+ for (index = 0; ; index++) {
+ if (index == ARRAY_INIT_LIMIT) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_ARRAY_INIT_TOO_BIG);
+ return NULL;
+ }
+
+ ts->flags |= TSF_OPERAND;
+ tt = js_PeekToken(cx, ts);
+ ts->flags &= ~TSF_OPERAND;
+ if (tt == TOK_RB) {
+ pn->pn_extra |= PNX_ENDCOMMA;
+ break;
+ }
+
+ if (tt == TOK_COMMA) {
+ /* So CURRENT_TOKEN gets TOK_COMMA and not TOK_LB. */
+ js_MatchToken(cx, ts, TOK_COMMA);
+ pn2 = NewParseNode(cx, ts, PN_NULLARY, tc);
+ } else {
+ pn2 = AssignExpr(cx, ts, tc);
+ }
+ if (!pn2)
+ return NULL;
+ PN_APPEND(pn, pn2);
+
+ if (tt != TOK_COMMA) {
+ /* If we didn't already match TOK_COMMA in above case. */
+ if (!js_MatchToken(cx, ts, TOK_COMMA))
+ break;
+ }
+ }
+
+#if JS_HAS_GENERATORS
+ /*
+ * At this point, (index == 0 && pn->pn_count != 0) implies one
+ * element initialiser was parsed (possibly with a defsharp before
+ * the left bracket).
+ *
+ * An array comprehension of the form:
+ *
+ * [i * j for (i in o) for (j in p) if (i != j)]
+ *
+ * translates to roughly the following let expression:
+ *
+ * let (array = new Array, i, j) {
+ * for (i in o) let {
+ * for (j in p)
+ * if (i != j)
+ * array.push(i * j)
+ * }
+ * array
+ * }
+ *
+ * where array is a nameless block-local variable. The "roughly"
+ * means that an implementation may optimize away the array.push.
+ * An array comprehension opens exactly one block scope, no matter
+ * how many for heads it contains.
+ *
+ * Each let () {...} or for (let ...) ... compiles to:
+ *
+ * JSOP_ENTERBLOCK <o> ... JSOP_LEAVEBLOCK <n>
+ *
+ * where <o> is a literal object representing the block scope,
+ * with <n> properties, naming each var declared in the block.
+ *
+ * Each var declaration in a let-block binds a name in <o> at
+ * compile time, and allocates a slot on the operand stack at
+ * runtime via JSOP_ENTERBLOCK. A block-local var is accessed
+ * by the JSOP_GETLOCAL and JSOP_SETLOCAL ops, and iterated with
+ * JSOP_FORLOCAL. These ops all have an immediate operand, the
+ * local slot's stack index from fp->spbase.
+ *
+ * The array comprehension iteration step, array.push(i * j) in
+ * the example above, is done by <i * j>; JSOP_ARRAYCOMP <array>,
+ * where <array> is the index of array's stack slot.
+ */
+ if (index == 0 &&
+ pn->pn_count != 0 &&
+ js_MatchToken(cx, ts, TOK_FOR)) {
+ JSParseNode **pnp, *pnexp, *pntop, *pnlet;
+ BindData data;
+ JSRuntime *rt;
+ JSStmtInfo stmtInfo;
+ JSAtom *atom;
+
+ /* Relabel pn as an array comprehension node. */
+ pn->pn_type = TOK_ARRAYCOMP;
+
+ /*
+ * Remove the comprehension expression from pn's linked list
+ * and save it via pnexp. We'll re-install it underneath the
+ * ARRAYPUSH node after we parse the rest of the comprehension.
+ */
+ pnexp = PN_LAST(pn);
+ JS_ASSERT(pn->pn_count == 1 || pn->pn_count == 2);
+ pn->pn_tail = (--pn->pn_count == 1)
+ ? &pn->pn_head->pn_next
+ : &pn->pn_head;
+ *pn->pn_tail = NULL;
+
+ /*
+ * Make a parse-node and literal object representing the array
+ * comprehension's block scope.
+ */
+ pntop = PushLexicalScope(cx, ts, tc, &stmtInfo);
+ if (!pntop)
+ return NULL;
+ pnp = &pntop->pn_expr;
+
+ data.pn = NULL;
+ data.ts = ts;
+ data.obj = tc->blockChain;
+ data.op = JSOP_NOP;
+ data.binder = BindLet;
+ data.u.let.index = 0;
+ data.u.let.overflow = JSMSG_ARRAY_INIT_TOO_BIG;
+
+ rt = cx->runtime;
+ do {
+ /*
+ * FOR node is binary, left is control and right is body.
+ * Use index to count each block-local let-variable on the
+ * left-hand side of IN.
+ */
+ pn2 = NewParseNode(cx, ts, PN_BINARY, tc);
+ if (!pn2)
+ return NULL;
+
+ pn2->pn_op = JSOP_FORIN;
+ if (js_MatchToken(cx, ts, TOK_NAME)) {
+ if (CURRENT_TOKEN(ts).t_atom == rt->atomState.eachAtom)
+ pn2->pn_op = JSOP_FOREACH;
+ else
+ js_UngetToken(ts);
+ }
+ MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR);
+
+ tt = js_GetToken(cx, ts);
+ switch (tt) {
+#if JS_HAS_DESTRUCTURING
+ case TOK_LB:
+ case TOK_LC:
+ pnlet = DestructuringExpr(cx, &data, tc, tt);
+ if (!pnlet)
+ return NULL;
+
+ if (pnlet->pn_type != TOK_RB || pnlet->pn_count != 2) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS |
+ JSREPORT_ERROR,
+ JSMSG_BAD_FOR_LEFTSIDE);
+ return NULL;
+ }
+
+ /* Destructuring requires [key, value] enumeration. */
+ if (pn2->pn_op != JSOP_FOREACH)
+ pn2->pn_op = JSOP_FOREACHKEYVAL;
+ break;
+#endif
+
+ case TOK_NAME:
+ atom = CURRENT_TOKEN(ts).t_atom;
+ if (!data.binder(cx, &data, atom, tc))
+ return NULL;
+
+ /*
+ * Create a name node with op JSOP_NAME. We can't set
+ * op to JSOP_GETLOCAL here, because we don't yet know
+ * the block's depth in the operand stack frame. The
+ * code generator computes that, and it tries to bind
+ * all names to slots, so we must let it do the deed.
+ */
+ pnlet = NewParseNode(cx, ts, PN_NAME, tc);
+ if (!pnlet)
+ return NULL;
+ pnlet->pn_op = JSOP_NAME;
+ pnlet->pn_atom = atom;
+ pnlet->pn_expr = NULL;
+ pnlet->pn_slot = -1;
+ pnlet->pn_attrs = 0;
+ break;
+
+ default:
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS|JSREPORT_ERROR,
+ JSMSG_NO_VARIABLE_NAME);
+ return NULL;
+ }
+
+ MUST_MATCH_TOKEN(TOK_IN, JSMSG_IN_AFTER_FOR_NAME);
+ pn3 = NewBinary(cx, TOK_IN, JSOP_NOP, pnlet,
+ Expr(cx, ts, tc), tc);
+ if (!pn3)
+ return NULL;
+
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL);
+ pn2->pn_left = pn3;
+ *pnp = pn2;
+ pnp = &pn2->pn_right;
+ } while (js_MatchToken(cx, ts, TOK_FOR));
+
+ if (js_MatchToken(cx, ts, TOK_IF)) {
+ pn2 = NewParseNode(cx, ts, PN_TERNARY, tc);
+ if (!pn2)
+ return NULL;
+ pn2->pn_kid1 = Condition(cx, ts, tc);
+ if (!pn2->pn_kid1)
+ return NULL;
+ pn2->pn_kid2 = NULL;
+ pn2->pn_kid3 = NULL;
+ *pnp = pn2;
+ pnp = &pn2->pn_kid2;
+ }
+
+ pn2 = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!pn2)
+ return NULL;
+ pn2->pn_type = TOK_ARRAYPUSH;
+ pn2->pn_op = JSOP_ARRAYPUSH;
+ pn2->pn_kid = pnexp;
+ *pnp = pn2;
+ PN_APPEND(pn, pntop);
+
+ js_PopStatement(tc);
+ }
+#endif /* JS_HAS_GENERATORS */
+
+ MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_LIST);
+ }
+ pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
+ return pn;
+ }
+
+#if JS_HAS_BLOCK_SCOPE
+ case TOK_LET:
+ pn = LetBlock(cx, ts, tc, JS_FALSE);
+ if (!pn)
+ return NULL;
+ break;
+#endif
+
+ case TOK_LC:
+ {
+ JSBool afterComma;
+
+ pn = NewParseNode(cx, ts, PN_LIST, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_type = TOK_RC;
+
+#if JS_HAS_SHARP_VARS
+ if (defsharp) {
+ PN_INIT_LIST_1(pn, defsharp);
+ defsharp = NULL;
+ } else
+#endif
+ PN_INIT_LIST(pn);
+
+ afterComma = JS_FALSE;
+ for (;;) {
+ ts->flags |= TSF_KEYWORD_IS_NAME;
+ tt = js_GetToken(cx, ts);
+ ts->flags &= ~TSF_KEYWORD_IS_NAME;
+ switch (tt) {
+ case TOK_NUMBER:
+ pn3 = NewParseNode(cx, ts, PN_NULLARY, tc);
+ if (pn3)
+ pn3->pn_dval = CURRENT_TOKEN(ts).t_dval;
+ break;
+ case TOK_NAME:
+#if JS_HAS_GETTER_SETTER
+ {
+ JSAtom *atom;
+ JSRuntime *rt;
+
+ atom = CURRENT_TOKEN(ts).t_atom;
+ rt = cx->runtime;
+ if (atom == rt->atomState.getAtom ||
+ atom == rt->atomState.setAtom) {
+ op = (atom == rt->atomState.getAtom)
+ ? JSOP_GETTER
+ : JSOP_SETTER;
+ if (js_MatchToken(cx, ts, TOK_NAME)) {
+ pn3 = NewParseNode(cx, ts, PN_NAME, tc);
+ if (!pn3)
+ return NULL;
+ pn3->pn_atom = CURRENT_TOKEN(ts).t_atom;
+ pn3->pn_expr = NULL;
+ pn3->pn_slot = -1;
+ pn3->pn_attrs = 0;
+
+ /* We have to fake a 'function' token here. */
+ CURRENT_TOKEN(ts).t_op = JSOP_NOP;
+ CURRENT_TOKEN(ts).type = TOK_FUNCTION;
+ pn2 = FunctionExpr(cx, ts, tc);
+ pn2 = NewBinary(cx, TOK_COLON, op, pn3, pn2, tc);
+ goto skip;
+ }
+ }
+ /* else fall thru ... */
+ }
+#endif
+ case TOK_STRING:
+ pn3 = NewParseNode(cx, ts, PN_NULLARY, tc);
+ if (pn3)
+ pn3->pn_atom = CURRENT_TOKEN(ts).t_atom;
+ break;
+ case TOK_RC:
+ if (afterComma &&
+ !js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS |
+ JSREPORT_WARNING |
+ JSREPORT_STRICT,
+ JSMSG_TRAILING_COMMA)) {
+ return NULL;
+ }
+ goto end_obj_init;
+ default:
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_PROP_ID);
+ return NULL;
+ }
+
+ tt = js_GetToken(cx, ts);
+#if JS_HAS_GETTER_SETTER
+ if (tt == TOK_NAME) {
+ tt = CheckGetterOrSetter(cx, ts, TOK_COLON);
+ if (tt == TOK_ERROR)
+ return NULL;
+ }
+#endif
+ if (tt != TOK_COLON) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_COLON_AFTER_ID);
+ return NULL;
+ }
+ op = CURRENT_TOKEN(ts).t_op;
+ pn2 = NewBinary(cx, TOK_COLON, op, pn3, AssignExpr(cx, ts, tc), tc);
+#if JS_HAS_GETTER_SETTER
+ skip:
+#endif
+ if (!pn2)
+ return NULL;
+ PN_APPEND(pn, pn2);
+
+ tt = js_GetToken(cx, ts);
+ if (tt == TOK_RC)
+ goto end_obj_init;
+ if (tt != TOK_COMMA) {
+ js_ReportCompileErrorNumber(cx, ts,
+ JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_CURLY_AFTER_LIST);
+ return NULL;
+ }
+ afterComma = JS_TRUE;
+ }
+ end_obj_init:
+ pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
+ return pn;
+ }
+
+#if JS_HAS_SHARP_VARS
+ case TOK_DEFSHARP:
+ if (defsharp)
+ goto badsharp;
+ defsharp = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!defsharp)
+ return NULL;
+ defsharp->pn_kid = NULL;
+ defsharp->pn_num = (jsint) CURRENT_TOKEN(ts).t_dval;
+ ts->flags |= TSF_OPERAND;
+ tt = js_GetToken(cx, ts);
+ ts->flags &= ~TSF_OPERAND;
+ goto again;
+
+ case TOK_USESHARP:
+ /* Check for forward/dangling references at runtime, to allow eval. */
+ pn = NewParseNode(cx, ts, PN_NULLARY, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_num = (jsint) CURRENT_TOKEN(ts).t_dval;
+ notsharp = JS_TRUE;
+ break;
+#endif /* JS_HAS_SHARP_VARS */
+
+ case TOK_LP:
+ pn = NewParseNode(cx, ts, PN_UNARY, tc);
+ if (!pn)
+ return NULL;
+ pn2 = BracketedExpr(cx, ts, tc);
+ if (!pn2)
+ return NULL;
+
+ MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN);
+ if (pn2->pn_type == TOK_RP ||
+ (js_CodeSpec[pn2->pn_op].prec >= js_CodeSpec[JSOP_GETPROP].prec &&
+ !afterDot)) {
+ /*
+ * Avoid redundant JSOP_GROUP opcodes, for efficiency and mainly
+ * to help the decompiler look ahead from a JSOP_ENDINIT to see a
+ * JSOP_GROUP followed by a POP or POPV. That sequence means the
+ * parentheses are mandatory, to disambiguate object initialisers
+ * as expression statements from block statements.
+ *
+ * Also drop pn if pn2 is a member or a primary expression of any
+ * kind. This is required to avoid generating a JSOP_GROUP that
+ * will null the |obj| interpreter register, causing |this| in any
+ * call of that member expression to bind to the global object.
+ */
+ pn->pn_kid = NULL;
+ RecycleTree(pn, tc);
+ pn = pn2;
+ } else {
+ pn->pn_type = TOK_RP;
+ pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
+ pn->pn_kid = pn2;
+ }
+ break;
+
+#if JS_HAS_XML_SUPPORT
+ case TOK_STAR:
+ pn = QualifiedIdentifier(cx, ts, tc);
+ if (!pn)
+ return NULL;
+ notsharp = JS_TRUE;
+ break;
+
+ case TOK_AT:
+ pn = AttributeIdentifier(cx, ts, tc);
+ if (!pn)
+ return NULL;
+ notsharp = JS_TRUE;
+ break;
+
+ case TOK_XMLSTAGO:
+ pn = XMLElementOrListRoot(cx, ts, tc, JS_TRUE);
+ if (!pn)
+ return NULL;
+ notsharp = JS_TRUE; /* XXXbe could be sharp? */
+ break;
+#endif /* JS_HAS_XML_SUPPORT */
+
+ case TOK_STRING:
+#if JS_HAS_SHARP_VARS
+ notsharp = JS_TRUE;
+ /* FALL THROUGH */
+#endif
+
+#if JS_HAS_XML_SUPPORT
+ case TOK_XMLCDATA:
+ case TOK_XMLCOMMENT:
+ case TOK_XMLPI:
+#endif
+ case TOK_NAME:
+ case TOK_OBJECT:
+ pn = NewParseNode(cx, ts, PN_NULLARY, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_atom = CURRENT_TOKEN(ts).t_atom;
+#if JS_HAS_XML_SUPPORT
+ if (tt == TOK_XMLPI)
+ pn->pn_atom2 = CURRENT_TOKEN(ts).t_atom2;
+ else
+#endif
+ pn->pn_op = CURRENT_TOKEN(ts).t_op;
+ if (tt == TOK_NAME) {
+ pn->pn_arity = PN_NAME;
+ pn->pn_expr = NULL;
+ pn->pn_slot = -1;
+ pn->pn_attrs = 0;
+
+#if JS_HAS_XML_SUPPORT
+ if (js_MatchToken(cx, ts, TOK_DBLCOLON)) {
+ if (afterDot) {
+ JSString *str;
+
+ /*
+ * Here PrimaryExpr is called after '.' or '..' and we
+ * just scanned .name:: or ..name:: . This is the only
+ * case where a keyword after '.' or '..' is not
+ * treated as a property name.
+ */
+ str = ATOM_TO_STRING(pn->pn_atom);
+ tt = js_CheckKeyword(JSSTRING_CHARS(str),
+ JSSTRING_LENGTH(str));
+ if (tt == TOK_FUNCTION) {
+ pn->pn_arity = PN_NULLARY;
+ pn->pn_type = TOK_FUNCTION;
+ } else if (tt != TOK_EOF) {
+ js_ReportCompileErrorNumber(
+ cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_KEYWORD_NOT_NS);
+ return NULL;
+ }
+ }
+ pn = QualifiedSuffix(cx, ts, pn, tc);
+ if (!pn)
+ return NULL;
+ break;
+ }
+#endif
+
+ /* Unqualified __parent__ and __proto__ uses require activations. */
+ if (pn->pn_atom == cx->runtime->atomState.parentAtom ||
+ pn->pn_atom == cx->runtime->atomState.protoAtom) {
+ tc->flags |= TCF_FUN_HEAVYWEIGHT;
+ } else {
+ JSAtomListElement *ale;
+ JSStackFrame *fp;
+ JSBool loopy;
+
+ /* Measure optimizable global variable uses. */
+ ATOM_LIST_SEARCH(ale, &tc->decls, pn->pn_atom);
+ if (ale &&
+ !(fp = cx->fp)->fun &&
+ fp->scopeChain == fp->varobj &&
+ js_IsGlobalReference(tc, pn->pn_atom, &loopy)) {
+ tc->globalUses++;
+ if (loopy)
+ tc->loopyGlobalUses++;
+ }
+ }
+ }
+ break;
+
+ case TOK_NUMBER:
+ pn = NewParseNode(cx, ts, PN_NULLARY, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_dval = CURRENT_TOKEN(ts).t_dval;
+#if JS_HAS_SHARP_VARS
+ notsharp = JS_TRUE;
+#endif
+ break;
+
+ case TOK_PRIMARY:
+ pn = NewParseNode(cx, ts, PN_NULLARY, tc);
+ if (!pn)
+ return NULL;
+ pn->pn_op = CURRENT_TOKEN(ts).t_op;
+#if JS_HAS_SHARP_VARS
+ notsharp = JS_TRUE;
+#endif
+ break;
+
+#if !JS_HAS_EXPORT_IMPORT
+ case TOK_EXPORT:
+ case TOK_IMPORT:
+#endif
+ case TOK_ERROR:
+ /* The scanner or one of its subroutines reported the error. */
+ return NULL;
+
+ default:
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_SYNTAX_ERROR);
+ return NULL;
+ }
+
+#if JS_HAS_SHARP_VARS
+ if (defsharp) {
+ if (notsharp) {
+ badsharp:
+ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
+ JSMSG_BAD_SHARP_VAR_DEF);
+ return NULL;
+ }
+ defsharp->pn_kid = pn;
+ return defsharp;
+ }
+#endif
+ return pn;
+}
+
+/*
+ * Fold from one constant type to another.
+ * XXX handles only strings and numbers for now
+ */
+static JSBool
+FoldType(JSContext *cx, JSParseNode *pn, JSTokenType type)
+{
+ if (pn->pn_type != type) {
+ switch (type) {
+ case TOK_NUMBER:
+ if (pn->pn_type == TOK_STRING) {
+ jsdouble d;
+ if (!js_ValueToNumber(cx, ATOM_KEY(pn->pn_atom), &d))
+ return JS_FALSE;
+ pn->pn_dval = d;
+ pn->pn_type = TOK_NUMBER;
+ pn->pn_op = JSOP_NUMBER;
+ }
+ break;
+
+ case TOK_STRING:
+ if (pn->pn_type == TOK_NUMBER) {
+ JSString *str = js_NumberToString(cx, pn->pn_dval);
+ if (!str)
+ return JS_FALSE;
+ pn->pn_atom = js_AtomizeString(cx, str, 0);
+ if (!pn->pn_atom)
+ return JS_FALSE;
+ pn->pn_type = TOK_STRING;
+ pn->pn_op = JSOP_STRING;
+ }
+ break;
+
+ default:;
+ }
+ }
+ return JS_TRUE;
+}
+
+/*
+ * Fold two numeric constants. Beware that pn1 and pn2 are recycled, unless
+ * one of them aliases pn, so you can't safely fetch pn2->pn_next, e.g., after
+ * a successful call to this function.
+ */
+static JSBool
+FoldBinaryNumeric(JSContext *cx, JSOp op, JSParseNode *pn1, JSParseNode *pn2,
+ JSParseNode *pn, JSTreeContext *tc)
+{
+ jsdouble d, d2;
+ int32 i, j;
+ uint32 u;
+
+ JS_ASSERT(pn1->pn_type == TOK_NUMBER && pn2->pn_type == TOK_NUMBER);
+ d = pn1->pn_dval;
+ d2 = pn2->pn_dval;
+ switch (op) {
+ case JSOP_LSH:
+ case JSOP_RSH:
+ if (!js_DoubleToECMAInt32(cx, d, &i))
+ return JS_FALSE;
+ if (!js_DoubleToECMAInt32(cx, d2, &j))
+ return JS_FALSE;
+ j &= 31;
+ d = (op == JSOP_LSH) ? i << j : i >> j;
+ break;
+
+ case JSOP_URSH:
+ if (!js_DoubleToECMAUint32(cx, d, &u))
+ return JS_FALSE;
+ if (!js_DoubleToECMAInt32(cx, d2, &j))
+ return JS_FALSE;
+ j &= 31;
+ d = u >> j;
+ break;
+
+ case JSOP_ADD:
+ d += d2;
+ break;
+
+ case JSOP_SUB:
+ d -= d2;
+ break;
+
+ case JSOP_MUL:
+ d *= d2;
+ break;
+
+ case JSOP_DIV:
+ if (d2 == 0) {
+#if defined(XP_WIN)
+ /* XXX MSVC miscompiles such that (NaN == 0) */
+ if (JSDOUBLE_IS_NaN(d2))
+ d = *cx->runtime->jsNaN;
+ else
+#endif
+ if (d == 0 || JSDOUBLE_IS_NaN(d))
+ d = *cx->runtime->jsNaN;
+ else if ((JSDOUBLE_HI32(d) ^ JSDOUBLE_HI32(d2)) >> 31)
+ d = *cx->runtime->jsNegativeInfinity;
+ else
+ d = *cx->runtime->jsPositiveInfinity;
+ } else {
+ d /= d2;
+ }
+ break;
+
+ case JSOP_MOD:
+ if (d2 == 0) {
+ d = *cx->runtime->jsNaN;
+ } else {
+#if defined(XP_WIN)
+ /* Workaround MS fmod bug where 42 % (1/0) => NaN, not 42. */
+ if (!(JSDOUBLE_IS_FINITE(d) && JSDOUBLE_IS_INFINITE(d2)))
+#endif
+ d = fmod(d, d2);
+ }
+ break;
+
+ default:;
+ }
+
+ /* Take care to allow pn1 or pn2 to alias pn. */
+ if (pn1 != pn)
+ RecycleTree(pn1, tc);
+ if (pn2 != pn)
+ RecycleTree(pn2, tc);
+ pn->pn_type = TOK_NUMBER;
+ pn->pn_op = JSOP_NUMBER;
+ pn->pn_arity = PN_NULLARY;
+ pn->pn_dval = d;
+ return JS_TRUE;
+}
+
+#if JS_HAS_XML_SUPPORT
+
+static JSBool
+FoldXMLConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc)
+{
+ JSTokenType tt;
+ JSParseNode **pnp, *pn1, *pn2;
+ JSString *accum, *str;
+ uint32 i, j;
+
+ JS_ASSERT(pn->pn_arity == PN_LIST);
+ tt = pn->pn_type;
+ pnp = &pn->pn_head;
+ pn1 = *pnp;
+ accum = NULL;
+ if ((pn->pn_extra & PNX_CANTFOLD) == 0) {
+ if (tt == TOK_XMLETAGO)
+ accum = ATOM_TO_STRING(cx->runtime->atomState.etagoAtom);
+ else if (tt == TOK_XMLSTAGO || tt == TOK_XMLPTAGC)
+ accum = ATOM_TO_STRING(cx->runtime->atomState.stagoAtom);
+ }
+
+ for (pn2 = pn1, i = j = 0; pn2; pn2 = pn2->pn_next, i++) {
+ /* The parser already rejected end-tags with attributes. */
+ JS_ASSERT(tt != TOK_XMLETAGO || i == 0);
+ switch (pn2->pn_type) {
+ case TOK_XMLATTR:
+ if (!accum)
+ goto cantfold;
+ /* FALL THROUGH */
+ case TOK_XMLNAME:
+ case TOK_XMLSPACE:
+ case TOK_XMLTEXT:
+ case TOK_STRING:
+ if (pn2->pn_arity == PN_LIST)
+ goto cantfold;
+ str = ATOM_TO_STRING(pn2->pn_atom);
+ break;
+
+ case TOK_XMLCDATA:
+ str = js_MakeXMLCDATAString(cx, ATOM_TO_STRING(pn2->pn_atom));
+ if (!str)
+ return JS_FALSE;
+ break;
+
+ case TOK_XMLCOMMENT:
+ str = js_MakeXMLCommentString(cx, ATOM_TO_STRING(pn2->pn_atom));
+ if (!str)
+ return JS_FALSE;
+ break;
+
+ case TOK_XMLPI:
+ str = js_MakeXMLPIString(cx, ATOM_TO_STRING(pn2->pn_atom),
+ ATOM_TO_STRING(pn2->pn_atom2));
+ if (!str)
+ return JS_FALSE;
+ break;
+
+ cantfold:
+ default:
+ JS_ASSERT(*pnp == pn1);
+ if ((tt == TOK_XMLSTAGO || tt == TOK_XMLPTAGC) &&
+ (i & 1) ^ (j & 1)) {
+#ifdef DEBUG_brendanXXX
+ printf("1: %d, %d => %s\n",
+ i, j, accum ? JS_GetStringBytes(accum) : "NULL");
+#endif
+ } else if (accum && pn1 != pn2) {
+ while (pn1->pn_next != pn2) {
+ pn1 = RecycleTree(pn1, tc);
+ --pn->pn_count;
+ }
+ pn1->pn_type = TOK_XMLTEXT;
+ pn1->pn_op = JSOP_STRING;
+ pn1->pn_arity = PN_NULLARY;
+ pn1->pn_atom = js_AtomizeString(cx, accum, 0);
+ if (!pn1->pn_atom)
+ return JS_FALSE;
+ JS_ASSERT(pnp != &pn1->pn_next);
+ *pnp = pn1;
+ }
+ pnp = &pn2->pn_next;
+ pn1 = *pnp;
+ accum = NULL;
+ continue;
+ }
+
+ if (accum) {
+ str = ((tt == TOK_XMLSTAGO || tt == TOK_XMLPTAGC) && i != 0)
+ ? js_AddAttributePart(cx, i & 1, accum, str)
+ : js_ConcatStrings(cx, accum, str);
+ if (!str)
+ return JS_FALSE;
+#ifdef DEBUG_brendanXXX
+ printf("2: %d, %d => %s (%u)\n",
+ i, j, JS_GetStringBytes(str), JSSTRING_LENGTH(str));
+#endif
+ ++j;
+ }
+ accum = str;
+ }
+
+ if (accum) {
+ str = NULL;
+ if ((pn->pn_extra & PNX_CANTFOLD) == 0) {
+ if (tt == TOK_XMLPTAGC)
+ str = ATOM_TO_STRING(cx->runtime->atomState.ptagcAtom);
+ else if (tt == TOK_XMLSTAGO || tt == TOK_XMLETAGO)
+ str = ATOM_TO_STRING(cx->runtime->atomState.tagcAtom);
+ }
+ if (str) {
+ accum = js_ConcatStrings(cx, accum, str);
+ if (!accum)
+ return JS_FALSE;
+ }
+
+ JS_ASSERT(*pnp == pn1);
+ while (pn1->pn_next) {
+ pn1 = RecycleTree(pn1, tc);
+ --pn->pn_count;
+ }
+ pn1->pn_type = TOK_XMLTEXT;
+ pn1->pn_op = JSOP_STRING;
+ pn1->pn_arity = PN_NULLARY;
+ pn1->pn_atom = js_AtomizeString(cx, accum, 0);
+ if (!pn1->pn_atom)
+ return JS_FALSE;
+ JS_ASSERT(pnp != &pn1->pn_next);
+ *pnp = pn1;
+ }
+
+ if (pn1 && pn->pn_count == 1) {
+ /*
+ * Only one node under pn, and it has been folded: move pn1 onto pn
+ * unless pn is an XML root (in which case we need it to tell the code
+ * generator to emit a JSOP_TOXML or JSOP_TOXMLLIST op). If pn is an
+ * XML root *and* it's a point-tag, rewrite it to TOK_XMLELEM to avoid
+ * extra "<" and "/>" bracketing at runtime.
+ */
+ if (!(pn->pn_extra & PNX_XMLROOT)) {
+ PN_MOVE_NODE(pn, pn1);
+ } else if (tt == TOK_XMLPTAGC) {
+ pn->pn_type = TOK_XMLELEM;
+ pn->pn_op = JSOP_TOXML;
+ }
+ }
+ return JS_TRUE;
+}
+
+#endif /* JS_HAS_XML_SUPPORT */
+
+static JSBool
+StartsWith(JSParseNode *pn, JSTokenType tt)
+{
+#define TAIL_RECURSE(pn2) JS_BEGIN_MACRO pn = (pn2); goto recur; JS_END_MACRO
+
+recur:
+ if (pn->pn_type == tt)
+ return JS_TRUE;
+ switch (pn->pn_arity) {
+ case PN_FUNC:
+ return tt == TOK_FUNCTION;
+ case PN_LIST:
+ if (pn->pn_head)
+ TAIL_RECURSE(pn->pn_head);
+ break;
+ case PN_TERNARY:
+ if (pn->pn_kid1)
+ TAIL_RECURSE(pn->pn_kid1);
+ break;
+ case PN_BINARY:
+ if (pn->pn_left)
+ TAIL_RECURSE(pn->pn_left);
+ break;
+ case PN_UNARY:
+ /* A parenthesized expression starts with a left parenthesis. */
+ if (pn->pn_type == TOK_RP)
+ return tt == TOK_LP;
+ if (pn->pn_kid)
+ TAIL_RECURSE(pn->pn_kid);
+ break;
+ case PN_NAME:
+ if (pn->pn_type == TOK_DOT || pn->pn_type == TOK_DBLDOT)
+ TAIL_RECURSE(pn->pn_expr);
+ /* FALL THROUGH */
+ }
+ return JS_FALSE;
+#undef TAIL_RECURSE
+}
+
+JSBool
+js_FoldConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc)
+{
+ JSParseNode *pn1 = NULL, *pn2 = NULL, *pn3 = NULL;
+ int stackDummy;
+
+ if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) {
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED);
+ return JS_FALSE;
+ }
+
+ switch (pn->pn_arity) {
+ case PN_FUNC:
+ {
+ uint16 oldflags = tc->flags;
+
+ tc->flags = (uint16) pn->pn_flags;
+ if (!js_FoldConstants(cx, pn->pn_body, tc))
+ return JS_FALSE;
+ tc->flags = oldflags;
+ break;
+ }
+
+ case PN_LIST:
+#if 0 /* JS_HAS_XML_SUPPORT */
+ switch (pn->pn_type) {
+ case TOK_XMLELEM:
+ case TOK_XMLLIST:
+ case TOK_XMLPTAGC:
+ /*
+ * Try to fold this XML parse tree once, from the top down, into
+ * a JSXML tree with just one object wrapping the tree root.
+ *
+ * Certain subtrees could be folded similarly, but we'd have to
+ * ensure that none used namespace prefixes declared elsewhere in
+ * its super-tree, and we would have to convert each XML object
+ * created at runtime for such sub-trees back into a string, and
+ * concatenate and re-parse anyway.
+ */
+ if ((pn->pn_extra & (PNX_XMLROOT | PNX_CANTFOLD)) == PNX_XMLROOT &&
+ !(tc->flags & TCF_HAS_DEFXMLNS)) {
+ JSObject *obj;
+ JSAtom *atom;
+
+ obj = js_ParseNodeToXMLObject(cx, pn);
+ if (!obj)
+ return JS_FALSE;
+ atom = js_AtomizeObject(cx, obj, 0);
+ if (!atom)
+ return JS_FALSE;
+ pn->pn_op = JSOP_XMLOBJECT;
+ pn->pn_arity = PN_NULLARY;
+ pn->pn_atom = atom;
+ return JS_TRUE;
+ }
+
+ /*
+ * Can't fold from parse node to XML tree -- try folding strings
+ * as much as possible, and folding XML sub-trees bottom up to
+ * minimize string concatenation and ToXML/ToXMLList operations
+ * at runtime.
+ */
+ break;
+
+ default:;
+ }
+#endif
+
+ /* Save the list head in pn1 for later use. */
+ for (pn1 = pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
+ if (!js_FoldConstants(cx, pn2, tc))
+ return JS_FALSE;
+ }
+ break;
+
+ case PN_TERNARY:
+ /* Any kid may be null (e.g. for (;;)). */
+ pn1 = pn->pn_kid1;
+ pn2 = pn->pn_kid2;
+ pn3 = pn->pn_kid3;
+ if (pn1 && !js_FoldConstants(cx, pn1, tc))
+ return JS_FALSE;
+ if (pn2 && !js_FoldConstants(cx, pn2, tc))
+ return JS_FALSE;
+ if (pn3 && !js_FoldConstants(cx, pn3, tc))
+ return JS_FALSE;
+ break;
+
+ case PN_BINARY:
+ /* First kid may be null (for default case in switch). */
+ pn1 = pn->pn_left;
+ pn2 = pn->pn_right;
+ if (pn1 && !js_FoldConstants(cx, pn1, tc))
+ return JS_FALSE;
+ if (!js_FoldConstants(cx, pn2, tc))
+ return JS_FALSE;
+ break;
+
+ case PN_UNARY:
+ /* Our kid may be null (e.g. return; vs. return e;). */
+ pn1 = pn->pn_kid;
+ if (pn1 && !js_FoldConstants(cx, pn1, tc))
+ return JS_FALSE;
+ break;
+
+ case PN_NAME:
+ /*
+ * Skip pn1 down along a chain of dotted member expressions to avoid
+ * excessive recursion. Our only goal here is to fold constants (if
+ * any) in the primary expression operand to the left of the first
+ * dot in the chain.
+ */
+ pn1 = pn->pn_expr;
+ while (pn1 && pn1->pn_arity == PN_NAME)
+ pn1 = pn1->pn_expr;
+ if (pn1 && !js_FoldConstants(cx, pn1, tc))
+ return JS_FALSE;
+ break;
+
+ case PN_NULLARY:
+ break;
+ }
+
+ switch (pn->pn_type) {
+ case TOK_IF:
+ if (ContainsStmt(pn2, TOK_VAR) || ContainsStmt(pn3, TOK_VAR))
+ break;
+ /* FALL THROUGH */
+
+ case TOK_HOOK:
+ /* Reduce 'if (C) T; else E' into T for true C, E for false. */
+ while (pn1->pn_type == TOK_RP)
+ pn1 = pn1->pn_kid;
+ switch (pn1->pn_type) {
+ case TOK_NUMBER:
+ if (pn1->pn_dval == 0)
+ pn2 = pn3;
+ break;
+ case TOK_STRING:
+ if (JSSTRING_LENGTH(ATOM_TO_STRING(pn1->pn_atom)) == 0)
+ pn2 = pn3;
+ break;
+ case TOK_PRIMARY:
+ if (pn1->pn_op == JSOP_TRUE)
+ break;
+ if (pn1->pn_op == JSOP_FALSE || pn1->pn_op == JSOP_NULL) {
+ pn2 = pn3;
+ break;
+ }
+ /* FALL THROUGH */
+ default:
+ /* Early return to dodge common code that copies pn2 to pn. */
+ return JS_TRUE;
+ }
+
+ if (pn2) {
+ /*
+ * pn2 is the then- or else-statement subtree to compile. Take
+ * care not to expose an object initialiser, which would be parsed
+ * as a block, to the Statement parser via eval(uneval(e)) where e
+ * is '1 ? {p:2, q:3}[i] : r;' or the like.
+ */
+ if (pn->pn_type == TOK_HOOK && StartsWith(pn2, TOK_RC)) {
+ pn->pn_type = TOK_RP;
+ pn->pn_arity = PN_UNARY;
+ pn->pn_kid = pn2;
+ } else {
+ PN_MOVE_NODE(pn, pn2);
+ }
+ }
+ if (!pn2 || (pn->pn_type == TOK_SEMI && !pn->pn_kid)) {
+ /*
+ * False condition and no else, or an empty then-statement was
+ * moved up over pn. Either way, make pn an empty block (not an
+ * empty statement, which does not decompile, even when labeled).
+ * NB: pn must be a TOK_IF as TOK_HOOK can never have a null kid
+ * or an empty statement for a child.
+ */
+ pn->pn_type = TOK_LC;
+ pn->pn_arity = PN_LIST;
+ PN_INIT_LIST(pn);
+ }
+ RecycleTree(pn2, tc);
+ if (pn3 && pn3 != pn2)
+ RecycleTree(pn3, tc);
+ break;
+
+ case TOK_ASSIGN:
+ /*
+ * Compound operators such as *= should be subject to folding, in case
+ * the left-hand side is constant, and so that the decompiler produces
+ * the same string that you get from decompiling a script or function
+ * compiled from that same string. As with +, += is special.
+ */
+ if (pn->pn_op == JSOP_NOP)
+ break;
+ if (pn->pn_op != JSOP_ADD)
+ goto do_binary_op;
+ /* FALL THROUGH */
+
+ case TOK_PLUS:
+ if (pn->pn_arity == PN_LIST) {
+ size_t length, length2;
+ jschar *chars;
+ JSString *str, *str2;
+
+ /*
+ * Any string literal term with all others number or string means
+ * this is a concatenation. If any term is not a string or number
+ * literal, we can't fold.
+ */
+ JS_ASSERT(pn->pn_count > 2);
+ if (pn->pn_extra & PNX_CANTFOLD)
+ return JS_TRUE;
+ if (pn->pn_extra != PNX_STRCAT)
+ goto do_binary_op;
+
+ /* Ok, we're concatenating: convert non-string constant operands. */
+ length = 0;
+ for (pn2 = pn1; pn2; pn2 = pn2->pn_next) {
+ if (!FoldType(cx, pn2, TOK_STRING))
+ return JS_FALSE;
+ /* XXX fold only if all operands convert to string */
+ if (pn2->pn_type != TOK_STRING)
+ return JS_TRUE;
+ length += ATOM_TO_STRING(pn2->pn_atom)->length;
+ }
+
+ /* Allocate a new buffer and string descriptor for the result. */
+ chars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar));
+ if (!chars)
+ return JS_FALSE;
+ str = js_NewString(cx, chars, length, 0);
+ if (!str) {
+ JS_free(cx, chars);
+ return JS_FALSE;
+ }
+
+ /* Fill the buffer, advancing chars and recycling kids as we go. */
+ for (pn2 = pn1; pn2; pn2 = RecycleTree(pn2, tc)) {
+ str2 = ATOM_TO_STRING(pn2->pn_atom);
+ length2 = str2->length;
+ js_strncpy(chars, str2->chars, length2);
+ chars += length2;
+ }
+ *chars = 0;
+
+ /* Atomize the result string and mutate pn to refer to it. */
+ pn->pn_atom = js_AtomizeString(cx, str, 0);
+ if (!pn->pn_atom)
+ return JS_FALSE;
+ pn->pn_type = TOK_STRING;
+ pn->pn_op = JSOP_STRING;
+ pn->pn_arity = PN_NULLARY;
+ break;
+ }
+
+ /* Handle a binary string concatenation. */
+ JS_ASSERT(pn->pn_arity == PN_BINARY);
+ if (pn1->pn_type == TOK_STRING || pn2->pn_type == TOK_STRING) {
+ JSString *left, *right, *str;
+
+ if (!FoldType(cx, (pn1->pn_type != TOK_STRING) ? pn1 : pn2,
+ TOK_STRING)) {
+ return JS_FALSE;
+ }
+ if (pn1->pn_type != TOK_STRING || pn2->pn_type != TOK_STRING)
+ return JS_TRUE;
+ left = ATOM_TO_STRING(pn1->pn_atom);
+ right = ATOM_TO_STRING(pn2->pn_atom);
+ str = js_ConcatStrings(cx, left, right);
+ if (!str)
+ return JS_FALSE;
+ pn->pn_atom = js_AtomizeString(cx, str, 0);
+ if (!pn->pn_atom)
+ return JS_FALSE;
+ pn->pn_type = TOK_STRING;
+ pn->pn_op = JSOP_STRING;
+ pn->pn_arity = PN_NULLARY;
+ RecycleTree(pn1, tc);
+ RecycleTree(pn2, tc);
+ break;
+ }
+
+ /* Can't concatenate string literals, let's try numbers. */
+ goto do_binary_op;
+
+ case TOK_STAR:
+ /* The * in 'import *;' parses as a nullary star node. */
+ if (pn->pn_arity == PN_NULLARY)
+ break;
+ /* FALL THROUGH */
+
+ case TOK_SHOP:
+ case TOK_MINUS:
+ case TOK_DIVOP:
+ do_binary_op:
+ if (pn->pn_arity == PN_LIST) {
+ JS_ASSERT(pn->pn_count > 2);
+ for (pn2 = pn1; pn2; pn2 = pn2->pn_next) {
+ if (!FoldType(cx, pn2, TOK_NUMBER))
+ return JS_FALSE;
+ }
+ for (pn2 = pn1; pn2; pn2 = pn2->pn_next) {
+ /* XXX fold only if all operands convert to number */
+ if (pn2->pn_type != TOK_NUMBER)
+ break;
+ }
+ if (!pn2) {
+ JSOp op = pn->pn_op;
+
+ pn2 = pn1->pn_next;
+ pn3 = pn2->pn_next;
+ if (!FoldBinaryNumeric(cx, op, pn1, pn2, pn, tc))
+ return JS_FALSE;
+ while ((pn2 = pn3) != NULL) {
+ pn3 = pn2->pn_next;
+ if (!FoldBinaryNumeric(cx, op, pn, pn2, pn, tc))
+ return JS_FALSE;
+ }
+ }
+ } else {
+ JS_ASSERT(pn->pn_arity == PN_BINARY);
+ if (!FoldType(cx, pn1, TOK_NUMBER) ||
+ !FoldType(cx, pn2, TOK_NUMBER)) {
+ return JS_FALSE;
+ }
+ if (pn1->pn_type == TOK_NUMBER && pn2->pn_type == TOK_NUMBER) {
+ if (!FoldBinaryNumeric(cx, pn->pn_op, pn1, pn2, pn, tc))
+ return JS_FALSE;
+ }
+ }
+ break;
+
+ case TOK_UNARYOP:
+ while (pn1->pn_type == TOK_RP)
+ pn1 = pn1->pn_kid;
+ if (pn1->pn_type == TOK_NUMBER) {
+ jsdouble d;
+ int32 i;
+
+ /* Operate on one numeric constant. */
+ d = pn1->pn_dval;
+ switch (pn->pn_op) {
+ case JSOP_BITNOT:
+ if (!js_DoubleToECMAInt32(cx, d, &i))
+ return JS_FALSE;
+ d = ~i;
+ break;
+
+ case JSOP_NEG:
+#ifdef HPUX
+ /*
+ * Negation of a zero doesn't produce a negative
+ * zero on HPUX. Perform the operation by bit
+ * twiddling.
+ */
+ JSDOUBLE_HI32(d) ^= JSDOUBLE_HI32_SIGNBIT;
+#else
+ d = -d;
+#endif
+ break;
+
+ case JSOP_POS:
+ break;
+
+ case JSOP_NOT:
+ pn->pn_type = TOK_PRIMARY;
+ pn->pn_op = (d == 0) ? JSOP_TRUE : JSOP_FALSE;
+ pn->pn_arity = PN_NULLARY;
+ /* FALL THROUGH */
+
+ default:
+ /* Return early to dodge the common TOK_NUMBER code. */
+ return JS_TRUE;
+ }
+ pn->pn_type = TOK_NUMBER;
+ pn->pn_op = JSOP_NUMBER;
+ pn->pn_arity = PN_NULLARY;
+ pn->pn_dval = d;
+ RecycleTree(pn1, tc);
+ }
+ break;
+
+#if JS_HAS_XML_SUPPORT
+ case TOK_XMLELEM:
+ case TOK_XMLLIST:
+ case TOK_XMLPTAGC:
+ case TOK_XMLSTAGO:
+ case TOK_XMLETAGO:
+ case TOK_XMLNAME:
+ if (pn->pn_arity == PN_LIST) {
+ JS_ASSERT(pn->pn_type == TOK_XMLLIST || pn->pn_count != 0);
+ if (!FoldXMLConstants(cx, pn, tc))
+ return JS_FALSE;
+ }
+ break;
+
+ case TOK_AT:
+ if (pn1->pn_type == TOK_XMLNAME) {
+ jsval v;
+ JSAtom *atom;
+
+ v = ATOM_KEY(pn1->pn_atom);
+ if (!js_ToAttributeName(cx, &v))
+ return JS_FALSE;
+ JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
+ atom = js_AtomizeObject(cx, JSVAL_TO_OBJECT(v), 0);
+ if (!atom)
+ return JS_FALSE;
+
+ pn->pn_type = TOK_XMLNAME;
+ pn->pn_op = JSOP_OBJECT;
+ pn->pn_arity = PN_NULLARY;
+ pn->pn_atom = atom;
+ RecycleTree(pn1, tc);
+ }
+ break;
+#endif /* JS_HAS_XML_SUPPORT */
+
+ default:;
+ }
+
+ return JS_TRUE;
+}