From d14ddb6461a14351e92eae9c73e22733ef19ab4e Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Sat, 18 Apr 2020 00:49:11 +0100 Subject: [PATCH] LibJS: Implement nullish coalescing operator (??) --- Libraries/LibJS/AST.cpp | 26 ++++++++----- Libraries/LibJS/AST.h | 1 + Libraries/LibJS/Parser.cpp | 6 ++- .../LibJS/Tests/logical-expressions-basic.js | 39 ++++++++++++++++++- 4 files changed, 60 insertions(+), 12 deletions(-) diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index 93ae05e2f7..4fec657128 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -338,20 +338,25 @@ Value LogicalExpression::execute(Interpreter& interpreter) const auto rhs_result = m_rhs->execute(interpreter); if (interpreter.exception()) return {}; - - return Value(rhs_result); + return rhs_result; } - - return Value(lhs_result); - case LogicalOp::Or: + return lhs_result; + case LogicalOp::Or: { if (lhs_result.to_boolean()) - return Value(lhs_result); - + return lhs_result; auto rhs_result = m_rhs->execute(interpreter); if (interpreter.exception()) return {}; - - return Value(rhs_result); + return rhs_result; + } + case LogicalOp::NullishCoalescing: + if (lhs_result.is_null() || lhs_result.is_undefined()) { + auto rhs_result = m_rhs->execute(interpreter); + if (interpreter.exception()) + return {}; + return rhs_result; + } + return lhs_result; } ASSERT_NOT_REACHED(); @@ -515,6 +520,9 @@ void LogicalExpression::dump(int indent) const case LogicalOp::Or: op_string = "||"; break; + case LogicalOp::NullishCoalescing: + op_string = "??"; + break; } print_indent(indent); diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h index 660cd1dad5..09835df3ec 100644 --- a/Libraries/LibJS/AST.h +++ b/Libraries/LibJS/AST.h @@ -367,6 +367,7 @@ private: enum class LogicalOp { And, Or, + NullishCoalescing, }; class LogicalExpression : public Expression { diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index 18a1feecf3..41f9c137dd 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/Libraries/LibJS/Parser.cpp @@ -601,6 +601,9 @@ NonnullRefPtr Parser::parse_secondary_expression(NonnullRefPtr(LogicalOp::Or, move(lhs), parse_expression(min_precedence, associativity)); + case TokenType::DoubleQuestionMark: + consume(); + return create_ast_node(LogicalOp::NullishCoalescing, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::QuestionMark: return parse_conditional_expression(move(lhs)); default: @@ -1039,7 +1042,8 @@ bool Parser::match_secondary_expression() const || type == TokenType::Pipe || type == TokenType::Caret || type == TokenType::DoubleAmpersand - || type == TokenType::DoublePipe; + || type == TokenType::DoublePipe + || type == TokenType::DoubleQuestionMark; } bool Parser::match_statement() const diff --git a/Libraries/LibJS/Tests/logical-expressions-basic.js b/Libraries/LibJS/Tests/logical-expressions-basic.js index b02eb6fcdd..ebd2b7c865 100644 --- a/Libraries/LibJS/Tests/logical-expressions-basic.js +++ b/Libraries/LibJS/Tests/logical-expressions-basic.js @@ -17,7 +17,6 @@ try { assert(("foo" && true) === true); assert((false && "bar") === false); assert((true && "bar") === "bar"); - assert((null && true) === null); assert((0 && false) === 0); assert((0 && true) === 0); assert((42 && false) === false); @@ -55,7 +54,6 @@ try { assert(("foo" || true) === "foo"); assert((false || "bar") === "bar"); assert((true || "bar") === true); - assert((null || true) === true); assert((0 || false) === false); assert((0 || true) === true); assert((42 || false) === 42); @@ -77,6 +75,43 @@ try { assert((false || undefined) === undefined); assert((true || undefined) === true); + assert((true ?? true) === true); + assert((false ?? false) === false); + assert((true ?? false) === true); + assert((false ?? true) === false); + assert((false ?? (1 === 2)) === false); + assert((true ?? (1 === 2)) === true); + assert(("" ?? "") === ""); + assert(("" ?? false) === ""); + assert(("" ?? true) === ""); + assert((false ?? "") === false); + assert((true ?? "") === true); + assert(("foo" ?? "bar") === "foo"); + assert(("foo" ?? false) === "foo"); + assert(("foo" ?? true) === "foo"); + assert((false ?? "bar") === false); + assert((true ?? "bar") === true); + assert((0 ?? false) === 0); + assert((0 ?? true) === 0); + assert((42 ?? false) === 42); + assert((42 ?? true) === 42); + assert((false ?? 0) === false); + assert((true ?? 0) === true); + assert((false ?? 42) === false); + assert((true ?? 42) === true); + assert(([] ?? false).length === 0); + assert(([] ?? true).length === 0); + assert((false ?? []) === false); + assert((true ?? []) === true); + assert((null ?? false) === false); + assert((null ?? true) === true); + assert((false ?? null) === false); + assert((true ?? null) === true); + assert((undefined ?? false) === false); + assert((undefined ?? true) === true); + assert((false ?? undefined) === false); + assert((true ?? undefined) === true); + console.log("PASS"); } catch (e) { console.log("FAIL: " + e);