# HG changeset patch # Parent d5830c12eb5864eb3a8905dc67b36cde87fbdbe5 # 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 @@ -6375,16 +6375,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 @@ -4576,26 +4576,60 @@ 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) { + const Token &token = tokenStream.currentToken(); + /* 'is' and 'isnt' are defined as [no LineTerminator here]. */ + if (TokenIsHarmonyEquality(token, context) && + left->pn_pos.end.lineno == token.pos.begin.lineno) { + JSOp op; + ParseNodeKind kind; + if (token.name() == context->runtime->atomState.isAtom) { + op = JSOP_IS; + kind = PNK_IS; + } else { + JS_ASSERT(token.name() == context->runtime->atomState.isntAtom); + 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(token); + JSOp op = token.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(); @@ -7165,24 +7199,26 @@ 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; + 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; @@ -7286,57 +7322,55 @@ 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(); + if (isGenerator) + *isGenerator = false; + #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 (isGenerator) { if (tokenStream.getToken() != TOK_RP) { - reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_GENERATOR_SYNTAX, - js_generator_str); + reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_GENERATOR_SYNTAX, js_generator_str); return NULL; } pn->pn_pos.end = tokenStream.currentToken().pos.end; - *genexp = JS_TRUE; + *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 = NULL); /* * 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,69 @@ +/* + * 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, expected) { + var error; + try { + eval(code); + } catch (e) { + error = e; + } + assertEq(error.name, expected || 'SyntaxError'); +} + +var is = 0; +assertEq(is is 0, true); +assertEq(is isnt 0, false); + +/* + * These don't actually test, the is operator, + * but constructs that should be illegal + */ + +throws('0 \n is 1'); +throws('(0 == 1) \n is 1'); +throws('0 \n is(5)', 'TypeError'); /* 'is' a number, thus not callable */ + +(function (a) { + var invoked = false + function is() {invoked = true;}; + + a++ + is(7); /* This should invoke the 'is' function */ + + assertEq(invoked, true); + invoked = false; + + assertEq(a++ is (7), false); + assertEq(a++ is(7), false); + + assertEq(invoked, false); +})(0); + +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/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/jsinfer.cpp b/js/src/jsinfer.cpp --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -3462,16 +3462,18 @@ ScriptAnalysis::analyzeTypesBytecode(JSC case JSOP_GT: case JSOP_GE: case JSOP_NOT: case JSOP_STRICTEQ: case JSOP_STRICTNE: case JSOP_IN: case JSOP_INSTANCEOF: case JSOP_DELDESC: + case JSOP_IS: + case JSOP_ISNT: pushed[0].addType(cx, Type::BooleanType()); break; case JSOP_DOUBLE: pushed[0].addType(cx, Type::DoubleType()); break; case JSOP_STRING: case JSOP_TYPEOF: case JSOP_TYPEOFEXPR: diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -2282,16 +2282,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)) \ @@ -2324,17 +2326,44 @@ BEGIN_CASE(JSOP_CASE) regs.sp--; len = GET_JUMP_OFFSET(regs.pc); BRANCH(len); } } END_CASE(JSOP_CASE) #undef STRICT_EQUALITY_OP - +#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) + +#undef SAME_VALUE_OP #define RELATIONAL_OP(OP) \ JS_BEGIN_MACRO \ Value &rval = regs.sp[-1]; \ Value &lval = regs.sp[-2]; \ bool cond; \ /* Optimize for two int-tagged operands (typical loop control). */ \ if (lval.isInt32() && rval.isInt32()) { \ cond = lval.toInt32() OP rval.toInt32(); \ diff --git a/js/src/jsopcode.tbl b/js/src/jsopcode.tbl --- a/js/src/jsopcode.tbl +++ b/js/src/jsopcode.tbl @@ -567,8 +567,11 @@ OPDEF(JSOP_INITMETHOD, 223,"initmetho 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) /* Push the implicit 'this' value for calls to the associated name. */ OPDEF(JSOP_IMPLICITTHIS, 226, "implicitthis", "", 3, 0, 1, 0, JOF_ATOM) + +OPDEF(JSOP_IS, 227, "is", "is", 1, 2, 1, 10, JOF_BYTE|JOF_LEFTASSOC|JOF_ARITH) +OPDEF(JSOP_ISNT, 228, "isnt", "isnt", 1, 2, 1, 10, JOF_BYTE|JOF_LEFTASSOC|JOF_ARITH) 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,