Ignore:
Timestamp:
May 14, 2015, 9:07:54 AM (10 years ago)
Author:
Yusuke Suzuki
Message:

[ES6] Implement tagged templates
https://bugs.webkit.org/show_bug.cgi?id=143183

Reviewed by Oliver Hunt.

This patch implements ES6 tagged templates.
In tagged templates, the function takes the template object.

The template object contains the raw and cooked template strings,
so when parsing the tagged templates, we need to tokenize the raw and cooked strings.
While tagged templates require the both strings, the template literal only requires
the cooked strings. So when tokenizing under the template literal context,
we only builds the cooked strings.

As per ES6 spec, the template objects for the same raw strings are shared in the same realm.
The template objects is cached. And every time we evaluate the same tagged templates,
the same (cached) template objects are used.
Since the spec freezes this template objects completely,
we cannot attach some properties to it.
So we can say that it behaves as if the template objects are the primitive values (like JSString).
Since we cannot attach properties, the only way to test the identity of the template object is comparing. (===)
As the result, when there is no reference to the template object, we can garbage collect it
because the user has no way to test that the newly created template object does not equal
to the already collected template object.

So, to implement tagged templates, we implement the following components.

  1. JSTemplateRegistryKey

It holds the template registry key and it does not exposed to users.
TemplateRegistryKey holds the vector of raw and cooked strings with the pre-computed hash value.
When obtaining the template object for the (statically, a.k.a. at the parsing time) given raw string vectors,
we use this JSTemplateRegistryKey as a key to the map and look up the template object from
TemplateRegistry.
JSTemplateRegistryKey is created at the bytecode compiling time and
stored in the CodeBlock as like as JSString content values.

  1. TemplateRegistry

This manages the cached template objects.
It holds the weak map (JSTemplateRegistryKey -> the template object).
The template object is weakly referenced.
So if there is no reference to the template object,
the template object is automatically GC-ed.
When looking up the template object, it searches the cached template object.
If it is found, it is returned to the users.
If there is no cached template objects, it creates the new template object and
stores it with the given template registry key.

(JSC::BytecodeGenerator::addTemplateRegistryKeyConstant):
(JSC::BytecodeGenerator::emitGetTemplateObject):

  • bytecompiler/BytecodeGenerator.h:
  • bytecompiler/NodesCodegen.cpp:

(JSC::TaggedTemplateNode::emitBytecode):
(JSC::TemplateLiteralNode::emitBytecode): Deleted.

  • parser/ASTBuilder.h:

(JSC::ASTBuilder::createTaggedTemplate):
(JSC::ASTBuilder::createTemplateLiteral): Deleted.

  • parser/Lexer.cpp:

(JSC::Lexer<T>::setCode):
(JSC::Lexer<T>::parseTemplateLiteral):
(JSC::Lexer<T>::lex):
(JSC::Lexer<T>::scanTrailingTemplateString):
(JSC::Lexer<T>::clear):

  • parser/Lexer.h:

(JSC::Lexer<T>::makeEmptyIdentifier):

  • parser/NodeConstructors.h:

(JSC::TaggedTemplateNode::TaggedTemplateNode):
(JSC::TemplateLiteralNode::TemplateLiteralNode): Deleted.

  • parser/Nodes.h:

(JSC::TemplateLiteralNode::templateStrings):
(JSC::TemplateLiteralNode::templateExpressions):
(JSC::TaggedTemplateNode::templateLiteral):

  • parser/Parser.cpp:

(JSC::Parser<LexerType>::parseTemplateString):
(JSC::Parser<LexerType>::parseTemplateLiteral):
(JSC::Parser<LexerType>::parsePrimaryExpression):
(JSC::Parser<LexerType>::parseMemberExpression):

  • parser/Parser.h:
  • parser/ParserArena.h:

(JSC::IdentifierArena::makeEmptyIdentifier):

  • parser/SyntaxChecker.h:

(JSC::SyntaxChecker::createTaggedTemplate):
(JSC::SyntaxChecker::createTemplateLiteral): Deleted.

  • runtime/CommonIdentifiers.h:
  • runtime/JSGlobalObject.cpp:

(JSC::getTemplateObject):
(JSC::JSGlobalObject::JSGlobalObject):
(JSC::JSGlobalObject::init):

  • runtime/JSGlobalObject.h:

(JSC::JSGlobalObject::templateRegistry):

  • runtime/JSTemplateRegistryKey.cpp: Added.

(JSC::JSTemplateRegistryKey::JSTemplateRegistryKey):
(JSC::JSTemplateRegistryKey::create):
(JSC::JSTemplateRegistryKey::destroy):

  • runtime/JSTemplateRegistryKey.h: Added.
  • runtime/ObjectConstructor.cpp:

(JSC::objectConstructorFreeze):

  • runtime/ObjectConstructor.h:
  • runtime/TemplateRegistry.cpp: Added.

(JSC::TemplateRegistry::TemplateRegistry):
(JSC::TemplateRegistry::getTemplateObject):

  • runtime/TemplateRegistry.h: Added.
  • runtime/TemplateRegistryKey.h: Added.

(JSC::TemplateRegistryKey::isDeletedValue):
(JSC::TemplateRegistryKey::isEmptyValue):
(JSC::TemplateRegistryKey::hash):
(JSC::TemplateRegistryKey::rawStrings):
(JSC::TemplateRegistryKey::cookedStrings):
(JSC::TemplateRegistryKey::operator==):
(JSC::TemplateRegistryKey::operator!=):
(JSC::TemplateRegistryKey::Hasher::hash):
(JSC::TemplateRegistryKey::Hasher::equal):
(JSC::TemplateRegistryKey::TemplateRegistryKey):

  • runtime/VM.cpp:

(JSC::VM::VM):

  • runtime/VM.h:
  • tests/stress/tagged-templates-identity.js: Added.

(shouldBe):

  • tests/stress/tagged-templates-raw-strings.js: Added.

(shouldBe):
(tag):
(testEval):

  • tests/stress/tagged-templates-syntax.js: Added.

(tag):
(testSyntax):
(testSyntaxError):

  • tests/stress/tagged-templates-template-object.js: Added.

(shouldBe):
(tag):

  • tests/stress/tagged-templates-this.js: Added.

(shouldBe):
(tag):

  • tests/stress/tagged-templates.js: Added.

(shouldBe):
(raw):
(cooked):
(Counter):

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/parser/Lexer.cpp

    r183552 r184337  
    570570    m_buffer8.reserveInitialCapacity(initialReadBufferCapacity);
    571571    m_buffer16.reserveInitialCapacity((m_codeEnd - m_code) / 2);
     572    m_bufferForRawTemplateString16.reserveInitialCapacity(initialReadBufferCapacity);
    572573   
    573574    if (LIKELY(m_code < m_codeEnd))
     
    13751376
    13761377template <typename T>
    1377 template <bool shouldBuildStrings> typename Lexer<T>::StringParseResult Lexer<T>::parseTemplateLiteral(JSTokenData* tokenData)
     1378template <bool shouldBuildStrings> typename Lexer<T>::StringParseResult Lexer<T>::parseTemplateLiteral(JSTokenData* tokenData, RawStringsBuildMode rawStringsBuildMode)
    13781379{
    13791380    const T* stringStart = currentSourcePtr();
     
    14361437                if (m_current == '\r') {
    14371438                    // Normalize <CR>, <CR><LF> to <LF>.
    1438                     if (stringStart != currentSourcePtr() && shouldBuildStrings)
    1439                         append16(stringStart, currentSourcePtr() - stringStart);
    1440                     if (shouldBuildStrings)
     1439                    if (shouldBuildStrings) {
     1440                        if (stringStart != currentSourcePtr())
     1441                            append16(stringStart, currentSourcePtr() - stringStart);
     1442                        if (rawStringStart != currentSourcePtr() && rawStringsBuildMode == RawStringsBuildMode::BuildRawStrings)
     1443                            m_bufferForRawTemplateString16.append(rawStringStart, currentSourcePtr() - rawStringStart);
     1444
    14411445                        record16('\n');
     1446                        if (rawStringsBuildMode == RawStringsBuildMode::BuildRawStrings)
     1447                            m_bufferForRawTemplateString16.append('\n');
     1448                    }
    14421449                    lineNumberAdder.add(m_current);
    14431450                    shift();
     
    14471454                    }
    14481455                    stringStart = currentSourcePtr();
     1456                    rawStringStart = currentSourcePtr();
    14491457                } else {
    14501458                    lineNumberAdder.add(m_current);
     
    14621470    bool isTail = m_current == '`';
    14631471
    1464     if (currentSourcePtr() != stringStart && shouldBuildStrings)
    1465         append16(stringStart, currentSourcePtr() - stringStart);
     1472    if (shouldBuildStrings) {
     1473        if (currentSourcePtr() != stringStart)
     1474            append16(stringStart, currentSourcePtr() - stringStart);
     1475        if (rawStringStart != currentSourcePtr() && rawStringsBuildMode == RawStringsBuildMode::BuildRawStrings)
     1476            m_bufferForRawTemplateString16.append(rawStringStart, currentSourcePtr() - rawStringStart);
     1477    }
    14661478
    14671479    if (shouldBuildStrings) {
    14681480        tokenData->cooked = makeIdentifier(m_buffer16.data(), m_buffer16.size());
    1469         // TODO: While line terminator normalization (e.g. <CR> => <LF>) should be applied to both the raw and cooked representations,
    1470         // this raw implementation just slices the source string. As a result, line terminators appear in the raw representation without normalization.
    1471         // For example, when parsing `<CR>`, <CR> appears in the raw representation.
    1472         // While non-tagged template literals don't use the raw representation, tagged templates use the raw representation.
    1473         // So line terminator normalization should be applied to the raw representation when implementing tagged templates.
    1474         tokenData->raw = makeIdentifier(rawStringStart, currentSourcePtr() - rawStringStart);
     1481        // Line terminator normalization (e.g. <CR> => <LF>) should be applied to both the raw and cooked representations.
     1482        if (rawStringsBuildMode == RawStringsBuildMode::BuildRawStrings)
     1483            tokenData->raw = makeIdentifier(m_bufferForRawTemplateString16.data(), m_bufferForRawTemplateString16.size());
     1484        else
     1485            tokenData->raw = makeEmptyIdentifier();
    14751486    } else {
    1476         tokenData->cooked = nullptr;
    1477         tokenData->raw = nullptr;
     1487        tokenData->cooked = makeEmptyIdentifier();
     1488        tokenData->raw = makeEmptyIdentifier();
    14781489    }
    14791490    tokenData->isTail = isTail;
    14801491
    14811492    m_buffer16.shrink(0);
     1493    m_bufferForRawTemplateString16.shrink(0);
    14821494
    14831495    if (isTail) {
     
    21262138        StringParseResult result = StringCannotBeParsed;
    21272139        if (lexerFlags & LexerFlagsDontBuildStrings)
    2128             result = parseTemplateLiteral<false>(tokenData);
     2140            result = parseTemplateLiteral<false>(tokenData, RawStringsBuildMode::BuildRawStrings);
    21292141        else
    2130             result = parseTemplateLiteral<true>(tokenData);
     2142            result = parseTemplateLiteral<true>(tokenData, RawStringsBuildMode::BuildRawStrings);
    21312143
    21322144        if (UNLIKELY(result != StringParsedSuccessfully)) {
     
    23322344#if ENABLE(ES6_TEMPLATE_LITERAL_SYNTAX)
    23332345template <typename T>
    2334 JSTokenType Lexer<T>::scanTrailingTemplateString(JSToken* tokenRecord)
     2346JSTokenType Lexer<T>::scanTrailingTemplateString(JSToken* tokenRecord, RawStringsBuildMode rawStringsBuildMode)
    23352347{
    23362348    JSTokenData* tokenData = &tokenRecord->m_data;
     
    23412353    // Leading closing brace } is already shifted in the previous token scan.
    23422354    // So in this re-scan phase, shift() is not needed here.
    2343     StringParseResult result = parseTemplateLiteral<true>(tokenData);
     2355    StringParseResult result = parseTemplateLiteral<true>(tokenData, rawStringsBuildMode);
    23442356    JSTokenType token = ERRORTOK;
    23452357    if (UNLIKELY(result != StringParsedSuccessfully)) {
     
    23752387    m_buffer16.swap(newBuffer16);
    23762388
     2389    Vector<UChar> newBufferForRawTemplateString16;
     2390    m_bufferForRawTemplateString16.swap(newBufferForRawTemplateString16);
     2391
    23772392    m_isReparsing = false;
    23782393}
Note: See TracChangeset for help on using the changeset viewer.