Implement ES6 Template Literals
authorcaitpotter88 <caitpotter88@gmail.com>
Fri, 14 Nov 2014 18:53:41 +0000 (10:53 -0800)
committerCommit bot <commit-bot@chromium.org>
Fri, 14 Nov 2014 18:53:52 +0000 (18:53 +0000)
BUG=v8:3230

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

Cr-Commit-Position: refs/heads/master@{#25362}

15 files changed:
BUILD.gn
src/ast-value-factory.h
src/bootstrapper.cc
src/flag-definitions.h
src/harmony-templates.js [new file with mode: 0644]
src/messages.js
src/parser.cc
src/parser.h
src/preparser.h
src/scanner.cc
src/scanner.h
src/token.h
test/cctest/test-parsing.cc
test/mjsunit/harmony/templates.js [new file with mode: 0644]
tools/gyp/v8.gyp

index 48beb06..1c2c52a 100644 (file)
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -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 = [
index 09a4140..9ccd511 100644 (file)
@@ -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") \
index eb8cd7c..03dd569 100644 (file)
@@ -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++) {
index 49d4474..a3e618a 100644 (file)
@@ -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")
diff --git a/src/harmony-templates.js b/src/harmony-templates.js
new file mode 100644 (file)
index 0000000..dd122cc
--- /dev/null
@@ -0,0 +1,14 @@
+// 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);
+}
index 8a4e5e8..8472454 100644 (file)
@@ -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"],
index b2cf3b1..c6fe2b4 100644 (file)
@@ -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
index e2afe56..820e149 100644 (file)
@@ -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_
index dd05361..a9db265 100644 (file)
@@ -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);
index ddcd937..bf7c9e3 100644 (file)
@@ -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();
index e626f20..46e6d32 100644 (file)
@@ -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
index bab925a..de8ede9 100644 (file)
@@ -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 {
index 0ae46c3..49c79a1 100644 (file)
@@ -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));
+}
diff --git a/test/mjsunit/harmony/templates.js b/test/mjsunit/harmony/templates.js
new file mode 100644 (file)
index 0000000..15ad9e9
--- /dev/null
@@ -0,0 +1,341 @@
+// 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`;
+})();
index 9c7fd7d..f2b33a0 100644 (file)
           '../../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',