"src/harmony-array.js",
"src/harmony-typedarray.js",
"src/harmony-classes.js",
- "src/harmony-tostring.js"
+ "src/harmony-tostring.js",
+ "src/harmony-templates.js"
]
outputs = [
F(dot_result, ".result") \
F(empty, "") \
F(eval, "eval") \
+ F(get_template_callsite, "GetTemplateCallSite") \
F(initialize_const_global, "initializeConstGlobal") \
F(initialize_var_global, "initializeVarGlobal") \
F(make_reference_error, "MakeReferenceErrorEmbedded") \
EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_arrow_functions)
EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_numeric_literals)
EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_tostring)
+EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_templates)
void Genesis::InstallNativeFunctions_harmony_proxies() {
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_numeric_literals)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_tostring)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_proxies)
+EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_templates)
void Genesis::InitializeGlobal_harmony_regexps() {
Handle<JSObject> builtins(native_context()->builtins());
static const char* harmony_numeric_literals_natives[] = {NULL};
static const char* harmony_tostring_natives[] = {"native harmony-tostring.js",
NULL};
+ static const char* harmony_templates_natives[] = {
+ "native harmony-templates.js", NULL};
for (int i = ExperimentalNatives::GetDebuggerCount();
i < ExperimentalNatives::GetBuiltinsCount(); i++) {
V(harmony_regexps, "harmony regular expression extensions") \
V(harmony_arrow_functions, "harmony arrow functions") \
V(harmony_tostring, "harmony toString") \
- V(harmony_proxies, "harmony proxies")
+ V(harmony_proxies, "harmony proxies") \
+ V(harmony_templates, "harmony template literals")
// Features that are complete (but still behind --harmony/es-staging flag).
#define HARMONY_STAGED(V) V(harmony_strings, "harmony string methods")
--- /dev/null
+// Copyright 2014 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+function GetTemplateCallSite(siteObj, rawStrings) {
+ // TODO(caitp): ensure same template callsite is used for subsequent tag calls
+
+ %AddNamedProperty(siteObj, "raw", %ObjectFreeze(rawStrings),
+ READ_ONLY | DONT_ENUM | DONT_DELETE);
+
+ return %ObjectFreeze(siteObj);
+}
unexpected_reserved: ["Unexpected reserved word"],
unexpected_strict_reserved: ["Unexpected strict mode reserved word"],
unexpected_eos: ["Unexpected end of input"],
+ unexpected_template_string: ["Unexpected template string"],
malformed_regexp: ["Invalid regular expression: /", "%0", "/: ", "%1"],
malformed_regexp_flags: ["Invalid regular expression flags"],
unterminated_regexp: ["Invalid regular expression: missing /"],
+ unterminated_template: ["Unterminated template literal"],
+ unterminated_template_expr: ["Missing } in template expression"],
regexp_flags: ["Cannot supply flags when constructing one RegExp from another"],
incompatible_method_receiver: ["Method ", "%0", " called on incompatible receiver ", "%1"],
multiple_defaults_in_switch: ["More than one default clause in switch statement"],
set_allow_harmony_numeric_literals(FLAG_harmony_numeric_literals);
set_allow_classes(FLAG_harmony_classes);
set_allow_harmony_object_literals(FLAG_harmony_object_literals);
+ set_allow_harmony_templates(FLAG_harmony_templates);
for (int feature = 0; feature < v8::Isolate::kUseCounterFeatureCount;
++feature) {
use_counts_[feature] = 0;
reusable_preparser_->set_allow_classes(allow_classes());
reusable_preparser_->set_allow_harmony_object_literals(
allow_harmony_object_literals());
+ reusable_preparser_->set_allow_harmony_templates(allow_harmony_templates());
}
PreParser::PreParseResult result =
reusable_preparser_->PreParseLazyFunction(strict_mode(),
log_ = NULL;
}
}
+
+
+ParserTraits::TemplateLiteralState Parser::OpenTemplateLiteral(int pos) {
+ return new (zone()) ParserTraits::TemplateLiteral(zone(), pos);
+}
+
+
+void Parser::AddTemplateSpan(TemplateLiteralState* state, bool tail) {
+ int pos = scanner()->location().beg_pos;
+ int end = scanner()->location().end_pos - (tail ? 1 : 2);
+ const AstRawString* tv = scanner()->CurrentSymbol(ast_value_factory());
+ Literal* cooked = factory()->NewStringLiteral(tv, pos);
+ (*state)->AddTemplateSpan(cooked, end, zone());
+}
+
+
+void Parser::AddTemplateExpression(TemplateLiteralState* state,
+ Expression* expression) {
+ (*state)->AddExpression(expression, zone());
+}
+
+
+Expression* Parser::CloseTemplateLiteral(TemplateLiteralState* state, int start,
+ Expression* tag) {
+ TemplateLiteral* lit = *state;
+ int pos = lit->position();
+ const ZoneList<Expression*>* cooked_strings = lit->cooked();
+ const ZoneList<Expression*>* expressions = lit->expressions();
+ CHECK(cooked_strings->length() == (expressions->length() + 1));
+
+ if (!tag) {
+ // Build tree of BinaryOps to simplify code-generation
+ Expression* expr = NULL;
+
+ if (expressions->length() == 0) {
+ // Simple case: treat as string literal
+ expr = cooked_strings->at(0);
+ } else {
+ int i;
+ Expression* cooked_str = cooked_strings->at(0);
+ expr = factory()->NewBinaryOperation(
+ Token::ADD, cooked_str, expressions->at(0), cooked_str->position());
+ for (i = 1; i < expressions->length(); ++i) {
+ cooked_str = cooked_strings->at(i);
+ expr = factory()->NewBinaryOperation(
+ Token::ADD, expr, factory()->NewBinaryOperation(
+ Token::ADD, cooked_str, expressions->at(i),
+ cooked_str->position()),
+ cooked_str->position());
+ }
+ cooked_str = cooked_strings->at(i);
+ expr = factory()->NewBinaryOperation(Token::ADD, expr, cooked_str,
+ cooked_str->position());
+ }
+ return expr;
+ } else {
+ ZoneList<Expression*>* raw_strings = TemplateRawStrings(lit);
+ Handle<String> source(String::cast(script()->source()));
+
+ int cooked_idx = function_state_->NextMaterializedLiteralIndex();
+ int raw_idx = function_state_->NextMaterializedLiteralIndex();
+
+ // GetTemplateCallSite
+ ZoneList<Expression*>* args = new (zone()) ZoneList<Expression*>(4, zone());
+ args->Add(factory()->NewArrayLiteral(
+ const_cast<ZoneList<Expression*>*>(cooked_strings),
+ cooked_idx, pos),
+ zone());
+ args->Add(
+ factory()->NewArrayLiteral(
+ const_cast<ZoneList<Expression*>*>(raw_strings), raw_idx, pos),
+ zone());
+ this->CheckPossibleEvalCall(tag, scope_);
+ Expression* call_site = factory()->NewCallRuntime(
+ ast_value_factory()->get_template_callsite_string(), NULL, args, start);
+
+ // Call TagFn
+ ZoneList<Expression*>* call_args =
+ new (zone()) ZoneList<Expression*>(expressions->length() + 1, zone());
+ call_args->Add(call_site, zone());
+ call_args->AddAll(*expressions, zone());
+ return factory()->NewCall(tag, call_args, pos);
+ }
+}
+
+
+ZoneList<Expression*>* Parser::TemplateRawStrings(const TemplateLiteral* lit) {
+ const ZoneList<int>* lengths = lit->lengths();
+ const ZoneList<Expression*>* cooked_strings = lit->cooked();
+ int total = lengths->length();
+ ZoneList<Expression*>* raw_strings;
+
+ // Given a TemplateLiteral, produce a list of raw strings, used for generating
+ // a CallSite object for a tagged template invocations.
+ //
+ // A raw string will consist of the unescaped characters of a template span,
+ // with end-of-line sequences normalized to U+000A LINE FEEDs, and without
+ // leading or trailing template delimiters.
+ //
+
+ DCHECK(total);
+
+ Handle<String> source(String::cast(script()->source()));
+
+ raw_strings = new (zone()) ZoneList<Expression*>(total, zone());
+
+ for (int index = 0; index < total; ++index) {
+ int span_start = cooked_strings->at(index)->position() + 1;
+ int span_end = lengths->at(index) - 1;
+ int length;
+ int to_index = 0;
+
+ SmartArrayPointer<char> raw_chars =
+ source->ToCString(ALLOW_NULLS, FAST_STRING_TRAVERSAL, span_start,
+ span_end, &length);
+
+ // Normalize raw line-feeds. [U+000D U+000A] (CRLF) and [U+000D] (CR) must
+ // be translated into U+000A (LF).
+ for (int from_index = 0; from_index < length; ++from_index) {
+ char ch = raw_chars[from_index];
+ if (ch == '\r') {
+ ch = '\n';
+ if (from_index + 1 < length && raw_chars[from_index + 1] == '\n') {
+ ++from_index;
+ }
+ }
+ raw_chars[to_index++] = ch;
+ }
+
+ const AstRawString* raw_str = ast_value_factory()->GetOneByteString(
+ OneByteVector(raw_chars.get(), to_index));
+ Literal* raw_lit = factory()->NewStringLiteral(raw_str, span_start - 1);
+ raw_strings->Add(raw_lit, zone());
+ }
+
+ return raw_strings;
+}
} } // namespace v8::internal
V8_INLINE void CheckConflictingVarDeclarations(v8::internal::Scope* scope,
bool* ok);
+ class TemplateLiteral : public ZoneObject {
+ public:
+ TemplateLiteral(Zone* zone, int pos)
+ : cooked_(8, zone),
+ lengths_(8, zone),
+ expressions_(8, zone),
+ pos_(pos) {}
+
+ const ZoneList<Expression*>* cooked() const { return &cooked_; }
+ const ZoneList<int>* lengths() const { return &lengths_; }
+ const ZoneList<Expression*>* expressions() const { return &expressions_; }
+ int position() const { return pos_; }
+
+ void AddTemplateSpan(Literal* cooked, int end, Zone* zone) {
+ DCHECK_NOT_NULL(cooked);
+ cooked_.Add(cooked, zone);
+ lengths_.Add(end - cooked->position(), zone);
+ }
+
+ void AddExpression(Expression* expression, Zone* zone) {
+ DCHECK_NOT_NULL(expression);
+ expressions_.Add(expression, zone);
+ }
+
+ private:
+ ZoneList<Expression*> cooked_;
+ ZoneList<int> lengths_;
+ ZoneList<Expression*> expressions_;
+ int pos_;
+ };
+
+ typedef TemplateLiteral* TemplateLiteralState;
+
+ V8_INLINE TemplateLiteralState OpenTemplateLiteral(int pos);
+ V8_INLINE void AddTemplateSpan(TemplateLiteralState* state, bool tail);
+ V8_INLINE void AddTemplateExpression(TemplateLiteralState* state,
+ Expression* expression);
+ V8_INLINE Expression* CloseTemplateLiteral(TemplateLiteralState* state,
+ int start, Expression* tag);
+ V8_INLINE Expression* NoTemplateTag() { return NULL; }
+
private:
Parser* parser_;
};
void ThrowPendingError();
+ TemplateLiteralState OpenTemplateLiteral(int pos);
+ void AddTemplateSpan(TemplateLiteralState* state, bool tail);
+ void AddTemplateExpression(TemplateLiteralState* state,
+ Expression* expression);
+ Expression* CloseTemplateLiteral(TemplateLiteralState* state, int start,
+ Expression* tag);
+ ZoneList<Expression*>* TemplateRawStrings(const TemplateLiteral* lit);
Scanner scanner_;
PreParser* reusable_preparser_;
Scope* original_scope_; // for ES5 function declarations in sloppy eval
DISALLOW_IMPLICIT_CONSTRUCTORS(CompileTimeValue);
};
+
+ParserTraits::TemplateLiteralState ParserTraits::OpenTemplateLiteral(int pos) {
+ return parser_->OpenTemplateLiteral(pos);
+}
+
+
+void ParserTraits::AddTemplateSpan(TemplateLiteralState* state, bool tail) {
+ parser_->AddTemplateSpan(state, tail);
+}
+
+
+void ParserTraits::AddTemplateExpression(TemplateLiteralState* state,
+ Expression* expression) {
+ parser_->AddTemplateExpression(state, expression);
+}
+
+
+Expression* ParserTraits::CloseTemplateLiteral(TemplateLiteralState* state,
+ int start, Expression* tag) {
+ return parser_->CloseTemplateLiteral(state, start, tag);
+}
} } // namespace v8::internal
#endif // V8_PARSER_H_
bool allow_harmony_object_literals() const {
return allow_harmony_object_literals_;
}
+ bool allow_harmony_templates() const { return scanner()->HarmonyTemplates(); }
// Setters that determine whether certain syntactical constructs are
// allowed to be parsed by this instance of the parser.
void set_allow_harmony_object_literals(bool allow) {
allow_harmony_object_literals_ = allow;
}
+ void set_allow_harmony_templates(bool allow) {
+ scanner()->SetHarmonyTemplates(allow);
+ }
protected:
enum AllowEvalOrArgumentsAsIdentifier {
bool* ok);
ExpressionT ParseArrowFunctionLiteral(int start_pos, ExpressionT params_ast,
bool* ok);
+ ExpressionT ParseTemplateLiteral(ExpressionT tag, int start, bool* ok);
+ void AddTemplateExpression(ExpressionT);
// Checks if the expression is a valid reference expression (e.g., on the
// left-hand side of assignments). Although ruled out by ECMA as early errors,
return 0;
}
+ struct TemplateLiteralState {};
+
+ TemplateLiteralState OpenTemplateLiteral(int pos) {
+ return TemplateLiteralState();
+ }
+ void AddTemplateSpan(TemplateLiteralState*, bool) {}
+ void AddTemplateExpression(TemplateLiteralState*, PreParserExpression) {}
+ PreParserExpression CloseTemplateLiteral(TemplateLiteralState*, int,
+ PreParserExpression) {
+ return EmptyExpression();
+ }
+ PreParserExpression NoTemplateTag() { return PreParserExpression::Default(); }
static AstValueFactory* ast_value_factory() { return NULL; }
void CheckConflictingVarDeclarations(PreParserScope scope, bool* ok) {}
case Token::FUTURE_STRICT_RESERVED_WORD:
return ReportMessageAt(source_location, strict_mode() == SLOPPY
? "unexpected_token_identifier" : "unexpected_strict_reserved");
+ case Token::TEMPLATE_SPAN:
+ case Token::TEMPLATE_TAIL:
+ return Traits::ReportMessageAt(source_location,
+ "unexpected_template_string");
default:
const char* name = Token::String(token);
DCHECK(name != NULL);
// RegExpLiteral
// ClassLiteral
// '(' Expression ')'
+ // TemplateLiteral
int pos = peek_position();
ExpressionT result = this->EmptyExpression();
break;
}
+ case Token::TEMPLATE_SPAN:
+ case Token::TEMPLATE_TAIL:
+ result =
+ this->ParseTemplateLiteral(Traits::NoTemplateTag(), pos, CHECK_OK);
+ break;
+
case Token::MOD:
if (allow_natives_syntax() || extension_ != NULL) {
result = this->ParseV8Intrinsic(CHECK_OK);
break;
}
+ case Token::TEMPLATE_SPAN:
+ case Token::TEMPLATE_TAIL: {
+ int pos;
+ if (scanner()->current_token() == Token::IDENTIFIER) {
+ pos = position();
+ } else {
+ pos = peek_position();
+ if (result->IsFunctionLiteral() && mode() == PARSE_EAGERLY) {
+ // If the tag function looks like an IIFE, set_parenthesized() to
+ // force eager compilation.
+ result->AsFunctionLiteral()->set_parenthesized();
+ }
+ }
+ result = ParseTemplateLiteral(result, pos, CHECK_OK);
+ break;
+ }
+
case Token::PERIOD: {
Consume(Token::PERIOD);
int pos = position();
template <typename Traits>
typename ParserBase<Traits>::ExpressionT
-ParserBase<Traits>::CheckAndRewriteReferenceExpression(
- ExpressionT expression,
- Scanner::Location location, const char* message, bool* ok) {
+ParserBase<Traits>::ParseTemplateLiteral(ExpressionT tag, int start, bool* ok) {
+ // A TemplateLiteral is made up of 0 or more TEMPLATE_SPAN tokens (literal
+ // text followed by a substitution expression), finalized by a single
+ // TEMPLATE_TAIL.
+ //
+ // In terms of draft language, TEMPLATE_SPAN may be either the TemplateHead or
+ // TemplateMiddle productions, while TEMPLATE_TAIL is either TemplateTail, or
+ // NoSubstitutionTemplate.
+ //
+ // When parsing a TemplateLiteral, we must have scanned either an initial
+ // TEMPLATE_SPAN, or a TEMPLATE_TAIL.
+ CHECK(peek() == Token::TEMPLATE_SPAN || peek() == Token::TEMPLATE_TAIL);
+
+ // If we reach a TEMPLATE_TAIL first, we are parsing a NoSubstitutionTemplate.
+ // In this case we may simply consume the token and build a template with a
+ // single TEMPLATE_SPAN and no expressions.
+ if (peek() == Token::TEMPLATE_TAIL) {
+ Consume(Token::TEMPLATE_TAIL);
+ int pos = position();
+ typename Traits::TemplateLiteralState ts = Traits::OpenTemplateLiteral(pos);
+ Traits::AddTemplateSpan(&ts, true);
+ return Traits::CloseTemplateLiteral(&ts, start, tag);
+ }
+
+ Consume(Token::TEMPLATE_SPAN);
+ int pos = position();
+ typename Traits::TemplateLiteralState ts = Traits::OpenTemplateLiteral(pos);
+ Traits::AddTemplateSpan(&ts, false);
+ Token::Value next;
+
+ // If we open with a TEMPLATE_SPAN, we must scan the subsequent expression,
+ // and repeat if the following token is a TEMPLATE_SPAN as well (in this
+ // case, representing a TemplateMiddle).
+
+ do {
+ next = peek();
+ if (!next) {
+ ReportMessageAt(Scanner::Location(start, peek_position()),
+ "unterminated_template");
+ *ok = false;
+ return Traits::EmptyExpression();
+ }
+
+ int pos = peek_position();
+ ExpressionT expression = this->ParseExpression(true, CHECK_OK);
+ Traits::AddTemplateExpression(&ts, expression);
+
+ if (peek() != Token::RBRACE) {
+ ReportMessageAt(Scanner::Location(pos, peek_position()),
+ "unterminated_template_expr");
+ *ok = false;
+ return Traits::EmptyExpression();
+ }
+
+ // If we didn't die parsing that expression, our next token should be a
+ // TEMPLATE_SPAN or TEMPLATE_TAIL.
+ next = scanner()->ScanTemplateSpan();
+ Next();
+
+ if (!next) {
+ ReportMessageAt(Scanner::Location(start, position()),
+ "unterminated_template");
+ *ok = false;
+ return Traits::EmptyExpression();
+ }
+
+ Traits::AddTemplateSpan(&ts, next == Token::TEMPLATE_TAIL);
+ } while (next == Token::TEMPLATE_SPAN);
+
+ DCHECK_EQ(next, Token::TEMPLATE_TAIL);
+ // Once we've reached a TEMPLATE_TAIL, we can close the TemplateLiteral.
+ return Traits::CloseTemplateLiteral(&ts, start, tag);
+}
+
+
+template <typename Traits>
+typename ParserBase<Traits>::ExpressionT ParserBase<
+ Traits>::CheckAndRewriteReferenceExpression(ExpressionT expression,
+ Scanner::Location location,
+ const char* message, bool* ok) {
if (strict_mode() == STRICT && this->IsIdentifier(expression) &&
this->IsEvalOrArguments(this->AsIdentifier(expression))) {
this->ReportMessageAt(location, "strict_eval_arguments", false);
harmony_scoping_(false),
harmony_modules_(false),
harmony_numeric_literals_(false),
- harmony_classes_(false) { }
+ harmony_classes_(false),
+ harmony_templates_(false) {}
void Scanner::Initialize(Utf16CharacterStream* source) {
token = Select(Token::BIT_NOT);
break;
+ case '`':
+ if (HarmonyTemplates()) {
+ token = ScanTemplateSpan();
+ break;
+ }
+
default:
if (c0_ < 0) {
token = Token::EOS;
}
+Token::Value Scanner::ScanTemplateSpan() {
+ // When scanning a TemplateSpan, we are looking for the following construct:
+ // TEMPLATE_SPAN ::
+ // ` LiteralChars* ${
+ // | } LiteralChars* ${
+ //
+ // TEMPLATE_TAIL ::
+ // ` LiteralChars* `
+ // | } LiteralChar* `
+ //
+ // A TEMPLATE_SPAN should always be followed by an Expression, while a
+ // TEMPLATE_TAIL terminates a TemplateLiteral and does not need to be
+ // followed by an Expression.
+ //
+
+ if (next_.token == Token::RBRACE) {
+ // After parsing an Expression, the source position is incorrect due to
+ // having scanned the brace. Push the RBRACE back into the stream.
+ PushBack('}');
+ }
+
+ next_.location.beg_pos = source_pos();
+ Token::Value result = Token::TEMPLATE_SPAN;
+ DCHECK(c0_ == '`' || c0_ == '}');
+ Advance(); // Consume ` or }
+
+ LiteralScope literal(this);
+ while (true) {
+ uc32 c = c0_;
+ Advance();
+ if (c == '`') {
+ result = Token::TEMPLATE_TAIL;
+ break;
+ } else if (c == '$' && c0_ == '{') {
+ Advance(); // Consume '{'
+ break;
+ } else if (c == '\\') {
+ if (unicode_cache_->IsLineTerminator(c0_)) {
+ // The TV of LineContinuation :: \ LineTerminatorSequence is the empty
+ // code unit sequence.
+ uc32 lastChar = c0_;
+ Advance();
+ if (lastChar == '\r' && c0_ == '\n') Advance();
+ } else if (c0_ == '0') {
+ Advance();
+ AddLiteralChar('0');
+ } else {
+ ScanEscape();
+ }
+ } else if (c < 0) {
+ // Unterminated template literal
+ PushBack(c);
+ break;
+ } else {
+ // The TRV of LineTerminatorSequence :: <CR> is the CV 0x000A.
+ // The TRV of LineTerminatorSequence :: <CR><LF> is the sequence
+ // consisting of the CV 0x000A.
+ if (c == '\r') {
+ if (c0_ == '\n') Advance();
+ c = '\n';
+ }
+ AddLiteralChar(c);
+ }
+ }
+ literal.Complete();
+ next_.location.end_pos = source_pos();
+ next_.token = result;
+ return result;
+}
+
+
void Scanner::ScanDecimalDigits() {
while (IsDecimalDigit(c0_))
AddLiteralCharAdvance();
void SetHarmonyClasses(bool classes) {
harmony_classes_ = classes;
}
+ bool HarmonyTemplates() const { return harmony_templates_; }
+ void SetHarmonyTemplates(bool templates) { harmony_templates_ = templates; }
// Returns true if there was a line terminator before the peek'ed token,
// possibly inside a multi-line comment.
// be empty).
bool ScanRegExpFlags();
+ // Scans the input as a template literal
+ Token::Value ScanTemplateSpan();
+
const LiteralBuffer* source_url() const { return &source_url_; }
const LiteralBuffer* source_mapping_url() const {
return &source_mapping_url_;
bool harmony_numeric_literals_;
// Whether we scan 'class', 'extends', 'static' and 'super' as keywords.
bool harmony_classes_;
+ // Whether we scan TEMPLATE_SPAN and TEMPLATE_TAIL
+ bool harmony_templates_;
};
} } // namespace v8::internal
T(ILLEGAL, "ILLEGAL", 0) \
\
/* Scanner-internal use only. */ \
- T(WHITESPACE, NULL, 0)
+ T(WHITESPACE, NULL, 0) \
+ \
+ /* ES6 Template Literals */ \
+ T(TEMPLATE_SPAN, NULL, 0) \
+ T(TEMPLATE_TAIL, NULL, 0)
class Token {
kAllowHarmonyNumericLiterals,
kAllowArrowFunctions,
kAllowClasses,
- kAllowHarmonyObjectLiterals
+ kAllowHarmonyObjectLiterals,
+ kAllowHarmonyTemplates
};
flags.Contains(kAllowHarmonyObjectLiterals));
parser->set_allow_arrow_functions(flags.Contains(kAllowArrowFunctions));
parser->set_allow_classes(flags.Contains(kAllowClasses));
+ parser->set_allow_harmony_templates(flags.Contains(kAllowHarmonyTemplates));
}
kAllowLazy,
kAllowModules,
kAllowNativesSyntax,
+ kAllowHarmonyTemplates
};
ParserFlag* generated_flags = NULL;
if (flags == NULL) {
NULL};
RunParserSyncTest(context_data, data, kError);
}
+
+
+TEST(ScanTemplateLiterals) {
+ const char* context_data[][2] = {{"'use strict';", ""},
+ {"function foo(){ 'use strict';"
+ " var a, b, c; return ", "}"},
+ {NULL, NULL}};
+
+ const char* data[] = {
+ "``",
+ "`no-subst-template`",
+ "`template-head${a}`",
+ "`${a}`",
+ "`${a}template-tail`",
+ "`template-head${a}template-tail`",
+ "`${a}${b}${c}`",
+ "`a${a}b${b}c${c}`",
+ "`${a}a${b}b${c}c`",
+ "`foo\n\nbar\r\nbaz`",
+ "`foo\n\n${ bar }\r\nbaz`",
+ "`foo${a /* comment */}`",
+ "`foo${a // comment\n}`",
+ "`foo${a \n}`",
+ "`foo${a \r\n}`",
+ "`foo${a \r}`",
+ "`foo${/* comment */ a}`",
+ "`foo${// comment\na}`",
+ "`foo${\n a}`",
+ "`foo${\r\n a}`",
+ "`foo${\r a}`",
+ "`foo${'a' in a}`",
+ NULL};
+ static const ParserFlag always_flags[] = {kAllowHarmonyTemplates};
+ RunParserSyncTest(context_data, data, kSuccess, NULL, 0, always_flags,
+ arraysize(always_flags));
+}
+
+
+TEST(ScanTaggedTemplateLiterals) {
+ const char* context_data[][2] = {{"'use strict';", ""},
+ {"function foo(){ 'use strict';"
+ " function tag() {}"
+ " var a, b, c; return ", "}"},
+ {NULL, NULL}};
+
+ const char* data[] = {
+ "tag ``",
+ "tag `no-subst-template`",
+ "tag`template-head${a}`",
+ "tag `${a}`",
+ "tag `${a}template-tail`",
+ "tag `template-head${a}template-tail`",
+ "tag\n`${a}${b}${c}`",
+ "tag\r\n`a${a}b${b}c${c}`",
+ "tag `${a}a${b}b${c}c`",
+ "tag\t`foo\n\nbar\r\nbaz`",
+ "tag\r`foo\n\n${ bar }\r\nbaz`",
+ "tag`foo${a /* comment */}`",
+ "tag`foo${a // comment\n}`",
+ "tag`foo${a \n}`",
+ "tag`foo${a \r\n}`",
+ "tag`foo${a \r}`",
+ "tag`foo${/* comment */ a}`",
+ "tag`foo${// comment\na}`",
+ "tag`foo${\n a}`",
+ "tag`foo${\r\n a}`",
+ "tag`foo${\r a}`",
+ "tag`foo${'a' in a}`",
+ NULL};
+ static const ParserFlag always_flags[] = {kAllowHarmonyTemplates};
+ RunParserSyncTest(context_data, data, kSuccess, NULL, 0, always_flags,
+ arraysize(always_flags));
+}
+
+
+TEST(ScanUnterminatedTemplateLiterals) {
+ const char* context_data[][2] = {{"'use strict';", ""},
+ {"function foo(){ 'use strict';"
+ " var a, b, c; return ", "}"},
+ {NULL, NULL}};
+
+ const char* data[] = {
+ "`no-subst-template",
+ "`template-head${a}",
+ "`${a}template-tail",
+ "`template-head${a}template-tail",
+ "`${a}${b}${c}",
+ "`a${a}b${b}c${c}",
+ "`${a}a${b}b${c}c",
+ "`foo\n\nbar\r\nbaz",
+ "`foo\n\n${ bar }\r\nbaz",
+ "`foo${a /* comment } */`",
+ "`foo${a /* comment } `*/",
+ "`foo${a // comment}`",
+ "`foo${a \n`",
+ "`foo${a \r\n`",
+ "`foo${a \r`",
+ "`foo${/* comment */ a`",
+ "`foo${// commenta}`",
+ "`foo${\n a`",
+ "`foo${\r\n a`",
+ "`foo${\r a`",
+ "`foo${fn(}`",
+ "`foo${1 if}`",
+ NULL};
+ static const ParserFlag always_flags[] = {kAllowHarmonyTemplates};
+ RunParserSyncTest(context_data, data, kError, NULL, 0, always_flags,
+ arraysize(always_flags));
+}
--- /dev/null
+// Copyright 2014 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --harmony-templates
+
+var num = 5;
+var str = "str";
+function fn() { return "result"; }
+var obj = {
+ num: num,
+ str: str,
+ fn: function() { return "result"; }
+};
+
+(function testBasicExpressions() {
+ assertEquals("foo 5 bar", `foo ${num} bar`);
+ assertEquals("foo str bar", `foo ${str} bar`);
+ assertEquals("foo [object Object] bar", `foo ${obj} bar`);
+ assertEquals("foo result bar", `foo ${fn()} bar`);
+ assertEquals("foo 5 bar", `foo ${obj.num} bar`);
+ assertEquals("foo str bar", `foo ${obj.str} bar`);
+ assertEquals("foo result bar", `foo ${obj.fn()} bar`);
+})();
+
+(function testExpressionsContainingTemplates() {
+ assertEquals("foo bar 5", `foo ${`bar ${num}`}`);
+})();
+
+(function testMultilineTemplates() {
+ assertEquals("foo\n bar\n baz", `foo
+ bar
+ baz`);
+
+ assertEquals("foo\n bar\n baz", eval("`foo\r\n bar\r baz`"));
+})();
+
+(function testLineContinuation() {
+ assertEquals("\n", `\
+
+`);
+})();
+
+(function testTaggedTemplates() {
+ var calls = 0;
+ (function(s) {
+ calls++;
+ })`test`;
+ assertEquals(1, calls);
+
+ calls = 0;
+ // assert tag is invoked in right context
+ obj = {
+ fn: function() {
+ calls++;
+ assertEquals(obj, this);
+ }
+ };
+
+ obj.fn`test`;
+ assertEquals(1, calls);
+
+ calls = 0;
+ // Simple templates only have a callSiteObj
+ (function(s) {
+ calls++;
+ assertEquals(1, arguments.length);
+ })`test`;
+ assertEquals(1, calls);
+
+ // Templates containing expressions have the values of evaluated expressions
+ calls = 0;
+ (function(site, n, s, o, f, r) {
+ calls++;
+ assertEquals(6, arguments.length);
+ assertEquals("number", typeof n);
+ assertEquals("string", typeof s);
+ assertEquals("object", typeof o);
+ assertEquals("function", typeof f);
+ assertEquals("result", r);
+ })`${num}${str}${obj}${fn}${fn()}`;
+ assertEquals(1, calls);
+
+ // The TV and TRV of NoSubstitutionTemplate :: `` is the empty code unit
+ // sequence.
+ calls = 0;
+ (function(s) {
+ calls++;
+ assertEquals(1, s.length);
+ assertEquals(1, s.raw.length);
+ assertEquals("", s[0]);
+
+ // Failure: expected <""> found <"foo barfoo barfoo foo foo foo testtest">
+ assertEquals("", s.raw[0]);
+ })``;
+ assertEquals(1, calls);
+
+ // The TV and TRV of TemplateHead :: `${ is the empty code unit sequence.
+ calls = 0;
+ (function(s) {
+ calls++;
+ assertEquals(2, s.length);
+ assertEquals(2, s.raw.length);
+ assertEquals("", s[0]);
+ assertEquals("", s.raw[0]);
+ })`${1}`;
+ assertEquals(1, calls);
+
+ // The TV and TRV of TemplateMiddle :: }${ is the empty code unit sequence.
+ calls = 0;
+ (function(s) {
+ calls++;
+ assertEquals(3, s.length);
+ assertEquals(3, s.raw.length);
+ assertEquals("", s[1]);
+ assertEquals("", s.raw[1]);
+ })`${1}${2}`;
+ assertEquals(1, calls);
+
+ // The TV and TRV of TemplateTail :: }` is the empty code unit sequence.
+ calls = 0;
+ (function(s) {
+ calls++;
+ assertEquals(2, s.length);
+ assertEquals(2, s.raw.length);
+ assertEquals("", s[1]);
+ assertEquals("", s.raw[1]);
+ })`${1}`;
+ assertEquals(1, calls);
+
+ // The TV of NoSubstitutionTemplate :: ` TemplateCharacters ` is the TV of
+ // TemplateCharacters.
+ calls = 0;
+ (function(s) { calls++; assertEquals("foo", s[0]); })`foo`;
+ assertEquals(1, calls);
+
+ // The TV of TemplateHead :: ` TemplateCharacters ${ is the TV of
+ // TemplateCharacters.
+ calls = 0;
+ (function(s) { calls++; assertEquals("foo", s[0]); })`foo${1}`;
+ assertEquals(1, calls);
+
+ // The TV of TemplateMiddle :: } TemplateCharacters ${ is the TV of
+ // TemplateCharacters.
+ calls = 0;
+ (function(s) { calls++; assertEquals("foo", s[1]); })`${1}foo${2}`;
+ assertEquals(1, calls);
+
+ // The TV of TemplateTail :: } TemplateCharacters ` is the TV of
+ // TemplateCharacters.
+ calls = 0;
+ (function(s) { calls++; assertEquals("foo", s[1]); })`${1}foo`;
+ assertEquals(1, calls);
+
+ // The TV of TemplateCharacters :: TemplateCharacter is the TV of
+ // TemplateCharacter.
+ calls = 0;
+ (function(s) { calls++; assertEquals("f", s[0]); })`f`;
+ assertEquals(1, calls);
+
+ // The TV of TemplateCharacter :: $ is the code unit value 0x0024.
+ calls = 0;
+ (function(s) { calls++; assertEquals("$", s[0]); })`$`;
+ assertEquals(1, calls);
+
+ // The TV of TemplateCharacter :: \ EscapeSequence is the CV of
+ // EscapeSequence.
+ calls = 0;
+ (function(s) { calls++; assertEquals("안녕", s[0]); })`\uc548\uB155`;
+ (function(s) { calls++; assertEquals("\xff", s[0]); })`\xff`;
+ (function(s) { calls++; assertEquals("\n", s[0]); })`\n`;
+ assertEquals(3, calls);
+
+ // The TV of TemplateCharacter :: LineContinuation is the TV of
+ // LineContinuation. The TV of LineContinuation :: \ LineTerminatorSequence is
+ // the empty code unit sequence.
+ calls = 0;
+ (function(s) { calls++; assertEquals("", s[0]); })`\
+`;
+ assertEquals(1, calls);
+
+ // The TRV of NoSubstitutionTemplate :: ` TemplateCharacters ` is the TRV of
+ // TemplateCharacters.
+ calls = 0;
+ (function(s) { calls++; assertEquals("test", s.raw[0]); })`test`;
+ assertEquals(1, calls);
+
+ // The TRV of TemplateHead :: ` TemplateCharacters ${ is the TRV of
+ // TemplateCharacters.
+ calls = 0;
+ (function(s) { calls++; assertEquals("test", s.raw[0]); })`test${1}`;
+ assertEquals(1, calls);
+
+ // The TRV of TemplateMiddle :: } TemplateCharacters ${ is the TRV of
+ // TemplateCharacters.
+ calls = 0;
+ (function(s) { calls++; assertEquals("test", s.raw[1]); })`${1}test${2}`;
+ assertEquals(1, calls);
+
+ // The TRV of TemplateTail :: } TemplateCharacters ` is the TRV of
+ // TemplateCharacters.
+ calls = 0;
+ (function(s) { calls++; assertEquals("test", s.raw[1]); })`${1}test`;
+ assertEquals(1, calls);
+
+ // The TRV of TemplateCharacters :: TemplateCharacter is the TRV of
+ // TemplateCharacter.
+ calls = 0;
+ (function(s) { calls++; assertEquals("f", s.raw[0]); })`f`;
+ assertEquals(1, calls);
+
+ // The TRV of TemplateCharacter :: $ is the code unit value 0x0024.
+ calls = 0;
+ (function(s) { calls++; assertEquals("\u0024", s.raw[0]); })`$`;
+ assertEquals(1, calls);
+
+ // The TRV of EscapeSequence :: 0 is the code unit value 0x0030.
+ calls = 0;
+ (function(s) { calls++; assertEquals("\u005C\u0030", s.raw[0]); })`\0`;
+ assertEquals(1, calls);
+
+ // The TRV of TemplateCharacter :: \ EscapeSequence is the sequence consisting
+ // of the code unit value 0x005C followed by the code units of TRV of
+ // EscapeSequence.
+
+ // The TRV of EscapeSequence :: HexEscapeSequence is the TRV of the
+ // HexEscapeSequence.
+ calls = 0;
+ (function(s) { calls++; assertEquals("\u005Cxff", s.raw[0]); })`\xff`;
+ assertEquals(1, calls);
+
+ // The TRV of EscapeSequence :: UnicodeEscapeSequence is the TRV of the
+ // UnicodeEscapeSequence.
+ calls = 0;
+ (function(s) { calls++; assertEquals("\u005Cuc548", s.raw[0]); })`\uc548`;
+ assertEquals(1, calls);
+
+ // The TRV of CharacterEscapeSequence :: SingleEscapeCharacter is the TRV of
+ // the SingleEscapeCharacter.
+ calls = 0;
+ (function(s) { calls++; assertEquals("\u005C\u0027", s.raw[0]); })`\'`;
+ (function(s) { calls++; assertEquals("\u005C\u0022", s.raw[0]); })`\"`;
+ (function(s) { calls++; assertEquals("\u005C\u005C", s.raw[0]); })`\\`;
+ (function(s) { calls++; assertEquals("\u005Cb", s.raw[0]); })`\b`;
+ (function(s) { calls++; assertEquals("\u005Cf", s.raw[0]); })`\f`;
+ (function(s) { calls++; assertEquals("\u005Cn", s.raw[0]); })`\n`;
+ (function(s) { calls++; assertEquals("\u005Cr", s.raw[0]); })`\r`;
+ (function(s) { calls++; assertEquals("\u005Ct", s.raw[0]); })`\t`;
+ (function(s) { calls++; assertEquals("\u005Cv", s.raw[0]); })`\v`;
+ (function(s) { calls++; assertEquals("\u005C`", s.raw[0]); })`\``;
+ assertEquals(10, calls);
+
+ // The TRV of CharacterEscapeSequence :: NonEscapeCharacter is the CV of the
+ // NonEscapeCharacter.
+ calls = 0;
+ (function(s) { calls++; assertEquals("\u005Cx", s.raw[0]); })`\x`;
+ assertEquals(1, calls);
+
+ // The TRV of LineTerminatorSequence :: <LF> is the code unit value 0x000A.
+ // The TRV of LineTerminatorSequence :: <CR> is the code unit value 0x000A.
+ // The TRV of LineTerminatorSequence :: <CR><LF> is the sequence consisting of
+ // the code unit value 0x000A.
+ calls = 0;
+ function testRawLineNormalization(cs) {
+ calls++;
+ assertEquals(cs.raw[0], "\n\n\n");
+ assertEquals(cs.raw[1], "\n\n\n");
+ }
+ eval("testRawLineNormalization`\r\n\n\r${1}\r\n\n\r`");
+ assertEquals(1, calls);
+
+ // The TRV of LineContinuation :: \ LineTerminatorSequence is the sequence
+ // consisting of the code unit value 0x005C followed by the code units of TRV
+ // of LineTerminatorSequence.
+ calls = 0;
+ function testRawLineContinuation(cs) {
+ calls++;
+ assertEquals(cs.raw[0], "\u005C\n\u005C\n\u005C\n");
+ assertEquals(cs.raw[1], "\u005C\n\u005C\n\u005C\n");
+ }
+ eval("testRawLineContinuation`\\\r\n\\\n\\\r${1}\\\r\n\\\n\\\r`");
+ assertEquals(1, calls);
+})();
+
+
+(function testCallSiteObj() {
+ var calls = 0;
+ function tag(cs) {
+ calls++;
+ assertTrue(cs.hasOwnProperty("raw"));
+ assertTrue(Object.isFrozen(cs));
+ assertTrue(Object.isFrozen(cs.raw));
+ var raw = Object.getOwnPropertyDescriptor(cs, "raw");
+ assertFalse(raw.writable);
+ assertFalse(raw.configurable);
+ assertFalse(raw.enumerable);
+ assertEquals(Array.prototype, Object.getPrototypeOf(cs.raw));
+ assertTrue(Array.isArray(cs.raw));
+ assertEquals(Array.prototype, Object.getPrototypeOf(cs));
+ assertTrue(Array.isArray(cs));
+
+ var cooked0 = Object.getOwnPropertyDescriptor(cs, "0");
+ assertFalse(cooked0.writable);
+ assertFalse(cooked0.configurable);
+ assertTrue(cooked0.enumerable);
+
+ var raw0 = Object.getOwnPropertyDescriptor(cs.raw, "0");
+ assertFalse(cooked0.writable);
+ assertFalse(cooked0.configurable);
+ assertTrue(cooked0.enumerable);
+
+ var length = Object.getOwnPropertyDescriptor(cs, "length");
+ assertFalse(length.writable);
+ assertFalse(length.configurable);
+ assertFalse(length.enumerable);
+
+ length = Object.getOwnPropertyDescriptor(cs.raw, "length");
+ assertFalse(length.writable);
+ assertFalse(length.configurable);
+ assertFalse(length.enumerable);
+ }
+ tag`${1}`;
+ assertEquals(1, calls);
+})();
+
+
+(function testUTF16ByteOrderMark() {
+ assertEquals("\uFEFFtest", `\uFEFFtest`);
+ assertEquals("\uFEFFtest", eval("`\uFEFFtest`"));
+})();
+
+
+(function testExtendedArrayPrototype() {
+ Object.defineProperty(Array.prototype, 0, {
+ set: function() {
+ assertUnreachable();
+ }
+ });
+ function tag(){}
+ tag`a${1}b`;
+})();
'../../src/harmony-tostring.js',
'../../src/harmony-typedarray.js',
'../../src/harmony-classes.js',
+ '../../src/harmony-templates.js'
],
'libraries_bin_file': '<(SHARED_INTERMEDIATE_DIR)/libraries.bin',
'libraries_experimental_bin_file': '<(SHARED_INTERMEDIATE_DIR)/libraries-experimental.bin',