summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Groh <mail@linusgroh.de>2020-04-23 19:37:53 +0100
committerAndreas Kling <kling@serenityos.org>2020-04-23 23:56:04 +0200
commit746dd5b190e3d3101bf91da22db2ccd86197b5f2 (patch)
tree20c09b5eecd1e2948f537517a14b1134658b5219
parentbebd5c097c9a5d8e1cfa20d8f1459006c76709e0 (diff)
downloadserenity-746dd5b190e3d3101bf91da22db2ccd86197b5f2.zip
LibJS: Implement computed properties in object expressions
-rw-r--r--Base/home/anon/js/object-expression.js5
-rw-r--r--Libraries/LibJS/AST.cpp29
-rw-r--r--Libraries/LibJS/AST.h25
-rw-r--r--Libraries/LibJS/Parser.cpp23
-rw-r--r--Libraries/LibJS/Tests/object-basic.js35
5 files changed, 98 insertions, 19 deletions
diff --git a/Base/home/anon/js/object-expression.js b/Base/home/anon/js/object-expression.js
index 58449a2c0a..143ece5967 100644
--- a/Base/home/anon/js/object-expression.js
+++ b/Base/home/anon/js/object-expression.js
@@ -1,7 +1,10 @@
const a = 1;
-const object = {a, b: 2};
+const computedKey = "d";
+const object = {a, b: 2, "c": 3, [computedKey]: 2 + 2};
const emptyObject = {};
console.log(object.a);
console.log(object.b);
+console.log(object.c);
+console.log(object.d);
console.log(emptyObject.foo);
diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp
index 82871129a9..8c254f69c9 100644
--- a/Libraries/LibJS/AST.cpp
+++ b/Libraries/LibJS/AST.cpp
@@ -936,13 +936,18 @@ void VariableDeclarator::dump(int indent) const
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
{
ASTNode::dump(indent);
- for (auto it : m_properties) {
- print_indent(indent + 1);
- printf("%s: ", it.key.characters());
- it.value->dump(0);
+ for (auto& property : m_properties) {
+ property.dump(indent + 1);
}
}
@@ -952,14 +957,24 @@ void ExpressionStatement::dump(int indent) const
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
{
auto* object = Object::create_empty(interpreter, interpreter.global_object());
- for (auto it : m_properties) {
- auto value = it.value->execute(interpreter);
+ for (auto& property : m_properties) {
+ auto key_result = property.key()->execute(interpreter);
+ if (interpreter.exception())
+ return {};
+ auto key = key_result.to_string();
+ auto value = property.value()->execute(interpreter);
if (interpreter.exception())
return {};
- object->put(it.key, value);
+ object->put(key, value);
}
return object;
}
diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h
index 72bac15cf9..ba37df528a 100644
--- a/Libraries/LibJS/AST.h
+++ b/Libraries/LibJS/AST.h
@@ -670,9 +670,30 @@ private:
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 {
public:
- ObjectExpression(HashMap<FlyString, NonnullRefPtr<Expression>> properties = {})
+ ObjectExpression(NonnullRefPtrVector<ObjectProperty> properties = {})
: m_properties(move(properties))
{
}
@@ -683,7 +704,7 @@ public:
private:
virtual const char* class_name() const override { return "ObjectExpression"; }
- HashMap<FlyString, NonnullRefPtr<Expression>> m_properties;
+ NonnullRefPtrVector<ObjectProperty> m_properties;
};
class ArrayExpression : public Expression {
diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp
index e608e87a87..64fa1deab1 100644
--- a/Libraries/LibJS/Parser.cpp
+++ b/Libraries/LibJS/Parser.cpp
@@ -435,19 +435,26 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
{
- HashMap<FlyString, NonnullRefPtr<Expression>> properties;
+ NonnullRefPtrVector<ObjectProperty> properties;
consume(TokenType::CurlyOpen);
while (!done() && !match(TokenType::CurlyClose)) {
- FlyString property_name;
+ RefPtr<Expression> property_key;
+ RefPtr<Expression> property_value;
auto need_colon = true;
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;
} 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)) {
- 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 {
m_parser_state.m_has_errors = true;
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)) {
consume(TokenType::Colon);
- properties.set(property_name, parse_expression(0));
- } else {
- properties.set(property_name, create_ast_node<Identifier>(property_name));
+ property_value = parse_expression(0);
}
+ auto property = create_ast_node<ObjectProperty>(*property_key, *property_value);
+ properties.append(property);
if (!match(TokenType::Comma))
break;
diff --git a/Libraries/LibJS/Tests/object-basic.js b/Libraries/LibJS/Tests/object-basic.js
index d93fe8ff07..2d02c9f453 100644
--- a/Libraries/LibJS/Tests/object-basic.js
+++ b/Libraries/LibJS/Tests/object-basic.js
@@ -1,13 +1,27 @@
load("test-common.js");
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.foo === "bar");
assert(o["foo"] === "bar");
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";
assert(o.baz === "test");
assert(o["baz"] === "test");
@@ -31,6 +45,25 @@ try {
assert(o2.catch === 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");
} catch (e) {
console.log("FAIL: " + e);