4865 lines
154 KiB
C
4865 lines
154 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
*
|
|
* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Mozilla Communicator client code, released
|
|
* March 31, 1998.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1998
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
|
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
/*
|
|
* JS parser.
|
|
*
|
|
* This is a recursive-descent parser for the JavaScript language specified by
|
|
* "The JavaScript 1.5 Language Specification". It uses lexical and semantic
|
|
* feedback to disambiguate non-LL(1) structures. It generates trees of nodes
|
|
* induced by the recursive parsing (not precise syntax trees, see jsparse.h).
|
|
* After tree construction, it rewrites trees to fold constants and evaluate
|
|
* compile-time expressions. Finally, it calls js_EmitTree (see jsemit.h) to
|
|
* generate bytecode.
|
|
*
|
|
* This parser attempts no error recovery.
|
|
*/
|
|
#include "jsstddef.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include "jstypes.h"
|
|
#include "jsarena.h" /* Added by JSIFY */
|
|
#include "jsutil.h" /* Added by JSIFY */
|
|
#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"
|
|
|
|
#if JS_HAS_XML_SUPPORT
|
|
#include "jsxml.h"
|
|
#endif
|
|
|
|
/*
|
|
* JS parsers, from lowest to highest precedence.
|
|
*
|
|
* Each parser takes a context, a token stream, and a tree context struct.
|
|
* Each returns a parse node tree or null on error.
|
|
*/
|
|
|
|
typedef JSParseNode *
|
|
JSParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc);
|
|
|
|
typedef JSParseNode *
|
|
JSMemberParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
|
|
JSBool allowCallSyntax);
|
|
|
|
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 JSMemberParser MemberExpr;
|
|
static JSParser PrimaryExpr;
|
|
|
|
/*
|
|
* Insist that the next token be of type tt, or report errno and return null.
|
|
* NB: this macro uses cx and ts from its lexical environment.
|
|
*/
|
|
#define MUST_MATCH_TOKEN(tt, errno) \
|
|
JS_BEGIN_MACRO \
|
|
if (js_GetToken(cx, ts) != tt) { \
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR, \
|
|
errno); \
|
|
return NULL; \
|
|
} \
|
|
JS_END_MACRO
|
|
|
|
#define CHECK_RECURSION() \
|
|
JS_BEGIN_MACRO \
|
|
int stackDummy; \
|
|
if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) { \
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR, \
|
|
JSMSG_OVER_RECURSED); \
|
|
return NULL; \
|
|
} \
|
|
JS_END_MACRO
|
|
|
|
#ifdef METER_PARSENODES
|
|
static uint32 parsenodes = 0;
|
|
static uint32 maxparsenodes = 0;
|
|
static uint32 recyclednodes = 0;
|
|
#endif
|
|
|
|
static JSParseNode *
|
|
RecycleTree(JSParseNode *pn, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *next;
|
|
|
|
if (!pn)
|
|
return NULL;
|
|
JS_ASSERT(pn != tc->nodeList); /* catch back-to-back dup recycles */
|
|
next = pn->pn_next;
|
|
pn->pn_next = tc->nodeList;
|
|
tc->nodeList = pn;
|
|
#ifdef METER_PARSENODES
|
|
recyclednodes++;
|
|
#endif
|
|
return next;
|
|
}
|
|
|
|
static JSParseNode *
|
|
NewOrRecycledNode(JSContext *cx, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
|
|
pn = tc->nodeList;
|
|
if (!pn) {
|
|
JS_ARENA_ALLOCATE_TYPE(pn, JSParseNode, &cx->tempPool);
|
|
if (!pn)
|
|
JS_ReportOutOfMemory(cx);
|
|
} else {
|
|
tc->nodeList = pn->pn_next;
|
|
|
|
/* Recycle immediate descendents only, to save work and working set. */
|
|
switch (pn->pn_arity) {
|
|
case PN_FUNC:
|
|
RecycleTree(pn->pn_body, tc);
|
|
break;
|
|
case PN_LIST:
|
|
if (pn->pn_head) {
|
|
/* XXX check for dup recycles in the list */
|
|
*pn->pn_tail = tc->nodeList;
|
|
tc->nodeList = pn->pn_head;
|
|
#ifdef METER_PARSENODES
|
|
recyclednodes += pn->pn_count;
|
|
#endif
|
|
}
|
|
break;
|
|
case PN_TERNARY:
|
|
RecycleTree(pn->pn_kid1, tc);
|
|
RecycleTree(pn->pn_kid2, tc);
|
|
RecycleTree(pn->pn_kid3, tc);
|
|
break;
|
|
case PN_BINARY:
|
|
RecycleTree(pn->pn_left, tc);
|
|
RecycleTree(pn->pn_right, tc);
|
|
break;
|
|
case PN_UNARY:
|
|
RecycleTree(pn->pn_kid, tc);
|
|
break;
|
|
case PN_NAME:
|
|
RecycleTree(pn->pn_expr, tc);
|
|
break;
|
|
case PN_NULLARY:
|
|
break;
|
|
}
|
|
}
|
|
#ifdef METER_PARSENODES
|
|
if (pn) {
|
|
parsenodes++;
|
|
if (parsenodes - recyclednodes > maxparsenodes)
|
|
maxparsenodes = parsenodes - recyclednodes;
|
|
}
|
|
#endif
|
|
return pn;
|
|
}
|
|
|
|
/*
|
|
* Allocate a JSParseNode from cx's temporary arena.
|
|
*/
|
|
static JSParseNode *
|
|
NewParseNode(JSContext *cx, JSTokenStream *ts, JSParseNodeArity arity,
|
|
JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
JSToken *tp;
|
|
|
|
pn = NewOrRecycledNode(cx, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
tp = &CURRENT_TOKEN(ts);
|
|
pn->pn_type = tp->type;
|
|
pn->pn_pos = tp->pos;
|
|
pn->pn_op = JSOP_NOP;
|
|
pn->pn_arity = arity;
|
|
pn->pn_next = NULL;
|
|
#if JS_HAS_XML_SUPPORT
|
|
pn->pn_ts = ts;
|
|
#endif
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
NewBinary(JSContext *cx, JSTokenType tt,
|
|
JSOp op, JSParseNode *left, JSParseNode *right,
|
|
JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn, *pn1, *pn2;
|
|
|
|
if (!left || !right)
|
|
return NULL;
|
|
|
|
/*
|
|
* Flatten a left-associative (left-heavy) tree of a given operator into
|
|
* a list, to reduce js_FoldConstants and js_EmitTree recursion.
|
|
*/
|
|
if (left->pn_type == tt &&
|
|
left->pn_op == op &&
|
|
(js_CodeSpec[op].format & JOF_LEFTASSOC)) {
|
|
if (left->pn_arity != PN_LIST) {
|
|
pn1 = left->pn_left, pn2 = left->pn_right;
|
|
left->pn_arity = PN_LIST;
|
|
PN_INIT_LIST_1(left, pn1);
|
|
PN_APPEND(left, pn2);
|
|
if (tt == TOK_PLUS) {
|
|
if (pn1->pn_type == TOK_STRING)
|
|
left->pn_extra |= PNX_STRCAT;
|
|
else if (pn1->pn_type != TOK_NUMBER)
|
|
left->pn_extra |= PNX_CANTFOLD;
|
|
if (pn2->pn_type == TOK_STRING)
|
|
left->pn_extra |= PNX_STRCAT;
|
|
else if (pn2->pn_type != TOK_NUMBER)
|
|
left->pn_extra |= PNX_CANTFOLD;
|
|
}
|
|
}
|
|
PN_APPEND(left, right);
|
|
left->pn_pos.end = right->pn_pos.end;
|
|
if (tt == TOK_PLUS) {
|
|
if (right->pn_type == TOK_STRING)
|
|
left->pn_extra |= PNX_STRCAT;
|
|
else if (right->pn_type != TOK_NUMBER)
|
|
left->pn_extra |= PNX_CANTFOLD;
|
|
}
|
|
return left;
|
|
}
|
|
|
|
/*
|
|
* Fold constant addition immediately, to conserve node space and, what's
|
|
* more, so js_FoldConstants never sees mixed addition and concatenation
|
|
* operations with more than one leading non-string operand in a PN_LIST
|
|
* generated for expressions such as 1 + 2 + "pt" (which should evaluate
|
|
* to "3pt", not "12pt").
|
|
*/
|
|
if (tt == TOK_PLUS &&
|
|
left->pn_type == TOK_NUMBER &&
|
|
right->pn_type == TOK_NUMBER) {
|
|
left->pn_dval += right->pn_dval;
|
|
left->pn_pos.end = right->pn_pos.end;
|
|
RecycleTree(right, tc);
|
|
return left;
|
|
}
|
|
|
|
pn = NewOrRecycledNode(cx, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn->pn_type = tt;
|
|
pn->pn_pos.begin = left->pn_pos.begin;
|
|
pn->pn_pos.end = right->pn_pos.end;
|
|
pn->pn_op = op;
|
|
pn->pn_arity = PN_BINARY;
|
|
pn->pn_left = left;
|
|
pn->pn_right = right;
|
|
pn->pn_next = NULL;
|
|
#if JS_HAS_XML_SUPPORT
|
|
pn->pn_ts = NULL;
|
|
#endif
|
|
return pn;
|
|
}
|
|
|
|
#if JS_HAS_GETTER_SETTER
|
|
static JSTokenType
|
|
CheckGetterOrSetter(JSContext *cx, JSTokenStream *ts, JSTokenType tt)
|
|
{
|
|
JSAtom *atom;
|
|
JSRuntime *rt;
|
|
JSOp op;
|
|
const char *name;
|
|
|
|
JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_NAME);
|
|
atom = CURRENT_TOKEN(ts).t_atom;
|
|
rt = cx->runtime;
|
|
if (atom == rt->atomState.getterAtom)
|
|
op = JSOP_GETTER;
|
|
else if (atom == rt->atomState.setterAtom)
|
|
op = JSOP_SETTER;
|
|
else
|
|
return TOK_NAME;
|
|
if (js_PeekTokenSameLine(cx, ts) != tt)
|
|
return TOK_NAME;
|
|
(void) js_GetToken(cx, ts);
|
|
if (CURRENT_TOKEN(ts).t_op != JSOP_NOP) {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_GETTER_OR_SETTER,
|
|
(op == JSOP_GETTER)
|
|
? js_getter_str
|
|
: js_setter_str);
|
|
return TOK_ERROR;
|
|
}
|
|
CURRENT_TOKEN(ts).t_op = op;
|
|
name = js_AtomToPrintableString(cx, atom);
|
|
if (!name ||
|
|
!js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS |
|
|
JSREPORT_WARNING |
|
|
JSREPORT_STRICT,
|
|
JSMSG_DEPRECATED_USAGE,
|
|
name)) {
|
|
return TOK_ERROR;
|
|
}
|
|
return tt;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Parse a top-level JS script.
|
|
*/
|
|
JS_FRIEND_API(JSParseNode *)
|
|
js_ParseTokenStream(JSContext *cx, JSObject *chain, JSTokenStream *ts)
|
|
{
|
|
JSStackFrame *fp, frame;
|
|
JSTreeContext tc;
|
|
JSParseNode *pn;
|
|
|
|
/*
|
|
* Push a compiler frame if we have no frames, or if the top frame is a
|
|
* lightweight function activation, or if its scope chain doesn't match
|
|
* the one passed to us.
|
|
*/
|
|
fp = cx->fp;
|
|
if (!fp || !fp->varobj || fp->scopeChain != chain) {
|
|
memset(&frame, 0, sizeof frame);
|
|
frame.varobj = frame.scopeChain = chain;
|
|
if (cx->options & JSOPTION_VAROBJFIX) {
|
|
while ((chain = JS_GetParent(cx, chain)) != NULL)
|
|
frame.varobj = chain;
|
|
}
|
|
frame.down = fp;
|
|
if (fp)
|
|
frame.flags = fp->flags & (JSFRAME_SPECIAL | JSFRAME_COMPILE_N_GO);
|
|
cx->fp = &frame;
|
|
}
|
|
|
|
/*
|
|
* Protect atoms from being collected by a GC activation, which might
|
|
* - nest on this thread due to out of memory (the so-called "last ditch"
|
|
* GC attempted within js_NewGCThing), or
|
|
* - run for any reason on another thread if this thread is suspended on
|
|
* an object lock before it finishes generating bytecode into a script
|
|
* protected from the GC by a root or a stack frame reference.
|
|
*/
|
|
JS_KEEP_ATOMS(cx->runtime);
|
|
TREE_CONTEXT_INIT(&tc);
|
|
pn = Statements(cx, ts, &tc);
|
|
if (pn) {
|
|
if (!js_MatchToken(cx, ts, TOK_EOF)) {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_SYNTAX_ERROR);
|
|
pn = NULL;
|
|
} else {
|
|
pn->pn_type = TOK_LC;
|
|
if (!js_FoldConstants(cx, pn, &tc))
|
|
pn = NULL;
|
|
}
|
|
}
|
|
|
|
TREE_CONTEXT_FINISH(&tc);
|
|
JS_UNKEEP_ATOMS(cx->runtime);
|
|
cx->fp = fp;
|
|
return pn;
|
|
}
|
|
|
|
/*
|
|
* Compile a top-level script.
|
|
*/
|
|
JS_FRIEND_API(JSBool)
|
|
js_CompileTokenStream(JSContext *cx, JSObject *chain, JSTokenStream *ts,
|
|
JSCodeGenerator *cg)
|
|
{
|
|
JSStackFrame *fp, frame;
|
|
uint32 flags;
|
|
JSParseNode *pn;
|
|
JSBool ok;
|
|
#ifdef METER_PARSENODES
|
|
void *sbrk(ptrdiff_t), *before = sbrk(0);
|
|
#endif
|
|
|
|
/*
|
|
* Push a compiler frame if we have no frames, or if the top frame is a
|
|
* lightweight function activation, or if its scope chain doesn't match
|
|
* the one passed to us.
|
|
*/
|
|
fp = cx->fp;
|
|
if (!fp || !fp->varobj || fp->scopeChain != chain) {
|
|
memset(&frame, 0, sizeof frame);
|
|
frame.varobj = frame.scopeChain = chain;
|
|
if (cx->options & JSOPTION_VAROBJFIX) {
|
|
while ((chain = JS_GetParent(cx, chain)) != NULL)
|
|
frame.varobj = chain;
|
|
}
|
|
frame.down = fp;
|
|
if (fp)
|
|
frame.flags = fp->flags & (JSFRAME_SPECIAL | JSFRAME_COMPILE_N_GO);
|
|
cx->fp = &frame;
|
|
}
|
|
flags = cx->fp->flags;
|
|
cx->fp->flags = flags |
|
|
(JS_HAS_COMPILE_N_GO_OPTION(cx)
|
|
? JSFRAME_COMPILING | JSFRAME_COMPILE_N_GO
|
|
: JSFRAME_COMPILING);
|
|
|
|
/* Prevent GC activation while compiling. */
|
|
JS_KEEP_ATOMS(cx->runtime);
|
|
|
|
pn = Statements(cx, ts, &cg->treeContext);
|
|
if (!pn) {
|
|
ok = JS_FALSE;
|
|
} else if (!js_MatchToken(cx, ts, TOK_EOF)) {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_SYNTAX_ERROR);
|
|
ok = JS_FALSE;
|
|
} else {
|
|
#ifdef METER_PARSENODES
|
|
printf("Parser growth: %d (%u nodes, %u max, %u unrecycled)\n",
|
|
(char *)sbrk(0) - (char *)before,
|
|
parsenodes,
|
|
maxparsenodes,
|
|
parsenodes - recyclednodes);
|
|
before = sbrk(0);
|
|
#endif
|
|
|
|
/*
|
|
* No need to emit code here -- Statements already has, for each
|
|
* statement in turn. Search for TCF_COMPILING in Statements, below.
|
|
* That flag is set for every tc == &cg->treeContext, and it implies
|
|
* that the tc can be downcast to a cg and used to emit code during
|
|
* parsing, rather than at the end of the parse phase.
|
|
*/
|
|
JS_ASSERT(cg->treeContext.flags & TCF_COMPILING);
|
|
ok = JS_TRUE;
|
|
}
|
|
|
|
#ifdef METER_PARSENODES
|
|
printf("Code-gen growth: %d (%u bytecodes, %u srcnotes)\n",
|
|
(char *)sbrk(0) - (char *)before, CG_OFFSET(cg), cg->noteCount);
|
|
#endif
|
|
#ifdef JS_ARENAMETER
|
|
JS_DumpArenaStats(stdout);
|
|
#endif
|
|
JS_UNKEEP_ATOMS(cx->runtime);
|
|
cx->fp->flags = flags;
|
|
cx->fp = fp;
|
|
return ok;
|
|
}
|
|
|
|
/*
|
|
* Insist on a final return before control flows out of pn, but don't be too
|
|
* smart about loops (do {...; return e2;} while(0) at the end of a function
|
|
* that contains an early return e1 will get a strict-option-only warning).
|
|
*/
|
|
#define ENDS_IN_OTHER 0
|
|
#define ENDS_IN_RETURN 1
|
|
#define ENDS_IN_BREAK 2
|
|
|
|
static int
|
|
HasFinalReturn(JSParseNode *pn)
|
|
{
|
|
uintN rv, rv2, hasDefault;
|
|
JSParseNode *pn2, *pn3;
|
|
|
|
switch (pn->pn_type) {
|
|
case TOK_LC:
|
|
if (!pn->pn_head)
|
|
return ENDS_IN_OTHER;
|
|
return HasFinalReturn(PN_LAST(pn));
|
|
|
|
case TOK_IF:
|
|
rv = HasFinalReturn(pn->pn_kid2);
|
|
if (pn->pn_kid3)
|
|
rv &= HasFinalReturn(pn->pn_kid3);
|
|
return rv;
|
|
|
|
#if JS_HAS_SWITCH_STATEMENT
|
|
case TOK_SWITCH:
|
|
rv = ENDS_IN_RETURN;
|
|
hasDefault = ENDS_IN_OTHER;
|
|
for (pn2 = pn->pn_kid2->pn_head; rv && pn2; pn2 = pn2->pn_next) {
|
|
if (pn2->pn_type == TOK_DEFAULT)
|
|
hasDefault = ENDS_IN_RETURN;
|
|
pn3 = pn2->pn_right;
|
|
JS_ASSERT(pn3->pn_type == TOK_LC);
|
|
if (pn3->pn_head) {
|
|
rv2 = HasFinalReturn(PN_LAST(pn3));
|
|
if (rv2 == ENDS_IN_OTHER && pn2->pn_next)
|
|
/* Falling through to next case or default. */;
|
|
else
|
|
rv &= rv2;
|
|
}
|
|
}
|
|
/* If a final switch has no default case, we judge it harshly. */
|
|
rv &= hasDefault;
|
|
return rv;
|
|
#endif /* JS_HAS_SWITCH_STATEMENT */
|
|
|
|
case TOK_BREAK:
|
|
return ENDS_IN_BREAK;
|
|
|
|
case TOK_WITH:
|
|
return HasFinalReturn(pn->pn_right);
|
|
|
|
case TOK_RETURN:
|
|
return ENDS_IN_RETURN;
|
|
|
|
case TOK_COLON:
|
|
return HasFinalReturn(pn->pn_expr);
|
|
|
|
#if JS_HAS_EXCEPTIONS
|
|
case TOK_THROW:
|
|
return ENDS_IN_RETURN;
|
|
|
|
case TOK_TRY:
|
|
/* If we have a finally block that returns, we are done. */
|
|
if (pn->pn_kid3) {
|
|
rv = HasFinalReturn(pn->pn_kid3);
|
|
if (rv == ENDS_IN_RETURN)
|
|
return rv;
|
|
}
|
|
|
|
/* Else check the try block and any and all catch statements. */
|
|
rv = HasFinalReturn(pn->pn_kid1);
|
|
if (pn->pn_kid2)
|
|
rv &= HasFinalReturn(pn->pn_kid2);
|
|
return rv;
|
|
|
|
case TOK_CATCH:
|
|
/* Check this block's code and iterate over further catch blocks. */
|
|
rv = HasFinalReturn(pn->pn_kid3);
|
|
for (pn2 = pn->pn_kid2; pn2; pn2 = pn2->pn_kid2)
|
|
rv &= HasFinalReturn(pn2->pn_kid3);
|
|
return rv;
|
|
#endif
|
|
|
|
default:
|
|
return ENDS_IN_OTHER;
|
|
}
|
|
}
|
|
|
|
static JSBool
|
|
ReportNoReturnValue(JSContext *cx, JSTokenStream *ts)
|
|
{
|
|
JSFunction *fun;
|
|
JSBool ok;
|
|
|
|
fun = cx->fp->fun;
|
|
if (fun->atom) {
|
|
char *name = js_GetStringBytes(ATOM_TO_STRING(fun->atom));
|
|
ok = js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS |
|
|
JSREPORT_WARNING |
|
|
JSREPORT_STRICT,
|
|
JSMSG_NO_RETURN_VALUE, name);
|
|
} else {
|
|
ok = js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS |
|
|
JSREPORT_WARNING |
|
|
JSREPORT_STRICT,
|
|
JSMSG_ANON_NO_RETURN_VALUE);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static JSBool
|
|
CheckFinalReturn(JSContext *cx, JSTokenStream *ts, JSParseNode *pn)
|
|
{
|
|
return HasFinalReturn(pn) == ENDS_IN_RETURN || ReportNoReturnValue(cx, ts);
|
|
}
|
|
|
|
static JSParseNode *
|
|
FunctionBody(JSContext *cx, JSTokenStream *ts, JSFunction *fun,
|
|
JSTreeContext *tc)
|
|
{
|
|
JSStackFrame *fp, frame;
|
|
JSObject *funobj;
|
|
uintN oldflags;
|
|
JSParseNode *pn;
|
|
|
|
fp = cx->fp;
|
|
funobj = fun->object;
|
|
if (!fp || fp->fun != fun || fp->varobj != funobj ||
|
|
fp->scopeChain != funobj) {
|
|
memset(&frame, 0, sizeof frame);
|
|
frame.fun = fun;
|
|
frame.varobj = frame.scopeChain = funobj;
|
|
frame.down = fp;
|
|
if (fp)
|
|
frame.flags = fp->flags & JSFRAME_COMPILE_N_GO;
|
|
cx->fp = &frame;
|
|
}
|
|
|
|
oldflags = tc->flags;
|
|
tc->flags &= ~(TCF_RETURN_EXPR | TCF_RETURN_VOID);
|
|
tc->flags |= TCF_IN_FUNCTION;
|
|
pn = Statements(cx, ts, tc);
|
|
|
|
/* Check for falling off the end of a function that returns a value. */
|
|
if (pn && JS_HAS_STRICT_OPTION(cx) && (tc->flags & TCF_RETURN_EXPR)) {
|
|
if (!CheckFinalReturn(cx, ts, pn))
|
|
pn = NULL;
|
|
}
|
|
|
|
cx->fp = fp;
|
|
tc->flags = oldflags | (tc->flags & (TCF_FUN_FLAGS | TCF_HAS_DEFXMLNS));
|
|
return pn;
|
|
}
|
|
|
|
/*
|
|
* Compile a JS function body, which might appear as the value of an event
|
|
* handler attribute in an HTML <INPUT> tag.
|
|
*/
|
|
JSBool
|
|
js_CompileFunctionBody(JSContext *cx, JSTokenStream *ts, JSFunction *fun)
|
|
{
|
|
JSArenaPool codePool, notePool;
|
|
JSCodeGenerator funcg;
|
|
JSStackFrame *fp, frame;
|
|
JSObject *funobj;
|
|
JSParseNode *pn;
|
|
JSBool ok;
|
|
|
|
JS_InitArenaPool(&codePool, "code", 1024, sizeof(jsbytecode));
|
|
JS_InitArenaPool(¬ePool, "note", 1024, sizeof(jssrcnote));
|
|
if (!js_InitCodeGenerator(cx, &funcg, &codePool, ¬ePool,
|
|
ts->filename, ts->lineno,
|
|
ts->principals)) {
|
|
return JS_FALSE;
|
|
}
|
|
|
|
/* Prevent GC activation while compiling. */
|
|
JS_KEEP_ATOMS(cx->runtime);
|
|
|
|
/* Push a JSStackFrame for use by FunctionBody. */
|
|
fp = cx->fp;
|
|
funobj = fun->object;
|
|
JS_ASSERT(!fp || (fp->fun != fun && fp->varobj != funobj &&
|
|
fp->scopeChain != funobj));
|
|
memset(&frame, 0, sizeof frame);
|
|
frame.fun = fun;
|
|
frame.varobj = frame.scopeChain = funobj;
|
|
frame.down = fp;
|
|
frame.flags = JS_HAS_COMPILE_N_GO_OPTION(cx)
|
|
? JSFRAME_COMPILING | JSFRAME_COMPILE_N_GO
|
|
: JSFRAME_COMPILING;
|
|
cx->fp = &frame;
|
|
|
|
/* Ensure that the body looks like a block statement to js_EmitTree. */
|
|
CURRENT_TOKEN(ts).type = TOK_LC;
|
|
pn = FunctionBody(cx, ts, fun, &funcg.treeContext);
|
|
if (!pn) {
|
|
ok = JS_FALSE;
|
|
} else {
|
|
/*
|
|
* No need to emit code here -- Statements (via FunctionBody) already
|
|
* has. See similar comment in js_CompileTokenStream, and bug 108257.
|
|
*/
|
|
fun->u.script = js_NewScriptFromCG(cx, &funcg, fun);
|
|
if (!fun->u.script) {
|
|
ok = JS_FALSE;
|
|
} else {
|
|
fun->interpreted = JS_TRUE;
|
|
if (funcg.treeContext.flags & TCF_FUN_HEAVYWEIGHT)
|
|
fun->flags |= JSFUN_HEAVYWEIGHT;
|
|
ok = JS_TRUE;
|
|
}
|
|
}
|
|
|
|
/* Restore saved state and release code generation arenas. */
|
|
cx->fp = fp;
|
|
JS_UNKEEP_ATOMS(cx->runtime);
|
|
js_FinishCodeGenerator(cx, &funcg);
|
|
JS_FinishArenaPool(&codePool);
|
|
JS_FinishArenaPool(¬ePool);
|
|
return ok;
|
|
}
|
|
|
|
static JSParseNode *
|
|
FunctionDef(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
|
|
JSBool lambda)
|
|
{
|
|
JSOp op, prevop;
|
|
JSParseNode *pn, *body, *result;
|
|
JSAtom *funAtom, *objAtom, *argAtom;
|
|
JSStackFrame *fp;
|
|
JSObject *varobj, *pobj;
|
|
JSAtomListElement *ale;
|
|
JSProperty *prop;
|
|
JSFunction *fun;
|
|
uintN dupflag;
|
|
JSBool ok;
|
|
JSTreeContext funtc;
|
|
|
|
/* Make a TOK_FUNCTION node. */
|
|
#if JS_HAS_GETTER_SETTER
|
|
op = CURRENT_TOKEN(ts).t_op;
|
|
#endif
|
|
pn = NewParseNode(cx, ts, PN_FUNC, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
|
|
/* Scan the optional function name into funAtom. */
|
|
funAtom = js_MatchToken(cx, ts, TOK_NAME) ? CURRENT_TOKEN(ts).t_atom : NULL;
|
|
#if !JS_HAS_LEXICAL_CLOSURE
|
|
if (!funAtom && !lambda) {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_SYNTAX_ERROR);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
/* Find the nearest variable-declaring scope and use it as our parent. */
|
|
fp = cx->fp;
|
|
varobj = fp->varobj;
|
|
|
|
/*
|
|
* Record names for function statements in tc->decls so we know when to
|
|
* avoid optimizing variable references that might name a function.
|
|
*/
|
|
if (!lambda && funAtom) {
|
|
ATOM_LIST_SEARCH(ale, &tc->decls, funAtom);
|
|
if (ale) {
|
|
prevop = ALE_JSOP(ale);
|
|
if (JS_HAS_STRICT_OPTION(cx) || prevop == JSOP_DEFCONST) {
|
|
const char *name = js_AtomToPrintableString(cx, funAtom);
|
|
if (!name ||
|
|
!js_ReportCompileErrorNumber(cx, ts,
|
|
(prevop != JSOP_DEFCONST)
|
|
? JSREPORT_TS |
|
|
JSREPORT_WARNING |
|
|
JSREPORT_STRICT
|
|
: JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_REDECLARED_VAR,
|
|
(prevop == JSOP_DEFFUN ||
|
|
prevop == JSOP_CLOSURE)
|
|
? js_function_str
|
|
: (prevop == JSOP_DEFCONST)
|
|
? js_const_str
|
|
: js_var_str,
|
|
name)) {
|
|
return NULL;
|
|
}
|
|
}
|
|
if (tc->topStmt && prevop == JSOP_DEFVAR)
|
|
tc->flags |= TCF_FUN_CLOSURE_VS_VAR;
|
|
} else {
|
|
ale = js_IndexAtom(cx, funAtom, &tc->decls);
|
|
if (!ale)
|
|
return NULL;
|
|
}
|
|
ALE_SET_JSOP(ale, tc->topStmt ? JSOP_CLOSURE : JSOP_DEFFUN);
|
|
|
|
#if JS_HAS_LEXICAL_CLOSURE
|
|
/*
|
|
* A function nested at top level inside another's body needs only a
|
|
* local variable to bind its name to its value, and not an activation
|
|
* object property (it might also need the activation property, if the
|
|
* outer function contains with statements, e.g., but the stack slot
|
|
* wins when jsemit.c's LookupArgOrVar can optimize a JSOP_NAME into a
|
|
* JSOP_GETVAR bytecode).
|
|
*/
|
|
if (!tc->topStmt && (tc->flags & TCF_IN_FUNCTION)) {
|
|
/*
|
|
* Define a property on the outer function so that LookupArgOrVar
|
|
* can properly optimize accesses.
|
|
*/
|
|
JS_ASSERT(OBJ_GET_CLASS(cx, varobj) == &js_FunctionClass);
|
|
JS_ASSERT(fp->fun == (JSFunction *) JS_GetPrivate(cx, varobj));
|
|
if (!js_LookupHiddenProperty(cx, varobj, ATOM_TO_JSID(funAtom),
|
|
&pobj, &prop)) {
|
|
return NULL;
|
|
}
|
|
if (prop)
|
|
OBJ_DROP_PROPERTY(cx, pobj, prop);
|
|
if (!prop ||
|
|
pobj != varobj ||
|
|
((JSScopeProperty *)prop)->getter != js_GetLocalVariable) {
|
|
if (!js_AddHiddenProperty(cx, varobj, ATOM_TO_JSID(funAtom),
|
|
js_GetLocalVariable,
|
|
js_SetLocalVariable,
|
|
SPROP_INVALID_SLOT,
|
|
JSPROP_PERMANENT | JSPROP_SHARED,
|
|
SPROP_HAS_SHORTID, fp->fun->nvars)) {
|
|
return NULL;
|
|
}
|
|
if (fp->fun->nvars == JS_BITMASK(16)) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_TOO_MANY_FUN_VARS);
|
|
return NULL;
|
|
}
|
|
fp->fun->nvars++;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
fun = js_NewFunction(cx, NULL, NULL, 0, lambda ? JSFUN_LAMBDA : 0, varobj,
|
|
funAtom);
|
|
if (!fun)
|
|
return NULL;
|
|
#if JS_HAS_GETTER_SETTER
|
|
if (op != JSOP_NOP)
|
|
fun->flags |= (op == JSOP_GETTER) ? JSPROP_GETTER : JSPROP_SETTER;
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Set interpreted early so js_EmitTree can test it to decide whether to
|
|
* eliminate useless expressions.
|
|
*/
|
|
fun->interpreted = JS_TRUE;
|
|
|
|
/*
|
|
* Atomize fun->object early to protect against a last-ditch GC under
|
|
* js_LookupHiddenProperty.
|
|
*
|
|
* Absent use of the new scoped local GC roots API around compiler calls,
|
|
* we need to atomize here to protect against a GC activation. Atoms are
|
|
* protected from GC during compilation by the JS_FRIEND_API entry points
|
|
* in this file. There doesn't seem to be any gain in switching from the
|
|
* atom-keeping method to the bulkier, slower scoped local roots method.
|
|
*/
|
|
objAtom = js_AtomizeObject(cx, fun->object, 0);
|
|
if (!objAtom)
|
|
return NULL;
|
|
|
|
/* Now parse formal argument list and compute fun->nargs. */
|
|
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_FORMAL);
|
|
if (!js_MatchToken(cx, ts, TOK_RP)) {
|
|
do {
|
|
MUST_MATCH_TOKEN(TOK_NAME, JSMSG_MISSING_FORMAL);
|
|
argAtom = CURRENT_TOKEN(ts).t_atom;
|
|
pobj = NULL;
|
|
if (!js_LookupHiddenProperty(cx, fun->object, ATOM_TO_JSID(argAtom),
|
|
&pobj, &prop)) {
|
|
return NULL;
|
|
}
|
|
dupflag = 0;
|
|
if (prop) {
|
|
ok = JS_TRUE;
|
|
if (pobj == fun->object &&
|
|
((JSScopeProperty *) prop)->getter == js_GetArgument) {
|
|
const char *name = js_AtomToPrintableString(cx, argAtom);
|
|
|
|
/*
|
|
* A duplicate parameter name. We force a duplicate node
|
|
* on the SCOPE_LAST_PROP(scope) list with the same id,
|
|
* distinguished by the SPROP_IS_DUPLICATE flag, and not
|
|
* mapped by an entry in scope.
|
|
*/
|
|
ok = name &&
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS |
|
|
JSREPORT_WARNING |
|
|
JSREPORT_STRICT,
|
|
JSMSG_DUPLICATE_FORMAL,
|
|
name);
|
|
|
|
dupflag = SPROP_IS_DUPLICATE;
|
|
}
|
|
OBJ_DROP_PROPERTY(cx, pobj, prop);
|
|
if (!ok)
|
|
return NULL;
|
|
prop = NULL;
|
|
}
|
|
if (!js_AddHiddenProperty(cx, fun->object, ATOM_TO_JSID(argAtom),
|
|
js_GetArgument, js_SetArgument,
|
|
SPROP_INVALID_SLOT,
|
|
JSPROP_PERMANENT | JSPROP_SHARED,
|
|
dupflag | SPROP_HAS_SHORTID,
|
|
fun->nargs)) {
|
|
return NULL;
|
|
}
|
|
if (fun->nargs == JS_BITMASK(16)) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_TOO_MANY_FUN_ARGS);
|
|
return NULL;
|
|
}
|
|
fun->nargs++;
|
|
} while (js_MatchToken(cx, ts, TOK_COMMA));
|
|
|
|
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FORMAL);
|
|
}
|
|
|
|
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_BODY);
|
|
pn->pn_pos.begin = CURRENT_TOKEN(ts).pos.begin;
|
|
|
|
TREE_CONTEXT_INIT(&funtc);
|
|
body = FunctionBody(cx, ts, fun, &funtc);
|
|
if (!body)
|
|
return NULL;
|
|
|
|
MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_BODY);
|
|
pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
|
|
|
|
#if JS_HAS_LEXICAL_CLOSURE
|
|
/*
|
|
* If we collected flags that indicate nested heavyweight functions, or
|
|
* this function contains heavyweight-making statements (references to
|
|
* __parent__ or __proto__; use of with, eval, import, or export; and
|
|
* assignment to arguments), flag the function as heavyweight (requiring
|
|
* a call object per invocation).
|
|
*/
|
|
if (funtc.flags & TCF_FUN_HEAVYWEIGHT) {
|
|
fun->flags |= JSFUN_HEAVYWEIGHT;
|
|
tc->flags |= TCF_FUN_HEAVYWEIGHT;
|
|
} else {
|
|
/*
|
|
* If this function is a named statement function not at top-level
|
|
* (i.e. a JSOP_CLOSURE), or if it refers to unqualified names that
|
|
* are not local args or vars (TCF_FUN_USES_NONLOCALS), then our
|
|
* enclosing function, if any, must be heavyweight.
|
|
*/
|
|
if ((!lambda && funAtom && tc->topStmt) ||
|
|
(funtc.flags & TCF_FUN_USES_NONLOCALS)) {
|
|
tc->flags |= TCF_FUN_HEAVYWEIGHT;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
result = pn;
|
|
#if JS_HAS_LEXICAL_CLOSURE
|
|
if (lambda) {
|
|
/*
|
|
* ECMA ed. 3 standard: function expression, possibly anonymous.
|
|
*/
|
|
op = funAtom ? JSOP_NAMEDFUNOBJ : JSOP_ANONFUNOBJ;
|
|
} else if (!funAtom) {
|
|
/*
|
|
* If this anonymous function definition is *not* embedded within a
|
|
* larger expression, we treat it as an expression statement, not as
|
|
* a function declaration -- and not as a syntax error (as ECMA-262
|
|
* Edition 3 would have it). Backward compatibility trumps all.
|
|
*/
|
|
result = NewParseNode(cx, ts, PN_UNARY, tc);
|
|
if (!result)
|
|
return NULL;
|
|
result->pn_type = TOK_SEMI;
|
|
result->pn_pos = pn->pn_pos;
|
|
result->pn_kid = pn;
|
|
op = JSOP_ANONFUNOBJ;
|
|
} else if (tc->topStmt) {
|
|
/*
|
|
* ECMA ed. 3 extension: a function expression statement not at the
|
|
* top level, e.g., in a compound statement such as the "then" part
|
|
* of an "if" statement, binds a closure only if control reaches that
|
|
* sub-statement.
|
|
*/
|
|
op = JSOP_CLOSURE;
|
|
} else
|
|
#endif
|
|
op = JSOP_NOP;
|
|
|
|
pn->pn_funAtom = objAtom;
|
|
pn->pn_op = op;
|
|
pn->pn_body = body;
|
|
pn->pn_flags = funtc.flags & (TCF_FUN_FLAGS | TCF_HAS_DEFXMLNS);
|
|
pn->pn_tryCount = funtc.tryCount;
|
|
TREE_CONTEXT_FINISH(&funtc);
|
|
return result;
|
|
}
|
|
|
|
static JSParseNode *
|
|
FunctionStmt(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
return FunctionDef(cx, ts, tc, JS_FALSE);
|
|
}
|
|
|
|
#if JS_HAS_LEXICAL_CLOSURE
|
|
static JSParseNode *
|
|
FunctionExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
return FunctionDef(cx, ts, tc, JS_TRUE);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Parse the statements in a block, creating a TOK_LC node that lists the
|
|
* statements' trees. If called from block-parsing code, the caller must
|
|
* match { before and } after.
|
|
*/
|
|
static JSParseNode *
|
|
Statements(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn, *pn2;
|
|
JSTokenType tt;
|
|
|
|
CHECK_RECURSION();
|
|
|
|
pn = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
PN_INIT_LIST(pn);
|
|
|
|
ts->flags |= TSF_OPERAND;
|
|
while ((tt = js_PeekToken(cx, ts)) > TOK_EOF && tt != TOK_RC) {
|
|
ts->flags &= ~TSF_OPERAND;
|
|
pn2 = Statement(cx, ts, tc);
|
|
if (!pn2) {
|
|
if (ts->flags & TSF_EOF)
|
|
ts->flags |= TSF_UNEXPECTED_EOF;
|
|
return NULL;
|
|
}
|
|
ts->flags |= TSF_OPERAND;
|
|
|
|
/* If compiling top-level statements, emit as we go to save space. */
|
|
if (!tc->topStmt && (tc->flags & TCF_COMPILING)) {
|
|
if (cx->fp->fun &&
|
|
JS_HAS_STRICT_OPTION(cx) &&
|
|
(tc->flags & TCF_RETURN_EXPR)) {
|
|
/*
|
|
* Check pn2 for lack of a final return statement if it is the
|
|
* last statement in the block.
|
|
*/
|
|
tt = js_PeekToken(cx, ts);
|
|
if ((tt == TOK_EOF || tt == TOK_RC) &&
|
|
!CheckFinalReturn(cx, ts, pn2)) {
|
|
tt = TOK_ERROR;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Clear TCF_RETURN_EXPR so FunctionBody doesn't try to
|
|
* CheckFinalReturn again.
|
|
*/
|
|
tc->flags &= ~TCF_RETURN_EXPR;
|
|
}
|
|
if (!js_FoldConstants(cx, pn2, tc) ||
|
|
!js_AllocTryNotes(cx, (JSCodeGenerator *)tc) ||
|
|
!js_EmitTree(cx, (JSCodeGenerator *)tc, pn2)) {
|
|
tt = TOK_ERROR;
|
|
break;
|
|
}
|
|
RecycleTree(pn2, tc);
|
|
} else {
|
|
PN_APPEND(pn, pn2);
|
|
}
|
|
}
|
|
ts->flags &= ~TSF_OPERAND;
|
|
if (tt == TOK_ERROR)
|
|
return NULL;
|
|
|
|
pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
Condition(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn, *pn2;
|
|
|
|
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND);
|
|
pn = Expr(cx, ts, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND);
|
|
|
|
/*
|
|
* Check for (a = b) and "correct" it to (a == b) iff b's operator has
|
|
* greater precedence than ==.
|
|
* XXX not ECMA, but documented in several books -- now a strict warning.
|
|
*/
|
|
if (pn->pn_type == TOK_ASSIGN &&
|
|
pn->pn_op == JSOP_NOP &&
|
|
pn->pn_right->pn_type > TOK_EQOP)
|
|
{
|
|
JSBool rewrite = !JS_VERSION_IS_ECMA(cx);
|
|
if (!js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS |
|
|
JSREPORT_WARNING |
|
|
JSREPORT_STRICT,
|
|
JSMSG_EQUAL_AS_ASSIGN,
|
|
rewrite
|
|
? "\nAssuming equality test"
|
|
: "")) {
|
|
return NULL;
|
|
}
|
|
if (rewrite) {
|
|
pn->pn_type = TOK_EQOP;
|
|
pn->pn_op = (JSOp)cx->jsop_eq;
|
|
pn2 = pn->pn_left;
|
|
switch (pn2->pn_op) {
|
|
case JSOP_SETNAME:
|
|
pn2->pn_op = JSOP_NAME;
|
|
break;
|
|
case JSOP_SETPROP:
|
|
pn2->pn_op = JSOP_GETPROP;
|
|
break;
|
|
case JSOP_SETELEM:
|
|
pn2->pn_op = JSOP_GETELEM;
|
|
break;
|
|
default:
|
|
JS_ASSERT(0);
|
|
}
|
|
}
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
static JSBool
|
|
MatchLabel(JSContext *cx, JSTokenStream *ts, JSParseNode *pn)
|
|
{
|
|
JSAtom *label;
|
|
#if JS_HAS_LABEL_STATEMENT
|
|
JSTokenType tt;
|
|
|
|
tt = js_PeekTokenSameLine(cx, ts);
|
|
if (tt == TOK_ERROR)
|
|
return JS_FALSE;
|
|
if (tt == TOK_NAME) {
|
|
(void) js_GetToken(cx, ts);
|
|
label = CURRENT_TOKEN(ts).t_atom;
|
|
} else {
|
|
label = NULL;
|
|
}
|
|
#else
|
|
label = NULL;
|
|
#endif
|
|
pn->pn_atom = label;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
#if JS_HAS_EXPORT_IMPORT
|
|
static JSParseNode *
|
|
ImportExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn, *pn2;
|
|
JSTokenType tt;
|
|
|
|
MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_IMPORT_NAME);
|
|
pn = NewParseNode(cx, ts, PN_NAME, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn->pn_op = JSOP_NAME;
|
|
pn->pn_atom = CURRENT_TOKEN(ts).t_atom;
|
|
pn->pn_expr = NULL;
|
|
pn->pn_slot = -1;
|
|
pn->pn_attrs = 0;
|
|
|
|
ts->flags |= TSF_OPERAND;
|
|
while ((tt = js_GetToken(cx, ts)) == TOK_DOT || tt == TOK_LB) {
|
|
ts->flags &= ~TSF_OPERAND;
|
|
if (pn->pn_op == JSOP_IMPORTALL)
|
|
goto bad_import;
|
|
|
|
if (tt == TOK_DOT) {
|
|
pn2 = NewParseNode(cx, ts, PN_NAME, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
if (js_MatchToken(cx, ts, TOK_STAR)) {
|
|
pn2->pn_op = JSOP_IMPORTALL;
|
|
pn2->pn_atom = NULL;
|
|
} else {
|
|
MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NAME_AFTER_DOT);
|
|
pn2->pn_op = JSOP_GETPROP;
|
|
pn2->pn_atom = CURRENT_TOKEN(ts).t_atom;
|
|
pn2->pn_slot = -1;
|
|
pn2->pn_attrs = 0;
|
|
}
|
|
pn2->pn_expr = pn;
|
|
pn2->pn_pos.begin = pn->pn_pos.begin;
|
|
pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
|
|
} else {
|
|
/* Make a TOK_LB binary node. */
|
|
pn2 = NewBinary(cx, tt, JSOP_GETELEM, pn, Expr(cx, ts, tc), tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
|
|
MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX);
|
|
}
|
|
|
|
pn = pn2;
|
|
ts->flags |= TSF_OPERAND;
|
|
}
|
|
ts->flags &= ~TSF_OPERAND;
|
|
if (tt == TOK_ERROR)
|
|
return NULL;
|
|
js_UngetToken(ts);
|
|
|
|
switch (pn->pn_op) {
|
|
case JSOP_GETPROP:
|
|
pn->pn_op = JSOP_IMPORTPROP;
|
|
break;
|
|
case JSOP_GETELEM:
|
|
pn->pn_op = JSOP_IMPORTELEM;
|
|
break;
|
|
case JSOP_IMPORTALL:
|
|
break;
|
|
default:
|
|
goto bad_import;
|
|
}
|
|
return pn;
|
|
|
|
bad_import:
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_IMPORT);
|
|
return NULL;
|
|
}
|
|
#endif /* JS_HAS_EXPORT_IMPORT */
|
|
|
|
extern const char js_with_statement_str[];
|
|
|
|
static JSParseNode *
|
|
Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSTokenType tt;
|
|
JSParseNode *pn, *pn1, *pn2, *pn3, *pn4;
|
|
JSStmtInfo stmtInfo, *stmt, *stmt2;
|
|
JSAtom *label;
|
|
|
|
CHECK_RECURSION();
|
|
|
|
ts->flags |= TSF_OPERAND;
|
|
tt = js_GetToken(cx, ts);
|
|
ts->flags &= ~TSF_OPERAND;
|
|
|
|
#if JS_HAS_GETTER_SETTER
|
|
if (tt == TOK_NAME) {
|
|
tt = CheckGetterOrSetter(cx, ts, TOK_FUNCTION);
|
|
if (tt == TOK_ERROR)
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
switch (tt) {
|
|
#if JS_HAS_EXPORT_IMPORT
|
|
case TOK_EXPORT:
|
|
pn = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
PN_INIT_LIST(pn);
|
|
if (js_MatchToken(cx, ts, TOK_STAR)) {
|
|
pn2 = NewParseNode(cx, ts, PN_NULLARY, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
PN_APPEND(pn, pn2);
|
|
} else {
|
|
do {
|
|
MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_EXPORT_NAME);
|
|
pn2 = NewParseNode(cx, ts, PN_NAME, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn2->pn_op = JSOP_NAME;
|
|
pn2->pn_atom = CURRENT_TOKEN(ts).t_atom;
|
|
pn2->pn_expr = NULL;
|
|
pn2->pn_slot = -1;
|
|
pn2->pn_attrs = 0;
|
|
PN_APPEND(pn, pn2);
|
|
} while (js_MatchToken(cx, ts, TOK_COMMA));
|
|
}
|
|
pn->pn_pos.end = PN_LAST(pn)->pn_pos.end;
|
|
tc->flags |= TCF_FUN_HEAVYWEIGHT;
|
|
break;
|
|
|
|
case TOK_IMPORT:
|
|
pn = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
PN_INIT_LIST(pn);
|
|
do {
|
|
pn2 = ImportExpr(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
PN_APPEND(pn, pn2);
|
|
} while (js_MatchToken(cx, ts, TOK_COMMA));
|
|
pn->pn_pos.end = PN_LAST(pn)->pn_pos.end;
|
|
tc->flags |= TCF_FUN_HEAVYWEIGHT;
|
|
break;
|
|
#endif /* JS_HAS_EXPORT_IMPORT */
|
|
|
|
case TOK_FUNCTION:
|
|
#if JS_HAS_XML_SUPPORT
|
|
if (js_PeekToken(cx, ts) == TOK_DBLCOLON)
|
|
goto expression;
|
|
#endif
|
|
return FunctionStmt(cx, ts, tc);
|
|
|
|
case TOK_IF:
|
|
/* An IF node has three kids: condition, then, and optional else. */
|
|
pn = NewParseNode(cx, ts, PN_TERNARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn1 = Condition(cx, ts, tc);
|
|
if (!pn1)
|
|
return NULL;
|
|
js_PushStatement(tc, &stmtInfo, STMT_IF, -1);
|
|
pn2 = Statement(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
if (js_MatchToken(cx, ts, TOK_ELSE)) {
|
|
stmtInfo.type = STMT_ELSE;
|
|
pn3 = Statement(cx, ts, tc);
|
|
if (!pn3)
|
|
return NULL;
|
|
pn->pn_pos.end = pn3->pn_pos.end;
|
|
} else {
|
|
pn3 = NULL;
|
|
pn->pn_pos.end = pn2->pn_pos.end;
|
|
}
|
|
js_PopStatement(tc);
|
|
pn->pn_kid1 = pn1;
|
|
pn->pn_kid2 = pn2;
|
|
pn->pn_kid3 = pn3;
|
|
return pn;
|
|
|
|
#if JS_HAS_SWITCH_STATEMENT
|
|
case TOK_SWITCH:
|
|
{
|
|
JSParseNode *pn5;
|
|
JSBool seenDefault = JS_FALSE;
|
|
|
|
pn = NewParseNode(cx, ts, PN_BINARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_SWITCH);
|
|
|
|
/* pn1 points to the switch's discriminant. */
|
|
pn1 = Expr(cx, ts, tc);
|
|
if (!pn1)
|
|
return NULL;
|
|
|
|
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_SWITCH);
|
|
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_SWITCH);
|
|
|
|
/* pn2 is a list of case nodes. The default case has pn_left == NULL */
|
|
pn2 = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
PN_INIT_LIST(pn2);
|
|
|
|
js_PushStatement(tc, &stmtInfo, STMT_SWITCH, -1);
|
|
|
|
while ((tt = js_GetToken(cx, ts)) != TOK_RC) {
|
|
switch (tt) {
|
|
case TOK_DEFAULT:
|
|
if (seenDefault) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_TOO_MANY_DEFAULTS);
|
|
return NULL;
|
|
}
|
|
seenDefault = JS_TRUE;
|
|
/* fall through */
|
|
|
|
case TOK_CASE:
|
|
pn3 = NewParseNode(cx, ts, PN_BINARY, tc);
|
|
if (!pn3)
|
|
return NULL;
|
|
if (tt == TOK_DEFAULT) {
|
|
pn3->pn_left = NULL;
|
|
} else {
|
|
pn3->pn_left = Expr(cx, ts, tc);
|
|
if (!pn3->pn_left)
|
|
return NULL;
|
|
}
|
|
PN_APPEND(pn2, pn3);
|
|
if (pn2->pn_count == JS_BIT(16)) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_TOO_MANY_CASES);
|
|
return NULL;
|
|
}
|
|
break;
|
|
|
|
case TOK_ERROR:
|
|
return NULL;
|
|
|
|
default:
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_SWITCH);
|
|
return NULL;
|
|
}
|
|
MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_AFTER_CASE);
|
|
|
|
pn4 = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!pn4)
|
|
return NULL;
|
|
pn4->pn_type = TOK_LC;
|
|
PN_INIT_LIST(pn4);
|
|
ts->flags |= TSF_OPERAND;
|
|
while ((tt = js_PeekToken(cx, ts)) != TOK_RC &&
|
|
tt != TOK_CASE && tt != TOK_DEFAULT) {
|
|
ts->flags &= ~TSF_OPERAND;
|
|
if (tt == TOK_ERROR)
|
|
return NULL;
|
|
pn5 = Statement(cx, ts, tc);
|
|
if (!pn5)
|
|
return NULL;
|
|
pn4->pn_pos.end = pn5->pn_pos.end;
|
|
PN_APPEND(pn4, pn5);
|
|
ts->flags |= TSF_OPERAND;
|
|
}
|
|
ts->flags &= ~TSF_OPERAND;
|
|
|
|
/* Fix the PN_LIST so it doesn't begin at the TOK_COLON. */
|
|
if (pn4->pn_head)
|
|
pn4->pn_pos.begin = pn4->pn_head->pn_pos.begin;
|
|
pn3->pn_pos.end = pn4->pn_pos.end;
|
|
pn3->pn_right = pn4;
|
|
}
|
|
|
|
js_PopStatement(tc);
|
|
|
|
pn->pn_pos.end = pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
|
|
pn->pn_kid1 = pn1;
|
|
pn->pn_kid2 = pn2;
|
|
return pn;
|
|
}
|
|
#endif /* JS_HAS_SWITCH_STATEMENT */
|
|
|
|
case TOK_WHILE:
|
|
pn = NewParseNode(cx, ts, PN_BINARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
js_PushStatement(tc, &stmtInfo, STMT_WHILE_LOOP, -1);
|
|
pn2 = Condition(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn->pn_left = pn2;
|
|
pn2 = Statement(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
js_PopStatement(tc);
|
|
pn->pn_pos.end = pn2->pn_pos.end;
|
|
pn->pn_right = pn2;
|
|
return pn;
|
|
|
|
#if JS_HAS_DO_WHILE_LOOP
|
|
case TOK_DO:
|
|
pn = NewParseNode(cx, ts, PN_BINARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
js_PushStatement(tc, &stmtInfo, STMT_DO_LOOP, -1);
|
|
pn2 = Statement(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn->pn_left = pn2;
|
|
MUST_MATCH_TOKEN(TOK_WHILE, JSMSG_WHILE_AFTER_DO);
|
|
pn2 = Condition(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
js_PopStatement(tc);
|
|
pn->pn_pos.end = pn2->pn_pos.end;
|
|
pn->pn_right = pn2;
|
|
if ((cx->version & JSVERSION_MASK) != JSVERSION_ECMA_3) {
|
|
/*
|
|
* All legacy and extended versions must do automatic semicolon
|
|
* insertion after do-while. See the testcase and discussion in
|
|
* http://bugzilla.mozilla.org/show_bug.cgi?id=238945.
|
|
*/
|
|
(void) js_MatchToken(cx, ts, TOK_SEMI);
|
|
return pn;
|
|
}
|
|
break;
|
|
#endif /* JS_HAS_DO_WHILE_LOOP */
|
|
|
|
case TOK_FOR:
|
|
/* A FOR node is binary, left is loop control and right is the body. */
|
|
pn = NewParseNode(cx, ts, PN_BINARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
js_PushStatement(tc, &stmtInfo, STMT_FOR_LOOP, -1);
|
|
|
|
#if JS_HAS_XML_SUPPORT
|
|
pn->pn_op = JSOP_NOP;
|
|
if (js_MatchToken(cx, ts, TOK_NAME)) {
|
|
if (CURRENT_TOKEN(ts).t_atom == cx->runtime->atomState.eachAtom)
|
|
pn->pn_op = JSOP_FOREACH;
|
|
else
|
|
js_UngetToken(ts);
|
|
}
|
|
#endif
|
|
|
|
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR);
|
|
ts->flags |= TSF_OPERAND;
|
|
tt = js_PeekToken(cx, ts);
|
|
ts->flags &= ~TSF_OPERAND;
|
|
if (tt == TOK_SEMI) {
|
|
#if JS_HAS_XML_SUPPORT
|
|
if (pn->pn_op == JSOP_FOREACH)
|
|
goto bad_for_each;
|
|
#endif
|
|
|
|
/* No initializer -- set first kid of left sub-node to null. */
|
|
pn1 = NULL;
|
|
} else {
|
|
/* Set pn1 to a var list or an initializing expression. */
|
|
#if JS_HAS_IN_OPERATOR
|
|
/*
|
|
* Set the TCF_IN_FOR_INIT flag during parsing of the first clause
|
|
* of the for statement. This flag will be used by the RelExpr
|
|
* production; if it is set, then the 'in' keyword will not be
|
|
* recognized as an operator, leaving it available to be parsed as
|
|
* part of a for/in loop. A side effect of this restriction is
|
|
* that (unparenthesized) expressions involving an 'in' operator
|
|
* are illegal in the init clause of an ordinary for loop.
|
|
*/
|
|
tc->flags |= TCF_IN_FOR_INIT;
|
|
#endif /* JS_HAS_IN_OPERATOR */
|
|
if (tt == TOK_VAR) {
|
|
(void) js_GetToken(cx, ts);
|
|
pn1 = Variables(cx, ts, tc);
|
|
} else {
|
|
pn1 = Expr(cx, ts, tc);
|
|
}
|
|
#if JS_HAS_IN_OPERATOR
|
|
tc->flags &= ~TCF_IN_FOR_INIT;
|
|
#endif /* JS_HAS_IN_OPERATOR */
|
|
if (!pn1)
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* We can be sure that it's a for/in loop if there's still an 'in'
|
|
* keyword here, even if JavaScript recognizes 'in' as an operator,
|
|
* as we've excluded 'in' from being parsed in RelExpr by setting
|
|
* the TCF_IN_FOR_INIT flag in our JSTreeContext.
|
|
*/
|
|
if (pn1 && js_MatchToken(cx, ts, TOK_IN)) {
|
|
stmtInfo.type = STMT_FOR_IN_LOOP;
|
|
|
|
/* Check that the left side of the 'in' is valid. */
|
|
while (pn1->pn_type == TOK_RP)
|
|
pn1 = pn1->pn_kid;
|
|
if ((pn1->pn_type == TOK_VAR)
|
|
? (pn1->pn_count > 1 || pn1->pn_op == JSOP_DEFCONST)
|
|
: (pn1->pn_type != TOK_NAME &&
|
|
pn1->pn_type != TOK_DOT &&
|
|
#if JS_HAS_LVALUE_RETURN
|
|
pn1->pn_type != TOK_LP &&
|
|
#endif
|
|
#if JS_HAS_XML_SUPPORT
|
|
(pn1->pn_type != TOK_UNARYOP ||
|
|
pn1->pn_op != JSOP_XMLNAME) &&
|
|
#endif
|
|
pn1->pn_type != TOK_LB)) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_FOR_LEFTSIDE);
|
|
return NULL;
|
|
}
|
|
|
|
if (pn1->pn_type == TOK_VAR) {
|
|
/* Tell js_EmitTree(TOK_VAR) that pn1 is part of a for/in. */
|
|
pn1->pn_extra |= PNX_FORINVAR;
|
|
|
|
/* Generate a final POP only if the var has an initializer. */
|
|
pn2 = pn1->pn_head;
|
|
if (pn2->pn_expr)
|
|
pn1->pn_extra |= PNX_POPVAR;
|
|
} else {
|
|
pn2 = pn1;
|
|
#if JS_HAS_LVALUE_RETURN
|
|
if (pn2->pn_type == TOK_LP)
|
|
pn2->pn_op = JSOP_SETCALL;
|
|
#endif
|
|
#if JS_HAS_XML_SUPPORT
|
|
if (pn2->pn_type == TOK_UNARYOP)
|
|
pn2->pn_op = JSOP_BINDXMLNAME;
|
|
#endif
|
|
}
|
|
|
|
/* Beware 'for (arguments in ...)' with or without a 'var'. */
|
|
if (pn2->pn_type == TOK_NAME &&
|
|
pn2->pn_atom == cx->runtime->atomState.argumentsAtom) {
|
|
tc->flags |= TCF_FUN_HEAVYWEIGHT;
|
|
}
|
|
|
|
/* Parse the object expression as the right operand of 'in'. */
|
|
pn2 = NewBinary(cx, TOK_IN, JSOP_NOP, pn1, Expr(cx, ts, tc), tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn->pn_left = pn2;
|
|
} else {
|
|
#if JS_HAS_XML_SUPPORT
|
|
if (pn->pn_op == JSOP_FOREACH)
|
|
goto bad_for_each;
|
|
#endif
|
|
|
|
/* Parse the loop condition or null into pn2. */
|
|
MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_INIT);
|
|
ts->flags |= TSF_OPERAND;
|
|
tt = js_PeekToken(cx, ts);
|
|
ts->flags &= ~TSF_OPERAND;
|
|
if (tt == TOK_SEMI) {
|
|
pn2 = NULL;
|
|
} else {
|
|
pn2 = Expr(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
}
|
|
|
|
/* Parse the update expression or null into pn3. */
|
|
MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_COND);
|
|
ts->flags |= TSF_OPERAND;
|
|
tt = js_PeekToken(cx, ts);
|
|
ts->flags &= ~TSF_OPERAND;
|
|
if (tt == TOK_RP) {
|
|
pn3 = NULL;
|
|
} else {
|
|
pn3 = Expr(cx, ts, tc);
|
|
if (!pn3)
|
|
return NULL;
|
|
}
|
|
|
|
/* Build the RESERVED node to use as the left kid of pn. */
|
|
pn4 = NewParseNode(cx, ts, PN_TERNARY, tc);
|
|
if (!pn4)
|
|
return NULL;
|
|
pn4->pn_type = TOK_RESERVED;
|
|
pn4->pn_op = JSOP_NOP;
|
|
pn4->pn_kid1 = pn1;
|
|
pn4->pn_kid2 = pn2;
|
|
pn4->pn_kid3 = pn3;
|
|
pn->pn_left = pn4;
|
|
}
|
|
|
|
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL);
|
|
|
|
/* Parse the loop body into pn->pn_right. */
|
|
pn2 = Statement(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn->pn_right = pn2;
|
|
js_PopStatement(tc);
|
|
|
|
/* Record the absolute line number for source note emission. */
|
|
pn->pn_pos.end = pn2->pn_pos.end;
|
|
return pn;
|
|
|
|
#if JS_HAS_XML_SUPPORT
|
|
bad_for_each:
|
|
js_ReportCompileErrorNumber(cx, pn,
|
|
JSREPORT_PN | JSREPORT_ERROR,
|
|
JSMSG_BAD_FOR_EACH_LOOP);
|
|
return NULL;
|
|
#endif
|
|
|
|
#if JS_HAS_EXCEPTIONS
|
|
case TOK_TRY: {
|
|
JSParseNode *catchtail = NULL;
|
|
/*
|
|
* try nodes are ternary.
|
|
* kid1 is the try Statement
|
|
* kid2 is the catch node
|
|
* kid3 is the finally Statement
|
|
*
|
|
* catch nodes are ternary.
|
|
* kid1 is the discriminant
|
|
* kid2 is the next catch node, or NULL
|
|
* kid3 is the catch block (on kid3 so that we can always append a
|
|
* new catch pn on catchtail->kid2)
|
|
*
|
|
* catch discriminant nodes are binary
|
|
* atom is the receptacle
|
|
* expr is the discriminant code
|
|
*
|
|
* finally nodes are unary (just the finally expression)
|
|
*/
|
|
pn = NewParseNode(cx, ts, PN_TERNARY, tc);
|
|
pn->pn_op = JSOP_NOP;
|
|
|
|
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_TRY);
|
|
js_PushStatement(tc, &stmtInfo, STMT_TRY, -1);
|
|
pn->pn_kid1 = Statements(cx, ts, tc);
|
|
if (!pn->pn_kid1)
|
|
return NULL;
|
|
MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_TRY);
|
|
js_PopStatement(tc);
|
|
|
|
catchtail = pn;
|
|
while (js_PeekToken(cx, ts) == TOK_CATCH) {
|
|
/* check for another catch after unconditional catch */
|
|
if (catchtail != pn && !catchtail->pn_kid1->pn_expr) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_CATCH_AFTER_GENERAL);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* legal catch forms are:
|
|
* catch (v)
|
|
* catch (v if <boolean_expression>)
|
|
* (the latter is legal only #ifdef JS_HAS_CATCH_GUARD)
|
|
*/
|
|
(void) js_GetToken(cx, ts); /* eat `catch' */
|
|
pn2 = NewParseNode(cx, ts, PN_TERNARY, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
|
|
/*
|
|
* We use a PN_NAME for the discriminant (catchguard) node
|
|
* with the actual discriminant code in the initializer spot
|
|
*/
|
|
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH);
|
|
MUST_MATCH_TOKEN(TOK_NAME, JSMSG_CATCH_IDENTIFIER);
|
|
pn3 = NewParseNode(cx, ts, PN_NAME, tc);
|
|
if (!pn3)
|
|
return NULL;
|
|
|
|
pn3->pn_atom = CURRENT_TOKEN(ts).t_atom;
|
|
pn3->pn_expr = NULL;
|
|
#if JS_HAS_CATCH_GUARD
|
|
/*
|
|
* We use `catch (x if x === 5)' (not `catch (x : x === 5)') to
|
|
* avoid conflicting with the JS2/ECMA2 proposed catchguard syntax.
|
|
*/
|
|
if (js_PeekToken(cx, ts) == TOK_IF) {
|
|
(void)js_GetToken(cx, ts); /* eat `if' */
|
|
pn3->pn_expr = Expr(cx, ts, tc);
|
|
if (!pn3->pn_expr)
|
|
return NULL;
|
|
}
|
|
#endif
|
|
pn2->pn_kid1 = pn3;
|
|
|
|
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_CATCH);
|
|
|
|
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_CATCH);
|
|
js_PushStatement(tc, &stmtInfo, STMT_CATCH, -1);
|
|
stmtInfo.label = pn3->pn_atom;
|
|
pn2->pn_kid3 = Statements(cx, ts, tc);
|
|
if (!pn2->pn_kid3)
|
|
return NULL;
|
|
MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_CATCH);
|
|
js_PopStatement(tc);
|
|
|
|
catchtail = catchtail->pn_kid2 = pn2;
|
|
}
|
|
catchtail->pn_kid2 = NULL;
|
|
|
|
if (js_MatchToken(cx, ts, TOK_FINALLY)) {
|
|
tc->tryCount++;
|
|
MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_FINALLY);
|
|
js_PushStatement(tc, &stmtInfo, STMT_FINALLY, -1);
|
|
pn->pn_kid3 = Statements(cx, ts, tc);
|
|
if (!pn->pn_kid3)
|
|
return NULL;
|
|
MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_FINALLY);
|
|
js_PopStatement(tc);
|
|
} else {
|
|
pn->pn_kid3 = NULL;
|
|
}
|
|
if (!pn->pn_kid2 && !pn->pn_kid3) {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_CATCH_OR_FINALLY);
|
|
return NULL;
|
|
}
|
|
tc->tryCount++;
|
|
return pn;
|
|
}
|
|
|
|
case TOK_THROW:
|
|
pn = NewParseNode(cx, ts, PN_UNARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
|
|
/* ECMA-262 Edition 3 says 'throw [no LineTerminator here] Expr'. */
|
|
ts->flags |= TSF_OPERAND;
|
|
tt = js_PeekTokenSameLine(cx, ts);
|
|
ts->flags &= ~TSF_OPERAND;
|
|
if (tt == TOK_ERROR)
|
|
return NULL;
|
|
if (tt == TOK_EOF || tt == TOK_EOL || tt == TOK_SEMI || tt == TOK_RC) {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_SYNTAX_ERROR);
|
|
return NULL;
|
|
}
|
|
|
|
pn2 = Expr(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn->pn_pos.end = pn2->pn_pos.end;
|
|
pn->pn_op = JSOP_THROW;
|
|
pn->pn_kid = pn2;
|
|
break;
|
|
|
|
/* TOK_CATCH and TOK_FINALLY are both handled in the TOK_TRY case */
|
|
case TOK_CATCH:
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_CATCH_WITHOUT_TRY);
|
|
return NULL;
|
|
|
|
case TOK_FINALLY:
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_FINALLY_WITHOUT_TRY);
|
|
return NULL;
|
|
|
|
#endif /* JS_HAS_EXCEPTIONS */
|
|
|
|
case TOK_BREAK:
|
|
pn = NewParseNode(cx, ts, PN_NULLARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
if (!MatchLabel(cx, ts, pn))
|
|
return NULL;
|
|
stmt = tc->topStmt;
|
|
label = pn->pn_atom;
|
|
if (label) {
|
|
for (; ; stmt = stmt->down) {
|
|
if (!stmt) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_LABEL_NOT_FOUND);
|
|
return NULL;
|
|
}
|
|
if (stmt->type == STMT_LABEL && stmt->label == label)
|
|
break;
|
|
}
|
|
} else {
|
|
for (; ; stmt = stmt->down) {
|
|
if (!stmt) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_TOUGH_BREAK);
|
|
return NULL;
|
|
}
|
|
if (STMT_IS_LOOP(stmt) || stmt->type == STMT_SWITCH)
|
|
break;
|
|
}
|
|
}
|
|
if (label)
|
|
pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
|
|
break;
|
|
|
|
case TOK_CONTINUE:
|
|
pn = NewParseNode(cx, ts, PN_NULLARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
if (!MatchLabel(cx, ts, pn))
|
|
return NULL;
|
|
stmt = tc->topStmt;
|
|
label = pn->pn_atom;
|
|
if (label) {
|
|
for (stmt2 = NULL; ; stmt = stmt->down) {
|
|
if (!stmt) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_LABEL_NOT_FOUND);
|
|
return NULL;
|
|
}
|
|
if (stmt->type == STMT_LABEL) {
|
|
if (stmt->label == label) {
|
|
if (!stmt2 || !STMT_IS_LOOP(stmt2)) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS |
|
|
JSREPORT_ERROR,
|
|
JSMSG_BAD_CONTINUE);
|
|
return NULL;
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
stmt2 = stmt;
|
|
}
|
|
}
|
|
} else {
|
|
for (; ; stmt = stmt->down) {
|
|
if (!stmt) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_CONTINUE);
|
|
return NULL;
|
|
}
|
|
if (STMT_IS_LOOP(stmt))
|
|
break;
|
|
}
|
|
}
|
|
if (label)
|
|
pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
|
|
break;
|
|
|
|
case TOK_WITH:
|
|
if (!js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS |
|
|
JSREPORT_WARNING |
|
|
JSREPORT_STRICT,
|
|
JSMSG_DEPRECATED_USAGE,
|
|
js_with_statement_str)) {
|
|
return NULL;
|
|
}
|
|
|
|
pn = NewParseNode(cx, ts, PN_BINARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH);
|
|
pn2 = Expr(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_WITH);
|
|
pn->pn_left = pn2;
|
|
|
|
js_PushStatement(tc, &stmtInfo, STMT_WITH, -1);
|
|
pn2 = Statement(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
js_PopStatement(tc);
|
|
|
|
pn->pn_pos.end = pn2->pn_pos.end;
|
|
pn->pn_right = pn2;
|
|
tc->flags |= TCF_FUN_HEAVYWEIGHT;
|
|
return pn;
|
|
|
|
case TOK_VAR:
|
|
pn = Variables(cx, ts, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
|
|
/* Tell js_EmitTree to generate a final POP. */
|
|
pn->pn_extra |= PNX_POPVAR;
|
|
break;
|
|
|
|
case TOK_RETURN:
|
|
if (!(tc->flags & TCF_IN_FUNCTION)) {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_RETURN);
|
|
return NULL;
|
|
}
|
|
pn = NewParseNode(cx, ts, PN_UNARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
|
|
/* This is ugly, but we don't want to require a semicolon. */
|
|
ts->flags |= TSF_OPERAND;
|
|
tt = js_PeekTokenSameLine(cx, ts);
|
|
ts->flags &= ~TSF_OPERAND;
|
|
if (tt == TOK_ERROR)
|
|
return NULL;
|
|
|
|
if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) {
|
|
pn2 = Expr(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
tc->flags |= TCF_RETURN_EXPR;
|
|
pn->pn_pos.end = pn2->pn_pos.end;
|
|
pn->pn_kid = pn2;
|
|
} else {
|
|
tc->flags |= TCF_RETURN_VOID;
|
|
pn->pn_kid = NULL;
|
|
}
|
|
|
|
if (JS_HAS_STRICT_OPTION(cx) &&
|
|
(~tc->flags & (TCF_RETURN_EXPR | TCF_RETURN_VOID)) == 0) {
|
|
/*
|
|
* We must be in a frame with a non-native function, because
|
|
* we're compiling one.
|
|
*/
|
|
if (!ReportNoReturnValue(cx, ts))
|
|
return NULL;
|
|
}
|
|
break;
|
|
|
|
case TOK_LC:
|
|
js_PushStatement(tc, &stmtInfo, STMT_BLOCK, -1);
|
|
pn = Statements(cx, ts, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
|
|
MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_IN_COMPOUND);
|
|
js_PopStatement(tc);
|
|
return pn;
|
|
|
|
case TOK_EOL:
|
|
case TOK_SEMI:
|
|
pn = NewParseNode(cx, ts, PN_UNARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn->pn_type = TOK_SEMI;
|
|
pn->pn_kid = NULL;
|
|
return pn;
|
|
|
|
#if JS_HAS_DEBUGGER_KEYWORD
|
|
case TOK_DEBUGGER:
|
|
pn = NewParseNode(cx, ts, PN_NULLARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn->pn_type = TOK_DEBUGGER;
|
|
tc->flags |= TCF_FUN_HEAVYWEIGHT;
|
|
break;
|
|
#endif /* JS_HAS_DEBUGGER_KEYWORD */
|
|
|
|
#if JS_HAS_XML_SUPPORT
|
|
case TOK_DEFAULT:
|
|
pn = NewParseNode(cx, ts, PN_UNARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
if (!js_MatchToken(cx, ts, TOK_NAME) ||
|
|
CURRENT_TOKEN(ts).t_atom != cx->runtime->atomState.xmlAtom ||
|
|
!js_MatchToken(cx, ts, TOK_NAME) ||
|
|
CURRENT_TOKEN(ts).t_atom != cx->runtime->atomState.namespaceAtom ||
|
|
!js_MatchToken(cx, ts, TOK_ASSIGN) ||
|
|
CURRENT_TOKEN(ts).t_op != JSOP_NOP) {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_DEFAULT_XML_NAMESPACE);
|
|
return NULL;
|
|
}
|
|
pn2 = Expr(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn->pn_op = JSOP_DEFXMLNS;
|
|
pn->pn_pos.end = pn2->pn_pos.end;
|
|
pn->pn_kid = pn2;
|
|
tc->flags |= TCF_HAS_DEFXMLNS;
|
|
break;
|
|
#endif
|
|
|
|
case TOK_ERROR:
|
|
return NULL;
|
|
|
|
default:
|
|
#if JS_HAS_XML_SUPPORT
|
|
expression:
|
|
#endif
|
|
js_UngetToken(ts);
|
|
pn2 = Expr(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
|
|
if (js_PeekToken(cx, ts) == TOK_COLON) {
|
|
if (pn2->pn_type != TOK_NAME) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_LABEL);
|
|
return NULL;
|
|
}
|
|
label = pn2->pn_atom;
|
|
for (stmt = tc->topStmt; stmt; stmt = stmt->down) {
|
|
if (stmt->type == STMT_LABEL && stmt->label == label) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_DUPLICATE_LABEL);
|
|
return NULL;
|
|
}
|
|
}
|
|
(void) js_GetToken(cx, ts);
|
|
|
|
/* Push a label struct and parse the statement. */
|
|
js_PushStatement(tc, &stmtInfo, STMT_LABEL, -1);
|
|
stmtInfo.label = label;
|
|
pn = Statement(cx, ts, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
|
|
/* Pop the label, set pn_expr, and return early. */
|
|
js_PopStatement(tc);
|
|
pn2->pn_type = TOK_COLON;
|
|
pn2->pn_pos.end = pn->pn_pos.end;
|
|
pn2->pn_expr = pn;
|
|
return pn2;
|
|
}
|
|
|
|
pn = NewParseNode(cx, ts, PN_UNARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn->pn_type = TOK_SEMI;
|
|
pn->pn_pos = pn2->pn_pos;
|
|
pn->pn_kid = pn2;
|
|
break;
|
|
}
|
|
|
|
/* Check termination of this primitive statement. */
|
|
if (ON_CURRENT_LINE(ts, pn->pn_pos)) {
|
|
tt = js_PeekTokenSameLine(cx, ts);
|
|
if (tt == TOK_ERROR)
|
|
return NULL;
|
|
if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_SEMI_BEFORE_STMNT);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
(void) js_MatchToken(cx, ts, TOK_SEMI);
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
Variables(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn, *pn2;
|
|
JSObject *obj, *pobj;
|
|
JSStackFrame *fp;
|
|
JSFunction *fun;
|
|
JSClass *clasp;
|
|
JSPropertyOp getter, setter, currentGetter, currentSetter;
|
|
JSAtom *atom;
|
|
JSAtomListElement *ale;
|
|
JSOp prevop;
|
|
JSProperty *prop;
|
|
JSScopeProperty *sprop;
|
|
JSBool ok;
|
|
|
|
/*
|
|
* The tricky part of this code is to create special parsenode opcodes for
|
|
* getting and setting variables (which will be stored as special slots in
|
|
* the frame). The complex special case is an eval() inside a function.
|
|
* If the evaluated string references variables in the enclosing function,
|
|
* then we need to generate the special variable opcodes. We determine
|
|
* this by looking up the variable id in the current variable scope.
|
|
*/
|
|
JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_VAR);
|
|
pn = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn->pn_op = CURRENT_TOKEN(ts).t_op;
|
|
PN_INIT_LIST(pn);
|
|
|
|
/*
|
|
* Skip eval and debugger frames when looking for the function whose code
|
|
* is being compiled. If we are called from FunctionBody, TCF_IN_FUNCTION
|
|
* will be set in tc->flags, and we can be sure fp->fun is the function to
|
|
* use. But if a function calls eval, the string argument is treated as a
|
|
* Program (per ECMA), so TCF_IN_FUNCTION won't be set.
|
|
*
|
|
* What's more, when the following code is reached from eval, cx->fp->fun
|
|
* is eval's JSFunction (a native function), so we need to skip its frame.
|
|
* We should find the scripted caller's function frame just below it, but
|
|
* we code a loop out of paranoia.
|
|
*/
|
|
for (fp = cx->fp; (fp->flags & JSFRAME_SPECIAL) && fp->down; fp = fp->down)
|
|
continue;
|
|
obj = fp->varobj;
|
|
fun = fp->fun;
|
|
clasp = OBJ_GET_CLASS(cx, obj);
|
|
if (fun && clasp == &js_FunctionClass) {
|
|
/* We are compiling code inside a function */
|
|
getter = js_GetLocalVariable;
|
|
setter = js_SetLocalVariable;
|
|
} else if (fun && clasp == &js_CallClass) {
|
|
/* We are compiling code from an eval inside a function */
|
|
getter = js_GetCallVariable;
|
|
setter = js_SetCallVariable;
|
|
} else {
|
|
getter = clasp->getProperty;
|
|
setter = clasp->setProperty;
|
|
}
|
|
|
|
ok = JS_TRUE;
|
|
do {
|
|
currentGetter = getter;
|
|
currentSetter = setter;
|
|
MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_VARIABLE_NAME);
|
|
atom = CURRENT_TOKEN(ts).t_atom;
|
|
|
|
ATOM_LIST_SEARCH(ale, &tc->decls, atom);
|
|
if (ale) {
|
|
prevop = ALE_JSOP(ale);
|
|
if (JS_HAS_STRICT_OPTION(cx)
|
|
? pn->pn_op != JSOP_DEFVAR || prevop != JSOP_DEFVAR
|
|
: pn->pn_op == JSOP_DEFCONST || prevop == JSOP_DEFCONST) {
|
|
const char *name = js_AtomToPrintableString(cx, atom);
|
|
if (!name ||
|
|
!js_ReportCompileErrorNumber(cx, ts,
|
|
(pn->pn_op != JSOP_DEFCONST &&
|
|
prevop != JSOP_DEFCONST)
|
|
? JSREPORT_TS |
|
|
JSREPORT_WARNING |
|
|
JSREPORT_STRICT
|
|
: JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_REDECLARED_VAR,
|
|
(prevop == JSOP_DEFFUN ||
|
|
prevop == JSOP_CLOSURE)
|
|
? js_function_str
|
|
: (prevop == JSOP_DEFCONST)
|
|
? js_const_str
|
|
: js_var_str,
|
|
name)) {
|
|
return NULL;
|
|
}
|
|
}
|
|
if (pn->pn_op == JSOP_DEFVAR && prevop == JSOP_CLOSURE)
|
|
tc->flags |= TCF_FUN_CLOSURE_VS_VAR;
|
|
} else {
|
|
ale = js_IndexAtom(cx, atom, &tc->decls);
|
|
if (!ale)
|
|
return NULL;
|
|
}
|
|
ALE_SET_JSOP(ale, pn->pn_op);
|
|
|
|
pn2 = NewParseNode(cx, ts, PN_NAME, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn2->pn_op = JSOP_NAME;
|
|
pn2->pn_atom = atom;
|
|
pn2->pn_expr = NULL;
|
|
pn2->pn_slot = -1;
|
|
pn2->pn_attrs = (pn->pn_op == JSOP_DEFCONST)
|
|
? JSPROP_PERMANENT | JSPROP_READONLY
|
|
: JSPROP_PERMANENT;
|
|
PN_APPEND(pn, pn2);
|
|
|
|
if (!fun) {
|
|
/* Don't lookup global variables at compile time. */
|
|
prop = NULL;
|
|
} else if (OBJ_IS_NATIVE(obj)) {
|
|
if (!js_LookupHiddenProperty(cx, obj, ATOM_TO_JSID(atom),
|
|
&pobj, &prop)) {
|
|
return NULL;
|
|
}
|
|
} else {
|
|
if (!OBJ_LOOKUP_PROPERTY(cx, obj, ATOM_TO_JSID(atom), &pobj, &prop))
|
|
return NULL;
|
|
}
|
|
if (prop && pobj == obj && OBJ_IS_NATIVE(pobj)) {
|
|
sprop = (JSScopeProperty *)prop;
|
|
if (sprop->getter == js_GetArgument) {
|
|
const char *name = js_AtomToPrintableString(cx, atom);
|
|
if (!name) {
|
|
ok = JS_FALSE;
|
|
} else if (pn->pn_op == JSOP_DEFCONST) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_REDECLARED_PARAM,
|
|
name);
|
|
ok = JS_FALSE;
|
|
} else {
|
|
currentGetter = js_GetArgument;
|
|
currentSetter = js_SetArgument;
|
|
ok = js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS |
|
|
JSREPORT_WARNING |
|
|
JSREPORT_STRICT,
|
|
JSMSG_VAR_HIDES_ARG,
|
|
name);
|
|
}
|
|
} else {
|
|
if (fun) {
|
|
/* Not an argument, must be a redeclared local var. */
|
|
if (clasp == &js_FunctionClass) {
|
|
JS_ASSERT(sprop->getter == js_GetLocalVariable);
|
|
JS_ASSERT((sprop->flags & SPROP_HAS_SHORTID) &&
|
|
(uint16) sprop->shortid < fun->nvars);
|
|
} else if (clasp == &js_CallClass) {
|
|
if (sprop->getter == js_GetCallVariable) {
|
|
/*
|
|
* Referencing a variable introduced by a var
|
|
* statement in the enclosing function. Check
|
|
* that the slot number we have is in range.
|
|
*/
|
|
JS_ASSERT((sprop->flags & SPROP_HAS_SHORTID) &&
|
|
(uint16) sprop->shortid < fun->nvars);
|
|
} else {
|
|
/*
|
|
* A variable introduced through another eval:
|
|
* don't use the special getters and setters
|
|
* since we can't allocate a slot in the frame.
|
|
*/
|
|
currentGetter = sprop->getter;
|
|
currentSetter = sprop->setter;
|
|
}
|
|
}
|
|
|
|
/* Override the old getter and setter, to handle eval. */
|
|
sprop = js_ChangeNativePropertyAttrs(cx, obj, sprop,
|
|
0, sprop->attrs,
|
|
currentGetter,
|
|
currentSetter);
|
|
if (!sprop)
|
|
ok = JS_FALSE;
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* Property not found in current variable scope: we have not seen
|
|
* this variable before. Define a new local variable by adding a
|
|
* property to the function's scope, allocating one slot in the
|
|
* function's frame. Global variables and any locals declared in
|
|
* with statement bodies are handled at runtime, by script prolog
|
|
* JSOP_DEFVAR bytecodes generated for slot-less vars.
|
|
*/
|
|
sprop = NULL;
|
|
if (prop) {
|
|
OBJ_DROP_PROPERTY(cx, pobj, prop);
|
|
prop = NULL;
|
|
}
|
|
if (currentGetter == js_GetCallVariable) {
|
|
/* Can't increase fun->nvars in an active frame! */
|
|
currentGetter = clasp->getProperty;
|
|
currentSetter = clasp->setProperty;
|
|
}
|
|
if (currentGetter == js_GetLocalVariable &&
|
|
atom != cx->runtime->atomState.argumentsAtom &&
|
|
fp->scopeChain == obj &&
|
|
!js_InWithStatement(tc)) {
|
|
if (!js_AddHiddenProperty(cx, obj, ATOM_TO_JSID(atom),
|
|
currentGetter, currentSetter,
|
|
SPROP_INVALID_SLOT,
|
|
pn2->pn_attrs | JSPROP_SHARED,
|
|
SPROP_HAS_SHORTID, fun->nvars)) {
|
|
return NULL;
|
|
}
|
|
if (fun->nvars == JS_BITMASK(16)) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_TOO_MANY_FUN_VARS);
|
|
return NULL;
|
|
}
|
|
fun->nvars++;
|
|
}
|
|
}
|
|
|
|
if (js_MatchToken(cx, ts, TOK_ASSIGN)) {
|
|
if (CURRENT_TOKEN(ts).t_op != JSOP_NOP) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_VAR_INIT);
|
|
ok = JS_FALSE;
|
|
} else {
|
|
pn2->pn_expr = AssignExpr(cx, ts, tc);
|
|
if (!pn2->pn_expr) {
|
|
ok = JS_FALSE;
|
|
} else {
|
|
pn2->pn_op = (pn->pn_op == JSOP_DEFCONST)
|
|
? JSOP_SETCONST
|
|
: JSOP_SETNAME;
|
|
if (atom == cx->runtime->atomState.argumentsAtom)
|
|
tc->flags |= TCF_FUN_HEAVYWEIGHT;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (prop)
|
|
OBJ_DROP_PROPERTY(cx, pobj, prop);
|
|
if (!ok)
|
|
return NULL;
|
|
} while (js_MatchToken(cx, ts, TOK_COMMA));
|
|
|
|
pn->pn_pos.end = PN_LAST(pn)->pn_pos.end;
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
Expr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn, *pn2;
|
|
|
|
pn = AssignExpr(cx, ts, tc);
|
|
if (pn && js_MatchToken(cx, ts, TOK_COMMA)) {
|
|
pn2 = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn2->pn_pos.begin = pn->pn_pos.begin;
|
|
PN_INIT_LIST_1(pn2, pn);
|
|
pn = pn2;
|
|
do {
|
|
pn2 = AssignExpr(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
PN_APPEND(pn, pn2);
|
|
} while (js_MatchToken(cx, ts, TOK_COMMA));
|
|
pn->pn_pos.end = PN_LAST(pn)->pn_pos.end;
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
AssignExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn, *pn2;
|
|
JSTokenType tt;
|
|
JSOp op;
|
|
|
|
CHECK_RECURSION();
|
|
|
|
pn = CondExpr(cx, ts, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
|
|
tt = js_GetToken(cx, ts);
|
|
#if JS_HAS_GETTER_SETTER
|
|
if (tt == TOK_NAME) {
|
|
tt = CheckGetterOrSetter(cx, ts, TOK_ASSIGN);
|
|
if (tt == TOK_ERROR)
|
|
return NULL;
|
|
}
|
|
#endif
|
|
if (tt != TOK_ASSIGN) {
|
|
js_UngetToken(ts);
|
|
return pn;
|
|
}
|
|
|
|
op = CURRENT_TOKEN(ts).t_op;
|
|
for (pn2 = pn; pn2->pn_type == TOK_RP; pn2 = pn2->pn_kid)
|
|
continue;
|
|
switch (pn2->pn_type) {
|
|
case TOK_NAME:
|
|
pn2->pn_op = JSOP_SETNAME;
|
|
if (pn2->pn_atom == cx->runtime->atomState.argumentsAtom)
|
|
tc->flags |= TCF_FUN_HEAVYWEIGHT;
|
|
break;
|
|
case TOK_DOT:
|
|
pn2->pn_op = (pn2->pn_op == JSOP_GETMETHOD)
|
|
? JSOP_SETMETHOD
|
|
: JSOP_SETPROP;
|
|
break;
|
|
case TOK_LB:
|
|
pn2->pn_op = JSOP_SETELEM;
|
|
break;
|
|
#if JS_HAS_LVALUE_RETURN
|
|
case TOK_LP:
|
|
pn2->pn_op = JSOP_SETCALL;
|
|
break;
|
|
#endif
|
|
#if JS_HAS_XML_SUPPORT
|
|
case TOK_UNARYOP:
|
|
if (pn2->pn_op == JSOP_XMLNAME) {
|
|
pn2->pn_op = JSOP_SETXMLNAME;
|
|
break;
|
|
}
|
|
/* FALL THROUGH */
|
|
#endif
|
|
default:
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_LEFTSIDE_OF_ASS);
|
|
return NULL;
|
|
}
|
|
pn = NewBinary(cx, TOK_ASSIGN, op, pn2, AssignExpr(cx, ts, tc), tc);
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
CondExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn, *pn1, *pn2, *pn3;
|
|
#if JS_HAS_IN_OPERATOR
|
|
uintN oldflags;
|
|
#endif /* JS_HAS_IN_OPERATOR */
|
|
|
|
pn = OrExpr(cx, ts, tc);
|
|
if (pn && js_MatchToken(cx, ts, TOK_HOOK)) {
|
|
pn1 = pn;
|
|
pn = NewParseNode(cx, ts, PN_TERNARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
#if JS_HAS_IN_OPERATOR
|
|
/*
|
|
* Always accept the 'in' operator in the middle clause of a ternary,
|
|
* where it's unambiguous, even if we might be parsing the init of a
|
|
* for statement.
|
|
*/
|
|
oldflags = tc->flags;
|
|
tc->flags &= ~TCF_IN_FOR_INIT;
|
|
#endif /* JS_HAS_IN_OPERATOR */
|
|
pn2 = AssignExpr(cx, ts, tc);
|
|
#if JS_HAS_IN_OPERATOR
|
|
tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS);
|
|
#endif /* JS_HAS_IN_OPERATOR */
|
|
|
|
if (!pn2)
|
|
return NULL;
|
|
MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_IN_COND);
|
|
pn3 = AssignExpr(cx, ts, tc);
|
|
if (!pn3)
|
|
return NULL;
|
|
pn->pn_pos.begin = pn1->pn_pos.begin;
|
|
pn->pn_pos.end = pn3->pn_pos.end;
|
|
pn->pn_kid1 = pn1;
|
|
pn->pn_kid2 = pn2;
|
|
pn->pn_kid3 = pn3;
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
OrExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
|
|
pn = AndExpr(cx, ts, tc);
|
|
if (pn && js_MatchToken(cx, ts, TOK_OR))
|
|
pn = NewBinary(cx, TOK_OR, JSOP_OR, pn, OrExpr(cx, ts, tc), tc);
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
AndExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
|
|
pn = BitOrExpr(cx, ts, tc);
|
|
if (pn && js_MatchToken(cx, ts, TOK_AND))
|
|
pn = NewBinary(cx, TOK_AND, JSOP_AND, pn, AndExpr(cx, ts, tc), tc);
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
BitOrExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
|
|
pn = BitXorExpr(cx, ts, tc);
|
|
while (pn && js_MatchToken(cx, ts, TOK_BITOR)) {
|
|
pn = NewBinary(cx, TOK_BITOR, JSOP_BITOR, pn, BitXorExpr(cx, ts, tc),
|
|
tc);
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
BitXorExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
|
|
pn = BitAndExpr(cx, ts, tc);
|
|
while (pn && js_MatchToken(cx, ts, TOK_BITXOR)) {
|
|
pn = NewBinary(cx, TOK_BITXOR, JSOP_BITXOR, pn, BitAndExpr(cx, ts, tc),
|
|
tc);
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
BitAndExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
|
|
pn = EqExpr(cx, ts, tc);
|
|
while (pn && js_MatchToken(cx, ts, TOK_BITAND))
|
|
pn = NewBinary(cx, TOK_BITAND, JSOP_BITAND, pn, EqExpr(cx, ts, tc), tc);
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
EqExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
JSOp op;
|
|
|
|
pn = RelExpr(cx, ts, tc);
|
|
while (pn && js_MatchToken(cx, ts, TOK_EQOP)) {
|
|
op = CURRENT_TOKEN(ts).t_op;
|
|
pn = NewBinary(cx, TOK_EQOP, op, pn, RelExpr(cx, ts, tc), tc);
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
RelExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
JSTokenType tt;
|
|
JSOp op;
|
|
#if JS_HAS_IN_OPERATOR
|
|
uintN inForInitFlag = tc->flags & TCF_IN_FOR_INIT;
|
|
|
|
/*
|
|
* Uses of the in operator in ShiftExprs are always unambiguous,
|
|
* so unset the flag that prohibits recognizing it.
|
|
*/
|
|
tc->flags &= ~TCF_IN_FOR_INIT;
|
|
#endif /* JS_HAS_IN_OPERATOR */
|
|
|
|
pn = ShiftExpr(cx, ts, tc);
|
|
while (pn &&
|
|
(js_MatchToken(cx, ts, TOK_RELOP)
|
|
#if JS_HAS_IN_OPERATOR
|
|
/*
|
|
* Recognize the 'in' token as an operator only if we're not
|
|
* currently in the init expr of a for loop.
|
|
*/
|
|
|| (inForInitFlag == 0 && js_MatchToken(cx, ts, TOK_IN))
|
|
#endif /* JS_HAS_IN_OPERATOR */
|
|
#if JS_HAS_INSTANCEOF
|
|
|| js_MatchToken(cx, ts, TOK_INSTANCEOF)
|
|
#endif /* JS_HAS_INSTANCEOF */
|
|
)) {
|
|
tt = CURRENT_TOKEN(ts).type;
|
|
op = CURRENT_TOKEN(ts).t_op;
|
|
pn = NewBinary(cx, tt, op, pn, ShiftExpr(cx, ts, tc), tc);
|
|
}
|
|
#if JS_HAS_IN_OPERATOR
|
|
/* Restore previous state of inForInit flag. */
|
|
tc->flags |= inForInitFlag;
|
|
#endif /* JS_HAS_IN_OPERATOR */
|
|
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
ShiftExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
JSOp op;
|
|
|
|
pn = AddExpr(cx, ts, tc);
|
|
while (pn && js_MatchToken(cx, ts, TOK_SHOP)) {
|
|
op = CURRENT_TOKEN(ts).t_op;
|
|
pn = NewBinary(cx, TOK_SHOP, op, pn, AddExpr(cx, ts, tc), tc);
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
AddExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
JSTokenType tt;
|
|
JSOp op;
|
|
|
|
pn = MulExpr(cx, ts, tc);
|
|
while (pn &&
|
|
(js_MatchToken(cx, ts, TOK_PLUS) ||
|
|
js_MatchToken(cx, ts, TOK_MINUS))) {
|
|
tt = CURRENT_TOKEN(ts).type;
|
|
op = (tt == TOK_PLUS) ? JSOP_ADD : JSOP_SUB;
|
|
pn = NewBinary(cx, tt, op, pn, MulExpr(cx, ts, tc), tc);
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
MulExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
JSTokenType tt;
|
|
JSOp op;
|
|
|
|
pn = UnaryExpr(cx, ts, tc);
|
|
while (pn &&
|
|
(js_MatchToken(cx, ts, TOK_STAR) ||
|
|
js_MatchToken(cx, ts, TOK_DIVOP))) {
|
|
tt = CURRENT_TOKEN(ts).type;
|
|
op = CURRENT_TOKEN(ts).t_op;
|
|
pn = NewBinary(cx, tt, op, pn, UnaryExpr(cx, ts, tc), tc);
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
SetLvalKid(JSContext *cx, JSTokenStream *ts, JSParseNode *pn, JSParseNode *kid,
|
|
const char *name)
|
|
{
|
|
while (kid->pn_type == TOK_RP)
|
|
kid = kid->pn_kid;
|
|
if (kid->pn_type != TOK_NAME &&
|
|
kid->pn_type != TOK_DOT &&
|
|
#if JS_HAS_LVALUE_RETURN
|
|
(kid->pn_type != TOK_LP || kid->pn_op != JSOP_CALL) &&
|
|
#endif
|
|
#if JS_HAS_XML_SUPPORT
|
|
(kid->pn_type != TOK_UNARYOP || kid->pn_op != JSOP_XMLNAME) &&
|
|
#endif
|
|
kid->pn_type != TOK_LB) {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_OPERAND, name);
|
|
return NULL;
|
|
}
|
|
pn->pn_kid = kid;
|
|
return kid;
|
|
}
|
|
|
|
static const char *incop_name_str[] = {"increment", "decrement"};
|
|
|
|
static JSBool
|
|
SetIncOpKid(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
|
|
JSParseNode *pn, JSParseNode *kid,
|
|
JSTokenType tt, JSBool preorder)
|
|
{
|
|
JSOp op;
|
|
|
|
kid = SetLvalKid(cx, ts, pn, kid, incop_name_str[tt == TOK_DEC]);
|
|
if (!kid)
|
|
return JS_FALSE;
|
|
switch (kid->pn_type) {
|
|
case TOK_NAME:
|
|
op = (tt == TOK_INC)
|
|
? (preorder ? JSOP_INCNAME : JSOP_NAMEINC)
|
|
: (preorder ? JSOP_DECNAME : JSOP_NAMEDEC);
|
|
if (kid->pn_atom == cx->runtime->atomState.argumentsAtom)
|
|
tc->flags |= TCF_FUN_HEAVYWEIGHT;
|
|
break;
|
|
|
|
case TOK_DOT:
|
|
op = (tt == TOK_INC)
|
|
? (preorder ? JSOP_INCPROP : JSOP_PROPINC)
|
|
: (preorder ? JSOP_DECPROP : JSOP_PROPDEC);
|
|
break;
|
|
|
|
#if JS_HAS_LVALUE_RETURN
|
|
case TOK_LP:
|
|
JS_ASSERT(kid->pn_op == JSOP_CALL);
|
|
kid->pn_op = JSOP_SETCALL;
|
|
/* FALL THROUGH */
|
|
#endif
|
|
#if JS_HAS_XML_SUPPORT
|
|
case TOK_UNARYOP:
|
|
if (kid->pn_op == JSOP_XMLNAME)
|
|
kid->pn_op = JSOP_SETXMLNAME;
|
|
/* FALL THROUGH */
|
|
#endif
|
|
case TOK_LB:
|
|
op = (tt == TOK_INC)
|
|
? (preorder ? JSOP_INCELEM : JSOP_ELEMINC)
|
|
: (preorder ? JSOP_DECELEM : JSOP_ELEMDEC);
|
|
break;
|
|
|
|
default:
|
|
JS_ASSERT(0);
|
|
op = JSOP_NOP;
|
|
}
|
|
pn->pn_op = op;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSParseNode *
|
|
UnaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSTokenType tt;
|
|
JSParseNode *pn, *pn2;
|
|
|
|
CHECK_RECURSION();
|
|
|
|
ts->flags |= TSF_OPERAND;
|
|
tt = js_GetToken(cx, ts);
|
|
ts->flags &= ~TSF_OPERAND;
|
|
|
|
switch (tt) {
|
|
case TOK_UNARYOP:
|
|
case TOK_PLUS:
|
|
case TOK_MINUS:
|
|
pn = NewParseNode(cx, ts, PN_UNARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn->pn_type = TOK_UNARYOP; /* PLUS and MINUS are binary */
|
|
pn->pn_op = CURRENT_TOKEN(ts).t_op;
|
|
pn2 = UnaryExpr(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn->pn_pos.end = pn2->pn_pos.end;
|
|
pn->pn_kid = pn2;
|
|
break;
|
|
|
|
case TOK_INC:
|
|
case TOK_DEC:
|
|
pn = NewParseNode(cx, ts, PN_UNARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn2 = MemberExpr(cx, ts, tc, JS_TRUE);
|
|
if (!pn2)
|
|
return NULL;
|
|
if (!SetIncOpKid(cx, ts, tc, pn, pn2, tt, JS_TRUE))
|
|
return NULL;
|
|
pn->pn_pos.end = pn2->pn_pos.end;
|
|
break;
|
|
|
|
case TOK_DELETE:
|
|
pn = NewParseNode(cx, ts, PN_UNARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn2 = UnaryExpr(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn->pn_pos.end = pn2->pn_pos.end;
|
|
|
|
/*
|
|
* Under ECMA3, deleting any unary expression is valid -- it simply
|
|
* returns true. Here we strip off any parentheses.
|
|
*/
|
|
while (pn2->pn_type == TOK_RP)
|
|
pn2 = pn2->pn_kid;
|
|
pn->pn_kid = pn2;
|
|
break;
|
|
|
|
case TOK_ERROR:
|
|
return NULL;
|
|
|
|
default:
|
|
js_UngetToken(ts);
|
|
pn = MemberExpr(cx, ts, tc, JS_TRUE);
|
|
if (!pn)
|
|
return NULL;
|
|
|
|
/* Don't look across a newline boundary for a postfix incop. */
|
|
if (ON_CURRENT_LINE(ts, pn->pn_pos)) {
|
|
tt = js_PeekTokenSameLine(cx, ts);
|
|
if (tt == TOK_INC || tt == TOK_DEC) {
|
|
(void) js_GetToken(cx, ts);
|
|
pn2 = NewParseNode(cx, ts, PN_UNARY, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
if (!SetIncOpKid(cx, ts, tc, pn2, pn, tt, JS_FALSE))
|
|
return NULL;
|
|
pn2->pn_pos.begin = pn->pn_pos.begin;
|
|
pn = pn2;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
static JSBool
|
|
ArgumentList(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
|
|
JSParseNode *listNode)
|
|
{
|
|
JSBool matched;
|
|
|
|
ts->flags |= TSF_OPERAND;
|
|
matched = js_MatchToken(cx, ts, TOK_RP);
|
|
ts->flags &= ~TSF_OPERAND;
|
|
if (!matched) {
|
|
do {
|
|
JSParseNode *argNode = AssignExpr(cx, ts, tc);
|
|
if (!argNode)
|
|
return JS_FALSE;
|
|
PN_APPEND(listNode, argNode);
|
|
} while (js_MatchToken(cx, ts, TOK_COMMA));
|
|
|
|
if (js_GetToken(cx, ts) != TOK_RP) {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_PAREN_AFTER_ARGS);
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSParseNode *
|
|
MemberExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
|
|
JSBool allowCallSyntax)
|
|
{
|
|
JSParseNode *pn, *pn2, *pn3;
|
|
JSTokenType tt;
|
|
|
|
CHECK_RECURSION();
|
|
|
|
/* Check for new expression first. */
|
|
ts->flags |= TSF_OPERAND;
|
|
tt = js_PeekToken(cx, ts);
|
|
ts->flags &= ~TSF_OPERAND;
|
|
if (tt == TOK_NEW) {
|
|
(void) js_GetToken(cx, ts);
|
|
|
|
pn = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn2 = MemberExpr(cx, ts, tc, JS_FALSE);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn->pn_op = JSOP_NEW;
|
|
PN_INIT_LIST_1(pn, pn2);
|
|
pn->pn_pos.begin = pn2->pn_pos.begin;
|
|
|
|
if (js_MatchToken(cx, ts, TOK_LP) && !ArgumentList(cx, ts, tc, pn))
|
|
return NULL;
|
|
if (pn->pn_count > ARGC_LIMIT) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_TOO_MANY_CON_ARGS);
|
|
return NULL;
|
|
}
|
|
pn->pn_pos.end = PN_LAST(pn)->pn_pos.end;
|
|
} else {
|
|
pn = PrimaryExpr(cx, ts, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
|
|
if (pn->pn_type == TOK_ANYNAME ||
|
|
pn->pn_type == TOK_AT ||
|
|
pn->pn_type == TOK_DBLCOLON) {
|
|
pn2 = NewOrRecycledNode(cx, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn2->pn_type = TOK_UNARYOP;
|
|
pn2->pn_pos = pn->pn_pos;
|
|
pn2->pn_op = JSOP_XMLNAME;
|
|
pn2->pn_arity = PN_UNARY;
|
|
pn2->pn_kid = pn;
|
|
pn2->pn_next = NULL;
|
|
pn = pn2;
|
|
}
|
|
}
|
|
|
|
while ((tt = js_GetToken(cx, ts)) > TOK_EOF) {
|
|
if (tt == TOK_DOT) {
|
|
pn2 = NewParseNode(cx, ts, PN_NAME, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
#if JS_HAS_XML_SUPPORT
|
|
pn3 = PrimaryExpr(cx, ts, tc);
|
|
if (!pn3)
|
|
return NULL;
|
|
tt = pn3->pn_type;
|
|
if (tt == TOK_NAME ||
|
|
(tt == TOK_DBLCOLON &&
|
|
pn3->pn_arity == PN_NAME &&
|
|
pn3->pn_expr->pn_type == TOK_FUNCTION)) {
|
|
pn2->pn_op = (tt == TOK_NAME) ? JSOP_GETPROP : JSOP_GETMETHOD;
|
|
pn2->pn_expr = pn;
|
|
pn2->pn_atom = pn3->pn_atom;
|
|
RecycleTree(pn3, tc);
|
|
} else {
|
|
if (TOKEN_TYPE_IS_XML(tt)) {
|
|
pn2->pn_type = TOK_LB;
|
|
pn2->pn_op = JSOP_GETELEM;
|
|
} else if (tt == TOK_RP) {
|
|
JSParseNode *group = pn3;
|
|
|
|
/* Recycle the useless TOK_RP/JSOP_GROUP node. */
|
|
pn3 = group->pn_kid;
|
|
group->pn_kid = NULL;
|
|
RecycleTree(group, tc);
|
|
pn2->pn_type = TOK_FILTER;
|
|
pn2->pn_op = JSOP_FILTER;
|
|
} else {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_NAME_AFTER_DOT);
|
|
return NULL;
|
|
}
|
|
pn2->pn_arity = PN_BINARY;
|
|
pn2->pn_left = pn;
|
|
pn2->pn_right = pn3;
|
|
}
|
|
#else
|
|
MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NAME_AFTER_DOT);
|
|
pn2->pn_op = JSOP_GETPROP;
|
|
pn2->pn_expr = pn;
|
|
pn2->pn_atom = CURRENT_TOKEN(ts).t_atom;
|
|
#endif
|
|
pn2->pn_pos.begin = pn->pn_pos.begin;
|
|
pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
|
|
#if JS_HAS_XML_SUPPORT
|
|
} else if (tt == TOK_DBLDOT) {
|
|
pn2 = NewParseNode(cx, ts, PN_BINARY, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn3 = PrimaryExpr(cx, ts, tc);
|
|
if (!pn3)
|
|
return NULL;
|
|
tt = pn3->pn_type;
|
|
if (tt == TOK_NAME) {
|
|
pn3->pn_type = TOK_STRING;
|
|
pn3->pn_arity = PN_NULLARY;
|
|
pn3->pn_op = JSOP_STRING;
|
|
} else if (!TOKEN_TYPE_IS_XML(tt)) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_NAME_AFTER_DOT);
|
|
return NULL;
|
|
}
|
|
pn2->pn_op = JSOP_DESCENDANTS;
|
|
pn2->pn_left = pn;
|
|
pn2->pn_right = pn3;
|
|
pn2->pn_pos.begin = pn->pn_pos.begin;
|
|
pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
|
|
#endif
|
|
} else if (tt == TOK_LB) {
|
|
pn2 = NewParseNode(cx, ts, PN_BINARY, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn3 = Expr(cx, ts, tc);
|
|
if (!pn3)
|
|
return NULL;
|
|
|
|
MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_IN_INDEX);
|
|
pn2->pn_pos.begin = pn->pn_pos.begin;
|
|
pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
|
|
|
|
/* Optimize o['p'] to o.p by rewriting pn2. */
|
|
if (pn3->pn_type == TOK_STRING) {
|
|
pn2->pn_type = TOK_DOT;
|
|
pn2->pn_op = JSOP_GETPROP;
|
|
pn2->pn_arity = PN_NAME;
|
|
pn2->pn_expr = pn;
|
|
pn2->pn_atom = pn3->pn_atom;
|
|
} else {
|
|
pn2->pn_op = JSOP_GETELEM;
|
|
pn2->pn_left = pn;
|
|
pn2->pn_right = pn3;
|
|
}
|
|
} else if (allowCallSyntax && tt == TOK_LP) {
|
|
pn2 = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
|
|
/* Pick JSOP_EVAL and flag tc as heavyweight if eval(...). */
|
|
pn2->pn_op = JSOP_CALL;
|
|
if (pn->pn_op == JSOP_NAME &&
|
|
pn->pn_atom == cx->runtime->atomState.evalAtom) {
|
|
pn2->pn_op = JSOP_EVAL;
|
|
tc->flags |= TCF_FUN_HEAVYWEIGHT;
|
|
}
|
|
|
|
PN_INIT_LIST_1(pn2, pn);
|
|
pn2->pn_pos.begin = pn->pn_pos.begin;
|
|
|
|
if (!ArgumentList(cx, ts, tc, pn2))
|
|
return NULL;
|
|
if (pn2->pn_count > ARGC_LIMIT) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_TOO_MANY_FUN_ARGS);
|
|
return NULL;
|
|
}
|
|
pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
|
|
} else {
|
|
js_UngetToken(ts);
|
|
return pn;
|
|
}
|
|
|
|
pn = pn2;
|
|
}
|
|
if (tt == TOK_ERROR)
|
|
return NULL;
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
BracketedExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
uintN oldflags;
|
|
JSParseNode *pn;
|
|
|
|
#if JS_HAS_IN_OPERATOR
|
|
/*
|
|
* Always accept the 'in' operator in a parenthesized expression,
|
|
* where it's unambiguous, even if we might be parsing the init of a
|
|
* for statement.
|
|
*/
|
|
oldflags = tc->flags;
|
|
tc->flags &= ~TCF_IN_FOR_INIT;
|
|
#endif
|
|
pn = Expr(cx, ts, tc);
|
|
#if JS_HAS_IN_OPERATOR
|
|
tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS);
|
|
#endif
|
|
return pn;
|
|
}
|
|
|
|
#if JS_HAS_XML_SUPPORT
|
|
|
|
static JSParseNode *
|
|
EndBracketedExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
|
|
pn = BracketedExpr(cx, ts, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
|
|
MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_ATTR_EXPR);
|
|
return pn;
|
|
}
|
|
|
|
/*
|
|
* From the ECMA-357 grammar in 11.1.1 and 11.1.2:
|
|
*
|
|
* AttributeIdentifier:
|
|
* @ PropertySelector
|
|
* @ QualifiedIdentifier
|
|
* @ [ Expression ]
|
|
*
|
|
* PropertySelector:
|
|
* Identifier
|
|
* *
|
|
*
|
|
* QualifiedIdentifier:
|
|
* PropertySelector :: PropertySelector
|
|
* PropertySelector :: [ Expression ]
|
|
*
|
|
* We adapt AttributeIdentifier and QualifiedIdentier to be LL(1), like so:
|
|
*
|
|
* AttributeIdentifier:
|
|
* @ QualifiedIdentifier
|
|
* @ [ Expression ]
|
|
*
|
|
* PropertySelector:
|
|
* Identifier
|
|
* *
|
|
*
|
|
* QualifiedIdentifier:
|
|
* PropertySelector :: PropertySelector
|
|
* PropertySelector :: [ Expression ]
|
|
* PropertySelector
|
|
*
|
|
* Since PrimaryExpression: Identifier in ECMA-262 and we want the semantics
|
|
* for that rule to result in a name node, but extend the grammar to include
|
|
* PrimaryExpression: QualifiedIdentifier, we factor further:
|
|
*
|
|
* QualifiedIdentifier:
|
|
* PropertySelector QualifiedSuffix
|
|
*
|
|
* QualifiedSuffix:
|
|
* :: PropertySelector
|
|
* :: [ Expression ]
|
|
* /nothing/
|
|
*
|
|
* And use this production instead of PrimaryExpression: QualifiedIdentifier:
|
|
*
|
|
* PrimaryExpression:
|
|
* Identifier QualifiedSuffix
|
|
*
|
|
* We hoists the :: match into callers of QualifiedSuffix, in order to tweak
|
|
* PropertySelector vs. Identifier pn_arity, pn_op, and other members.
|
|
*/
|
|
static JSParseNode *
|
|
PropertySelector(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
|
|
pn = NewParseNode(cx, ts, PN_NULLARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
if (pn->pn_type == TOK_STAR) {
|
|
pn->pn_type = TOK_ANYNAME;
|
|
pn->pn_op = JSOP_ANYNAME;
|
|
pn->pn_atom = cx->runtime->atomState.starAtom;
|
|
} else {
|
|
JS_ASSERT(pn->pn_type == TOK_NAME);
|
|
pn->pn_op = JSOP_QNAMEPART;
|
|
pn->pn_arity = PN_NAME;
|
|
pn->pn_atom = CURRENT_TOKEN(ts).t_atom;
|
|
pn->pn_expr = NULL;
|
|
pn->pn_slot = -1;
|
|
pn->pn_attrs = 0;
|
|
}
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
QualifiedSuffix(JSContext *cx, JSTokenStream *ts, JSParseNode *pn,
|
|
JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn2, *pn3;
|
|
JSTokenType tt;
|
|
|
|
JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_DBLCOLON);
|
|
pn2 = NewParseNode(cx, ts, PN_NAME, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
|
|
/* Left operand of :: must be evaluated if it is an identifier. */
|
|
if (pn->pn_op == JSOP_QNAMEPART)
|
|
pn->pn_op = JSOP_NAME;
|
|
|
|
tt = js_GetToken(cx, ts);
|
|
if (tt == TOK_STAR || tt == TOK_NAME) {
|
|
/* Inline and specialize PropertySelector for JSOP_QNAMECONST. */
|
|
pn2->pn_op = JSOP_QNAMECONST;
|
|
pn2->pn_atom = (tt == TOK_STAR)
|
|
? cx->runtime->atomState.starAtom
|
|
: CURRENT_TOKEN(ts).t_atom;
|
|
pn2->pn_expr = pn;
|
|
pn2->pn_slot = -1;
|
|
pn2->pn_attrs = 0;
|
|
return pn2;
|
|
}
|
|
|
|
if (tt != TOK_LB) {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_SYNTAX_ERROR);
|
|
return NULL;
|
|
}
|
|
pn3 = EndBracketedExpr(cx, ts, tc);
|
|
if (!pn3)
|
|
return NULL;
|
|
|
|
pn2->pn_op = JSOP_QNAME;
|
|
pn2->pn_arity = PN_BINARY;
|
|
pn2->pn_left = pn;
|
|
pn2->pn_right = pn3;
|
|
return pn2;
|
|
}
|
|
|
|
static JSParseNode *
|
|
QualifiedIdentifier(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
|
|
pn = PropertySelector(cx, ts, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
if (js_MatchToken(cx, ts, TOK_DBLCOLON))
|
|
pn = QualifiedSuffix(cx, ts, pn, tc);
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
AttributeIdentifier(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn, *pn2;
|
|
JSTokenType tt;
|
|
|
|
JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_AT);
|
|
pn = NewParseNode(cx, ts, PN_UNARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn->pn_op = JSOP_TOATTRNAME;
|
|
tt = js_GetToken(cx, ts);
|
|
if (tt == TOK_STAR || tt == TOK_NAME) {
|
|
pn2 = QualifiedIdentifier(cx, ts, tc);
|
|
} else if (tt == TOK_LB) {
|
|
pn2 = EndBracketedExpr(cx, ts, tc);
|
|
} else {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_SYNTAX_ERROR);
|
|
return NULL;
|
|
}
|
|
if (!pn2)
|
|
return NULL;
|
|
pn->pn_kid = pn2;
|
|
return pn;
|
|
}
|
|
|
|
/*
|
|
* Make a TOK_LC unary node whose pn_kid is an expression.
|
|
*/
|
|
static JSParseNode *
|
|
XMLExpr(JSContext *cx, JSTokenStream *ts, JSBool inTag, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn, *pn2;
|
|
uintN oldflags;
|
|
|
|
JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_LC);
|
|
pn = NewParseNode(cx, ts, PN_UNARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
|
|
/*
|
|
* Turn off XML tag mode, but don't restore it after parsing this braced
|
|
* expression. Instead, simply restore ts's old flags. This is required
|
|
* because XMLExpr is called both from within a tag, and from within text
|
|
* contained in an element, but outside of any start, end, or point tag.
|
|
*/
|
|
oldflags = ts->flags;
|
|
ts->flags = oldflags & ~TSF_XMLTAGMODE;
|
|
pn2 = Expr(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
|
|
MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_IN_XML_EXPR);
|
|
ts->flags = oldflags;
|
|
pn->pn_kid = pn2;
|
|
pn->pn_op = inTag ? JSOP_XMLTAGEXPR : JSOP_XMLELTEXPR;
|
|
return pn;
|
|
}
|
|
|
|
/*
|
|
* Make a terminal node for oneof TOK_XMLNAME, TOK_XMLATTR, TOK_XMLSPACE,
|
|
* TOK_XMLTEXT, TOK_XMLCDATA, TOK_XMLCOMMENT, or TOK_XMLPI. When converting
|
|
* parse tree to XML, we preserve a TOK_XMLSPACE node only if it's the sole
|
|
* child of a container tag.
|
|
*/
|
|
static JSParseNode *
|
|
XMLAtomNode(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn;
|
|
JSToken *tp;
|
|
|
|
pn = NewParseNode(cx, ts, PN_NULLARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
tp = &CURRENT_TOKEN(ts);
|
|
pn->pn_op = tp->t_op;
|
|
pn->pn_atom = tp->t_atom;
|
|
if (tp->type == TOK_XMLPI)
|
|
pn->pn_atom2 = tp->t_atom2;
|
|
return pn;
|
|
}
|
|
|
|
/*
|
|
* Parse the productions:
|
|
*
|
|
* XMLNameExpr:
|
|
* XMLName XMLNameExpr?
|
|
* { Expr } XMLNameExpr?
|
|
*
|
|
* Return a PN_LIST, PN_UNARY, or PN_NULLARY according as XMLNameExpr produces
|
|
* a list of names and/or expressions, a single expression, or a single name.
|
|
* If PN_LIST or PN_NULLARY, pn_type will be TOK_XMLNAME; if PN_UNARY, pn_type
|
|
* will be TOK_LC.
|
|
*/
|
|
static JSParseNode *
|
|
XMLNameExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn, *pn2, *list;
|
|
JSTokenType tt;
|
|
|
|
pn = list = NULL;
|
|
do {
|
|
tt = CURRENT_TOKEN(ts).type;
|
|
if (tt == TOK_LC) {
|
|
pn2 = XMLExpr(cx, ts, JS_TRUE, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
} else {
|
|
JS_ASSERT(tt == TOK_XMLNAME);
|
|
pn2 = XMLAtomNode(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
}
|
|
|
|
if (!pn) {
|
|
pn = pn2;
|
|
} else {
|
|
if (!list) {
|
|
list = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!list)
|
|
return NULL;
|
|
list->pn_type = TOK_XMLNAME;
|
|
list->pn_pos.begin = pn->pn_pos.begin;
|
|
PN_INIT_LIST_1(list, pn);
|
|
list->pn_extra = PNX_CANTFOLD;
|
|
pn = list;
|
|
}
|
|
pn->pn_pos.end = pn2->pn_pos.end;
|
|
PN_APPEND(pn, pn2);
|
|
}
|
|
} while ((tt = js_GetToken(cx, ts)) == TOK_XMLNAME || tt == TOK_LC);
|
|
|
|
js_UngetToken(ts);
|
|
return pn;
|
|
}
|
|
|
|
/*
|
|
* Macro to test whether an XMLNameExpr or XMLTagContent node can be folded
|
|
* at compile time into a JSXML tree.
|
|
*/
|
|
#define XML_FOLDABLE(pn) ((pn)->pn_arity == PN_LIST \
|
|
? ((pn)->pn_extra & PNX_CANTFOLD) == 0 \
|
|
: (pn)->pn_type != TOK_LC)
|
|
|
|
/*
|
|
* Parse the productions:
|
|
*
|
|
* XMLTagContent:
|
|
* XMLNameExpr
|
|
* XMLTagContent S XMLNameExpr S? = S? XMLAttr
|
|
* XMLTagContent S XMLNameExpr S? = S? { Expr }
|
|
*
|
|
* Return a PN_LIST, PN_UNARY, or PN_NULLARY according to how XMLTagContent
|
|
* produces a list of name and attribute values and/or braced expressions, a
|
|
* single expression, or a single name.
|
|
*
|
|
* If PN_LIST or PN_NULLARY, pn_type will be TOK_XMLNAME for the case where
|
|
* XMLTagContent: XMLNameExpr. If pn_type is not TOK_XMLNAME but pn_arity is
|
|
* PN_LIST, pn_type will be tagtype. If PN_UNARY, pn_type will be TOK_LC and
|
|
* we parsed exactly one expression.
|
|
*/
|
|
static JSParseNode *
|
|
XMLTagContent(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
|
|
JSTokenType tagtype, JSAtom **namep)
|
|
{
|
|
JSParseNode *pn, *pn2, *list;
|
|
JSTokenType tt;
|
|
|
|
pn = XMLNameExpr(cx, ts, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
*namep = (pn->pn_arity == PN_NULLARY) ? pn->pn_atom : NULL;
|
|
list = NULL;
|
|
|
|
while (js_MatchToken(cx, ts, TOK_XMLSPACE)) {
|
|
tt = js_GetToken(cx, ts);
|
|
if (tt != TOK_XMLNAME && tt != TOK_LC) {
|
|
js_UngetToken(ts);
|
|
break;
|
|
}
|
|
|
|
pn2 = XMLNameExpr(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
if (!list) {
|
|
list = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!list)
|
|
return NULL;
|
|
list->pn_type = tagtype;
|
|
list->pn_pos.begin = pn->pn_pos.begin;
|
|
PN_INIT_LIST_1(list, pn);
|
|
pn = list;
|
|
}
|
|
PN_APPEND(pn, pn2);
|
|
if (!XML_FOLDABLE(pn2))
|
|
pn->pn_extra |= PNX_CANTFOLD;
|
|
|
|
js_MatchToken(cx, ts, TOK_XMLSPACE);
|
|
MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_NO_ASSIGN_IN_XML_ATTR);
|
|
js_MatchToken(cx, ts, TOK_XMLSPACE);
|
|
|
|
tt = js_GetToken(cx, ts);
|
|
if (tt == TOK_XMLATTR) {
|
|
pn2 = XMLAtomNode(cx, ts, tc);
|
|
} else if (tt == TOK_LC) {
|
|
pn2 = XMLExpr(cx, ts, JS_TRUE, tc);
|
|
pn->pn_extra |= PNX_CANTFOLD;
|
|
} else {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_XML_ATTR_VALUE);
|
|
return NULL;
|
|
}
|
|
if (!pn2)
|
|
return NULL;
|
|
pn->pn_pos.end = pn2->pn_pos.end;
|
|
PN_APPEND(pn, pn2);
|
|
}
|
|
|
|
return pn;
|
|
}
|
|
|
|
#define XML_CHECK_FOR_ERROR_AND_EOF(tt,result) \
|
|
JS_BEGIN_MACRO \
|
|
if ((tt) <= TOK_EOF) { \
|
|
if ((tt) == TOK_EOF) { \
|
|
js_ReportCompileErrorNumber(cx, ts, \
|
|
JSREPORT_TS | JSREPORT_ERROR, \
|
|
JSMSG_END_OF_XML_SOURCE); \
|
|
} \
|
|
return result; \
|
|
} \
|
|
JS_END_MACRO
|
|
|
|
static JSParseNode *
|
|
XMLElementOrList(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
|
|
JSBool allowList);
|
|
|
|
/*
|
|
* Consume XML element tag content, including the TOK_XMLETAGO (</) sequence
|
|
* that opens the end tag for the container.
|
|
*/
|
|
static JSBool
|
|
XMLElementContent(JSContext *cx, JSTokenStream *ts, JSParseNode *pn,
|
|
JSTreeContext *tc)
|
|
{
|
|
JSTokenType tt;
|
|
JSParseNode *pn2;
|
|
JSAtom *textAtom;
|
|
|
|
ts->flags &= ~TSF_XMLTAGMODE;
|
|
for (;;) {
|
|
ts->flags |= TSF_XMLTEXTMODE;
|
|
tt = js_GetToken(cx, ts);
|
|
ts->flags &= ~TSF_XMLTEXTMODE;
|
|
XML_CHECK_FOR_ERROR_AND_EOF(tt, JS_FALSE);
|
|
|
|
JS_ASSERT(tt == TOK_XMLSPACE || tt == TOK_XMLTEXT);
|
|
textAtom = CURRENT_TOKEN(ts).t_atom;
|
|
if (textAtom) {
|
|
/* Non-zero-length XML text scanned. */
|
|
pn2 = XMLAtomNode(cx, ts, tc);
|
|
if (!pn2)
|
|
return JS_FALSE;
|
|
pn->pn_pos.end = pn2->pn_pos.end;
|
|
PN_APPEND(pn, pn2);
|
|
}
|
|
|
|
ts->flags |= TSF_OPERAND;
|
|
tt = js_GetToken(cx, ts);
|
|
ts->flags &= ~TSF_OPERAND;
|
|
XML_CHECK_FOR_ERROR_AND_EOF(tt, JS_FALSE);
|
|
if (tt == TOK_XMLETAGO)
|
|
break;
|
|
|
|
if (tt == TOK_LC) {
|
|
pn2 = XMLExpr(cx, ts, JS_FALSE, tc);
|
|
pn->pn_extra |= PNX_CANTFOLD;
|
|
} else if (tt == TOK_XMLSTAGO) {
|
|
pn2 = XMLElementOrList(cx, ts, tc, JS_FALSE);
|
|
if (pn2) {
|
|
pn2->pn_extra &= ~PNX_XMLROOT;
|
|
pn->pn_extra |= pn2->pn_extra;
|
|
}
|
|
} else {
|
|
JS_ASSERT(tt == TOK_XMLCDATA || tt == TOK_XMLCOMMENT ||
|
|
tt == TOK_XMLPI);
|
|
pn2 = XMLAtomNode(cx, ts, tc);
|
|
}
|
|
if (!pn2)
|
|
return JS_FALSE;
|
|
pn->pn_pos.end = pn2->pn_pos.end;
|
|
PN_APPEND(pn, pn2);
|
|
}
|
|
|
|
JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_XMLETAGO);
|
|
ts->flags |= TSF_XMLTAGMODE;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/*
|
|
* Return a PN_LIST node containing an XML or XMLList Initialiser.
|
|
*/
|
|
static JSParseNode *
|
|
XMLElementOrList(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
|
|
JSBool allowList)
|
|
{
|
|
JSParseNode *pn, *pn2, *list;
|
|
JSBool hadSpace;
|
|
JSTokenType tt;
|
|
JSAtom *startAtom, *endAtom;
|
|
|
|
JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_XMLSTAGO);
|
|
pn = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
|
|
ts->flags |= TSF_XMLTAGMODE;
|
|
hadSpace = js_MatchToken(cx, ts, TOK_XMLSPACE);
|
|
tt = js_GetToken(cx, ts);
|
|
if (tt == TOK_ERROR)
|
|
return NULL;
|
|
|
|
if (tt == TOK_XMLNAME || tt == TOK_LC) {
|
|
/*
|
|
* XMLElement. Append the tag and its contents, if any, to pn.
|
|
*/
|
|
pn2 = XMLTagContent(cx, ts, tc, TOK_XMLSTAGO, &startAtom);
|
|
if (!pn2)
|
|
return NULL;
|
|
js_MatchToken(cx, ts, TOK_XMLSPACE);
|
|
|
|
tt = js_GetToken(cx, ts);
|
|
if (tt == TOK_XMLPTAGC) {
|
|
/* Point tag (/>): recycle pn if pn2 is a list of tag contents. */
|
|
if (pn2->pn_type == TOK_XMLSTAGO) {
|
|
PN_INIT_LIST(pn);
|
|
RecycleTree(pn, tc);
|
|
pn = pn2;
|
|
} else {
|
|
JS_ASSERT(pn2->pn_type == TOK_XMLNAME ||
|
|
pn2->pn_type == TOK_LC);
|
|
PN_INIT_LIST_1(pn, pn2);
|
|
if (!XML_FOLDABLE(pn2))
|
|
pn->pn_extra |= PNX_CANTFOLD;
|
|
}
|
|
pn->pn_type = TOK_XMLPTAGC;
|
|
pn->pn_extra |= PNX_XMLROOT;
|
|
} else {
|
|
/* We had better have a tag-close (>) at this point. */
|
|
if (tt != TOK_XMLTAGC) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_XML_TAG_SYNTAX);
|
|
return NULL;
|
|
}
|
|
pn2->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
|
|
|
|
/* Make sure pn2 is a TOK_XMLSTAGO list containing tag contents. */
|
|
if (pn2->pn_type != TOK_XMLSTAGO) {
|
|
PN_INIT_LIST_1(pn, pn2);
|
|
if (!XML_FOLDABLE(pn2))
|
|
pn->pn_extra |= PNX_CANTFOLD;
|
|
pn2 = pn;
|
|
pn = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
}
|
|
|
|
/* Now make pn a nominal-root TOK_XMLELEM list containing pn2. */
|
|
pn->pn_type = TOK_XMLELEM;
|
|
PN_INIT_LIST_1(pn, pn2);
|
|
if (!XML_FOLDABLE(pn2))
|
|
pn->pn_extra |= PNX_CANTFOLD;
|
|
pn->pn_extra |= PNX_XMLROOT;
|
|
|
|
/* Get element contents and delimiting end-tag-open sequence. */
|
|
if (!XMLElementContent(cx, ts, pn, tc))
|
|
return NULL;
|
|
|
|
js_MatchToken(cx, ts, TOK_XMLSPACE);
|
|
tt = js_GetToken(cx, ts);
|
|
XML_CHECK_FOR_ERROR_AND_EOF(tt, NULL);
|
|
if (tt != TOK_XMLNAME && tt != TOK_LC) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_XML_TAG_SYNTAX);
|
|
return NULL;
|
|
}
|
|
|
|
/* Parse end tag; check mismatch at compile-time if we can. */
|
|
pn2 = XMLTagContent(cx, ts, tc, TOK_XMLETAGO, &endAtom);
|
|
if (!pn2)
|
|
return NULL;
|
|
if (pn2->pn_type == TOK_XMLETAGO) {
|
|
/* Oops, end tag has attributes! */
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_XML_TAG_SYNTAX);
|
|
return NULL;
|
|
}
|
|
if (endAtom && startAtom && endAtom != startAtom) {
|
|
/* End vs. start tag name mismatch: point to the tag name. */
|
|
++pn2->pn_pos.begin.index;
|
|
js_ReportCompileErrorNumber(cx, pn2,
|
|
JSREPORT_PN | JSREPORT_ERROR,
|
|
JSMSG_XML_TAG_NAME_MISMATCH);
|
|
return NULL;
|
|
}
|
|
|
|
/* Make a TOK_XMLETAGO list with pn2 as its single child. */
|
|
JS_ASSERT(pn2->pn_type == TOK_XMLNAME || pn2->pn_type == TOK_LC);
|
|
list = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!list)
|
|
return NULL;
|
|
list->pn_type = TOK_XMLETAGO;
|
|
PN_INIT_LIST_1(list, pn2);
|
|
PN_APPEND(pn, list);
|
|
if (!XML_FOLDABLE(pn2)) {
|
|
list->pn_extra |= PNX_CANTFOLD;
|
|
pn->pn_extra |= PNX_CANTFOLD;
|
|
}
|
|
|
|
js_MatchToken(cx, ts, TOK_XMLSPACE);
|
|
MUST_MATCH_TOKEN(TOK_XMLTAGC, JSMSG_BAD_XML_TAG_SYNTAX);
|
|
}
|
|
|
|
/* Set pn_op now that pn has been updated to its final value. */
|
|
pn->pn_op = JSOP_TOXML;
|
|
} else if (!hadSpace && allowList && tt == TOK_XMLTAGC) {
|
|
/* XMLList Initialiser. */
|
|
pn->pn_type = TOK_XMLLIST;
|
|
pn->pn_op = JSOP_TOXMLLIST;
|
|
PN_INIT_LIST(pn);
|
|
pn->pn_extra |= PNX_XMLROOT;
|
|
if (!XMLElementContent(cx, ts, pn, tc))
|
|
return NULL;
|
|
|
|
MUST_MATCH_TOKEN(TOK_XMLTAGC, JSMSG_BAD_XML_LIST_SYNTAX);
|
|
} else {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_XML_NAME_SYNTAX);
|
|
return NULL;
|
|
}
|
|
|
|
pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
|
|
ts->flags &= ~TSF_XMLTAGMODE;
|
|
return pn;
|
|
}
|
|
|
|
static JSParseNode *
|
|
XMLElementOrListRoot(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc,
|
|
JSBool allowList)
|
|
{
|
|
uint32 oldopts;
|
|
JSParseNode *pn;
|
|
|
|
/*
|
|
* Force XML support to be enabled so that comments and CDATA literals
|
|
* are recognized, instead of <! followed by -- starting an HTML comment
|
|
* to end of line (used in script tags to hide content from old browsers
|
|
* that don't recognize <script>).
|
|
*/
|
|
oldopts = JS_SetOptions(cx, cx->options | JSOPTION_XML);
|
|
pn = XMLElementOrList(cx, ts, tc, allowList);
|
|
JS_SetOptions(cx, oldopts);
|
|
return pn;
|
|
}
|
|
|
|
JS_FRIEND_API(JSParseNode *)
|
|
js_ParseXMLTokenStream(JSContext *cx, JSObject *chain, JSTokenStream *ts,
|
|
JSBool allowList)
|
|
{
|
|
JSStackFrame *fp, frame;
|
|
JSParseNode *pn;
|
|
JSTreeContext tc;
|
|
JSTokenType tt;
|
|
|
|
/*
|
|
* Push a compiler frame if we have no frames, or if the top frame is a
|
|
* lightweight function activation, or if its scope chain doesn't match
|
|
* the one passed to us.
|
|
*/
|
|
fp = cx->fp;
|
|
if (!fp || !fp->varobj || fp->scopeChain != chain) {
|
|
memset(&frame, 0, sizeof frame);
|
|
frame.varobj = frame.scopeChain = chain;
|
|
if (cx->options & JSOPTION_VAROBJFIX) {
|
|
while ((chain = JS_GetParent(cx, chain)) != NULL)
|
|
frame.varobj = chain;
|
|
}
|
|
frame.down = fp;
|
|
if (fp)
|
|
frame.flags = fp->flags & (JSFRAME_SPECIAL | JSFRAME_COMPILE_N_GO);
|
|
cx->fp = &frame;
|
|
}
|
|
|
|
JS_KEEP_ATOMS(cx->runtime);
|
|
TREE_CONTEXT_INIT(&tc);
|
|
|
|
/* Set XML-only mode to turn off special treatment of {expr} in XML. */
|
|
ts->flags |= TSF_OPERAND | TSF_XMLONLYMODE;
|
|
tt = js_GetToken(cx, ts);
|
|
ts->flags &= ~TSF_OPERAND;
|
|
|
|
if (tt != TOK_XMLSTAGO) {
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_XML_MARKUP);
|
|
pn = NULL;
|
|
} else {
|
|
pn = XMLElementOrListRoot(cx, ts, &tc, allowList);
|
|
}
|
|
|
|
ts->flags &= ~TSF_XMLONLYMODE;
|
|
TREE_CONTEXT_FINISH(&tc);
|
|
JS_UNKEEP_ATOMS(cx->runtime);
|
|
cx->fp = fp;
|
|
return pn;
|
|
}
|
|
|
|
#endif /* JS_HAS_XMLSUPPORT */
|
|
|
|
static JSParseNode *
|
|
PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc)
|
|
{
|
|
JSTokenType tt;
|
|
JSParseNode *pn, *pn2, *pn3;
|
|
#if JS_HAS_GETTER_SETTER
|
|
JSAtom *atom;
|
|
JSRuntime *rt;
|
|
#endif
|
|
|
|
#if JS_HAS_SHARP_VARS
|
|
JSParseNode *defsharp;
|
|
JSBool notsharp;
|
|
|
|
defsharp = NULL;
|
|
notsharp = JS_FALSE;
|
|
again:
|
|
/*
|
|
* Control flows here after #n= is scanned. If the following primary is
|
|
* not valid after such a "sharp variable" definition, the tt switch case
|
|
* should set notsharp.
|
|
*/
|
|
#endif
|
|
|
|
CHECK_RECURSION();
|
|
|
|
ts->flags |= TSF_OPERAND;
|
|
tt = js_GetToken(cx, ts);
|
|
ts->flags &= ~TSF_OPERAND;
|
|
|
|
#if JS_HAS_GETTER_SETTER
|
|
if (tt == TOK_NAME) {
|
|
tt = CheckGetterOrSetter(cx, ts, TOK_FUNCTION);
|
|
if (tt == TOK_ERROR)
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
switch (tt) {
|
|
#if JS_HAS_LEXICAL_CLOSURE || JS_HAS_XML_SUPPORT
|
|
case TOK_FUNCTION:
|
|
#if JS_HAS_XML_SUPPORT
|
|
if (js_MatchToken(cx, ts, TOK_DBLCOLON)) {
|
|
pn2 = NewParseNode(cx, ts, PN_NULLARY, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
pn2->pn_type = TOK_FUNCTION;
|
|
pn = QualifiedSuffix(cx, ts, pn2, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
break;
|
|
}
|
|
#endif
|
|
pn = FunctionExpr(cx, ts, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
break;
|
|
#endif
|
|
|
|
#if JS_HAS_INITIALIZERS
|
|
case TOK_LB:
|
|
{
|
|
JSBool matched;
|
|
jsuint atomIndex;
|
|
|
|
pn = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn->pn_type = TOK_RB;
|
|
|
|
#if JS_HAS_SHARP_VARS
|
|
if (defsharp) {
|
|
PN_INIT_LIST_1(pn, defsharp);
|
|
defsharp = NULL;
|
|
} else
|
|
#endif
|
|
PN_INIT_LIST(pn);
|
|
|
|
ts->flags |= TSF_OPERAND;
|
|
matched = js_MatchToken(cx, ts, TOK_RB);
|
|
ts->flags &= ~TSF_OPERAND;
|
|
if (!matched) {
|
|
for (atomIndex = 0; ; atomIndex++) {
|
|
if (atomIndex == ATOM_INDEX_LIMIT) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_ARRAY_INIT_TOO_BIG);
|
|
return NULL;
|
|
}
|
|
|
|
ts->flags |= TSF_OPERAND;
|
|
tt = js_PeekToken(cx, ts);
|
|
ts->flags &= ~TSF_OPERAND;
|
|
if (tt == TOK_RB) {
|
|
pn->pn_extra |= PNX_ENDCOMMA;
|
|
break;
|
|
}
|
|
|
|
if (tt == TOK_COMMA) {
|
|
/* So CURRENT_TOKEN gets TOK_COMMA and not TOK_LB. */
|
|
js_MatchToken(cx, ts, TOK_COMMA);
|
|
pn2 = NewParseNode(cx, ts, PN_NULLARY, tc);
|
|
} else {
|
|
pn2 = AssignExpr(cx, ts, tc);
|
|
}
|
|
if (!pn2)
|
|
return NULL;
|
|
PN_APPEND(pn, pn2);
|
|
|
|
if (tt != TOK_COMMA) {
|
|
/* If we didn't already match TOK_COMMA in above case. */
|
|
if (!js_MatchToken(cx, ts, TOK_COMMA))
|
|
break;
|
|
}
|
|
}
|
|
|
|
MUST_MATCH_TOKEN(TOK_RB, JSMSG_BRACKET_AFTER_LIST);
|
|
}
|
|
pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
|
|
return pn;
|
|
}
|
|
|
|
case TOK_LC:
|
|
pn = NewParseNode(cx, ts, PN_LIST, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn->pn_type = TOK_RC;
|
|
|
|
#if JS_HAS_SHARP_VARS
|
|
if (defsharp) {
|
|
PN_INIT_LIST_1(pn, defsharp);
|
|
defsharp = NULL;
|
|
} else
|
|
#endif
|
|
PN_INIT_LIST(pn);
|
|
|
|
if (!js_MatchToken(cx, ts, TOK_RC)) {
|
|
do {
|
|
JSOp op;
|
|
|
|
tt = js_GetToken(cx, ts);
|
|
switch (tt) {
|
|
case TOK_NUMBER:
|
|
pn3 = NewParseNode(cx, ts, PN_NULLARY, tc);
|
|
if (pn3)
|
|
pn3->pn_dval = CURRENT_TOKEN(ts).t_dval;
|
|
break;
|
|
case TOK_NAME:
|
|
#if JS_HAS_GETTER_SETTER
|
|
atom = CURRENT_TOKEN(ts).t_atom;
|
|
rt = cx->runtime;
|
|
if (atom == rt->atomState.getAtom ||
|
|
atom == rt->atomState.setAtom) {
|
|
op = (atom == rt->atomState.getAtom)
|
|
? JSOP_GETTER
|
|
: JSOP_SETTER;
|
|
if (js_MatchToken(cx, ts, TOK_NAME)) {
|
|
pn3 = NewParseNode(cx, ts, PN_NAME, tc);
|
|
if (!pn3)
|
|
return NULL;
|
|
pn3->pn_atom = CURRENT_TOKEN(ts).t_atom;
|
|
pn3->pn_expr = NULL;
|
|
|
|
/* We have to fake a 'function' token here. */
|
|
CURRENT_TOKEN(ts).t_op = JSOP_NOP;
|
|
CURRENT_TOKEN(ts).type = TOK_FUNCTION;
|
|
pn2 = FunctionExpr(cx, ts, tc);
|
|
pn2 = NewBinary(cx, TOK_COLON, op, pn3, pn2, tc);
|
|
goto skip;
|
|
}
|
|
}
|
|
/* else fall thru ... */
|
|
#endif
|
|
case TOK_STRING:
|
|
pn3 = NewParseNode(cx, ts, PN_NULLARY, tc);
|
|
if (pn3)
|
|
pn3->pn_atom = CURRENT_TOKEN(ts).t_atom;
|
|
break;
|
|
case TOK_RC:
|
|
if (!js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS |
|
|
JSREPORT_WARNING |
|
|
JSREPORT_STRICT,
|
|
JSMSG_TRAILING_COMMA)) {
|
|
return NULL;
|
|
}
|
|
goto end_obj_init;
|
|
default:
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_PROP_ID);
|
|
return NULL;
|
|
}
|
|
|
|
tt = js_GetToken(cx, ts);
|
|
#if JS_HAS_GETTER_SETTER
|
|
if (tt == TOK_NAME) {
|
|
tt = CheckGetterOrSetter(cx, ts, TOK_COLON);
|
|
if (tt == TOK_ERROR)
|
|
return NULL;
|
|
}
|
|
#endif
|
|
if (tt != TOK_COLON) {
|
|
js_ReportCompileErrorNumber(cx, ts,
|
|
JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_COLON_AFTER_ID);
|
|
return NULL;
|
|
}
|
|
op = CURRENT_TOKEN(ts).t_op;
|
|
pn2 = NewBinary(cx, TOK_COLON, op, pn3, AssignExpr(cx, ts, tc),
|
|
tc);
|
|
#if JS_HAS_GETTER_SETTER
|
|
skip:
|
|
#endif
|
|
if (!pn2)
|
|
return NULL;
|
|
PN_APPEND(pn, pn2);
|
|
} while (js_MatchToken(cx, ts, TOK_COMMA));
|
|
|
|
MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_LIST);
|
|
}
|
|
end_obj_init:
|
|
pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
|
|
return pn;
|
|
|
|
#if JS_HAS_SHARP_VARS
|
|
case TOK_DEFSHARP:
|
|
if (defsharp)
|
|
goto badsharp;
|
|
defsharp = NewParseNode(cx, ts, PN_UNARY, tc);
|
|
if (!defsharp)
|
|
return NULL;
|
|
defsharp->pn_kid = NULL;
|
|
defsharp->pn_num = (jsint) CURRENT_TOKEN(ts).t_dval;
|
|
goto again;
|
|
|
|
case TOK_USESHARP:
|
|
/* Check for forward/dangling references at runtime, to allow eval. */
|
|
pn = NewParseNode(cx, ts, PN_NULLARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn->pn_num = (jsint) CURRENT_TOKEN(ts).t_dval;
|
|
notsharp = JS_TRUE;
|
|
break;
|
|
#endif /* JS_HAS_SHARP_VARS */
|
|
#endif /* JS_HAS_INITIALIZERS */
|
|
|
|
case TOK_LP:
|
|
pn = NewParseNode(cx, ts, PN_UNARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn2 = BracketedExpr(cx, ts, tc);
|
|
if (!pn2)
|
|
return NULL;
|
|
|
|
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN);
|
|
pn->pn_type = TOK_RP;
|
|
pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end;
|
|
pn->pn_kid = pn2;
|
|
break;
|
|
|
|
#if JS_HAS_XML_SUPPORT
|
|
case TOK_STAR:
|
|
pn = QualifiedIdentifier(cx, ts, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
notsharp = JS_TRUE;
|
|
break;
|
|
|
|
case TOK_AT:
|
|
pn = AttributeIdentifier(cx, ts, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
notsharp = JS_TRUE;
|
|
break;
|
|
|
|
case TOK_XMLSTAGO:
|
|
pn = XMLElementOrListRoot(cx, ts, tc, JS_TRUE);
|
|
if (!pn)
|
|
return NULL;
|
|
notsharp = JS_TRUE; /* XXXbe could be sharp? */
|
|
break;
|
|
#endif /* JS_HAS_XML_SUPPORT */
|
|
|
|
case TOK_STRING:
|
|
#if JS_HAS_SHARP_VARS
|
|
notsharp = JS_TRUE;
|
|
/* FALL THROUGH */
|
|
#endif
|
|
|
|
#if JS_HAS_XML_SUPPORT
|
|
case TOK_XMLCDATA:
|
|
case TOK_XMLCOMMENT:
|
|
case TOK_XMLPI:
|
|
#endif
|
|
case TOK_NAME:
|
|
case TOK_OBJECT:
|
|
pn = NewParseNode(cx, ts, PN_NULLARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn->pn_atom = CURRENT_TOKEN(ts).t_atom;
|
|
#if JS_HAS_XML_SUPPORT
|
|
if (tt == TOK_XMLPI)
|
|
pn->pn_atom2 = CURRENT_TOKEN(ts).t_atom2;
|
|
else
|
|
#endif
|
|
pn->pn_op = CURRENT_TOKEN(ts).t_op;
|
|
if (tt == TOK_NAME) {
|
|
pn->pn_arity = PN_NAME;
|
|
pn->pn_expr = NULL;
|
|
pn->pn_slot = -1;
|
|
pn->pn_attrs = 0;
|
|
|
|
#if JS_HAS_XML_SUPPORT
|
|
if (js_MatchToken(cx, ts, TOK_DBLCOLON)) {
|
|
pn = QualifiedSuffix(cx, ts, pn, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
/* Unqualified __parent__ and __proto__ uses require activations. */
|
|
if (pn->pn_atom == cx->runtime->atomState.parentAtom ||
|
|
pn->pn_atom == cx->runtime->atomState.protoAtom) {
|
|
tc->flags |= TCF_FUN_HEAVYWEIGHT;
|
|
} else {
|
|
JSAtomListElement *ale;
|
|
JSStackFrame *fp;
|
|
JSStmtInfo *stmt;
|
|
|
|
/* Measure optimizable global variable uses. */
|
|
ATOM_LIST_SEARCH(ale, &tc->decls, pn->pn_atom);
|
|
if (ale &&
|
|
!(fp = cx->fp)->fun &&
|
|
fp->scopeChain == fp->varobj &&
|
|
!js_InWithStatement(tc) &&
|
|
!js_InCatchBlock(tc, pn->pn_atom)) {
|
|
tc->globalUses++;
|
|
for (stmt = tc->topStmt; stmt; stmt = stmt->down) {
|
|
if (STMT_IS_LOOP(stmt)) {
|
|
tc->loopyGlobalUses++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TOK_NUMBER:
|
|
pn = NewParseNode(cx, ts, PN_NULLARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn->pn_dval = CURRENT_TOKEN(ts).t_dval;
|
|
#if JS_HAS_SHARP_VARS
|
|
notsharp = JS_TRUE;
|
|
#endif
|
|
break;
|
|
|
|
case TOK_PRIMARY:
|
|
pn = NewParseNode(cx, ts, PN_NULLARY, tc);
|
|
if (!pn)
|
|
return NULL;
|
|
pn->pn_op = CURRENT_TOKEN(ts).t_op;
|
|
#if JS_HAS_SHARP_VARS
|
|
notsharp = JS_TRUE;
|
|
#endif
|
|
break;
|
|
|
|
#if !JS_HAS_EXPORT_IMPORT
|
|
case TOK_EXPORT:
|
|
case TOK_IMPORT:
|
|
#endif
|
|
case TOK_ERROR:
|
|
/* The scanner or one of its subroutines reported the error. */
|
|
return NULL;
|
|
|
|
default:
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_SYNTAX_ERROR);
|
|
return NULL;
|
|
}
|
|
|
|
#if JS_HAS_SHARP_VARS
|
|
if (defsharp) {
|
|
if (notsharp) {
|
|
badsharp:
|
|
js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR,
|
|
JSMSG_BAD_SHARP_VAR_DEF);
|
|
return NULL;
|
|
}
|
|
defsharp->pn_kid = pn;
|
|
return defsharp;
|
|
}
|
|
#endif
|
|
return pn;
|
|
}
|
|
|
|
static JSBool
|
|
ContainsVarStmt(JSParseNode *pn)
|
|
{
|
|
JSParseNode *pn2;
|
|
|
|
if (!pn)
|
|
return JS_FALSE;
|
|
switch (pn->pn_arity) {
|
|
case PN_LIST:
|
|
if (pn->pn_type == TOK_VAR)
|
|
return JS_TRUE;
|
|
for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
|
|
if (ContainsVarStmt(pn2))
|
|
return JS_TRUE;
|
|
}
|
|
break;
|
|
case PN_TERNARY:
|
|
return ContainsVarStmt(pn->pn_kid1) ||
|
|
ContainsVarStmt(pn->pn_kid2) ||
|
|
ContainsVarStmt(pn->pn_kid3);
|
|
case PN_BINARY:
|
|
/*
|
|
* Limit recursion if pn is a binary expression, which can't contain a
|
|
* var statement.
|
|
*/
|
|
if (pn->pn_op != JSOP_NOP)
|
|
return JS_FALSE;
|
|
return ContainsVarStmt(pn->pn_left) || ContainsVarStmt(pn->pn_right);
|
|
case PN_UNARY:
|
|
if (pn->pn_op != JSOP_NOP)
|
|
return JS_FALSE;
|
|
return ContainsVarStmt(pn->pn_kid);
|
|
default:;
|
|
}
|
|
return JS_FALSE;
|
|
}
|
|
|
|
/*
|
|
* Fold from one constant type to another.
|
|
* XXX handles only strings and numbers for now
|
|
*/
|
|
static JSBool
|
|
FoldType(JSContext *cx, JSParseNode *pn, JSTokenType type)
|
|
{
|
|
if (pn->pn_type != type) {
|
|
switch (type) {
|
|
case TOK_NUMBER:
|
|
if (pn->pn_type == TOK_STRING) {
|
|
jsdouble d;
|
|
if (!js_ValueToNumber(cx, ATOM_KEY(pn->pn_atom), &d))
|
|
return JS_FALSE;
|
|
pn->pn_dval = d;
|
|
pn->pn_type = TOK_NUMBER;
|
|
pn->pn_op = JSOP_NUMBER;
|
|
}
|
|
break;
|
|
|
|
case TOK_STRING:
|
|
if (pn->pn_type == TOK_NUMBER) {
|
|
JSString *str = js_NumberToString(cx, pn->pn_dval);
|
|
if (!str)
|
|
return JS_FALSE;
|
|
pn->pn_atom = js_AtomizeString(cx, str, 0);
|
|
if (!pn->pn_atom)
|
|
return JS_FALSE;
|
|
pn->pn_type = TOK_STRING;
|
|
pn->pn_op = JSOP_STRING;
|
|
}
|
|
break;
|
|
|
|
default:;
|
|
}
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/*
|
|
* Fold two numeric constants. Beware that pn1 and pn2 are recycled, unless
|
|
* one of them aliases pn, so you can't safely fetch pn2->pn_next, e.g., after
|
|
* a successful call to this function.
|
|
*/
|
|
static JSBool
|
|
FoldBinaryNumeric(JSContext *cx, JSOp op, JSParseNode *pn1, JSParseNode *pn2,
|
|
JSParseNode *pn, JSTreeContext *tc)
|
|
{
|
|
jsdouble d, d2;
|
|
int32 i, j;
|
|
uint32 u;
|
|
|
|
JS_ASSERT(pn1->pn_type == TOK_NUMBER && pn2->pn_type == TOK_NUMBER);
|
|
d = pn1->pn_dval;
|
|
d2 = pn2->pn_dval;
|
|
switch (op) {
|
|
case JSOP_LSH:
|
|
case JSOP_RSH:
|
|
if (!js_DoubleToECMAInt32(cx, d, &i))
|
|
return JS_FALSE;
|
|
if (!js_DoubleToECMAInt32(cx, d2, &j))
|
|
return JS_FALSE;
|
|
j &= 31;
|
|
d = (op == JSOP_LSH) ? i << j : i >> j;
|
|
break;
|
|
|
|
case JSOP_URSH:
|
|
if (!js_DoubleToECMAUint32(cx, d, &u))
|
|
return JS_FALSE;
|
|
if (!js_DoubleToECMAInt32(cx, d2, &j))
|
|
return JS_FALSE;
|
|
j &= 31;
|
|
d = u >> j;
|
|
break;
|
|
|
|
case JSOP_ADD:
|
|
d += d2;
|
|
break;
|
|
|
|
case JSOP_SUB:
|
|
d -= d2;
|
|
break;
|
|
|
|
case JSOP_MUL:
|
|
d *= d2;
|
|
break;
|
|
|
|
case JSOP_DIV:
|
|
if (d2 == 0) {
|
|
#if defined(XP_WIN)
|
|
/* XXX MSVC miscompiles such that (NaN == 0) */
|
|
if (JSDOUBLE_IS_NaN(d2))
|
|
d = *cx->runtime->jsNaN;
|
|
else
|
|
#endif
|
|
if (d == 0 || JSDOUBLE_IS_NaN(d))
|
|
d = *cx->runtime->jsNaN;
|
|
else if ((JSDOUBLE_HI32(d) ^ JSDOUBLE_HI32(d2)) >> 31)
|
|
d = *cx->runtime->jsNegativeInfinity;
|
|
else
|
|
d = *cx->runtime->jsPositiveInfinity;
|
|
} else {
|
|
d /= d2;
|
|
}
|
|
break;
|
|
|
|
case JSOP_MOD:
|
|
if (d2 == 0) {
|
|
d = *cx->runtime->jsNaN;
|
|
} else {
|
|
#if defined(XP_WIN)
|
|
/* Workaround MS fmod bug where 42 % (1/0) => NaN, not 42. */
|
|
if (!(JSDOUBLE_IS_FINITE(d) && JSDOUBLE_IS_INFINITE(d2)))
|
|
#endif
|
|
d = fmod(d, d2);
|
|
}
|
|
break;
|
|
|
|
default:;
|
|
}
|
|
|
|
/* Take care to allow pn1 or pn2 to alias pn. */
|
|
if (pn1 != pn)
|
|
RecycleTree(pn1, tc);
|
|
if (pn2 != pn)
|
|
RecycleTree(pn2, tc);
|
|
pn->pn_type = TOK_NUMBER;
|
|
pn->pn_op = JSOP_NUMBER;
|
|
pn->pn_arity = PN_NULLARY;
|
|
pn->pn_dval = d;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
#if JS_HAS_XML_SUPPORT
|
|
|
|
static JSBool
|
|
FoldXMLConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc)
|
|
{
|
|
JSTokenType tt;
|
|
JSParseNode **pnp, *pn1, *pn2;
|
|
JSString *accum, *str;
|
|
uint32 i, j;
|
|
|
|
JS_ASSERT(pn->pn_arity == PN_LIST);
|
|
tt = pn->pn_type;
|
|
pnp = &pn->pn_head;
|
|
pn1 = *pnp;
|
|
accum = NULL;
|
|
if ((pn->pn_extra & PNX_CANTFOLD) == 0) {
|
|
if (tt == TOK_XMLETAGO)
|
|
accum = ATOM_TO_STRING(cx->runtime->atomState.etagoAtom);
|
|
else if (tt == TOK_XMLSTAGO || tt == TOK_XMLPTAGC)
|
|
accum = ATOM_TO_STRING(cx->runtime->atomState.stagoAtom);
|
|
}
|
|
|
|
for (pn2 = pn1, i = j = 0; pn2; pn2 = pn2->pn_next, i++) {
|
|
/* The parser already rejected end-tags with attributes. */
|
|
JS_ASSERT(tt != TOK_XMLETAGO || i == 0);
|
|
switch (pn2->pn_type) {
|
|
case TOK_XMLNAME:
|
|
case TOK_XMLATTR:
|
|
case TOK_XMLSPACE:
|
|
case TOK_XMLTEXT:
|
|
case TOK_STRING:
|
|
if (pn->pn_arity == PN_LIST)
|
|
goto cantfold;
|
|
str = ATOM_TO_STRING(pn2->pn_atom);
|
|
break;
|
|
|
|
case TOK_XMLCDATA:
|
|
str = js_MakeXMLCDATAString(cx, ATOM_TO_STRING(pn2->pn_atom));
|
|
if (!str)
|
|
return JS_FALSE;
|
|
break;
|
|
|
|
case TOK_XMLCOMMENT:
|
|
str = js_MakeXMLCommentString(cx, ATOM_TO_STRING(pn2->pn_atom));
|
|
if (!str)
|
|
return JS_FALSE;
|
|
break;
|
|
|
|
case TOK_XMLPI:
|
|
str = js_MakeXMLPIString(cx, ATOM_TO_STRING(pn2->pn_atom),
|
|
ATOM_TO_STRING(pn2->pn_atom2));
|
|
if (!str)
|
|
return JS_FALSE;
|
|
break;
|
|
|
|
cantfold:
|
|
default:
|
|
JS_ASSERT(*pnp == pn1);
|
|
if ((tt == TOK_XMLSTAGO || tt == TOK_XMLPTAGC) &&
|
|
(i & 1) ^ (j & 1)) {
|
|
#ifdef DEBUG_brendanXXX
|
|
printf("1: %d, %d => %s\n",
|
|
i, j, accum ? JS_GetStringBytes(accum) : "NULL");
|
|
#endif
|
|
} else if (accum && pn1 != pn2) {
|
|
while (pn1->pn_next != pn2) {
|
|
pn1 = RecycleTree(pn1, tc);
|
|
--pn->pn_count;
|
|
}
|
|
pn1->pn_type = TOK_XMLTEXT;
|
|
pn1->pn_op = JSOP_STRING;
|
|
pn1->pn_arity = PN_NULLARY;
|
|
pn1->pn_atom = js_AtomizeString(cx, accum, 0);
|
|
if (!pn1->pn_atom)
|
|
return JS_FALSE;
|
|
JS_ASSERT(pnp != &pn1->pn_next);
|
|
*pnp = pn1;
|
|
}
|
|
pnp = &pn2->pn_next;
|
|
pn1 = *pnp;
|
|
accum = NULL;
|
|
continue;
|
|
}
|
|
|
|
if (accum) {
|
|
str = ((tt == TOK_XMLSTAGO || tt == TOK_XMLPTAGC) && i != 0)
|
|
? js_AddAttributePart(cx, i & 1, accum, str)
|
|
: js_ConcatStrings(cx, accum, str);
|
|
if (!str)
|
|
return JS_FALSE;
|
|
#ifdef DEBUG_brendanXXX
|
|
printf("2: %d, %d => %s (%u)\n",
|
|
i, j, JS_GetStringBytes(str), JSSTRING_LENGTH(str));
|
|
#endif
|
|
++j;
|
|
}
|
|
accum = str;
|
|
}
|
|
|
|
if (accum) {
|
|
str = NULL;
|
|
if ((pn->pn_extra & PNX_CANTFOLD) == 0) {
|
|
if (tt == TOK_XMLPTAGC)
|
|
str = ATOM_TO_STRING(cx->runtime->atomState.ptagcAtom);
|
|
else if (tt == TOK_XMLSTAGO || tt == TOK_XMLETAGO)
|
|
str = ATOM_TO_STRING(cx->runtime->atomState.tagcAtom);
|
|
}
|
|
if (str) {
|
|
accum = js_ConcatStrings(cx, accum, str);
|
|
if (!accum)
|
|
return JS_FALSE;
|
|
}
|
|
|
|
JS_ASSERT(*pnp == pn1);
|
|
while (pn1->pn_next) {
|
|
pn1 = RecycleTree(pn1, tc);
|
|
--pn->pn_count;
|
|
}
|
|
pn1->pn_type = TOK_XMLTEXT;
|
|
pn1->pn_op = JSOP_STRING;
|
|
pn1->pn_arity = PN_NULLARY;
|
|
pn1->pn_atom = js_AtomizeString(cx, accum, 0);
|
|
if (!pn1->pn_atom)
|
|
return JS_FALSE;
|
|
JS_ASSERT(pnp != &pn1->pn_next);
|
|
*pnp = pn1;
|
|
}
|
|
|
|
if (pn1 && pn->pn_count == 1) {
|
|
/*
|
|
* Only one node under pn, and it has been folded: move pn1 onto pn
|
|
* unless pn is an XML root (in which case we need it to tell the code
|
|
* generator to emit a JSOP_TOXML or JSOP_TOXMLLIST op). If pn is an
|
|
* XML root *and* it's a point-tag, rewrite it to TOK_XMLELEM to avoid
|
|
* extra "<" and "/>" bracketing at runtime.
|
|
*/
|
|
if (!(pn->pn_extra & PNX_XMLROOT)) {
|
|
PN_MOVE_NODE(pn, pn1);
|
|
} else if (tt == TOK_XMLPTAGC) {
|
|
pn->pn_type = TOK_XMLELEM;
|
|
pn->pn_op = JSOP_TOXML;
|
|
}
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
#endif /* JS_HAS_XML_SUPPORT */
|
|
|
|
JSBool
|
|
js_FoldConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc)
|
|
{
|
|
JSParseNode *pn1 = NULL, *pn2 = NULL, *pn3 = NULL;
|
|
int stackDummy;
|
|
|
|
if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED);
|
|
return JS_FALSE;
|
|
}
|
|
|
|
switch (pn->pn_arity) {
|
|
case PN_FUNC:
|
|
{
|
|
uint16 oldflags = tc->flags;
|
|
|
|
tc->flags = (uint16) pn->pn_flags;
|
|
if (!js_FoldConstants(cx, pn->pn_body, tc))
|
|
return JS_FALSE;
|
|
tc->flags = oldflags;
|
|
break;
|
|
}
|
|
|
|
case PN_LIST:
|
|
#if 0 /* JS_HAS_XML_SUPPORT */
|
|
switch (pn->pn_type) {
|
|
case TOK_XMLELEM:
|
|
case TOK_XMLLIST:
|
|
case TOK_XMLPTAGC:
|
|
/*
|
|
* Try to fold this XML parse tree once, from the top down, into
|
|
* a JSXML tree with just one object wrapping the tree root.
|
|
*
|
|
* Certain subtrees could be folded similarly, but we'd have to
|
|
* ensure that none used namespace prefixes declared elsewhere in
|
|
* its super-tree, and we would have to convert each XML object
|
|
* created at runtime for such sub-trees back into a string, and
|
|
* concatenate and re-parse anyway.
|
|
*/
|
|
if ((pn->pn_extra & (PNX_XMLROOT | PNX_CANTFOLD)) == PNX_XMLROOT &&
|
|
!(tc->flags & TCF_HAS_DEFXMLNS)) {
|
|
JSObject *obj;
|
|
JSAtom *atom;
|
|
|
|
obj = js_ParseNodeToXMLObject(cx, pn);
|
|
if (!obj)
|
|
return JS_FALSE;
|
|
atom = js_AtomizeObject(cx, obj, 0);
|
|
if (!atom)
|
|
return JS_FALSE;
|
|
pn->pn_op = JSOP_XMLOBJECT;
|
|
pn->pn_arity = PN_NULLARY;
|
|
pn->pn_atom = atom;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/*
|
|
* Can't fold from parse node to XML tree -- try folding strings
|
|
* as much as possible, and folding XML sub-trees bottom up to
|
|
* minimize string concatenation and ToXML/ToXMLList operations
|
|
* at runtime.
|
|
*/
|
|
break;
|
|
|
|
default:;
|
|
}
|
|
#endif
|
|
|
|
/* Save the list head in pn1 for later use. */
|
|
for (pn1 = pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
|
|
if (!js_FoldConstants(cx, pn2, tc))
|
|
return JS_FALSE;
|
|
}
|
|
break;
|
|
|
|
case PN_TERNARY:
|
|
/* Any kid may be null (e.g. for (;;)). */
|
|
pn1 = pn->pn_kid1;
|
|
pn2 = pn->pn_kid2;
|
|
pn3 = pn->pn_kid3;
|
|
if (pn1 && !js_FoldConstants(cx, pn1, tc))
|
|
return JS_FALSE;
|
|
if (pn2 && !js_FoldConstants(cx, pn2, tc))
|
|
return JS_FALSE;
|
|
if (pn3 && !js_FoldConstants(cx, pn3, tc))
|
|
return JS_FALSE;
|
|
break;
|
|
|
|
case PN_BINARY:
|
|
/* First kid may be null (for default case in switch). */
|
|
pn1 = pn->pn_left;
|
|
pn2 = pn->pn_right;
|
|
if (pn1 && !js_FoldConstants(cx, pn1, tc))
|
|
return JS_FALSE;
|
|
if (!js_FoldConstants(cx, pn2, tc))
|
|
return JS_FALSE;
|
|
break;
|
|
|
|
case PN_UNARY:
|
|
/* Our kid may be null (e.g. return; vs. return e;). */
|
|
pn1 = pn->pn_kid;
|
|
if (pn1 && !js_FoldConstants(cx, pn1, tc))
|
|
return JS_FALSE;
|
|
break;
|
|
|
|
case PN_NAME:
|
|
/*
|
|
* Skip pn1 down along a chain of dotted member expressions to avoid
|
|
* excessive recursion. Our only goal here is to fold constants (if
|
|
* any) in the primary expression operand to the left of the first
|
|
* dot in the chain.
|
|
*/
|
|
pn1 = pn->pn_expr;
|
|
while (pn1 && pn1->pn_arity == PN_NAME)
|
|
pn1 = pn1->pn_expr;
|
|
if (pn1 && !js_FoldConstants(cx, pn1, tc))
|
|
return JS_FALSE;
|
|
break;
|
|
|
|
case PN_NULLARY:
|
|
break;
|
|
}
|
|
|
|
switch (pn->pn_type) {
|
|
case TOK_IF:
|
|
if (ContainsVarStmt(pn2) || ContainsVarStmt(pn3))
|
|
break;
|
|
/* FALL THROUGH */
|
|
|
|
case TOK_HOOK:
|
|
/* Reduce 'if (C) T; else E' into T for true C, E for false. */
|
|
switch (pn1->pn_type) {
|
|
case TOK_NUMBER:
|
|
if (pn1->pn_dval == 0)
|
|
pn2 = pn3;
|
|
break;
|
|
case TOK_STRING:
|
|
if (JSSTRING_LENGTH(ATOM_TO_STRING(pn1->pn_atom)) == 0)
|
|
pn2 = pn3;
|
|
break;
|
|
case TOK_PRIMARY:
|
|
if (pn1->pn_op == JSOP_TRUE)
|
|
break;
|
|
if (pn1->pn_op == JSOP_FALSE || pn1->pn_op == JSOP_NULL) {
|
|
pn2 = pn3;
|
|
break;
|
|
}
|
|
/* FALL THROUGH */
|
|
default:
|
|
/* Early return to dodge common code that copies pn2 to pn. */
|
|
return JS_TRUE;
|
|
}
|
|
|
|
if (pn2) {
|
|
/* pn2 is the then- or else-statement subtree to compile. */
|
|
PN_MOVE_NODE(pn, pn2);
|
|
} else {
|
|
/* False condition and no else: make pn an empty statement. */
|
|
pn->pn_type = TOK_SEMI;
|
|
pn->pn_arity = PN_UNARY;
|
|
pn->pn_kid = NULL;
|
|
}
|
|
RecycleTree(pn2, tc);
|
|
if (pn3 && pn3 != pn2)
|
|
RecycleTree(pn3, tc);
|
|
break;
|
|
|
|
case TOK_PLUS:
|
|
if (pn->pn_arity == PN_LIST) {
|
|
size_t length, length2;
|
|
jschar *chars;
|
|
JSString *str, *str2;
|
|
|
|
/*
|
|
* Any string literal term with all others number or string means
|
|
* this is a concatenation. If any term is not a string or number
|
|
* literal, we can't fold.
|
|
*/
|
|
JS_ASSERT(pn->pn_count > 2);
|
|
if (pn->pn_extra & PNX_CANTFOLD)
|
|
return JS_TRUE;
|
|
if (pn->pn_extra != PNX_STRCAT)
|
|
goto do_binary_op;
|
|
|
|
/* Ok, we're concatenating: convert non-string constant operands. */
|
|
length = 0;
|
|
for (pn2 = pn1; pn2; pn2 = pn2->pn_next) {
|
|
if (!FoldType(cx, pn2, TOK_STRING))
|
|
return JS_FALSE;
|
|
/* XXX fold only if all operands convert to string */
|
|
if (pn2->pn_type != TOK_STRING)
|
|
return JS_TRUE;
|
|
length += ATOM_TO_STRING(pn2->pn_atom)->length;
|
|
}
|
|
|
|
/* Allocate a new buffer and string descriptor for the result. */
|
|
chars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar));
|
|
if (!chars)
|
|
return JS_FALSE;
|
|
str = js_NewString(cx, chars, length, 0);
|
|
if (!str) {
|
|
JS_free(cx, chars);
|
|
return JS_FALSE;
|
|
}
|
|
|
|
/* Fill the buffer, advancing chars and recycling kids as we go. */
|
|
for (pn2 = pn1; pn2; pn2 = RecycleTree(pn2, tc)) {
|
|
str2 = ATOM_TO_STRING(pn2->pn_atom);
|
|
length2 = str2->length;
|
|
js_strncpy(chars, str2->chars, length2);
|
|
chars += length2;
|
|
}
|
|
*chars = 0;
|
|
|
|
/* Atomize the result string and mutate pn to refer to it. */
|
|
pn->pn_atom = js_AtomizeString(cx, str, 0);
|
|
if (!pn->pn_atom)
|
|
return JS_FALSE;
|
|
pn->pn_type = TOK_STRING;
|
|
pn->pn_op = JSOP_STRING;
|
|
pn->pn_arity = PN_NULLARY;
|
|
break;
|
|
}
|
|
|
|
/* Handle a binary string concatenation. */
|
|
JS_ASSERT(pn->pn_arity == PN_BINARY);
|
|
if (pn1->pn_type == TOK_STRING || pn2->pn_type == TOK_STRING) {
|
|
JSString *left, *right, *str;
|
|
|
|
if (!FoldType(cx, (pn1->pn_type != TOK_STRING) ? pn1 : pn2,
|
|
TOK_STRING)) {
|
|
return JS_FALSE;
|
|
}
|
|
if (pn1->pn_type != TOK_STRING || pn2->pn_type != TOK_STRING)
|
|
return JS_TRUE;
|
|
left = ATOM_TO_STRING(pn1->pn_atom);
|
|
right = ATOM_TO_STRING(pn2->pn_atom);
|
|
str = js_ConcatStrings(cx, left, right);
|
|
if (!str)
|
|
return JS_FALSE;
|
|
pn->pn_atom = js_AtomizeString(cx, str, 0);
|
|
if (!pn->pn_atom)
|
|
return JS_FALSE;
|
|
pn->pn_type = TOK_STRING;
|
|
pn->pn_op = JSOP_STRING;
|
|
pn->pn_arity = PN_NULLARY;
|
|
RecycleTree(pn1, tc);
|
|
RecycleTree(pn2, tc);
|
|
break;
|
|
}
|
|
|
|
/* Can't concatenate string literals, let's try numbers. */
|
|
goto do_binary_op;
|
|
|
|
case TOK_STAR:
|
|
/* The * in 'import *;' parses as a nullary star node. */
|
|
if (pn->pn_arity == PN_NULLARY)
|
|
break;
|
|
/* FALL THROUGH */
|
|
|
|
case TOK_SHOP:
|
|
case TOK_MINUS:
|
|
case TOK_DIVOP:
|
|
do_binary_op:
|
|
if (pn->pn_arity == PN_LIST) {
|
|
JS_ASSERT(pn->pn_count > 2);
|
|
for (pn2 = pn1; pn2; pn2 = pn2->pn_next) {
|
|
if (!FoldType(cx, pn2, TOK_NUMBER))
|
|
return JS_FALSE;
|
|
/* XXX fold only if all operands convert to number */
|
|
if (pn2->pn_type != TOK_NUMBER)
|
|
break;
|
|
}
|
|
if (!pn2) {
|
|
JSOp op = pn->pn_op;
|
|
|
|
pn2 = pn1->pn_next;
|
|
pn3 = pn2->pn_next;
|
|
if (!FoldBinaryNumeric(cx, op, pn1, pn2, pn, tc))
|
|
return JS_FALSE;
|
|
while ((pn2 = pn3) != NULL) {
|
|
pn3 = pn2->pn_next;
|
|
if (!FoldBinaryNumeric(cx, op, pn, pn2, pn, tc))
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
} else {
|
|
JS_ASSERT(pn->pn_arity == PN_BINARY);
|
|
if (!FoldType(cx, pn1, TOK_NUMBER) ||
|
|
!FoldType(cx, pn2, TOK_NUMBER)) {
|
|
return JS_FALSE;
|
|
}
|
|
if (pn1->pn_type == TOK_NUMBER && pn2->pn_type == TOK_NUMBER) {
|
|
if (!FoldBinaryNumeric(cx, pn->pn_op, pn1, pn2, pn, tc))
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TOK_UNARYOP:
|
|
if (pn1->pn_type == TOK_NUMBER) {
|
|
jsdouble d;
|
|
int32 i;
|
|
|
|
/* Operate on one numeric constant. */
|
|
d = pn1->pn_dval;
|
|
switch (pn->pn_op) {
|
|
case JSOP_BITNOT:
|
|
if (!js_DoubleToECMAInt32(cx, d, &i))
|
|
return JS_FALSE;
|
|
d = ~i;
|
|
break;
|
|
|
|
case JSOP_NEG:
|
|
#ifdef HPUX
|
|
/*
|
|
* Negation of a zero doesn't produce a negative
|
|
* zero on HPUX. Perform the operation by bit
|
|
* twiddling.
|
|
*/
|
|
JSDOUBLE_HI32(d) ^= JSDOUBLE_HI32_SIGNBIT;
|
|
#else
|
|
d = -d;
|
|
#endif
|
|
break;
|
|
|
|
case JSOP_POS:
|
|
break;
|
|
|
|
case JSOP_NOT:
|
|
pn->pn_type = TOK_PRIMARY;
|
|
pn->pn_op = (d == 0) ? JSOP_TRUE : JSOP_FALSE;
|
|
pn->pn_arity = PN_NULLARY;
|
|
/* FALL THROUGH */
|
|
|
|
default:
|
|
/* Return early to dodge the common TOK_NUMBER code. */
|
|
return JS_TRUE;
|
|
}
|
|
pn->pn_type = TOK_NUMBER;
|
|
pn->pn_op = JSOP_NUMBER;
|
|
pn->pn_arity = PN_NULLARY;
|
|
pn->pn_dval = d;
|
|
RecycleTree(pn1, tc);
|
|
}
|
|
break;
|
|
|
|
#if JS_HAS_XML_SUPPORT
|
|
case TOK_XMLELEM:
|
|
case TOK_XMLLIST:
|
|
case TOK_XMLPTAGC:
|
|
case TOK_XMLSTAGO:
|
|
case TOK_XMLETAGO:
|
|
case TOK_XMLNAME:
|
|
if (pn->pn_arity == PN_LIST) {
|
|
JS_ASSERT(pn->pn_type == TOK_XMLLIST || pn->pn_count != 0);
|
|
if (!FoldXMLConstants(cx, pn, tc))
|
|
return JS_FALSE;
|
|
}
|
|
break;
|
|
|
|
case TOK_AT:
|
|
if (pn1->pn_type == TOK_XMLNAME) {
|
|
jsval v;
|
|
JSAtom *atom;
|
|
|
|
v = ATOM_KEY(pn1->pn_atom);
|
|
if (!js_ToAttributeName(cx, &v))
|
|
return JS_FALSE;
|
|
JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
|
|
atom = js_AtomizeObject(cx, JSVAL_TO_OBJECT(v), 0);
|
|
if (!atom)
|
|
return JS_FALSE;
|
|
|
|
pn->pn_type = TOK_XMLNAME;
|
|
pn->pn_op = JSOP_OBJECT;
|
|
pn->pn_arity = PN_NULLARY;
|
|
pn->pn_atom = atom;
|
|
RecycleTree(pn1, tc);
|
|
}
|
|
break;
|
|
#endif /* JS_HAS_XML_SUPPORT */
|
|
|
|
default:;
|
|
}
|
|
|
|
return JS_TRUE;
|
|
}
|