ソースを参照

LibJS: Implement nullish coalescing operator (??)

Linus Groh 5 年 前
コミット
d14ddb6461

+ 17 - 9
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);

+ 1 - 0
Libraries/LibJS/AST.h

@@ -367,6 +367,7 @@ private:
 enum class LogicalOp {
     And,
     Or,
+    NullishCoalescing,
 };
 
 class LogicalExpression : public Expression {

+ 5 - 1
Libraries/LibJS/Parser.cpp

@@ -601,6 +601,9 @@ NonnullRefPtr<Expression> Parser::parse_secondary_expression(NonnullRefPtr<Expre
     case TokenType::DoublePipe:
         consume();
         return create_ast_node<LogicalExpression>(LogicalOp::Or, move(lhs), parse_expression(min_precedence, associativity));
+    case TokenType::DoubleQuestionMark:
+        consume();
+        return create_ast_node<LogicalExpression>(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

+ 37 - 2
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);