mirror of
https://github.com/RGBCube/serenity
synced 2025-07-24 18:27:42 +00:00
LibJS: Implement tagged template literals (foobar
)
To make processing tagged template literals easier, template literals will now add one empty StringLiteral before and after each template expression *if* there's no other string - e.g.: `${foo}` -> "", foo, "" `test${foo}${bar}test` -> "test", foo, "", bar, "test" This also matches the behaviour of many other parsers.
This commit is contained in:
parent
eea62dd365
commit
4d20cf57db
4 changed files with 161 additions and 3 deletions
|
@ -1277,8 +1277,7 @@ Value ArrayExpression::execute(Interpreter& interpreter) const
|
||||||
void TemplateLiteral::dump(int indent) const
|
void TemplateLiteral::dump(int indent) const
|
||||||
{
|
{
|
||||||
ASTNode::dump(indent);
|
ASTNode::dump(indent);
|
||||||
|
for (auto& expression : m_expressions)
|
||||||
for (auto& expression : expressions())
|
|
||||||
expression.dump(indent + 1);
|
expression.dump(indent + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1286,7 +1285,7 @@ Value TemplateLiteral::execute(Interpreter& interpreter) const
|
||||||
{
|
{
|
||||||
StringBuilder string_builder;
|
StringBuilder string_builder;
|
||||||
|
|
||||||
for (auto& expression : expressions()) {
|
for (auto& expression : m_expressions) {
|
||||||
auto expr = expression.execute(interpreter);
|
auto expr = expression.execute(interpreter);
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
return {};
|
return {};
|
||||||
|
@ -1296,6 +1295,45 @@ Value TemplateLiteral::execute(Interpreter& interpreter) const
|
||||||
return js_string(interpreter, string_builder.build());
|
return js_string(interpreter, string_builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TaggedTemplateLiteral::dump(int indent) const
|
||||||
|
{
|
||||||
|
ASTNode::dump(indent);
|
||||||
|
print_indent(indent + 1);
|
||||||
|
printf("(Tag)\n");
|
||||||
|
m_tag->dump(indent + 2);
|
||||||
|
print_indent(indent + 1);
|
||||||
|
printf("(Template Literal)\n");
|
||||||
|
m_template_literal->dump(indent + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Value TaggedTemplateLiteral::execute(Interpreter& interpreter) const
|
||||||
|
{
|
||||||
|
auto tag = m_tag->execute(interpreter);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
if (!tag.is_function()) {
|
||||||
|
interpreter.throw_exception<TypeError>(String::format("%s is not a function", tag.to_string().characters()));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto& tag_function = tag.as_function();
|
||||||
|
auto& expressions = m_template_literal->expressions();
|
||||||
|
auto* strings = Array::create(interpreter.global_object());
|
||||||
|
MarkedValueList arguments(interpreter.heap());
|
||||||
|
arguments.append(strings);
|
||||||
|
for (size_t i = 0; i < expressions.size(); ++i) {
|
||||||
|
auto value = expressions[i].execute(interpreter);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
// tag`${foo}` -> "", foo, "" -> tag(["", ""], foo)
|
||||||
|
// tag`foo${bar}baz${qux}` -> "foo", bar, "baz", qux, "" -> tag(["foo", "baz", ""], bar, qux)
|
||||||
|
if (i % 2 == 0)
|
||||||
|
strings->elements().append(value);
|
||||||
|
else
|
||||||
|
arguments.append(value);
|
||||||
|
}
|
||||||
|
return interpreter.call(tag_function, js_undefined(), move(arguments));
|
||||||
|
}
|
||||||
|
|
||||||
void TryStatement::dump(int indent) const
|
void TryStatement::dump(int indent) const
|
||||||
{
|
{
|
||||||
ASTNode::dump(indent);
|
ASTNode::dump(indent);
|
||||||
|
|
|
@ -786,6 +786,24 @@ private:
|
||||||
const NonnullRefPtrVector<Expression> m_expressions;
|
const NonnullRefPtrVector<Expression> m_expressions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class TaggedTemplateLiteral final : public Expression {
|
||||||
|
public:
|
||||||
|
TaggedTemplateLiteral(NonnullRefPtr<Expression> tag, NonnullRefPtr<TemplateLiteral> template_literal)
|
||||||
|
: m_tag(move(tag))
|
||||||
|
, m_template_literal(move(template_literal))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Value execute(Interpreter&) const override;
|
||||||
|
virtual void dump(int indent) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual const char* class_name() const override { return "TaggedTemplateLiteral"; }
|
||||||
|
|
||||||
|
const NonnullRefPtr<Expression> m_tag;
|
||||||
|
const NonnullRefPtr<TemplateLiteral> m_template_literal;
|
||||||
|
};
|
||||||
|
|
||||||
class MemberExpression final : public Expression {
|
class MemberExpression final : public Expression {
|
||||||
public:
|
public:
|
||||||
MemberExpression(NonnullRefPtr<Expression> object, NonnullRefPtr<Expression> property, bool computed = false)
|
MemberExpression(NonnullRefPtr<Expression> object, NonnullRefPtr<Expression> property, bool computed = false)
|
||||||
|
|
|
@ -560,6 +560,9 @@ NonnullRefPtr<TemplateLiteral> Parser::parse_template_literal()
|
||||||
|
|
||||||
NonnullRefPtrVector<Expression> expressions;
|
NonnullRefPtrVector<Expression> expressions;
|
||||||
|
|
||||||
|
if (!match(TokenType::TemplateLiteralString))
|
||||||
|
expressions.append(create_ast_node<StringLiteral>(""));
|
||||||
|
|
||||||
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()));
|
expressions.append(create_ast_node<StringLiteral>(consume().string_value()));
|
||||||
|
@ -576,6 +579,9 @@ NonnullRefPtr<TemplateLiteral> Parser::parse_template_literal()
|
||||||
return create_ast_node<TemplateLiteral>(expressions);
|
return create_ast_node<TemplateLiteral>(expressions);
|
||||||
}
|
}
|
||||||
consume(TokenType::TemplateLiteralExprEnd);
|
consume(TokenType::TemplateLiteralExprEnd);
|
||||||
|
|
||||||
|
if (!match(TokenType::TemplateLiteralString))
|
||||||
|
expressions.append(create_ast_node<StringLiteral>(""));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -591,6 +597,10 @@ NonnullRefPtr<TemplateLiteral> Parser::parse_template_literal()
|
||||||
NonnullRefPtr<Expression> Parser::parse_expression(int min_precedence, Associativity associativity)
|
NonnullRefPtr<Expression> Parser::parse_expression(int min_precedence, Associativity associativity)
|
||||||
{
|
{
|
||||||
auto expression = parse_primary_expression();
|
auto expression = parse_primary_expression();
|
||||||
|
while (match(TokenType::TemplateLiteralStart)) {
|
||||||
|
auto template_literal = parse_template_literal();
|
||||||
|
expression = create_ast_node<TaggedTemplateLiteral>(move(expression), move(template_literal));
|
||||||
|
}
|
||||||
while (match_secondary_expression()) {
|
while (match_secondary_expression()) {
|
||||||
int new_precedence = operator_precedence(m_parser_state.m_current_token.type());
|
int new_precedence = operator_precedence(m_parser_state.m_current_token.type());
|
||||||
if (new_precedence < min_precedence)
|
if (new_precedence < min_precedence)
|
||||||
|
@ -600,6 +610,10 @@ 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)) {
|
||||||
|
auto template_literal = parse_template_literal();
|
||||||
|
expression = create_ast_node<TaggedTemplateLiteral>(move(expression), move(template_literal));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
|
|
88
Libraries/LibJS/Tests/tagged-template-literals.js
Normal file
88
Libraries/LibJS/Tests/tagged-template-literals.js
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
load("test-common.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
assertThrowsError(() => {
|
||||||
|
foo`bar${baz}`;
|
||||||
|
}, {
|
||||||
|
error: ReferenceError,
|
||||||
|
message: "'foo' not known"
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
function foo() { }
|
||||||
|
foo`bar${baz}`;
|
||||||
|
}, {
|
||||||
|
error: ReferenceError,
|
||||||
|
message: "'baz' not known"
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrowsError(() => {
|
||||||
|
undefined``````;
|
||||||
|
}, {
|
||||||
|
error: TypeError,
|
||||||
|
message: "undefined is not a function"
|
||||||
|
});
|
||||||
|
|
||||||
|
function test1(strings) {
|
||||||
|
assert(strings instanceof Array);
|
||||||
|
assert(strings.length === 1);
|
||||||
|
assert(strings[0] === "");
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
assert(test1`` === 42);
|
||||||
|
|
||||||
|
function test2(s) {
|
||||||
|
return function (strings) {
|
||||||
|
assert(strings instanceof Array);
|
||||||
|
assert(strings.length === 1);
|
||||||
|
assert(strings[0] === "bar");
|
||||||
|
return s + strings[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(test2("foo")`bar` === "foobar");
|
||||||
|
|
||||||
|
var test3 = {
|
||||||
|
foo(strings, p1) {
|
||||||
|
assert(strings instanceof Array);
|
||||||
|
assert(strings.length === 2);
|
||||||
|
assert(strings[0] === "");
|
||||||
|
assert(strings[1] === "");
|
||||||
|
assert(p1 === "bar");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
test3.foo`${"bar"}`;
|
||||||
|
|
||||||
|
function test4(strings, p1) {
|
||||||
|
assert(strings instanceof Array);
|
||||||
|
assert(strings.length === 2);
|
||||||
|
assert(strings[0] === "foo");
|
||||||
|
assert(strings[1] === "");
|
||||||
|
assert(p1 === 42);
|
||||||
|
}
|
||||||
|
var bar = 42;
|
||||||
|
test4`foo${bar}`;
|
||||||
|
|
||||||
|
function test5(strings, p1, p2) {
|
||||||
|
assert(strings instanceof Array);
|
||||||
|
assert(strings.length === 3);
|
||||||
|
assert(strings[0] === "foo");
|
||||||
|
assert(strings[1] === "baz");
|
||||||
|
assert(strings[2] === "");
|
||||||
|
assert(p1 === 42);
|
||||||
|
assert(p2 === "qux");
|
||||||
|
return (strings, value) => `${value}${strings[0]}`;
|
||||||
|
}
|
||||||
|
var bar = 42;
|
||||||
|
assert(test5`foo${bar}baz${"qux"}``test${123}` === "123test");
|
||||||
|
|
||||||
|
function review(strings, name, rating) {
|
||||||
|
return `${strings[0]}**${name}**${strings[1]}_${rating}_${strings[2]}`;
|
||||||
|
}
|
||||||
|
var name = "SerenityOS";
|
||||||
|
var rating = "great";
|
||||||
|
assert(review`${name} is a ${rating} project!` === "**SerenityOS** is a _great_ project!");
|
||||||
|
|
||||||
|
console.log("PASS");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("FAIL: " + e);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue