From 48e1521edc5e9b53cc6ad3699b05acac1186f147 Mon Sep 17 00:00:00 2001 From: "brendan@mozilla.org" Date: Thu, 17 May 2007 18:41:17 -0700 Subject: [PATCH] Support generator expressions for JS1.8 (380237, r=mrbkap). --- js/src/js.msg | 4 +- js/src/jsconfig.h | 43 +++- js/src/jsemit.c | 16 +- js/src/jsemit.h | 2 + js/src/jsopcode.c | 258 ++++++++++++++++---- js/src/jsopcode.h | 3 + js/src/jsopcode.tbl | 30 +-- js/src/jsparse.c | 580 +++++++++++++++++++++++++++++--------------- js/src/jsparse.h | 3 +- 9 files changed, 675 insertions(+), 264 deletions(-) diff --git a/js/src/js.msg b/js/src/js.msg index 69906d0b74ba..2730e6ee6dd9 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -294,8 +294,10 @@ MSG_DEF(JSMSG_IN_AFTER_FOR_NAME, 211, 0, JSEXN_SYNTAXERR, "missing in after MSG_DEF(JSMSG_BAD_ITERATOR_RETURN, 212, 2, JSEXN_TYPEERR, "{0}.{1} returned a primitive value") MSG_DEF(JSMSG_KEYWORD_NOT_NS, 213, 0, JSEXN_SYNTAXERR, "keyword is used as namespace") MSG_DEF(JSMSG_BAD_GENERATOR_YIELD, 214, 1, JSEXN_TYPEERR, "yield from closing generator {0}") -MSG_DEF(JSMSG_BAD_YIELD_SYNTAX, 215, 0, JSEXN_SYNTAXERR, "yield expression must be parenthesized") +MSG_DEF(JSMSG_BAD_GENERATOR_SYNTAX, 215, 1, JSEXN_SYNTAXERR, "{0} expression must be parenthesized") MSG_DEF(JSMSG_ARRAY_COMP_LEFTSIDE, 216, 0, JSEXN_SYNTAXERR, "invalid array comprehension left-hand side") MSG_DEF(JSMSG_YIELD_FROM_FILTER, 217, 0, JSEXN_INTERNALERR, "yield not yet supported from filtering predicate") MSG_DEF(JSMSG_EMPTY_ARRAY_REDUCE, 218, 0, JSEXN_TYPEERR, "reduce of empty array with no initial value") MSG_DEF(JSMSG_NON_LIST_XML_METHOD, 219, 2, JSEXN_TYPEERR, "cannot call {0} method on an XML list with {1} elements") +MSG_DEF(JSMSG_BAD_DELETE_OPERAND, 220, 0, JSEXN_SYNTAXERR, "invalid delete operand") +MSG_DEF(JSMSG_BAD_INCOP_OPERAND, 221, 0, JSEXN_SYNTAXERR, "invalid increment/decrement operand") diff --git a/js/src/jsconfig.h b/js/src/jsconfig.h index c681776d30b9..830cc3fd50c4 100644 --- a/js/src/jsconfig.h +++ b/js/src/jsconfig.h @@ -41,17 +41,17 @@ * JS configuration macros. */ #ifndef JS_VERSION -#define JS_VERSION 170 +#define JS_VERSION 180 #endif /* * Compile-time JS version configuration. The JS version numbers lie on the * number line like so: * - * 1.0 1.1 1.2 1.3 1.4 ECMAv3 1.5 1.6 - * ^ ^ - * | | - * basis for ECMAv1 close to ECMAv2 + * 1.0 1.1 1.2 1.3 1.4 ECMAv3 1.5 1.6 1.7 1.8 + * ^ ^ + * | | + * basis for ECMAv1 close to ECMAv2 * * where ECMAv3 stands for ECMA-262 Edition 3. See the runtime version enum * JSVersion in jspubtd.h. Code in the engine can therefore count on version @@ -107,6 +107,7 @@ #define JS_HAS_GENERATORS 0 /* has yield in generator function */ #define JS_HAS_BLOCK_SCOPE 0 /* has block scope via let/arraycomp */ #define JS_HAS_DESTRUCTURING 0 /* has [a,b] = ... or {p:a,q:b} = ... */ +#define JS_HAS_GENERATOR_EXPRS 0 /* has (expr for (lhs in iterable)) */ #elif JS_VERSION < 150 @@ -139,6 +140,7 @@ #define JS_HAS_GENERATORS 0 /* has yield in generator function */ #define JS_HAS_BLOCK_SCOPE 0 /* has block scope via let/arraycomp */ #define JS_HAS_DESTRUCTURING 0 /* has [a,b] = ... or {p:a,q:b} = ... */ +#define JS_HAS_GENERATOR_EXPRS 0 /* has (expr for (lhs in iterable)) */ #elif JS_VERSION == 160 @@ -167,6 +169,7 @@ #define JS_HAS_GENERATORS 0 /* has yield in generator function */ #define JS_HAS_BLOCK_SCOPE 0 /* has block scope via let/arraycomp */ #define JS_HAS_DESTRUCTURING 0 /* has [a,b] = ... or {p:a,q:b} = ... */ +#define JS_HAS_GENERATOR_EXPRS 0 /* has (expr for (lhs in iterable)) */ #elif JS_VERSION == 170 @@ -195,6 +198,36 @@ #define JS_HAS_GENERATORS 1 /* has yield in generator function */ #define JS_HAS_BLOCK_SCOPE 1 /* has block scope via let/arraycomp */ #define JS_HAS_DESTRUCTURING 1 /* has [a,b] = ... or {p:a,q:b} = ... */ +#define JS_HAS_GENERATOR_EXPRS 0 /* has (expr for (lhs in iterable)) */ + +#elif JS_VERSION == 180 + +#define JS_HAS_STR_HTML_HELPERS 1 /* has str.anchor, str.bold, etc. */ +#define JS_HAS_PERL_SUBSTR 1 /* has str.substr */ +#define JS_HAS_OBJ_PROTO_PROP 1 /* has o.__proto__ etc. */ +#define JS_HAS_OBJ_WATCHPOINT 1 /* has o.watch and o.unwatch */ +#define JS_HAS_EXPORT_IMPORT 1 /* has export fun; import obj.fun */ +#define JS_HAS_EVAL_THIS_SCOPE 1 /* Math.eval is same as with (Math) */ +#define JS_HAS_SHARP_VARS 1 /* has #n=, #n# for object literals */ +#define JS_HAS_SCRIPT_OBJECT 0 /* has (new Script("x++")).exec() */ +#define JS_HAS_XDR 1 /* has XDR API and internal support */ +#define JS_HAS_XDR_FREEZE_THAW 0 /* has XDR freeze/thaw script methods */ +#define JS_HAS_TOSOURCE 1 /* has Object/Array toSource method */ +#define JS_HAS_DEBUGGER_KEYWORD 1 /* has hook for debugger keyword */ +#define JS_HAS_CATCH_GUARD 1 /* has exception handling catch guard */ +#define JS_HAS_SPARSE_ARRAYS 0 /* array methods preserve empty elems */ +#define JS_HAS_GETTER_SETTER 1 /* has JS2 getter/setter functions */ +#define JS_HAS_UNEVAL 1 /* has uneval() top-level function */ +#define JS_HAS_CONST 1 /* has JS2 const as alternative var */ +#define JS_HAS_FUN_EXPR_STMT 1 /* has function expression statement */ +#define JS_HAS_LVALUE_RETURN 1 /* has o.item(i) = j; for native item */ +#define JS_HAS_NO_SUCH_METHOD 1 /* has o.__noSuchMethod__ handler */ +#define JS_HAS_XML_SUPPORT 1 /* has ECMAScript for XML support */ +#define JS_HAS_ARRAY_EXTRAS 1 /* has indexOf and Lispy extras */ +#define JS_HAS_GENERATORS 1 /* has yield in generator function */ +#define JS_HAS_BLOCK_SCOPE 1 /* has block scope via let/arraycomp */ +#define JS_HAS_DESTRUCTURING 1 /* has [a,b] = ... or {p:a,q:b} = ... */ +#define JS_HAS_GENERATOR_EXPRS 1 /* has (expr for (lhs in iterable)) */ #else diff --git a/js/src/jsemit.c b/js/src/jsemit.c index e15cc2884b04..ee074872ff85 100644 --- a/js/src/jsemit.c +++ b/js/src/jsemit.c @@ -4098,6 +4098,10 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) /* Emit a bytecode pointing to the closure object in its immediate. */ if (pn->pn_op != JSOP_NOP) { + if ((pn->pn_flags & TCF_GENEXP_LAMBDA) && + js_NewSrcNote(cx, cg, SRC_GENEXP) < 0) { + return JS_FALSE; + } EMIT_ATOM_INDEX_OP(pn->pn_op, atomIndex); break; } @@ -5215,6 +5219,12 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) #if JS_HAS_GENERATORS case TOK_YIELD: + if (!(cg->treeContext.flags & TCF_IN_FUNCTION)) { + js_ReportCompileErrorNumber(cx, pn, JSREPORT_PN | JSREPORT_ERROR, + JSMSG_BAD_RETURN_OR_YIELD, + js_yield_str); + return JS_FALSE; + } if (pn->pn_kid) { if (!js_EmitTree(cx, cg, pn->pn_kid)) return JS_FALSE; @@ -5222,6 +5232,8 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) if (js_Emit1(cx, cg, JSOP_PUSH) < 0) return JS_FALSE; } + if (pn->pn_hidden && js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0) + return JS_FALSE; if (js_Emit1(cx, cg, JSOP_YIELD) < 0) return JS_FALSE; break; @@ -5866,10 +5878,6 @@ js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn) #endif #if JS_HAS_LVALUE_RETURN case TOK_LP: - if (pn2->pn_op != JSOP_SETCALL) { - JS_ASSERT(pn2->pn_op == JSOP_CALL || pn2->pn_op == JSOP_EVAL); - pn2->pn_op = JSOP_SETCALL; - } top = CG_OFFSET(cg); if (!js_EmitTree(cx, cg, pn2)) return JS_FALSE; diff --git a/js/src/jsemit.h b/js/src/jsemit.h index 7a36ce14f665..12860c930992 100644 --- a/js/src/jsemit.h +++ b/js/src/jsemit.h @@ -183,6 +183,7 @@ struct JSTreeContext { /* tree context for semantic checks */ #define TCF_FUN_FLAGS 0x1E0 /* flags to propagate from FunctionBody */ #define TCF_HAS_DEFXMLNS 0x200 /* default xml namespace = ...; parsed */ #define TCF_HAS_FUNCTION_STMT 0x400 /* block contains a function statement */ +#define TCF_GENEXP_LAMBDA 0x800 /* flag lambda from generator expression */ #define TREE_CONTEXT_INIT(tc) \ ((tc)->flags = (tc)->numGlobalVars = 0, \ @@ -528,6 +529,7 @@ typedef enum JSSrcNoteType { SRC_INITPROP = 1, /* disjoint meaning applied to JSOP_INITELEM or to an index label in a regular (structuring) or a destructuring object initialiser */ + SRC_GENEXP = 1, /* JSOP_ANONFUNOBJ from generator expression */ SRC_IF_ELSE = 2, /* JSOP_IFEQ bytecode is from an if-then-else */ SRC_WHILE = 3, /* JSOP_IFEQ is from a while loop */ SRC_FOR = 4, /* JSOP_NOP or JSOP_POP in for loop head */ diff --git a/js/src/jsopcode.c b/js/src/jsopcode.c index 50fbf930a54b..3e9d3a716a05 100644 --- a/js/src/jsopcode.c +++ b/js/src/jsopcode.c @@ -802,6 +802,7 @@ typedef struct SprintStack { jsbytecode *opcodes; /* parallel stack of JS opcodes */ uintN top; /* top of stack index */ uintN inArrayInit; /* array initialiser/comprehension level */ + JSBool inGenExp; /* in generator expression */ JSPrinter *printer; /* permanent output goes here */ } SprintStack; @@ -870,6 +871,13 @@ GetStr(SprintStack *ss, uintN i) #define JSOP_GETPROP2 256 #define JSOP_GETELEM2 257 +static void +AddParenSlop(SprintStack *ss) +{ + memset(OFF2STR(&ss->sprinter, ss->sprinter.offset), 0, PAREN_SLOP); + ss->sprinter.offset += PAREN_SLOP; +} + static JSBool PushOff(SprintStack *ss, ptrdiff_t off, JSOp op) { @@ -892,8 +900,7 @@ PushOff(SprintStack *ss, ptrdiff_t off, JSOp op) : (op == JSOP_GETELEM2) ? JSOP_GETELEM : (jsbytecode) op; ss->top = ++top; - memset(OFF2STR(&ss->sprinter, ss->sprinter.offset), 0, PAREN_SLOP); - ss->sprinter.offset += PAREN_SLOP; + AddParenSlop(ss); return JS_TRUE; } @@ -1579,6 +1586,29 @@ DecompileGroupAssignment(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc, #endif /* JS_HAS_DESTRUCTURING */ +static JSBool +InitSprintStack(JSContext *cx, SprintStack *ss, JSPrinter *jp, uintN depth) +{ + size_t offsetsz, opcodesz; + void *space; + + INIT_SPRINTER(cx, &ss->sprinter, &cx->tempPool, PAREN_SLOP); + + /* Allocate the parallel (to avoid padding) offset and opcode stacks. */ + offsetsz = depth * sizeof(ptrdiff_t); + opcodesz = depth * sizeof(jsbytecode); + JS_ARENA_ALLOCATE(space, &cx->tempPool, offsetsz + opcodesz); + if (!space) + return JS_FALSE; + ss->offsets = (ptrdiff_t *) space; + ss->opcodes = (jsbytecode *) ((char *)space + offsetsz); + + ss->top = ss->inArrayInit = 0; + ss->inGenExp = JS_FALSE; + ss->printer = jp; + return JS_TRUE; +} + /* * If nb is non-negative, decompile nb bytecodes starting at pc. Otherwise * the decompiler starts at pc and continues until it reaches an opcode for @@ -2653,49 +2683,98 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) #if JS_HAS_GENERATORS case JSOP_YIELD: op = JSOP_SETNAME; /* turn off most parens */ - rval = POP_STR(); - todo = (*rval != '\0') - ? Sprint(&ss->sprinter, - (strncmp(rval, js_yield_str, 5) == 0 && - (rval[5] == ' ' || rval[5] == '\0')) - ? "%s (%s)" - : "%s %s", - js_yield_str, rval) - : SprintCString(&ss->sprinter, js_yield_str); - break; + + if (!ss->inGenExp || !(sn = js_GetSrcNote(jp->script, pc))) { + rval = POP_STR(); + todo = (*rval != '\0') + ? Sprint(&ss->sprinter, + (strncmp(rval, js_yield_str, 5) == 0 && + (rval[5] == ' ' || rval[5] == '\0')) + ? "%s (%s)" + : "%s %s", + js_yield_str, rval) + : SprintCString(&ss->sprinter, js_yield_str); + break; + } + LOCAL_ASSERT(SN_TYPE(sn) == SRC_HIDDEN); + /* FALL THROUGH */ case JSOP_ARRAYPUSH: { - uintN pos, blockpos, startpos; + uintN pos, forpos; ptrdiff_t start; + /* Pop the expression being pushed or yielded. */ rval = POP_STR(); + + /* + * Skip down over iterables left stacked by JSOP_FOR* until + * we hit a block-local or the new Array initialiser (empty + * destructuring patterns yield zero-count blocks). + */ pos = ss->top; while ((op = ss->opcodes[--pos]) != JSOP_ENTERBLOCK && op != JSOP_NEWINIT) { - LOCAL_ASSERT(pos != 0); + if (pos == 0) + break; } - blockpos = pos; + + /* + * Make forpos index the space before the left-most |for| in + * the single string of accumulated |for| heads and optional + * final |if (condition)|. + */ + forpos = pos + (op == JSOP_ENTERBLOCK || op == JSOP_NEWINIT); + LOCAL_ASSERT(forpos < ss->top); + + /* + * Now skip down over the block's local slots, if any. There + * may be no locals for an empty destructuring pattern. + */ while (ss->opcodes[pos] == JSOP_ENTERBLOCK) { if (pos == 0) break; --pos; } + + if (saveop == JSOP_YIELD) { + /* + * Generator expression: decompile just rval followed by + * the string starting at forpos. Leave the result string + * in ss->offsets[0] so it can be recovered by our caller + * (the JSOP_ANONFUNOBJ with SRC_GENEXP case). Bump the + * top of stack to balance yield, which is an expression + * (so has neutral stack balance). + */ + LOCAL_ASSERT(pos == 0); + xval = OFF2STR(&ss->sprinter, ss->offsets[forpos]); + ss->sprinter.offset = PAREN_SLOP; + todo = Sprint(&ss->sprinter, ss_format, rval, xval); + if (todo < 0) + return NULL; + ss->offsets[0] = todo; + ++ss->top; + return pc; + } + + /* + * Array comprehension: retract the sprinter to the beginning + * of the array initialiser and decompile "[ for ...]". + */ LOCAL_ASSERT(ss->opcodes[pos] == JSOP_NEWINIT); - startpos = pos; start = ss->offsets[pos]; LOCAL_ASSERT(ss->sprinter.base[start] == '[' || ss->sprinter.base[start] == '#'); - pos = blockpos + 1; - LOCAL_ASSERT(pos < ss->top); - xval = OFF2STR(&ss->sprinter, ss->offsets[pos]); + LOCAL_ASSERT(forpos < ss->top); + xval = OFF2STR(&ss->sprinter, ss->offsets[forpos]); lval = OFF2STR(&ss->sprinter, start); RETRACT(&ss->sprinter, lval); + todo = Sprint(&ss->sprinter, "%s%s%.*s", lval, rval, rval - xval, xval); if (todo < 0) return NULL; - ss->offsets[startpos] = todo; + ss->offsets[pos] = todo; todo = -2; break; } @@ -2782,10 +2861,12 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) case SRC_IF_ELSE: op = JSOP_NOP; /* turn off parens */ rval = POP_STR(); - if (ss->inArrayInit) { + if (ss->inArrayInit || ss->inGenExp) { LOCAL_ASSERT(SN_TYPE(sn) == SRC_IF); + ss->sprinter.offset -= PAREN_SLOP; if (Sprint(&ss->sprinter, " if (%s)", rval) < 0) return NULL; + AddParenSlop(ss); } else { js_printf(SET_MAYBE_BRACE(jp), elseif ? " if (%s) {\n" : "\tif (%s) {\n", @@ -2796,7 +2877,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) if (SN_TYPE(sn) == SRC_IF) { DECOMPILE_CODE(pc + oplen, len - oplen); } else { - LOCAL_ASSERT(!ss->inArrayInit); + LOCAL_ASSERT(!ss->inArrayInit && !ss->inGenExp); tail = js_GetSrcNoteOffset(sn, 0); DECOMPILE_CODE(pc + oplen, tail - oplen); jp->indent -= 4; @@ -2827,7 +2908,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) DECOMPILE_CODE(pc + oplen, len - oplen); } - if (!ss->inArrayInit) { + if (!ss->inArrayInit && !ss->inGenExp) { jp->indent -= 4; js_printf(jp, "\t}\n"); } @@ -2999,12 +3080,18 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) lval = OFF2STR(&ss->sprinter, todo); rval = GetStr(ss, ss->top-1); RETRACT(&ss->sprinter, rval); - if (ss->inArrayInit) { + if (ss->inArrayInit || ss->inGenExp) { + if (ss->top > 1 && + (js_CodeSpec[ss->opcodes[ss->top-2]].format & + JOF_FOR)) { + ss->sprinter.offset -= PAREN_SLOP; + } todo = Sprint(&ss->sprinter, " %s in %s)", lval, rval); if (todo < 0) return NULL; ss->offsets[ss->top-1] = todo; - ss->sprinter.offset += PAREN_SLOP; + ss->opcodes[ss->top-1] = op; + AddParenSlop(ss); DECOMPILE_CODE(pc + oplen, tail - oplen); } else { js_printf(SET_MAYBE_BRACE(jp), "\t%s in %s) {\n", @@ -3627,9 +3714,105 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) todo = STR2OFF(&ss->sprinter, rval); END_LITOPX_CASE + case JSOP_ANONFUNOBJ: + sn = js_GetSrcNote(jp->script, pc); + if (sn && SN_TYPE(sn) == SRC_GENEXP) { + JSScript *inner, *outer; + void *mark; + SprintStack ss2; + + LOAD_ATOM(0); + obj = ATOM_TO_OBJECT(atom); + fun = (JSFunction *) JS_GetPrivate(cx, obj); + LOCAL_ASSERT(FUN_INTERPRETED(fun)); + inner = fun->u.i.script; + + /* + * All allocation when decompiling is LIFO, using malloc + * or, more commonly, arena-alloocating from cx->tempPool. + * After InitSprintStack succeeds, we must release to mark + * before returning. + */ + mark = JS_ARENA_MARK(&cx->tempPool); + if (!InitSprintStack(cx, &ss2, jp, inner->depth)) + return NULL; + ss2.inGenExp = JS_TRUE; + + /* + * Recursively decompile this generator function as an + * un-parenthesized generator expression. The ss->inGenExp + * special case of JSOP_YIELD shares array comprehension + * decompilation code that leaves the result as the single + * string pushed on ss2. + */ + outer = jp->script; + LOCAL_ASSERT(JS_UPTRDIFF(pc, outer->code) <= outer->length); + jp->script = inner; + if (!Decompile(&ss2, inner->code, inner->length)) { + JS_ARENA_RELEASE(&cx->tempPool, mark); + return NULL; + } + jp->script = outer; + + /* + * Advance over this op and its null |this| push, and + * arrange to advance over the call to this lambda. + */ + pc2 = pc; + pc += len; + LOCAL_ASSERT(*pc == JSOP_NULL); + pc += JSOP_NULL_LENGTH; + LOCAL_ASSERT(*pc == JSOP_CALL); + LOCAL_ASSERT(GET_ARGC(pc) == 0); + len = JSOP_CALL_LENGTH; + + /* + * Arrange to parenthesize this genexp unless: + * + * 1. It is consumed by a control flow bytecode such + * as JSOP_TABLESWITCH (the syntax from which such ops + * come always parenthesizes the controlling expression). + * 2. It is the sole argument to a function call. + * 3. It is the condition of an if statement and not of a + * ?: expression. + * + * But always parenthesize if this genexp is an operand in + * a comma expression (i.e. if JSOP_ANONFUNOBJ is preceded + * immediately by JSOP_POP with SRC_PCDELTA). + */ + LOCAL_ASSERT(ss2.top == 1); + ss2.opcodes[0] = JSOP_POP; + op = (JSOp) pc[len]; + op = (((js_CodeSpec[op].format & JOF_PARENHEAD) || + ((js_CodeSpec[op].format & JOF_INVOKE) && + GET_ARGC(pc + len) == 1) || + (((op == JSOP_IFEQ || op == JSOP_IFEQX) && + (sn2 = js_GetSrcNote(outer, pc + len)) && + SN_TYPE(sn2) != SRC_COND))) && + !(pc2 > outer->main && + pc2[-1] == JSOP_POP && + (sn2 = js_GetSrcNote(outer, pc2 - 1)) && + SN_TYPE(sn2) == SRC_PCDELTA)) + ? JSOP_POP + : JSOP_SETNAME; + + /* + * Alas, we have to malloc a copy of the result left on + * the top of ss2 because both ss and ss2 arena-allocate + * from cx's tempPool. + */ + rval = JS_strdup(cx, PopStr(&ss2, op)); + JS_ARENA_RELEASE(&cx->tempPool, mark); + if (!rval) + return NULL; + todo = SprintCString(&ss->sprinter, rval); + JS_free(cx, (void *)rval); + break; + } + /* FALL THROUGH */ + case JSOP_OBJECT: case JSOP_REGEXP: - case JSOP_ANONFUNOBJ: case JSOP_NAMEDFUNOBJ: LOAD_ATOM(0); if (op == JSOP_OBJECT || op == JSOP_REGEXP) { @@ -4029,6 +4212,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) break; case JSOP_INITELEM: + op = JSOP_SETNAME; /* turn off most parens */ rval = POP_STR(); xval = POP_STR(); lval = POP_STR(); @@ -4272,28 +4456,6 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) return pc; } -static JSBool -InitSprintStack(JSContext *cx, SprintStack *ss, JSPrinter *jp, uintN depth) -{ - size_t offsetsz, opcodesz; - void *space; - - INIT_SPRINTER(cx, &ss->sprinter, &cx->tempPool, PAREN_SLOP); - - /* Allocate the parallel (to avoid padding) offset and opcode stacks. */ - offsetsz = depth * sizeof(ptrdiff_t); - opcodesz = depth * sizeof(jsbytecode); - JS_ARENA_ALLOCATE(space, &cx->tempPool, offsetsz + opcodesz); - if (!space) - return JS_FALSE; - ss->offsets = (ptrdiff_t *) space; - ss->opcodes = (jsbytecode *) ((char *)space + offsetsz); - - ss->top = ss->inArrayInit = 0; - ss->printer = jp; - return JS_TRUE; -} - JSBool js_DecompileCode(JSPrinter *jp, JSScript *script, jsbytecode *pc, uintN len, uintN pcdepth) diff --git a/js/src/jsopcode.h b/js/src/jsopcode.h index e64288ee8e69..858b02bfdd60 100644 --- a/js/src/jsopcode.h +++ b/js/src/jsopcode.h @@ -110,6 +110,9 @@ typedef enum JSOpLength { #define JOF_DECLARING 0x20000 /* var, const, or function declaration op */ #define JOF_ATOMBASE 0x40000 /* atom segment base setting prefix op */ #define JOF_CALLOP 0x80000 /* call operation pushing function and this */ +#define JOF_PARENHEAD 0x100000 /* opcode consumes value of expression in + parenthesized statement head */ +#define JOF_INVOKE 0x200000 /* JSOP_CALL, JSOP_NEW, JSOP_EVAL */ #define JOF_TYPE_IS_EXTENDED_JUMP(t) \ ((unsigned)((t) - JOF_JUMPX) <= (unsigned)(JOF_LOOKUPSWITCHX - JOF_JUMPX)) diff --git a/js/src/jsopcode.tbl b/js/src/jsopcode.tbl index af9d8d852b57..7c95f1a0f0b9 100644 --- a/js/src/jsopcode.tbl +++ b/js/src/jsopcode.tbl @@ -79,7 +79,7 @@ * 13 +, -, etc. JSOP_ADD, JSOP_SUB, etc. * 14 *, /, % JSOP_MUL, JSOP_DIV, JSOP_MOD * 15 !, ~, etc. JSOP_NOT, JSOP_BITNOT, etc. - * 16 0, function(){} etc. JSOP_ZERO, JSOP_ANONFUNOBJ, etc. + * 16 3.14, 0, etc. JSOP_NUMBER, JSOP_ZERO, etc. * 17 delete, new JSOP_DEL*, JSOP_NEW * 18 x.y, f(), etc. JSOP_GETPROP, JSOP_CALL, etc. * 19 x, null, etc. JSOP_NAME, JSOP_NULL, etc. @@ -100,12 +100,12 @@ OPDEF(JSOP_NOP, 0, "nop", NULL, 1, 0, 0, 0, JOF_BYTE) OPDEF(JSOP_PUSH, 1, "push", NULL, 1, 0, 1, 0, JOF_BYTE) OPDEF(JSOP_POPV, 2, "popv", NULL, 1, 1, 0, 2, JOF_BYTE) -OPDEF(JSOP_ENTERWITH, 3, "enterwith", NULL, 1, 1, 1, 0, JOF_BYTE) +OPDEF(JSOP_ENTERWITH, 3, "enterwith", NULL, 1, 1, 1, 0, JOF_BYTE|JOF_PARENHEAD) OPDEF(JSOP_LEAVEWITH, 4, "leavewith", NULL, 1, 1, 0, 0, JOF_BYTE) OPDEF(JSOP_RETURN, 5, "return", NULL, 1, 1, 0, 0, JOF_BYTE) OPDEF(JSOP_GOTO, 6, "goto", NULL, 3, 0, 0, 0, JOF_JUMP) OPDEF(JSOP_IFEQ, 7, "ifeq", NULL, 3, 1, 0, 4, JOF_JUMP|JOF_DETECTING) -OPDEF(JSOP_IFNE, 8, "ifne", NULL, 3, 1, 0, 0, JOF_JUMP) +OPDEF(JSOP_IFNE, 8, "ifne", NULL, 3, 1, 0, 0, JOF_JUMP|JOF_PARENHEAD) /* Get the arguments object for the current, lightweight function activation. */ OPDEF(JSOP_ARGUMENTS, 9, js_arguments_str, js_arguments_str, 1, 0, 1, 18, JOF_BYTE) @@ -138,7 +138,7 @@ OPDEF(JSOP_MOD, 31, "mod", "%", 1, 2, 1, 14, JOF_BYTE|J OPDEF(JSOP_NOT, 32, "not", "!", 1, 1, 1, 15, JOF_BYTE|JOF_DETECTING) OPDEF(JSOP_BITNOT, 33, "bitnot", "~", 1, 1, 1, 15, JOF_BYTE) OPDEF(JSOP_NEG, 34, "neg", "- ", 1, 1, 1, 15, JOF_BYTE) -OPDEF(JSOP_NEW, 35, js_new_str, NULL, 3, -1, 1, 17, JOF_UINT16) +OPDEF(JSOP_NEW, 35, js_new_str, NULL, 3, -1, 1, 17, JOF_UINT16|JOF_INVOKE) OPDEF(JSOP_DELNAME, 36, "delname", NULL, 3, 0, 1, 17, JOF_CONST|JOF_NAME|JOF_DEL) OPDEF(JSOP_DELPROP, 37, "delprop", NULL, 3, 1, 1, 17, JOF_CONST|JOF_PROP|JOF_DEL) OPDEF(JSOP_DELELEM, 38, "delelem", NULL, 1, 2, 1, 17, JOF_BYTE |JOF_ELEM|JOF_DEL) @@ -161,7 +161,7 @@ OPDEF(JSOP_SETPROP, 54, "setprop", NULL, 3, 2, 1, 3, JOF_CONST| OPDEF(JSOP_GETELEM, 55, "getelem", NULL, 1, 2, 1, 18, JOF_BYTE |JOF_ELEM|JOF_LEFTASSOC) OPDEF(JSOP_SETELEM, 56, "setelem", NULL, 1, 3, 1, 3, JOF_BYTE |JOF_ELEM|JOF_SET|JOF_ASSIGNING|JOF_DETECTING) OPDEF(JSOP_CALLNAME, 57, "callname", NULL, 3, 0, 2, 19, JOF_CONST|JOF_NAME|JOF_CALLOP) -OPDEF(JSOP_CALL, 58, "call", NULL, 3, -1, 1, 18, JOF_UINT16) +OPDEF(JSOP_CALL, 58, "call", NULL, 3, -1, 1, 18, JOF_UINT16|JOF_INVOKE) OPDEF(JSOP_NAME, 59, "name", NULL, 3, 0, 1, 19, JOF_CONST|JOF_NAME) OPDEF(JSOP_NUMBER, 60, "number", NULL, 3, 0, 1, 16, JOF_CONST) OPDEF(JSOP_STRING, 61, "string", NULL, 3, 0, 1, 19, JOF_CONST) @@ -175,8 +175,8 @@ OPDEF(JSOP_OR, 68, "or", NULL, 3, 1, 0, 5, JOF_JUMP|J OPDEF(JSOP_AND, 69, "and", NULL, 3, 1, 0, 6, JOF_JUMP|JOF_DETECTING) /* The switch bytecodes have variable length. */ -OPDEF(JSOP_TABLESWITCH, 70, "tableswitch", NULL, -1, 1, 0, 0, JOF_TABLESWITCH|JOF_DETECTING) -OPDEF(JSOP_LOOKUPSWITCH, 71, "lookupswitch", NULL, -1, 1, 0, 0, JOF_LOOKUPSWITCH|JOF_DETECTING) +OPDEF(JSOP_TABLESWITCH, 70, "tableswitch", NULL, -1, 1, 0, 0, JOF_TABLESWITCH|JOF_DETECTING|JOF_PARENHEAD) +OPDEF(JSOP_LOOKUPSWITCH, 71, "lookupswitch", NULL, -1, 1, 0, 0, JOF_LOOKUPSWITCH|JOF_DETECTING|JOF_PARENHEAD) /* New, infallible/transitive identity ops. */ OPDEF(JSOP_STRICTEQ, 72, "stricteq", NULL, 1, 2, 1, 10, JOF_BYTE|JOF_DETECTING) @@ -269,14 +269,14 @@ OPDEF(JSOP_SETSP, 117,"setsp", NULL, 3, 0, 0, 0, JOF_UINT16 * CONDSWITCH is a decompilable NOP; CASE is ===, POP, jump if true, re-push * lval if false; and DEFAULT is POP lval and GOTO. */ -OPDEF(JSOP_CONDSWITCH,118,"condswitch", NULL, 1, 0, 0, 0, JOF_BYTE) +OPDEF(JSOP_CONDSWITCH,118,"condswitch", NULL, 1, 0, 0, 0, JOF_BYTE|JOF_PARENHEAD) OPDEF(JSOP_CASE, 119,"case", NULL, 3, 1, 0, 0, JOF_JUMP) OPDEF(JSOP_DEFAULT, 120,"default", NULL, 3, 1, 0, 0, JOF_JUMP) /* * ECMA-compliant call to eval op */ -OPDEF(JSOP_EVAL, 121,"eval", NULL, 3, -1, 1, 18, JOF_UINT16) +OPDEF(JSOP_EVAL, 121,"eval", NULL, 3, -1, 1, 18, JOF_UINT16|JOF_INVOKE) /* * ECMA-compliant helper for 'for (x[i] in o)' loops. @@ -299,10 +299,10 @@ OPDEF(JSOP_DEFCONST, 126,"defconst", NULL, 3, 0, 0, 0, JOF_CONST| OPDEF(JSOP_DEFVAR, 127,"defvar", NULL, 3, 0, 0, 0, JOF_CONST|JOF_DECLARING) /* Auto-clone (if needed due to re-parenting) and push an anonymous function. */ -OPDEF(JSOP_ANONFUNOBJ, 128, "anonfunobj", NULL, 3, 0, 1, 16, JOF_CONST) +OPDEF(JSOP_ANONFUNOBJ, 128, "anonfunobj", NULL, 3, 0, 1, 19, JOF_CONST) /* ECMA ed. 3 named function expression. */ -OPDEF(JSOP_NAMEDFUNOBJ, 129, "namedfunobj", NULL, 3, 0, 1, 16, JOF_CONST) +OPDEF(JSOP_NAMEDFUNOBJ, 129, "namedfunobj", NULL, 3, 0, 1, 19, JOF_CONST) /* * Like JSOP_SETLOCAL, but specialized to avoid requiring JSOP_POP immediately @@ -345,15 +345,15 @@ OPDEF(JSOP_DEFLOCALFUN, 138,"deflocalfun",NULL, 5, 0, 0, 0, JOF_INDEXC /* Extended jumps. */ OPDEF(JSOP_GOTOX, 139,"gotox", NULL, 5, 0, 0, 0, JOF_JUMPX) -OPDEF(JSOP_IFEQX, 140,"ifeqx", NULL, 5, 1, 0, 3, JOF_JUMPX|JOF_DETECTING) -OPDEF(JSOP_IFNEX, 141,"ifnex", NULL, 5, 1, 0, 0, JOF_JUMPX) +OPDEF(JSOP_IFEQX, 140,"ifeqx", NULL, 5, 1, 0, 4, JOF_JUMPX|JOF_DETECTING) +OPDEF(JSOP_IFNEX, 141,"ifnex", NULL, 5, 1, 0, 0, JOF_JUMPX|JOF_PARENHEAD) OPDEF(JSOP_ORX, 142,"orx", NULL, 5, 1, 0, 5, JOF_JUMPX|JOF_DETECTING) OPDEF(JSOP_ANDX, 143,"andx", NULL, 5, 1, 0, 6, JOF_JUMPX|JOF_DETECTING) OPDEF(JSOP_GOSUBX, 144,"gosubx", NULL, 5, 0, 0, 0, JOF_JUMPX) OPDEF(JSOP_CASEX, 145,"casex", NULL, 5, 1, 0, 0, JOF_JUMPX) OPDEF(JSOP_DEFAULTX, 146,"defaultx", NULL, 5, 1, 0, 0, JOF_JUMPX) -OPDEF(JSOP_TABLESWITCHX, 147,"tableswitchx",NULL, -1, 1, 0, 0, JOF_TABLESWITCHX|JOF_DETECTING) -OPDEF(JSOP_LOOKUPSWITCHX, 148,"lookupswitchx",NULL, -1, 1, 0, 0, JOF_LOOKUPSWITCHX|JOF_DETECTING) +OPDEF(JSOP_TABLESWITCHX, 147,"tableswitchx",NULL, -1, 1, 0, 0, JOF_TABLESWITCHX|JOF_DETECTING|JOF_PARENHEAD) +OPDEF(JSOP_LOOKUPSWITCHX, 148,"lookupswitchx",NULL, -1, 1, 0, 0, JOF_LOOKUPSWITCHX|JOF_DETECTING|JOF_PARENHEAD) /* Placeholders for a real jump opcode set during backpatch chain fixup. */ OPDEF(JSOP_BACKPATCH, 149,"backpatch",NULL, 3, 0, 0, 0, JOF_JUMP|JOF_BACKPATCH) diff --git a/js/src/jsparse.c b/js/src/jsparse.c index f0510bac79d7..4ee0475a41b8 100644 --- a/js/src/jsparse.c +++ b/js/src/jsparse.c @@ -102,6 +102,10 @@ typedef JSParseNode * JSPrimaryParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, JSTokenType tt, JSBool afterDot); +typedef JSParseNode * +JSParenParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, + JSParseNode *pn1, JSBool *genexp); + static JSParser FunctionStmt; static JSParser FunctionExpr; static JSParser Statements; @@ -121,8 +125,9 @@ static JSParser ShiftExpr; static JSParser AddExpr; static JSParser MulExpr; static JSParser UnaryExpr; -static JSMemberParser MemberExpr; +static JSMemberParser MemberExpr; static JSPrimaryParser PrimaryExpr; +static JSParenParser ParenExpr; /* * Insist that the next token be of type tt, or report errno and return null. @@ -132,7 +137,7 @@ static JSPrimaryParser PrimaryExpr; JS_BEGIN_MACRO \ if (js_GetToken(cx, ts) != tt) { \ js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR, \ - errno); \ + errno); \ return NULL; \ } \ JS_END_MACRO @@ -224,6 +229,8 @@ NewOrRecycledNode(JSContext *cx, JSTreeContext *tc) maxparsenodes = parsenodes - recyclednodes; } #endif + memset(&pn->pn_u, 0, sizeof pn->pn_u); + pn->pn_next = NULL; return pn; } @@ -245,9 +252,7 @@ NewParseNode(JSContext *cx, JSTokenStream *ts, JSParseNodeArity arity, pn->pn_pos = tp->pos; pn->pn_op = JSOP_NOP; pn->pn_arity = arity; - pn->pn_next = NULL; pn->pn_ts = ts; - pn->pn_source = NULL; return pn; } @@ -321,9 +326,7 @@ NewBinary(JSContext *cx, JSTokenType tt, pn->pn_arity = PN_BINARY; pn->pn_left = left; pn->pn_right = right; - pn->pn_next = NULL; - pn->pn_ts = NULL; - pn->pn_source = NULL; + pn->pn_ts = left->pn_ts; return pn; } @@ -1277,9 +1280,7 @@ FunctionDef(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, rhs->pn_type = TOK_NAME; rhs->pn_op = JSOP_GETARG; rhs->pn_atom = cx->runtime->atomState.emptyAtom; - rhs->pn_expr = NULL; rhs->pn_slot = slot; - rhs->pn_attrs = 0; item = NewBinary(cx, TOK_ASSIGN, JSOP_NOP, lhs, rhs, tc); if (!item) @@ -1542,7 +1543,7 @@ Condition(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) JSParseNode *pn; MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND); - pn = Expr(cx, ts, tc); + pn = ParenExpr(cx, ts, tc, NULL, NULL); if (!pn) return NULL; MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND); @@ -1599,9 +1600,7 @@ ImportExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) 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) { @@ -1616,15 +1615,12 @@ ImportExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) ts->flags |= TSF_KEYWORD_IS_NAME; if (js_MatchToken(cx, ts, TOK_STAR)) { pn2->pn_op = JSOP_IMPORTALL; - pn2->pn_atom = NULL; pn2->pn_slot = -1; - pn2->pn_attrs = 0; } 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; } ts->flags &= ~TSF_KEYWORD_IS_NAME; pn2->pn_expr = pn; @@ -1907,6 +1903,22 @@ BindDestructuringVar(JSContext *cx, BindData *data, JSParseNode *pn, return JS_TRUE; } +static JSBool +MakeSetCall(JSContext *cx, JSParseNode *pn, uintN msg) +{ + JSParseNode *pn2; + + JS_ASSERT(pn->pn_arity == PN_LIST); + JS_ASSERT(pn->pn_op == JSOP_CALL || pn->pn_op == JSOP_EVAL); + pn2 = pn->pn_head; + if (pn2->pn_type == TOK_FUNCTION && (pn2->pn_flags & TCF_GENEXP_LAMBDA)) { + js_ReportCompileErrorNumber(cx, pn, JSREPORT_PN | JSREPORT_ERROR, msg); + return JS_FALSE; + } + pn->pn_op = JSOP_SETCALL; + return JS_TRUE; +} + /* * Here, we are destructuring {... P: Q, ...} = R, where P is any id, Q is any * LHS expression except a destructuring initialiser, and R is on the stack. @@ -1943,8 +1955,8 @@ BindDestructuringLHS(JSContext *cx, JSParseNode *pn, JSTreeContext *tc) #if JS_HAS_LVALUE_RETURN case TOK_LP: - JS_ASSERT(pn->pn_op == JSOP_CALL || pn->pn_op == JSOP_EVAL); - pn->pn_op = JSOP_SETCALL; + if (!MakeSetCall(cx, pn, JSMSG_BAD_LEFTSIDE_OF_ASS)) + return JS_FALSE; break; #endif @@ -2300,13 +2312,9 @@ ReturnOrYield(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, JSParseNode *pn, *pn2; tt = CURRENT_TOKEN(ts).type; - if (!(tc->flags & TCF_IN_FUNCTION)) { + if (tt == TOK_RETURN && !(tc->flags & TCF_IN_FUNCTION)) { js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR, - JSMSG_BAD_RETURN_OR_YIELD, -#if JS_HAS_GENERATORS - (tt == TOK_YIELD) ? js_yield_str : -#endif - js_return_str); + JSMSG_BAD_RETURN_OR_YIELD, js_return_str); return NULL; } @@ -2346,7 +2354,6 @@ ReturnOrYield(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, if (tt == TOK_RETURN) #endif tc->flags |= TCF_RETURN_VOID; - pn->pn_kid = NULL; } if ((~tc->flags & (TCF_RETURN_EXPR | TCF_FUN_IS_GENERATOR)) == 0) { @@ -2392,9 +2399,7 @@ PushLexicalScope(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, pn->pn_type = TOK_LEXICALSCOPE; pn->pn_op = JSOP_LEAVEBLOCK; pn->pn_atom = atom; - pn->pn_expr = NULL; pn->pn_slot = -1; - pn->pn_attrs = 0; return pn; } @@ -2511,9 +2516,7 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) 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)); } @@ -2589,7 +2592,7 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_SWITCH); /* pn1 points to the switch's discriminant. */ - pn1 = Expr(cx, ts, tc); + pn1 = ParenExpr(cx, ts, tc, NULL, NULL); if (!pn1) return NULL; @@ -2616,15 +2619,13 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) return NULL; } seenDefault = JS_TRUE; - /* fall through */ + /* 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 { + if (tt == TOK_CASE) { pn3->pn_left = Expr(cx, ts, tc); if (!pn3->pn_left) return NULL; @@ -2872,8 +2873,10 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) } else { pn2 = pn1; #if JS_HAS_LVALUE_RETURN - if (pn2->pn_type == TOK_LP) - pn2->pn_op = JSOP_SETCALL; + if (pn2->pn_type == TOK_LP && + !MakeSetCall(cx, pn2, JSMSG_BAD_LEFTSIDE_OF_ASS)) { + return NULL; + } #endif #if JS_HAS_XML_SUPPORT if (pn2->pn_type == TOK_UNARYOP) @@ -3094,9 +3097,6 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) if (!pn3) return NULL; pn3->pn_atom = label; - pn3->pn_expr = NULL; - pn3->pn_slot = 0; - pn3->pn_attrs = 0; break; default: @@ -3107,7 +3107,6 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) } pn2->pn_kid1 = pn3; - pn2->pn_kid2 = NULL; #if JS_HAS_CATCH_GUARD /* * We use 'catch (x if x === 5)' (not 'catch (x : x === 5)') @@ -3149,7 +3148,6 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) js_PopStatement(tc); } else { js_UngetToken(ts); - pn->pn_kid3 = NULL; } if (!catchList && !pn->pn_kid3) { js_ReportCompileErrorNumber(cx, ts, JSREPORT_TS | JSREPORT_ERROR, @@ -3283,7 +3281,7 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) if (!pn) return NULL; MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH); - pn2 = Expr(cx, ts, tc); + pn2 = ParenExpr(cx, ts, tc, NULL, NULL); if (!pn2) return NULL; MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_WITH); @@ -3419,7 +3417,6 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) pn1->pn_atom = atom; pn1->pn_expr = tc->blockNode; pn1->pn_slot = -1; - pn1->pn_attrs = 0; tc->blockNode = pn1; } @@ -3469,7 +3466,6 @@ Statement(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) if (!pn) return NULL; pn->pn_type = TOK_SEMI; - pn->pn_kid = NULL; return pn; #if JS_HAS_DEBUGGER_KEYWORD @@ -3712,9 +3708,9 @@ Variables(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) return NULL; pn2->pn_op = JSOP_NAME; pn2->pn_atom = atom; - pn2->pn_expr = NULL; pn2->pn_slot = -1; - pn2->pn_attrs = let ? 0 : data.u.var.attrs; + if (!let) + pn2->pn_attrs = data.u.var.attrs; PN_APPEND(pn, pn2); if (js_MatchToken(cx, ts, TOK_ASSIGN)) { @@ -3760,7 +3756,8 @@ Expr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) if (pn2->pn_type == TOK_YIELD) { js_ReportCompileErrorNumber(cx, pn2, JSREPORT_PN | JSREPORT_ERROR, - JSMSG_BAD_YIELD_SYNTAX); + JSMSG_BAD_GENERATOR_SYNTAX, + js_yield_str); return NULL; } #endif @@ -3839,8 +3836,8 @@ AssignExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) #endif #if JS_HAS_LVALUE_RETURN case TOK_LP: - JS_ASSERT(pn2->pn_op == JSOP_CALL || pn2->pn_op == JSOP_EVAL); - pn2->pn_op = JSOP_SETCALL; + if (!MakeSetCall(cx, pn2, JSMSG_BAD_LEFTSIDE_OF_ASS)) + return NULL; break; #endif #if JS_HAS_XML_SUPPORT @@ -4106,8 +4103,8 @@ SetIncOpKid(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, #if JS_HAS_LVALUE_RETURN case TOK_LP: - JS_ASSERT(kid->pn_op == JSOP_CALL || kid->pn_op == JSOP_EVAL); - kid->pn_op = JSOP_SETCALL; + if (!MakeSetCall(cx, kid, JSMSG_BAD_INCOP_OPERAND)) + return JS_FALSE; /* FALL THROUGH */ #endif #if JS_HAS_XML_SUPPORT @@ -4186,6 +4183,11 @@ UnaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) */ while (pn2->pn_type == TOK_RP) pn2 = pn2->pn_kid; + if (pn2->pn_type == TOK_LP && + pn2->pn_op != JSOP_SETCALL && + !MakeSetCall(cx, pn2, JSMSG_BAD_DELETE_OPERAND)) { + return NULL; + } pn->pn_kid = pn2; break; @@ -4219,6 +4221,244 @@ UnaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) return pn; } +#if JS_HAS_GENERATORS + +/* + * Starting from a |for| keyword after the first array initialiser element or + * an expression in an open parenthesis, parse the tail of the comprehension + * or generator expression signified by this |for| keyword in context. + * + * Return null on failure, else return the top-most parse node for the array + * comprehension or generator expression, with a unary node as the body of the + * (possibly nested) for-loop, initialized by |type, op, kid|. + */ +static JSParseNode * +ComprehensionTail(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, + JSTokenType type, JSOp op, JSParseNode *kid) +{ + JSParseNode *pn, *pn2, *pn3, **pnp; + JSStmtInfo stmtInfo; + BindData data; + JSRuntime *rt; + JSTokenType tt; + JSAtom *atom; + + JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_FOR); + + /* + * Make a parse-node and literal object representing the block scope of + * this array comprehension or generator expression. + */ + pn = PushLexicalScope(cx, ts, tc, &stmtInfo); + if (!pn) + return NULL; + pnp = &pn->pn_expr; + + data.pn = NULL; + data.ts = ts; + data.obj = tc->blockChain; + data.op = JSOP_NOP; + data.binder = BindLet; + data.u.let.index = 0; + data.u.let.overflow = JSMSG_ARRAY_INIT_TOO_BIG; + + rt = cx->runtime; + do { + /* + * FOR node is binary, left is loop control and right is body. Use + * index to count each block-local let-variable on the left-hand side + * of the IN. + */ + pn2 = NewParseNode(cx, ts, PN_BINARY, tc); + if (!pn2) + return NULL; + + pn2->pn_op = JSOP_FORIN; + if (js_MatchToken(cx, ts, TOK_NAME)) { + if (CURRENT_TOKEN(ts).t_atom == rt->atomState.eachAtom) + pn2->pn_op = JSOP_FOREACH; + else + js_UngetToken(ts); + } + MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); + + tt = js_GetToken(cx, ts); + switch (tt) { +#if JS_HAS_DESTRUCTURING + case TOK_LB: + case TOK_LC: + pn3 = DestructuringExpr(cx, &data, tc, tt); + if (!pn3) + return NULL; + + if (pn3->pn_type != TOK_RB || pn3->pn_count != 2) { + js_ReportCompileErrorNumber(cx, ts, + JSREPORT_TS | + JSREPORT_ERROR, + JSMSG_BAD_FOR_LEFTSIDE); + return NULL; + } + + /* Destructuring requires [key, value] enumeration. */ + if (pn2->pn_op != JSOP_FOREACH) + pn2->pn_op = JSOP_FOREACHKEYVAL; + break; +#endif + + case TOK_NAME: + atom = CURRENT_TOKEN(ts).t_atom; + if (!data.binder(cx, &data, atom, tc)) + return NULL; + + /* + * Create a name node with pn_op JSOP_NAME. We can't set pn_op to + * JSOP_GETLOCAL here, because we don't yet know the block's depth + * in the operand stack frame. The code generator computes that, + * and it tries to bind all names to slots, so we must let it do + * the deed. + */ + pn3 = NewParseNode(cx, ts, PN_NAME, tc); + if (!pn3) + return NULL; + pn3->pn_op = JSOP_NAME; + pn3->pn_atom = atom; + pn3->pn_slot = -1; + break; + + default: + js_ReportCompileErrorNumber(cx, ts, + JSREPORT_TS|JSREPORT_ERROR, + JSMSG_NO_VARIABLE_NAME); + return NULL; + } + + MUST_MATCH_TOKEN(TOK_IN, JSMSG_IN_AFTER_FOR_NAME); + pn3 = NewBinary(cx, TOK_IN, JSOP_NOP, pn3, + Expr(cx, ts, tc), tc); + if (!pn3) + return NULL; + + MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); + pn2->pn_left = pn3; + *pnp = pn2; + pnp = &pn2->pn_right; + } while (js_MatchToken(cx, ts, TOK_FOR)); + + if (js_MatchToken(cx, ts, TOK_IF)) { + pn2 = NewParseNode(cx, ts, PN_TERNARY, tc); + if (!pn2) + return NULL; + pn2->pn_kid1 = Condition(cx, ts, tc); + if (!pn2->pn_kid1) + return NULL; + *pnp = pn2; + pnp = &pn2->pn_kid2; + } + + pn2 = NewParseNode(cx, ts, PN_UNARY, tc); + if (!pn2) + return NULL; + pn2->pn_type = type; + pn2->pn_op = op; + pn2->pn_kid = kid; + *pnp = pn2; + + js_PopStatement(tc); + return pn; +} + +#if JS_HAS_GENERATOR_EXPRS + +/* + * Starting from a |for| keyword after an expression, parse the comprehension + * tail completing this generator expression. Wrap the expression at kid in a + * generator function that is immediately called to evaluate to the generator + * iterator that is the value of this generator expression. + * + * Callers pass a blank unary node via pn, which GeneratorExpr fills in as the + * yield expression, which ComprehensionTail in turn wraps in a TOK_SEMI-type + * expression-statement node that constitutes the body of the |for| loop(s) in + * the generator function. + * + * Note how unlike Python, we do not evaluate the expression to the right of + * the first |in| in the chain of |for| heads. Instead, a generator expression + * is merely sugar for a generator function expression and its application. + */ +static JSParseNode * +GeneratorExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, + uintN oldflags, JSParseNode *pn, JSParseNode *kid) +{ + JSParseNode *body, *lambda; + JSFunction *fun; + + /* Initialize pn, connecting it to kid. */ + JS_ASSERT(pn->pn_arity == PN_UNARY); + pn->pn_type = TOK_YIELD; + pn->pn_op = JSOP_YIELD; + pn->pn_pos = kid->pn_pos; + pn->pn_kid = kid; + pn->pn_hidden = JS_TRUE; + + /* + * Parse the comprehension tail at hand, making pn the kid of the loop + * body's expression statement. + */ + body = ComprehensionTail(cx, ts, tc, TOK_SEMI, JSOP_NOP, pn); + if (!body) + return NULL; + body->pn_pos.begin = kid->pn_pos.begin; + + /* + * Make the generator function and flag it as interpreted ASAP (see the + * comment in FunctionBody). + */ + fun = js_NewFunction(cx, NULL, NULL, 0, JSFUN_LAMBDA, cx->fp->varobj, + NULL); + if (!fun) + return NULL; + fun->flags |= JSFUN_INTERPRETED; + + /* + * This generator function is referenced by an anonymous function object + * node. Here is where we must take care to propagate certain tc->flags + * that may have changed from oldflags to reflect crucial facts about the + * expression on the left of |for| and in the comprehension tail after it. + */ + lambda = NewParseNode(cx, ts, PN_FUNC, tc); + if (!lambda) + return NULL; + lambda->pn_type = TOK_FUNCTION; + lambda->pn_op = JSOP_ANONFUNOBJ; + lambda->pn_pos.begin = body->pn_pos.begin; + lambda->pn_funAtom = js_AtomizeObject(cx, fun->object, 0); + if (!lambda->pn_funAtom) + return NULL; + lambda->pn_body = body; + lambda->pn_flags = TCF_FUN_IS_GENERATOR | TCF_GENEXP_LAMBDA | + ((oldflags ^ tc->flags) & TCF_FUN_FLAGS); + + /* + * Re-use pn to name the result node, a call expression invoking the + * anonymous generator function object. + */ + pn = NewParseNode(cx, ts, PN_LIST, tc); + if (!pn) + return NULL; + pn->pn_type = TOK_LP; + pn->pn_op = JSOP_CALL; + pn->pn_pos.begin = lambda->pn_pos.begin; + PN_INIT_LIST_1(pn, lambda); + + body->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + tc->flags = oldflags; + return pn; +} + +static const char js_generator_str[] = "generator"; + +#endif /* JS_HAS_GENERATOR_EXPRS */ +#endif /* JS_HAS_GENERATORS */ + static JSBool ArgumentList(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, JSParseNode *listNode) @@ -4230,6 +4470,9 @@ ArgumentList(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, ts->flags &= ~TSF_OPERAND; if (!matched) { do { +#if JS_HAS_GENERATOR_EXPRS + uintN oldflags = tc->flags; +#endif JSParseNode *argNode = AssignExpr(cx, ts, tc); if (!argNode) return JS_FALSE; @@ -4237,9 +4480,28 @@ ArgumentList(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, if (argNode->pn_type == TOK_YIELD) { js_ReportCompileErrorNumber(cx, argNode, JSREPORT_PN | JSREPORT_ERROR, - JSMSG_BAD_YIELD_SYNTAX); + JSMSG_BAD_GENERATOR_SYNTAX, + js_yield_str); return JS_FALSE; } +#endif +#if JS_HAS_GENERATOR_EXPRS + if (js_MatchToken(cx, ts, TOK_FOR)) { + JSParseNode *pn = NewParseNode(cx, ts, PN_UNARY, tc); + if (!pn) + return JS_FALSE; + argNode = GeneratorExpr(cx, ts, tc, oldflags, pn, argNode); + if (!argNode) + return JS_FALSE; + if (listNode->pn_count > 1 || + js_PeekToken(cx, ts) == TOK_COMMA) { + js_ReportCompileErrorNumber(cx, argNode, + JSREPORT_PN | JSREPORT_ERROR, + JSMSG_BAD_GENERATOR_SYNTAX, + js_generator_str); + return JS_FALSE; + } + } #endif PN_APPEND(listNode, argNode); } while (js_MatchToken(cx, ts, TOK_COMMA)); @@ -4301,9 +4563,7 @@ MemberExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, pn2->pn_op = JSOP_XMLNAME; pn2->pn_arity = PN_UNARY; pn2->pn_kid = pn; - pn2->pn_next = NULL; pn2->pn_ts = ts; - pn2->pn_source = NULL; pn = pn2; } } @@ -4314,7 +4574,6 @@ MemberExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, if (!pn2) return NULL; pn2->pn_slot = -1; - pn2->pn_attrs = 0; #if JS_HAS_XML_SUPPORT ts->flags |= TSF_OPERAND | TSF_KEYWORD_IS_NAME; tt = js_GetToken(cx, ts); @@ -4553,9 +4812,7 @@ PropertySelector(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc) 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; } @@ -4587,7 +4844,6 @@ QualifiedSuffix(JSContext *cx, JSTokenStream *ts, JSParseNode *pn, : CURRENT_TOKEN(ts).t_atom; pn2->pn_expr = pn; pn2->pn_slot = -1; - pn2->pn_attrs = 0; return pn2; } @@ -5289,11 +5545,7 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, if (index == 0 && pn->pn_count != 0 && js_MatchToken(cx, ts, TOK_FOR)) { - JSParseNode **pnp, *pnexp, *pntop, *pnlet; - BindData data; - JSRuntime *rt; - JSStmtInfo stmtInfo; - JSAtom *atom; + JSParseNode *pnexp, *pntop; /* Relabel pn as an array comprehension node. */ pn->pn_type = TOK_ARRAYCOMP; @@ -5310,130 +5562,11 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, : &pn->pn_head; *pn->pn_tail = NULL; - /* - * Make a parse-node and literal object representing the array - * comprehension's block scope. - */ - pntop = PushLexicalScope(cx, ts, tc, &stmtInfo); + pntop = ComprehensionTail(cx, ts, tc, TOK_ARRAYPUSH, + JSOP_ARRAYPUSH, pnexp); if (!pntop) return NULL; - pnp = &pntop->pn_expr; - - data.pn = NULL; - data.ts = ts; - data.obj = tc->blockChain; - data.op = JSOP_NOP; - data.binder = BindLet; - data.u.let.index = 0; - data.u.let.overflow = JSMSG_ARRAY_INIT_TOO_BIG; - - rt = cx->runtime; - do { - /* - * FOR node is binary, left is control and right is body. - * Use index to count each block-local let-variable on the - * left-hand side of IN. - */ - pn2 = NewParseNode(cx, ts, PN_BINARY, tc); - if (!pn2) - return NULL; - - pn2->pn_op = JSOP_FORIN; - if (js_MatchToken(cx, ts, TOK_NAME)) { - if (CURRENT_TOKEN(ts).t_atom == rt->atomState.eachAtom) - pn2->pn_op = JSOP_FOREACH; - else - js_UngetToken(ts); - } - MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); - - tt = js_GetToken(cx, ts); - switch (tt) { -#if JS_HAS_DESTRUCTURING - case TOK_LB: - case TOK_LC: - pnlet = DestructuringExpr(cx, &data, tc, tt); - if (!pnlet) - return NULL; - - if (pnlet->pn_type != TOK_RB || pnlet->pn_count != 2) { - js_ReportCompileErrorNumber(cx, ts, - JSREPORT_TS | - JSREPORT_ERROR, - JSMSG_BAD_FOR_LEFTSIDE); - return NULL; - } - - /* Destructuring requires [key, value] enumeration. */ - if (pn2->pn_op != JSOP_FOREACH) - pn2->pn_op = JSOP_FOREACHKEYVAL; - break; -#endif - - case TOK_NAME: - atom = CURRENT_TOKEN(ts).t_atom; - if (!data.binder(cx, &data, atom, tc)) - return NULL; - - /* - * Create a name node with op JSOP_NAME. We can't set - * op to JSOP_GETLOCAL here, because we don't yet know - * the block's depth in the operand stack frame. The - * code generator computes that, and it tries to bind - * all names to slots, so we must let it do the deed. - */ - pnlet = NewParseNode(cx, ts, PN_NAME, tc); - if (!pnlet) - return NULL; - pnlet->pn_op = JSOP_NAME; - pnlet->pn_atom = atom; - pnlet->pn_expr = NULL; - pnlet->pn_slot = -1; - pnlet->pn_attrs = 0; - break; - - default: - js_ReportCompileErrorNumber(cx, ts, - JSREPORT_TS|JSREPORT_ERROR, - JSMSG_NO_VARIABLE_NAME); - return NULL; - } - - MUST_MATCH_TOKEN(TOK_IN, JSMSG_IN_AFTER_FOR_NAME); - pn3 = NewBinary(cx, TOK_IN, JSOP_NOP, pnlet, - Expr(cx, ts, tc), tc); - if (!pn3) - return NULL; - - MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL); - pn2->pn_left = pn3; - *pnp = pn2; - pnp = &pn2->pn_right; - } while (js_MatchToken(cx, ts, TOK_FOR)); - - if (js_MatchToken(cx, ts, TOK_IF)) { - pn2 = NewParseNode(cx, ts, PN_TERNARY, tc); - if (!pn2) - return NULL; - pn2->pn_kid1 = Condition(cx, ts, tc); - if (!pn2->pn_kid1) - return NULL; - pn2->pn_kid2 = NULL; - pn2->pn_kid3 = NULL; - *pnp = pn2; - pnp = &pn2->pn_kid2; - } - - pn2 = NewParseNode(cx, ts, PN_UNARY, tc); - if (!pn2) - return NULL; - pn2->pn_type = TOK_ARRAYPUSH; - pn2->pn_op = JSOP_ARRAYPUSH; - pn2->pn_kid = pnexp; - *pnp = pn2; PN_APPEND(pn, pntop); - - js_PopStatement(tc); } #endif /* JS_HAS_GENERATORS */ @@ -5503,9 +5636,7 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, if (!pn3) return NULL; pn3->pn_atom = CURRENT_TOKEN(ts).t_atom; - pn3->pn_expr = NULL; pn3->pn_slot = -1; - pn3->pn_attrs = 0; /* We have to fake a 'function' token here. */ CURRENT_TOKEN(ts).t_op = JSOP_NOP; @@ -5584,7 +5715,6 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, defsharp = NewParseNode(cx, ts, PN_UNARY, tc); if (!defsharp) return NULL; - defsharp->pn_kid = NULL; defsharp->pn_num = (jsint) CURRENT_TOKEN(ts).t_dval; ts->flags |= TSF_OPERAND; tt = js_GetToken(cx, ts); @@ -5602,12 +5732,17 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, #endif /* JS_HAS_SHARP_VARS */ case TOK_LP: + { + JSBool genexp; + pn = NewParseNode(cx, ts, PN_UNARY, tc); if (!pn) return NULL; - pn2 = BracketedExpr(cx, ts, tc); + pn2 = ParenExpr(cx, ts, tc, pn, &genexp); if (!pn2) return NULL; + if (genexp) + return pn2; MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN); if (pn2->pn_type == TOK_RP || @@ -5625,7 +5760,6 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, * will null the |obj| interpreter register, causing |this| in any * call of that member expression to bind to the global object. */ - pn->pn_kid = NULL; RecycleTree(pn, tc); pn = pn2; } else { @@ -5634,6 +5768,7 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, pn->pn_kid = pn2; } break; + } #if JS_HAS_XML_SUPPORT case TOK_STAR: @@ -5683,9 +5818,7 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, 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)) { @@ -5790,6 +5923,67 @@ PrimaryExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, return pn; } +static JSParseNode * +ParenExpr(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, + JSParseNode *pn1, JSBool *genexp) +{ + JSTokenPtr begin; + JSParseNode *pn; +#if JS_HAS_GENERATOR_EXPRS + uintN oldflags = tc->flags; +#endif + + JS_ASSERT(CURRENT_TOKEN(ts).type == TOK_LP); + begin = CURRENT_TOKEN(ts).pos.begin; + + if (genexp) + *genexp = JS_FALSE; + pn = BracketedExpr(cx, ts, tc); + if (!pn) + return NULL; + +#if JS_HAS_GENERATOR_EXPRS + if (js_MatchToken(cx, ts, TOK_FOR)) { + if (pn->pn_type == TOK_YIELD) { + js_ReportCompileErrorNumber(cx, pn, + JSREPORT_PN | JSREPORT_ERROR, + JSMSG_BAD_GENERATOR_SYNTAX, + js_yield_str); + return NULL; + } + if (pn->pn_type == TOK_COMMA) { + js_ReportCompileErrorNumber(cx, PN_LAST(pn), + JSREPORT_PN | JSREPORT_ERROR, + JSMSG_BAD_GENERATOR_SYNTAX, + js_generator_str); + return NULL; + } + if (!pn1) { + pn1 = NewParseNode(cx, ts, PN_UNARY, tc); + if (!pn1) + return NULL; + } + pn->pn_pos.begin = begin; + pn = GeneratorExpr(cx, ts, tc, oldflags, pn1, pn); + if (!pn) + return NULL; + if (genexp) { + if (js_GetToken(cx, ts) != TOK_RP) { + js_ReportCompileErrorNumber(cx, ts, + JSREPORT_TS | JSREPORT_ERROR, + JSMSG_BAD_GENERATOR_SYNTAX, + js_generator_str); + return NULL; + } + pn->pn_pos.end = CURRENT_TOKEN(ts).pos.end; + *genexp = JS_TRUE; + } + } +#endif /* JS_HAS_GENERATOR_EXPRS */ + + return pn; +} + /* * Fold from one constant type to another. * XXX handles only strings and numbers for now @@ -6273,6 +6467,12 @@ js_FoldConstants(JSContext *cx, JSParseNode *pn, JSTreeContext *tc) return JS_TRUE; } +#if JS_HAS_GENERATOR_EXPRS + /* Don't fold a trailing |if (0)| in a generator expression. */ + if (!pn2 && (tc->flags & TCF_GENEXP_LAMBDA)) + break; +#endif + if (pn2) { /* * pn2 is the then- or else-statement subtree to compile. Take diff --git a/js/src/jsparse.h b/js/src/jsparse.h index f30c3377e9c1..80d0d2b16a87 100644 --- a/js/src/jsparse.h +++ b/js/src/jsparse.h @@ -302,6 +302,7 @@ struct JSParseNode { struct { /* one kid if unary */ JSParseNode *kid; jsint num; /* -1 or sharp variable number */ + JSBool hidden; /* hidden genexp-induced JSOP_YIELD */ } unary; struct { /* name, labeled statement, etc. */ JSAtom *atom; /* name or label atom, null if slot */ @@ -317,7 +318,6 @@ struct JSParseNode { } pn_u; JSParseNode *pn_next; /* to align dval and pn_u on RISCs */ JSTokenStream *pn_ts; /* token stream for error reports */ - JSAtom *pn_source; /* saved source for decompilation */ }; #define pn_funAtom pn_u.func.funAtom @@ -336,6 +336,7 @@ struct JSParseNode { #define pn_val pn_u.binary.val #define pn_kid pn_u.unary.kid #define pn_num pn_u.unary.num +#define pn_hidden pn_u.unary.hidden #define pn_atom pn_u.name.atom #define pn_expr pn_u.name.expr #define pn_slot pn_u.name.slot