Skip to content

Commit 1e6c2a6

Browse files
committed
Implement code blocks
1 parent 4509dc9 commit 1e6c2a6

File tree

5 files changed

+160
-22
lines changed

5 files changed

+160
-22
lines changed

src/main/antlr4/dev/vepo/jsonata/functions/generated/JSONataGrammar.g4

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ object: OBJ_OPEN fieldList OBJ_CLOSE #
88
expression:
99
functionStatement # functionCall
1010
| ROOT # rootPath
11+
| DOLLAR IDENTIFIER # variableUsage
1112
| IDENTIFIER # identifier
1213
| '*' # fieldValues
1314
| DESCEND # allDescendantSearch
@@ -26,6 +27,8 @@ expression:
2627
| expression '?' expression (':' expression)? # inlineIfExpression
2728
| expression '&' expression # concatValues
2829
| '(' expression ')' # contextValue
30+
| '(' expression ';' (expression ';')+ ')' # blockExpression
31+
| IDENTIFIER VAR_ASSIGN (expression|functionDeclaration) # variableAssignment
2932
| STRING # stringValue
3033
| NUMBER # numberValue
3134
| FLOAT # floatValue
@@ -51,6 +54,7 @@ ARR_OPEN: '[';
5154
ARR_CLOSE: ']';
5255
OBJ_OPEN: '{';
5356
OBJ_CLOSE: '}';
57+
VAR_ASSIGN : ':=' ;
5458
uniqueObj: (DOLLAR DOT)?;
5559

5660
IDENTIFIER: [\p{L}_$] [\p{L}0-9_$]*
@@ -74,4 +78,5 @@ fragment HEX : [0-9a-fA-F];
7478
DOT: '.';
7579

