Bug 1317375 - Implement "Template Literals Revision / Lifting Template Literal Restriction" ECMAScript proposal r=arai

MozReview-Commit-ID: 4OBI6kCe7Lf
This commit is contained in:
Kevin Gibbons 2017-01-19 11:14:00 +09:00
Родитель 7acd123c94
Коммит 8049403822
14 изменённых файлов: 388 добавлений и 70 удалений

Просмотреть файл

@ -3044,7 +3044,12 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
RootedValue expr(cx);
expr.setString(next->pn_atom);
if (next->isKind(PNK_RAW_UNDEFINED)) {
expr.setUndefined();
} else {
MOZ_ASSERT(next->isKind(PNK_TEMPLATE_STRING));
expr.setString(next->pn_atom);
}
cooked.infallibleAppend(expr);
}
@ -3136,6 +3141,7 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
case PNK_RAW_UNDEFINED:
return literal(pn, dst);
case PNK_YIELD_STAR:
@ -3276,6 +3282,10 @@ ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst)
val.setNull();
break;
case PNK_RAW_UNDEFINED:
val.setUndefined();
break;
case PNK_TRUE:
val.setBoolean(true);
break;

Просмотреть файл

@ -2660,6 +2660,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
case PNK_RAW_UNDEFINED:
case PNK_ELISION:
case PNK_GENERATOR:
case PNK_NUMBER:
@ -5825,6 +5826,9 @@ ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObje
case PNK_NULL:
vp.setNull();
return true;
case PNK_RAW_UNDEFINED:
vp.setUndefined();
return true;
case PNK_CALLSITEOBJ:
case PNK_ARRAY: {
unsigned count;
@ -10210,6 +10214,7 @@ BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote)
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
case PNK_RAW_UNDEFINED:
if (!emit1(pn->getOp()))
return false;
break;

Просмотреть файл

@ -378,6 +378,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result)
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
case PNK_RAW_UNDEFINED:
case PNK_THIS:
case PNK_ELISION:
case PNK_NUMBER:
@ -468,6 +469,7 @@ IsEffectless(ParseNode* node)
node->isKind(PNK_TEMPLATE_STRING) ||
node->isKind(PNK_NUMBER) ||
node->isKind(PNK_NULL) ||
node->isKind(PNK_RAW_UNDEFINED) ||
node->isKind(PNK_FUNCTION) ||
node->isKind(PNK_GENEXP);
}
@ -492,6 +494,7 @@ Boolish(ParseNode* pn)
case PNK_FALSE:
case PNK_NULL:
case PNK_RAW_UNDEFINED:
return Falsy;
case PNK_VOID: {
@ -1643,6 +1646,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
case PNK_RAW_UNDEFINED:
case PNK_ELISION:
case PNK_NUMBER:
case PNK_DEBUGGER:

Просмотреть файл

@ -183,6 +183,10 @@ class FullParseHandler
return new_<NullLiteral>(pos);
}
ParseNode* newRawUndefinedLiteral(const TokenPos& pos) {
return new_<RawUndefinedLiteral>(pos);
}
// The Boxer object here is any object that can allocate ObjectBoxes.
// Specifically, a Boxer has a .newObjectBox(T) method that accepts a
// Rooted<RegExpObject*> argument and returns an ObjectBox*.

Просмотреть файл

@ -316,7 +316,8 @@ class NameResolver
return false;
// Next is the callsite object node. This node only contains
// internal strings and an array -- no user-controlled expressions.
// internal strings or undefined and an array -- no user-controlled
// expressions.
element = element->pn_next;
#ifdef DEBUG
{
@ -326,7 +327,7 @@ class NameResolver
for (ParseNode* kid = array->pn_head; kid; kid = kid->pn_next)
MOZ_ASSERT(kid->isKind(PNK_TEMPLATE_STRING));
for (ParseNode* next = array->pn_next; next; next = next->pn_next)
MOZ_ASSERT(next->isKind(PNK_TEMPLATE_STRING));
MOZ_ASSERT(next->isKind(PNK_TEMPLATE_STRING) || next->isKind(PNK_RAW_UNDEFINED));
}
#endif
@ -382,6 +383,7 @@ class NameResolver
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
case PNK_RAW_UNDEFINED:
case PNK_ELISION:
case PNK_GENERATOR:
case PNK_NUMBER:

