Skip to content

Commit

Permalink
Merge pull request #38 from apex-dev-tools/null-coalesce
Browse files Browse the repository at this point in the history
Null coalesce
  • Loading branch information
pwrightcertinia authored Feb 27, 2024
2 parents f432ba7 + c6872c4 commit 19ccabe
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# apex-parser - Changelog

## 3.6.0 - 2024-02-15

- Add null coalesce operator and expression

## 3.5.0 - 2023-10-15

- Correct do-while to require block rather than statement
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ Maven
<dependency>
<groupId>io.github.apex-dev-tools</groupId>
<artifactId>apex-parser</artifactId>
<version>3.5.0</version>
<version>3.6.0</version>
</dependency>

NPM

"@apexdevtools/apex-parser": "^3.5.0"
"@apexdevtools/apex-parser": "^3.6.0"

## Building

Expand Down
1 change: 1 addition & 0 deletions antlr/ApexLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ LESSANDGREATER : '<>';
TRIPLENOTEQUAL : '!==';
AND : '&&';
OR : '||';
COAL : '??';
INC : '++';
DEC : '--';
ADD : '+';
Expand Down
1 change: 1 addition & 0 deletions antlr/ApexParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ expression
| expression BITOR expression # bitOrExpression
| expression AND expression # logAndExpression
| expression OR expression # logOrExpression
| expression COAL expression # coalExpression
| <assoc=right> expression QUESTION expression COLON expression # condExpression
| <assoc=right> expression
( ASSIGN
Expand Down
2 changes: 1 addition & 1 deletion jvm/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>io.github.apex-dev-tools</groupId>
<artifactId>apex-parser</artifactId>
<version>3.5.0</version>
<version>3.6.0</version>
<packaging>jar</packaging>

<name>apex-parser</name>
Expand Down
47 changes: 47 additions & 0 deletions jvm/src/test/java/com/nawforce/apexparser/ApexParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Map;

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

@Test
void testCoalesceExpression() {
Map.Entry<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser("a ?? 5");
ApexParser.ExpressionContext context = parserAndCounter.getKey().expression();
assertTrue(context instanceof ApexParser.CoalExpressionContext);
assertEquals(2, ((ApexParser.CoalExpressionContext) context).expression().size());
}

@Test
void testCoalescePrecedence() {
Map.Entry<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser("top ?? 100 - bottom ?? 0");
ApexParser.ExpressionContext context = parserAndCounter.getKey().expression();
assertTrue(context instanceof ApexParser.CoalExpressionContext);

List<ApexParser.ExpressionContext> outer = ((ApexParser.CoalExpressionContext) context).expression();
assertEquals(2, outer.size());

assertTrue(outer.get(0) instanceof ApexParser.CoalExpressionContext);
List<ApexParser.ExpressionContext> inner = ((ApexParser.CoalExpressionContext) outer.get(0)).expression();
assertEquals(2, inner.size());

assertTrue(inner.get(0) instanceof ApexParser.PrimaryExpressionContext);
assertTrue(inner.get(1) instanceof ApexParser.Arth2ExpressionContext);

assertTrue(outer.get(1) instanceof ApexParser.PrimaryExpressionContext);
}

@Test
void testCoalescePrecedenceBoolean() {
Map.Entry<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser("a ?? false || b ?? false");
ApexParser.ExpressionContext context = parserAndCounter.getKey().expression();
assertTrue(context instanceof ApexParser.CoalExpressionContext);

List<ApexParser.ExpressionContext> outer = ((ApexParser.CoalExpressionContext) context).expression();
assertEquals(2, outer.size());

assertTrue(outer.get(0) instanceof ApexParser.CoalExpressionContext);
List<ApexParser.ExpressionContext> inner = ((ApexParser.CoalExpressionContext) outer.get(0)).expression();
assertEquals(2, inner.size());

assertTrue(inner.get(0) instanceof ApexParser.PrimaryExpressionContext);
assertTrue(inner.get(1) instanceof ApexParser.LogOrExpressionContext);

assertTrue(outer.get(1) instanceof ApexParser.PrimaryExpressionContext);
}

@Test
void testClass() {
Map.Entry<ApexParser, SyntaxErrorCounter> parserAndCounter = createParser("public class Hello {}");
Expand Down
4 changes: 2 additions & 2 deletions npm/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion npm/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@apexdevtools/apex-parser",
"version": "3.5.0",
"version": "3.6.0",
"author": "Apex Dev Tools Team <[email protected]> (https://github.com/apex-dev-tools)",
"bugs": "https://github.com/apex-dev-tools/apex-parser/issues",
"description": "Javascript parser for Salesforce Apex Language",
Expand Down
65 changes: 63 additions & 2 deletions npm/src/__tests__/ApexParserTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,16 @@
derived from this software without specific prior written permission.
*/
import {
LiteralContext, Arth1ExpressionContext, CompilationUnitContext,
StatementContext, TriggerUnitContext, QueryContext
LiteralContext,
Arth1ExpressionContext,
CompilationUnitContext,
StatementContext,
TriggerUnitContext,
QueryContext,
CoalExpressionContext,
PrimaryExpressionContext,
Arth2ExpressionContext,
LogOrExpressionContext
} from "../ApexParser";
import { ThrowingErrorListener, SyntaxException } from "../ThrowingErrorListener";
import { createParser } from "./SyntaxErrorCounter";
Expand All @@ -39,6 +47,59 @@ test('Expression', () => {
expect(arthExpression.expression().length).toBe(2)
})

test("Coalesce Expression", () => {
const [parser, errorCounter] = createParser("a ?? 5");
const context = parser.expression();

expect(errorCounter.getNumErrors()).toEqual(0);
expect(context).toBeInstanceOf(CoalExpressionContext);
const coalExpression = context as CoalExpressionContext;
expect(coalExpression.expression().length).toBe(2);
});

test("Coalesce Precedence - Arithmetic", () => {
// Based on the example in release notes / docs
// should NOT evaluate to (top ?? 100) - (bottom ?? 0) as you want
//
// left assoc = (top ?? (100 - bottom)) ?? 0
// right assoc = top ?? ((100 - bottom) ?? 0)
const [parser, errorCounter] = createParser("top ?? 100 - bottom ?? 0");
const context = parser.expression();

expect(errorCounter.getNumErrors()).toEqual(0);
expect(context).toBeInstanceOf(CoalExpressionContext);
const outer = (context as CoalExpressionContext).expression();
expect(outer.length).toBe(2);
expect(outer[0]).toBeInstanceOf(CoalExpressionContext);

const inner = (outer[0] as CoalExpressionContext).expression(); // top ?? 100 - bottom
expect(inner.length).toBe(2);
expect(inner[0]).toBeInstanceOf(PrimaryExpressionContext); // top
expect(inner[1]).toBeInstanceOf(Arth2ExpressionContext); // 100 - bottom

expect(outer[1]).toBeInstanceOf(PrimaryExpressionContext); // 0
});

test("Coalesce Precedence - Boolean", () => {
// This is more nonsense but using a much lower precedence op
// should NOT evaluate to (a ?? false) || (b ?? false)
const [parser, errorCounter] = createParser("a ?? false || b ?? false");
const context = parser.expression();

expect(errorCounter.getNumErrors()).toEqual(0);
expect(context).toBeInstanceOf(CoalExpressionContext);
const outer = (context as CoalExpressionContext).expression();
expect(outer.length).toBe(2);
expect(outer[0]).toBeInstanceOf(CoalExpressionContext);

const inner = (outer[0] as CoalExpressionContext).expression(); // a ?? false || b
expect(inner.length).toBe(2);
expect(inner[0]).toBeInstanceOf(PrimaryExpressionContext); // a
expect(inner[1]).toBeInstanceOf(LogOrExpressionContext); // false || b

expect(outer[1]).toBeInstanceOf(PrimaryExpressionContext); // false
});

test('Compilation Unit', () => {
const [parser, errorCounter] = createParser("public class Hello {}")

Expand Down

0 comments on commit 19ccabe

Please sign in to comment.