mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 12:05:15 +00:00
LibJS: Add spreading in array literals
Implement the syntax and behavor necessary to support array literals such as [...[1, 2, 3]]. A type error is thrown if the target of the spread operator does not evaluate to an array (though it should eventually just check for an iterable). Note that the spread token's name is TripleDot, since the '...' token is used for two features: spread and rest. Calling it anything involving 'spread' or 'rest' would be a bit confusing.
This commit is contained in:
parent
49c438ce32
commit
80fecc615a
Notes:
sideshowbarker
2024-07-19 07:15:00 +09:00
Author: https://github.com/mattco98 🔰 Commit: https://github.com/SerenityOS/serenity/commit/80fecc615a6 Pull-request: https://github.com/SerenityOS/serenity/pull/1981
6 changed files with 101 additions and 2 deletions
|
@ -736,6 +736,17 @@ void Identifier::dump(int indent) const
|
|||
printf("Identifier \"%s\"\n", m_string.characters());
|
||||
}
|
||||
|
||||
void SpreadExpression::dump(int indent) const
|
||||
{
|
||||
ASTNode::dump(indent);
|
||||
m_target->dump(indent + 1);
|
||||
}
|
||||
|
||||
Value SpreadExpression::execute(Interpreter& interpreter) const
|
||||
{
|
||||
return m_target->execute(interpreter);
|
||||
}
|
||||
|
||||
Value ThisExpression::execute(Interpreter& interpreter) const
|
||||
{
|
||||
return interpreter.this_value();
|
||||
|
@ -1085,8 +1096,27 @@ Value ArrayExpression::execute(Interpreter& interpreter) const
|
|||
auto value = Value();
|
||||
if (element) {
|
||||
value = element->execute(interpreter);
|
||||
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
|
||||
if (element->is_spread_expression()) {
|
||||
if (!value.is_array()) {
|
||||
interpreter.throw_exception<TypeError>(String::format("%s is not iterable", value.to_string().characters()));
|
||||
return {};
|
||||
}
|
||||
|
||||
auto& array_to_spread = static_cast<Array&>(value.as_object());
|
||||
for (auto& it : array_to_spread.elements()) {
|
||||
if (it.is_empty()) {
|
||||
array->elements().append(js_undefined());
|
||||
} else {
|
||||
array->elements().append(it);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
array->elements().append(value);
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ public:
|
|||
virtual Value execute(Interpreter&) const = 0;
|
||||
virtual void dump(int indent) const;
|
||||
virtual bool is_identifier() const { return false; }
|
||||
virtual bool is_spread_expression() const { return false; }
|
||||
virtual bool is_member_expression() const { return false; }
|
||||
virtual bool is_scope_node() const { return false; }
|
||||
virtual bool is_program() const { return false; }
|
||||
|
@ -520,6 +521,23 @@ private:
|
|||
FlyString m_string;
|
||||
};
|
||||
|
||||
class SpreadExpression final : public Expression {
|
||||
public:
|
||||
explicit SpreadExpression(NonnullRefPtr<Expression> target)
|
||||
: m_target(target)
|
||||
{
|
||||
}
|
||||
|
||||
virtual Value execute(Interpreter&) const override;
|
||||
virtual void dump(int indent) const override;
|
||||
virtual bool is_spread_expression() const override { return true; }
|
||||
|
||||
private:
|
||||
virtual const char* class_name() const override { return "SpreadExpression"; }
|
||||
|
||||
NonnullRefPtr<Expression> m_target;
|
||||
};
|
||||
|
||||
class ThisExpression final : public Expression {
|
||||
public:
|
||||
virtual Value execute(Interpreter&) const override;
|
||||
|
|
|
@ -84,6 +84,7 @@ Lexer::Lexer(StringView source)
|
|||
s_three_char_tokens.set("<<=", TokenType::ShiftLeftEquals);
|
||||
s_three_char_tokens.set(">>=", TokenType::ShiftRightEquals);
|
||||
s_three_char_tokens.set(">>>", TokenType::UnsignedShiftRight);
|
||||
s_three_char_tokens.set("...", TokenType::TripleDot);
|
||||
}
|
||||
|
||||
if (s_two_char_tokens.is_empty()) {
|
||||
|
|
|
@ -492,10 +492,16 @@ NonnullRefPtr<ArrayExpression> Parser::parse_array_expression()
|
|||
consume(TokenType::BracketOpen);
|
||||
|
||||
Vector<RefPtr<Expression>> elements;
|
||||
while (match_expression() || match(TokenType::Comma)) {
|
||||
while (match_expression() || match(TokenType::TripleDot) || match(TokenType::Comma)) {
|
||||
RefPtr<Expression> expression;
|
||||
if (match_expression())
|
||||
|
||||
if (match(TokenType::TripleDot)) {
|
||||
consume(TokenType::TripleDot);
|
||||
expression = create_ast_node<SpreadExpression>(parse_expression(0));
|
||||
} else if (match_expression()) {
|
||||
expression = parse_expression(0);
|
||||
}
|
||||
|
||||
elements.append(expression);
|
||||
if (!match(TokenType::Comma))
|
||||
break;
|
||||
|
|
43
Libraries/LibJS/Tests/array-spread.js
Normal file
43
Libraries/LibJS/Tests/array-spread.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
load("test-common.js");
|
||||
|
||||
function testArray(arr) {
|
||||
return arr.length === 4 &&
|
||||
arr[0] === 0 &&
|
||||
arr[1] === 1 &&
|
||||
arr[2] === 2 &&
|
||||
arr[3] === 3;
|
||||
}
|
||||
|
||||
try {
|
||||
let arr = [0, ...[1, 2], 3];
|
||||
assert(testArray(arr));
|
||||
|
||||
let a = [1, 2];
|
||||
arr = [0, ...a, 3];
|
||||
assert(testArray(arr));
|
||||
|
||||
let obj = { a: [1, 2] };
|
||||
arr = [0, ...obj.a, 3];
|
||||
assert(testArray(arr));
|
||||
|
||||
arr = [...[], ...[...[0, 1, 2]], 3];
|
||||
assert(testArray(arr));
|
||||
|
||||
assertThrowsError(() => {
|
||||
[...1];
|
||||
}, {
|
||||
error: TypeError,
|
||||
message: "1 is not iterable",
|
||||
});
|
||||
|
||||
assertThrowsError(() => {
|
||||
[...{}];
|
||||
}, {
|
||||
error: TypeError,
|
||||
message: "[object Object] is not iterable",
|
||||
});
|
||||
|
||||
console.log("PASS");
|
||||
} catch (e) {
|
||||
console.log("FAIL: " + e);
|
||||
}
|
|
@ -115,6 +115,7 @@ namespace JS {
|
|||
__ENUMERATE_JS_TOKEN(This) \
|
||||
__ENUMERATE_JS_TOKEN(Throw) \
|
||||
__ENUMERATE_JS_TOKEN(Tilde) \
|
||||
__ENUMERATE_JS_TOKEN(TripleDot) \
|
||||
__ENUMERATE_JS_TOKEN(Try) \
|
||||
__ENUMERATE_JS_TOKEN(Typeof) \
|
||||
__ENUMERATE_JS_TOKEN(UnsignedShiftRight) \
|
||||
|
|
Loading…
Add table
Reference in a new issue