Просмотреть файл

@ -190,6 +190,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
case PNK_RAW_UNDEFINED:
case PNK_ELISION:
case PNK_GENERATOR:
case PNK_NUMBER:
@ -685,6 +686,7 @@ NullaryNode::dump()
case PNK_TRUE: fprintf(stderr, "#true"); break;
case PNK_FALSE: fprintf(stderr, "#false"); break;
case PNK_NULL: fprintf(stderr, "#null"); break;
case PNK_RAW_UNDEFINED: fprintf(stderr, "#undefined"); break;
case PNK_NUMBER: {
ToCStringBuf cbuf;

Просмотреть файл

@ -54,6 +54,7 @@ class ObjectBox;
F(TRUE) \
F(FALSE) \
F(NULL) \
F(RAW_UNDEFINED) \
F(THIS) \
F(FUNCTION) \
F(MODULE) \
@ -406,7 +407,8 @@ IsTypeofKind(ParseNodeKind kind)
* PNK_NUMBER dval pn_dval: double value of numeric literal
* PNK_TRUE, nullary pn_op: JSOp bytecode
* PNK_FALSE,
* PNK_NULL
* PNK_NULL,
* PNK_RAW_UNDEFINED
*
* PNK_THIS, unary pn_kid: '.this' Name if function `this`, else nullptr
* PNK_SUPERBASE unary pn_kid: '.this' Name
@ -686,7 +688,8 @@ class ParseNode
isKind(PNK_STRING) ||
isKind(PNK_TRUE) ||
isKind(PNK_FALSE) ||
isKind(PNK_NULL);
isKind(PNK_NULL) ||
isKind(PNK_RAW_UNDEFINED);
}
/* Return true if this node appears in a Directive Prologue. */
@ -1141,6 +1144,16 @@ class NullLiteral : public ParseNode
explicit NullLiteral(const TokenPos& pos) : ParseNode(PNK_NULL, JSOP_NULL, PN_NULLARY, pos) { }
};
// This is only used internally, currently just for tagged templates.
// It represents the value 'undefined' (aka `void 0`), like NullLiteral
// represents the value 'null'.
class RawUndefinedLiteral : public ParseNode
{
public:
explicit RawUndefinedLiteral(const TokenPos& pos)
: ParseNode(PNK_RAW_UNDEFINED, JSOP_UNDEFINED, PN_NULLARY, pos) { }
};
class BooleanLiteral : public ParseNode
{
public:
@ -1361,6 +1374,7 @@ ParseNode::isConstant()
case PNK_STRING:
case PNK_TEMPLATE_STRING:
case PNK_NULL:
case PNK_RAW_UNDEFINED:
case PNK_FALSE:
case PNK_TRUE:
return true;

Просмотреть файл

@ -3085,7 +3085,7 @@ template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::templateLiteral(YieldHandling yieldHandling)
{
Node pn = noSubstitutionTemplate();
Node pn = noSubstitutionUntaggedTemplate();
if (!pn)
return null();
@ -3098,7 +3098,7 @@ Parser<ParseHandler>::templateLiteral(YieldHandling yieldHandling)
if (!addExprAndGetNextTemplStrToken(yieldHandling, nodeList, &tt))
return null();
pn = noSubstitutionTemplate();
pn = noSubstitutionUntaggedTemplate();
if (!pn)
return null();
@ -3321,7 +3321,7 @@ template <typename ParseHandler>
bool
Parser<ParseHandler>::appendToCallSiteObj(Node callSiteObj)
{
Node cookedNode = noSubstitutionTemplate();
Node cookedNode = noSubstitutionTaggedTemplate();
if (!cookedNode)
return false;
@ -8711,8 +8711,23 @@ Parser<ParseHandler>::stringLiteral()
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::noSubstitutionTemplate()
Parser<ParseHandler>::noSubstitutionTaggedTemplate()
{
if (tokenStream.hasInvalidTemplateEscape()) {
tokenStream.clearInvalidTemplateEscape();
return handler.newRawUndefinedLiteral(pos());
}
return handler.newTemplateStringLiteral(stopStringCompression(), pos());
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::noSubstitutionUntaggedTemplate()
{
if (!tokenStream.checkForInvalidTemplateEscapeError())
return null();
return handler.newTemplateStringLiteral(stopStringCompression(), pos());
}
@ -9425,7 +9440,7 @@ Parser<ParseHandler>::primaryExpr(YieldHandling yieldHandling, TripledotHandling
return templateLiteral(yieldHandling);
case TOK_NO_SUBS_TEMPLATE:
return noSubstitutionTemplate();
return noSubstitutionUntaggedTemplate();
case TOK_STRING:
return stringLiteral();

Просмотреть файл

@ -1053,7 +1053,8 @@ class Parser final : public ParserBase, private JS::AutoGCRooter
JSAtom* stopStringCompression();
Node stringLiteral();
Node noSubstitutionTemplate();
Node noSubstitutionTaggedTemplate();
Node noSubstitutionUntaggedTemplate();
Node templateLiteral(YieldHandling yieldHandling);
bool taggedTemplate(YieldHandling yieldHandling, Node nodeList, TokenKind tt);
bool appendToCallSiteObj(Node callSiteObj);

Просмотреть файл

@ -224,6 +224,7 @@ class SyntaxParseHandler
Node newThisLiteral(const TokenPos& pos, Node thisName) { return NodeGeneric; }
Node newNullLiteral(const TokenPos& pos) { return NodeGeneric; }
Node newRawUndefinedLiteral(const TokenPos& pos) { return NodeGeneric; }
template <class Boxer>
Node newRegExp(RegExpObject* reobj, const TokenPos& pos, Boxer& boxer) { return NodeGeneric; }

Просмотреть файл

@ -1908,56 +1908,6 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
return false;
}
bool
TokenStream::matchBracedUnicode(bool* matched, uint32_t* cp)
{
int32_t c;
if (!peekChar(&c))
return false;
if (c != '{') {
*matched = false;
return true;
}
consumeKnownChar('{');
uint32_t start = userbuf.offset();
bool first = true;
uint32_t code = 0;
do {
int32_t c = getCharIgnoreEOL();
if (c == EOF) {
error(JSMSG_MALFORMED_ESCAPE, "Unicode");
return false;
}
if (c == '}') {
if (first) {
error(JSMSG_MALFORMED_ESCAPE, "Unicode");
return false;
}
break;
}
if (!JS7_ISHEX(c)) {
error(JSMSG_MALFORMED_ESCAPE, "Unicode");
return false;
}
code = (code << 4) | JS7_UNHEX(c);
if (code > unicode::NonBMPMax) {
errorAt(start, JSMSG_UNICODE_OVERFLOW, "escape sequence");
return false;
}
first = false;
} while (true);
*matched = true;
*cp = code;
return true;
}
bool
TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
{
@ -1980,6 +1930,10 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
}
if (c == '\\') {
// When parsing templates, we don't immediately report errors for
// invalid escapes; these are handled by the parser.
// In those cases we don't append to tokenbuf, since it won't be
// read.
switch (c = getChar()) {
case 'b': c = '\b'; break;
case 'f': c = '\f'; break;
@ -1995,11 +1949,74 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
// Unicode character specification.
case 'u': {
bool matched;
uint32_t code;
if (!matchBracedUnicode(&matched, &code))
uint32_t code = 0;
int32_t c2;
if (!peekChar(&c2))
return false;
if (matched) {
uint32_t start = userbuf.offset() - 2;
if (c2 == '{') {
consumeKnownChar('{');
bool first = true;
bool valid = true;
do {
int32_t c = getCharIgnoreEOL();
if (c == EOF) {
if (parsingTemplate) {
setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
valid = false;
break;
}
reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
return false;
}
if (c == '}') {
if (first) {
if (parsingTemplate) {
setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
valid = false;
break;
}
reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
return false;
}
break;
}
if (!JS7_ISHEX(c)) {
if (parsingTemplate) {
// We put the character back so that we read
// it on the next pass, which matters if it
// was '`' or '\'.
ungetCharIgnoreEOL(c);
setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
valid = false;
break;
}
reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
return false;
}
code = (code << 4) | JS7_UNHEX(c);
if (code > unicode::NonBMPMax) {
if (parsingTemplate) {
setInvalidTemplateEscape(start + 3, InvalidEscapeType::UnicodeOverflow);
valid = false;
break;
}
reportInvalidEscapeError(start + 3, InvalidEscapeType::UnicodeOverflow);
return false;
}
first = false;
} while (true);
if (!valid)
continue;
MOZ_ASSERT(code <= unicode::NonBMPMax);
if (code < unicode::NonBMPMin) {
c = code;
@ -2021,7 +2038,11 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
c = (c << 4) + JS7_UNHEX(cp[3]);
skipChars(4);
} else {
error(JSMSG_MALFORMED_ESCAPE, "Unicode");
if (parsingTemplate) {
setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
continue;
}
reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
return false;
}
break;
@ -2034,7 +2055,12 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
c = (JS7_UNHEX(cp[0]) << 4) + JS7_UNHEX(cp[1]);
skipChars(2);
} else {
error(JSMSG_MALFORMED_ESCAPE, "hexadecimal");
uint32_t start = userbuf.offset() - 2;
if (parsingTemplate) {
setInvalidTemplateEscape(start, InvalidEscapeType::Hexadecimal);
continue;
}
reportInvalidEscapeError(start, InvalidEscapeType::Hexadecimal);
return false;
}
break;
@ -2051,8 +2077,8 @@ TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
// Strict mode code allows only \0, then a non-digit.
if (val != 0 || JS7_ISDEC(c)) {
if (parsingTemplate) {
error(JSMSG_DEPRECATED_OCTAL);
return false;
setInvalidTemplateEscape(userbuf.offset() - 2, InvalidEscapeType::Octal);
continue;
}
if (!reportStrictModeError(JSMSG_DEPRECATED_OCTAL))
return false;

Просмотреть файл

@ -80,6 +80,20 @@ struct TokenPos {
enum DecimalPoint { NoDecimal = false, HasDecimal = true };
enum class InvalidEscapeType {
// No invalid character escapes.
None,
// A malformed \x escape.
Hexadecimal,
// A malformed \u escape.
Unicode,
// An otherwise well-formed \u escape which represents a
// codepoint > 10FFFF.
UnicodeOverflow,
// An octal escape in a template token.
Octal
};
class TokenStream;
struct Token
@ -361,6 +375,23 @@ class MOZ_STACK_CLASS TokenStream
bool hadError() const { return flags.hadError; }
void clearSawOctalEscape() { flags.sawOctalEscape = false; }
bool hasInvalidTemplateEscape() const {
return invalidTemplateEscapeType != InvalidEscapeType::None;
}
void clearInvalidTemplateEscape() {
invalidTemplateEscapeType = InvalidEscapeType::None;
}
// If there is an invalid escape in a template, report it and return false,
// otherwise return true.
bool checkForInvalidTemplateEscapeError() {
if (invalidTemplateEscapeType == InvalidEscapeType::None)
return true;
reportInvalidEscapeError(invalidTemplateEscapeOffset, invalidTemplateEscapeType);
return false;
}
// TokenStream-specific error reporters.
bool reportError(unsigned errorNumber, ...);
bool reportErrorNoOffset(unsigned errorNumber, ...);
@ -422,6 +453,33 @@ class MOZ_STACK_CLASS TokenStream
bool reportStrictModeError(unsigned errorNumber, ...);
bool strictMode() const { return strictModeGetter && strictModeGetter->strictMode(); }
void setInvalidTemplateEscape(uint32_t offset, InvalidEscapeType type) {
MOZ_ASSERT(type != InvalidEscapeType::None);
if (invalidTemplateEscapeType != InvalidEscapeType::None)
return;
invalidTemplateEscapeOffset = offset;
invalidTemplateEscapeType = type;
}
void reportInvalidEscapeError(uint32_t offset, InvalidEscapeType type) {
switch (type) {
case InvalidEscapeType::None:
MOZ_ASSERT_UNREACHABLE("unexpected InvalidEscapeType");
return;
case InvalidEscapeType::Hexadecimal:
errorAt(offset, JSMSG_MALFORMED_ESCAPE, "hexadecimal");
return;
case InvalidEscapeType::Unicode:
errorAt(offset, JSMSG_MALFORMED_ESCAPE, "Unicode");
return;
case InvalidEscapeType::UnicodeOverflow:
errorAt(offset, JSMSG_UNICODE_OVERFLOW, "escape sequence");
return;
case InvalidEscapeType::Octal:
errorAt(offset, JSMSG_DEPRECATED_OCTAL);
return;
}
}
static JSAtom* atomize(ExclusiveContext* cx, CharBuffer& cb);
MOZ_MUST_USE bool putIdentInTokenbuf(const char16_t* identStart);
@ -442,6 +500,9 @@ class MOZ_STACK_CLASS TokenStream
bool awaitIsKeyword = false;
friend class AutoAwaitIsKeyword;
uint32_t invalidTemplateEscapeOffset = 0;
InvalidEscapeType invalidTemplateEscapeType = InvalidEscapeType::None;
public:
typedef Token::Modifier Modifier;
static constexpr Modifier None = Token::None;
@ -955,7 +1016,6 @@ class MOZ_STACK_CLASS TokenStream
MOZ_MUST_USE bool getTokenInternal(TokenKind* ttp, Modifier modifier);
MOZ_MUST_USE bool matchBracedUnicode(bool* matched, uint32_t* code);
MOZ_MUST_USE bool getStringOrTemplateToken(int untilChar, Token** tp);
int32_t getChar();

Просмотреть файл

@ -287,5 +287,177 @@ assertEq(String.raw`h\r\ney${4}there\n`, "h\\r\\ney4there\\n");
assertEq(String.raw`hey`, "hey");
assertEq(String.raw``, "");
// Invalid escape sequences
check(raw`\01`, ["\\01"]);
check(raw`\01${0}right`, ["\\01","right"]);
check(raw`left${0}\01`, ["left","\\01"]);
check(raw`left${0}\01${1}right`, ["left","\\01","right"]);
check(raw`\1`, ["\\1"]);
check(raw`\1${0}right`, ["\\1","right"]);
check(raw`left${0}\1`, ["left","\\1"]);
check(raw`left${0}\1${1}right`, ["left","\\1","right"]);
check(raw`\xg`, ["\\xg"]);
check(raw`\xg${0}right`, ["\\xg","right"]);
check(raw`left${0}\xg`, ["left","\\xg"]);
check(raw`left${0}\xg${1}right`, ["left","\\xg","right"]);
check(raw`\xAg`, ["\\xAg"]);
check(raw`\xAg${0}right`, ["\\xAg","right"]);
check(raw`left${0}\xAg`, ["left","\\xAg"]);
check(raw`left${0}\xAg${1}right`, ["left","\\xAg","right"]);
check(raw`\u0`, ["\\u0"]);
check(raw`\u0${0}right`, ["\\u0","right"]);
check(raw`left${0}\u0`, ["left","\\u0"]);
check(raw`left${0}\u0${1}right`, ["left","\\u0","right"]);
check(raw`\u0g`, ["\\u0g"]);
check(raw`\u0g${0}right`, ["\\u0g","right"]);
check(raw`left${0}\u0g`, ["left","\\u0g"]);
check(raw`left${0}\u0g${1}right`, ["left","\\u0g","right"]);
check(raw`\u00g`, ["\\u00g"]);
check(raw`\u00g${0}right`, ["\\u00g","right"]);
check(raw`left${0}\u00g`, ["left","\\u00g"]);
check(raw`left${0}\u00g${1}right`, ["left","\\u00g","right"]);
check(raw`\u000g`, ["\\u000g"]);
check(raw`\u000g${0}right`, ["\\u000g","right"]);
check(raw`left${0}\u000g`, ["left","\\u000g"]);
check(raw`left${0}\u000g${1}right`, ["left","\\u000g","right"]);
check(raw`\u{}`, ["\\u{}"]);
check(raw`\u{}${0}right`, ["\\u{}","right"]);
check(raw`left${0}\u{}`, ["left","\\u{}"]);
check(raw`left${0}\u{}${1}right`, ["left","\\u{}","right"]);
check(raw`\u{-0}`, ["\\u{-0}"]);
check(raw`\u{-0}${0}right`, ["\\u{-0}","right"]);
check(raw`left${0}\u{-0}`, ["left","\\u{-0}"]);
check(raw`left${0}\u{-0}${1}right`, ["left","\\u{-0}","right"]);
check(raw`\u{g}`, ["\\u{g}"]);
check(raw`\u{g}${0}right`, ["\\u{g}","right"]);
check(raw`left${0}\u{g}`, ["left","\\u{g}"]);
check(raw`left${0}\u{g}${1}right`, ["left","\\u{g}","right"]);
check(raw`\u{0`, ["\\u{0"]);
check(raw`\u{0${0}right`, ["\\u{0","right"]);
check(raw`left${0}\u{0`, ["left","\\u{0"]);
check(raw`left${0}\u{0${1}right`, ["left","\\u{0","right"]);
check(raw`\u{\u{0}`, ["\\u{\\u{0}"]);
check(raw`\u{\u{0}${0}right`, ["\\u{\\u{0}","right"]);
check(raw`left${0}\u{\u{0}`, ["left","\\u{\\u{0}"]);
check(raw`left${0}\u{\u{0}${1}right`, ["left","\\u{\\u{0}","right"]);
check(raw`\u{110000}`, ["\\u{110000}"]);
check(raw`\u{110000}${0}right`, ["\\u{110000}","right"]);
check(raw`left${0}\u{110000}`, ["left","\\u{110000}"]);
check(raw`left${0}\u{110000}${1}right`, ["left","\\u{110000}","right"]);
check(cooked`\01`, [void 0]);
check(cooked`\01${0}right`, [void 0,"right"]);
check(cooked`left${0}\01`, ["left",void 0]);
check(cooked`left${0}\01${1}right`, ["left",void 0,"right"]);
check(cooked`\1`, [void 0]);
check(cooked`\1${0}right`, [void 0,"right"]);
check(cooked`left${0}\1`, ["left",void 0]);
check(cooked`left${0}\1${1}right`, ["left",void 0,"right"]);
check(cooked`\xg`, [void 0]);
check(cooked`\xg${0}right`, [void 0,"right"]);
check(cooked`left${0}\xg`, ["left",void 0]);
check(cooked`left${0}\xg${1}right`, ["left",void 0,"right"]);
check(cooked`\xAg`, [void 0]);
check(cooked`\xAg${0}right`, [void 0,"right"]);
check(cooked`left${0}\xAg`, ["left",void 0]);
check(cooked`left${0}\xAg${1}right`, ["left",void 0,"right"]);
check(cooked`\u0`, [void 0]);
check(cooked`\u0${0}right`, [void 0,"right"]);
check(cooked`left${0}\u0`, ["left",void 0]);
check(cooked`left${0}\u0${1}right`, ["left",void 0,"right"]);
check(cooked`\u0g`, [void 0]);
check(cooked`\u0g${0}right`, [void 0,"right"]);
check(cooked`left${0}\u0g`, ["left",void 0]);
check(cooked`left${0}\u0g${1}right`, ["left",void 0,"right"]);
check(cooked`\u00g`, [void 0]);
check(cooked`\u00g${0}right`, [void 0,"right"]);
check(cooked`left${0}\u00g`, ["left",void 0]);
check(cooked`left${0}\u00g${1}right`, ["left",void 0,"right"]);
check(cooked`\u000g`, [void 0]);
check(cooked`\u000g${0}right`, [void 0,"right"]);
check(cooked`left${0}\u000g`, ["left",void 0]);
check(cooked`left${0}\u000g${1}right`, ["left",void 0,"right"]);
check(cooked`\u{}`, [void 0]);
check(cooked`\u{}${0}right`, [void 0,"right"]);
check(cooked`left${0}\u{}`, ["left",void 0]);
check(cooked`left${0}\u{}${1}right`, ["left",void 0,"right"]);
check(cooked`\u{-0}`, [void 0]);
check(cooked`\u{-0}${0}right`, [void 0,"right"]);
check(cooked`left${0}\u{-0}`, ["left",void 0]);
check(cooked`left${0}\u{-0}${1}right`, ["left",void 0,"right"]);
check(cooked`\u{g}`, [void 0]);
check(cooked`\u{g}${0}right`, [void 0,"right"]);
check(cooked`left${0}\u{g}`, ["left",void 0]);
check(cooked`left${0}\u{g}${1}right`, ["left",void 0,"right"]);
check(cooked`\u{0`, [void 0]);
check(cooked`\u{0${0}right`, [void 0,"right"]);
check(cooked`left${0}\u{0`, ["left",void 0]);
check(cooked`left${0}\u{0${1}right`, ["left",void 0,"right"]);
check(cooked`\u{\u{0}`, [void 0]);
check(cooked`\u{\u{0}${0}right`, [void 0,"right"]);
check(cooked`left${0}\u{\u{0}`, ["left",void 0]);
check(cooked`left${0}\u{\u{0}${1}right`, ["left",void 0,"right"]);
check(cooked`\u{110000}`, [void 0]);
check(cooked`\u{110000}${0}right`, [void 0,"right"]);
check(cooked`left${0}\u{110000}`, ["left",void 0]);
check(cooked`left${0}\u{110000}${1}right`, ["left",void 0,"right"]);
syntaxError("`\\01`");
syntaxError("`\\01${0}right`");
syntaxError("`left${0}\\01`");
syntaxError("`left${0}\\01${1}right`");
syntaxError("`\\1`");
syntaxError("`\\1${0}right`");
syntaxError("`left${0}\\1`");
syntaxError("`left${0}\\1${1}right`");
syntaxError("`\\xg`");
syntaxError("`\\xg${0}right`");
syntaxError("`left${0}\\xg`");
syntaxError("`left${0}\\xg${1}right`");
syntaxError("`\\xAg`");
syntaxError("`\\xAg${0}right`");
syntaxError("`left${0}\\xAg`");
syntaxError("`left${0}\\xAg${1}right`");
syntaxError("`\\u0`");
syntaxError("`\\u0${0}right`");
syntaxError("`left${0}\\u0`");
syntaxError("`left${0}\\u0${1}right`");
syntaxError("`\\u0g`");
syntaxError("`\\u0g${0}right`");
syntaxError("`left${0}\\u0g`");
syntaxError("`left${0}\\u0g${1}right`");
syntaxError("`\\u00g`");
syntaxError("`\\u00g${0}right`");
syntaxError("`left${0}\\u00g`");
syntaxError("`left${0}\\u00g${1}right`");
syntaxError("`\\u000g`");
syntaxError("`\\u000g${0}right`");
syntaxError("`left${0}\\u000g`");
syntaxError("`left${0}\\u000g${1}right`");
syntaxError("`\\u{}`");
syntaxError("`\\u{}${0}right`");
syntaxError("`left${0}\\u{}`");
syntaxError("`left${0}\\u{}${1}right`");
syntaxError("`\\u{-0}`");
syntaxError("`\\u{-0}${0}right`");
syntaxError("`left${0}\\u{-0}`");
syntaxError("`left${0}\\u{-0}${1}right`");
syntaxError("`\\u{g}`");
syntaxError("`\\u{g}${0}right`");
syntaxError("`left${0}\\u{g}`");
syntaxError("`left${0}\\u{g}${1}right`");
syntaxError("`\\u{0`");
syntaxError("`\\u{0${0}right`");
syntaxError("`left${0}\\u{0`");
syntaxError("`left${0}\\u{0${1}right`");
syntaxError("`\\u{\\u{0}`");
syntaxError("`\\u{\\u{0}${0}right`");
syntaxError("`left${0}\\u{\\u{0}`");
syntaxError("`left${0}\\u{\\u{0}${1}right`");
syntaxError("`\\u{110000}`");
syntaxError("`\\u{110000}${0}right`");
syntaxError("`left${0}\\u{110000}`");
syntaxError("`left${0}\\u{110000}${1}right`");
reportCompare(0, 0, "ok");

Просмотреть файл

@ -7,6 +7,8 @@ assertStringExpr("`hey\nthere`", literal("hey\nthere"));
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("func`hey\\x`", taggedTemplate(ident("func"), template(["hey\\x"], [void 0])));
assertExpr("func`hey${4}\\x`", taggedTemplate(ident("func"), template(["hey","\\x"], ["hey",void 0], lit(4))));
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"])));