mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 14:38:11 +00:00
LibJS: Implement computed properties in object expressions
This commit is contained in:
parent
bebd5c097c
commit
746dd5b190
5 changed files with 98 additions and 19 deletions
|
@ -1,7 +1,10 @@
|
||||||
const a = 1;
|
const a = 1;
|
||||||
const object = {a, b: 2};
|
const computedKey = "d";
|
||||||
|
const object = {a, b: 2, "c": 3, [computedKey]: 2 + 2};
|
||||||
const emptyObject = {};
|
const emptyObject = {};
|
||||||
|
|
||||||
console.log(object.a);
|
console.log(object.a);
|
||||||
console.log(object.b);
|
console.log(object.b);
|
||||||
|
console.log(object.c);
|
||||||
|
console.log(object.d);
|
||||||
console.log(emptyObject.foo);
|
console.log(emptyObject.foo);
|
||||||
|
|
|
@ -936,13 +936,18 @@ void VariableDeclarator::dump(int indent) const
|
||||||
m_init->dump(indent + 1);
|
m_init->dump(indent + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ObjectProperty::dump(int indent) const
|
||||||
|
{
|
||||||
|
ASTNode::dump(indent);
|
||||||
|
m_key->dump(indent + 1);
|
||||||
|
m_value->dump(indent + 1);
|
||||||
|
}
|
||||||
|
|
||||||
void ObjectExpression::dump(int indent) const
|
void ObjectExpression::dump(int indent) const
|
||||||
{
|
{
|
||||||
ASTNode::dump(indent);
|
ASTNode::dump(indent);
|
||||||
for (auto it : m_properties) {
|
for (auto& property : m_properties) {
|
||||||
print_indent(indent + 1);
|
property.dump(indent + 1);
|
||||||
printf("%s: ", it.key.characters());
|
|
||||||
it.value->dump(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -952,14 +957,24 @@ void ExpressionStatement::dump(int indent) const
|
||||||
m_expression->dump(indent + 1);
|
m_expression->dump(indent + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Value ObjectProperty::execute(Interpreter&) const
|
||||||
|
{
|
||||||
|
// NOTE: ObjectProperty execution is handled by ObjectExpression.
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
Value ObjectExpression::execute(Interpreter& interpreter) const
|
Value ObjectExpression::execute(Interpreter& interpreter) const
|
||||||
{
|
{
|
||||||
auto* object = Object::create_empty(interpreter, interpreter.global_object());
|
auto* object = Object::create_empty(interpreter, interpreter.global_object());
|
||||||
for (auto it : m_properties) {
|
for (auto& property : m_properties) {
|
||||||
auto value = it.value->execute(interpreter);
|
auto key_result = property.key()->execute(interpreter);
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
return {};
|
return {};
|
||||||
object->put(it.key, value);
|
auto key = key_result.to_string();
|
||||||
|
auto value = property.value()->execute(interpreter);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
object->put(key, value);
|
||||||
}
|
}
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
|
@ -670,9 +670,30 @@ private:
|
||||||
NonnullRefPtrVector<VariableDeclarator> m_declarations;
|
NonnullRefPtrVector<VariableDeclarator> m_declarations;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ObjectProperty final : public ASTNode {
|
||||||
|
public:
|
||||||
|
ObjectProperty(NonnullRefPtr<Expression> key, NonnullRefPtr<Expression> value)
|
||||||
|
: m_key(move(key))
|
||||||
|
, m_value(move(value))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const Expression& key() const { return m_key; }
|
||||||
|
const Expression& value() const { return m_value; }
|
||||||
|
|
||||||
|
virtual void dump(int indent) const override;
|
||||||
|
virtual Value execute(Interpreter&) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual const char* class_name() const override { return "ObjectProperty"; }
|
||||||
|
|
||||||
|
NonnullRefPtr<Expression> m_key;
|
||||||
|
NonnullRefPtr<Expression> m_value;
|
||||||
|
};
|
||||||
|
|
||||||
class ObjectExpression : public Expression {
|
class ObjectExpression : public Expression {
|
||||||
public:
|
public:
|
||||||
ObjectExpression(HashMap<FlyString, NonnullRefPtr<Expression>> properties = {})
|
ObjectExpression(NonnullRefPtrVector<ObjectProperty> properties = {})
|
||||||
: m_properties(move(properties))
|
: m_properties(move(properties))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -683,7 +704,7 @@ public:
|
||||||
private:
|
private:
|
||||||
virtual const char* class_name() const override { return "ObjectExpression"; }
|
virtual const char* class_name() const override { return "ObjectExpression"; }
|
||||||
|
|
||||||
HashMap<FlyString, NonnullRefPtr<Expression>> m_properties;
|
NonnullRefPtrVector<ObjectProperty> m_properties;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ArrayExpression : public Expression {
|
class ArrayExpression : public Expression {
|
||||||
|
|
|
@ -435,19 +435,26 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
|
||||||
|
|
||||||
NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
|
NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
|
||||||
{
|
{
|
||||||
HashMap<FlyString, NonnullRefPtr<Expression>> properties;
|
NonnullRefPtrVector<ObjectProperty> properties;
|
||||||
consume(TokenType::CurlyOpen);
|
consume(TokenType::CurlyOpen);
|
||||||
|
|
||||||
while (!done() && !match(TokenType::CurlyClose)) {
|
while (!done() && !match(TokenType::CurlyClose)) {
|
||||||
FlyString property_name;
|
RefPtr<Expression> property_key;
|
||||||
|
RefPtr<Expression> property_value;
|
||||||
auto need_colon = true;
|
auto need_colon = true;
|
||||||
if (match_identifier_name()) {
|
if (match_identifier_name()) {
|
||||||
property_name = consume().value();
|
auto identifier = consume().value();
|
||||||
|
property_key = create_ast_node<StringLiteral>(identifier);
|
||||||
|
property_value = create_ast_node<Identifier>(identifier);
|
||||||
need_colon = false;
|
need_colon = false;
|
||||||
} else if (match(TokenType::StringLiteral)) {
|
} else if (match(TokenType::StringLiteral)) {
|
||||||
property_name = consume(TokenType::StringLiteral).string_value();
|
property_key = create_ast_node<StringLiteral>(consume(TokenType::StringLiteral).string_value());
|
||||||
} else if (match(TokenType::NumericLiteral)) {
|
} else if (match(TokenType::NumericLiteral)) {
|
||||||
property_name = consume(TokenType::NumericLiteral).value();
|
property_key = create_ast_node<StringLiteral>(consume(TokenType::NumericLiteral).value());
|
||||||
|
} else if (match(TokenType::BracketOpen)) {
|
||||||
|
consume(TokenType::BracketOpen);
|
||||||
|
property_key = parse_expression(0);
|
||||||
|
consume(TokenType::BracketClose);
|
||||||
} else {
|
} else {
|
||||||
m_parser_state.m_has_errors = true;
|
m_parser_state.m_has_errors = true;
|
||||||
auto& current_token = m_parser_state.m_current_token;
|
auto& current_token = m_parser_state.m_current_token;
|
||||||
|
@ -461,10 +468,10 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
|
||||||
|
|
||||||
if (need_colon || match(TokenType::Colon)) {
|
if (need_colon || match(TokenType::Colon)) {
|
||||||
consume(TokenType::Colon);
|
consume(TokenType::Colon);
|
||||||
properties.set(property_name, parse_expression(0));
|
property_value = parse_expression(0);
|
||||||
} else {
|
|
||||||
properties.set(property_name, create_ast_node<Identifier>(property_name));
|
|
||||||
}
|
}
|
||||||
|
auto property = create_ast_node<ObjectProperty>(*property_key, *property_value);
|
||||||
|
properties.append(property);
|
||||||
|
|
||||||
if (!match(TokenType::Comma))
|
if (!match(TokenType::Comma))
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,13 +1,27 @@
|
||||||
load("test-common.js");
|
load("test-common.js");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var o = { 1: 23, foo: "bar", "hello": "friends" };
|
var foo = "bar";
|
||||||
|
var computed = "computed"
|
||||||
|
var o = {
|
||||||
|
1: 23,
|
||||||
|
foo,
|
||||||
|
bar: "baz",
|
||||||
|
"hello": "friends",
|
||||||
|
[1 + 2]: 42,
|
||||||
|
["I am a " + computed + " key"]: foo,
|
||||||
|
duplicate: "hello",
|
||||||
|
duplicate: "world"
|
||||||
|
};
|
||||||
assert(o[1] === 23);
|
assert(o[1] === 23);
|
||||||
assert(o["1"] === 23);
|
assert(o["1"] === 23);
|
||||||
assert(o.foo === "bar");
|
assert(o.foo === "bar");
|
||||||
assert(o["foo"] === "bar");
|
assert(o["foo"] === "bar");
|
||||||
assert(o.hello === "friends");
|
assert(o.hello === "friends");
|
||||||
assert(o["hello"] === "friends");
|
assert(o["hello"] === "friends");
|
||||||
|
assert(o[3] === 42);
|
||||||
|
assert(o["I am a computed key"] === "bar");
|
||||||
|
assert(o.duplicate === "world");
|
||||||
o.baz = "test";
|
o.baz = "test";
|
||||||
assert(o.baz === "test");
|
assert(o.baz === "test");
|
||||||
assert(o["baz"] === "test");
|
assert(o["baz"] === "test");
|
||||||
|
@ -31,6 +45,25 @@ try {
|
||||||
assert(o2.catch === 1);
|
assert(o2.catch === 1);
|
||||||
assert(o2.break === 1);
|
assert(o2.break === 1);
|
||||||
|
|
||||||
|
var a;
|
||||||
|
var append = x => { a.push(x); };
|
||||||
|
|
||||||
|
a = [];
|
||||||
|
var o3 = {[append(1)]: 1, [append(2)]: 2, [append(3)]: 3}
|
||||||
|
assert(a.length === 3);
|
||||||
|
assert(a[0] === 1);
|
||||||
|
assert(a[1] === 2);
|
||||||
|
assert(a[2] === 3);
|
||||||
|
assert(o3.undefined === 3);
|
||||||
|
|
||||||
|
a = [];
|
||||||
|
var o4 = {"test": append(1), "test": append(2), "test": append(3)}
|
||||||
|
assert(a.length === 3);
|
||||||
|
assert(a[0] === 1);
|
||||||
|
assert(a[1] === 2);
|
||||||
|
assert(a[2] === 3);
|
||||||
|
assert(o4.test === undefined);
|
||||||
|
|
||||||
console.log("PASS");
|
console.log("PASS");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("FAIL: " + e);
|
console.log("FAIL: " + e);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue