From 11728b7db5e79756e5a212c7fe4a1acaf5657767 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Thu, 23 Apr 2020 16:06:01 +0100 Subject: [PATCH] LibJS: Implement 'in' operator --- Libraries/LibJS/AST.cpp | 5 ++++ Libraries/LibJS/AST.h | 1 + Libraries/LibJS/Parser.cpp | 4 +++ Libraries/LibJS/Runtime/Value.cpp | 8 +++++ Libraries/LibJS/Runtime/Value.h | 1 + Libraries/LibJS/Tests/in-operator-basic.js | 35 ++++++++++++++++++++++ 6 files changed, 54 insertions(+) create mode 100644 Libraries/LibJS/Tests/in-operator-basic.js diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index 1fc0b29bf0..82871129a9 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -331,6 +331,8 @@ Value BinaryExpression::execute(Interpreter& interpreter) const return right_shift(interpreter, lhs_result, rhs_result); case BinaryOp::UnsignedRightShift: return unsigned_right_shift(interpreter, lhs_result, rhs_result); + case BinaryOp::In: + return in(interpreter, lhs_result, rhs_result); case BinaryOp::InstanceOf: return instance_of(interpreter, lhs_result, rhs_result); } @@ -512,6 +514,9 @@ void BinaryExpression::dump(int indent) const case BinaryOp::UnsignedRightShift: op_string = ">>>"; break; + case BinaryOp::In: + op_string = "in"; + break; case BinaryOp::InstanceOf: op_string = "instanceof"; break; diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h index 8005441c74..72bac15cf9 100644 --- a/Libraries/LibJS/AST.h +++ b/Libraries/LibJS/AST.h @@ -343,6 +343,7 @@ enum class BinaryOp { LeftShift, RightShift, UnsignedRightShift, + In, InstanceOf, }; diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index c58a9a3f27..b9b6cf0025 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/Libraries/LibJS/Parser.cpp @@ -566,6 +566,9 @@ NonnullRefPtr Parser::parse_secondary_expression(NonnullRefPtr(BinaryOp::AbstractInequals, move(lhs), parse_expression(min_precedence, associativity)); + case TokenType::In: + consume(); + return create_ast_node(BinaryOp::In, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::Instanceof: consume(); return create_ast_node(BinaryOp::InstanceOf, move(lhs), parse_expression(min_precedence, associativity)); @@ -1076,6 +1079,7 @@ bool Parser::match_secondary_expression() const || type == TokenType::BracketOpen || type == TokenType::PlusPlus || type == TokenType::MinusMinus + || type == TokenType::In || type == TokenType::Instanceof || type == TokenType::QuestionMark || type == TokenType::Ampersand diff --git a/Libraries/LibJS/Runtime/Value.cpp b/Libraries/LibJS/Runtime/Value.cpp index 2540621c85..ddb74d6d37 100644 --- a/Libraries/LibJS/Runtime/Value.cpp +++ b/Libraries/LibJS/Runtime/Value.cpp @@ -382,6 +382,14 @@ Value eq(Interpreter& interpreter, Value lhs, Value rhs) return Value(false); } +Value in(Interpreter& interpreter, Value lhs, Value rhs) +{ + if (!rhs.is_object()) + return interpreter.throw_exception("'in' operator must be used on object"); + + return Value(rhs.as_object().get(lhs.to_string()).has_value()); +} + Value instance_of(Interpreter&, Value lhs, Value rhs) { if (!lhs.is_object() || !rhs.is_object()) diff --git a/Libraries/LibJS/Runtime/Value.h b/Libraries/LibJS/Runtime/Value.h index c05ca22cc2..df0bd23efb 100644 --- a/Libraries/LibJS/Runtime/Value.h +++ b/Libraries/LibJS/Runtime/Value.h @@ -222,6 +222,7 @@ Value mod(Interpreter&, Value lhs, Value rhs); Value exp(Interpreter&, Value lhs, Value rhs); Value eq(Interpreter&, Value lhs, Value rhs); Value typed_eq(Interpreter&, Value lhs, Value rhs); +Value in(Interpreter&, Value lhs, Value rhs); Value instance_of(Interpreter&, Value lhs, Value rhs); const LogStream& operator<<(const LogStream&, const Value&); diff --git a/Libraries/LibJS/Tests/in-operator-basic.js b/Libraries/LibJS/Tests/in-operator-basic.js new file mode 100644 index 0000000000..0cc523e756 --- /dev/null +++ b/Libraries/LibJS/Tests/in-operator-basic.js @@ -0,0 +1,35 @@ +load("test-common.js"); + +try { + ["foo", 123, null, undefined].forEach(value => { + assertThrowsError(() => { + "prop" in value; + }, { + error: TypeError, + message: "'in' operator must be used on object" + }); + }); + + var o = {foo: "bar", bar: undefined}; + assert("" in o === false); + assert("foo" in o === true); + assert("bar" in o === true); + assert("baz" in o === false); + assert("toString" in o === true); + + var a = ["hello", "friends"]; + assert(0 in a === true); + assert(1 in a === true); + assert(2 in a === false); + assert("0" in a === true); + assert("hello" in a === false); + assert("friends" in a === false); + assert("length" in a === true); + + var s = new String("foo"); + assert("length" in s); + + console.log("PASS"); +} catch (e) { + console.log("FAIL: " + e); +}