/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* * JS parser. * * This is a recursive-descent parser for the JavaScript language specified by * "The JavaScript 1.2 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 #include #include #include "prtypes.h" #include "prarena.h" #include "prassert.h" #include "prprintf.h" #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); 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 JSParser MemberExpr; static JSParser PrimaryExpr; /* * Insist that the next token be of type tt, or report err and throw or fail. * NB: this macro uses cx and ts from its lexical environment. */ #define MUST_MATCH_TOKEN_THROW(tt, err, throw) \ PR_BEGIN_MACRO \ if (js_GetToken(cx, ts) != tt) { \ js_ReportCompileError(cx, ts, err); \ throw; \ } \ PR_END_MACRO #define MUST_MATCH_TOKEN(tt, err) \ MUST_MATCH_TOKEN_THROW(tt, err, return NULL) /* * Allocate a JSParseNode from cx's temporary arena. */ static JSParseNode * NewParseNode(JSContext *cx, JSToken *tok, JSParseNodeArity arity) { JSParseNode *pn; PR_ARENA_ALLOCATE(pn, &cx->tempPool, sizeof(JSParseNode)); if (!pn) return NULL; pn->pn_type = tok->type; pn->pn_pos = tok->pos; pn->pn_arity = arity; pn->pn_next = NULL; return pn; } static JSParseNode * NewBinary(JSContext *cx, JSTokenType tt, JSOp op, JSParseNode *left, JSParseNode *right) { JSParseNode *pn; if (!left || !right) return NULL; PR_ARENA_ALLOCATE(pn, &cx->tempPool, sizeof(JSParseNode)); 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; return pn; } static JSBool WellTerminated(JSContext *cx, JSTokenStream *ts, JSTokenType lastExprType) { JSTokenType tt; tt = js_PeekTokenSameLine(cx, ts); if (tt == TOK_ERROR) return JS_FALSE; if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) { #if JS_HAS_LEXICAL_CLOSURE if ((tt == TOK_FUNCTION || lastExprType == TOK_FUNCTION) && cx->version < JSVERSION_1_2) { return JS_TRUE; } #endif js_ReportCompileError(cx, ts, "missing ; before statement"); return JS_FALSE; } return JS_TRUE; } /* * Parse a top-level JS script. */ JS_FRIEND_API(JSBool) js_CompileTokenStream(JSContext *cx, JSObject *chain, JSTokenStream *ts, JSCodeGenerator *cg) { JSStackFrame *fp, frame; JSTokenType stop, tt; JSBool ok; JSParseNode *pn; fp = cx->fp; if (!fp || fp->scopeChain != chain) { memset(&frame, 0, sizeof frame); frame.scopeChain = chain; frame.down = fp; cx->fp = &frame; } if (ts->flags & TSF_INTERACTIVE) { SCAN_NEWLINES(ts); stop = TOK_EOL; } else { stop = TOK_EOF; } ok = JS_TRUE; do { ts->flags |= TSF_REGEXP; tt = js_GetToken(cx, ts); ts->flags &= ~TSF_REGEXP; if (tt == stop || tt <= TOK_EOF) { if (tt == TOK_ERROR) ok = JS_FALSE; break; } switch (tt) { case TOK_FUNCTION: pn = FunctionStmt(cx, ts, &cg->treeContext); if (pn && pn->pn_pos.end.lineno == ts->lineno) { ok = WellTerminated(cx, ts, TOK_FUNCTION); if (!ok) goto out; } break; default: js_UngetToken(ts); pn = Statement(cx, ts, &cg->treeContext); if (pn) { ok = js_FoldConstants(cx, pn); if (!ok) goto out; } break; } if (pn) { ok = js_AllocTryNotes(cx, cg); if (ok) ok = js_EmitTree(cx, cg, pn); } else { ok = JS_FALSE; } } while (ok); out: cx->fp = fp; if (!ok) CLEAR_PUSHBACK(ts); 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 an error XXX should be warning * option). */ static JSBool CheckFinalReturn(JSParseNode *pn) { JSBool ok; JSParseNode *pn2, *pn3; switch (pn->pn_type) { case TOK_LC: if (!pn->pn_head) return JS_FALSE; return CheckFinalReturn(PN_LAST(pn)); case TOK_IF: ok = CheckFinalReturn(pn->pn_kid2); ok &= pn->pn_kid3 && CheckFinalReturn(pn->pn_kid3); return ok; case TOK_SWITCH: /* If a final switch has no default case, we judge it harshly. */ ok = pn->pn_kid3 && CheckFinalReturn(pn->pn_kid3); for (pn2 = pn->pn_kid2->pn_head; ok && pn2; pn2 = pn2->pn_next) { pn3 = pn2->pn_right; PR_ASSERT(pn3->pn_type == TOK_LC); if (pn3->pn_head) ok &= CheckFinalReturn(PN_LAST(pn3)); } return ok; case TOK_WITH: return CheckFinalReturn(pn->pn_right); case TOK_RETURN: return JS_TRUE; default: return JS_FALSE; } } static char badreturn_str[] = "function does not always return a value"; static JSParseNode * FunctionBody(JSContext *cx, JSTokenStream *ts, JSFunction *fun, JSTreeContext *tc) { JSStackFrame *fp, frame; uintN oldflags; JSParseNode *pn; fp = cx->fp; if (!fp || fp->scopeChain != fun->object) { memset(&frame, 0, sizeof frame); frame.scopeChain = fun->object; 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); #ifdef CHECK_RETURN_EXPR /* Check for falling off the end of a function that returns a value. */ if (pn && (tc->flags & TCF_RETURN_EXPR)) { if (!CheckFinalReturn(pn)) { js_ReportCompileError(cx, ts, badreturn_str); pn = NULL; } } #endif cx->fp = fp; tc->flags = oldflags; return pn; } /* * Compile a JS function body, which might appear as the value of an event * handler attribute in an HTML tag. */ JSBool js_CompileFunctionBody(JSContext *cx, JSTokenStream *ts, JSFunction *fun) { JSCodeGenerator funcg; JSParseNode *pn; JSBool ok; if (!js_InitCodeGenerator(cx, &funcg, ts->filename, ts->lineno, ts->principals)) { return JS_FALSE; } /* Satisfy the assertion at the top of Statements. */ ts->token.type = TOK_LC; pn = FunctionBody(cx, ts, fun, &funcg.treeContext); if (!pn) { CLEAR_PUSHBACK(ts); ok = JS_FALSE; } else { ok = js_FoldConstants(cx, pn); if (ok) ok = js_EmitFunctionBody(cx, &funcg, pn, fun); } js_ResetCodeGenerator(cx, &funcg); return ok; } static JSBool InWithStatement(JSTreeContext *tc) { JSStmtInfo *stmt; for (stmt = tc->topStmt; stmt; stmt = stmt->down) { if (stmt->type == STMT_WITH) return JS_TRUE; } return JS_FALSE; } static JSParseNode * FunctionDef(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, JSBool lambda) { JSParseNode *pn, *pn2; JSAtom *funAtom, *argAtom; JSObject *parent; JSFunction *fun, *outerFun; JSStmtInfo *topStmt; JSBool ok, named; JSObject *pobj; JSScopeProperty *sprop; jsval junk; /* Make a TOK_FUNCTION node. */ pn = NewParseNode(cx, &ts->token, PN_FUNC); if (!pn) return NULL; /* Scan the optional function name into funAtom. */ if (js_MatchToken(cx, ts, TOK_NAME)) funAtom = ts->token.t_atom; else funAtom = NULL; /* Find the nearest variable-declaring scope and use it as our parent. */ parent = js_FindVariableScope(cx, &outerFun); if (!parent) return NULL; /* Clear tc->topStmt for semantic checking (restore at label out:). */ topStmt = tc->topStmt; tc->topStmt = NULL; #if JS_HAS_LEXICAL_CLOSURE if (!funAtom || cx->fp->scopeChain != parent || InWithStatement(tc)) { /* Don't name the function if enclosed by a with statement or equiv. */ fun = js_NewFunction(cx, NULL, NULL, 0, 0, cx->fp->scopeChain, funAtom); named = JS_FALSE; } else #endif { /* Override any previously defined property using js_DefineFunction. */ fun = js_DefineFunction(cx, parent, funAtom, NULL, 0, JSPROP_ENUMERATE); named = (fun != NULL); } if (!fun) { ok = JS_FALSE; goto out; } /* Now parse formal argument list and compute fun->nargs. */ MUST_MATCH_TOKEN_THROW(TOK_LP, "missing ( before formal parameters", ok = JS_FALSE; goto out); /* balance) */ if (!js_MatchToken(cx, ts, TOK_RP)) { do { MUST_MATCH_TOKEN_THROW(TOK_NAME, "missing formal parameter", ok = JS_FALSE; goto out); argAtom = ts->token.t_atom; pobj = NULL; ok = js_LookupProperty(cx, fun->object, (jsid)argAtom, &pobj, (JSProperty **)&sprop); if (!ok) goto out; if (sprop && pobj == fun->object) { #ifdef CHECK_ARGUMENT_HIDING if (sprop->getter == js_GetArgument) { OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); js_ReportCompileError(cx, ts, "duplicate formal argument %s", ATOM_BYTES(argAtom)); ok = JS_FALSE; goto out; } #endif sprop->getter = js_GetArgument; sprop->setter = js_SetArgument; sprop->attrs |= JSPROP_ENUMERATE | JSPROP_PERMANENT; } else { if (sprop) { OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); sprop = NULL; } ok = js_DefineProperty(cx, fun->object, (jsid)argAtom, JSVAL_VOID, js_GetArgument, js_SetArgument, JSPROP_ENUMERATE | JSPROP_PERMANENT, (JSProperty **)&sprop); } if (!ok) goto out; PR_ASSERT(sprop); sprop->id = INT_TO_JSVAL(fun->nargs++); OBJ_DROP_PROPERTY(cx, fun->object, (JSProperty *)sprop); } while (js_MatchToken(cx, ts, TOK_COMMA)); /* (balance: */ MUST_MATCH_TOKEN_THROW(TOK_RP, "missing ) after formal parameters", ok = JS_FALSE; goto out); } MUST_MATCH_TOKEN_THROW(TOK_LC, "missing { before function body", ok = JS_FALSE; goto out); pn->pn_pos.begin = ts->token.pos.begin; pn2 = FunctionBody(cx, ts, fun, tc); if (!pn2) { ok = JS_FALSE; goto out; } MUST_MATCH_TOKEN_THROW(TOK_RC, "missing } after function body", ok = JS_FALSE; goto out); pn->pn_pos.end = ts->token.pos.end; pn->pn_fun = fun; pn->pn_body = pn2; pn->pn_tryCount = tc->tryCount; #if JS_HAS_LEXICAL_CLOSURE if (outerFun || cx->fp->scopeChain != parent || InWithStatement(tc)) pn->pn_op = JSOP_CLOSURE; else if (lambda) pn->pn_op = JSOP_OBJECT; else #endif pn->pn_op = JSOP_NOP; ok = JS_TRUE; out: tc->topStmt = topStmt; if (!ok) { if (named) (void) OBJ_DELETE_PROPERTY(cx, parent, (jsid)funAtom, &junk); return NULL; } 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. Our caller must match { before and } after. */ static JSParseNode * Statements(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) { JSParseNode *pn, *pn2; uintN newlines; JSTokenType tt; PR_ASSERT(ts->token.type == TOK_LC); pn = NewParseNode(cx, &ts->token, PN_LIST); if (!pn) return NULL; PN_INIT_LIST(pn); newlines = ts->flags & TSF_NEWLINES; if (newlines) HIDE_NEWLINES(ts); while ((tt = js_PeekToken(cx, ts)) > TOK_EOF && tt != TOK_RC) { pn2 = Statement(cx, ts, tc); if (!pn2) { pn = NULL; goto out; } PN_APPEND(pn, pn2); } pn->pn_pos.end = ts->token.pos.end; if (tt == TOK_ERROR) pn = NULL; out: if (newlines) SCAN_NEWLINES(ts); return pn; } static JSParseNode * Condition(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) { JSParseNode *pn, *pn2; MUST_MATCH_TOKEN(TOK_LP, "missing ( before condition"); pn = Expr(cx, ts, tc); if (!pn) return NULL; MUST_MATCH_TOKEN(TOK_RP, "missing ) after condition"); /* * Check for (a = b) and "correct" it to (a == b). * XXX not ECMA, but documented in several books -- need a compile option */ if (pn->pn_type == TOK_ASSIGN && pn->pn_op == JSOP_NOP) { js_ReportCompileError(cx, ts, "test for equality (==) mistyped as assignment (=)?\n" "Assuming equality test"); pn->pn_type = TOK_EQOP; pn->pn_op = cx->jsop_eq; pn2 = pn->pn_left; switch (pn2->pn_op) { case JSOP_SETARG: pn2->pn_op = JSOP_GETARG; break; case JSOP_SETVAR: pn2->pn_op = JSOP_GETVAR; break; case JSOP_SETNAME2: 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: PR_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 = ts->token.t_atom; } else { label = NULL; } #else label = NULL; #endif pn->pn_atom = label; if (pn->pn_pos.end.lineno == ts->lineno) return WellTerminated(cx, ts, TOK_ERROR); 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, "missing name in import statement"); pn = NewParseNode(cx, &ts->token, PN_NULLARY); if (!pn) return NULL; pn->pn_op = JSOP_NAME; pn->pn_atom = ts->token.t_atom; pn->pn_slot = -1; 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, &ts->token, PN_NAME); if (!pn2) return NULL; pn2->pn_expr = pn; if (js_MatchToken(cx, ts, TOK_STAR)) { pn2->pn_op = JSOP_IMPORTALL; pn2->pn_atom = NULL; } else { MUST_MATCH_TOKEN(TOK_NAME, "missing name after . operator"); pn2->pn_op = JSOP_GETPROP; pn2->pn_atom = ts->token.t_atom; pn2->pn_slot = -1; } pn2->pn_pos.begin = pn->pn_pos.begin; pn2->pn_pos.end = ts->token.pos.end; } else { /* Make a TOK_LB node. */ pn2 = NewParseNode(cx, &ts->token, PN_BINARY); if (!pn2) return NULL; pn3 = Expr(cx, ts, tc); if (!pn3) return NULL; /* [balance: */ MUST_MATCH_TOKEN(TOK_RB, "missing ] in index expression"); pn2->pn_pos.begin = pn->pn_pos.begin; pn2->pn_pos.end = ts->token.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; } } 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_ReportCompileError(cx, ts, "invalid import expression"); return NULL; } #endif /* JS_HAS_EXPORT_IMPORT */ static JSParseNode * Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) { JSTokenType tt, lastExprType; 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; switch (tt) { #if JS_HAS_EXPORT_IMPORT case TOK_EXPORT: pn = NewParseNode(cx, &ts->token, PN_LIST); if (!pn) return NULL; PN_INIT_LIST(pn); if (js_MatchToken(cx, ts, TOK_STAR)) { pn2 = NewParseNode(cx, &ts->token, PN_NULLARY); if (!pn2) return NULL; PN_APPEND(pn, pn2); } else { do { MUST_MATCH_TOKEN(TOK_NAME, "missing name in export statement"); pn2 = NewParseNode(cx, &ts->token, PN_NULLARY); if (!pn2) return NULL; pn2->pn_op = JSOP_NAME; pn2->pn_atom = ts->token.t_atom; pn2->pn_slot = -1; PN_APPEND(pn, pn2); } while (js_MatchToken(cx, ts, TOK_COMMA)); } pn->pn_pos.end = PN_LAST(pn)->pn_pos.end; if (pn->pn_pos.end.lineno == ts->lineno && !WellTerminated(cx, ts, TOK_ERROR)) { return NULL; } break; case TOK_IMPORT: pn = NewParseNode(cx, &ts->token, PN_LIST); 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; if (pn->pn_pos.end.lineno == ts->lineno && !WellTerminated(cx, ts, TOK_ERROR)) { return NULL; } break; #endif /* JS_HAS_EXPORT_IMPORT */ case TOK_IF: /* An IF node has three kids: condition, then, and optional else. */ pn = NewParseNode(cx, &ts->token, PN_TERNARY); 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: { uintN newlines; JSParseNode *pn5, *pn6; pn = NewParseNode(cx, &ts->token, PN_TERNARY); if (!pn) return NULL; MUST_MATCH_TOKEN(TOK_LP, "missing ( before switch expression"); /* pn1 points to the switch's discriminant. */ pn1 = Expr(cx, ts, tc); if (!pn1) return NULL; MUST_MATCH_TOKEN(TOK_RP, "missing ) after switch expression"); MUST_MATCH_TOKEN(TOK_LC, "missing { before switch body"); /* balance} */ /* pn2 is a list of case nodes, not including the default case. */ pn2 = NewParseNode(cx, &ts->token, PN_LIST); if (!pn2) return NULL; PN_INIT_LIST(pn2); /* If not null, pn3 points to the default case's node. */ pn3 = NULL; js_PushStatement(tc, &stmtInfo, STMT_SWITCH, -1); newlines = ts->flags & TSF_NEWLINES; if (newlines) HIDE_NEWLINES(ts); while ((tt = js_GetToken(cx, ts)) != TOK_RC) { switch (tt) { case TOK_CASE: pn4 = NewParseNode(cx, &ts->token, PN_BINARY); if (!pn4) goto bad_switch; pn4->pn_left = Expr(cx, ts, tc); if (!pn4->pn_left) goto bad_switch; PN_APPEND(pn2, pn4); if (pn2->pn_count == PR_BIT(16)) { js_ReportCompileError(cx, ts, "too many switch cases"); goto bad_switch; } break; case TOK_DEFAULT: if (pn3) { js_ReportCompileError(cx, ts, "more than one switch default"); goto bad_switch; } pn4 = NULL; break; case TOK_ERROR: goto bad_switch; default: js_ReportCompileError(cx, ts, "invalid switch statement"); goto bad_switch; } MUST_MATCH_TOKEN(TOK_COLON, "missing : after case label"); pn5 = NewParseNode(cx, &ts->token, PN_LIST); if (!pn5) goto bad_switch; pn5->pn_type = TOK_LC; PN_INIT_LIST(pn5); while ((tt = js_PeekToken(cx, ts)) != TOK_RC && tt != TOK_CASE && tt != TOK_DEFAULT) { if (tt == TOK_ERROR) goto bad_switch; pn6 = Statement(cx, ts, tc); if (!pn6) goto bad_switch; pn5->pn_pos.end = pn6->pn_pos.end; PN_APPEND(pn5, pn6); } if (pn4) { pn4->pn_pos.end = pn5->pn_pos.end; pn4->pn_right = pn5; } else { pn3 = pn5; } } if (newlines) SCAN_NEWLINES(ts); js_PopStatement(tc); pn->pn_pos.end = pn2->pn_pos.end = ts->token.pos.end; pn->pn_kid1 = pn1; pn->pn_kid2 = pn2; pn->pn_kid3 = pn3; return pn; bad_switch: if (newlines) SCAN_NEWLINES(ts); return NULL; } #endif /* JS_HAS_SWITCH_STATEMENT */ case TOK_WHILE: pn = NewParseNode(cx, &ts->token, PN_BINARY); 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, &ts->token, PN_BINARY); 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, "missing while after do-loop body"); 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, &ts->token, PN_BINARY); if (!pn) return NULL; js_PushStatement(tc, &stmtInfo, STMT_FOR_LOOP, -1); MUST_MATCH_TOKEN(TOK_LP, "missing ( after for"); /* balance) */ 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 (tt == TOK_VAR) { (void) js_GetToken(cx, ts); pn1 = Variables(cx, ts, tc); } else { pn1 = Expr(cx, ts, tc); } if (!pn1) return NULL; } /* * There are three kinds of for/in syntax (see ECMA 12.6.3): * 1. for (i in o) ... * 2. for (var i in o) ... * 3. for (var i = e in o) ... * The 'var i = e in o' in 3 will be parsed as a variable declaration * with an 'in' expression as its initializer, leaving ts->pushback at * the right parenthesis. This condition tests 1, then 3, then 2: */ if (pn1 && (pn1->pn_type == TOK_IN || (pn1->pn_type == TOK_VAR && ts->pushback.type == TOK_RP) || js_MatchToken(cx, ts, TOK_IN))) { stmtInfo.type = STMT_FOR_IN_LOOP; switch (pn1->pn_type) { case TOK_IN: pn2 = pn1; pn1 = pn1->pn_left; break; case TOK_VAR: if (pn1->pn_count != 1) { js_ReportCompileError(cx, ts, "invalid for/in variables"); return NULL; } pn2 = pn1->pn_head->pn_expr; if (pn2) { /* pn1 is 'var i = e' -- must pop e before loop. */ pn1->pn_op = JSOP_POP; if (pn2->pn_type == TOK_IN) { /* Found 'var i = e in o' -- make 'in' be the root. */ PR_ASSERT(ts->pushback.type == TOK_RP); pn1->pn_head->pn_expr = pn2->pn_left; pn2->pn_left = pn1; break; } } /* FALL THROUGH */ default: pn2 = NULL; break; } /* Check that the left side of the 'in' is valid. */ if (pn1->pn_type != TOK_VAR && pn1->pn_type != TOK_NAME && pn1->pn_type != TOK_DOT && pn1->pn_type != TOK_LB) { js_ReportCompileError(cx, ts, "invalid for/in left-hand side"); return NULL; } /* Parse the object expression as the right operand of 'in'. */ if (!pn2) { pn2 = NewBinary(cx, TOK_IN, JSOP_NOP, pn1, Expr(cx, ts, tc)); if (!pn2) return NULL; } pn->pn_left = pn2; } else { /* Parse the loop condition or null into pn2. */ MUST_MATCH_TOKEN(TOK_SEMI, "missing ; after for-loop initializer"); 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, "missing ; after for-loop condition"); 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, &ts->token, PN_TERNARY); if (!pn4) return NULL; pn4->pn_type = TOK_RESERVED; pn4->pn_kid1 = pn1; pn4->pn_kid2 = pn2; pn4->pn_kid3 = pn3; pn->pn_left = pn4; } /* (balance: */ MUST_MATCH_TOKEN(TOK_RP, "missing ) after for-loop control"); /* 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: /* * try nodes are ternary. * kid1 is the try Statement * kid2 is the catch node * kid3 is the finally Statement */ pn = NewParseNode(cx, &ts->token, PN_TERNARY); pn1 = Statement(cx, ts, tc); if (!pn1) return NULL; if (js_PeekToken(cx, ts) == TOK_CATCH) { pn2 = NewParseNode(cx, &ts->token, PN_BINARY); if (!pn2) return NULL; (void)js_GetToken(cx, ts); MUST_MATCH_TOKEN(TOK_LP, "missing ( after catch"); /* balance) */ tt = js_PeekToken(cx, ts); if (tt == TOK_VAR) { (void)js_GetToken(cx, ts); pn4 = Variables(cx, ts, tc); if (!pn4) return NULL; if (pn4->pn_count != 1) { js_ReportCompileError(cx, ts, "only one variable declaration permitted in catch declaration"); return NULL; } } else { pn4 = Expr(cx, ts, tc); if (!pn4) return NULL; /* restrict the expression to instanceof */ if (pn4->pn_op != JSOP_INSTANCEOF && pn4->pn_op != JSOP_NAME && pn4->pn_op != JSOP_GETARG && pn4->pn_op != JSOP_GETVAR) { js_ReportCompileError(cx, ts, "catch conditional must be instanceof or variable declaration"); PR_ASSERT(0); return NULL; } } /* rewrite the declaration/conditional expr as appropriate */ switch(pn4->pn_type) { case TOK_NAME: switch(pn4->pn_op) { case JSOP_NAME: pn4->pn_op = JSOP_SETNAME; break; case JSOP_GETARG: pn4->pn_op = JSOP_SETARG; break; default: PR_ASSERT(0); } break; case TOK_VAR: PR_ASSERT(pn4->pn_head->pn_type == TOK_NAME); switch(pn4->pn_head->pn_op) { case JSOP_GETVAR: pn4->pn_head->pn_op = JSOP_SETVAR; break; case JSOP_GETARG: pn4->pn_head->pn_op = JSOP_SETARG; break; case JSOP_NAME: pn4->pn_head->pn_op = JSOP_SETNAME; case JSOP_NOP: break; default: PR_ASSERT(0); } break; case TOK_INSTANCEOF: PR_ASSERT(0); default: PR_ASSERT(0); } pn2->pn_left = pn4; /* (balance: */ MUST_MATCH_TOKEN(TOK_RP, "missing ) after catch declaration"); pn2->pn_right = Statement(cx, ts, tc); if (!pn2->pn_right) return NULL; } else { pn2 = NULL; } if (js_MatchToken(cx, ts, TOK_FINALLY)) { pn3 = Statement(cx, ts, tc); if (!pn3) return NULL; } else { pn3 = NULL; } if (!pn2 && !pn3) { js_ReportCompileError(cx, ts, "missing catch or finally after try"); return NULL; } tc->tryCount++; pn->pn_kid1 = pn1; pn->pn_kid2 = pn2; pn->pn_kid3 = pn3; return pn; case TOK_THROW: pn = NewParseNode(cx, &ts->token, PN_UNARY); if (!pn) return NULL; pn2 = Expr(cx, ts, tc); if (!pn2) return NULL; pn->pn_pos.end = pn2->pn_pos.end; if (pn->pn_pos.end.lineno == ts->lineno && !WellTerminated(cx, ts, TOK_ERROR)) { return NULL; } pn->pn_op = JSOP_THROW; pn->pn_kid = pn2; break; #endif /* JS_HAS_EXCEPTIONS */ case TOK_BREAK: pn = NewParseNode(cx, &ts->token, PN_NULLARY); 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_ReportCompileError(cx, ts, "label not found"); return NULL; } if (stmt->type == STMT_LABEL && stmt->label == label) break; } } else { for (; ; stmt = stmt->down) { if (!stmt) { js_ReportCompileError(cx, ts, "invalid break"); return NULL; } if (STMT_IS_LOOP(stmt) || stmt->type == STMT_SWITCH) break; } } if (label) pn->pn_pos.end = ts->token.pos.end; break; case TOK_CONTINUE: pn = NewParseNode(cx, &ts->token, PN_NULLARY); 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_ReportCompileError(cx, ts, "label not found"); return NULL; } if (stmt->type == STMT_LABEL) { if (stmt->label == label) { if (!stmt2 || !STMT_IS_LOOP(stmt2)) { js_ReportCompileError(cx, ts, "invalid continue"); return NULL; } break; } } else { stmt2 = stmt; } } } else { for (; ; stmt = stmt->down) { if (!stmt) { js_ReportCompileError(cx, ts, "invalid continue"); return NULL; } if (STMT_IS_LOOP(stmt)) break; } } if (label) pn->pn_pos.end = ts->token.pos.end; break; case TOK_WITH: pn = NewParseNode(cx, &ts->token, PN_BINARY); if (!pn) return NULL; MUST_MATCH_TOKEN(TOK_LP, "missing ( before with-statement object"); /* balance) */ pn2 = Expr(cx, ts, tc); if (!pn2) return NULL; /* (balance: */ MUST_MATCH_TOKEN(TOK_RP, "missing ) after with-statement object"); 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; return pn; case TOK_VAR: pn = Variables(cx, ts, tc); if (!pn) return NULL; if (pn->pn_pos.end.lineno == ts->lineno && !WellTerminated(cx, ts, TOK_ERROR)) { return NULL; } /* Tell js_EmitTree to generate a final POP. */ pn->pn_op = JSOP_POP; break; case TOK_RETURN: if (!(tc->flags & TCF_IN_FUNCTION)) { js_ReportCompileError(cx, ts, "invalid return"); return NULL; } pn = NewParseNode(cx, &ts->token, PN_UNARY); 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; if (pn2->pn_pos.end.lineno == ts->lineno && !WellTerminated(cx, ts, TOK_ERROR)) { 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 ((tc->flags & (TCF_RETURN_EXPR | TCF_RETURN_VOID)) == (TCF_RETURN_EXPR | TCF_RETURN_VOID)) { js_ReportCompileError(cx, ts, badreturn_str); return NULL; } break; case TOK_LC: js_PushStatement(tc, &stmtInfo, STMT_BLOCK, -1); pn = Statements(cx, ts, tc); if (!pn) return NULL; /* {balance: */ MUST_MATCH_TOKEN(TOK_RC, "missing } in compound statement"); js_PopStatement(tc); return pn; case TOK_EOL: case TOK_SEMI: pn = NewParseNode(cx, &ts->token, PN_UNARY); if (!pn) return NULL; pn->pn_type = TOK_SEMI; pn->pn_kid = NULL; return pn; case TOK_ERROR: return NULL; default: lastExprType = ts->token.type; js_UngetToken(ts); pn2 = Expr(cx, ts, tc); if (!pn2) return NULL; tt = ts->pushback.type; if (tt == TOK_COLON) { if (pn2->pn_type != TOK_NAME) { js_ReportCompileError(cx, ts, "invalid label"); return NULL; } label = pn2->pn_atom; for (stmt = tc->topStmt; stmt; stmt = stmt->down) { if (stmt->type == STMT_LABEL && stmt->label == label) { js_ReportCompileError(cx, ts, "duplicate label"); return NULL; } } 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; } /* Check explicity against (multi-line) function statement */ if (pn2->pn_pos.end.lineno == ts->lineno && !WellTerminated(cx, ts, lastExprType)) { return NULL; } pn = NewParseNode(cx, &ts->token, PN_UNARY); if (!pn) return NULL; pn->pn_type = TOK_SEMI; pn->pn_pos = pn2->pn_pos; pn->pn_kid = pn2; break; } (void) js_MatchToken(cx, ts, TOK_SEMI); return pn; } static JSParseNode * Variables(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) { JSParseNode *pn, *pn2; JSObject *obj, *pobj; JSFunction *fun; JSClass *clasp; JSPropertyOp getter, setter; JSAtom *atom; JSProperty *prop; JSScopeProperty *sprop; JSBool ok; PR_ASSERT(ts->token.type == TOK_VAR); pn = NewParseNode(cx, &ts->token, PN_LIST); if (!pn) return NULL; pn->pn_op = JSOP_NOP; PN_INIT_LIST(pn); obj = js_FindVariableScope(cx, &fun); if (!obj) return NULL; clasp = OBJ_GET_CLASS(cx, obj); if (fun && clasp == &js_FunctionClass) { getter = js_GetLocalVariable; setter = js_SetLocalVariable; } else { getter = clasp->getProperty; setter = clasp->setProperty; } do { MUST_MATCH_TOKEN(TOK_NAME, "missing variable name"); atom = ts->token.t_atom; pn2 = NewParseNode(cx, &ts->token, PN_NAME); if (!pn2) return NULL; pn2->pn_op = JSOP_NAME; pn2->pn_atom = atom; pn2->pn_expr = NULL; pn2->pn_slot = -1; 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) { getter = sprop->getter; #ifdef CHECK_ARGUMENT_HIDING js_ReportCompileError(cx, ts, "variable %s hides argument", ATOM_BYTES(atom)); ok = JS_FALSE; #else ok = JS_TRUE; #endif } else { ok = JS_TRUE; if (fun) { /* Not an argument, must be a redeclared local var. */ if (clasp == &js_FunctionClass) { PR_ASSERT(sprop->getter == js_GetLocalVariable); PR_ASSERT(JSVAL_IS_INT(sprop->id) && JSVAL_TO_INT(sprop->id) < fun->nvars); } } else { /* Global var: (re-)set id a la js_DefineProperty. */ sprop->id = ATOM_KEY(atom); } sprop->getter = getter; sprop->setter = setter; sprop->attrs |= JSPROP_ENUMERATE | JSPROP_PERMANENT; sprop->attrs &= ~JSPROP_READONLY; } } else { sprop = NULL; if (prop) { OBJ_DROP_PROPERTY(cx, pobj, prop); prop = NULL; } ok = OBJ_DEFINE_PROPERTY(cx, obj, (jsid)atom, JSVAL_VOID, getter, setter, JSPROP_ENUMERATE | JSPROP_PERMANENT, &prop); if (ok && prop) { pobj = obj; if (getter == js_GetLocalVariable) { sprop = (JSScopeProperty *)prop; sprop->id = INT_TO_JSVAL(fun->nvars++); } } } if (js_MatchToken(cx, ts, TOK_ASSIGN)) { if (ts->token.t_op != JSOP_NOP) { js_ReportCompileError(cx, ts, "invalid variable initialization"); ok = JS_FALSE; } else { pn2->pn_expr = AssignExpr(cx, ts, tc); if (pn2->pn_expr) pn2->pn_op = JSOP_SETNAME2; else ok = JS_FALSE; } } if (ok && fun && clasp == &js_FunctionClass && !InWithStatement(tc)) { PR_ASSERT(sprop); if (getter == js_GetArgument) { PR_ASSERT(sprop && JSVAL_IS_INT(sprop->id)); pn2->pn_op = (pn2->pn_op == JSOP_NAME) ? JSOP_GETARG : JSOP_SETARG; pn2->pn_slot = JSVAL_TO_INT(sprop->id); } else if (getter == js_GetLocalVariable) { PR_ASSERT(sprop && JSVAL_IS_INT(sprop->id)); pn2->pn_op = (pn2->pn_op == JSOP_NAME) ? JSOP_GETVAR : JSOP_SETVAR; pn2->pn_slot = JSVAL_TO_INT(sprop->id); } } 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, &ts->token, PN_LIST); 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; } /* ZZZbe don't create functions till codegen? or at least don't bind fn name */ static JSBool LookupArgOrVar(JSContext *cx, JSAtom *atom, JSTreeContext *tc, JSOp *opp, jsint *slotp) { JSObject *obj, *pobj; JSScopeProperty *sprop; obj = cx->fp->scopeChain; if (OBJ_GET_CLASS(cx, obj) != &js_FunctionClass) return JS_TRUE; if (InWithStatement(tc)) return JS_TRUE; if (!js_LookupProperty(cx, obj, (jsid)atom, &pobj, (JSProperty **)&sprop)) return JS_FALSE; *opp = JSOP_NAME; *slotp = -1; if (sprop) { if (sprop->getter == js_GetArgument) { *opp = JSOP_GETARG; *slotp = JSVAL_TO_INT(sprop->id); } else if (sprop->getter == js_GetLocalVariable) { *opp = JSOP_GETVAR; *slotp = JSVAL_TO_INT(sprop->id); } OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop); } return JS_TRUE; } static JSParseNode * AssignExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) { JSParseNode *pn, *pn2; JSOp op; pn = CondExpr(cx, ts, tc); if (pn && js_MatchToken(cx, ts, TOK_ASSIGN)) { op = ts->token.t_op; for (pn2 = pn; pn2->pn_type == TOK_RP; pn2 = pn2->pn_kid) ; switch (pn2->pn_type) { case TOK_NAME: if (pn2->pn_slot >= 0) { PR_ASSERT(pn2->pn_op == JSOP_GETARG || pn2->pn_op == JSOP_GETVAR); if (pn2->pn_op == JSOP_GETARG) pn2->pn_op = JSOP_SETARG; else pn2->pn_op = JSOP_SETVAR; } else { pn2->pn_op = JSOP_SETNAME2; } break; case TOK_DOT: pn2->pn_op = JSOP_SETPROP; break; case TOK_LB: pn2->pn_op = JSOP_SETELEM; break; default: js_ReportCompileError(cx, ts, "invalid assignment left-hand side"); return NULL; } pn = NewBinary(cx, TOK_ASSIGN, op, pn2, AssignExpr(cx, ts, tc)); } return pn; } static JSParseNode * CondExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) { JSParseNode *pn, *pn1, *pn2, *pn3; pn = OrExpr(cx, ts, tc); if (pn && js_MatchToken(cx, ts, TOK_HOOK)) { pn1 = pn; pn = NewParseNode(cx, &ts->token, PN_TERNARY); if (!pn) return NULL; pn2 = AssignExpr(cx, ts, tc); if (!pn2) return NULL; MUST_MATCH_TOKEN(TOK_COLON, "missing : in conditional expression"); 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)); 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)); 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)); 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)); 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)); 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 = ts->token.t_op; pn = NewBinary(cx, TOK_EQOP, op, pn, RelExpr(cx, ts, tc)); } return pn; } static JSParseNode * RelExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) { JSParseNode *pn; JSTokenType tt; JSOp op; pn = ShiftExpr(cx, ts, tc); while (pn && (js_MatchToken(cx, ts, TOK_RELOP) #if JS_HAS_IN_OPERATOR || js_MatchToken(cx, ts, TOK_IN) #endif #if JS_HAS_INSTANCEOF || js_MatchToken(cx, ts, TOK_INSTANCEOF) #endif )) { tt = ts->token.type; op = ts->token.t_op; pn = NewBinary(cx, tt, op, pn, ShiftExpr(cx, ts, tc)); } 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 = ts->token.t_op; pn = NewBinary(cx, TOK_SHOP, op, pn, AddExpr(cx, ts, 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 = ts->token.type; op = (tt == TOK_PLUS) ? JSOP_ADD : JSOP_SUB; pn = NewBinary(cx, tt, op, pn, MulExpr(cx, ts, 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 = ts->token.type; op = ts->token.t_op; pn = NewBinary(cx, tt, op, pn, UnaryExpr(cx, ts, 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 && kid->pn_type != TOK_LB) { js_ReportCompileError(cx, ts, "invalid %s 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) { jsint num; JSOp op; kid = SetLvalKid(cx, ts, pn, kid, incop_name_str[tt == TOK_DEC]); if (!kid) return JS_FALSE; num = -1; switch (kid->pn_type) { case TOK_NAME: if (!LookupArgOrVar(cx, kid->pn_atom, tc, &op, &num)) return JS_FALSE; if (op == JSOP_GETARG) { op = (tt == TOK_INC) ? (preorder ? JSOP_INCARG : JSOP_ARGINC) : (preorder ? JSOP_DECARG : JSOP_ARGDEC); } else if (op == JSOP_GETVAR) { op = (tt == TOK_INC) ? (preorder ? JSOP_INCVAR : JSOP_VARINC) : (preorder ? JSOP_DECVAR : JSOP_VARDEC); } else { op = (tt == TOK_INC) ? (preorder ? JSOP_INCNAME : JSOP_NAMEINC) : (preorder ? JSOP_DECNAME : JSOP_NAMEDEC); } break; case TOK_DOT: op = (tt == TOK_INC) ? (preorder ? JSOP_INCPROP : JSOP_PROPINC) : (preorder ? JSOP_DECPROP : JSOP_PROPDEC); break; case TOK_LB: op = (tt == TOK_INC) ? (preorder ? JSOP_INCELEM : JSOP_ELEMINC) : (preorder ? JSOP_DECELEM : JSOP_ELEMDEC); break; default: PR_ASSERT(0); } pn->pn_op = op; pn->pn_num = num; return JS_TRUE; } static JSParseNode * UnaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) { JSTokenType tt; JSParseNode *pn, *pn2, *pn3; 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, &ts->token, PN_UNARY); if (!pn) return NULL; pn->pn_type = TOK_UNARYOP; /* PLUS and MINUS are binary */ pn->pn_op = ts->token.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->token, PN_UNARY); if (!pn) return NULL; pn2 = MemberExpr(cx, ts, tc); 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_NEW: /* Allow 'new this.ctor(...)' constructor expressions. */ pn = NewParseNode(cx, &ts->token, PN_LIST); if (!pn) return NULL; tt = js_GetToken(cx, ts); pn2 = NewParseNode(cx, &ts->token, PN_NULLARY); if (!pn2) return NULL; switch (tt) { case TOK_NAME: pn2->pn_op = JSOP_NAME; pn2->pn_atom = ts->token.t_atom; pn2->pn_slot = -1; break; case TOK_PRIMARY: if (ts->token.t_op == JSOP_THIS) { pn2->pn_op = JSOP_THIS; break; } /* FALL THROUGH */ default: js_ReportCompileError(cx, ts, "missing name after new operator"); return NULL; } while (js_MatchToken(cx, ts, TOK_DOT)) { pn3 = NewParseNode(cx, &ts->token, PN_NAME); if (!pn3) return NULL; MUST_MATCH_TOKEN(TOK_NAME, "missing name in constructor expression"); pn3->pn_pos.begin = pn2->pn_pos.begin; pn3->pn_pos.end = ts->token.pos.end; pn3->pn_op = JSOP_GETPROP; pn3->pn_expr = pn2; pn3->pn_atom = ts->token.t_atom; pn2 = pn3; } PN_INIT_LIST_1(pn, pn2); if (js_MatchToken(cx, ts, TOK_LP) && !js_MatchToken(cx, ts, TOK_RP)) { do { pn2 = AssignExpr(cx, ts, tc); if (!pn2) return NULL; PN_APPEND(pn, pn2); } while (js_MatchToken(cx, ts, TOK_COMMA)); /* (balance: */ MUST_MATCH_TOKEN(TOK_RP, "missing ) after constructor argument list"); } if (pn->pn_count - 1 >= ARGC_LIMIT) { JS_ReportError(cx, "too many constructor arguments"); return NULL; } pn->pn_pos.end = PN_LAST(pn)->pn_pos.end; break; case TOK_DELETE: pn = NewParseNode(cx, &ts->token, PN_UNARY); if (!pn) return NULL; pn2 = MemberExpr(cx, ts, tc); if (!pn2) return NULL; if (!SetLvalKid(cx, ts, pn, pn2, js_delete_str)) return NULL; pn->pn_pos.end = pn2->pn_pos.end; break; case TOK_ERROR: return NULL; default: js_UngetToken(ts); pn = MemberExpr(cx, ts, tc); if (!pn) return NULL; /* Don't look across a newline boundary for a postfix incop. */ if (pn->pn_pos.end.lineno == ts->lineno) { tt = js_PeekTokenSameLine(cx, ts); if (tt == TOK_INC || tt == TOK_DEC) { (void) js_GetToken(cx, ts); pn2 = NewParseNode(cx, &ts->token, PN_UNARY); 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 JSParseNode * MemberExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) { JSParseNode *pn, *pn2, *pn3; JSTokenType tt; JSBool matched; pn = PrimaryExpr(cx, ts, tc); if (!pn) return NULL; while ((tt = js_GetToken(cx, ts)) > TOK_EOF) { if (tt == TOK_DOT) { pn2 = NewParseNode(cx, &ts->token, PN_NAME); if (!pn2) return NULL; MUST_MATCH_TOKEN(TOK_NAME, "missing name after . operator"); pn2->pn_pos.begin = pn->pn_pos.begin; pn2->pn_pos.end = ts->token.pos.end; pn2->pn_op = JSOP_GETPROP; pn2->pn_expr = pn; pn2->pn_atom = ts->token.t_atom; } else if (tt == TOK_LB) { pn2 = NewParseNode(cx, &ts->token, PN_BINARY); if (!pn2) return NULL; pn3 = Expr(cx, ts, tc); if (!pn3) return NULL; /* [balance: */ MUST_MATCH_TOKEN(TOK_RB, "missing ] in index expression"); pn2->pn_pos.begin = pn->pn_pos.begin; pn2->pn_pos.end = ts->token.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 (tt == TOK_LP) { pn2 = NewParseNode(cx, &ts->token, PN_LIST); if (!pn2) return NULL; PN_INIT_LIST_1(pn2, pn); pn2->pn_op = JSOP_CALL; ts->flags |= TSF_REGEXP; matched = js_MatchToken(cx, ts, TOK_RP); ts->flags &= ~TSF_REGEXP; if (!matched) { do { pn3 = AssignExpr(cx, ts, tc); if (!pn3) return NULL; PN_APPEND(pn2, pn3); } while (js_MatchToken(cx, ts, TOK_COMMA)); /* (balance: */ MUST_MATCH_TOKEN(TOK_RP, "missing ) after argument list"); } if (pn2->pn_count - 1 >= ARGC_LIMIT) { JS_ReportError(cx, "too many function arguments"); 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; #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 ts->flags |= TSF_REGEXP; tt = js_GetToken(cx, ts); ts->flags &= ~TSF_REGEXP; 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; jsint atomIndex; pn = NewParseNode(cx, &ts->token, PN_LIST); 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) pn2 = NewParseNode(cx, &ts->token, PN_NULLARY); else pn2 = AssignExpr(cx, ts, tc); if (!pn2) return NULL; PN_APPEND(pn, pn2); if (!js_MatchToken(cx, ts, TOK_COMMA)) break; } /* [balance: */ MUST_MATCH_TOKEN(TOK_RB, "missing ] after element list"); } pn->pn_pos.end = ts->token.pos.end; return pn; } case TOK_LC: pn = NewParseNode(cx, &ts->token, PN_LIST); 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 { tt = js_GetToken(cx, ts); switch (tt) { case TOK_NUMBER: pn3 = NewParseNode(cx, &ts->token, PN_NULLARY); if (pn3) pn3->pn_dval = ts->token.t_dval; break; case TOK_NAME: case TOK_STRING: pn3 = NewParseNode(cx, &ts->token, PN_NULLARY); if (pn3) pn3->pn_atom = ts->token.t_atom; break; case TOK_RC: goto end_obj_init; default: js_ReportCompileError(cx, ts, "invalid property id"); return NULL; } MUST_MATCH_TOKEN(TOK_COLON, "missing : after property id"); pn2 = NewBinary(cx, TOK_COLON, JSOP_INITPROP, pn3, AssignExpr(cx, ts, tc)); if (!pn2) return NULL; PN_APPEND(pn, pn2); } while (js_MatchToken(cx, ts, TOK_COMMA)); /* {balance: */ MUST_MATCH_TOKEN(TOK_RC, "missing } after property list"); } end_obj_init: pn->pn_pos.end = ts->token.pos.end; return pn; #if JS_HAS_SHARP_VARS case TOK_DEFSHARP: if (defsharp) goto badsharp; defsharp = NewParseNode(cx, &ts->token, PN_UNARY); if (!defsharp) return NULL; defsharp->pn_num = (jsint) ts->token.t_dval; defsharp->pn_kid = NULL; goto again; case TOK_USESHARP: /* Check for forward/dangling references at runtime, to allow eval. */ pn = NewParseNode(cx, &ts->token, PN_NULLARY); if (!pn) return NULL; pn->pn_num = (jsint) ts->token.t_dval; notsharp = JS_TRUE; break; #endif /* JS_HAS_SHARP_VARS */ #endif /* JS_HAS_INITIALIZERS */ case TOK_LP: pn = NewParseNode(cx, &ts->token, PN_UNARY); if (!pn) return NULL; pn2 = Expr(cx, ts, tc); if (!pn2) return NULL; /* (balance: */ MUST_MATCH_TOKEN(TOK_RP, "missing ) in parenthetical"); pn->pn_type = TOK_RP; pn->pn_pos.end = ts->token.pos.end; pn->pn_kid = pn2; break; case TOK_STRING: notsharp = JS_TRUE; /* FALL THROUGH */ case TOK_NAME: case TOK_OBJECT: pn = NewParseNode(cx, &ts->token, PN_NULLARY); if (!pn) return NULL; pn->pn_op = ts->token.t_op; pn->pn_atom = ts->token.t_atom; pn->pn_slot = -1; if (tt == TOK_NAME) { if (!LookupArgOrVar(cx, pn->pn_atom, tc, &pn->pn_op, &pn->pn_slot)) return NULL; pn->pn_arity = PN_NAME; pn->pn_expr = NULL; } break; case TOK_NUMBER: pn = NewParseNode(cx, &ts->token, PN_NULLARY); if (!pn) return NULL; pn->pn_dval = ts->token.t_dval; notsharp = JS_TRUE; break; case TOK_PRIMARY: pn = NewParseNode(cx, &ts->token, PN_NULLARY); if (!pn) return NULL; pn->pn_op = ts->token.t_op; notsharp = JS_TRUE; break; #if !JS_HAS_EXPORT_IMPORT case TOK_EXPORT: case TOK_IMPORT: #endif case TOK_RESERVED: js_ReportCompileError(cx, ts, "%s is a reserved identifier", js_DeflateString(cx, ts->token.ptr, ts->token.pos.end.index - ts->token.pos.begin.index)); return NULL; case TOK_ERROR: /* The scanner or one of its subroutines reported the error. */ return NULL; default: js_ReportCompileError(cx, ts, "syntax error"); return NULL; } #if JS_HAS_SHARP_VARS if (defsharp) { if (notsharp) { badsharp: js_ReportCompileError(cx, ts, "invalid sharp variable definition"); return NULL; } defsharp->pn_kid = pn; return defsharp; } #endif return pn; } JSBool js_FoldConstants(JSContext *cx, JSParseNode *pn) { JSParseNode *pn1, *pn2, *pn3; switch (pn->pn_arity) { case PN_FUNC: if (!js_FoldConstants(cx, pn->pn_body)) return JS_FALSE; break; case PN_LIST: for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { if (!js_FoldConstants(cx, pn2)) 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)) return JS_FALSE; if (pn2 && !js_FoldConstants(cx, pn2)) return JS_FALSE; if (pn3 && !js_FoldConstants(cx, pn3)) return JS_FALSE; break; case PN_BINARY: /* Neither kid may be null. */ pn1 = pn->pn_left; pn2 = pn->pn_right; if (!js_FoldConstants(cx, pn1)) return JS_FALSE; if (!js_FoldConstants(cx, pn2)) 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)) return JS_FALSE; break; case PN_NAME: pn1 = pn->pn_expr; if (pn1 && !js_FoldConstants(cx, pn1)) return JS_FALSE; break; case PN_NULLARY: break; } switch (pn->pn_type) { case TOK_PLUS: if (pn1->pn_type == TOK_STRING && pn2->pn_type == TOK_STRING) { JSString *str1, *str2; size_t length, length1, length2, nbytes; void *mark; jschar *chars; /* Concatenate string constants. */ str1 = ATOM_TO_STRING(pn1->pn_atom); str2 = ATOM_TO_STRING(pn2->pn_atom); length1 = str1->length; length2 = str2->length; length = length1 + length2; nbytes = (length + 1) * sizeof(jschar); mark = PR_ARENA_MARK(&cx->tempPool); PR_ARENA_ALLOCATE(chars, &cx->tempPool, nbytes); if (!chars) { JS_ReportOutOfMemory(cx); return JS_FALSE; } js_strncpy(chars, str1->chars, length1); js_strncpy(chars + length1, str2->chars, length2); chars[length] = 0; pn->pn_atom = js_AtomizeChars(cx, chars, length, 0); if (!pn->pn_atom) return JS_FALSE; PR_ARENA_RELEASE(&cx->tempPool, mark); pn->pn_type = TOK_STRING; pn->pn_op = JSOP_STRING; pn->pn_arity = PN_NULLARY; break; } /* FALL THROUGH */ case TOK_SHOP: case TOK_MINUS: case TOK_STAR: case TOK_DIVOP: if (pn1->pn_type == TOK_NUMBER && pn2->pn_type == TOK_NUMBER) { jsdouble d, d2; int32 i, j; uint32 u; /* Fold two numeric constants. */ d = pn1->pn_dval; d2 = pn2->pn_dval; switch (pn->pn_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 = (pn->pn_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) { #ifdef XP_PC /* 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 { #ifdef XP_PC /* 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:; } pn->pn_type = TOK_NUMBER; pn->pn_op = JSOP_NUMBER; pn->pn_arity = PN_NULLARY; pn->pn_dval = d; } break; case TOK_UNARYOP: if (pn1->pn_type == TOK_NUMBER) { jsdouble d; int32 i; /* Operate on one numeric constants. */ 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: d = -d; 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; } break; default:; } return JS_TRUE; }