Commit 353b6964 authored by caitpotter88's avatar caitpotter88 Committed by Commit bot

Implement ES6 Template Literals

BUG=v8:3230

Review URL: https://codereview.chromium.org/663683006

Cr-Commit-Position: refs/heads/master@{#25362}
parent c97a7acf
......@@ -245,7 +245,8 @@ action("js2c_experimental") {
"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 = [
......
......@@ -248,6 +248,7 @@ class AstValue : public ZoneObject {
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") \
......
......@@ -1597,6 +1597,7 @@ EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_regexps)
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() {
......@@ -1623,6 +1624,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_arrow_functions)
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());
......@@ -2176,6 +2178,8 @@ bool Genesis::InstallExperimentalNatives() {
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++) {
......
......@@ -172,7 +172,8 @@ DEFINE_IMPLICATION(harmony, es_staging)
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")
......
// 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);
}
......@@ -18,9 +18,12 @@ var kMessages = {
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"],
......
......@@ -802,6 +802,7 @@ Parser::Parser(CompilationInfo* info, ParseInfo* parse_info)
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;
......@@ -3882,6 +3883,7 @@ PreParser::PreParseResult Parser::ParseLazyFunctionBodyWithPreParser(
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(),
......@@ -5084,4 +5086,141 @@ void Parser::ParseOnBackground() {
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
......@@ -586,6 +586,47 @@ class ParserTraits {
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_;
};
......@@ -826,6 +867,13 @@ class Parser : public ParserBase<ParserTraits> {
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
......@@ -925,6 +973,27 @@ class CompileTimeValue: public AllStatic {
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_
......@@ -102,6 +102,7 @@ class ParserBase : public Traits {
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.
......@@ -119,6 +120,9 @@ class ParserBase : public Traits {
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 {
......@@ -490,6 +494,8 @@ class ParserBase : public Traits {
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,
......@@ -1365,6 +1371,18 @@ class PreParserTraits {
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) {}
......@@ -1599,6 +1617,10 @@ void ParserBase<Traits>::ReportUnexpectedToken(Token::Value token) {
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);
......@@ -1745,6 +1767,7 @@ ParserBase<Traits>::ParsePrimaryExpression(bool* ok) {
// RegExpLiteral
// ClassLiteral
// '(' Expression ')'
// TemplateLiteral
int pos = peek_position();
ExpressionT result = this->EmptyExpression();
......@@ -1833,6 +1856,12 @@ ParserBase<Traits>::ParsePrimaryExpression(bool* ok) {
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);
......@@ -2456,6 +2485,23 @@ ParserBase<Traits>::ParseLeftHandSideExpression(bool* 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();
......@@ -2724,9 +2770,86 @@ typename ParserBase<Traits>::ExpressionT ParserBase<
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);
......
......@@ -38,7 +38,8 @@ Scanner::Scanner(UnicodeCache* unicode_cache)
harmony_scoping_(false),
harmony_modules_(false),
harmony_numeric_literals_(false),
harmony_classes_(false) { }
harmony_classes_(false),
harmony_templates_(false) {}
void Scanner::Initialize(Utf16CharacterStream* source) {
......@@ -626,6 +627,12 @@ void Scanner::Scan() {
token = Select(Token::BIT_NOT);
break;
case '`':
if (HarmonyTemplates()) {
token = ScanTemplateSpan();
break;
}
default:
if (c0_ < 0) {
token = Token::EOS;
......@@ -770,6 +777,77 @@ Token::Value Scanner::ScanString() {
}
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();
......
......@@ -458,6 +458,8 @@ class Scanner {
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.
......@@ -473,6 +475,9 @@ class Scanner {
// 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_;
......@@ -681,6 +686,8 @@ class Scanner {
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
......
......@@ -163,7 +163,11 @@ namespace 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 {
......
......@@ -1344,7 +1344,8 @@ enum ParserFlag {
kAllowHarmonyNumericLiterals,
kAllowArrowFunctions,
kAllowClasses,
kAllowHarmonyObjectLiterals
kAllowHarmonyObjectLiterals,
kAllowHarmonyTemplates
};
......@@ -1367,6 +1368,7 @@ void SetParserFlags(i::ParserBase<Traits>* parser,
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));
}
......@@ -1666,6 +1668,7 @@ void RunParserSyncTest(const char* context_data[][2],
kAllowLazy,
kAllowModules,
kAllowNativesSyntax,
kAllowHarmonyTemplates
};
ParserFlag* generated_flags = NULL;
if (flags == NULL) {
......@@ -4318,3 +4321,112 @@ TEST(InvalidUnicodeEscapes) {
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));
}
// 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`;
})();
......@@ -1628,6 +1628,7 @@
'../../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',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment