зеркало из https://github.com/mozilla/gecko-dev.git
2585 строки
61 KiB
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;
|
|
}
|