diff options
| author | MenTaLguY <mental@rydia.net> | 2006-01-16 02:36:01 +0000 |
|---|---|---|
| committer | mental <mental@users.sourceforge.net> | 2006-01-16 02:36:01 +0000 |
| commit | 179fa413b047bede6e32109e2ce82437c5fb8d34 (patch) | |
| tree | a5a6ac2c1708bd02288fbd8edb2ff500ff2e0916 /src/extension/script/js/jsparse.c | |
| download | inkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.tar.gz inkscape-179fa413b047bede6e32109e2ce82437c5fb8d34.zip | |
moving trunk for module inkscape
(bzr r1)
Diffstat (limited to 'src/extension/script/js/jsparse.c')
| -rw-r--r-- | src/extension/script/js/jsparse.c | 3547 |
1 files changed, 3547 insertions, 0 deletions
diff --git a/src/extension/script/js/jsparse.c b/src/extension/script/js/jsparse.c new file mode 100644 index 000000000..7cf18d454 --- /dev/null +++ b/src/extension/script/js/jsparse.c @@ -0,0 +1,3547 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** 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. The dense JSTokenType enumeration + * was designed with error recovery built on 64-bit first and follow bitsets + * in mind, however. + */ +#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 "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" + +/* + * JS parsers, from lowest to highest precedence. + * + * Each parser takes a context and a token stream, and emits bytecode using + * a code generator. + */ + +typedef JSParseNode * +JSParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc); + +typedef JSParseNode * +JSMemberParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, + JSBool allowCallSyntax); + +static JSParser FunctionStmt; +#if JS_HAS_LEXICAL_CLOSURE +static JSParser FunctionExpr; +#endif +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 JSParser 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, NULL, 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, NULL, 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 void +RecycleTree(JSParseNode *pn, JSTreeContext *tc) +{ + if (!pn) + return; + JS_ASSERT(pn != tc->nodeList); /* catch back-to-back dup recycles */ + pn->pn_next = tc->nodeList; + tc->nodeList = pn; +#ifdef METER_PARSENODES + recyclednodes++; +#endif +} + +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; + } + } + return pn; +} + +/* + * Allocate a JSParseNode from cx's temporary arena. + */ +static JSParseNode * +NewParseNode(JSContext *cx, JSToken *tok, JSParseNodeArity arity, + JSTreeContext *tc) +{ + JSParseNode *pn; + + pn = NewOrRecycledNode(cx, tc); + if (!pn) + return NULL; + pn->pn_type = tok->type; + pn->pn_pos = tok->pos; + pn->pn_op = JSOP_NOP; + pn->pn_arity = arity; + pn->pn_next = NULL; +#ifdef METER_PARSENODES + parsenodes++; + if (parsenodes - recyclednodes > maxparsenodes) + maxparsenodes = parsenodes - recyclednodes; +#endif + 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); + left->pn_extra = 0; + 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; + 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; +#ifdef METER_PARSENODES + parsenodes++; + if (parsenodes - recyclednodes > maxparsenodes) + maxparsenodes = parsenodes - recyclednodes; +#endif + 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, NULL, 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; + name = js_AtomToPrintableString(cx, atom); + if (!name || + !js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_DEPRECATED_USAGE, + name)) { + return TOK_ERROR; + } + return tt; +} +#endif + +/* + * 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; + if (!fp || !fp->varobj || fp->scopeChain != chain) { + memset(&frame, 0, sizeof frame); + frame.varobj = frame.scopeChain = chain; + if (cx->options & JSOPTION_VAROBJFIX) { + while ((chain = JS_GetParent(cx, chain)) != NULL) + frame.varobj = chain; + } + frame.down = fp; + cx->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_AllocGCThing), 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, NULL, 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; + 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; + if (!fp || !fp->varobj || fp->scopeChain != chain) { + memset(&frame, 0, sizeof frame); + frame.varobj = frame.scopeChain = chain; + if (cx->options & JSOPTION_VAROBJFIX) { + while ((chain = JS_GetParent(cx, chain)) != NULL) + frame.varobj = chain; + } + frame.down = fp; + cx->fp = &frame; + } + + /* 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, NULL, 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 code 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. + */ + JS_ASSERT(cg->treeContext.flags & TCF_COMPILING); + ok = JS_TRUE; + } + +#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 = fp; + return ok; +} + +/* + * Insist on a final return before control flows out of pn, but don't be too + * smart about loops (do {...; return e2;} while(0) at the end of a function + * that contains an early return e1 will get a strict-option-only warning). + */ +static JSBool +HasFinalReturn(JSParseNode *pn) +{ + JSBool ok, hasDefault; + JSParseNode *pn2, *pn3; + + switch (pn->pn_type) { + case TOK_LC: + if (!pn->pn_head) + return JS_FALSE; + return HasFinalReturn(PN_LAST(pn)); + + case TOK_IF: + ok = HasFinalReturn(pn->pn_kid2); + ok &= pn->pn_kid3 && HasFinalReturn(pn->pn_kid3); + return ok; + +#if JS_HAS_SWITCH_STATEMENT + case TOK_SWITCH: + ok = JS_TRUE; + hasDefault = JS_FALSE; + for (pn2 = pn->pn_kid2->pn_head; ok && pn2; pn2 = pn2->pn_next) { + if (pn2->pn_type == TOK_DEFAULT) + hasDefault = JS_TRUE; + pn3 = pn2->pn_right; + JS_ASSERT(pn3->pn_type == TOK_LC); + if (pn3->pn_head) + ok &= HasFinalReturn(PN_LAST(pn3)); + } + /* If a final switch has no default case, we judge it harshly. */ + ok &= hasDefault; + return ok; +#endif /* JS_HAS_SWITCH_STATEMENT */ + + case TOK_WITH: + return HasFinalReturn(pn->pn_right); + + case TOK_RETURN: + return JS_TRUE; + +#if JS_HAS_EXCEPTIONS + case TOK_THROW: + return JS_TRUE; + + case TOK_TRY: + /* If we have a finally block that returns, we are done. */ + if (pn->pn_kid3 && HasFinalReturn(pn->pn_kid3)) + return JS_TRUE; + + /* Else check the try block and any and all catch statements. */ + ok = HasFinalReturn(pn->pn_kid1); + if (pn->pn_kid2) + ok &= HasFinalReturn(pn->pn_kid2); + return ok; + + case TOK_CATCH: + /* Check this block's code and iterate over further catch blocks. */ + ok = HasFinalReturn(pn->pn_kid3); + for (pn2 = pn->pn_kid2; pn2; pn2 = pn2->pn_kid2) + ok &= HasFinalReturn(pn2->pn_kid3); + return ok; +#endif + + default: + return JS_FALSE; + } +} + +static JSBool +ReportNoReturnValue(JSContext *cx, JSTokenStream *ts) +{ + JSFunction *fun; + JSBool ok; + + fun = cx->fp->fun; + if (fun->atom) { + char *name = js_GetStringBytes(ATOM_TO_STRING(fun->atom)); + ok = js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_NO_RETURN_VALUE, name); + } else { + ok = js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_ANON_NO_RETURN_VALUE); + } + return ok; +} + +static JSBool +CheckFinalReturn(JSContext *cx, JSTokenStream *ts, JSParseNode *pn) +{ + return HasFinalReturn(pn) || ReportNoReturnValue(cx, ts); +} + +static JSParseNode * +FunctionBody(JSContext *cx, JSTokenStream *ts, JSFunction *fun, + JSTreeContext *tc) +{ + JSStackFrame *fp, frame; + JSObject *funobj; + uintN oldflags; + 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; + cx->fp = &frame; + } + + oldflags = tc->flags; + tc->flags &= ~(TCF_RETURN_EXPR | TCF_RETURN_VOID); + tc->flags |= TCF_IN_FUNCTION; + pn = Statements(cx, ts, 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; + } + + cx->fp = fp; + tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS); + 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; + JSBool ok; + + JS_InitArenaPool(&codePool, "code", 1024, sizeof(jsbytecode)); + JS_InitArenaPool(¬ePool, "note", 1024, sizeof(jssrcnote)); + if (!js_InitCodeGenerator(cx, &funcg, &codePool, ¬ePool, + 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 and js_EmitFunctionBody. */ + 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; + cx->fp = &frame; + + /* Ensure that the body looks like a block statement to js_EmitTree. */ + CURRENT_TOKEN(ts).type = TOK_LC; + pn = FunctionBody(cx, ts, fun, &funcg.treeContext); + if (!pn) { + ok = JS_FALSE; + } else { + /* + * No need to emit code here -- Statements (via FunctionBody) already + * has. See similar comment in js_CompileTokenStream, and bug 108257. + */ + fun->script = js_NewScriptFromCG(cx, &funcg, fun); + if (!fun->script) { + ok = JS_FALSE; + } else { + if (funcg.treeContext.flags & TCF_FUN_HEAVYWEIGHT) + fun->flags |= JSFUN_HEAVYWEIGHT; + ok = JS_TRUE; + } + } + + /* 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(¬ePool); + return ok; +} + +static JSParseNode * +FunctionDef(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, + JSBool lambda) +{ + JSParseNode *pn, *body; + JSOp op, prevop; + JSAtom *funAtom, *argAtom; + JSFunction *fun; + JSObject *parent; + JSObject *pobj; + JSScopeProperty *sprop; + uintN dupflag; + JSBool ok; + JSTreeContext funtc; + JSAtomListElement *ale; + + /* Make a TOK_FUNCTION node. */ + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_FUNC, tc); + if (!pn) + return NULL; +#if JS_HAS_GETTER_SETTER + op = CURRENT_TOKEN(ts).t_op; +#endif + + /* Scan the optional function name into funAtom. */ + if (js_MatchToken(cx, ts, TOK_NAME)) + funAtom = CURRENT_TOKEN(ts).t_atom; + else + funAtom = NULL; + + /* Find the nearest variable-declaring scope and use it as our parent. */ + parent = cx->fp->varobj; + fun = js_NewFunction(cx, NULL, NULL, 0, lambda ? JSFUN_LAMBDA : 0, parent, + funAtom); + if (!fun) + return NULL; + +#if JS_HAS_GETTER_SETTER + if (op != JSOP_NOP) + fun->flags |= (op == JSOP_GETTER) ? JSPROP_GETTER : JSPROP_SETTER; +#endif + + /* 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)) { + do { + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_MISSING_FORMAL); + argAtom = CURRENT_TOKEN(ts).t_atom; + pobj = NULL; + if (!js_LookupProperty(cx, fun->object, (jsid)argAtom, &pobj, + (JSProperty **)&sprop)) { + return NULL; + } + dupflag = 0; + if (sprop) { + ok = JS_TRUE; + if (pobj == fun->object && + sprop->getter == js_GetArgument) { + const char *name = js_AtomToPrintableString(cx, argAtom); + + /* + * A duplicate parameter name. 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, ts, NULL, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_DUPLICATE_FORMAL, + name); + + dupflag = SPROP_IS_DUPLICATE; + } + OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); + if (!ok) + return NULL; + sprop = NULL; + } + if (!js_AddNativeProperty(cx, fun->object, (jsid)argAtom, + js_GetArgument, js_SetArgument, + SPROP_INVALID_SLOT, + JSPROP_ENUMERATE | JSPROP_PERMANENT | + JSPROP_SHARED, + SPROP_HAS_SHORTID | dupflag, + fun->nargs)) { + return NULL; + } + fun->nargs++; + } 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; + + TREE_CONTEXT_INIT(&funtc); + body = FunctionBody(cx, ts, fun, &funtc); + 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_LEXICAL_CLOSURE + /* + * 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), or if it refers to unqualified names that + * are not local args or vars (TCF_FUN_USES_NONLOCALS), then our + * enclosing function, if any, must be heavyweight. + */ + if ((!lambda && funAtom && tc->topStmt) || + (funtc.flags & TCF_FUN_USES_NONLOCALS)) { + tc->flags |= TCF_FUN_HEAVYWEIGHT; + } + } +#endif + + /* + * 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, NULL, + (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 NULL; + } + } + if (tc->topStmt && 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, tc->topStmt ? JSOP_CLOSURE : JSOP_DEFFUN); + +#if JS_HAS_LEXICAL_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 LookupArgOrVar can optimize a JSOP_NAME into a + * JSOP_GETVAR bytecode). + */ + if (!tc->topStmt && (tc->flags & TCF_IN_FUNCTION)) { + JSStackFrame *fp; + JSObject *varobj; + + /* + * Define a property on the outer function so that LookupArgOrVar + * can properly optimize accesses. + * + * XXX Here and in Variables, we use the function object's scope, + * XXX arguably polluting it, when we could use a compiler-private + * XXX scope structure. Tradition! + */ + fp = cx->fp; + varobj = fp->varobj; + JS_ASSERT(OBJ_GET_CLASS(cx, varobj) == &js_FunctionClass); + JS_ASSERT(fp->fun == (JSFunction *) JS_GetPrivate(cx, varobj)); + if (!js_DefineNativeProperty(cx, varobj, (jsid)funAtom, + OBJECT_TO_JSVAL(fun->object), + js_GetLocalVariable, + js_SetLocalVariable, + JSPROP_ENUMERATE, + SPROP_HAS_SHORTID, fp->fun->nvars, + NULL)) { + return NULL; + } + fp->fun->nvars++; + } +#endif + } + +#if JS_HAS_LEXICAL_CLOSURE + if (lambda || !funAtom) { + /* + * ECMA ed. 3 standard: function expression, possibly anonymous (even + * if at top-level, an unnamed function is an expression statement, not + * a function declaration). + */ + op = fun->atom ? JSOP_NAMEDFUNOBJ : JSOP_ANONFUNOBJ; + } else if (tc->topStmt) { + /* + * 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 +#endif + op = JSOP_NOP; + + /* + * Pending a better automatic GC root management scheme (see Mozilla bug + * 40757, http://bugzilla.mozilla.org/show_bug.cgi?id=40757), we need to + * atomize here to protect against a GC activation. + */ + pn->pn_funAtom = js_AtomizeObject(cx, fun->object, 0); + if (!pn->pn_funAtom) + return NULL; + + pn->pn_op = op; + pn->pn_body = body; + pn->pn_flags = funtc.flags & TCF_FUN_FLAGS; + pn->pn_tryCount = funtc.tryCount; + TREE_CONTEXT_FINISH(&funtc); + return pn; +} + +static JSParseNode * +FunctionStmt(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + return FunctionDef(cx, ts, tc, JS_FALSE); +} + +#if JS_HAS_LEXICAL_CLOSURE +static JSParseNode * +FunctionExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + return FunctionDef(cx, ts, tc, JS_TRUE); +} +#endif + +/* + * 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; + JSTokenType tt; + + CHECK_RECURSION(); + + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn) + return NULL; + PN_INIT_LIST(pn); + + ts->flags |= TSF_REGEXP; + while ((tt = js_PeekToken(cx, ts)) > TOK_EOF && tt != TOK_RC) { + ts->flags &= ~TSF_REGEXP; + pn2 = Statement(cx, ts, tc); + if (!pn2) + return NULL; + ts->flags |= TSF_REGEXP; + + /* 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); + } + } + ts->flags &= ~TSF_REGEXP; + 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 = !JSVERSION_IS_ECMA(cx->version); + if (!js_ReportCompileErrorNumber(cx, ts, NULL, + 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; +#if JS_HAS_LABEL_STATEMENT + 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; + } +#else + label = NULL; +#endif + pn->pn_atom = label; + return JS_TRUE; +} + +#if JS_HAS_EXPORT_IMPORT +static JSParseNode * +ImportExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn, *pn2, *pn3; + JSTokenType tt; + + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_IMPORT_NAME); + pn = NewParseNode(cx, &CURRENT_TOKEN(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_REGEXP; + while ((tt = js_GetToken(cx, ts)) == TOK_DOT || tt == TOK_LB) { + ts->flags &= ~TSF_REGEXP; + if (pn->pn_op == JSOP_IMPORTALL) + goto bad_import; + + if (tt == TOK_DOT) { + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NAME, tc); + if (!pn2) + return NULL; + if (js_MatchToken(cx, ts, TOK_STAR)) { + pn2->pn_op = JSOP_IMPORTALL; + pn2->pn_atom = NULL; + } 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; + } + 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 node. */ + pn2 = NewParseNode(cx, &CURRENT_TOKEN(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; + + pn2->pn_op = JSOP_GETELEM; + pn2->pn_left = pn; + pn2->pn_right = pn3; + } + + pn = pn2; + ts->flags |= TSF_REGEXP; + } + ts->flags &= ~TSF_REGEXP; + 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, NULL, JSREPORT_ERROR, JSMSG_BAD_IMPORT); + return NULL; +} +#endif /* JS_HAS_EXPORT_IMPORT */ + +extern const char js_with_statement_str[]; + +static JSParseNode * +Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSTokenType tt; + JSParseNode *pn, *pn1, *pn2, *pn3, *pn4; + JSStmtInfo stmtInfo, *stmt, *stmt2; + JSAtom *label; + + ts->flags |= TSF_REGEXP; + tt = js_GetToken(cx, ts); + ts->flags &= ~TSF_REGEXP; + +#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, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn) + return NULL; + PN_INIT_LIST(pn); + if (js_MatchToken(cx, ts, TOK_STAR)) { + pn2 = NewParseNode(cx, &CURRENT_TOKEN(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, &CURRENT_TOKEN(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, &CURRENT_TOKEN(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: + return FunctionStmt(cx, ts, tc); + + case TOK_IF: + /* An IF node has three kids: condition, then, and optional else. */ + pn = NewParseNode(cx, &CURRENT_TOKEN(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; + if (js_MatchToken(cx, ts, TOK_ELSE)) { + stmtInfo.type = STMT_ELSE; + pn3 = Statement(cx, ts, tc); + if (!pn3) + return NULL; + pn->pn_pos.end = pn3->pn_pos.end; + } else { + 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; + +#if JS_HAS_SWITCH_STATEMENT + case TOK_SWITCH: + { + JSParseNode *pn5; + JSBool seenDefault = JS_FALSE; + + pn = NewParseNode(cx, &CURRENT_TOKEN(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, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn2) + return NULL; + 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, NULL, JSREPORT_ERROR, + JSMSG_TOO_MANY_DEFAULTS); + return NULL; + } + seenDefault = JS_TRUE; + /* fall through */ + + case TOK_CASE: + pn3 = NewParseNode(cx, &CURRENT_TOKEN(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, NULL, JSREPORT_ERROR, + JSMSG_TOO_MANY_CASES); + return NULL; + } + break; + + case TOK_ERROR: + return NULL; + + default: + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_SWITCH); + return NULL; + } + MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_AFTER_CASE); + + pn4 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn4) + return NULL; + pn4->pn_type = TOK_LC; + PN_INIT_LIST(pn4); + while ((tt = js_PeekToken(cx, ts)) != TOK_RC && + tt != TOK_CASE && tt != TOK_DEFAULT) { + 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); + } + + /* 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; + } + + js_PopStatement(tc); + + pn->pn_pos.end = pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + pn->pn_kid1 = pn1; + pn->pn_kid2 = pn2; + return pn; + } +#endif /* JS_HAS_SWITCH_STATEMENT */ + + case TOK_WHILE: + pn = NewParseNode(cx, &CURRENT_TOKEN(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; + +#if JS_HAS_DO_WHILE_LOOP + case TOK_DO: + pn = NewParseNode(cx, &CURRENT_TOKEN(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; + break; +#endif /* JS_HAS_DO_WHILE_LOOP */ + + case TOK_FOR: + /* A FOR node is binary, left is loop control and right is the body. */ + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_BINARY, tc); + if (!pn) + return NULL; + js_PushStatement(tc, &stmtInfo, STMT_FOR_LOOP, -1); + + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); + tt = js_PeekToken(cx, ts); + if (tt == TOK_SEMI) { + /* No initializer -- set first kid of left sub-node to null. */ + pn1 = NULL; + } else { + /* Set pn1 to a var list or an initializing expression. */ +#if JS_HAS_IN_OPERATOR + /* + * 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; +#endif /* JS_HAS_IN_OPERATOR */ + if (tt == TOK_VAR) { + (void) js_GetToken(cx, ts); + pn1 = Variables(cx, ts, tc); + } else { + pn1 = Expr(cx, ts, tc); + } +#if JS_HAS_IN_OPERATOR + tc->flags &= ~TCF_IN_FOR_INIT; +#endif /* JS_HAS_IN_OPERATOR */ + 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. */ + if ((pn1->pn_type == TOK_VAR) + ? (pn1->pn_count > 1 || pn1->pn_op == JSOP_DEFCONST) + : (pn1->pn_type != TOK_NAME && + pn1->pn_type != TOK_DOT && + pn1->pn_type != TOK_LB)) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_FOR_LEFTSIDE); + return NULL; + } + + if (pn1->pn_type == TOK_VAR) { + /* Tell js_EmitTree(TOK_VAR) to generate a final POP. */ + pn1->pn_extra = JS_TRUE; + pn2 = pn1->pn_head; + } else { + pn2 = pn1; + } + + /* Beware 'for (arguments in ...)' with or without a 'var'. */ + if (pn2->pn_type == TOK_NAME && + pn2->pn_atom == cx->runtime->atomState.argumentsAtom) { + tc->flags |= TCF_FUN_HEAVYWEIGHT; + } + + /* 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 { + /* Parse the loop condition or null into pn2. */ + MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_INIT); + if (js_PeekToken(cx, ts) == 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); + if (js_PeekToken(cx, ts) == 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, &CURRENT_TOKEN(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; + js_PopStatement(tc); + + /* Record the absolute line number for source note emission. */ + pn->pn_pos.end = pn2->pn_pos.end; + return pn; + +#if JS_HAS_EXCEPTIONS + case TOK_TRY: { + JSParseNode *catchtail = NULL; + /* + * try nodes are ternary. + * kid1 is the try Statement + * kid2 is the catch node + * kid3 is the finally Statement + * + * catch nodes are ternary. + * kid1 is the discriminant + * kid2 is the next catch node, or NULL + * kid3 is the catch block (on kid3 so that we can always append a + * new catch pn on catchtail->kid2) + * + * catch discriminant nodes are binary + * atom is the receptacle + * expr is the discriminant code + * + * finally nodes are unary (just the finally expression) + */ + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_TERNARY, tc); + 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); + + catchtail = pn; + while (js_PeekToken(cx, ts) == TOK_CATCH) { + /* check for another catch after unconditional catch */ + if (catchtail != pn && !catchtail->pn_kid1->pn_expr) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_CATCH_AFTER_GENERAL); + return NULL; + } + + /* + * legal catch forms are: + * catch (v) + * catch (v if <boolean_expression>) + * (the latter is legal only #ifdef JS_HAS_CATCH_GUARD) + */ + (void) js_GetToken(cx, ts); /* eat `catch' */ + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_TERNARY, tc); + if (!pn2) + return NULL; + + /* + * We use a PN_NAME for the discriminant (catchguard) node + * with the actual discriminant code in the initializer spot + */ + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH); + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_CATCH_IDENTIFIER); + pn3 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NAME, tc); + if (!pn3) + return NULL; + + pn3->pn_atom = CURRENT_TOKEN(ts).t_atom; + pn3->pn_expr = NULL; +#if JS_HAS_CATCH_GUARD + /* + * We use `catch (x if x === 5)' (not `catch (x : x === 5)') to + * avoid conflicting with the JS2/ECMA2 proposed catchguard syntax. + */ + if (js_PeekToken(cx, ts) == TOK_IF) { + (void)js_GetToken(cx, ts); /* eat `if' */ + pn3->pn_expr = Expr(cx, ts, tc); + if (!pn3->pn_expr) + return NULL; + } +#endif + pn2->pn_kid1 = pn3; + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_CATCH); + + MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CATCH); + js_PushStatement(tc, &stmtInfo, STMT_CATCH, -1); + stmtInfo.label = pn3->pn_atom; + 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); + + catchtail = catchtail->pn_kid2 = pn2; + } + catchtail->pn_kid2 = NULL; + + if (js_MatchToken(cx, ts, 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 { + pn->pn_kid3 = NULL; + } + if (!pn->pn_kid2 && !pn->pn_kid3) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_CATCH_OR_FINALLY); + return NULL; + } + tc->tryCount++; + return pn; + } + + case TOK_THROW: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_UNARY, tc); + if (!pn) + 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, NULL, JSREPORT_ERROR, + JSMSG_CATCH_WITHOUT_TRY); + return NULL; + + case TOK_FINALLY: + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_FINALLY_WITHOUT_TRY); + return NULL; + +#endif /* JS_HAS_EXCEPTIONS */ + + case TOK_BREAK: + pn = NewParseNode(cx, &CURRENT_TOKEN(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, NULL, JSREPORT_ERROR, + JSMSG_LABEL_NOT_FOUND); + return NULL; + } + if (stmt->type == STMT_LABEL && stmt->label == label) + break; + } + } else { + for (; ; stmt = stmt->down) { + if (!stmt) { + js_ReportCompileErrorNumber(cx, ts, NULL, 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, &CURRENT_TOKEN(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, NULL, JSREPORT_ERROR, + JSMSG_LABEL_NOT_FOUND); + return NULL; + } + if (stmt->type == STMT_LABEL) { + if (stmt->label == label) { + if (!stmt2 || !STMT_IS_LOOP(stmt2)) { + js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_ERROR, + JSMSG_BAD_CONTINUE); + return NULL; + } + break; + } + } else { + stmt2 = stmt; + } + } + } else { + for (; ; stmt = stmt->down) { + if (!stmt) { + js_ReportCompileErrorNumber(cx, ts, NULL, 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, &CURRENT_TOKEN(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); + + /* Deprecate after parsing, in case of WERROR option. */ + if (!js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | JSREPORT_STRICT, + JSMSG_DEPRECATED_USAGE, + js_with_statement_str)) { + return NULL; + } + + 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 = JS_TRUE; + break; + + case TOK_RETURN: + if (!(tc->flags & TCF_IN_FUNCTION)) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_RETURN); + return NULL; + } + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_UNARY, tc); + if (!pn) + return NULL; + + /* This is ugly, but we don't want to require a semicolon. */ + ts->flags |= TSF_REGEXP; + tt = js_PeekTokenSameLine(cx, ts); + ts->flags &= ~TSF_REGEXP; + if (tt == TOK_ERROR) + return NULL; + + if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) { + pn2 = Expr(cx, ts, tc); + if (!pn2) + return NULL; + tc->flags |= TCF_RETURN_EXPR; + pn->pn_pos.end = pn2->pn_pos.end; + pn->pn_kid = pn2; + } else { + tc->flags |= TCF_RETURN_VOID; + pn->pn_kid = NULL; + } + + if (JS_HAS_STRICT_OPTION(cx) && + (~tc->flags & (TCF_RETURN_EXPR | TCF_RETURN_VOID)) == 0) { + /* + * We must be in a frame with a non-native function, because + * we're compiling one. + */ + if (!ReportNoReturnValue(cx, ts)) + return NULL; + } + break; + + case TOK_LC: + 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); + return pn; + + case TOK_EOL: + case TOK_SEMI: + pn = NewParseNode(cx, &CURRENT_TOKEN(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, &CURRENT_TOKEN(ts), PN_NULLARY, tc); + if (!pn) + return NULL; + pn->pn_type = TOK_DEBUGGER; + tc->flags |= TCF_FUN_HEAVYWEIGHT; + break; +#endif /* JS_HAS_DEBUGGER_KEYWORD */ + + case TOK_ERROR: + return NULL; + + default: + 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, NULL, 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->label == label) { + js_ReportCompileErrorNumber(cx, ts, NULL, 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.label = label; + pn = Statement(cx, ts, tc); + if (!pn) + return NULL; + + /* 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, &CURRENT_TOKEN(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)) { + tt = js_PeekTokenSameLine(cx, ts); + if (tt == TOK_ERROR) + return NULL; + if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) { + js_ReportCompileErrorNumber(cx, ts, NULL, 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) +{ + JSParseNode *pn, *pn2; + JSObject *obj, *pobj; + JSStackFrame *fp; + JSFunction *fun; + JSClass *clasp; + JSPropertyOp getter, setter, currentGetter, currentSetter; + JSAtom *atom; + JSAtomListElement *ale; + JSOp prevop; + JSProperty *prop; + JSScopeProperty *sprop; + JSBool ok; + + /* + * 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 complex special 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 id in the current variable scope. + */ + JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_VAR); + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn) + return NULL; + pn->pn_op = CURRENT_TOKEN(ts).t_op; + pn->pn_extra = JS_FALSE; /* assume no JSOP_POP needed */ + PN_INIT_LIST(pn); + + /* + * Skip eval and debugger frames when looking for the function whose code + * is being compiled. If we are called from FunctionBody, TCF_IN_FUNCTION + * will be set in tc->flags, and we can be sure fp->fun is the function to + * use. But if a function calls eval, the string argument is treated as a + * Program (per ECMA), so TCF_IN_FUNCTION won't be set. + * + * What's more, when the following code is reached from eval, cx->fp->fun + * is eval's JSFunction (a native function), so we need to skip its frame. + * We should find the scripted caller's function frame just below it, but + * we code a loop out of paranoia. + */ + for (fp = cx->fp; (fp->flags & JSFRAME_SPECIAL) && fp->down; fp = fp->down) + continue; + obj = fp->varobj; + fun = fp->fun; + clasp = OBJ_GET_CLASS(cx, obj); + if (fun && clasp == &js_FunctionClass) { + /* We are compiling code inside a function */ + getter = js_GetLocalVariable; + setter = js_SetLocalVariable; + } else if (fun && clasp == &js_CallClass) { + /* We are compiling code from an eval inside a function */ + getter = js_GetCallVariable; + setter = js_SetCallVariable; + } else { + getter = clasp->getProperty; + setter = clasp->setProperty; + } + + ok = JS_TRUE; + do { + currentGetter = getter; + currentSetter = setter; + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_VARIABLE_NAME); + atom = CURRENT_TOKEN(ts).t_atom; + + ATOM_LIST_SEARCH(ale, &tc->decls, atom); + if (ale) { + prevop = ALE_JSOP(ale); + if (JS_HAS_STRICT_OPTION(cx) || + pn->pn_op == JSOP_DEFCONST || + prevop == JSOP_DEFCONST) { + const char *name = js_AtomToPrintableString(cx, atom); + if (!name || + !js_ReportCompileErrorNumber(cx, ts, NULL, + (pn->pn_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 NULL; + } + } + if (pn->pn_op == JSOP_DEFVAR && prevop == JSOP_CLOSURE) + tc->flags |= TCF_FUN_CLOSURE_VS_VAR; + } else { + ale = js_IndexAtom(cx, atom, &tc->decls); + if (!ale) + return NULL; + } + ALE_SET_JSOP(ale, pn->pn_op); + + pn2 = NewParseNode(cx, &CURRENT_TOKEN(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 = (pn->pn_op == JSOP_DEFCONST) + ? JSPROP_ENUMERATE | JSPROP_PERMANENT | + JSPROP_READONLY + : JSPROP_ENUMERATE | JSPROP_PERMANENT; + PN_APPEND(pn, pn2); + + if (!OBJ_LOOKUP_PROPERTY(cx, obj, (jsid)atom, &pobj, &prop)) + return NULL; + if (pobj == obj && + OBJ_IS_NATIVE(pobj) && + (sprop = (JSScopeProperty *)prop) != NULL) { + if (sprop->getter == js_GetArgument) { + const char *name = js_AtomToPrintableString(cx, atom); + if (!name) { + ok = JS_FALSE; + } else if (pn->pn_op == JSOP_DEFCONST) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_REDECLARED_PARAM, + name); + ok = JS_FALSE; + } else { + currentGetter = js_GetArgument; + currentSetter = js_SetArgument; + ok = js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_VAR_HIDES_ARG, + name); + } + } else { + if (fun) { + /* Not an argument, must be a redeclared local var. */ + if (clasp == &js_FunctionClass) { + JS_ASSERT(sprop->getter == js_GetLocalVariable); + JS_ASSERT((sprop->flags & SPROP_HAS_SHORTID) && + sprop->shortid < fun->nvars); + } else if (clasp == &js_CallClass) { + if (sprop->getter == js_GetCallVariable) { + /* + * Referencing a variable 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) && + sprop->shortid < fun->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. + */ + currentGetter = sprop->getter; + currentSetter = sprop->setter; + } + } + + /* Override the old getter and setter, to handle eval. */ + sprop = js_ChangeNativePropertyAttrs(cx, obj, sprop, + 0, sprop->attrs, + currentGetter, + currentSetter); + if (!sprop) + ok = JS_FALSE; + } + } + } 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 frame. Global variables and any locals declared in + * with statement bodies are handled at runtime, by script prolog + * JSOP_DEFVAR bytecodes generated for slot-less vars. + */ + sprop = NULL; + if (prop) { + OBJ_DROP_PROPERTY(cx, pobj, prop); + prop = NULL; + } + if (currentGetter == js_GetCallVariable) { + /* Can't increase fun->nvars in an active frame! */ + currentGetter = clasp->getProperty; + currentSetter = clasp->setProperty; + } + if (currentGetter == js_GetLocalVariable && + atom != cx->runtime->atomState.argumentsAtom && + fp->scopeChain == obj && + !js_InWithStatement(tc)) { + if (!js_AddNativeProperty(cx, obj, (jsid)atom, + currentGetter, currentSetter, + SPROP_INVALID_SLOT, + pn2->pn_attrs | JSPROP_SHARED, + SPROP_HAS_SHORTID, fun->nvars)) { + ok = JS_FALSE; + } + fun->nvars++; + } + } + + if (js_MatchToken(cx, ts, TOK_ASSIGN)) { + if (CURRENT_TOKEN(ts).t_op != JSOP_NOP) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_VAR_INIT); + ok = JS_FALSE; + } else { + pn2->pn_expr = AssignExpr(cx, ts, tc); + if (!pn2->pn_expr) { + ok = JS_FALSE; + } else { + pn2->pn_op = (pn->pn_op == JSOP_DEFCONST) + ? JSOP_SETCONST + : JSOP_SETNAME; + if (atom == cx->runtime->atomState.argumentsAtom) + tc->flags |= TCF_FUN_HEAVYWEIGHT; + } + } + } + + if (prop) + OBJ_DROP_PROPERTY(cx, pobj, prop); + if (!ok) + return NULL; + } while (js_MatchToken(cx, ts, TOK_COMMA)); + + pn->pn_pos.end = PN_LAST(pn)->pn_pos.end; + return pn; +} + +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, &CURRENT_TOKEN(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 { + 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(); + + 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 = JSOP_SETPROP; + break; + case TOK_LB: + pn2->pn_op = JSOP_SETELEM; + break; +#if JS_HAS_LVALUE_RETURN + case TOK_LP: + pn2->pn_op = JSOP_SETCALL; + break; +#endif + default: + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_LEFTSIDE_OF_ASS); + return NULL; + } + pn = NewBinary(cx, TOK_ASSIGN, op, pn2, AssignExpr(cx, ts, tc), tc); + return pn; +} + +static JSParseNode * +CondExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSParseNode *pn, *pn1, *pn2, *pn3; +#if JS_HAS_IN_OPERATOR + uintN oldflags; +#endif /* JS_HAS_IN_OPERATOR */ + + pn = OrExpr(cx, ts, tc); + if (pn && js_MatchToken(cx, ts, TOK_HOOK)) { + pn1 = pn; + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_TERNARY, tc); + if (!pn) + return NULL; +#if JS_HAS_IN_OPERATOR + /* + * 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; +#endif /* JS_HAS_IN_OPERATOR */ + pn2 = AssignExpr(cx, ts, tc); +#if JS_HAS_IN_OPERATOR + tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS); +#endif /* JS_HAS_IN_OPERATOR */ + + 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; +#if JS_HAS_IN_OPERATOR + 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; +#endif /* JS_HAS_IN_OPERATOR */ + + pn = ShiftExpr(cx, ts, tc); + while (pn && + (js_MatchToken(cx, ts, TOK_RELOP) +#if JS_HAS_IN_OPERATOR + /* + * 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)) +#endif /* JS_HAS_IN_OPERATOR */ +#if JS_HAS_INSTANCEOF + || js_MatchToken(cx, ts, TOK_INSTANCEOF) +#endif /* JS_HAS_INSTANCEOF */ + )) { + tt = CURRENT_TOKEN(ts).type; + op = CURRENT_TOKEN(ts).t_op; + pn = NewBinary(cx, tt, op, pn, ShiftExpr(cx, ts, tc), tc); + } +#if JS_HAS_IN_OPERATOR + /* Restore previous state of inForInit flag. */ + tc->flags |= inForInitFlag; +#endif /* JS_HAS_IN_OPERATOR */ + + 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 + kid->pn_type != TOK_LB) { + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_OPERAND, name); + return NULL; + } + pn->pn_kid = kid; + return kid; +} + +static const char *incop_name_str[] = {"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: + kid->pn_op = JSOP_SETCALL; +#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; + + ts->flags |= TSF_REGEXP; + tt = js_GetToken(cx, ts); + ts->flags &= ~TSF_REGEXP; + + switch (tt) { + case TOK_UNARYOP: + case TOK_PLUS: + case TOK_MINUS: + pn = NewParseNode(cx, &CURRENT_TOKEN(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, &CURRENT_TOKEN(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, &CURRENT_TOKEN(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)) { + tt = js_PeekTokenSameLine(cx, ts); + if (tt == TOK_INC || tt == TOK_DEC) { + (void) js_GetToken(cx, ts); + pn2 = NewParseNode(cx, &CURRENT_TOKEN(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_REGEXP; + matched = js_MatchToken(cx, ts, TOK_RP); + ts->flags &= ~TSF_REGEXP; + if (!matched) { + do { + JSParseNode *argNode = AssignExpr(cx, ts, tc); + if (!argNode) + return JS_FALSE; + PN_APPEND(listNode, argNode); + } while (js_MatchToken(cx, ts, TOK_COMMA)); + + if (js_GetToken(cx, ts) != TOK_RP) { + js_ReportCompileErrorNumber(cx, ts, NULL, 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_REGEXP; + tt = js_PeekToken(cx, ts); + ts->flags &= ~TSF_REGEXP; + if (tt == TOK_NEW) { + (void) js_GetToken(cx, ts); + + pn = NewParseNode(cx, &CURRENT_TOKEN(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); + if (!pn) + return NULL; + } + + while ((tt = js_GetToken(cx, ts)) > TOK_EOF) { + if (tt == TOK_DOT) { + pn2 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NAME, tc); + if (!pn2) + return NULL; + MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NAME_AFTER_DOT); + pn2->pn_pos.begin = pn->pn_pos.begin; + pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + pn2->pn_op = JSOP_GETPROP; + pn2->pn_expr = pn; + pn2->pn_atom = CURRENT_TOKEN(ts).t_atom; + } else if (tt == TOK_LB) { + pn2 = NewParseNode(cx, &CURRENT_TOKEN(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, &CURRENT_TOKEN(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 = PN_LAST(pn2)->pn_pos.end; + } else { + js_UngetToken(ts); + return pn; + } + + pn = pn2; + } + if (tt == TOK_ERROR) + return NULL; + return pn; +} + +static JSParseNode * +PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) +{ + JSTokenType tt; + JSParseNode *pn, *pn2, *pn3; + char *badWord; +#if JS_HAS_GETTER_SETTER + JSAtom *atom; + JSRuntime *rt; +#endif + +#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 token type case + * should set notsharp. + */ +#endif + + CHECK_RECURSION(); + + ts->flags |= TSF_REGEXP; + tt = js_GetToken(cx, ts); + ts->flags &= ~TSF_REGEXP; + +#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_LEXICAL_CLOSURE + case TOK_FUNCTION: + pn = FunctionExpr(cx, ts, tc); + if (!pn) + return NULL; + break; +#endif + +#if JS_HAS_INITIALIZERS + case TOK_LB: + { + JSBool matched; + jsuint atomIndex; + + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_LIST, tc); + if (!pn) + return NULL; + pn->pn_type = TOK_RB; + pn->pn_extra = JS_FALSE; + +#if JS_HAS_SHARP_VARS + if (defsharp) { + PN_INIT_LIST_1(pn, defsharp); + defsharp = NULL; + } else +#endif + PN_INIT_LIST(pn); + + ts->flags |= TSF_REGEXP; + matched = js_MatchToken(cx, ts, TOK_RB); + ts->flags &= ~TSF_REGEXP; + if (!matched) { + for (atomIndex = 0; atomIndex < ATOM_INDEX_LIMIT; atomIndex++) { + ts->flags |= TSF_REGEXP; + tt = js_PeekToken(cx, ts); + ts->flags &= ~TSF_REGEXP; + if (tt == TOK_RB) { + pn->pn_extra = JS_TRUE; + break; + } + + if (tt == TOK_COMMA) { + /* So CURRENT_TOKEN gets TOK_COMMA and not TOK_LB. */ + js_MatchToken(cx, ts, TOK_COMMA); + pn2 = NewParseNode(cx, &CURRENT_TOKEN(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; + } + } + + MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_LIST); + } + pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + return pn; + } + + case TOK_LC: + pn = NewParseNode(cx, &CURRENT_TOKEN(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); + + if (!js_MatchToken(cx, ts, TOK_RC)) { + do { + JSOp op; + + tt = js_GetToken(cx, ts); + switch (tt) { + case TOK_NUMBER: + pn3 = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NULLARY, tc); + if (pn3) + pn3->pn_dval = CURRENT_TOKEN(ts).t_dval; + break; + case TOK_NAME: +#if JS_HAS_GETTER_SETTER + 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, &CURRENT_TOKEN(ts), PN_NAME, + tc); + if (!pn3) + return NULL; + pn3->pn_atom = CURRENT_TOKEN(ts).t_atom; + pn3->pn_expr = NULL; + + /* 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, &CURRENT_TOKEN(ts), PN_NULLARY, tc); + if (pn3) + pn3->pn_atom = CURRENT_TOKEN(ts).t_atom; + break; + case TOK_RC: + if (!js_ReportCompileErrorNumber(cx, ts, NULL, + JSREPORT_WARNING | + JSREPORT_STRICT, + JSMSG_TRAILING_COMMA)) { + return NULL; + } + goto end_obj_init; + default: + js_ReportCompileErrorNumber(cx, ts, NULL, 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, NULL, 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); + } while (js_MatchToken(cx, ts, TOK_COMMA)); + + MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_LIST); + } + 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, &CURRENT_TOKEN(ts), PN_UNARY, tc); + if (!defsharp) + return NULL; + defsharp->pn_kid = NULL; + defsharp->pn_num = (jsint) CURRENT_TOKEN(ts).t_dval; + goto again; + + case TOK_USESHARP: + /* Check for forward/dangling references at runtime, to allow eval. */ + pn = NewParseNode(cx, &CURRENT_TOKEN(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 */ +#endif /* JS_HAS_INITIALIZERS */ + + case TOK_LP: + { +#if JS_HAS_IN_OPERATOR + uintN oldflags; +#endif + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_UNARY, tc); + if (!pn) + return NULL; +#if JS_HAS_IN_OPERATOR + /* + * 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; +#endif + pn2 = Expr(cx, ts, tc); +#if JS_HAS_IN_OPERATOR + tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS); +#endif + if (!pn2) + return NULL; + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN); + pn->pn_type = TOK_RP; + pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + pn->pn_kid = pn2; + break; + } + + case TOK_STRING: +#if JS_HAS_SHARP_VARS + notsharp = JS_TRUE; +#endif + /* FALL THROUGH */ + case TOK_NAME: + case TOK_OBJECT: + pn = NewParseNode(cx, &CURRENT_TOKEN(ts), PN_NULLARY, tc); + if (!pn) + return NULL; + pn->pn_op = CURRENT_TOKEN(ts).t_op; + pn->pn_atom = CURRENT_TOKEN(ts).t_atom; + if (tt == TOK_NAME) { + pn->pn_arity = PN_NAME; + pn->pn_expr = NULL; + pn->pn_slot = -1; + pn->pn_attrs = 0; + + /* 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; + } + } + break; + + case TOK_NUMBER: + pn = NewParseNode(cx, &CURRENT_TOKEN(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, &CURRENT_TOKEN(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_RESERVED: + badWord = js_DeflateString(cx, CURRENT_TOKEN(ts).ptr, + (size_t) CURRENT_TOKEN(ts).pos.end.index + - CURRENT_TOKEN(ts).pos.begin.index); + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_RESERVED_ID, badWord); + JS_free(cx, badWord); + return NULL; + + case TOK_ERROR: + /* The scanner or one of its subroutines reported the error. */ + return NULL; + + default: + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_SYNTAX_ERROR); + return NULL; + } + +#if JS_HAS_SHARP_VARS + if (defsharp) { + if (notsharp) { + badsharp: + js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, + JSMSG_BAD_SHARP_VAR_DEF); + return NULL; + } + defsharp->pn_kid = pn; + return defsharp; + } +#endif + return pn; +} + +static JSBool +ContainsVarStmt(JSParseNode *pn) +{ + JSParseNode *pn2; + + if (!pn) + return JS_FALSE; + switch (pn->pn_arity) { + case PN_LIST: + if (pn->pn_type == TOK_VAR) + return JS_TRUE; + for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { + if (ContainsVarStmt(pn2)) + return JS_TRUE; + } + break; + case PN_TERNARY: + return ContainsVarStmt(pn->pn_kid1) || + ContainsVarStmt(pn->pn_kid2) || + ContainsVarStmt(pn->pn_kid3); + 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 JS_FALSE; + return ContainsVarStmt(pn->pn_left) || ContainsVarStmt(pn->pn_right); + case PN_UNARY: + if (pn->pn_op != JSOP_NOP) + return JS_FALSE; + return ContainsVarStmt(pn->pn_kid); + default:; + } + return JS_FALSE; +} + +/* + * 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; +} + +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: + if (!js_FoldConstants(cx, pn->pn_body, tc)) + return JS_FALSE; + break; + + case PN_LIST: + /* 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 (ContainsVarStmt(pn2) || ContainsVarStmt(pn3)) + break; + /* FALL THROUGH */ + + case TOK_HOOK: + /* Reduce 'if (C) T; else E' into T for true C, E for false. */ + 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. */ + PN_MOVE_NODE(pn, pn2); + } else { + /* False condition and no else: make pn an empty statement. */ + pn->pn_type = TOK_SEMI; + pn->pn_arity = PN_UNARY; + pn->pn_kid = NULL; + } + RecycleTree(pn2, tc); + if (pn3 && pn3 != pn2) + RecycleTree(pn3, tc); + break; + + 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 = pn3) { + str2 = ATOM_TO_STRING(pn2->pn_atom); + length2 = str2->length; + js_strncpy(chars, str2->chars, length2); + chars += length2; + pn3 = pn2->pn_next; + RecycleTree(pn2, tc); + } + *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; + /* 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: + 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; + + default:; + } + + return JS_TRUE; +} |
