2456 lines
62 KiB
C
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;
|
|
}
|