From 746dd5b190e3d3101bf91da22db2ccd86197b5f2 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Thu, 23 Apr 2020 19:37:53 +0100 Subject: [PATCH] LibJS: Implement computed properties in object expressions --- Base/home/anon/js/object-expression.js | 5 +++- Libraries/LibJS/AST.cpp | 29 +++++++++++++++------ Libraries/LibJS/AST.h | 25 ++++++++++++++++-- Libraries/LibJS/Parser.cpp | 23 +++++++++++------ Libraries/LibJS/Tests/object-basic.js | 35 +++++++++++++++++++++++++- 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 58449a2c0ab..143ece5967b 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 82871129a92..8c254f69c97 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 {}; - 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; } diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h index 72bac15cf90..ba37df528af 100644 --- a/Libraries/LibJS/AST.h +++ b/Libraries/LibJS/AST.h @@ -670,9 +670,30 @@ private: NonnullRefPtrVector m_declarations; }; +class ObjectProperty final : public ASTNode { +public: + ObjectProperty(NonnullRefPtr key, NonnullRefPtr 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 m_key; + NonnullRefPtr m_value; +}; + class ObjectExpression : public Expression { public: - ObjectExpression(HashMap> properties = {}) + ObjectExpression(NonnullRefPtrVector properties = {}) : m_properties(move(properties)) { } @@ -683,7 +704,7 @@ public: private: virtual const char* class_name() const override { return "ObjectExpression"; } - HashMap> m_properties; + NonnullRefPtrVector m_properties; }; class ArrayExpression : public Expression { diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index e608e87a87e..64fa1deab15 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/Libraries/LibJS/Parser.cpp @@ -435,19 +435,26 @@ NonnullRefPtr Parser::parse_unary_prefixed_expression() NonnullRefPtr Parser::parse_object_expression() { - HashMap> properties; + NonnullRefPtrVector properties; consume(TokenType::CurlyOpen); while (!done() && !match(TokenType::CurlyClose)) { - FlyString property_name; + RefPtr property_key; + RefPtr property_value; auto need_colon = true; if (match_identifier_name()) { - property_name = consume().value(); + auto identifier = consume().value(); + property_key = create_ast_node(identifier); + property_value = create_ast_node(identifier); need_colon = false; } else if (match(TokenType::StringLiteral)) { - property_name = consume(TokenType::StringLiteral).string_value(); + property_key = create_ast_node(consume(TokenType::StringLiteral).string_value()); } else if (match(TokenType::NumericLiteral)) { - property_name = consume(TokenType::NumericLiteral).value(); + property_key = create_ast_node(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 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(property_name)); + property_value = parse_expression(0); } + auto property = create_ast_node(*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 d93fe8ff074..2d02c9f453b 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);