Browse Source

LibJS: Add raw strings to tagged template literals

When calling a function with a tagged template, the first array that is
passed in now contains a "raw" property with the raw, escaped strings.
Matthew Olsson 5 years ago
parent
commit
b5f1df57ed

+ 10 - 0
Libraries/LibJS/AST.cpp

@@ -1347,6 +1347,16 @@ Value TaggedTemplateLiteral::execute(Interpreter& interpreter) const
         else
         else
             arguments.append(value);
             arguments.append(value);
     }
     }
+
+    auto* raw_strings = Array::create(interpreter.global_object());
+    for (auto& raw_string : m_template_literal->raw_strings()) {
+        auto value = raw_string.execute(interpreter);
+        if (interpreter.exception())
+            return {};
+        raw_strings->elements().append(value);
+    }
+    strings->put("raw", raw_strings, 0);
+
     return interpreter.call(tag_function, js_undefined(), move(arguments));
     return interpreter.call(tag_function, js_undefined(), move(arguments));
 }
 }
 
 

+ 9 - 1
Libraries/LibJS/AST.h

@@ -779,7 +779,13 @@ private:
 class TemplateLiteral final : public Expression {
 class TemplateLiteral final : public Expression {
 public:
 public:
     TemplateLiteral(NonnullRefPtrVector<Expression> expressions)
     TemplateLiteral(NonnullRefPtrVector<Expression> expressions)
-        : m_expressions(expressions)
+        : m_expressions(move(expressions))
+    {
+    }
+
+    TemplateLiteral(NonnullRefPtrVector<Expression> expressions, NonnullRefPtrVector<Expression> raw_strings)
+        : m_expressions(move(expressions))
+        , m_raw_strings(move(raw_strings))
     {
     {
     }
     }
 
 
@@ -787,11 +793,13 @@ public:
     virtual void dump(int indent) const override;
     virtual void dump(int indent) const override;
 
 
     const NonnullRefPtrVector<Expression>& expressions() const { return m_expressions; }
     const NonnullRefPtrVector<Expression>& expressions() const { return m_expressions; }
+    const NonnullRefPtrVector<Expression>& raw_strings() const { return m_raw_strings; }
 
 
 private:
 private:
     virtual const char* class_name() const override { return "TemplateLiteral"; }
     virtual const char* class_name() const override { return "TemplateLiteral"; }
 
 
     const NonnullRefPtrVector<Expression> m_expressions;
     const NonnullRefPtrVector<Expression> m_expressions;
+    const NonnullRefPtrVector<Expression> m_raw_strings;
 };
 };
 
 
 class TaggedTemplateLiteral final : public Expression {
 class TaggedTemplateLiteral final : public Expression {

+ 20 - 7
Libraries/LibJS/Parser.cpp

@@ -417,7 +417,7 @@ NonnullRefPtr<Expression> Parser::parse_primary_expression()
     case TokenType::BracketOpen:
     case TokenType::BracketOpen:
         return parse_array_expression();
         return parse_array_expression();
     case TokenType::TemplateLiteralStart:
     case TokenType::TemplateLiteralStart:
-        return parse_template_literal();
+        return parse_template_literal(false);
     case TokenType::New:
     case TokenType::New:
         return parse_new_expression();
         return parse_new_expression();
     default:
     default:
@@ -560,18 +560,29 @@ NonnullRefPtr<ArrayExpression> Parser::parse_array_expression()
     return create_ast_node<ArrayExpression>(move(elements));
     return create_ast_node<ArrayExpression>(move(elements));
 }
 }
 
 
-NonnullRefPtr<TemplateLiteral> Parser::parse_template_literal()
+NonnullRefPtr<TemplateLiteral> Parser::parse_template_literal(bool is_tagged)
 {
 {
     consume(TokenType::TemplateLiteralStart);
     consume(TokenType::TemplateLiteralStart);
 
 
     NonnullRefPtrVector<Expression> expressions;
     NonnullRefPtrVector<Expression> expressions;
+    NonnullRefPtrVector<Expression> raw_strings;
+
+    auto append_empty_string = [&expressions, &raw_strings, is_tagged]() {
+        auto string_literal = create_ast_node<StringLiteral>("");
+        expressions.append(string_literal);
+        if (is_tagged)
+            raw_strings.append(string_literal);
+    };
 
 
     if (!match(TokenType::TemplateLiteralString))
     if (!match(TokenType::TemplateLiteralString))
-        expressions.append(create_ast_node<StringLiteral>(""));
+        append_empty_string();
 
 
     while (!match(TokenType::TemplateLiteralEnd) && !match(TokenType::UnterminatedTemplateLiteral)) {
     while (!match(TokenType::TemplateLiteralEnd) && !match(TokenType::UnterminatedTemplateLiteral)) {
         if (match(TokenType::TemplateLiteralString)) {
         if (match(TokenType::TemplateLiteralString)) {
-            expressions.append(create_ast_node<StringLiteral>(consume().string_value()));
+            auto token = consume();
+            expressions.append(create_ast_node<StringLiteral>(token.string_value()));
+            if (is_tagged)
+                raw_strings.append(create_ast_node<StringLiteral>(token.value()));
         } else if (match(TokenType::TemplateLiteralExprStart)) {
         } else if (match(TokenType::TemplateLiteralExprStart)) {
             consume(TokenType::TemplateLiteralExprStart);
             consume(TokenType::TemplateLiteralExprStart);
             if (match(TokenType::TemplateLiteralExprEnd)) {
             if (match(TokenType::TemplateLiteralExprEnd)) {
@@ -587,7 +598,7 @@ NonnullRefPtr<TemplateLiteral> Parser::parse_template_literal()
             consume(TokenType::TemplateLiteralExprEnd);
             consume(TokenType::TemplateLiteralExprEnd);
 
 
             if (!match(TokenType::TemplateLiteralString))
             if (!match(TokenType::TemplateLiteralString))
-                expressions.append(create_ast_node<StringLiteral>(""));
+                append_empty_string();
         }
         }
     }
     }
 
 
@@ -597,6 +608,8 @@ NonnullRefPtr<TemplateLiteral> Parser::parse_template_literal()
         consume(TokenType::TemplateLiteralEnd);
         consume(TokenType::TemplateLiteralEnd);
     }
     }
 
 
+    if (is_tagged)
+        return create_ast_node<TemplateLiteral>(expressions, raw_strings);
     return create_ast_node<TemplateLiteral>(expressions);
     return create_ast_node<TemplateLiteral>(expressions);
 }
 }
 
 
