You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@royale.apache.org by jo...@apache.org on 2022/11/28 23:57:00 UTC

[royale-compiler] branch develop updated: nullish coalescing operator (references #219)

This is an automated email from the ASF dual-hosted git repository.

joshtynjala pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/royale-compiler.git


The following commit(s) were added to refs/heads/develop by this push:
     new 64bd91223 nullish coalescing operator (references #219)
64bd91223 is described below

commit 64bd91223fbdf0db698be68a828549f1d1731cc2
Author: Josh Tynjala <jo...@apache.org>
AuthorDate: Tue Nov 15 09:28:00 2022 -0800

    nullish coalescing operator (references #219)
---
 .../royale/compiler/internal/parsing/as/ASParser.g |  1 +
 .../compiler/internal/parsing/as/ASToken.java      |  3 +
 .../compiler/internal/parsing/as/BaseASParser.java | 81 ++++++++++++++-------
 .../internal/parsing/as/StreamingASTokenizer.java  |  2 +
 ...enthesizedNullishCoalescingOperatorProblem.java | 39 ++++++++++
 .../internal/parsing/as/RawASTokenizer.lex         |  5 ++
 .../java/as/ASNullishCoalescingOperatorTests.java  | 84 ++++++++++++++++++++++
 7 files changed, 189 insertions(+), 26 deletions(-)

diff --git a/compiler/src/main/antlr/org/apache/royale/compiler/internal/parsing/as/ASParser.g b/compiler/src/main/antlr/org/apache/royale/compiler/internal/parsing/as/ASParser.g
index 934bdfdf7..552fd3c6c 100644
--- a/compiler/src/main/antlr/org/apache/royale/compiler/internal/parsing/as/ASParser.g
+++ b/compiler/src/main/antlr/org/apache/royale/compiler/internal/parsing/as/ASParser.g
@@ -2354,6 +2354,7 @@ binaryOperators
     |   TOKEN_OPERATOR_DIVISION 
     |   TOKEN_OPERATOR_MODULO 
     |   TOKEN_OPERATOR_STAR
+    |   TOKEN_OPERATOR_NULLISH_COALESCING
 	;	
 
 /**
diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/ASToken.java b/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/ASToken.java
index 6c2d1c3c9..9d2d592b8 100644
--- a/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/ASToken.java
+++ b/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/ASToken.java
@@ -373,6 +373,7 @@ public class ASToken extends TokenBase implements IASToken, ASTokenTypes
             case TOKEN_KEYWORD_INSTANCEOF:
             case TOKEN_OPERATOR_ATSIGN:
             case TOKEN_OPERATOR_DESCENDANT_ACCESS:
+            case TOKEN_OPERATOR_NULLISH_COALESCING:
                 return true;
             default:
                 return false;
@@ -763,6 +764,7 @@ public class ASToken extends TokenBase implements IASToken, ASTokenTypes
             case TOKEN_OPERATOR_BITWISE_OR:
             case TOKEN_OPERATOR_LOGICAL_AND:
             case TOKEN_OPERATOR_LOGICAL_OR:
+            case TOKEN_OPERATOR_NULLISH_COALESCING:
 
             case TOKEN_OPERATOR_PLUS:
             case TOKEN_OPERATOR_MINUS:
@@ -831,6 +833,7 @@ public class ASToken extends TokenBase implements IASToken, ASTokenTypes
             case TOKEN_OPERATOR_BITWISE_OR:
             case TOKEN_OPERATOR_LOGICAL_AND:
             case TOKEN_OPERATOR_LOGICAL_OR:
+            case TOKEN_OPERATOR_NULLISH_COALESCING:
             case TOKEN_OPERATOR_PLUS:
             case TOKEN_OPERATOR_MINUS:
             case TOKEN_OPERATOR_BITWISE_NOT:
diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/BaseASParser.java b/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/BaseASParser.java
index 01cdd12cd..99bcb31c8 100644
--- a/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/BaseASParser.java
+++ b/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/BaseASParser.java
@@ -74,6 +74,7 @@ import org.apache.royale.compiler.internal.semantics.PostProcessStep;
 import org.apache.royale.compiler.internal.tree.as.ArrayLiteralNode;
 import org.apache.royale.compiler.internal.tree.as.BaseDefinitionNode;
 import org.apache.royale.compiler.internal.tree.as.BinaryOperatorNodeBase;
+import org.apache.royale.compiler.internal.tree.as.BinaryOperatorNotEqualNode;
 import org.apache.royale.compiler.internal.tree.as.BlockNode;
 import org.apache.royale.compiler.internal.tree.as.ClassNode;
 import org.apache.royale.compiler.internal.tree.as.ConfigConstNode;
@@ -96,6 +97,7 @@ import org.apache.royale.compiler.internal.tree.as.NamespaceNode;
 import org.apache.royale.compiler.internal.tree.as.NodeBase;
 import org.apache.royale.compiler.internal.tree.as.QualifiedNamespaceExpressionNode;
 import org.apache.royale.compiler.internal.tree.as.ScopedBlockNode;
+import org.apache.royale.compiler.internal.tree.as.TernaryOperatorNode;
 import org.apache.royale.compiler.internal.tree.as.UnaryOperatorNodeBase;
 import org.apache.royale.compiler.internal.tree.as.VariableNode;
 import org.apache.royale.compiler.internal.tree.as.metadata.MetaTagsNode;
@@ -130,6 +132,7 @@ import org.apache.royale.compiler.problems.SyntaxProblem;
 import org.apache.royale.compiler.problems.UnboundMetadataProblem;
 import org.apache.royale.compiler.problems.UnexpectedEOFProblem;
 import org.apache.royale.compiler.problems.UnexpectedTokenProblem;
+import org.apache.royale.compiler.problems.UnparenthesizedNullishCoalescingOperatorProblem;
 import org.apache.royale.compiler.problems.XMLOpenCloseTagNotMatchProblem;
 import org.apache.royale.compiler.projects.IASProject;
 import org.apache.royale.compiler.tree.ASTNodeID;
@@ -2103,31 +2106,32 @@ abstract class BaseASParser extends LLkParser implements IProblemReporter
                     .put(TOKEN_OPERATOR_BITWISE_RIGHT_SHIFT_ASSIGNMENT, 2)
                     .put(TOKEN_OPERATOR_BITWISE_UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, 2)
                     .put(TOKEN_OPERATOR_TERNARY, 3)
-                    .put(TOKEN_OPERATOR_LOGICAL_OR, 4)
-                    .put(TOKEN_OPERATOR_LOGICAL_AND, 5)
-                    .put(TOKEN_OPERATOR_BITWISE_OR, 6)
-                    .put(TOKEN_OPERATOR_BITWISE_XOR, 7)
-                    .put(TOKEN_OPERATOR_BITWISE_AND, 8)
-                    .put(TOKEN_OPERATOR_EQUAL, 9)
-                    .put(TOKEN_OPERATOR_NOT_EQUAL, 9)
-                    .put(TOKEN_OPERATOR_STRICT_EQUAL, 9)
-                    .put(TOKEN_OPERATOR_STRICT_NOT_EQUAL, 9)
-                    .put(TOKEN_OPERATOR_GREATER_THAN, 10)
-                    .put(TOKEN_OPERATOR_GREATER_THAN_EQUALS, 10)
-                    .put(TOKEN_OPERATOR_LESS_THAN, 10)
-                    .put(TOKEN_OPERATOR_LESS_THAN_EQUALS, 10)
-                    .put(TOKEN_KEYWORD_INSTANCEOF, 10)
-                    .put(TOKEN_KEYWORD_IS, 10)
-                    .put(TOKEN_KEYWORD_AS, 10)
-                    .put(TOKEN_KEYWORD_IN, 10)
-                    .put(TOKEN_OPERATOR_BITWISE_LEFT_SHIFT, 11)
-                    .put(TOKEN_OPERATOR_BITWISE_RIGHT_SHIFT, 11)
-                    .put(TOKEN_OPERATOR_BITWISE_UNSIGNED_RIGHT_SHIFT, 11)
-                    .put(TOKEN_OPERATOR_MINUS, 12)
-                    .put(TOKEN_OPERATOR_PLUS, 12)
-                    .put(TOKEN_OPERATOR_DIVISION, 13)
-                    .put(TOKEN_OPERATOR_MODULO, 13)
-                    .put(TOKEN_OPERATOR_STAR, 13)
+                    .put(TOKEN_OPERATOR_NULLISH_COALESCING, 4)
+                    .put(TOKEN_OPERATOR_LOGICAL_OR, 5)
+                    .put(TOKEN_OPERATOR_LOGICAL_AND, 6)
+                    .put(TOKEN_OPERATOR_BITWISE_OR, 7)
+                    .put(TOKEN_OPERATOR_BITWISE_XOR, 8)
+                    .put(TOKEN_OPERATOR_BITWISE_AND, 9)
+                    .put(TOKEN_OPERATOR_EQUAL, 10)
+                    .put(TOKEN_OPERATOR_NOT_EQUAL, 10)
+                    .put(TOKEN_OPERATOR_STRICT_EQUAL, 10)
+                    .put(TOKEN_OPERATOR_STRICT_NOT_EQUAL, 10)
+                    .put(TOKEN_OPERATOR_GREATER_THAN, 11)
+                    .put(TOKEN_OPERATOR_GREATER_THAN_EQUALS, 11)
+                    .put(TOKEN_OPERATOR_LESS_THAN, 11)
+                    .put(TOKEN_OPERATOR_LESS_THAN_EQUALS, 11)
+                    .put(TOKEN_KEYWORD_INSTANCEOF, 11)
+                    .put(TOKEN_KEYWORD_IS, 11)
+                    .put(TOKEN_KEYWORD_AS, 11)
+                    .put(TOKEN_KEYWORD_IN, 11)
+                    .put(TOKEN_OPERATOR_BITWISE_LEFT_SHIFT, 12)
+                    .put(TOKEN_OPERATOR_BITWISE_RIGHT_SHIFT, 12)
+                    .put(TOKEN_OPERATOR_BITWISE_UNSIGNED_RIGHT_SHIFT, 12)
+                    .put(TOKEN_OPERATOR_MINUS, 13)
+                    .put(TOKEN_OPERATOR_PLUS, 13)
+                    .put(TOKEN_OPERATOR_DIVISION, 14)
+                    .put(TOKEN_OPERATOR_MODULO, 14)
+                    .put(TOKEN_OPERATOR_STAR, 14)
                     .build();
 
     /**
@@ -2169,7 +2173,12 @@ abstract class BaseASParser extends LLkParser implements IProblemReporter
                 consume();
 
                 ExpressionNodeBase t = precedenceParseExpression(p1 + 1); // parse any sub expressions that are of higher precedence
-                result = (ExpressionNodeBase)BinaryOperatorNodeBase.create(op, result, t);
+                if (op.getType() == ASTokenTypes.TOKEN_OPERATOR_NULLISH_COALESCING) {
+                    result = transformNullishCoalescingExpression(result, op, t);
+                }
+                else {
+                    result = (ExpressionNodeBase)BinaryOperatorNodeBase.create(op, result, t);
+                }
                 op = LT(1);
                 p2 = precedence(op);
             }
@@ -3118,4 +3127,24 @@ abstract class BaseASParser extends LLkParser implements IProblemReporter
 
         return new LiteralNode(token, LiteralType.STRING);
     }
+
+    private final ExpressionNodeBase transformNullishCoalescingExpression(ExpressionNodeBase left, ASToken op, ExpressionNodeBase right)
+    {
+        if (left.getNodeID() == ASTNodeID.Op_LogicalAndID
+                || left.getNodeID() == ASTNodeID.Op_LogicalOrID
+                || right.getNodeID() == ASTNodeID.Op_LogicalAndID
+                || right.getNodeID() == ASTNodeID.Op_LogicalOrID) {
+            getSyntaxProblems().add(new UnparenthesizedNullishCoalescingOperatorProblem(op));
+        }
+
+        // (x != null) ? x : y;
+        ASToken notEqualToken = new ASToken(ASTokenTypes.TOKEN_OPERATOR_NOT_EQUAL, -1, -1, -1, -1, "!=");
+        ASToken nullToken = new ASToken(ASTokenTypes.TOKEN_KEYWORD_NULL, -1, -1, -1, -1, "null");
+        LiteralNode nullNode = new LiteralNode(nullToken, LiteralType.NULL);
+        BinaryOperatorNotEqualNode conditionalNode = new BinaryOperatorNotEqualNode(notEqualToken, left, nullNode);
+
+        ASToken ternaryOp = new ASToken(ASTokenTypes.TOKEN_OPERATOR_TERNARY, -1, -1, -1, -1, "?");
+        TernaryOperatorNode ternaryNode = new TernaryOperatorNode(ternaryOp, conditionalNode, left, right);
+        return ternaryNode;
+    }
 }
diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/StreamingASTokenizer.java b/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/StreamingASTokenizer.java
index 52e923d2d..49f6242d2 100644
--- a/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/StreamingASTokenizer.java
+++ b/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/StreamingASTokenizer.java
@@ -1179,6 +1179,7 @@ public class StreamingASTokenizer implements ASTokenTypes, IASTokenizer, Closeab
                 case TOKEN_OPERATOR_LOGICAL_OR:
                 case TOKEN_OPERATOR_LOGICAL_AND_ASSIGNMENT:
                 case TOKEN_OPERATOR_LOGICAL_OR_ASSIGNMENT:
+                case TOKEN_OPERATOR_NULLISH_COALESCING:
                 case TOKEN_TYPED_COLLECTION_OPEN:
                 case TOKEN_TYPED_COLLECTION_CLOSE:
                 case TOKEN_OPERATOR_MEMBER_ACCESS:
@@ -1442,6 +1443,7 @@ public class StreamingASTokenizer implements ASTokenTypes, IASTokenizer, Closeab
                             case TOKEN_OPERATOR_MODULO:
                             case TOKEN_OPERATOR_BITWISE_AND:
                             case TOKEN_OPERATOR_BITWISE_OR:
+                            case TOKEN_OPERATOR_NULLISH_COALESCING:
                             case TOKEN_KEYWORD_AS:
                             case TOKEN_OPERATOR_BITWISE_XOR:
                             case TOKEN_OPERATOR_LOGICAL_AND:
diff --git a/compiler/src/main/java/org/apache/royale/compiler/problems/UnparenthesizedNullishCoalescingOperatorProblem.java b/compiler/src/main/java/org/apache/royale/compiler/problems/UnparenthesizedNullishCoalescingOperatorProblem.java
new file mode 100644
index 000000000..8e8c9f45d
--- /dev/null
+++ b/compiler/src/main/java/org/apache/royale/compiler/problems/UnparenthesizedNullishCoalescingOperatorProblem.java
@@ -0,0 +1,39 @@
+/*
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.royale.compiler.problems;
+
+import org.apache.royale.compiler.common.ISourceLocation;
+
+/**
+ * Diagnostic emitted when a nullish coalescing operator is used unparenthesized
+ * within logical or or logical and expressions.
+ */
+public final class UnparenthesizedNullishCoalescingOperatorProblem extends SemanticProblem
+{
+    public static final String DESCRIPTION =
+        "Cannot use '??' unparenthesized within '||' and '&&' expressions.";
+
+    public UnparenthesizedNullishCoalescingOperatorProblem(ISourceLocation site)
+    {
+        super(site);
+    }
+}
+
+
diff --git a/compiler/src/main/jflex/org/apache/royale/compiler/internal/parsing/as/RawASTokenizer.lex b/compiler/src/main/jflex/org/apache/royale/compiler/internal/parsing/as/RawASTokenizer.lex
index 907354eda..f21ae0d71 100644
--- a/compiler/src/main/jflex/org/apache/royale/compiler/internal/parsing/as/RawASTokenizer.lex
+++ b/compiler/src/main/jflex/org/apache/royale/compiler/internal/parsing/as/RawASTokenizer.lex
@@ -546,6 +546,11 @@ REGEX_CLASS="[" ({REGEX_ESCAPE}|[^\n\r\]\\])* "]"
 	return buildToken(TOKEN_OPERATOR_LOGICAL_AND, "&&");
 }	
 
+<YYINITIAL> "??"
+{
+    return buildToken(TOKEN_OPERATOR_NULLISH_COALESCING, "??");
+}   
+
 <YYINITIAL> "||"
 {
 	return buildToken(TOKEN_OPERATOR_LOGICAL_OR, "||");
diff --git a/compiler/src/test/java/as/ASNullishCoalescingOperatorTests.java b/compiler/src/test/java/as/ASNullishCoalescingOperatorTests.java
new file mode 100644
index 000000000..a58d777b1
--- /dev/null
+++ b/compiler/src/test/java/as/ASNullishCoalescingOperatorTests.java
@@ -0,0 +1,84 @@
+/*
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package as;
+
+import java.io.File;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ASNullishCoalescingOperatorTests extends ASFeatureTestsBase
+{
+    @Test
+    public void testNonNull()
+    {
+        String[] testCode = new String[]
+        {
+            "var s:String = 'foo';",
+			"var result:Object = s ?? 'bar';",
+			"assertEqual('non-null nullish coalescing', result, 'foo');",
+        };
+        String source = getAS(new String[0], new String[0], testCode, new String[0]);
+
+        compileAndRun(source);
+    }
+
+	@Test
+    public void testNull()
+    {
+        String[] testCode = new String[]
+        {
+            "var s:String = null;",
+			"var result:Object = s ?? 'bar';",
+			"assertEqual('non-null nullish coalescing', result, 'bar');",
+        };
+        String source = getAS(new String[0], new String[0], testCode, new String[0]);
+
+        compileAndRun(source);
+    }
+
+	@Test
+    public void testNonNullMemberAccess()
+    {
+        String[] testCode = new String[]
+        {
+            "var o:Object = {field: 'foo'};",
+			"var result:Object = o.field ?? 'bar';",
+			"assertEqual('non-null nullish coalescing', result, 'foo');",
+        };
+        String source = getAS(new String[0], new String[0], testCode, new String[0]);
+
+        compileAndRun(source);
+    }
+
+	@Test
+    public void testNullMemberAccess()
+    {
+        String[] testCode = new String[]
+        {
+            "var o:Object = {field: null};",
+			"var result:Object = o.field ?? 'bar';",
+			"assertEqual('non-null nullish coalescing', result, 'bar');",
+        };
+        String source = getAS(new String[0], new String[0], testCode, new String[0]);
+
+        compileAndRun(source);
+    }
+}