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);
+ }
+}