@@ -604,7 +617,7 @@ NonnullRefPtr<Expression> Parser::parse_expression(int min_precedence, Associati
 {
 {
     auto expression = parse_primary_expression();
     auto expression = parse_primary_expression();
     while (match(TokenType::TemplateLiteralStart)) {
     while (match(TokenType::TemplateLiteralStart)) {
-        auto template_literal = parse_template_literal();
+        auto template_literal = parse_template_literal(true);
         expression = create_ast_node<TaggedTemplateLiteral>(move(expression), move(template_literal));
         expression = create_ast_node<TaggedTemplateLiteral>(move(expression), move(template_literal));
     }
     }
     while (match_secondary_expression()) {
     while (match_secondary_expression()) {
@@ -617,7 +630,7 @@ NonnullRefPtr<Expression> Parser::parse_expression(int min_precedence, Associati
         Associativity new_associativity = operator_associativity(m_parser_state.m_current_token.type());
         Associativity new_associativity = operator_associativity(m_parser_state.m_current_token.type());
         expression = parse_secondary_expression(move(expression), new_precedence, new_associativity);
         expression = parse_secondary_expression(move(expression), new_precedence, new_associativity);
         while (match(TokenType::TemplateLiteralStart)) {
         while (match(TokenType::TemplateLiteralStart)) {
-            auto template_literal = parse_template_literal();
+            auto template_literal = parse_template_literal(true);
             expression = create_ast_node<TaggedTemplateLiteral>(move(expression), move(template_literal));
             expression = create_ast_node<TaggedTemplateLiteral>(move(expression), move(template_literal));
         }
         }
     }
     }

+ 1 - 1
Libraries/LibJS/Parser.h

@@ -69,7 +69,7 @@ public:
     NonnullRefPtr<Expression> parse_unary_prefixed_expression();
     NonnullRefPtr<Expression> parse_unary_prefixed_expression();
     NonnullRefPtr<ObjectExpression> parse_object_expression();
     NonnullRefPtr<ObjectExpression> parse_object_expression();
     NonnullRefPtr<ArrayExpression> parse_array_expression();
     NonnullRefPtr<ArrayExpression> parse_array_expression();
-    NonnullRefPtr<TemplateLiteral> parse_template_literal();
+    NonnullRefPtr<TemplateLiteral> parse_template_literal(bool is_tagged);
     NonnullRefPtr<Expression> parse_secondary_expression(NonnullRefPtr<Expression>, int min_precedence, Associativity associate = Associativity::Right);
     NonnullRefPtr<Expression> parse_secondary_expression(NonnullRefPtr<Expression>, int min_precedence, Associativity associate = Associativity::Right);
     NonnullRefPtr<CallExpression> parse_call_expression(NonnullRefPtr<Expression>);
     NonnullRefPtr<CallExpression> parse_call_expression(NonnullRefPtr<Expression>);
     NonnullRefPtr<NewExpression> parse_new_expression();
     NonnullRefPtr<NewExpression> parse_new_expression();

+ 12 - 0
Libraries/LibJS/Tests/tagged-template-literals.js

@@ -82,6 +82,18 @@ try {
     var rating = "great";
     var rating = "great";
     assert(review`${name} is a ${rating} project!` === "**SerenityOS** is a _great_ project!");
     assert(review`${name} is a ${rating} project!` === "**SerenityOS** is a _great_ project!");
 
 
+    const getTemplateObject = (...rest) => rest;
+    const getRawTemplateStrings = arr => arr.raw;
+
+    let o = getTemplateObject`foo\nbar`;
+    assert(Object.getOwnPropertyNames(o[0]).includes('raw'));
+
+    let raw = getRawTemplateStrings`foo${1 + 3}\nbar`;
+    assert(!Object.getOwnPropertyNames(raw).includes('raw'));
+    assert(raw.length === 2);
+    assert(raw[0] === 'foo');
+    assert(raw[1].length === 5 && raw[1] === '\\nbar');
+
     console.log("PASS");
     console.log("PASS");
 } catch (e) {
 } catch (e) {
     console.log("FAIL: " + e);
     console.log("FAIL: " + e);