gecko-dev/js/ref/jsparse.c

2585 строки
61 KiB
C

/* -*- 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 <stdlib.h>
#include <string.h>
#include <math.h>
#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 <INPUT> 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;
}