7680
// Just ignore WhiteSpaces
81+
COMMENT: '/*' .*? '*/' -> skip; // allow comments
7782
WS: [ \t\r\n]+ -> skip;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package dev.vepo.jsonata.functions;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
import java.util.Optional;
6+
7+
public class BockContext {
8+
private final Map<String, JSONataFunction> variables;
9+
private final Map<String, DeclaredFunction> functions;
10+
11+
public BockContext() {
12+
this.variables = new HashMap<>();
13+
this.functions = new HashMap<>();
14+
}
15+
16+
public void defineVariable(String identifier, JSONataFunction variableExpression) {
17+
variables.put(identifier, variableExpression);
18+
}
19+
20+
public void defineFunction(String identifier, DeclaredFunction fn) {
21+
functions.put(identifier, fn);
22+
}
23+
24+
public Optional<DeclaredFunction> function(String identifier) {
25+
return Optional.ofNullable(functions.get(identifier));
26+
}
27+
28+
public Optional<JSONataFunction> variable(String identifier) {
29+
return Optional.ofNullable(variables.get(identifier));
30+
}
31+
32+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package dev.vepo.jsonata.functions;
2+
3+
import java.util.List;
4+
5+
import dev.vepo.jsonata.functions.data.Data;
6+
7+
public record UserDefinedFunctionJSONataFunction(List<JSONataFunction> valueProviders, DeclaredFunction fn) implements JSONataFunction {
8+
9+
@Override
10+
public Data map(Data original, Data current) {
11+
return fn.accept(valueProviders.stream()
12+
.map(provider -> provider.map(original, current))
13+
.toArray(Data[]::new));
14+
}
15+
16+
}

src/main/java/dev/vepo/jsonata/parser/JSONataGrammarListener.java

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.List;
1313
import java.util.Objects;
1414
import java.util.Optional;
15+
import java.util.Queue;
1516
import java.util.stream.Stream;
1617

1718
import org.antlr.v4.runtime.tree.TerminalNode;
@@ -26,6 +27,7 @@
2627
import dev.vepo.jsonata.functions.ArrayIndexJSONataFunction;
2728
import dev.vepo.jsonata.functions.ArrayQueryJSONataFunction;
2829
import dev.vepo.jsonata.functions.ArrayRangeJSONataFunction;
30+
import dev.vepo.jsonata.functions.BockContext;
2931
import dev.vepo.jsonata.functions.BooleanExpressionJSONataFunction;
3032
import dev.vepo.jsonata.functions.BooleanOperator;
3133
import dev.vepo.jsonata.functions.BuiltInSortJSONataFunction;
@@ -43,13 +45,15 @@
4345
import dev.vepo.jsonata.functions.ObjectBuilderJSONataFunction;
4446
import dev.vepo.jsonata.functions.ObjectMapperJSONataFunction;
4547
import dev.vepo.jsonata.functions.StringConcatJSONataFunction;
48+
import dev.vepo.jsonata.functions.UserDefinedFunctionJSONataFunction;
4649
import dev.vepo.jsonata.functions.WildcardJSONataFunction;
4750
import dev.vepo.jsonata.functions.generated.JSONataGrammarBaseListener;
4851
import dev.vepo.jsonata.functions.generated.JSONataGrammarParser.AlgebraicExpressionContext;
4952
import dev.vepo.jsonata.functions.generated.JSONataGrammarParser.AllDescendantSearchContext;
5053
import dev.vepo.jsonata.functions.generated.JSONataGrammarParser.ArrayConstructorContext;
5154
import dev.vepo.jsonata.functions.generated.JSONataGrammarParser.ArrayIndexQueryContext;
5255
import dev.vepo.jsonata.functions.generated.JSONataGrammarParser.ArrayQueryContext;
56+
import dev.vepo.jsonata.functions.generated.JSONataGrammarParser.BlockExpressionContext;
5357
import dev.vepo.jsonata.functions.generated.JSONataGrammarParser.BooleanCompareContext;
5458
import dev.vepo.jsonata.functions.generated.JSONataGrammarParser.BooleanExpressionContext;
5559
import dev.vepo.jsonata.functions.generated.JSONataGrammarParser.BooleanValueContext;
@@ -73,19 +77,18 @@
7377
import dev.vepo.jsonata.functions.generated.JSONataGrammarParser.RootPathContext;
7478
import dev.vepo.jsonata.functions.generated.JSONataGrammarParser.StringValueContext;
7579
import dev.vepo.jsonata.functions.generated.JSONataGrammarParser.ToArrayContext;
80+
import dev.vepo.jsonata.functions.generated.JSONataGrammarParser.VariableAssignmentContext;
81+
import dev.vepo.jsonata.functions.generated.JSONataGrammarParser.VariableUsageContext;
7682

7783
public class JSONataGrammarListener extends JSONataGrammarBaseListener {
78-
private static final Logger logger = LoggerFactory.getLogger(JSONataGrammarListener.class);
79-
8084
public enum BuiltInFunction {
8185
SORT("$sort"),
8286
SUM("$sum");
8387

84-
public static BuiltInFunction get(String name) {
88+
public static Optional<BuiltInFunction> get(String name) {
8589
return Stream.of(values())
8690
.filter(n -> n.name.compareToIgnoreCase(name) == 0)
87-
.findAny()
88-
.orElseThrow(() -> new JSONataException(String.format("Unknown function!!! function=%s", name)));
91+
.findAny();
8992
}
9093

9194
private String name;
@@ -95,6 +98,8 @@ public static BuiltInFunction get(String name) {
9598
}
9699
}
97100

101+
private static final Logger logger = LoggerFactory.getLogger(JSONataGrammarListener.class);
102+
98103
private static String fieldName2Text(TerminalNode ctx) {
99104
if (!ctx.getText().startsWith("`")) {
100105
return ctx.getText();
@@ -113,12 +118,13 @@ private static String sanitise(String str) {
113118
}
114119

115120
private final Deque<JSONataFunction> expressions;
116-
117121
private final Deque<DeclaredFunction> functionsDeclared;
122+
private final Queue<BockContext> blocks;
118123

119124
public JSONataGrammarListener() {
120125
this.expressions = new LinkedList<>();
121126
this.functionsDeclared = new LinkedList<>();
127+
this.blocks = new LinkedList<>();
122128
}
123129

124130
@Override
@@ -131,15 +137,28 @@ public void exitFunctionDeclarationBuilder(FunctionDeclarationBuilderContext ctx
131137
this.expressions.removeLast()));
132138
}
133139

140+
private List<JSONataFunction> previous(int size) {
141+
var fns = new ArrayList<JSONataFunction>(size);
142+
for (int i = 0; i < size; ++i) {
143+
fns.addFirst(expressions.removeLast());
144+
}
145+
return fns;
146+
}
147+
134148
@Override
135149
public void exitFunctionCall(FunctionCallContext ctx) {
136150
logger.atInfo().setMessage("Function call! {}").addArgument(ctx::getText).log();
137-
var valueProvider = expressions.removeLast();
138151
Optional<DeclaredFunction> maybeFn = functionsDeclared.isEmpty() ? Optional.empty() : Optional.of(functionsDeclared.removeLast());
139-
expressions.offer(switch (BuiltInFunction.get(ctx.functionStatement().IDENTIFIER().getText())) {
140-
case SORT -> new BuiltInSortJSONataFunction(valueProvider, maybeFn);
141-
case SUM -> new BuiltInSumJSONataFunction(valueProvider);
142-
});
152+
var fnName = ctx.functionStatement().IDENTIFIER().getText();
153+
expressions.offer(BuiltInFunction.get(fnName)
154+
.map(fn -> switch (fn) {
155+
case SORT -> new BuiltInSortJSONataFunction(expressions.removeLast(), maybeFn);
156+
case SUM -> new BuiltInSumJSONataFunction(expressions.removeLast());
157+
})
158+
.orElseGet(() -> this.blocks.peek().function(fnName)
159+
.map(fn -> new UserDefinedFunctionJSONataFunction(previous(fn.parameterNames().size()),
160+
fn))
161+
.orElseThrow(() -> new JSONataException("Function not found: " + fnName))));
143162
}
144163

145164
@Override
@@ -151,7 +170,13 @@ public void exitRootPath(RootPathContext ctx) {
151170
@Override
152171
public void exitIdentifier(IdentifierContext ctx) {
153172
logger.atInfo().setMessage("Identifier! {}").addArgument(ctx::getText).log();
154-
expressions.offer(new FieldMapJSONataFunction(fieldName2Text(ctx.IDENTIFIER())));
173+
if (blocks.isEmpty()) {
174+
expressions.offer(new FieldMapJSONataFunction(fieldName2Text(ctx.IDENTIFIER())));
175+
} else {
176+
expressions.offer(Objects.requireNonNull(this.blocks.peek(), "Variable should only be defined in blocks!")
177+
.variable(ctx.IDENTIFIER().getText())
178+
.orElseGet(() -> new FieldMapJSONataFunction(fieldName2Text(ctx.IDENTIFIER()))));
179+
}
155180
}
156181

157182
@Override
@@ -330,6 +355,42 @@ public void exitObjectConstructor(ObjectConstructorContext ctx) {
330355
expressions.offer(new JoinJSONataFunction(previousFunction, new ObjectBuilderJSONataFunction(fieldList)));
331356
}
332357

358+
@Override
359+
public void exitObjectBuilder(ObjectBuilderContext ctx) {
360+
logger.atInfo().setMessage("Object builder! {}").addArgument(ctx::getText).log();
361+
expressions.offer(new ObjectBuilderJSONataFunction(objectFields(ctx.fieldList())));
362+
}
363+
364+
@Override
365+
public void enterBlockExpression(BlockExpressionContext ctx) {
366+
logger.atInfo().setMessage("Block expression! {}").addArgument(ctx::getText).log();
367+
this.blocks.offer(new BockContext());
368+
}
369+
370+
@Override
371+
public void exitVariableUsage(VariableUsageContext ctx) {
372+
logger.atInfo().setMessage("Variable usage! {}").addArgument(ctx::getText).log();
373+
expressions.offer(Objects.requireNonNull(this.blocks.peek(), "Variable should only be defined in blocks!")
374+
.variable(ctx.IDENTIFIER().getText())
375+
.orElseThrow(() -> new JSONataException("Variable not found: " + ctx.IDENTIFIER().getText())));
376+
}
377+
378+
@Override
379+
public void exitVariableAssignment(VariableAssignmentContext ctx) {
380+
logger.atInfo().setMessage("Variable assignment! {}").addArgument(ctx::getText).log();
381+
if (Objects.nonNull(ctx.expression())) {
382+
Objects.requireNonNull(this.blocks.peek(), "Variable should only be defined in blocks!")
383+
.defineVariable(ctx.IDENTIFIER().getText(), expressions.removeLast());
384+
} else {
385+
Objects.requireNonNull(this.blocks.peek(), "Variable should only be defined in blocks!")
386+
.defineFunction(ctx.IDENTIFIER().getText(), functionsDeclared.removeLast());
387+
}
388+
}
389+
390+
public List<JSONataFunction> getExpressions() {
391+
return expressions.stream().toList();
392+
}
393+
333394
private List<FieldContent> objectFields(FieldListContext ctx) {
334395
var expresisonCounter = ctx.expression().size();
335396
var fieldBuilder = new ArrayList<FieldContent>(expresisonCounter);
@@ -340,14 +401,4 @@ private List<FieldContent> objectFields(FieldListContext ctx) {
340401
}
341402
return fieldBuilder;
342403
}
343-
344-
@Override
345-
public void exitObjectBuilder(ObjectBuilderContext ctx) {
346-
logger.atInfo().setMessage("Object builder! {}").addArgument(ctx::getText).log();
347-
expressions.offer(new ObjectBuilderJSONataFunction(objectFields(ctx.fieldList())));
348-
}
349-
350-
public List<JSONataFunction> getExpressions() {
351-
return expressions.stream().toList();
352-
}
353404
}

src/test/java/dev/vepo/jsonata/JSONataTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,40 @@ void expressionTest() {
403403

404404
@Nested
405405
class Programming {
406+
@Test
407+
void commentsDefitionTest() {
408+
assertThat(jsonata("""
409+
/* This is a comment */
410+
5 + 5
411+
""").evaluate("{}").asInt()).isEqualTo(10);
412+
assertThat(jsonata("""
413+
/* This is a comment
414+
* with multiple lines
415+
*/
416+
5 + 5
417+
""").evaluate("{}").asInt()).isEqualTo(10);
418+
}
419+
420+
// @Disabled
421+
@Test
422+
void variableDefinitionTest() {
423+
assertThat(jsonata("""
424+
(
425+
$volume := function($l, $w, $h){ $l * $w * $h };
426+
$volume(10, 10, 5);
427+
)
428+
""").evaluate("{}").asInt()).isEqualTo(500);
429+
assertThat(jsonata("""
430+
(
431+
$volume := function($l, $w, $h){ $l * $w * $h };
432+
$v1 := 10;
433+
$x2 := 5;
434+
$abcdefghijlmnopqrstuvxz := 10000;
435+
$volume($v1, $x2, $abcdefghijlmnopqrstuvxz);
436+
)
437+
""").evaluate("{}").asInt()).isEqualTo(500_000);
438+
}
439+
406440
@Test
407441
void conditionalInlineTest() {
408442
assertThat(jsonata("""

0 commit comments

Comments
 (0)