Skip to content

Commit 19ccabe

Browse files
Merge pull request #38 from apex-dev-tools/null-coalesce
Null coalesce
2 parents f432ba7 + c6872c4 commit 19ccabe

File tree

9 files changed

+122
-8
lines changed

9 files changed

+122
-8
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# apex-parser - Changelog
22

3+
## 3.6.0 - 2024-02-15
4+
5+
- Add null coalesce operator and expression
6+
37
## 3.5.0 - 2023-10-15
48

59
- Correct do-while to require block rather than statement

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ Maven
4848
<dependency>
4949
<groupId>io.github.apex-dev-tools</groupId>
5050
<artifactId>apex-parser</artifactId>
51-
<version>3.5.0</version>
51+
<version>3.6.0</version>
5252
</dependency>
5353

5454
NPM
5555

56-
"@apexdevtools/apex-parser": "^3.5.0"
56+
"@apexdevtools/apex-parser": "^3.6.0"
5757

5858
## Building
5959

antlr/ApexLexer.g4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ LESSANDGREATER : '<>';
384384
TRIPLENOTEQUAL : '!==';
385385
AND : '&&';
386386
OR : '||';
387+
COAL : '??';
387388
INC : '++';
388389
DEC : '--';
389390
ADD : '+';

antlr/ApexParser.g4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ expression
451451
| expression BITOR expression # bitOrExpression
452452
| expression AND expression # logAndExpression
453453
| expression OR expression # logOrExpression
454+
| expression COAL expression # coalExpression
454455
| <assoc=right> expression QUESTION expression COLON expression # condExpression
455456
| <assoc=right> expression
456457
( ASSIGN

jvm/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<groupId>io.github.apex-dev-tools</groupId>
66
<artifactId>apex-parser</artifactId>
7-
<version>3.5.0</version>
7+
<version>3.6.0</version>
88
<packaging>jar</packaging>
99

1010
<name>apex-parser</name>

jvm/src/test/java/com/nawforce/apexparser/ApexParserTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import org.junit.jupiter.api.Test;
1717

18+
import java.util.List;
1819
import java.util.Map;
1920

2021
import static com.nawforce.apexparser.SyntaxErrorCounter.createParser;
@@ -40,6 +41,52 @@ void testExpression() {
4041
assertEquals(2, ((ApexParser.Arth1ExpressionContext) context).expression().size());
4142
}
4243

44+
@Test
45+
void testCoalesceExpression() {
46+
Map.Entry<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser("a ?? 5");
47+
ApexParser.ExpressionContext context = parserAndCounter.getKey().expression();
48+
assertTrue(context instanceof ApexParser.CoalExpressionContext);
49+
assertEquals(2, ((ApexParser.CoalExpressionContext) context).expression().size());
50+
}
51+
52+
@Test
53+
void testCoalescePrecedence() {
54+
Map.Entry<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser("top ?? 100 - bottom ?? 0");
55+
ApexParser.ExpressionContext context = parserAndCounter.getKey().expression();
56+
assertTrue(context instanceof ApexParser.CoalExpressionContext);
57+
58+
List<ApexParser.ExpressionContext> outer = ((ApexParser.CoalExpressionContext) context).expression();
59+
assertEquals(2, outer.size());
60+
61+
assertTrue(outer.get(0) instanceof ApexParser.CoalExpressionContext);
62+
List<ApexParser.ExpressionContext> inner = ((ApexParser.CoalExpressionContext) outer.get(0)).expression();
63+
assertEquals(2, inner.size());
64+
65+
assertTrue(inner.get(0) instanceof ApexParser.PrimaryExpressionContext);
66+
assertTrue(inner.get(1) instanceof ApexParser.Arth2ExpressionContext);
67+
68+
assertTrue(outer.get(1) instanceof ApexParser.PrimaryExpressionContext);
69+
}
70+
71+
@Test
72+
void testCoalescePrecedenceBoolean() {
73+
Map.Entry<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser("a ?? false || b ?? false");
74+
ApexParser.ExpressionContext context = parserAndCounter.getKey().expression();
75+
assertTrue(context instanceof ApexParser.CoalExpressionContext);
76+
77+
List<ApexParser.ExpressionContext> outer = ((ApexParser.CoalExpressionContext) context).expression();
78+
assertEquals(2, outer.size());
79+
80+
assertTrue(outer.get(0) instanceof ApexParser.CoalExpressionContext);
81+
List<ApexParser.ExpressionContext> inner = ((ApexParser.CoalExpressionContext) outer.get(0)).expression();
82+
assertEquals(2, inner.size());
83+
84+
assertTrue(inner.get(0) instanceof ApexParser.PrimaryExpressionContext);
85+
assertTrue(inner.get(1) instanceof ApexParser.LogOrExpressionContext);
86+
87+
assertTrue(outer.get(1) instanceof ApexParser.PrimaryExpressionContext);
88+
}
89+
4390
@Test
4491
void testClass() {
4592
Map.Entry<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser("public class Hello {}");

npm/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

npm/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@apexdevtools/apex-parser",
3-
"version": "3.5.0",
3+
"version": "3.6.0",
44
"author": "Apex Dev Tools Team <[email protected]> (https://github.com/apex-dev-tools)",
55
"bugs": "https://github.com/apex-dev-tools/apex-parser/issues",
66
"description": "Javascript parser for Salesforce Apex Language",

npm/src/__tests__/ApexParserTest.ts

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,16 @@
1212
derived from this software without specific prior written permission.
1313
*/
1414
import {
15-
LiteralContext, Arth1ExpressionContext, CompilationUnitContext,
16-
StatementContext, TriggerUnitContext, QueryContext
15+
LiteralContext,
16+
Arth1ExpressionContext,
17+
CompilationUnitContext,
18+
StatementContext,
19+
TriggerUnitContext,
20+
QueryContext,
21+
CoalExpressionContext,
22+
PrimaryExpressionContext,
23+
Arth2ExpressionContext,
24+
LogOrExpressionContext
1725
} from "../ApexParser";
1826
import { ThrowingErrorListener, SyntaxException } from "../ThrowingErrorListener";
1927
import { createParser } from "./SyntaxErrorCounter";
@@ -39,6 +47,59 @@ test('Expression', () => {
3947
expect(arthExpression.expression().length).toBe(2)
4048
})
4149

50+
test("Coalesce Expression", () => {
51+
const [parser, errorCounter] = createParser("a ?? 5");
52+
const context = parser.expression();
53+
54+
expect(errorCounter.getNumErrors()).toEqual(0);
55+
expect(context).toBeInstanceOf(CoalExpressionContext);
56+
const coalExpression = context as CoalExpressionContext;
57+
expect(coalExpression.expression().length).toBe(2);
58+
});
59+
60+
test("Coalesce Precedence - Arithmetic", () => {
61+
// Based on the example in release notes / docs
62+
// should NOT evaluate to (top ?? 100) - (bottom ?? 0) as you want
63+
//
64+
// left assoc = (top ?? (100 - bottom)) ?? 0
65+
// right assoc = top ?? ((100 - bottom) ?? 0)
66+
const [parser, errorCounter] = createParser("top ?? 100 - bottom ?? 0");
67+
const context = parser.expression();
68+
69+
expect(errorCounter.getNumErrors()).toEqual(0);
70+
expect(context).toBeInstanceOf(CoalExpressionContext);
71+
const outer = (context as CoalExpressionContext).expression();
72+
expect(outer.length).toBe(2);
73+
expect(outer[0]).toBeInstanceOf(CoalExpressionContext);
74+
75+
const inner = (outer[0] as CoalExpressionContext).expression(); // top ?? 100 - bottom
76+
expect(inner.length).toBe(2);
77+
expect(inner[0]).toBeInstanceOf(PrimaryExpressionContext); // top
78+
expect(inner[1]).toBeInstanceOf(Arth2ExpressionContext); // 100 - bottom
79+
80+
expect(outer[1]).toBeInstanceOf(PrimaryExpressionContext); // 0
81+
});
82+
83+
test("Coalesce Precedence - Boolean", () => {
84+
// This is more nonsense but using a much lower precedence op
85+
// should NOT evaluate to (a ?? false) || (b ?? false)
86+
const [parser, errorCounter] = createParser("a ?? false || b ?? false");
87+
const context = parser.expression();
88+
89+
expect(errorCounter.getNumErrors()).toEqual(0);
90+
expect(context).toBeInstanceOf(CoalExpressionContext);
91+
const outer = (context as CoalExpressionContext).expression();
92+
expect(outer.length).toBe(2);
93+
expect(outer[0]).toBeInstanceOf(CoalExpressionContext);
94+
95+
const inner = (outer[0] as CoalExpressionContext).expression(); // a ?? false || b
96+
expect(inner.length).toBe(2);
97+
expect(inner[0]).toBeInstanceOf(PrimaryExpressionContext); // a
98+
expect(inner[1]).toBeInstanceOf(LogOrExpressionContext); // false || b
99+
100+
expect(outer[1]).toBeInstanceOf(PrimaryExpressionContext); // false
101+
});
102+
42103
test('Compilation Unit', () => {
43104
const [parser, errorCounter] = createParser("public class Hello {}")
44105

0 commit comments

Comments
 (0)