Bug 1031397 - Implement Tagged Templates as described in ES6 draft section 12.3.7. r=jorendorff

This commit is contained in:
Guptha Rajagopal 2014-07-30 10:14:00 +02:00
parent cc829656d8
commit 7797397ded
22 changed files with 763 additions and 52 deletions

View File

@ -1038,6 +1038,17 @@ EmitObjectOp(ExclusiveContext *cx, ObjectBox *objbox, JSOp op, BytecodeEmitter *
return EmitInternedObjectOp(cx, bce->objectList.add(objbox), op, bce);
}
#ifdef JS_HAS_TEMPLATE_STRINGS
static bool
EmitObjectPairOp(ExclusiveContext *cx, ObjectBox *objbox1, ObjectBox *objbox2, JSOp op,
BytecodeEmitter *bce)
{
uint32_t index = bce->objectList.add(objbox1);
bce->objectList.add(objbox2);
return EmitInternedObjectOp(cx, index, op, bce);
}
#endif
static bool
EmitRegExp(ExclusiveContext *cx, uint32_t index, BytecodeEmitter *bce)
{
@ -3845,12 +3856,15 @@ EmitAssignment(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *lhs, JSOp
}
bool
ParseNode::getConstantValue(ExclusiveContext *cx, bool strictChecks, MutableHandleValue vp)
ParseNode::getConstantValue(ExclusiveContext *cx, MutableHandleValue vp)
{
switch (getKind()) {
case PNK_NUMBER:
vp.setNumber(pn_dval);
return true;
#ifdef JS_HAS_TEMPLATE_STRINGS
case PNK_TEMPLATE_STRING:
#endif
case PNK_STRING:
vp.setString(pn_atom);
return true;
@ -3865,25 +3879,43 @@ ParseNode::getConstantValue(ExclusiveContext *cx, bool strictChecks, MutableHand
return true;
case PNK_SPREAD:
return false;
#ifdef JS_HAS_TEMPLATE_STRINGS
case PNK_CALLSITEOBJ:
#endif
case PNK_ARRAY: {
JS_ASSERT(isOp(JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST));
RootedValue value(cx);
unsigned count;
ParseNode *pn;
RootedObject obj(cx,
NewDenseAllocatedArray(cx, pn_count, nullptr, MaybeSingletonObject));
#ifdef JS_HAS_TEMPLATE_STRINGS
bool isArray = (getKind() == PNK_ARRAY);
if (!isArray) {
count = pn_count - 1;
pn = pn_head->pn_next;
} else {
#endif
JS_ASSERT(isOp(JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST));
count = pn_count;
pn = pn_head;
#ifdef JS_HAS_TEMPLATE_STRINGS
}
#endif
RootedObject obj(cx, NewDenseAllocatedArray(cx, count, nullptr, MaybeSingletonObject));
if (!obj)
return false;
unsigned idx = 0;
RootedId id(cx);
RootedValue value(cx);
for (ParseNode *pn = pn_head; pn; idx++, pn = pn->pn_next) {
if (!pn->getConstantValue(cx, strictChecks, &value))
for (; pn; idx++, pn = pn->pn_next) {
if (!pn->getConstantValue(cx, &value))
return false;
id = INT_TO_JSID(idx);
if (!JSObject::defineGeneric(cx, obj, id, value, nullptr, nullptr, JSPROP_ENUMERATE))
return false;
}
JS_ASSERT(idx == pn_count);
JS_ASSERT(idx == count);
types::FixArrayType(cx, obj);
vp.setObject(*obj);
@ -3900,7 +3932,7 @@ ParseNode::getConstantValue(ExclusiveContext *cx, bool strictChecks, MutableHand
RootedValue value(cx), idvalue(cx);
for (ParseNode *pn = pn_head; pn; pn = pn->pn_next) {
if (!pn->pn_right->getConstantValue(cx, strictChecks, &value))
if (!pn->pn_right->getConstantValue(cx, &value))
return false;
ParseNode *pnid = pn->pn_left;
@ -3954,7 +3986,7 @@ static bool
EmitSingletonInitialiser(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
RootedValue value(cx);
if (!pn->getConstantValue(cx, bce->sc->needStrictChecks(), &value))
if (!pn->getConstantValue(cx, &value))
return false;
JS_ASSERT(value.isObject());
@ -3965,6 +3997,33 @@ EmitSingletonInitialiser(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *
return EmitObjectOp(cx, objbox, JSOP_OBJECT, bce);
}
#ifdef JS_HAS_TEMPLATE_STRINGS
static bool
EmitCallSiteObject(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
RootedValue value(cx);
if (!pn->getConstantValue(cx, &value))
return false;
JS_ASSERT(value.isObject());
ObjectBox *objbox1 = bce->parser->newObjectBox(&value.toObject());
if (!objbox1)
return false;
if (!pn->as<CallSiteNode>().getRawArrayValue(cx, &value))
return false;
JS_ASSERT(value.isObject());
ObjectBox *objbox2 = bce->parser->newObjectBox(&value.toObject());
if (!objbox2)
return false;
return EmitObjectPairOp(cx, objbox1, objbox2, JSOP_CALLSITEOBJ, bce);
}
#endif
/* See the SRC_FOR source note offsetBias comments later in this file. */
JS_STATIC_ASSERT(JSOP_NOP_LENGTH == 1);
JS_STATIC_ASSERT(JSOP_POP_LENGTH == 1);
@ -5580,7 +5639,11 @@ EmitArray(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, uint32_t co
static bool
EmitCallOrNew(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
{
bool callop = pn->isKind(PNK_CALL);
bool callop = pn->isKind(PNK_CALL)
#ifdef JS_HAS_TEMPLATE_STRINGS
|| pn->isKind(PNK_TAGGED_TEMPLATE)
#endif
;
/*
* Emit callable invocation or operator new (constructor call) code.
@ -6604,6 +6667,9 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
break;
case PNK_NEW:
#ifdef JS_HAS_TEMPLATE_STRINGS
case PNK_TAGGED_TEMPLATE:
#endif
case PNK_CALL:
case PNK_GENEXP:
ok = EmitCallOrNew(cx, bce, pn);
@ -6640,7 +6706,11 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
return false;
break;
}
#ifdef JS_HAS_TEMPLATE_STRINGS
case PNK_CALLSITEOBJ:
ok = EmitCallSiteObject(cx, bce, pn);
break;
#endif
case PNK_ARRAY:
if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && bce->checkSingletonContext())
ok = EmitSingletonInitialiser(cx, bce, pn);

View File

@ -124,6 +124,38 @@ class FullParseHandler
ParseNode *newTemplateStringLiteral(JSAtom *atom, const TokenPos &pos) {
return new_<NullaryNode>(PNK_TEMPLATE_STRING, JSOP_NOP, pos, atom);
}
ParseNode *newCallSiteObject(uint32_t begin, unsigned blockidGen) {
ParseNode *callSite = new_<CallSiteNode>(begin);
if (!callSite)
return null();
Node propExpr = newArrayLiteral(getPosition(callSite).begin, blockidGen);
if (!propExpr)
return null();
if (!addArrayElement(callSite, propExpr))
return null();
return callSite;
}
bool addToCallSiteObject(ParseNode *callSiteObj, ParseNode *rawNode, ParseNode *cookedNode) {
MOZ_ASSERT(callSiteObj->isKind(PNK_CALLSITEOBJ));
if (!addArrayElement(callSiteObj, cookedNode))
return false;
if (!addArrayElement(callSiteObj->pn_head, rawNode))
return false;
/*
* We don't know when the last noSubstTemplate will come in, and we
* don't want to deal with this outside this method
*/
setEndPosition(callSiteObj, callSiteObj->pn_head);
return true;
}
#endif
ParseNode *newThisLiteral(const TokenPos &pos) {

View File

@ -93,6 +93,8 @@ class UpvarCookie
F(STRING) \
F(TEMPLATE_STRING_LIST) \
F(TEMPLATE_STRING) \
F(TAGGED_TEMPLATE) \
F(CALLSITEOBJ) \
F(REGEXP) \
F(TRUE) \
F(FALSE) \
@ -397,6 +399,14 @@ enum ParseNodeKind
* with pn_cookie telling (staticLevel, slot) (see
* jsscript.h's UPVAR macros) and pn_dflags telling
* const-ness and static analysis results
* PNK_TEMPLATE_STRING_LIST pn_head: list of alternating expr and template strings
* list
* PNK_TEMPLATE_STRING pn_atom: template string atom
nullary pn_op: JSOP_NOP
* PNK_TAGGED_TEMPLATE pn_head: list of call, call site object, arg1, arg2, ... argN
* list pn_count: 2 + N (N is the number of substitutions)
* PNK_CALLSITEOBJ list pn_head: a PNK_ARRAY node followed by
* list of pn_count - 1 PNK_TEMPLATE_STRING nodes
* PNK_REGEXP nullary pn_objbox: RegExp model object
* PNK_NAME name If pn_used, PNK_NAME uses the lexdef member instead
* of the expr member it overlays
@ -818,7 +828,7 @@ class ParseNode
#endif
;
bool getConstantValue(ExclusiveContext *cx, bool strictChecks, MutableHandleValue vp);
bool getConstantValue(ExclusiveContext *cx, MutableHandleValue vp);
inline bool isConstant();
template <class NodeType>
@ -1250,6 +1260,23 @@ class PropertyByValue : public ParseNode
}
};
#ifdef JS_HAS_TEMPLATE_STRINGS
/*
* A CallSiteNode represents the implicit call site object argument in a TaggedTemplate.
*/
struct CallSiteNode : public ListNode {
explicit CallSiteNode(uint32_t begin): ListNode(PNK_CALLSITEOBJ, TokenPos(begin, begin + 1)) {}
static bool test(const ParseNode &node) {
return node.isKind(PNK_CALLSITEOBJ);
}
bool getRawArrayValue(ExclusiveContext *cx, MutableHandleValue vp) {
return pn_head->getConstantValue(cx, vp);
}
};
#endif
#ifdef DEBUG
void DumpParseTree(ParseNode *pn, int indent = 0);
#endif
@ -1445,6 +1472,9 @@ ParseNode::isConstant()
switch (pn_type) {
case PNK_NUMBER:
case PNK_STRING:
#ifdef JS_HAS_TEMPLATE_STRINGS
case PNK_TEMPLATE_STRING:
#endif
case PNK_NULL:
case PNK_FALSE:
case PNK_TRUE:

View File

@ -1964,45 +1964,67 @@ Parser<SyntaxParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
}
#ifdef JS_HAS_TEMPLATE_STRINGS
template <typename ParseHandler>
bool
Parser<ParseHandler>::addExprAndGetNextTemplStrToken(Node nodeList, TokenKind &tt)
{
Node pn = expr();
if (!pn)
return false;
handler.addList(nodeList, pn);
tt = tokenStream.getToken();
if (tt != TOK_RC) {
if (tt != TOK_ERROR)
report(ParseError, false, null(), JSMSG_TEMPLSTR_UNTERM_EXPR);
return false;
}
tt = tokenStream.getToken(TokenStream::TemplateTail);
if (tt == TOK_ERROR)
return false;
return true;
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::taggedTemplate(Node nodeList, TokenKind tt)
{
Node callSiteObjNode = handler.newCallSiteObject(pos().begin, pc->blockidGen);
if (!callSiteObjNode)
return false;
handler.addList(nodeList, callSiteObjNode);
while (true) {
if (!appendToCallSiteObj(callSiteObjNode))
return false;
if (tt != TOK_TEMPLATE_HEAD)
break;
if (!addExprAndGetNextTemplStrToken(nodeList, tt))
return false;
}
handler.setEndPosition(nodeList, callSiteObjNode);
return true;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::templateLiteral()
{
Node pn = noSubstitutionTemplate();
if (!pn) {
report(ParseError, false, null(),
JSMSG_SYNTAX_ERROR);
if (!pn)
return null();
}
Node nodeList = handler.newList(PNK_TEMPLATE_STRING_LIST, pn);
TokenKind tt;
do {
pn = expr();
if (!pn) {
report(ParseError, false, null(),
JSMSG_SYNTAX_ERROR);
if (!addExprAndGetNextTemplStrToken(nodeList, tt))
return null();
}
handler.addList(nodeList, pn);
tt = tokenStream.getToken();
if (tt != TOK_RC) {
report(ParseError, false, null(),
JSMSG_SYNTAX_ERROR);
return null();
}
tt = tokenStream.getToken(TokenStream::TemplateTail);
if (tt == TOK_ERROR) {
report(ParseError, false, null(),
JSMSG_SYNTAX_ERROR);
return null();
}
pn = noSubstitutionTemplate();
if (!pn) {
report(ParseError, false, null(),
JSMSG_SYNTAX_ERROR);
if (!pn)
return null();
}
handler.addList(nodeList, pn);
} while (tt == TOK_TEMPLATE_HEAD);
@ -2289,6 +2311,26 @@ Parser<SyntaxParseHandler>::functionArgsAndBody(Node pn, HandleFunction fun,
return outerpc->innerFunctions.append(fun);
}
#ifdef JS_HAS_TEMPLATE_STRINGS
template <typename ParseHandler>
bool
Parser<ParseHandler>::appendToCallSiteObj(Node callSiteObj)
{
Node cookedNode = noSubstitutionTemplate();
if (!cookedNode)
return false;
JSAtom *atom = tokenStream.getRawTemplateStringAtom();
if (!atom)
return false;
Node rawNode = handler.newTemplateStringLiteral(atom, pos());
if (!rawNode)
return false;
return handler.addToCallSiteObject(callSiteObj, rawNode, cookedNode);
}
#endif
template <>
ParseNode *
Parser<FullParseHandler>::standaloneLazyFunction(HandleFunction fun, unsigned staticLevel,
@ -6922,14 +6964,32 @@ Parser<ParseHandler>::memberExpr(TokenKind tt, bool allowCallSyntax)
nextMember = handler.newPropertyByValue(lhs, propExpr, pos().end);
if (!nextMember)
return null();
} else if (allowCallSyntax && tt == TOK_LP) {
} else if ((allowCallSyntax &&
tt == TOK_LP)
#ifdef JS_HAS_TEMPLATE_STRINGS
|| tt == TOK_TEMPLATE_HEAD
|| tt == TOK_NO_SUBS_TEMPLATE
#endif
)
{
JSOp op = JSOP_CALL;
nextMember = handler.newList(PNK_CALL, null(), JSOP_CALL);
#ifdef JS_HAS_TEMPLATE_STRINGS
if (tt == TOK_LP)
#endif
nextMember = handler.newList(PNK_CALL, null(), JSOP_CALL);
#ifdef JS_HAS_TEMPLATE_STRINGS
else
nextMember = handler.newList(PNK_TAGGED_TEMPLATE, null(), JSOP_CALL);
#endif
if (!nextMember)
return null();
if (JSAtom *atom = handler.isName(lhs)) {
if (atom == context->names().eval) {
if (
#ifdef JS_HAS_TEMPLATE_STRINGS
tt == TOK_LP &&
#endif
atom == context->names().eval) {
/* Select JSOP_EVAL and flag pc as heavyweight. */
op = JSOP_EVAL;
pc->sc->setBindingsAccessedDynamically();
@ -6955,11 +7015,20 @@ Parser<ParseHandler>::memberExpr(TokenKind tt, bool allowCallSyntax)
handler.setBeginPosition(nextMember, lhs);
handler.addList(nextMember, lhs);
bool isSpread = false;
if (!argumentList(nextMember, &isSpread))
return null();
if (isSpread)
op = (op == JSOP_EVAL ? JSOP_SPREADEVAL : JSOP_SPREADCALL);
#ifdef JS_HAS_TEMPLATE_STRINGS
if (tt == TOK_LP) {
#endif
bool isSpread = false;
if (!argumentList(nextMember, &isSpread))
return null();
if (isSpread)
op = (op == JSOP_EVAL ? JSOP_SPREADEVAL : JSOP_SPREADCALL);
#ifdef JS_HAS_TEMPLATE_STRINGS
} else {
if (!taggedTemplate(nextMember, tt))
return null();
}
#endif
handler.setOp(nextMember, op);
} else {
tokenStream.ungetToken();

View File

@ -437,6 +437,9 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
#ifdef JS_HAS_TEMPLATE_STRINGS
Node noSubstitutionTemplate();
Node templateLiteral();
bool taggedTemplate(Node nodeList, TokenKind tt);
bool appendToCallSiteObj(Node callSiteObj);
bool addExprAndGetNextTemplStrToken(Node nodeList, TokenKind &tt);
#endif
inline Node newName(PropertyName *name);

View File

@ -79,6 +79,14 @@ class SyntaxParseHandler
Node newTemplateStringLiteral(JSAtom *atom, const TokenPos &pos) {
return NodeGeneric;
}
Node newCallSiteObject(uint32_t begin, unsigned blockidGen) {
return NodeGeneric;
}
bool addToCallSiteObject(Node callSiteObj, Node rawNode, Node cookedNode) {
return true;
}
#endif
Node newThisLiteral(const TokenPos &pos) { return NodeGeneric; }

View File

@ -470,6 +470,36 @@ class MOZ_STACK_CLASS TokenStream
// asm.js reporter
void reportAsmJSError(uint32_t offset, unsigned errorNumber, ...);
#ifdef JS_HAS_TEMPLATE_STRINGS
JSAtom *getRawTemplateStringAtom() {
JS_ASSERT(currentToken().type == TOK_TEMPLATE_HEAD ||
currentToken().type == TOK_NO_SUBS_TEMPLATE);
const jschar *cur = userbuf.base() + currentToken().pos.begin + 1;
const jschar *end;
if (currentToken().type == TOK_TEMPLATE_HEAD) {
// Of the form |`...${| or |}...${|
end = userbuf.base() + currentToken().pos.end - 2;
} else {
// NO_SUBS_TEMPLATE is of the form |`...`| or |}...`|
end = userbuf.base() + currentToken().pos.end - 1;
}
CharBuffer charbuf(cx);
while (cur < end) {
int32_t ch = *cur;
if (ch == '\r') {
ch = '\n';
if ((cur + 1 < end) && (*(cur + 1) == '\n'))
cur++;
}
if (!charbuf.append(ch))
return nullptr;
cur++;
}
return AtomizeChars(cx, charbuf.begin(), charbuf.length());
}
#endif
private:
// These are private because they should only be called by the tokenizer
// while tokenizing not by, for example, BytecodeEmitter.

View File

@ -44,4 +44,10 @@ function evalWithCache(code, ctx) {
assertEq(res0, res2);
assertEq(res0, res3);
}
if (ctx.checkFrozen) {
assertEq(Object.isFrozen(res0), Object.isFrozen(res1));
assertEq(Object.isFrozen(res0), Object.isFrozen(res2));
assertEq(Object.isFrozen(res0), Object.isFrozen(res3));
}
}

View File

@ -36,3 +36,10 @@ evalWithCache(test, { assertEqBytecode: true, assertEqResult : true });
// code a function which has an object literal.
test = "function f() { return { x: 2 }; }; f();";
evalWithCache(test, { assertEqBytecode: true });
// code call site object
var hasTemplateStrings = false; try { eval("``"); hasTemplateStrings = true; } catch (exc) { }
if (hasTemplateStrings == true) {
test = "function f(a) { return a; }; f`a${4}b`;";
evalWithCache(test, { assertEqBytecode: true, checkFrozen: true});
}

View File

@ -442,3 +442,4 @@ MSG_DEF(JSMSG_PROXY_CONSTRUCT_OBJECT, 387, 0, JSEXN_TYPEERR, "proxy [[Construc
MSG_DEF(JSMSG_PROXY_GETOWN_OBJORUNDEF, 388, 0, JSEXN_TYPEERR, "proxy [[GetOwnProperty]] must return an object or undefined")
MSG_DEF(JSMSG_CANT_REPORT_C_AS_NC, 389, 0, JSEXN_TYPEERR, "proxy can't report existing configurable property as non-configurable")
MSG_DEF(JSMSG_PROXY_REVOKED, 390, 0, JSEXN_TYPEERR, "illegal operation attempted on a revoked proxy")
MSG_DEF(JSMSG_TEMPLSTR_UNTERM_EXPR, 391, 0, JSEXN_SYNTAXERR, "missing } in template string")

View File

@ -71,5 +71,7 @@ ASTDEF(AST_ARRAY_PATT, "ArrayPattern", "arrayPatter
ASTDEF(AST_OBJECT_PATT, "ObjectPattern", "objectPattern")
ASTDEF(AST_PROP_PATT, "Property", "propertyPattern")
ASTDEF(AST_TEMPLATE_LITERAL, "TemplateLiteral", "templateLiteral")
ASTDEF(AST_TAGGED_TEMPLATE, "TaggedTemplate", "taggedTemplate")
ASTDEF(AST_CALL_SITE_OBJ, "CallSiteObject", "callSiteObject")
/* AST_LIMIT = last + 1 */

View File

@ -2210,6 +2210,22 @@ js::XDRObjectLiteral(XDRState<mode> *xdr, MutableHandleObject obj)
FixObjectType(cx, obj);
}
{
uint32_t frozen;
bool extensible;
if (mode == XDR_ENCODE) {
if (!JSObject::isExtensible(cx, obj, &extensible))
return false;
frozen = extensible ? 0 : 1;
}
if (!xdr->codeUint32(&frozen))
return false;
if (mode == XDR_DECODE && frozen == 1) {
if (!JSObject::freeze(cx, obj))
return false;
}
}
return true;
}

View File

@ -629,8 +629,15 @@ class NodeBuilder
bool arrayExpression(NodeVector &elts, TokenPos *pos, MutableHandleValue dst);
#ifdef JS_HAS_TEMPLATE_STRINGS
bool templateLiteral(NodeVector &elts, TokenPos *pos, MutableHandleValue dst);
bool taggedTemplate(HandleValue callee, NodeVector &args, TokenPos *pos,
MutableHandleValue dst);
bool callSiteObj(NodeVector &raw, NodeVector &cooked, TokenPos *pos, MutableHandleValue dst);
#endif
bool spreadExpression(HandleValue expr, TokenPos *pos, MutableHandleValue dst);
bool objectExpression(NodeVector &elts, TokenPos *pos, MutableHandleValue dst);
@ -1212,6 +1219,37 @@ NodeBuilder::arrayExpression(NodeVector &elts, TokenPos *pos, MutableHandleValue
}
#ifdef JS_HAS_TEMPLATE_STRINGS
bool
NodeBuilder::callSiteObj(NodeVector &raw, NodeVector &cooked, TokenPos *pos, MutableHandleValue dst)
{
RootedValue rawVal(cx);
if (!newArray(raw, &rawVal))
return false;
RootedValue cookedVal(cx);
if (!newArray(cooked, &cookedVal))
return false;
return newNode(AST_CALL_SITE_OBJ, pos,
"raw", rawVal,
"cooked", cookedVal,
dst);
}
bool
NodeBuilder::taggedTemplate(HandleValue callee, NodeVector &args, TokenPos *pos,
MutableHandleValue dst)
{
RootedValue array(cx);
if (!newArray(args, &array))
return false;
return newNode(AST_TAGGED_TEMPLATE, pos,
"callee", callee,
"arguments", array,
dst);
}
bool
NodeBuilder::templateLiteral(NodeVector &elts, TokenPos *pos, MutableHandleValue dst)
{
@ -2728,6 +2766,9 @@ ASTSerializer::expression(ParseNode *pn, MutableHandleValue dst)
#endif
case PNK_NEW:
#ifdef JS_HAS_TEMPLATE_STRINGS
case PNK_TAGGED_TEMPLATE:
#endif
case PNK_CALL:
{
ParseNode *next = pn->pn_head;
@ -2750,6 +2791,10 @@ ASTSerializer::expression(ParseNode *pn, MutableHandleValue dst)
args.infallibleAppend(arg);
}
#ifdef JS_HAS_TEMPLATE_STRINGS
if (pn->getKind() == PNK_TAGGED_TEMPLATE)
return builder.taggedTemplate(callee, args, &pn->pn_pos, dst);
#endif
return pn->isKind(PNK_NEW)
? builder.newExpression(callee, args, &pn->pn_pos, dst)
@ -2777,7 +2822,36 @@ ASTSerializer::expression(ParseNode *pn, MutableHandleValue dst)
expression(pn->pn_right, &right) &&
builder.memberExpression(true, left, right, &pn->pn_pos, dst);
}
#ifdef JS_HAS_TEMPLATE_STRINGS
case PNK_CALLSITEOBJ:
{
NodeVector raw(cx);
if (!raw.reserve(pn->pn_head->pn_count))
return false;
for (ParseNode *next = pn->pn_head->pn_head; next; next = next->pn_next) {
JS_ASSERT(pn->pn_pos.encloses(next->pn_pos));
RootedValue expr(cx);
expr.setString(next->pn_atom);
raw.infallibleAppend(expr);
}
NodeVector cooked(cx);
if (!cooked.reserve(pn->pn_count - 1))
return false;
for (ParseNode *next = pn->pn_head->pn_next; next; next = next->pn_next) {
JS_ASSERT(pn->pn_pos.encloses(next->pn_pos));
RootedValue expr(cx);
expr.setString(next->pn_atom);
cooked.infallibleAppend(expr);
}
return builder.callSiteObj(raw, cooked, &pn->pn_pos, dst);
}
#endif
case PNK_ARRAY:
{
NodeVector elts(cx);

View File

@ -0,0 +1,297 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This test case is weird in the sense the actual work happens at the eval
// at the end. If template strings are not enabled, the test cases would throw
// a syntax error and we'd have failure reported. To avoid that, the entire
// test case is commented out and is part of a function. We use toString to
// get the string version, obtain the actual lines to run, and then use eval to
// do the actual evaluation.
function testCaseFn() {
/*
function syntaxError (script) {
try {
Function(script);
} catch (e) {
if (e.name === "SyntaxError") {
return;
}
}
throw new Error('Expected syntax error: ' + script);
}
// function definitions
function check(actual, expected) {
assertEq(actual.length, expected.length);
for (var i = 0; i < expected.length; i++)
assertEq(actual[i], expected[i]);
}
function cooked(cs) { return cs; }
function raw(cs) { return cs.raw; }
function args(cs, ...rest) { return rest; }
// TEST BEGIN
// Literals
check(raw``, [""]);
check(raw`${4}a`, ["","a"]);
check(raw`${4}`, ["",""]);
check(raw`a${4}`, ["a",""]);
check(raw`a${4}b`, ["a","b"]);
check(raw`a${4}b${3}`, ["a","b",""]);
check(raw`a${4}b${3}c`, ["a","b","c"]);
check(raw`a${4}${3}c`, ["a","","c"]);
check(raw`${4}${3}`, ["","",""]);
check(raw`${4}\r\r${3}`, ["","\\r\\r",""]);
check(raw`${4}${3}c`, ["","","c"]);
check(raw`zyx${4}wvut${3}c`, ["zyx","wvut","c"]);
check(raw`zyx${4}wvut${3}\r\n`, ["zyx","wvut","\\r\\n"]);
check(raw`hey`, ["hey"]);
check(raw`he\r\ny`, ["he\\r\\ny"]);
check(raw`he\ry`, ["he\\ry"]);
check(raw`he\r\ry`, ["he\\r\\ry"]);
check(raw`he\ny`, ["he\\ny"]);
check(raw`he\n\ny`, ["he\\n\\ny"]);
check(cooked`hey`, ["hey"]);
check(cooked`he\r\ny`, ["he\r\ny"]);
check(cooked`he\ry`, ["he\ry"]);
check(cooked`he\r\ry`, ["he\r\ry"]);
check(cooked`he\ny`, ["he\ny"]);
check(cooked`he\n\ny`, ["he\n\ny"]);
check(eval("raw`\r`"), ["\n"]);
check(eval("raw`\r\n`"), ["\n"]);
check(eval("raw`\r\r\n`"), ["\n\n"]);
check(eval("raw`he\r\ny`"), ["he\ny"]);
check(eval("raw`he\ry`"), ["he\ny"]);
check(eval("raw`he\r\ry`"), ["he\n\ny"]);
check(eval("raw`he\r\r\ny`"), ["he\n\ny"]);
check(eval("cooked`\r`"), ["\n"]);
check(eval("cooked`\r\n`"), ["\n"]);
check(eval("cooked`\r\r\n`"), ["\n\n"]);
check(eval("cooked`he\r\ny`"), ["he\ny"]);
check(eval("cooked`he\ry`"), ["he\ny"]);
check(eval("cooked`he\r\ry`"), ["he\n\ny"]);
check(eval("cooked`he\r\r\ny`"), ["he\n\ny"]);
// Expressions
check(args`hey${"there"}now`, ["there"]);
check(args`hey${4}now`, [4]);
check(args`hey${4}`, [4]);
check(args`${4}`, [4]);
check(args`${4}${5}`, [4,5]);
check(args`a${4}${5}`, [4,5]);
check(args`a${4}b${5}`, [4,5]);
check(args`a${4}b${5}c`, [4,5]);
check(args`${4}b${5}c`, [4,5]);
check(args`${4}${5}c`, [4,5]);
var a = 10;
var b = 15;
check(args`${4 + a}${5 + b}c`, [14,20]);
check(args`${4 + a}${a + b}c`, [14,25]);
check(args`${b + a}${5 + b}c`, [25,20]);
check(args`${4 + a}${a + b}c${"a"}`, [14,25,"a"]);
check(args`a${"b"}${"c"}${"d"}`, ["b","c","d"]);
check(args`a${"b"}${"c"}${a + b}`, ["b","c",25]);
check(args`a${"b"}`, ["b"]);
// Expressions - complex substitutions
check(args`${`hey ${b + a} there`}${5 + b}c`, ["hey 25 there",20]);
check(args`${`hey ${`my ${b + a} good`} there`}${5 + b}c`, ["hey my 25 good there",20]);
syntaxError("args`${}`");
syntaxError("args`${`");
syntaxError("args`${\\n}`");
syntaxError("args`${yield 0}`");
syntaxError("args`");
syntaxError("args`$");
syntaxError("args`${");
syntaxError("args.``");
// Template substitution tests in the context of tagged templates
// Extra whitespace inside a template substitution is ignored.
check(args`a${
0
}`, [0]);
// Extra whitespace between tag and template is ignored
check(args
`a
${
0
}`, [0]);
check(args`${5}${ // Comments work in template substitutions.
// Even comments that look like code:
// 0}`, "FAIL"); /* NOTE: This whole line is a comment.
0}`, [5,0]);
check(args // Comments work in template substitutions.
// Even comments that look like code:
// 0}`, "FAIL"); /* NOTE: This whole line is a comment.
`${5}${0}`, [5,0]);
// Template substitutions are expressions, not statements.
syntaxError("args`${0;}`");
check(args`${
function f() {
return "ok";
}()
}`, ["ok"]);
// Template substitutions can have side effects.
var x = 0;
check(args`${x += 1}`, [1]);
assertEq(x, 1);
// The production for a template substitution is Expression, not
// AssignmentExpression.
x = 0;
check(args`${++x, "o"}k`, ["o"]);
assertEq(x, 1);
// --> is not a comment inside a template.
check(cooked`
--> this is text
`, ["\n--> this is text\n"]);
// reentrancy
function f(n) {
if (n === 0)
return "";
var res = args`${n}${f(n - 1)}`;
return res[0] + res[1] + "";
}
assertEq(f(9), "987654321");
// Template string substitutions in generator functions can yield.
function* g() {
var res = args`${yield 1} ${yield 2}`;
return res[0] + res[1] + "";
}
var it = g();
var next = it.next();
assertEq(next.done, false);
assertEq(next.value, 1);
next = it.next("hello");
assertEq(next.done, false);
assertEq(next.value, 2);
next = it.next("world");
assertEq(next.done, true);
assertEq(next.value, "helloworld");
// undefined
assertEq(args`${void 0}`[0] + "", "undefined");
assertEq(args`${Object.doesNotHaveThisProperty}`[0] + "", "undefined");
var callSiteObj = [];
callSiteObj[0] = cooked`aa${4}bb`;
for (var i = 1; i < 3; i++)
callSiteObj[i] = cooked`aa${4}bb`;
// Same call site object behavior
assertEq(callSiteObj[1], callSiteObj[2]);
assertEq(callSiteObj[0] !== callSiteObj[1], true);
assertEq("raw" in callSiteObj[0], true);
// Array length
assertEq(callSiteObj[0].raw.length, 2);
assertEq(callSiteObj[0].length, 2);
// Frozen objects
assertEq(Object.isFrozen(callSiteObj[0]), true);
assertEq(Object.isFrozen(callSiteObj[0].raw), true);
// Raw not enumerable
assertEq(callSiteObj[0].propertyIsEnumerable(callSiteObj[0].raw), false);
// Allow call syntax
check(new ((cs, sub) => function(){ return sub }) `${[1, 2, 3]}`, [1,2,3]);
var a = [];
function test() {
var x = callSite => callSite;
for (var i = 0; i < 2; i++)
a[i] = eval("x``");
}
test();
assertEq(a[0] !== a[1], true);
// Test that |obj.method`template`| works
var newObj = {
methodRetThis : function () {
return this;
},
methodRetCooked : function (a) {
return a;
},
methodRetRaw : function (a) {
return a.raw;
},
methodRetArgs : function (a, ...args) {
return args;
}
}
assertEq(newObj.methodRetThis`abc${4}`, newObj);
check(newObj.methodRetCooked`abc${4}\r`, ["abc","\r"]);
check(eval("newObj.methodRetCooked`abc${4}\r`"), ["abc","\n"]);
check(newObj.methodRetRaw`abc${4}\r`, ["abc","\\r"]);
check(eval("newObj.methodRetRaw`abc${4}\r`"), ["abc","\n"]);
check(eval("newObj.methodRetArgs`abc${4}${5}\r${6}`"), [4,5,6]);
// Chained calls
function func(a) {
if (a[0] === "hey") {
return function(a) {
if (a[0] === "there") {
return function(a) {
if (a[0] === "mine")
return "was mine";
else
return "was not mine";
}
} else {
return function(a) {
return "was not there";
}
}
}
} else {
return function(a) {
return function(a) {
return "was not hey";
}
}
}
}
assertEq(func`hey``there``mine`, "was mine");
assertEq(func`hey``there``amine`, "was not mine");
assertEq(func`hey``tshere``amine`, "was not there");
assertEq(func`heys``there``mine`, "was not hey");
*/
/*End func*/}
var str = testCaseFn.toString().replace("/*","").replace("*/","");
str = str.replace("function testCaseFn() {\n", "").replace("/*End func*/}","");
var hasTemplateStrings = false;
try { eval("``"); hasTemplateStrings = true; } catch (exc) { }
if (hasTemplateStrings)
eval(str);
reportCompare(0, 0, "ok");

View File

@ -19,7 +19,7 @@ function syntaxError (script) {
return;
}
}
throw "Expected syntax error: " + script;
throw new Error('Expected syntax error: ' + script);
}
// TEST BEGIN

View File

@ -93,6 +93,10 @@ function callExpr(callee, args) Pattern({ type: "CallExpression", callee: callee
function arrExpr(elts) Pattern({ type: "ArrayExpression", elements: elts })
function objExpr(elts) Pattern({ type: "ObjectExpression", properties: elts })
function templateLit(elts) Pattern({ type: "TemplateLiteral", elements: elts })
function taggedTemplate(tagPart, templatePart) Pattern({ type: "TaggedTemplate", callee: tagPart,
arguments : templatePart })
function template(raw, cooked, ...args) Pattern([{ type: "CallSiteObject", raw: raw, cooked:
cooked}, ...args])
function compExpr(body, blocks, filter) Pattern({ type: "ComprehensionExpression", body: body, blocks: blocks, filter: filter })
function genExpr(body, blocks, filter) Pattern({ type: "GeneratorExpression", body: body, blocks: blocks, filter: filter })
function graphExpr(idx, body) Pattern({ type: "GraphExpression", index: idx, expression: body })
@ -410,7 +414,20 @@ if (hasTemplateStrings == true) {
assertExpr("`hey${\"there\"}`", templateLit([lit("hey"), lit("there"), lit("")]));
assertExpr("`hey${\"there\"}mine`", templateLit([lit("hey"), lit("there"), lit("mine")]));
assertExpr("`hey${a == 5}mine`", templateLit([lit("hey"), binExpr("==", ident("a"), lit(5)), lit("mine")]));
assertExpr("`hey${`there${\"how\"}`}mine`", templateLit([lit("hey"), templateLit([lit("there"), lit("how"), lit("")]), lit("mine")]));
assertExpr("`hey${`there${\"how\"}`}mine`", templateLit([lit("hey"),
templateLit([lit("there"), lit("how"), lit("")]), lit("mine")]));
assertExpr("func`hey`", taggedTemplate(ident("func"), template(["hey"], ["hey"])));
assertExpr("func`hey${\"4\"}there`", taggedTemplate(ident("func"),
template(["hey", "there"], ["hey", "there"], lit("4"))));
assertExpr("func`hey${\"4\"}there${5}`", taggedTemplate(ident("func"),
template(["hey", "there", ""], ["hey", "there", ""],
lit("4"), lit(5))));
assertExpr("func`hey\r\n`", taggedTemplate(ident("func"), template(["hey\n"], ["hey\n"])));
assertExpr("func`hey${4}``${5}there``mine`",
taggedTemplate(taggedTemplate(taggedTemplate(
ident("func"), template(["hey", ""], ["hey", ""], lit(4))),
template(["", "there"], ["", "there"], lit(5))),
template(["mine"], ["mine"])));
}
assertStringExpr("\"hey there\"", literal("hey there"));

View File

@ -203,6 +203,7 @@
macro(y, y, "y") \
macro(yield, yield, "yield") \
macro(z, z, "z") \
macro(raw, raw, "raw") \
/* Type names must be contiguous and ordered; see js::TypeName. */ \
macro(undefined, undefined, "undefined") \
macro(object, object, "object") \

View File

@ -521,6 +521,28 @@ InitArrayElemOperation(JSContext *cx, jsbytecode *pc, HandleObject obj, uint32_t
return true;
}
static MOZ_ALWAYS_INLINE bool
ProcessCallSiteObjOperation(JSContext *cx, RootedObject &cso, RootedObject &raw,
RootedValue &rawValue)
{
bool extensible;
if (!JSObject::isExtensible(cx, cso, &extensible))
return false;
if (extensible) {
JSAtom *name = cx->names().raw;
if (!JSObject::defineProperty(cx, cso, name->asPropertyName(), rawValue,
nullptr, nullptr, 0))
{
return false;
}
if (!JSObject::freeze(cx, raw))
return false;
if (!JSObject::freeze(cx, cso))
return false;
}
return true;
}
#define RELATIONAL_OP(OP) \
JS_BEGIN_MACRO \
/* Optimize for two int-tagged operands (typical loop control). */ \

View File

@ -1589,7 +1589,6 @@ CASE(JSOP_UNUSED51)
CASE(JSOP_UNUSED52)
CASE(JSOP_UNUSED57)
CASE(JSOP_UNUSED83)
CASE(JSOP_UNUSED101)
CASE(JSOP_UNUSED102)
CASE(JSOP_UNUSED103)
CASE(JSOP_UNUSED104)
@ -2726,6 +2725,23 @@ CASE(JSOP_OBJECT)
}
END_CASE(JSOP_OBJECT)
CASE(JSOP_CALLSITEOBJ)
{
RootedObject &cso = rootObject0;
cso = script->getObject(REGS.pc);
RootedObject &raw = rootObject1;
raw = script->getObject(GET_UINT32_INDEX(REGS.pc) + 1);
RootedValue &rawValue = rootValue0;
rawValue.setObject(*raw);
if (!ProcessCallSiteObjOperation(cx, cso, raw, rawValue))
goto error;
PUSH_OBJECT(*cso);
}
END_CASE(JSOP_CALLSITEOBJ)
CASE(JSOP_REGEXP)
{
/*

View File

@ -883,7 +883,17 @@
*/ \
macro(JSOP_INITELEM_SETTER, 100, "initelem_setter", NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_SET|JOF_DETECTING) \
\
macro(JSOP_UNUSED101, 101, "unused101", NULL, 1, 0, 0, JOF_BYTE) \
/*
* Pushes the call site object specified by objectIndex onto the stack. Defines the raw
* property specified by objectIndex + 1 on the call site object and freezes both the call site
* object as well as its raw property.
* Category: Literals
* Type: Object
* Operands: uint32_t objectIndex
* Stack: => obj
*/ \
macro(JSOP_CALLSITEOBJ, 101, "callsiteobj", NULL, 5, 0, 1, JOF_OBJECT) \
\
macro(JSOP_UNUSED102, 102, "unused102", NULL, 1, 0, 0, JOF_BYTE) \
macro(JSOP_UNUSED103, 103, "unused103", NULL, 1, 0, 0, JOF_BYTE) \
macro(JSOP_UNUSED104, 104, "unused104", NULL, 1, 0, 0, JOF_BYTE) \

View File

@ -28,7 +28,7 @@ namespace js {
*
* https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
*/
static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 178);
static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 179);
class XDRBuffer {
public: