Skip to content

Commit

Permalink
FEAT: Implement ??
Browse files Browse the repository at this point in the history
Co-authored-by: Greg Caban <[email protected]>
  • Loading branch information
0xe and nabacg committed Aug 29, 2024
1 parent 4e6523b commit b6365bc
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 10 deletions.
27 changes: 23 additions & 4 deletions rhino/src/main/java/org/mozilla/javascript/IRFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

package org.mozilla.javascript;

import static org.mozilla.javascript.Token.NULL;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -171,7 +173,7 @@ private Node transform(AstNode node) {
case Token.TRUE:
case Token.FALSE:
case Token.THIS:
case Token.NULL:
case NULL:
case Token.DEBUGGER:
return transformLiteral(node);

Expand Down Expand Up @@ -437,7 +439,7 @@ private Node transformAssignment(Assignment node) {
}

private AstNode transformAssignmentLeft(Assignment node, AstNode left, AstNode right) {
if (right.getType() == Token.NULL
if (right.getType() == NULL
&& node.getType() == Token.ASSIGN
&& left instanceof Name
&& right instanceof KeywordLiteral) {
Expand Down Expand Up @@ -1888,7 +1890,7 @@ private Node createMemberRefGet(Node target, String namespace, Node elem, int me
if (namespace != null) {
// See 11.1.2 in ECMA 357
if (namespace.equals("*")) {
nsNode = new Node(Token.NULL);
nsNode = new Node(NULL);
} else {
nsNode = parser.createName(namespace);
}
Expand Down Expand Up @@ -2038,6 +2040,23 @@ private static Node createBinary(int nodeType, Node left, Node right) {
}
break;
}

case Token.NULLISH_COALESCING:
{
// foo ?? default =>
// (foo == undefined || foo == null) ? foo (left) : default (right)

Node undefinedNode = new Name(0, "undefined");
Node nullNode = new Node(NULL);

Node conditional =
new Node(
Token.OR,
new Node(Token.SHEQ, nullNode, left),
new Node(Token.SHEQ, undefinedNode, left));

return new Node(Token.HOOK, conditional, right, left);
}
}

return new Node(nodeType, left, right);
Expand Down Expand Up @@ -2169,7 +2188,7 @@ private static Node makeReference(Node node) {
private static int isAlwaysDefinedBoolean(Node node) {
switch (node.getType()) {
case Token.FALSE:
case Token.NULL:
case NULL:
return ALWAYS_FALSE_BOOLEAN;
case Token.TRUE:
return ALWAYS_TRUE_BOOLEAN;
Expand Down
11 changes: 10 additions & 1 deletion rhino/src/main/java/org/mozilla/javascript/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -2370,7 +2370,7 @@ private AstNode assignExpr() throws IOException {
}

private AstNode condExpr() throws IOException {
AstNode pn = orExpr();
AstNode pn = nullishCoalescingExpr();
if (matchToken(Token.HOOK, true)) {
int line = ts.lineno;
int qmarkPos = ts.tokenBeg, colonPos = -1;
Expand Down Expand Up @@ -2402,6 +2402,15 @@ private AstNode condExpr() throws IOException {
return pn;
}

private AstNode nullishCoalescingExpr() throws IOException {
AstNode pn = orExpr();
if (matchToken(Token.NULLISH_COALESCING, true)) {
int opPos = ts.tokenBeg;
pn = new InfixExpression(Token.NULLISH_COALESCING, pn, nullishCoalescingExpr(), opPos);
}
return pn;
}

private AstNode orExpr() throws IOException {
AstNode pn = andExpr();
if (matchToken(Token.OR, true)) {
Expand Down
5 changes: 4 additions & 1 deletion rhino/src/main/java/org/mozilla/javascript/Token.java
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,8 @@ public static enum CommentType {
TEMPLATE_LITERAL_SUBST = 173, // template literal - substitution
TAGGED_TEMPLATE_LITERAL = 174, // template literal - tagged/handler
DOTDOTDOT = 175, // spread/rest ...
LAST_TOKEN = 175;
NULLISH_COALESCING = 176, // nullish coalescing (??)
LAST_TOKEN = 176;

/**
* Returns a name for the token. If Rhino is compiled with certain hardcoded debugging flags in
Expand Down Expand Up @@ -462,6 +463,8 @@ public static String typeToName(int token) {
return "COLON";
case OR:
return "OR";
case NULLISH_COALESCING:
return "NULLISH_COALESCING";
case AND:
return "AND";
case INC:
Expand Down
3 changes: 3 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/TokenStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,9 @@ && peekChar() == '!'
case ',':
return Token.COMMA;
case '?':
if (matchChar('?')) {
return Token.NULLISH_COALESCING;
}
return Token.HOOK;
case ':':
if (matchChar(':')) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.mozilla.javascript.tests;

import org.junit.Assert;
import org.junit.Test;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;

public class NullishCoalescingOpTest {

@Test
public void testNullishColascingBasic() {
try (Context cx = Context.enter()) {
Scriptable scope = cx.initStandardObjects();
cx.setLanguageVersion(Context.VERSION_ES6);

String script = "null ?? 'default string'";
Assert.assertEquals(
"default string",
cx.evaluateString(scope, script, "nullish coalescing basic", 0, null));

String script2 = "undefined ?? 'default string'";
Assert.assertEquals(
"default string",
cx.evaluateString(scope, script2, "nullish coalescing basic", 0, null));
}
}

@Test
public void testNullishColascingPrecedence() {
try (Context cx = Context.enter()) {
Scriptable scope = cx.initStandardObjects();
cx.setLanguageVersion(Context.VERSION_ES6);
cx.setOptimizationLevel(-1);

String script1 = "3 == 3 ? 'yes' ?? 'default string' : 'no'";
Assert.assertEquals(
"yes", cx.evaluateString(scope, script1, "nullish coalescing basic", 0, null));

String script3 = "3 || null ?? 'default string'";
Assert.assertEquals(
3.0, cx.evaluateString(scope, script3, "nullish coalescing basic", 0, null));

String script2 = "3 && null ?? 'default string'";
Assert.assertEquals(
"default string",
cx.evaluateString(scope, script2, "nullish coalescing basic", 0, null));
}
}
}
6 changes: 2 additions & 4 deletions tests/testsrc/test262.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4616,8 +4616,7 @@ language/expressions/compound-assignment 137/454 (30.18%)

language/expressions/concatenation 0/5 (0.0%)

language/expressions/conditional 3/22 (13.64%)
coalesce-expr-ternary.js
language/expressions/conditional 2/22 (9.09%)
tco-cond.js {unsupported: [tail-call-optimization]}
tco-pos.js {unsupported: [tail-call-optimization]}

Expand Down Expand Up @@ -5282,7 +5281,7 @@ language/expressions/new 41/59 (69.49%)

~language/expressions/new.target

language/expressions/object 866/1169 (74.08%)
language/expressions/object 865/1169 (73.99%)
dstr/async-gen-meth-ary-init-iter-close.js {unsupported: [async-iteration, async]}
dstr/async-gen-meth-ary-init-iter-get-err.js {unsupported: [async-iteration]}
dstr/async-gen-meth-ary-init-iter-get-err-array-prototype.js {unsupported: [async-iteration]}
Expand Down Expand Up @@ -6090,7 +6089,6 @@ language/expressions/object 866/1169 (74.08%)
cpn-obj-lit-computed-property-name-from-assignment-expression-logical-or.js
cpn-obj-lit-computed-property-name-from-async-arrow-function-expression.js
cpn-obj-lit-computed-property-name-from-await-expression.js {unsupported: [module, async]}
cpn-obj-lit-computed-property-name-from-expression-coalesce.js
cpn-obj-lit-computed-property-name-from-yield-expression.js
fn-name-accessor-get.js
fn-name-accessor-set.js
Expand Down

0 comments on commit b6365bc

Please sign in to comment.