diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index a3537577e9..0a758197c0 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -1347,6 +1347,16 @@ Value TaggedTemplateLiteral::execute(Interpreter& interpreter) const else 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)); } diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h index af753de830..e487c98d21 100644 --- a/Libraries/LibJS/AST.h +++ b/Libraries/LibJS/AST.h @@ -779,7 +779,13 @@ private: class TemplateLiteral final : public Expression { public: TemplateLiteral(NonnullRefPtrVector expressions) - : m_expressions(expressions) + : m_expressions(move(expressions)) + { + } + + TemplateLiteral(NonnullRefPtrVector expressions, NonnullRefPtrVector raw_strings) + : m_expressions(move(expressions)) + , m_raw_strings(move(raw_strings)) { } @@ -787,11 +793,13 @@ public: virtual void dump(int indent) const override; const NonnullRefPtrVector& expressions() const { return m_expressions; } + const NonnullRefPtrVector& raw_strings() const { return m_raw_strings; } private: virtual const char* class_name() const override { return "TemplateLiteral"; } const NonnullRefPtrVector m_expressions; + const NonnullRefPtrVector m_raw_strings; }; class TaggedTemplateLiteral final : public Expression { diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index 05568e7dd4..9ca17144a5 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/Libraries/LibJS/Parser.cpp @@ -417,7 +417,7 @@ NonnullRefPtr Parser::parse_primary_expression() case TokenType::BracketOpen: return parse_array_expression(); case TokenType::TemplateLiteralStart: - return parse_template_literal(); + return parse_template_literal(false); case TokenType::New: return parse_new_expression(); default: @@ -560,18 +560,29 @@ NonnullRefPtr Parser::parse_array_expression() return create_ast_node(move(elements)); } -NonnullRefPtr Parser::parse_template_literal() +NonnullRefPtr Parser::parse_template_literal(bool is_tagged) { consume(TokenType::TemplateLiteralStart); NonnullRefPtrVector expressions; + NonnullRefPtrVector raw_strings; + + auto append_empty_string = [&expressions, &raw_strings, is_tagged]() { + auto string_literal = create_ast_node(""); + expressions.append(string_literal); + if (is_tagged) + raw_strings.append(string_literal); + }; if (!match(TokenType::TemplateLiteralString)) - expressions.append(create_ast_node("")); + append_empty_string(); while (!match(TokenType::TemplateLiteralEnd) && !match(TokenType::UnterminatedTemplateLiteral)) { if (match(TokenType::TemplateLiteralString)) { - expressions.append(create_ast_node(consume().string_value())); + auto token = consume(); + expressions.append(create_ast_node(token.string_value())); + if (is_tagged) + raw_strings.append(create_ast_node(token.value())); } else if (match(TokenType::TemplateLiteralExprStart)) { consume(TokenType::TemplateLiteralExprStart); if (match(TokenType::TemplateLiteralExprEnd)) { @@ -587,7 +598,7 @@ NonnullRefPtr Parser::parse_template_literal() consume(TokenType::TemplateLiteralExprEnd); if (!match(TokenType::TemplateLiteralString)) - expressions.append(create_ast_node("")); + append_empty_string(); } } @@ -597,6 +608,8 @@ NonnullRefPtr Parser::parse_template_literal() consume(TokenType::TemplateLiteralEnd); } + if (is_tagged) + return create_ast_node(expressions, raw_strings); return create_ast_node(expressions); } @@ -604,7 +617,7 @@ NonnullRefPtr Parser::parse_expression(int min_precedence, Associati { auto expression = parse_primary_expression(); while (match(TokenType::TemplateLiteralStart)) { - auto template_literal = parse_template_literal(); + auto template_literal = parse_template_literal(true); expression = create_ast_node(move(expression), move(template_literal)); } while (match_secondary_expression()) { @@ -617,7 +630,7 @@ NonnullRefPtr Parser::parse_expression(int min_precedence, Associati Associativity new_associativity = operator_associativity(m_parser_state.m_current_token.type()); expression = parse_secondary_expression(move(expression), new_precedence, new_associativity); while (match(TokenType::TemplateLiteralStart)) { - auto template_literal = parse_template_literal(); + auto template_literal = parse_template_literal(true); expression = create_ast_node(move(expression), move(template_literal)); } } diff --git a/Libraries/LibJS/Parser.h b/Libraries/LibJS/Parser.h index 273dce7c53..90eb95ed09 100644 --- a/Libraries/LibJS/Parser.h +++ b/Libraries/LibJS/Parser.h @@ -69,7 +69,7 @@ public: NonnullRefPtr parse_unary_prefixed_expression(); NonnullRefPtr parse_object_expression(); NonnullRefPtr parse_array_expression(); - NonnullRefPtr parse_template_literal(); + NonnullRefPtr parse_template_literal(bool is_tagged); NonnullRefPtr parse_secondary_expression(NonnullRefPtr, int min_precedence, Associativity associate = Associativity::Right); NonnullRefPtr parse_call_expression(NonnullRefPtr); NonnullRefPtr parse_new_expression(); diff --git a/Libraries/LibJS/Tests/tagged-template-literals.js b/Libraries/LibJS/Tests/tagged-template-literals.js index 7318ab5e35..1c7498e1bd 100644 --- a/Libraries/LibJS/Tests/tagged-template-literals.js +++ b/Libraries/LibJS/Tests/tagged-template-literals.js @@ -82,6 +82,18 @@ try { var rating = "great"; 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"); } catch (e) { console.log("FAIL: " + e);