Files
Mozilla/mozilla/js/src/jsparse.c
ltabb 8ed5afe62c Free the lizard
git-svn-id: svn://10.0.0.236/trunk@10 18797224-902f-48f8-a5cc-f745e15eee43
1998-03-28 02:44:41 +00:00

2456 lines
62 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 bytecode as it
* parses, with backpatching and code rewriting limited to downward branches,
* for loops, and assignable "lvalue" expressions.
*
* 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 "prtypes.h"
#ifndef NSPR20
#include "prarena.h"
#else
#include "plarena.h"
#endif
#include "prlog.h"
#include "prprf.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 JSBool
JSParser(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg);
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 return false.
* NB: this macro uses cx, ts, and cg from its lexical environment.
*/
#define MUST_MATCH_TOKEN_THROW(tt, err, throw) \
PR_BEGIN_MACRO \
if (js_GetToken(cx, ts, cg) != tt) { \
js_ReportCompileError(cx, ts, err); \
throw; \
} \
PR_END_MACRO
#define MUST_MATCH_TOKEN(tt, err) \
MUST_MATCH_TOKEN_THROW(tt, err, return JS_FALSE)
/*
* Emit a bytecode and its 2-byte constant (atom) index immediate operand.
* NB: We use cx and cg from our caller's lexical environment, and return
* false on error.
*/
#define EMIT_CONST_ATOM_OP(op, atomIndex) \
PR_BEGIN_MACRO \
if (js_Emit3(cx, cg, op, ATOM_INDEX_HI(atomIndex), \
ATOM_INDEX_LO(atomIndex)) < 0) { \
return JS_FALSE; \
} \
PR_END_MACRO
/*
* Parse a top-level JS script.
*/
JS_FRIEND_API(JSBool)
js_Parse(JSContext *cx, JSObject *chain, JSTokenStream *ts, JSCodeGenerator *cg)
{
JSStackFrame *fp, frame;
JSTokenType stop, tt;
JSBool ok;
PR_ASSERT(JS_IS_LOCKED(cx));
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, cg);
ts->flags &= ~TSF_REGEXP;
if (tt == stop || tt <= TOK_EOF) {
if (tt == TOK_ERROR)
ok = JS_FALSE;
break;
}
switch (tt) {
case TOK_FUNCTION:
if (!FunctionStmt(cx, ts, cg))
ok = JS_FALSE;
break;
default:
js_UngetToken(ts);
if (!Statement(cx, ts, cg))
ok = JS_FALSE;
}
} while (ok);
cx->fp = fp;
if (ok)
ok = js_FlushNewlines(cx, ts, cg);
if (!ok) {
CLEAR_PUSHBACK(ts);
js_DropUnmappedAtoms(cx, &cg->atomList);
}
return ok;
}
/*
* Parse a JS function body, which might appear as the value of an event
* handler attribute in a HTML <INPUT> tag.
*/
JSBool
js_ParseFunctionBody(JSContext *cx, JSTokenStream *ts, JSFunction *fun,
JSSymbol *args)
{
uintN lineno, oldflags;
JSCodeGenerator funcg;
JSStackFrame *fp, frame;
JSBool ok;
jsbytecode *pc;
PR_ASSERT(JS_IS_LOCKED(cx));
lineno = ts->lineno;
if (!js_InitCodeGenerator(cx, &funcg, &cx->codePool))
return JS_FALSE;
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 = ts->flags;
ts->flags &= ~(TSF_RETURN_EXPR | TSF_RETURN_VOID);
ts->flags |= TSF_FUNCTION;
ok = Statements(cx, ts, &funcg);
/* Check for falling off the end of a function that returns a value. */
if (ok && (ts->flags & TSF_RETURN_EXPR)) {
for (pc = CG_CODE(&funcg,funcg.lastCodeOffset);
*pc == JSOP_NOP || *pc == JSOP_LEAVEWITH;
pc--) {
/* nothing */;
}
if (*pc != JSOP_RETURN) {
js_ReportCompileError(cx, ts,
"function does not always return a value");
ok = JS_FALSE;
}
}
ts->flags = oldflags;
cx->fp = fp;
if (ok)
ok = js_FlushNewlines(cx, ts, &funcg);
if (!ok) {
CLEAR_PUSHBACK(ts);
js_DropUnmappedAtoms(cx, &funcg.atomList);
} else {
funcg.args = args;
fun->script = js_NewScript(cx, &funcg, ts->filename, lineno,
ts->principals, fun);
if (!fun->script)
ok = JS_FALSE;
}
return ok;
}
#if JS_HAS_LEXICAL_CLOSURE
static JSBool
InWithStatement(JSCodeGenerator *cg)
{
StmtInfo *stmt;
for (stmt = cg->stmtInfo; stmt; stmt = stmt->down) {
if (stmt->type == STMT_WITH)
return JS_TRUE;
}
return JS_FALSE;
}
#endif
static JSBool
FunctionDef(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg, JSBool expr)
{
JSAtomMap map;
JSAtom *funAtom, *argAtom;
JSObject *parent;
JSFunction *fun, *outerFun;
JSBool ok, named;
JSSymbol *arg, *args, **argp;
JSObject *pobj;
JSProperty *prop;
void *mark;
jsval junk;
uint32 i;
/* Save atoms indexed but not mapped for the top-level script. */
if (!js_InitAtomMap(cx, &map, &cg->atomList))
return JS_FALSE;
if (js_MatchToken(cx, ts, cg, TOK_NAME))
funAtom = js_HoldAtom(cx, ts->token.u.atom);
else
funAtom = NULL;
/* Find the nearest variable-declaring scope and use it as our parent. */
parent = js_FindVariableScope(cx, &outerFun);
if (!parent) {
ok = JS_FALSE;
goto out;
}
#if JS_HAS_LEXICAL_CLOSURE
if (!funAtom || InWithStatement(cg) || cx->fp->scopeChain != parent) {
/* Don't name the function if enclosed by a with statement or equiv. */
fun = js_NewFunction(cx, 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. */
args = NULL;
MUST_MATCH_TOKEN_THROW(TOK_LP, "missing ( before formal parameters",
ok = JS_FALSE; goto out);
/* balance) */
if (!js_MatchToken(cx, ts, cg, TOK_RP)) {
argp = &args;
do {
MUST_MATCH_TOKEN_THROW(TOK_NAME, "missing formal parameter",
ok = JS_FALSE; goto out);
argAtom = js_HoldAtom(cx, ts->token.u.atom);
pobj = NULL;
ok = js_LookupProperty(cx, fun->object, (jsval)argAtom, &pobj,
&prop);
if (!ok)
goto out;
if (prop && prop->object == fun->object) {
if (prop->getter == js_GetArgument) {
js_ReportCompileError(cx, ts,
"duplicate formal argument %s",
ATOM_BYTES(argAtom));
ok = JS_FALSE;
goto out;
}
prop->getter = js_GetArgument;
prop->setter = js_SetArgument;
prop->flags |= JSPROP_ENUMERATE | JSPROP_PERMANENT;
} else {
prop = js_DefineProperty(cx, fun->object,
(jsval)argAtom, JSVAL_VOID,
js_GetArgument, js_SetArgument,
JSPROP_ENUMERATE | JSPROP_PERMANENT);
}
js_DropAtom(cx, argAtom);
if (!prop) {
ok = JS_FALSE;
goto out;
}
prop->id = INT_TO_JSVAL(fun->nargs++);
arg = prop->symbols;
*argp = arg;
argp = &arg->next;
} while (js_MatchToken(cx, ts, cg, 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);
mark = PR_ARENA_MARK(&cx->codePool);
ok = js_ParseFunctionBody(cx, ts, fun, args);
if (ok) {
MUST_MATCH_TOKEN_THROW(TOK_RC, "missing } after function body",
ok = JS_FALSE; goto out);
fun->script->depth += fun->nvars;
/* Generate a setline note for script that follows this function. */
if (js_NewSrcNote2(cx, cg, SRC_SETLINE, (ptrdiff_t)TRUE_LINENO(ts)) < 0)
ok = JS_FALSE;
}
PR_ARENA_RELEASE(&cx->codePool, mark);
out:
if (!ok && named)
(void) js_DeleteProperty(cx, parent, (jsval)funAtom, &junk);
for (i = 0; i < map.length; i++) {
js_IndexAtom(cx, map.vector[i], &cg->atomList);
PR_ASSERT(map.vector[i]->index == i);
}
js_FreeAtomMap(cx, &map);
if (funAtom)
js_DropAtom(cx, funAtom);
#if JS_HAS_LEXICAL_CLOSURE
if (fun) {
JSBool closure = (outerFun ||
InWithStatement(cg) ||
cx->fp->scopeChain != parent);
if (closure || expr) {
jsatomid atomIndex;
/* Make the function object a literal in the outer script's pool. */
funAtom = js_AtomizeObject(cx, fun->object, ATOM_NOHOLD);
if (!funAtom)
return JS_FALSE;
/* Emit a closure or push naming the literal in its immediate. */
atomIndex = js_IndexAtom(cx, funAtom, &cg->atomList);
EMIT_CONST_ATOM_OP(closure ? JSOP_CLOSURE : JSOP_OBJECT, atomIndex);
}
}
#endif
return ok;
}
static JSBool
FunctionStmt(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
return FunctionDef(cx, ts, cg, JS_FALSE);
}
#if JS_HAS_LEXICAL_CLOSURE
static JSBool
FunctionExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
return FunctionDef(cx, ts, cg, JS_TRUE);
}
#endif
static JSBool
Statements(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
uintN newlines;
JSBool ok;
JSTokenType tt;
newlines = ts->flags & TSF_NEWLINES;
if (newlines)
HIDE_NEWLINES(ts);
ok = JS_TRUE;
while ((tt = js_PeekToken(cx, ts, cg)) > TOK_EOF && tt != TOK_RC) {
ok = Statement(cx, ts, cg);
if (!ok)
break;
}
if (tt == TOK_ERROR)
ok = JS_FALSE;
if (newlines)
SCAN_NEWLINES(ts);
return ok;
}
static JSBool
Condition(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
jsbytecode *pc;
JSOp lastop;
jsatomid atomIndex;
MUST_MATCH_TOKEN(TOK_LP, "missing ( before condition");
if (!Expr(cx, ts, cg))
return JS_FALSE;
MUST_MATCH_TOKEN(TOK_RP, "missing ) after condition");
/*
* Check for an AssignExpr (see below) and "correct" it to an EqExpr.
*/
pc = CG_CODE(cg, cg->lastCodeOffset);
lastop = (JSOp) *pc;
if (lastop == JSOP_SETNAME &&
(cg->noteCount == 0 ||
cg->lastNoteOffset != cg->lastCodeOffset - 1 ||
SN_TYPE(&cg->notes[cg->saveNoteCount]) != SRC_ASSIGNOP)) {
js_ReportCompileError(cx, ts,
"test for equality (==) mistyped as assignment (=)?\n"
"Assuming equality test");
atomIndex = GET_ATOM_INDEX(pc);
js_CancelLastOpcode(cx, cg, &ts->newlines);
EMIT_CONST_ATOM_OP(JSOP_NAME, atomIndex);
if (js_Emit1(cx, cg, cx->jsop_eq) < 0)
return JS_FALSE;
}
return JS_TRUE;
}
static JSBool
WellTerminated(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg,
JSTokenType lastExprType)
{
JSTokenType tt;
tt = ts->pushback.type;
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;
}
static JSBool
MatchLabel(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg,
JSAtom **labelp)
{
#if JS_HAS_LABEL_STATEMENT
JSTokenType tt;
tt = js_PeekTokenSameLine(cx, ts, cg);
if (tt == TOK_ERROR)
return JS_FALSE;
if (tt == TOK_NAME) {
if (js_GetToken(cx, ts, cg) == TOK_ERROR)
return JS_FALSE;
*labelp = ts->token.u.atom;
js_IndexAtom(cx, *labelp, &cg->atomList);
} else {
*labelp = NULL;
}
#else
*labelp = NULL;
#endif
return WellTerminated(cx, ts, cg, TOK_ERROR);
}
#if JS_HAS_EXPORT_IMPORT
static JSBool
ImportExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
ptrdiff_t begin;
jsatomid atomIndex;
JSOp op;
JSTokenType tt;
begin = CG_OFFSET(cg);
MUST_MATCH_TOKEN(TOK_NAME, "missing name in import statement");
atomIndex = js_IndexAtom(cx, ts->token.u.atom, &cg->atomList);
op = JSOP_NAME;
ts->flags |= TSF_LOOKAHEAD;
while ((tt = js_GetToken(cx, ts, cg)) == TOK_DOT || tt == TOK_LB) {
ts->flags &= ~TSF_LOOKAHEAD;
switch (op) {
case JSOP_NAME:
case JSOP_GETPROP:
EMIT_CONST_ATOM_OP(op, atomIndex);
break;
case JSOP_GETELEM:
if (js_Emit1(cx, cg, op) < 0)
return JS_FALSE;
break;
case JSOP_IMPORTALL:
goto bad_import;
default:;
}
if (!js_FlushNewlines(cx, ts, cg))
return JS_FALSE;
if (tt == TOK_DOT) {
/*
* Compile import-property opcode.
*/
if (js_MatchToken(cx, ts, cg, TOK_STAR)) {
op = JSOP_IMPORTALL;
} else {
MUST_MATCH_TOKEN(TOK_NAME, "missing name after . operator");
if (js_NewSrcNote2(cx, cg, SRC_PCBASE,
(ptrdiff_t)(CG_OFFSET(cg) - begin)) < 0) {
return JS_FALSE;
}
atomIndex = js_IndexAtom(cx, ts->token.u.atom, &cg->atomList);
op = JSOP_GETPROP;
}
} else {
/*
* Compile index expression and get-slot opcode.
*/
if (!Expr(cx, ts, cg))
return JS_FALSE;
/* [balance: */
MUST_MATCH_TOKEN(TOK_RB, "missing ] in index expression");
if (js_NewSrcNote2(cx, cg, SRC_PCBASE,
(ptrdiff_t)(CG_OFFSET(cg) - begin)) < 0) {
return JS_FALSE;
}
op = JSOP_GETELEM;
}
ts->flags |= TSF_LOOKAHEAD;
}
ts->flags &= ~TSF_LOOKAHEAD;
if (tt == TOK_ERROR)
return JS_FALSE;
js_UngetToken(ts);
switch (op) {
case JSOP_GETPROP:
EMIT_CONST_ATOM_OP(JSOP_IMPORTPROP, atomIndex);
break;
case JSOP_GETELEM:
op = JSOP_IMPORTELEM;
/* FALL THROUGH */
case JSOP_IMPORTALL:
if (js_Emit1(cx, cg, op) < 0)
return JS_FALSE;
break;
default:
goto bad_import;
}
return JS_TRUE;
bad_import:
js_ReportCompileError(cx, ts, "invalid import expression");
return JS_FALSE;
}
#endif /* JS_HAS_EXPORT_IMPORT */
static JSBool
Statement(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
StmtInfo stmtInfo, *stmt, *stmt2;
intN snindex;
uintN lineno, topNoteCount;
JSBool ok, isForInLoop, hasVarNote;
ptrdiff_t top, beq, jmp;
JSCodeGenerator cg2;
jsbytecode *pc;
JSOp lastop;
jsatomid atomIndex;
JSTokenType tt, lastExprType;
JSAtom *label;
switch (js_GetToken(cx, ts, cg)) {
#if JS_HAS_EXPORT_IMPORT
case TOK_EXPORT:
lineno = ts->lineno;
if (js_MatchToken(cx, ts, cg, TOK_STAR)) {
if (js_Emit1(cx, cg, JSOP_EXPORTALL) < 0)
return JS_FALSE;
} else {
do {
MUST_MATCH_TOKEN(TOK_NAME, "missing name in export statement");
atomIndex = js_IndexAtom(cx, ts->token.u.atom, &cg->atomList);
EMIT_CONST_ATOM_OP(JSOP_EXPORTNAME, atomIndex);
} while (js_MatchToken(cx, ts, cg, TOK_COMMA));
}
if (ts->lineno == lineno && !WellTerminated(cx, ts, cg, TOK_ERROR))
return JS_FALSE;
break;
case TOK_IMPORT:
lineno = ts->lineno;
do {
if (!ImportExpr(cx, ts, cg))
return JS_FALSE;
} while (js_MatchToken(cx, ts, cg, TOK_COMMA));
if (ts->lineno == lineno && !WellTerminated(cx, ts, cg, TOK_ERROR))
return JS_FALSE;
break;
#endif /* JS_HAS_EXPORT_IMPORT */
case TOK_IF:
if (!Condition(cx, ts, cg))
return JS_FALSE;
js_PushStatement(cg, &stmtInfo, STMT_IF, CG_OFFSET(cg));
snindex = js_NewSrcNote(cx, cg, SRC_IF);
if (snindex < 0)
return JS_FALSE;
beq = js_Emit3(cx, cg, JSOP_IFEQ, 0, 0);
if (beq < 0)
return JS_FALSE;
if (!Statement(cx, ts, cg))
return JS_FALSE;
if (js_MatchToken(cx, ts, cg, TOK_ELSE)) {
stmtInfo.type = STMT_ELSE;
SN_SET_TYPE(&cg->notes[snindex], SRC_IF_ELSE);
jmp = js_Emit3(cx, cg, JSOP_GOTO, 0, 0);
if (jmp < 0)
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
if (!Statement(cx, ts, cg))
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, jmp);
} else {
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
}
return js_PopStatement(cx, cg);
#if JS_HAS_SWITCH_STATEMENT
case TOK_SWITCH:
{
JSOp switchop;
uintN newlines;
SwitchCase *caselist; /* singly-linked case list */
SwitchCase **casetail; /* pointer to pointer to last case */
SwitchCase *defaultsc; /* default switch case or null */
uint32 ncases, tablen;
JSAtomMap map;
JSScript *script;
jsval value;
SwitchCase *sc;
jsint i, low, high;
size_t length;
ptrdiff_t offset;
MUST_MATCH_TOKEN(TOK_LP, "missing ( before switch expression");
if (!Expr(cx, ts, cg))
return JS_FALSE;
MUST_MATCH_TOKEN(TOK_RP, "missing ) after switch expression");
MUST_MATCH_TOKEN(TOK_LC, "missing { before switch body");
/* balance} */
top = CG_OFFSET(cg);
js_PushStatement(cg, &stmtInfo, STMT_SWITCH, top);
snindex = js_NewSrcNote2(cx, cg, SRC_SWITCH, 0);
if (snindex < 0)
return JS_FALSE;
topNoteCount = cg->noteCount;
switchop = JSOP_TABLESWITCH;
if (js_Emit1(cx, cg, switchop) < 0)
return JS_FALSE;
ok = JS_TRUE;
cg2.base = NULL;
caselist = NULL;
casetail = &caselist;
defaultsc = NULL;
ncases = 0;
newlines = ts->flags & TSF_NEWLINES;
if (newlines)
HIDE_NEWLINES(ts);
while ((tt = js_GetToken(cx, ts, cg)) != TOK_RC) {
switch (tt) {
case TOK_CASE:
if (!cg2.base) {
ok = js_InitCodeGenerator(cx, &cg2, &cx->tempPool);
if (!ok)
goto end_switch;
} else {
CG_RESET(&cg2);
}
lineno = ts->lineno;
/* Save atoms indexed but not mapped before compiling. */
ok = js_InitAtomMap(cx, &map, &cg->atomList);
if (!ok)
goto end_switch;
ok = Expr(cx, ts, &cg2) && js_Emit1(cx, &cg2, JSOP_POPV) >= 0;
if (ok) {
script = js_NewScript(cx, &cg2, ts->filename, lineno,
ts->principals, NULL);
if (!script)
ok = JS_FALSE;
}
for (i = 0; i < (jsint)map.length; i++) {
js_IndexAtom(cx, map.vector[i], &cg->atomList);
PR_ASSERT((jsint)map.vector[i]->index == i);
}
js_FreeAtomMap(cx, &map);
if (!ok)
goto end_switch;
ok = js_Execute(cx, cx->fp->scopeChain, script, cx->fp, &value);
js_DestroyScript(cx, script);
if (!ok)
goto end_switch;
if (!JSVAL_IS_NUMBER(value) &&
!JSVAL_IS_STRING(value) &&
!JSVAL_IS_BOOLEAN(value)) {
js_ReportCompileError(cx, ts, "invalid case expression");
ok = JS_FALSE;
goto end_switch;
}
if (++ncases == PR_BIT(16)) {
js_ReportCompileError(cx, ts, "too many switch cases");
ok = JS_FALSE;
goto end_switch;
}
JSVAL_LOCK(cx, value);
break;
case TOK_DEFAULT:
if (defaultsc) {
js_ReportCompileError(cx, ts,
"more than one switch default");
ok = JS_FALSE;
goto end_switch;
}
value = JSVAL_VOID;
break;
case TOK_ERROR:
ok = JS_FALSE;
goto end_switch;
default:
js_ReportCompileError(cx, ts, "invalid switch statement");
ok = JS_FALSE;
goto end_switch;
}
MUST_MATCH_TOKEN_THROW(TOK_COLON, "missing : after case expression",
ok = JS_FALSE; goto end_switch);
PR_ARENA_ALLOCATE(sc, &cx->tempPool, sizeof(SwitchCase));
if (!sc) {
JSVAL_UNLOCK(cx, value);
JS_ReportOutOfMemory(cx);
ok = JS_FALSE;
goto end_switch;
}
sc->value = value;
sc->offset = CG_OFFSET(cg);
sc->next = NULL;
*casetail = sc;
casetail = &sc->next;
if (tt == TOK_DEFAULT)
defaultsc = sc;
while ((tt = js_PeekToken(cx, ts, cg)) != TOK_RC &&
tt != TOK_CASE && tt != TOK_DEFAULT) {
if (tt == TOK_ERROR) {
ok = JS_FALSE;
goto end_switch;
}
ok = Statement(cx, ts, cg);
if (!ok)
goto end_switch;
}
}
if (!caselist) {
low = high = 0;
tablen = 0;
} else {
low = JSVAL_INT_MAX;
high = JSVAL_INT_MIN;
for (sc = caselist; sc; sc = sc->next) {
if (sc == defaultsc)
continue;
if (!JSVAL_IS_INT(sc->value)) {
switchop = JSOP_LOOKUPSWITCH;
break;
}
i = JSVAL_TO_INT(sc->value);
if ((jsuint)(i + (jsint)PR_BIT(15)) >= (jsuint)PR_BIT(16)) {
switchop = JSOP_LOOKUPSWITCH;
break;
}
if (i < low)
low = i;
if (high < i)
high = i;
}
tablen = (uint32)(high - low + 1);
if (tablen >= PR_BIT(16) || tablen > 2 * ncases)
switchop = JSOP_LOOKUPSWITCH;
}
if (switchop == JSOP_TABLESWITCH) {
length = (size_t)(6 + 2 * tablen);
} else {
length = (size_t)(4 + 4 * ncases);
*CG_CODE(cg, top) = switchop;
}
/* Slide body code down to make room for the jump or lookup table. */
if (js_MoveCode(cx, cg, top + 1, cg, top + 1 + length) < 0) {
ok = JS_FALSE;
goto end_switch;
}
pc = CG_CODE(cg, top);
/* Don't forget to adjust the case offsets. */
for (sc = caselist; sc; sc = sc->next)
sc->offset += length;
/* If the body has source notes, bump the first one's delta. */
if (cg->noteCount > topNoteCount) {
ok = js_BumpSrcNoteDelta(cx, cg, topNoteCount, (ptrdiff_t)length);
if (!ok)
goto end_switch;
}
/* Set the SRC_SWITCH note's offset operand to tell end of switch. */
offset = CG_OFFSET(cg) - top;
ok = js_SetSrcNoteOffset(cx, cg, snindex, 0, (ptrdiff_t)offset);
if (!ok)
goto end_switch;
/* Fill in the default jump offset. */
if (defaultsc)
offset = defaultsc->offset - top;
else
offset = CG_OFFSET(cg) - top;
ok = js_SetJumpOffset(cx, cg, pc, offset);
if (!ok)
goto end_switch;
pc += 2;
/* Fill in jump or lookup table. */
if (switchop == JSOP_TABLESWITCH) {
SET_JUMP_OFFSET(pc, low);
pc += 2;
SET_JUMP_OFFSET(pc, high);
pc += 2;
if (tablen) {
size_t tablesize;
SwitchCase **table;
tablesize = (size_t)tablen * sizeof *table;
PR_ARENA_ALLOCATE(table, &cx->tempPool, tablesize);
if (!table) {
JS_ReportOutOfMemory(cx);
ok = JS_FALSE;
goto end_switch;
}
memset(table, 0, tablesize);
for (sc = caselist; sc; sc = sc->next) {
if (sc == defaultsc)
continue;
i = JSVAL_TO_INT(sc->value);
i -= low;
PR_ASSERT((uint32)i < tablen);
table[i] = sc;
}
for (i = 0; i < (jsint)tablen; i++) {
sc = table[i];
offset = sc ? sc->offset - top : 0;
ok = js_SetJumpOffset(cx, cg, pc, offset);
if (!ok)
goto end_switch;
pc += 2;
}
}
} else {
SET_ATOM_INDEX(pc, ncases);
pc += 2;
for (sc = caselist; sc; sc = sc->next) {
JSAtom *atom;
if (sc == defaultsc)
continue;
value = sc->value;
atom = js_AtomizeValue(cx, value, 0);
if (!atom) {
ok = JS_FALSE;
goto end_switch;
}
atomIndex = js_IndexAtom(cx, atom, &cg->atomList);
js_DropAtom(cx, atom);
SET_ATOM_INDEX(pc, atomIndex);
pc += 2;
offset = sc->offset - top;
ok = js_SetJumpOffset(cx, cg, pc, offset);
if (!ok)
goto end_switch;
pc += 2;
}
}
end_switch:
for (sc = caselist; sc; sc = sc->next)
JSVAL_UNLOCK(cx, sc->value);
if (newlines)
SCAN_NEWLINES(ts);
return js_PopStatement(cx, cg);
}
#endif /* JS_HAS_SWITCH_STATEMENT */
case TOK_WHILE:
top = CG_OFFSET(cg);
js_PushStatement(cg, &stmtInfo, STMT_WHILE_LOOP, top);
if (!Condition(cx, ts, cg))
return JS_FALSE;
snindex = js_NewSrcNote(cx, cg, SRC_WHILE);
if (snindex < 0)
return JS_FALSE;
beq = js_Emit3(cx, cg, JSOP_IFEQ, 0, 0);
if (beq < 0)
return JS_FALSE;
if (!Statement(cx, ts, cg))
return JS_FALSE;
jmp = js_Emit3(cx, cg, JSOP_GOTO, 0, 0);
if (jmp < 0)
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET(cx, cg, CG_CODE(cg,jmp), top - jmp);
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
return js_PopStatement(cx, cg);
#if JS_HAS_DO_WHILE_LOOP
case TOK_DO:
/* Emit an annotated nop so we know to decompile a 'do' keyword. */
if (js_NewSrcNote(cx, cg, SRC_WHILE) < 0 ||
js_Emit1(cx, cg, JSOP_NOP) < 0) {
return JS_FALSE;
}
/* Compile the loop body and condition. */
top = CG_OFFSET(cg);
js_PushStatement(cg, &stmtInfo, STMT_DO_LOOP, top);
if (!Statement(cx, ts, cg))
return JS_FALSE;
MUST_MATCH_TOKEN(TOK_WHILE, "missing while after do-loop body");
if (!Condition(cx, ts, cg))
return JS_FALSE;
/* Re-use the SRC_WHILE note, this time for the JSOP_IFNE opcode. */
if (js_NewSrcNote(cx, cg, SRC_WHILE) < 0)
return JS_FALSE;
jmp = top - CG_OFFSET(cg);
if (js_Emit3(cx, cg, JSOP_IFNE,
JUMP_OFFSET_HI(jmp), JUMP_OFFSET_LO(jmp)) < 0) {
return JS_FALSE;
}
return js_PopStatement(cx, cg);
#endif /* JS_HAS_DO_WHILE_LOOP */
case TOK_FOR:
top = CG_OFFSET(cg);
js_PushStatement(cg, &stmtInfo, STMT_FOR_LOOP, top);
snindex = -1;
topNoteCount = cg->noteCount;
cg2.base = NULL;
MUST_MATCH_TOKEN(TOK_LP, "missing ( after for"); /* balance) */
tt = js_PeekToken(cx, ts, cg);
if (tt == TOK_SEMI) {
/* No initializer -- emit an annotated nop for the decompiler. */
snindex = js_NewSrcNote(cx, cg, SRC_FOR);
if (snindex < 0 || js_Emit1(cx, cg, JSOP_NOP) < 0)
return JS_FALSE;
} else {
if (tt == TOK_VAR) {
(void) js_GetToken(cx, ts, cg);
if (!Variables(cx, ts, cg))
return JS_FALSE;
} else {
if (!Expr(cx, ts, cg))
return JS_FALSE;
}
if (js_PeekToken(cx, ts, cg) != TOK_IN) {
snindex = js_NewSrcNote(cx, cg, SRC_FOR);
if (snindex < 0 || js_Emit1(cx, cg, JSOP_POP) < 0)
return JS_FALSE;
}
}
isForInLoop = js_MatchToken(cx, ts, cg, TOK_IN);
if (isForInLoop) {
/* Insert a JSOP_PUSH to allocate an iterator before this loop. */
stmtInfo.type = STMT_FOR_IN_LOOP;
if (js_MoveCode(cx, cg, top, cg, top + 1) < 0)
return JS_FALSE;
*CG_CODE(cg, top) = JSOP_PUSH;
js_UpdateDepth(cx, cg, top);
top++;
SET_STATEMENT_TOP(&stmtInfo, top);
/* If the iterator has a SRC_VAR note, remember to emit it later. */
hasVarNote = (cg->noteCount > topNoteCount);
if (hasVarNote) {
hasVarNote = (SN_TYPE(&cg->notes[topNoteCount]) == SRC_VAR);
if (hasVarNote) {
/* XXX blow away any ASSIGNOP notes in initializers */
cg->noteCount = topNoteCount;
} else {
/* Otherwise bump the first notes's delta. */
if (!js_BumpSrcNoteDelta(cx, cg, topNoteCount, 1))
return JS_FALSE;
}
}
/* Cancel the last bytecode, saving it for re-emission later. */
pc = CG_CODE(cg, cg->lastCodeOffset);
lastop = (JSOp) *pc;
switch (lastop) {
case JSOP_NAME:
case JSOP_GETPROP:
atomIndex = GET_ATOM_INDEX(pc);
break;
case JSOP_GETELEM:
/* GETELEM and FORELEM have no immediate operand. */
break;
default:
js_ReportCompileError(cx, ts, "invalid for/in left-hand side");
return JS_FALSE;
}
js_CancelLastOpcode(cx, cg, &ts->newlines);
/* Now compile the object expression over which we're iterating. */
if (!Expr(cx, ts, cg))
return JS_FALSE;
if (lastop == JSOP_NAME) {
if (hasVarNote && js_NewSrcNote(cx, cg, SRC_VAR) < 0)
return JS_FALSE;
} else {
if (js_NewSrcNote2(cx, cg, SRC_PCBASE,
(int16)(CG_OFFSET(cg) - top)) < 0) {
return JS_FALSE;
}
}
/* Re-emit the left-hand side's bytecode as a JSOP_FOR* variant. */
switch (lastop) {
case JSOP_NAME:
EMIT_CONST_ATOM_OP(JSOP_FORNAME, atomIndex);
break;
case JSOP_GETPROP:
EMIT_CONST_ATOM_OP(JSOP_FORPROP, atomIndex);
break;
case JSOP_GETELEM:
if (js_Emit1(cx, cg, JSOP_FORELEM) < 0)
return JS_FALSE;
break;
default:;
}
/* Pop and test the loop condition generated by JSOP_FOR*. */
beq = js_Emit3(cx, cg, JSOP_IFEQ, 0, 0);
if (beq < 0)
return JS_FALSE;
} else {
MUST_MATCH_TOKEN(TOK_SEMI, "missing ; after for-loop initializer");
top = CG_OFFSET(cg);
SET_STATEMENT_TOP(&stmtInfo, top);
if (js_PeekToken(cx, ts, cg) == TOK_SEMI) {
/* No loop condition -- flag this fact in the source note. */
if (!js_SetSrcNoteOffset(cx, cg, snindex, 0, 0))
return JS_FALSE;
beq = 0;
} else {
if (!Expr(cx, ts, cg))
return JS_FALSE;
if (!js_SetSrcNoteOffset(cx, cg, snindex, 0,
(ptrdiff_t)(CG_OFFSET(cg) - top))) {
return JS_FALSE;
}
beq = js_Emit3(cx, cg, JSOP_IFEQ, 0, 0);
if (beq < 0)
return JS_FALSE;
}
MUST_MATCH_TOKEN(TOK_SEMI, "missing ; after for-loop condition");
if (js_PeekToken(cx, ts, cg) != TOK_RP) {
if (!js_InitCodeGenerator(cx, &cg2, &cx->tempPool))
return JS_FALSE;
CG_PUSH(cg, &cg2);
if (js_NewSrcNote2(cx, &cg2, SRC_SETLINE,
(ptrdiff_t)TRUE_LINENO(ts)) < 0) {
return JS_FALSE;
}
if (!Expr(cx, ts, &cg2))
return JS_FALSE;
if (js_Emit1(cx, &cg2, JSOP_POP) < 0)
return JS_FALSE;
CG_POP(cg, &cg2);
}
}
/* (balance: */
MUST_MATCH_TOKEN(TOK_RP, "missing ) after for-loop control");
if (!Statement(cx, ts, cg))
return JS_FALSE;
if (snindex != -1 &&
!js_SetSrcNoteOffset(cx, cg, snindex, 1,
(ptrdiff_t)(CG_OFFSET(cg) - top))) {
return JS_FALSE;
}
if (cg2.base) {
/* Set loop and enclosing "update" offsets, for continue. */
stmt = &stmtInfo;
stmt->update = CG_OFFSET(cg);
while ((stmt = stmt->down) != 0 && stmt->type == STMT_LABEL)
stmt->update = CG_OFFSET(cg);
/* Copy the update code from its temporary pool into cg's pool. */
if (!js_MoveSrcNotes(cx, &cg2, cg))
return JS_FALSE;
if (js_MoveCode(cx, &cg2, 0, cg, stmtInfo.update) < 0)
return JS_FALSE;
/* Restore the absolute line number for source note readers. */
if (js_NewSrcNote2(cx, cg, SRC_SETLINE, (ptrdiff_t)TRUE_LINENO(ts))
< 0) {
return JS_FALSE;
}
}
if (snindex != -1 &&
!js_SetSrcNoteOffset(cx, cg, snindex, 2,
(ptrdiff_t)(CG_OFFSET(cg) - top))) {
return JS_FALSE;
}
jmp = js_Emit3(cx, cg, JSOP_GOTO, 0, 0);
if (jmp < 0)
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET(cx, cg, CG_CODE(cg,jmp), top - jmp);
if (beq > 0)
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
if (!js_PopStatement(cx, cg))
return JS_FALSE;
if (isForInLoop) {
/*
* Generate the iterator pop after popping the stmtInfo stack, so
* breaks will go to the pop bytecode.
*/
if (js_Emit1(cx, cg, JSOP_POP) < 0)
return JS_FALSE;
}
return JS_TRUE;
case TOK_BREAK:
if (!MatchLabel(cx, ts, cg, &label))
return JS_FALSE;
stmt = cg->stmtInfo;
if (label) {
for (; ; stmt = stmt->down) {
if (!stmt) {
js_ReportCompileError(cx, ts, "label not found");
return JS_FALSE;
}
if (stmt->type == STMT_LABEL && stmt->label == label)
break;
}
} else {
for (; ; stmt = stmt->down) {
if (!stmt) {
js_ReportCompileError(cx, ts, "invalid break");
return JS_FALSE;
}
if (STMT_IS_LOOP(stmt) || stmt->type == STMT_SWITCH)
break;
}
}
if (js_EmitBreak(cx, cg, stmt, label) < 0)
return JS_FALSE;
break;
case TOK_CONTINUE:
if (!MatchLabel(cx, ts, cg, &label))
return JS_FALSE;
stmt = cg->stmtInfo;
if (label) {
for (stmt2 = NULL; ; stmt = stmt->down) {
if (!stmt) {
js_ReportCompileError(cx, ts, "label not found");
return JS_FALSE;
}
if (stmt->type == STMT_LABEL) {
if (stmt->label == label) {
if (!stmt2 || !STMT_IS_LOOP(stmt2)) {
js_ReportCompileError(cx, ts, "invalid continue");
return JS_FALSE;
}
break;
}
} else {
stmt2 = stmt;
}
}
} else {
for (; ; stmt = stmt->down) {
if (!stmt) {
js_ReportCompileError(cx, ts, "invalid continue");
return JS_FALSE;
}
if (STMT_IS_LOOP(stmt))
break;
}
if (js_NewSrcNote(cx, cg, SRC_CONTINUE) < 0)
return JS_FALSE;
}
if (js_EmitContinue(cx, cg, stmt, label) < 0)
return JS_FALSE;
break;
case TOK_WITH:
MUST_MATCH_TOKEN(TOK_LP, "missing ( before with-statement object");
/* balance) */
if (!Expr(cx, ts, cg))
return JS_FALSE;
/* (balance: */
MUST_MATCH_TOKEN(TOK_RP, "missing ) after with-statement object");
js_PushStatement(cg, &stmtInfo, STMT_WITH, CG_OFFSET(cg));
if (js_Emit1(cx, cg, JSOP_ENTERWITH) < 0)
return JS_FALSE;
if (!Statement(cx, ts, cg))
return JS_FALSE;
if (js_Emit1(cx, cg, JSOP_LEAVEWITH) < 0)
return JS_FALSE;
return js_PopStatement(cx, cg);
case TOK_VAR:
lineno = ts->lineno;
if (!Variables(cx, ts, cg))
return JS_FALSE;
if (ts->lineno == lineno && !WellTerminated(cx, ts, cg, TOK_ERROR))
return JS_FALSE;
if (js_Emit1(cx, cg, JSOP_POP) < 0)
return JS_FALSE;
break;
case TOK_RETURN:
if (!(ts->flags & TSF_FUNCTION)) {
js_ReportCompileError(cx, ts, "invalid return");
return JS_FALSE;
}
/* This is ugly, but we don't want to require a semicolon. */
ts->flags |= TSF_REGEXP;
tt = js_PeekTokenSameLine(cx, ts, cg);
ts->flags &= ~TSF_REGEXP;
if (tt == TOK_ERROR)
return JS_FALSE;
if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) {
lineno = ts->lineno;
if (!Expr(cx, ts, cg))
return JS_FALSE;
if (ts->lineno == lineno && !WellTerminated(cx, ts, cg, TOK_ERROR))
return JS_FALSE;
ts->flags |= TSF_RETURN_EXPR;
} else {
if (js_Emit1(cx, cg, JSOP_PUSH) < 0)
return JS_FALSE;
ts->flags |= TSF_RETURN_VOID;
}
if ((ts->flags & (TSF_RETURN_EXPR | TSF_RETURN_VOID)) ==
(TSF_RETURN_EXPR | TSF_RETURN_VOID)) {
js_ReportCompileError(cx, ts,
"function does not always return a value");
return JS_FALSE;
}
if (js_Emit1(cx, cg, JSOP_RETURN) < 0)
return JS_FALSE;
break;
case TOK_LC:
js_PushStatement(cg, &stmtInfo, STMT_BLOCK, CG_OFFSET(cg));
if (!Statements(cx, ts, cg))
return JS_FALSE;
/* {balance: */
MUST_MATCH_TOKEN(TOK_RC, "missing } in compound statement");
return js_PopStatement(cx, cg);
case TOK_EOL:
case TOK_SEMI:
return JS_TRUE;
case TOK_ERROR:
return JS_FALSE;
default:
lastExprType = ts->token.type;
js_UngetToken(ts);
lineno = ts->lineno;
top = CG_OFFSET(cg);
if (!Expr(cx, ts, cg))
return JS_FALSE;
tt = ts->pushback.type;
if (tt == TOK_COLON) {
if (cg->lastCodeOffset != top || *CG_CODE(cg, top) != JSOP_NAME) {
js_ReportCompileError(cx, ts, "invalid label");
return JS_FALSE;
}
label = cg->atomList.list;
for (stmt = cg->stmtInfo; stmt; stmt = stmt->down) {
if (stmt->type == STMT_LABEL && stmt->label == label) {
js_ReportCompileError(cx, ts, "duplicate label");
return JS_FALSE;
}
}
js_CancelLastOpcode(cx, cg, &ts->newlines);
js_GetToken(cx, ts, cg);
/* Emit an annotated nop so we know to decompile a label. */
snindex = js_NewSrcNote(cx, cg,
(js_PeekToken(cx, ts, cg) == TOK_LC)
? SRC_LABELBRACE
: SRC_LABEL);
if (snindex < 0 ||
!js_SetSrcNoteOffset(cx, cg, snindex, 0,
(ptrdiff_t)label->index) ||
js_Emit1(cx, cg, JSOP_NOP) < 0) {
return JS_FALSE;
}
/* Push a label struct and parse the statement. */
js_PushStatement(cg, &stmtInfo, STMT_LABEL, CG_OFFSET(cg));
stmtInfo.label = label;
if (!Statement(cx, ts, cg))
return JS_FALSE;
/* If the statement was compound, emit a note for the end brace */
if (SN_TYPE(&cg->notes[snindex]) == SRC_LABELBRACE) {
if (js_NewSrcNote(cx, cg, SRC_ENDBRACE) < 0 ||
js_Emit1(cx, cg, JSOP_NOP) < 0) {
return JS_FALSE;
}
}
/* Finally, pop the label and return early. */
return js_PopStatement(cx, cg);
}
if (ts->lineno == lineno && !WellTerminated(cx, ts, cg, lastExprType))
return JS_FALSE;
if (js_Emit1(cx, cg, JSOP_POPV) < 0)
return JS_FALSE;
break;
}
(void) js_MatchToken(cx, ts, cg, TOK_SEMI);
return JS_TRUE;
}
static JSBool
Variables(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
JSObject *obj, *pobj;
JSFunction *fun;
JSClass *clasp;
JSBool ok, first;
JSPropertyOp getter, setter;
intN snindex;
ptrdiff_t offset, newoffset;
JSAtom *atom;
jsatomid atomIndex;
JSProperty *prop;
JSSymbol *var;
JSOp op;
obj = js_FindVariableScope(cx, &fun);
if (!obj)
return JS_FALSE;
clasp = obj->map->clasp;
if (fun && clasp != &js_CallClass) {
getter = js_GetLocalVariable;
setter = js_SetLocalVariable;
} else {
getter = clasp->getProperty;
setter = clasp->setProperty;
}
ok = first = JS_TRUE;
snindex = -1;
for (;;) {
MUST_MATCH_TOKEN(TOK_NAME, "missing variable name");
atom = ts->token.u.atom;
atomIndex = js_IndexAtom(cx, atom, &cg->atomList);
pobj = NULL;
if (!js_LookupProperty(cx, obj, (jsval)atom, &pobj, &prop))
return JS_FALSE;
if (prop && prop->object == obj) {
if (prop->getter == js_GetArgument) {
#ifdef CHECK_ARGUMENT_HIDING
js_ReportCompileError(cx, ts, "variable %s hides argument",
ATOM_BYTES(atom));
ok = JS_FALSE;
#endif
} else {
if (fun) {
/* Not an argument, must be a redeclared local var. */
if (clasp != &js_CallClass) {
PR_ASSERT(prop->getter == js_GetLocalVariable);
PR_ASSERT(JSVAL_IS_INT(prop->id) &&
JSVAL_TO_INT(prop->id) < fun->nvars);
}
} else {
/* Global var: (re-)set id a la js_DefineProperty. */
prop->id = ATOM_KEY(atom);
}
prop->getter = getter;
prop->setter = setter;
prop->flags |= JSPROP_ENUMERATE | JSPROP_PERMANENT;
prop->flags &= ~JSPROP_READONLY;
}
} else {
prop = js_DefineProperty(cx, obj, (jsval)atom, JSVAL_VOID,
getter, setter,
JSPROP_ENUMERATE | JSPROP_PERMANENT);
if (!prop)
return JS_FALSE;
if (getter == js_GetLocalVariable)
prop->id = INT_TO_JSVAL(fun->nvars++);
var = prop->symbols;
var->next = cg->vars;
cg->vars = var;
}
if (js_MatchToken(cx, ts, cg, TOK_ASSIGN)) {
if (ts->token.u.op != JSOP_NOP) {
js_ReportCompileError(cx, ts,
"invalid variable initialization");
ok = JS_FALSE;
}
if (!AssignExpr(cx, ts, cg))
return JS_FALSE;
op = JSOP_SETNAME;
} else {
op = JSOP_NAME;
}
if (first && js_NewSrcNote(cx, cg, SRC_VAR) < 0)
return JS_FALSE;
EMIT_CONST_ATOM_OP(op, atomIndex);
if (snindex >= 0) {
newoffset = CG_OFFSET(cg);
if (!js_SetSrcNoteOffset(cx, cg, snindex, 0, newoffset - offset))
return JS_FALSE;
offset = newoffset;
}
if (!js_MatchToken(cx, ts, cg, TOK_COMMA))
break;
offset = CG_OFFSET(cg);
snindex = js_NewSrcNote2(cx, cg, SRC_COMMA, 0);
if (snindex < 0 || js_Emit1(cx, cg, JSOP_POP) < 0)
return JS_FALSE;
first = JS_FALSE;
}
return ok;
}
static JSBool
Expr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
intN snindex;
ptrdiff_t offset, newoffset;
snindex = -1;
for (;;) {
if (!AssignExpr(cx, ts, cg))
return JS_FALSE;
if (snindex >= 0) {
newoffset = CG_OFFSET(cg);
if (!js_SetSrcNoteOffset(cx, cg, snindex, 0, newoffset - offset))
return JS_FALSE;
offset = newoffset;
}
if (!js_MatchToken(cx, ts, cg, TOK_COMMA))
break;
offset = CG_OFFSET(cg);
snindex = js_NewSrcNote2(cx, cg, SRC_COMMA, 0);
if (snindex < 0 || js_Emit1(cx, cg, JSOP_POP) < 0)
return JS_FALSE;
}
return JS_TRUE;
}
/*
* XXX Need to build abstract syntax tree for complete error checking:
* XXX (true ? 3 : x) = 4 does not result in an error, it evaluates to 3.
* XXX similarly for (true && x) = 5, etc.
*/
static JSBool
AssignExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
ptrdiff_t begin;
jsbytecode *pc;
JSOp lastop, op;
jsatomid atomIndex;
begin = CG_OFFSET(cg);
if (!CondExpr(cx, ts, cg))
return JS_FALSE;
if (js_MatchToken(cx, ts, cg, TOK_ASSIGN)) {
pc = CG_CODE(cg, cg->lastCodeOffset);
lastop = (JSOp) *pc;
switch (lastop) {
case JSOP_NAME:
case JSOP_GETPROP:
atomIndex = GET_ATOM_INDEX(pc);
break;
case JSOP_GETELEM:
/* GETELEM and SETELEM have no immediate operand. */
break;
default:
js_ReportCompileError(cx, ts, "invalid assignment left-hand side");
return JS_FALSE;
}
js_CancelLastOpcode(cx, cg, &ts->newlines);
op = ts->token.u.op;
if (op != JSOP_NOP) {
switch (lastop) {
case JSOP_NAME:
EMIT_CONST_ATOM_OP(lastop, atomIndex);
break;
case JSOP_GETPROP:
if (js_Emit1(cx, cg, JSOP_DUP) < 0)
return JS_FALSE;
EMIT_CONST_ATOM_OP(lastop, atomIndex);
break;
case JSOP_GETELEM:
if (js_Emit1(cx, cg, JSOP_DUP2) < 0)
return JS_FALSE;
if (js_Emit1(cx, cg, JSOP_GETELEM) < 0)
return JS_FALSE;
break;
default:;
}
}
if (!AssignExpr(cx, ts, cg))
return JS_FALSE;
if (op != JSOP_NOP) {
if (js_NewSrcNote(cx, cg, SRC_ASSIGNOP) < 0 ||
js_Emit1(cx, cg, op) < 0) {
return JS_FALSE;
}
}
if (lastop != JSOP_NAME) {
if (js_NewSrcNote2(cx, cg, SRC_PCBASE,
(ptrdiff_t)(CG_OFFSET(cg) - begin)) < 0) {
return JS_FALSE;
}
}
switch (lastop) {
case JSOP_NAME:
EMIT_CONST_ATOM_OP(JSOP_SETNAME, atomIndex);
break;
case JSOP_GETPROP:
EMIT_CONST_ATOM_OP(JSOP_SETPROP, atomIndex);
break;
case JSOP_GETELEM:
if (js_Emit1(cx, cg, JSOP_SETELEM) < 0)
return JS_FALSE;
break;
default:;
}
}
return JS_TRUE;
}
static JSBool
CondExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
ptrdiff_t beq, jmp;
if (!OrExpr(cx, ts, cg))
return JS_FALSE;
if (js_MatchToken(cx, ts, cg, TOK_HOOK)) {
if (js_NewSrcNote(cx, cg, SRC_COND) < 0)
return JS_FALSE;
beq = js_Emit3(cx, cg, JSOP_IFEQ, 0, 0);
if (beq < 0 || !AssignExpr(cx, ts, cg))
return JS_FALSE;
MUST_MATCH_TOKEN(TOK_COLON, "missing : in conditional expression");
jmp = js_Emit3(cx, cg, JSOP_GOTO, 0, 0);
if (jmp < 0)
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
if (!AssignExpr(cx, ts, cg))
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, jmp);
}
return JS_TRUE;
}
static JSBool
OrExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
#if JS_BUG_SHORT_CIRCUIT
ptrdiff_t beq, tmp, jmp;
#else
ptrdiff_t jmp;
#endif
if (!AndExpr(cx, ts, cg))
return JS_FALSE;
if (js_MatchToken(cx, ts, cg, TOK_OR)) {
#if JS_BUG_SHORT_CIRCUIT
beq = js_Emit3(cx, cg, JSOP_IFEQ, 0, 0);
tmp = js_Emit1(cx, cg, JSOP_TRUE);
jmp = js_Emit3(cx, cg, JSOP_GOTO, 0, 0);
if (beq < 0 || tmp < 0 || jmp < 0)
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
#else
jmp = js_Emit3(cx, cg, JSOP_OR, 0, 0);
if (jmp < 0)
return JS_FALSE;
#endif
if (!OrExpr(cx, ts, cg))
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, jmp);
}
return JS_TRUE;
}
static JSBool
AndExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
#if JS_BUG_SHORT_CIRCUIT
ptrdiff_t bne, tmp, jmp;
#else
ptrdiff_t jmp;
#endif
if (!BitOrExpr(cx, ts, cg))
return JS_FALSE;
if (js_MatchToken(cx, ts, cg, TOK_AND)) {
#if JS_BUG_SHORT_CIRCUIT
bne = js_Emit3(cx, cg, JSOP_IFNE, 0, 0);
tmp = js_Emit1(cx, cg, JSOP_FALSE);
jmp = js_Emit3(cx, cg, JSOP_GOTO, 0, 0);
if (bne < 0 || tmp < 0 || jmp < 0)
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, bne);
#else
jmp = js_Emit3(cx, cg, JSOP_AND, 0, 0);
if (jmp < 0)
return JS_FALSE;
#endif
if (!AndExpr(cx, ts, cg))
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, jmp);
}
return JS_TRUE;
}
static JSBool
BitOrExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
if (!BitXorExpr(cx, ts, cg))
return JS_FALSE;
while (js_MatchToken(cx, ts, cg, TOK_BITOR)) {
if (!BitXorExpr(cx, ts, cg) ||
js_Emit1(cx, cg, JSOP_BITOR) < 0) {
return JS_FALSE;
}
}
return JS_TRUE;
}
static JSBool
BitXorExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
if (!BitAndExpr(cx, ts, cg))
return JS_FALSE;
while (js_MatchToken(cx, ts, cg, TOK_BITXOR)) {
if (!BitAndExpr(cx, ts, cg) ||
js_Emit1(cx, cg, JSOP_BITXOR) < 0) {
return JS_FALSE;
}
}
return JS_TRUE;
}
static JSBool
BitAndExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
if (!EqExpr(cx, ts, cg))
return JS_FALSE;
while (js_MatchToken(cx, ts, cg, TOK_BITAND)) {
if (!EqExpr(cx, ts, cg) || js_Emit1(cx, cg, JSOP_BITAND) < 0)
return JS_FALSE;
}
return JS_TRUE;
}
static JSBool
EqExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
JSOp op;
if (!RelExpr(cx, ts, cg))
return JS_FALSE;
while (js_MatchToken(cx, ts, cg, TOK_EQOP)) {
op = ts->token.u.op;
if (!RelExpr(cx, ts, cg) || js_Emit1(cx, cg, op) < 0)
return JS_FALSE;
}
return JS_TRUE;
}
static JSBool
RelExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
JSOp op;
if (!ShiftExpr(cx, ts, cg))
return JS_FALSE;
while (js_MatchToken(cx, ts, cg, TOK_RELOP)) {
op = ts->token.u.op;
if (!ShiftExpr(cx, ts, cg) || js_Emit1(cx, cg, op) < 0)
return JS_FALSE;
}
return JS_TRUE;
}
static JSBool
ShiftExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
JSOp op;
if (!AddExpr(cx, ts, cg))
return JS_FALSE;
while (js_MatchToken(cx, ts, cg, TOK_SHOP)) {
op = ts->token.u.op;
if (!AddExpr(cx, ts, cg) || js_Emit1(cx, cg, op) < 0)
return JS_FALSE;
}
return JS_TRUE;
}
static JSBool
AddExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
JSTokenType tt;
if (!MulExpr(cx, ts, cg))
return JS_FALSE;
ts->flags |= TSF_LOOKAHEAD;
while ((tt = js_GetToken(cx, ts, cg)) == TOK_PLUS || tt == TOK_MINUS) {
ts->flags &= ~TSF_LOOKAHEAD;
if (!js_FlushNewlines(cx, ts, cg) ||
!MulExpr(cx, ts, cg) ||
js_Emit1(cx, cg, (tt == TOK_PLUS) ? JSOP_ADD : JSOP_SUB) < 0) {
return JS_FALSE;
}
ts->flags |= TSF_LOOKAHEAD;
}
ts->flags &= ~TSF_LOOKAHEAD;
js_UngetToken(ts);
return JS_TRUE;
}
static JSBool
MulExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
JSOp op;
if (!UnaryExpr(cx, ts, cg))
return JS_FALSE;
while (js_MatchToken(cx, ts, cg, TOK_STAR) ||
js_MatchToken(cx, ts, cg, TOK_DIVOP)) {
op = ts->token.u.op;
if (!UnaryExpr(cx, ts, cg) || js_Emit1(cx, cg, op) < 0)
return JS_FALSE;
}
return JS_TRUE;
}
static JSBool
EmitIncOp(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg,
JSTokenType tt, JSBool preorder)
{
jsbytecode *pc;
JSOp op;
pc = CG_CODE(cg, cg->lastCodeOffset);
op = (JSOp) *pc;
switch (op) {
case JSOP_NAME:
if (tt == TOK_INC)
op = preorder ? JSOP_INCNAME : JSOP_NAMEINC;
else
op = preorder ? JSOP_DECNAME : JSOP_NAMEDEC;
break;
case JSOP_GETPROP:
if (tt == TOK_INC)
op = preorder ? JSOP_INCPROP : JSOP_PROPINC;
else
op = preorder ? JSOP_DECPROP : JSOP_PROPDEC;
break;
case JSOP_GETELEM:
if (tt == TOK_INC)
op = preorder ? JSOP_INCELEM : JSOP_ELEMINC;
else
op = preorder ? JSOP_DECELEM : JSOP_ELEMDEC;
break;
default:
js_ReportCompileError(cx, ts, "invalid %screment expression",
(tt == TOK_INC) ? "in" : "de");
return JS_FALSE;
}
*pc = op;
return JS_TRUE;
}
static JSBool
UnaryExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
JSTokenType tt;
JSOp op, lastop;
ptrdiff_t begin;
jsatomid atomIndex;
uintN argc, lineno;
jsbytecode *pc;
ts->flags |= TSF_REGEXP;
tt = js_GetToken(cx, ts, cg);
ts->flags &= ~TSF_REGEXP;
switch (tt) {
case TOK_UNARYOP:
case TOK_PLUS:
case TOK_MINUS:
op = ts->token.u.op;
if (!UnaryExpr(cx, ts, cg) || js_Emit1(cx, cg, op) < 0)
return JS_FALSE;
break;
case TOK_INC:
case TOK_DEC:
if (!MemberExpr(cx, ts, cg))
return JS_FALSE;
if (!EmitIncOp(cx, ts, cg, tt, JS_TRUE))
return JS_FALSE;
break;
case TOK_NEW:
/* Allow 'new this.ctor(...)' constructor expressions. */
begin = CG_OFFSET(cg);
tt = js_GetToken(cx, ts, cg);
switch (tt) {
case TOK_NAME:
atomIndex = js_IndexAtom(cx, ts->token.u.atom, &cg->atomList);
EMIT_CONST_ATOM_OP(JSOP_NAME, atomIndex);
break;
case TOK_PRIMARY:
if (ts->token.u.op == JSOP_THIS) {
if (js_Emit1(cx, cg, JSOP_THIS) < 0)
return JS_FALSE;
break;
}
/* FALL THROUGH */
default:
js_ReportCompileError(cx, ts, "missing name after new operator");
return JS_FALSE;
}
while (js_MatchToken(cx, ts, cg, TOK_DOT)) {
MUST_MATCH_TOKEN(TOK_NAME,
"missing name in constructor expression");
if (js_NewSrcNote2(cx, cg, SRC_PCBASE,
(ptrdiff_t)(CG_OFFSET(cg) - begin)) < 0) {
return JS_FALSE;
}
atomIndex = js_IndexAtom(cx, ts->token.u.atom, &cg->atomList);
EMIT_CONST_ATOM_OP(JSOP_GETPROP, atomIndex);
}
if (js_Emit1(cx, cg, JSOP_PUSHOBJ) < 0)
return JS_FALSE;
argc = 0;
if (js_MatchToken(cx, ts, cg, TOK_LP) &&
!js_MatchToken(cx, ts, cg, TOK_RP)) {
do {
if (!AssignExpr(cx, ts, cg))
return JS_FALSE;
argc++;
} while (js_MatchToken(cx, ts, cg, TOK_COMMA));
/* (balance: */
MUST_MATCH_TOKEN(TOK_RP,
"missing ) after constructor argument list");
}
if (argc >= ARGC_LIMIT) {
JS_ReportError(cx, "too many constructor arguments");
return JS_FALSE;
}
if (js_Emit3(cx, cg, JSOP_NEW, ARGC_HI(argc), ARGC_LO(argc)) < 0)
return JS_FALSE;
break;
case TOK_DELETE:
/* Compile the operand expression. */
begin = CG_OFFSET(cg);
if (!MemberExpr(cx, ts, cg))
return JS_FALSE;
/* Cancel the last bytecode, saving it for re-emission later. */
pc = CG_CODE(cg, cg->lastCodeOffset);
lastop = (JSOp) *pc;
switch (lastop) {
case JSOP_NAME:
case JSOP_GETPROP:
atomIndex = GET_ATOM_INDEX(pc);
break;
case JSOP_GETELEM:
/* GETELEM and DELELEM have no immediate operand. */
break;
default:
js_ReportCompileError(cx, ts, "invalid %s operand", js_delete_str);
return JS_FALSE;
}
js_CancelLastOpcode(cx, cg, &ts->newlines);
if (lastop != JSOP_NAME) {
if (js_NewSrcNote2(cx, cg, SRC_PCBASE,
(int16)(CG_OFFSET(cg) - begin)) < 0) {
return JS_FALSE;
}
}
/* Re-emit the operand's last bytecode as a JSOP_DEL* variant. */
switch (lastop) {
case JSOP_NAME:
EMIT_CONST_ATOM_OP(JSOP_DELNAME, atomIndex);
break;
case JSOP_GETPROP:
EMIT_CONST_ATOM_OP(JSOP_DELPROP, atomIndex);
break;
case JSOP_GETELEM:
if (js_Emit1(cx, cg, JSOP_DELELEM) < 0)
return JS_FALSE;
break;
default:;
}
break;
case TOK_ERROR:
return JS_FALSE;
default:
js_UngetToken(ts);
lineno = ts->lineno;
if (!MemberExpr(cx, ts, cg))
return JS_FALSE;
/* Don't look across a newline boundary for a postfix incop. */
if (ts->lineno == lineno &&
(js_MatchToken(cx, ts, cg, TOK_INC) ||
js_MatchToken(cx, ts, cg, TOK_DEC))) {
if (!EmitIncOp(cx, ts, cg, ts->token.type, JS_FALSE))
return JS_FALSE;
}
}
return JS_TRUE;
}
static JSBool
MemberExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
ptrdiff_t begin;
JSToken token;
JSTokenType tt;
jsatomid atomIndex;
uintN argc;
JSBool matched;
JSObject *pobj;
JSProperty *prop;
jsval val;
JSFunction *fun;
char *option;
JSAtom *atom;
begin = CG_OFFSET(cg);
if (!PrimaryExpr(cx, ts, cg))
return JS_FALSE;
token = ts->token;
ts->flags |= TSF_LOOKAHEAD;
while ((tt = js_GetToken(cx, ts, cg)) > TOK_EOF) {
ts->flags &= ~TSF_LOOKAHEAD;
if (tt == TOK_DOT) {
/*
* Compile get-property opcode.
*/
if (!js_FlushNewlines(cx, ts, cg))
return JS_FALSE;
MUST_MATCH_TOKEN(TOK_NAME, "missing name after . operator");
if (js_NewSrcNote2(cx, cg, SRC_PCBASE,
(ptrdiff_t)(CG_OFFSET(cg) - begin)) < 0) {
return JS_FALSE;
}
atomIndex = js_IndexAtom(cx, ts->token.u.atom, &cg->atomList);
EMIT_CONST_ATOM_OP(JSOP_GETPROP, atomIndex);
} else if (tt == TOK_LB) {
/*
* Compile index expression and get-slot opcode.
*/
if (!js_FlushNewlines(cx, ts, cg))
return JS_FALSE;
if (!Expr(cx, ts, cg))
return JS_FALSE;
/* [balance: */
MUST_MATCH_TOKEN(TOK_RB, "missing ] in index expression");
if (js_NewSrcNote2(cx, cg, SRC_PCBASE,
(ptrdiff_t)(CG_OFFSET(cg) - begin)) < 0) {
return JS_FALSE;
}
if (js_Emit1(cx, cg, JSOP_GETELEM) < 0)
return JS_FALSE;
} else if (tt == TOK_LP) {
/*
* Compile 'this' and the actual args, followed by a call opcode.
*/
if (js_Emit1(cx, cg, JSOP_PUSHOBJ) < 0)
return JS_FALSE;
if (!js_FlushNewlines(cx, ts, cg))
return JS_FALSE;
argc = 0;
ts->flags |= TSF_REGEXP;
matched = js_MatchToken(cx, ts, cg, TOK_RP);
ts->flags &= ~TSF_REGEXP;
if (!matched) {
do {
if (!AssignExpr(cx, ts, cg))
return JS_FALSE;
argc++;
} while (js_MatchToken(cx, ts, cg, TOK_COMMA));
/* (balance: */
MUST_MATCH_TOKEN(TOK_RP, "missing ) after argument list");
}
if (argc >= ARGC_LIMIT) {
JS_ReportError(cx, "too many function arguments");
return JS_FALSE;
}
if (js_Emit3(cx, cg, JSOP_CALL, ARGC_HI(argc), ARGC_LO(argc)) < 0)
return JS_FALSE;
} else {
js_UngetToken(ts);
/*
* Look for a name, number, or string immediately after a name.
* Such a token juxtaposition is likely to be a "command-style"
* function call.
*/
if ((ts->flags & TSF_COMMAND) &&
token.type == TOK_NAME &&
*CG_CODE(cg, cg->lastCodeOffset) == JSOP_NAME &&
(tt == TOK_EOL || tt == TOK_MINUS || IS_PRIMARY_TOKEN(tt))) {
pobj = NULL;
if (!js_LookupProperty(cx, cx->fp->scopeChain,
(jsval)token.u.atom, &pobj, &prop)) {
return JS_FALSE;
}
if (prop &&
(val = prop->object->slots[prop->slot]) &&
JSVAL_IS_FUNCTION(val) &&
(fun = js_ValueToFunction(cx, val)) &&
(tt != TOK_EOL || fun->nargs == 0)) {
if (js_Emit1(cx, cg, JSOP_PUSHOBJ) < 0)
return JS_FALSE;
if (!js_FlushNewlines(cx, ts, cg))
return JS_FALSE;
argc = 0;
while (tt != TOK_EOL && tt != TOK_SEMI &&
tt != TOK_RP && tt != TOK_RC) {
if (tt == TOK_MINUS && JS7_ISLET(ts->token.ptr[1])) {
(void) js_GetToken(cx, ts, cg);
MUST_MATCH_TOKEN(TOK_NAME,
"missing option after - in command");
option = PR_smprintf("-%s",
ATOM_BYTES(ts->token.u.atom));
if (!option) {
JS_ReportOutOfMemory(cx);
return JS_FALSE;
}
atom = js_Atomize(cx, option, strlen(option), 0);
free(option);
if (!atom)
return JS_FALSE;
atomIndex = js_IndexAtom(cx, atom, &cg->atomList);
EMIT_CONST_ATOM_OP(JSOP_STRING, atomIndex);
js_DropAtom(cx, atom);
} else {
if (!AssignExpr(cx, ts, cg))
return JS_FALSE;
}
argc++;
js_MatchToken(cx, ts, cg, TOK_COMMA);
tt = js_PeekToken(cx, ts, cg);
}
if (argc >= ARGC_LIMIT) {
JS_ReportError(cx, "too many command arguments");
return JS_FALSE;
}
if (js_Emit3(cx, cg, JSOP_CALL,
ARGC_HI(argc), ARGC_LO(argc)) < 0) {
return JS_FALSE;
}
}
}
break;
}
token = ts->token;
ts->flags |= TSF_LOOKAHEAD;
}
ts->flags &= ~TSF_LOOKAHEAD;
return (tt != TOK_ERROR);
}
static JSBool
EmitNumberOp(JSContext *cx, jsdouble dval, JSCodeGenerator *cg)
{
jsint ival;
JSAtom *atom;
jsatomid atomIndex;
ival = (jsint)dval;
if (JSDOUBLE_IS_INT(dval, ival) && INT_FITS_IN_JSVAL(ival)) {
if (ival == 0)
return js_Emit1(cx, cg, JSOP_ZERO) >= 0;
if (ival == 1)
return js_Emit1(cx, cg, JSOP_ONE) >= 0;
if ((jsuint)ival < (jsuint)ATOM_INDEX_LIMIT) {
atomIndex = (jsatomid)ival;
EMIT_CONST_ATOM_OP(JSOP_UINT16, atomIndex);
return JS_TRUE;
}
atom = js_AtomizeInt(cx, ival, ATOM_NOHOLD);
} else {
atom = js_AtomizeDouble(cx, dval, ATOM_NOHOLD);
}
if (!atom)
return JS_FALSE;
atomIndex = js_IndexAtom(cx, atom, &cg->atomList);
EMIT_CONST_ATOM_OP(JSOP_NUMBER, atomIndex);
return JS_TRUE;
}
static JSBool
PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSCodeGenerator *cg)
{
JSTokenType tt;
jsatomid atomIndex;
#if JS_HAS_SHARP_VARS
JSBool defsharp = JS_FALSE;
jsatomid sharpnum = 0;
again:
#endif
ts->flags |= TSF_REGEXP;
tt = js_GetToken(cx, ts, cg);
ts->flags &= ~TSF_REGEXP;
switch (tt) {
#if JS_HAS_LEXICAL_CLOSURE
case TOK_FUNCTION:
return FunctionExpr(cx, ts, cg);
#endif
#if JS_HAS_OBJECT_LITERAL
case TOK_LB:
{
JSBool matched;
jsint index;
atomIndex = js_IndexAtom(cx, cx->runtime->atomState.ArrayAtom,
&cg->atomList);
EMIT_CONST_ATOM_OP(JSOP_NAME, atomIndex);
if (js_Emit1(cx, cg, JSOP_PUSHOBJ) < 0)
return JS_FALSE;
if (js_Emit1(cx, cg, JSOP_NEWINIT) < 0)
return JS_FALSE;
#if JS_HAS_SHARP_VARS
if (defsharp)
EMIT_CONST_ATOM_OP(JSOP_DEFSHARP, sharpnum);
#endif
ts->flags |= TSF_REGEXP;
matched = js_MatchToken(cx, ts, cg, TOK_RB);
ts->flags &= ~TSF_REGEXP;
if (!matched) {
for (index = 0; index < ATOM_INDEX_LIMIT; index++) {
ts->flags |= TSF_REGEXP;
tt = js_PeekToken(cx, ts, cg);
ts->flags &= ~TSF_REGEXP;
if (tt == TOK_RB) {
if (js_NewSrcNote(cx, cg, SRC_COMMA) < 0)
return JS_FALSE;
break;
}
if (index == 0) {
if (js_Emit1(cx, cg, JSOP_ZERO) < 0)
return JS_FALSE;
} else if (index == 1) {
if (js_Emit1(cx, cg, JSOP_ONE) < 0)
return JS_FALSE;
} else {
atomIndex = (jsatomid) index;
EMIT_CONST_ATOM_OP(JSOP_UINT16, atomIndex);
}
if (tt == TOK_COMMA) {
if (js_Emit1(cx, cg, JSOP_PUSH) < 0)
return JS_FALSE;
} else {
if (!AssignExpr(cx, ts, cg))
return JS_FALSE;
}
if (js_Emit1(cx, cg, JSOP_INITELEM) < 0)
return JS_FALSE;
if (!js_MatchToken(cx, ts, cg, TOK_COMMA))
break;
}
/* [balance: */
MUST_MATCH_TOKEN(TOK_RB, "missing ] after element list");
}
/* Emit an annotated op for sharpArray cleanup and decompilation. */
if (js_Emit1(cx, cg, JSOP_ENDINIT) < 0)
return JS_FALSE;
break;
}
case TOK_LC:
{
atomIndex = js_IndexAtom(cx, cx->runtime->atomState.ObjectAtom,
&cg->atomList);
EMIT_CONST_ATOM_OP(JSOP_NAME, atomIndex);
if (js_Emit1(cx, cg, JSOP_PUSHOBJ) < 0)
return JS_FALSE;
if (js_Emit1(cx, cg, JSOP_NEWINIT) < 0)
return JS_FALSE;
#if JS_HAS_SHARP_VARS
if (defsharp)
EMIT_CONST_ATOM_OP(JSOP_DEFSHARP, sharpnum);
#endif
if (!js_MatchToken(cx, ts, cg, TOK_RC)) {
do {
tt = js_GetToken(cx, ts, cg);
switch (tt) {
case TOK_NUMBER:
if (!EmitNumberOp(cx, ts->token.u.dval, cg))
return JS_FALSE;
break;
case TOK_NAME:
case TOK_STRING:
atomIndex = js_IndexAtom(cx, ts->token.u.atom,
&cg->atomList);
break;
default:
js_ReportCompileError(cx, ts, "invalid property id");
return JS_FALSE;
}
MUST_MATCH_TOKEN(TOK_COLON, "missing : after property id");
if (!AssignExpr(cx, ts, cg))
return JS_FALSE;
if (tt == TOK_NUMBER) {
if (js_NewSrcNote(cx, cg, SRC_LABEL) < 0)
return JS_FALSE;
if (js_Emit1(cx, cg, JSOP_INITELEM) < 0)
return JS_FALSE;
} else {
EMIT_CONST_ATOM_OP(JSOP_INITPROP, atomIndex);
}
} while (js_MatchToken(cx, ts, cg, TOK_COMMA));
/* {balance: */
MUST_MATCH_TOKEN(TOK_RC, "missing } after property list");
}
/* Emit an op for sharpArray cleanup and decompilation. */
if (js_Emit1(cx, cg, JSOP_ENDINIT) < 0)
return JS_FALSE;
break;
}
#if JS_HAS_SHARP_VARS
case TOK_DEFSHARP:
defsharp = JS_TRUE;
sharpnum = (jsatomid) ts->token.u.dval;
ts->flags |= TSF_REGEXP;
tt = js_PeekToken(cx, ts, cg);
ts->flags &= ~TSF_REGEXP;
if (tt != TOK_LB && tt != TOK_LC) {
js_ReportCompileError(cx, ts, "invalid sharp variable definition");
return JS_FALSE;
}
goto again;
case TOK_USESHARP:
/* Check for forward/dangling references at runtime, to allow eval. */
sharpnum = (jsatomid) ts->token.u.dval;
EMIT_CONST_ATOM_OP(JSOP_USESHARP, sharpnum);
break;
#endif /* JS_HAS_SHARP_VARS */
#endif /* JS_HAS_OBJECT_LITERAL */
case TOK_LP:
if (!Expr(cx, ts, cg))
return JS_FALSE;
/* (balance: */
MUST_MATCH_TOKEN(TOK_RP, "missing ) in parenthetical");
/* Emit an annotated NOP so we can decompile user parens. */
if (js_NewSrcNote(cx, cg, SRC_PAREN) < 0 ||
js_Emit1(cx, cg, JSOP_NOP) < 0) {
return JS_FALSE;
}
break;
case TOK_NAME:
atomIndex = js_IndexAtom(cx, ts->token.u.atom, &cg->atomList);
EMIT_CONST_ATOM_OP(JSOP_NAME, atomIndex);
break;
case TOK_NUMBER:
return EmitNumberOp(cx, ts->token.u.dval, cg);
case TOK_STRING:
atomIndex = js_IndexAtom(cx, ts->token.u.atom, &cg->atomList);
EMIT_CONST_ATOM_OP(JSOP_STRING, atomIndex);
break;
case TOK_OBJECT:
atomIndex = js_IndexAtom(cx, ts->token.u.atom, &cg->atomList);
EMIT_CONST_ATOM_OP(JSOP_OBJECT, atomIndex);
break;
case TOK_PRIMARY:
if (js_Emit1(cx, cg, ts->token.u.op) < 0)
return JS_FALSE;
break;
#if !JS_HAS_EXPORT_IMPORT
case TOK_EXPORT:
case TOK_IMPORT:
#endif
case TOK_RESERVED:
js_ReportCompileError(cx, ts, "identifier is a reserved word");
return JS_FALSE;
case TOK_ERROR:
/* The scanner or one of its subroutines reported the error. */
return JS_FALSE;
default:
js_ReportCompileError(cx, ts, "syntax error");
return JS_FALSE;
}
return JS_TRUE;
}