# HG changeset patch # Parent 0c98dd9fd645d3c76edc881fc8e4050747afa4a3 # User Tom Schuster diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -7228,16 +7228,18 @@ frontend::EmitTree(JSContext *cx, Byteco case PNK_ADD: case PNK_SUB: case PNK_BITOR: case PNK_BITXOR: case PNK_BITAND: case PNK_STRICTEQ: case PNK_EQ: case PNK_STRICTNE: + case PNK_IS: + case PNK_ISNT: case PNK_NE: case PNK_LT: case PNK_LE: case PNK_GT: case PNK_GE: case PNK_IN: case PNK_INSTANCEOF: case PNK_LSH: diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -161,16 +161,20 @@ enum ParseNodeKind { */ /* Equality operators. */ PNK_STRICTEQ, PNK_EQ, PNK_STRICTNE, PNK_NE, + /* Harmony is/isnt */ + PNK_IS, + PNK_ISNT, + /* Unary operators. */ PNK_TYPEOF, PNK_VOID, PNK_NOT, PNK_BITNOT, /* Relational operators (< <= > >=). */ PNK_LT, diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -1863,17 +1863,18 @@ Parser::statements() pn->pn_pos.end = tokenStream.currentToken().pos.end; return pn; } ParseNode * Parser::condition() { MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_COND); - ParseNode *pn = parenExpr(); + bool isGenerator = false; + ParseNode *pn = parenExpr(isGenerator); if (!pn) return NULL; MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_COND); /* Check for (a = b) and warn about possible (a == b) mistype. */ JS_ASSERT_IF(pn->isKind(PNK_ASSIGN), pn->isOp(JSOP_NOP)); if (pn->isKind(PNK_ASSIGN) && !pn->isInParens() && @@ -3019,17 +3020,18 @@ Parser::switchStatement() { JS_ASSERT(tc->parser->tokenStream.currentToken().type == TOK_SWITCH); ParseNode *pn = BinaryNode::create(PNK_SWITCH, tc); if (!pn) return NULL; MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_SWITCH); /* pn1 points to the switch's discriminant. */ - ParseNode *pn1 = parenExpr(); + bool isGenerator = false; + ParseNode *pn1 = parenExpr(isGenerator); if (!pn1) return NULL; MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_SWITCH); MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_SWITCH); /* * NB: we must push stmtInfo before calling GenerateBlockIdForStmtNode @@ -3677,17 +3679,18 @@ Parser::withStatement() reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_STRICT_CODE_WITH); return NULL; } ParseNode *pn = BinaryNode::create(PNK_WITH, tc); if (!pn) return NULL; MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_WITH); - ParseNode *pn2 = parenExpr(); + bool isGenerator = false; + ParseNode *pn2 = parenExpr(isGenerator); if (!pn2) return NULL; MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_WITH); pn->pn_left = pn2; ParseNode *oldWith = tc->innermostWith; tc->innermostWith = pn; @@ -4568,26 +4571,63 @@ EqualityTokenToParseNodeKind(const Token case TOK_STRICTNE: return PNK_STRICTNE; default: JS_ASSERT(token.type == TOK_NE); return PNK_NE; } } +bool +TokenIsHarmonyEquality(const Token &token, JSContext *cx) +{ + if (token.type != TOK_NAME) + return false; + if (token.name() == cx->runtime->atomState.isAtom) + return true; + if (token.name() == cx->runtime->atomState.isntAtom) + return true; + return false; +} + BEGIN_EXPR_PARSER(eqExpr1) { ParseNode *left = relExpr1i(); - while (left && tokenStream.isCurrentTokenEquality()) { - ParseNodeKind kind = EqualityTokenToParseNodeKind(tokenStream.currentToken()); - JSOp op = tokenStream.currentToken().t_op; - ParseNode *right = relExpr1n(); - if (!right) - return NULL; - left = tc->parser->new_(kind, op, left, right); + while (left) { + if (TokenIsHarmonyEquality(tokenStream.currentToken(), context)) { + /* Is defined as [no LineTerminator here]. */ + if (left->pn_pos.end.lineno < tokenStream.currentToken().pos.begin.lineno) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NEW_LINE_BEFORE_IS); + return NULL; + } + + JSOp op; + ParseNodeKind kind; + if (tokenStream.currentToken().name() == context->runtime->atomState.isAtom) { + op = JSOP_IS; + kind = PNK_IS; + } else { + op = JSOP_ISNT; + kind = PNK_ISNT; + } + + ParseNode *right = relExpr1n(); + if (!right) + return NULL; + left = tc->parser->new_(kind, op, left, right); + } else if (tokenStream.isCurrentTokenEquality()) { + ParseNodeKind kind = EqualityTokenToParseNodeKind(tokenStream.currentToken()); + JSOp op = tokenStream.currentToken().t_op; + ParseNode *right = relExpr1n(); + if (!right) + return NULL; + left = tc->parser->new_(kind, op, left, right); + } else { + break; + } } return left; } END_EXPR_PARSER(eqExpr1) BEGIN_EXPR_PARSER(bitAndExpr1) { ParseNode *pn = eqExpr1i(); @@ -7157,24 +7197,25 @@ Parser::primaryExpr(TokenKind tt, JSBool if (!pn) return NULL; break; } #endif /* JS_HAS_SHARP_VARS */ case TOK_LP: { - JSBool genexp; - - pn = parenExpr(&genexp); + bool isGenerator = false; + pn = parenExpr(isGenerator); if (!pn) return NULL; pn->setInParens(true); - if (!genexp) + if (!isGenerator) { MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_IN_PAREN); + pn->pn_pos.end = tokenStream.currentToken().pos.end; + } break; } case TOK_STRING: pn = atomNode(PNK_STRING, JSOP_STRING); if (!pn) return NULL; break; @@ -7278,57 +7319,52 @@ Parser::primaryExpr(TokenKind tt, JSBool default: reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_SYNTAX_ERROR); return NULL; } return pn; } ParseNode * -Parser::parenExpr(JSBool *genexp) +Parser::parenExpr(bool &isGenerator) { - TokenPtr begin; - ParseNode *pn; - JS_ASSERT(tokenStream.currentToken().type == TOK_LP); - begin = tokenStream.currentToken().pos.begin; - - if (genexp) - *genexp = JS_FALSE; - + TokenPtr begin = tokenStream.currentToken().pos.begin; + GenexpGuard guard(tc); - - pn = bracketedExpr(); + ParseNode *pn = bracketedExpr(); if (!pn) return NULL; guard.endBody(); + pn->pn_pos.begin = begin; + #if JS_HAS_GENERATOR_EXPRS if (tokenStream.matchToken(TOK_FOR)) { if (!guard.checkValidBody(pn)) return NULL; + JS_ASSERT(!pn->isKind(PNK_YIELD)); + if (pn->isKind(PNK_COMMA) && !pn->isInParens()) { reportErrorNumber(pn->last(), JSREPORT_ERROR, JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); return NULL; } pn = generatorExpr(pn); if (!pn) return NULL; - pn->pn_pos.begin = begin; - if (genexp) { - if (tokenStream.getToken() != TOK_RP) { - reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_GENERATOR_SYNTAX, - js_generator_str); - return NULL; - } - pn->pn_pos.end = tokenStream.currentToken().pos.end; - *genexp = JS_TRUE; + + pn->pn_pos.end = tokenStream.currentToken().pos.end; + + if (tokenStream.getToken() != TOK_RP) { + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); + return NULL; } + + isGenerator = true; } else #endif /* JS_HAS_GENERATOR_EXPRS */ - if (!guard.maybeNoteGenerator(pn)) return NULL; return pn; } diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -230,17 +230,17 @@ struct Parser : private AutoGCRooter ParseNode *shiftExpr1n(); ParseNode *addExpr1i(); ParseNode *addExpr1n(); ParseNode *mulExpr1i(); ParseNode *mulExpr1n(); ParseNode *unaryExpr(); ParseNode *memberExpr(JSBool allowCallSyntax); ParseNode *primaryExpr(TokenKind tt, JSBool afterDot); - ParseNode *parenExpr(JSBool *genexp = NULL); + ParseNode *parenExpr(bool &isGenerator); /* * Additional JS parsers. */ enum FunctionType { Getter, Setter, Normal }; bool functionArguments(TreeContext &funtc, FunctionBox *funbox, ParseNode **list); ParseNode *functionDef(PropertyName *name, FunctionType type, FunctionSyntaxKind kind); diff --git a/js/src/jit-test/tests/harmony/harmony-is.js b/js/src/jit-test/tests/harmony/harmony-is.js new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/harmony/harmony-is.js @@ -0,0 +1,47 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +assertEq(1 is 1, true); +assertEq(0 is 1, false); + +assertEq(NaN is NaN, true); +assertEq(NaN is undefined, false); + +assertEq(Infinity is Infinity, true); +assertEq(Infinity is -Infinity, false); + +assertEq(0 is 0, true); +assertEq(0 is -0, false); +assertEq(-0 is 0, false); + +var obj = {}; +assertEq(obj is obj, true); + +function throws(code) { + var error; + try { + eval(code); + } catch (e) { + error = e; + } + assertEq(error.name, 'SyntaxError'); +} + +var is = 0; +assertEq(is is 0, true); +assertEq(is isnt 0, false); + +throws('0 \n is 1'); +throws('(0 == 1) \n is 1'); + +assertEq((0 == + 1) is true, false); + +assertEq((0 == 0 + ) isnt false, true); + +var a; +a = (1 is 1) == true; +assertEq(a, true); diff --git a/js/src/js.msg b/js/src/js.msg --- a/js/src/js.msg +++ b/js/src/js.msg @@ -369,8 +369,9 @@ MSG_DEF(JSMSG_DEBUG_BAD_OFFSET, 28 MSG_DEF(JSMSG_DEBUG_BAD_LINE, 283, 0, JSEXN_TYPEERR, "invalid line number") MSG_DEF(JSMSG_DEBUG_NOT_DEBUGGING, 284, 0, JSEXN_ERR, "can't set breakpoint: script global is not a debuggee") MSG_DEF(JSMSG_DEBUG_COMPARTMENT_MISMATCH, 285, 2, JSEXN_TYPEERR, "{0}: descriptor .{1} property is an object in a different compartment than the target object") MSG_DEF(JSMSG_DEBUG_NOT_SCRIPT_FRAME, 286, 0, JSEXN_ERR, "stack frame is not running JavaScript code") MSG_DEF(JSMSG_CANT_WATCH_PROP, 287, 0, JSEXN_TYPEERR, "properties whose names are objects can't be watched") MSG_DEF(JSMSG_CSP_BLOCKED_EVAL, 288, 0, JSEXN_ERR, "call to eval() blocked by CSP") MSG_DEF(JSMSG_DEBUG_NO_SCOPE_OBJECT, 289, 0, JSEXN_TYPEERR, "declarative Environments don't have binding objects") MSG_DEF(JSMSG_EMPTY_CONSEQUENT, 290, 0, JSEXN_SYNTAXERR, "mistyped ; after conditional?") +MSG_DEF(JSMSG_NEW_LINE_BEFORE_IS, 291, 0, JSEXN_SYNTAXERR, "new line before 'is' or 'isnt'") diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -209,17 +209,20 @@ const char *const js_common_atom_names[] "keys", /* keysAtom */ "iterate", /* iterateAtom */ "WeakMap", /* WeakMapAtom */ "byteLength", /* byteLengthAtom */ "return", /* returnAtom */ - "throw" /* throwAtom */ + "throw", /* throwAtom */ + + "is", /* isAtom */ + "isnt" /* isntAtom */ }; void JSAtomState::checkStaticInvariants() { /* * Start and limit offsets for atom pointers in JSAtomState must be aligned * on the word boundary. diff --git a/js/src/jsatom.h b/js/src/jsatom.h --- a/js/src/jsatom.h +++ b/js/src/jsatom.h @@ -358,16 +358,19 @@ struct JSAtomState js::PropertyName *WeakMapAtom; js::PropertyName *byteLengthAtom; js::PropertyName *returnAtom; js::PropertyName *throwAtom; + js::PropertyName *isAtom; + js::PropertyName *isntAtom; + /* Less frequently used atoms, pinned lazily by JS_ResolveStandardClass. */ struct { js::PropertyName *XMLListAtom; js::PropertyName *decodeURIAtom; js::PropertyName *decodeURIComponentAtom; js::PropertyName *defineGetterAtom; js::PropertyName *defineSetterAtom; js::PropertyName *encodeURIAtom; diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -2385,16 +2385,18 @@ END_CASE(JSOP_BITAND) BEGIN_CASE(JSOP_EQ) EQUALITY_OP(==); END_CASE(JSOP_EQ) BEGIN_CASE(JSOP_NE) EQUALITY_OP(!=); END_CASE(JSOP_NE) + + #undef EQUALITY_OP #define STRICT_EQUALITY_OP(OP, COND) \ JS_BEGIN_MACRO \ const Value &rref = regs.sp[-1]; \ const Value &lref = regs.sp[-2]; \ JSBool equal; \ if (!StrictlyEqual(cx, lref, rref, &equal)) \ @@ -2414,16 +2416,43 @@ END_CASE(JSOP_STRICTEQ) BEGIN_CASE(JSOP_STRICTNE) { bool cond; STRICT_EQUALITY_OP(!=, cond); regs.sp[-1].setBoolean(cond); } END_CASE(JSOP_STRICTNE) +#define SAME_VALUE_OP(OP, COND) \ + JS_BEGIN_MACRO \ + const Value &rref = regs.sp[-1]; \ + const Value &lref = regs.sp[-2]; \ + JSBool equal; \ + if (!SameValue(cx, lref, rref, &equal)) \ + goto error; \ + COND = equal OP JS_TRUE; \ + regs.sp--; \ + JS_END_MACRO + +BEGIN_CASE(JSOP_IS) +{ + bool cond; + SAME_VALUE_OP(==, cond); + regs.sp[-1].setBoolean(cond); +} +END_CASE(JSOP_IS) + +BEGIN_CASE(JSOP_ISNT) +{ + bool cond; + SAME_VALUE_OP(!=, cond); + regs.sp[-1].setBoolean(cond); +} +END_CASE(JSOP_ISNT) + BEGIN_CASE(JSOP_CASE) { bool cond; STRICT_EQUALITY_OP(==, cond); if (cond) { regs.sp--; len = GET_JUMP_OFFSET(regs.pc); BRANCH(len); diff --git a/js/src/jsopcode.tbl b/js/src/jsopcode.tbl --- a/js/src/jsopcode.tbl +++ b/js/src/jsopcode.tbl @@ -565,8 +565,11 @@ OPDEF(JSOP_LAMBDA_FC, 221,"lambda_fc */ OPDEF(JSOP_SETMETHOD, 222,"setmethod", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) OPDEF(JSOP_INITMETHOD, 223,"initmethod", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) OPDEF(JSOP_SHARPINIT, 224,"sharpinit", NULL, 3, 0, 0, 0, JOF_UINT16|JOF_SHARPSLOT) /* Pop the stack, convert to a jsid (int or string), and push back. */ OPDEF(JSOP_TOID, 225, "toid", NULL, 1, 1, 1, 0, JOF_BYTE) + +OPDEF(JSOP_IS, 226, "is", "is", 1, 2, 1, 10, JOF_BYTE|JOF_LEFTASSOC|JOF_ARITH|JOF_DETECTING) +OPDEF(JSOP_ISNT, 227, "isnt", "isnt", 1, 2, 1, 10, JOF_BYTE|JOF_LEFTASSOC|JOF_ARITH|JOF_DETECTING) diff --git a/js/src/jsreflect.cpp b/js/src/jsreflect.cpp --- a/js/src/jsreflect.cpp +++ b/js/src/jsreflect.cpp @@ -85,16 +85,18 @@ char const *aopNames[] = { "&=" /* AOP_BITAND */ }; char const *binopNames[] = { "==", /* BINOP_EQ */ "!=", /* BINOP_NE */ "===", /* BINOP_STRICTEQ */ "!==", /* BINOP_STRICTNE */ + "is", /* BINOP_IS */ + "isnt", /* BINOP_ISNT */ "<", /* BINOP_LT */ "<=", /* BINOP_LE */ ">", /* BINOP_GT */ ">=", /* BINOP_GE */ "<<", /* BINOP_LSH */ ">>", /* BINOP_RSH */ ">>>", /* BINOP_URSH */ "+", /* BINOP_PLUS */ @@ -1763,16 +1765,20 @@ ASTSerializer::binop(ParseNodeKind kind, case PNK_GT: return BINOP_GT; case PNK_GE: return BINOP_GE; case PNK_EQ: return BINOP_EQ; case PNK_NE: return BINOP_NE; + case PNK_IS: + return BINOP_IS; + case PNK_ISNT: + return BINOP_ISNT; case PNK_STRICTEQ: return BINOP_STRICTEQ; case PNK_STRICTNE: return BINOP_STRICTNE; case PNK_ADD: return BINOP_ADD; case PNK_SUB: return BINOP_SUB; @@ -2465,16 +2471,18 @@ ASTSerializer::expression(ParseNode *pn, } case PNK_ADD: case PNK_SUB: case PNK_STRICTEQ: case PNK_EQ: case PNK_STRICTNE: case PNK_NE: + case PNK_IS: + case PNK_ISNT: case PNK_LT: case PNK_LE: case PNK_GT: case PNK_GE: case PNK_LSH: case PNK_RSH: case PNK_URSH: case PNK_STAR: diff --git a/js/src/jsreflect.h b/js/src/jsreflect.h --- a/js/src/jsreflect.h +++ b/js/src/jsreflect.h @@ -71,16 +71,18 @@ enum AssignmentOperator { AOP_LIMIT }; enum BinaryOperator { BINOP_ERR = -1, /* eq */ BINOP_EQ = 0, BINOP_NE, BINOP_STRICTEQ, BINOP_STRICTNE, + /* harmony eq */ + BINOP_IS, BINOP_ISNT, /* rel */ BINOP_LT, BINOP_LE, BINOP_GT, BINOP_GE, /* shift */ BINOP_LSH, BINOP_RSH, BINOP_URSH, /* arithmetic */ BINOP_ADD, BINOP_SUB, BINOP_STAR, BINOP_DIV, BINOP_MOD, /* binary */ BINOP_BITOR, BINOP_BITXOR, BINOP_BITAND,