diff --git a/parser/src/main/antlr4/imports/Common.g4 b/parser/src/main/antlr4/imports/Common.g4 index fbcbd0d..8844343 100644 --- a/parser/src/main/antlr4/imports/Common.g4 +++ b/parser/src/main/antlr4/imports/Common.g4 @@ -19,7 +19,7 @@ configvalue : (IDENTIFIER|VALUE) ; namedTypeDeclaration : typeName typeDeclaration ; typeName : IDENTIFIER ; typeDeclaration : '(' typeField (',' typeField)* ')' ; -typeField : fieldName ':' fieldType ; +typeField : fieldName COLON fieldType ; fieldName : IDENTIFIER ; fieldType : IDENTIFIER ; @@ -33,8 +33,9 @@ fragment DIGIT : [0-9] ; WS : [ \t\n\r]+ -> skip; COMMENT : COMMENT_BEGIN .*? COMMENT_END -> skip; -IDENTIFIER : (LOWERCASE | UPPERCASE) (LOWERCASE | UPPERCASE | DIGIT | GENERICS | DOT)* ; -VALUE : ~[ ,{}:()/="#';*\n\r\t]+ ; LEFT_ARROW : '<-' ; RIGHT_ARROW : '->' ; -SLASH : '/' ; \ No newline at end of file +IDENTIFIER : (LOWERCASE | UPPERCASE) (LOWERCASE | UPPERCASE | DIGIT | GENERICS | DOT)* ; +VALUE : ~[ ,{}:()/="#';*\n\r\t]+ ; +SLASH : '/' ; +COLON : ':' ; \ No newline at end of file diff --git a/parser/src/main/antlr4/nu/zoom/dsl/parser/Endpoints.g4 b/parser/src/main/antlr4/nu/zoom/dsl/parser/Endpoints.g4 index f15a207..1a74607 100644 --- a/parser/src/main/antlr4/nu/zoom/dsl/parser/Endpoints.g4 +++ b/parser/src/main/antlr4/nu/zoom/dsl/parser/Endpoints.g4 @@ -20,4 +20,3 @@ responseBody : RIGHT_ARROW (namedTypeDeclaration | typeDeclaration | endpoint : path requestBody responseBody?; path : (pathSegment)+ ; pathSegment : SLASH (IDENTIFIER|VALUE) ; - diff --git a/parser/src/main/antlr4/nu/zoom/dsl/parser/States.g4 b/parser/src/main/antlr4/nu/zoom/dsl/parser/States.g4 new file mode 100644 index 0000000..dbfe628 --- /dev/null +++ b/parser/src/main/antlr4/nu/zoom/dsl/parser/States.g4 @@ -0,0 +1,22 @@ +// Copyright 2025 "Johan Maasing" +// +// Licensed 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. +grammar States; +import Common; + +document : generatorconfig? transition (',' transition)* ; +transition : from RIGHT_ARROW to COLON message ; +from : state ; +to : state ; +message : typeName typeDeclaration? ; +state : typeName typeDeclaration? ; \ No newline at end of file diff --git a/parser/src/main/java/nu/zoom/dsl/ast/DocumentNode.java b/parser/src/main/java/nu/zoom/dsl/ast/DocumentNode.java index 24ac244..fcb8621 100644 --- a/parser/src/main/java/nu/zoom/dsl/ast/DocumentNode.java +++ b/parser/src/main/java/nu/zoom/dsl/ast/DocumentNode.java @@ -15,9 +15,11 @@ package nu.zoom.dsl.ast; import java.util.List; import java.util.Map; +import java.util.Set; public record DocumentNode( Map config, - List typeDefinitions, - List endpoints) { + Set typeDefinitions, + List endpoints, + Set states) { } diff --git a/parser/src/main/java/nu/zoom/dsl/ast/EndpointsVisitorTransformer.java b/parser/src/main/java/nu/zoom/dsl/ast/EndpointsVisitorTransformer.java index 5d846d6..e530a80 100644 --- a/parser/src/main/java/nu/zoom/dsl/ast/EndpointsVisitorTransformer.java +++ b/parser/src/main/java/nu/zoom/dsl/ast/EndpointsVisitorTransformer.java @@ -19,7 +19,8 @@ import org.antlr.v4.runtime.tree.TerminalNode; import java.util.*; -public class EndpointsVisitorTransformer extends EndpointsBaseVisitor { +public class EndpointsVisitorTransformer + extends EndpointsBaseVisitor { private final ArrayList endpoints = new ArrayList<>(); private final HashMap config = new HashMap<>(); private final HashSet dataTypes = new HashSet<>(); @@ -35,8 +36,8 @@ public class EndpointsVisitorTransformer extends EndpointsBaseVisitor getDataTypes() { - return List.copyOf(dataTypes); + public Set getDataTypes() { + return Set.copyOf(dataTypes); } @Override @@ -130,7 +131,7 @@ public class EndpointsVisitorTransformer extends EndpointsBaseVisitor getEndpoints(); + + Map getConfig(); + + List getDataTypes(); + +} diff --git a/parser/src/main/java/nu/zoom/dsl/ast/ParserWrapper.java b/parser/src/main/java/nu/zoom/dsl/ast/ParserWrapper.java index 2252cac..11a95fd 100644 --- a/parser/src/main/java/nu/zoom/dsl/ast/ParserWrapper.java +++ b/parser/src/main/java/nu/zoom/dsl/ast/ParserWrapper.java @@ -13,23 +13,43 @@ // limitations under the License. package nu.zoom.dsl.ast; -import nu.zoom.dsl.parser.EndpointsLexer; -import nu.zoom.dsl.parser.EndpointsParser; +import nu.zoom.dsl.parser.*; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.util.List; +import java.util.Set; public class ParserWrapper { - public static DocumentNode parse(Path sourcePath) throws IOException { + public static DocumentNode parseEndpoints(Path sourcePath) throws IOException { var ins = CharStreams.fromPath(sourcePath, StandardCharsets.UTF_8); EndpointsLexer lexer = new EndpointsLexer(ins); EndpointsParser parser = new EndpointsParser(new CommonTokenStream(lexer)); var document = parser.document(); var astTransformer = new EndpointsVisitorTransformer(); astTransformer.visit(document); - return new DocumentNode(astTransformer.getConfig(), astTransformer.getDataTypes(), astTransformer.getEndpoints()); + return new DocumentNode( + astTransformer.getConfig(), + astTransformer.getDataTypes(), + astTransformer.getEndpoints(), + Set.of() + ); + } + public static DocumentNode parseStates(Path sourcePath) throws IOException { + var ins = CharStreams.fromPath(sourcePath, StandardCharsets.UTF_8); + StatesLexer lexer = new StatesLexer(ins); + StatesParser parser = new StatesParser(new CommonTokenStream(lexer)); + var document = parser.document(); + var astTransformer = new StatesVisitorTransformer(); + astTransformer.visit(document); + return new DocumentNode( + astTransformer.getConfig(), + astTransformer.getTypes(), + List.of(), + astTransformer.getStates() + ); } } diff --git a/parser/src/main/java/nu/zoom/dsl/ast/StateNode.java b/parser/src/main/java/nu/zoom/dsl/ast/StateNode.java new file mode 100644 index 0000000..5f8b7ff --- /dev/null +++ b/parser/src/main/java/nu/zoom/dsl/ast/StateNode.java @@ -0,0 +1,6 @@ +package nu.zoom.dsl.ast; + +import java.util.Set; + +public record StateNode(String name, String data, Set transitions) { +} diff --git a/parser/src/main/java/nu/zoom/dsl/ast/StatesVisitorTransformer.java b/parser/src/main/java/nu/zoom/dsl/ast/StatesVisitorTransformer.java new file mode 100644 index 0000000..4fd3c97 --- /dev/null +++ b/parser/src/main/java/nu/zoom/dsl/ast/StatesVisitorTransformer.java @@ -0,0 +1,56 @@ +package nu.zoom.dsl.ast; + +import nu.zoom.dsl.parser.StatesBaseVisitor; +import nu.zoom.dsl.parser.StatesParser; + +import java.util.*; + +public class StatesVisitorTransformer extends StatesBaseVisitor { + private final HashMap config = new HashMap<>(); + private final HashSet nodeTypes = new HashSet<>(); + private final HashSet messageTypes = new HashSet<>(); + // from -> + private final HashMap> transitions = new HashMap<>(); + + @Override + public StatesParser.DocumentContext visitTransition(StatesParser.TransitionContext ctx) { + String from = ctx.from().state().typeName().IDENTIFIER().getText() ; + String to = ctx.to().state().typeName().IDENTIFIER().getText() ; + String message = ctx.message().typeName().IDENTIFIER().getText() ; + this.transitions.computeIfAbsent(from, k -> new HashMap<>()).put(to, message); + return super.visitTransition(ctx); + } + + @Override + public StatesParser.DocumentContext visitState(StatesParser.StateContext ctx) { + String stateName = ctx.typeName().IDENTIFIER().getText() ; + List fields = extractFields(ctx.typeDeclaration()) ; + this.nodeTypes.add(new TypeNode(stateName, fields)); + return super.visitState(ctx); + } + + public Set getStates() { + // TODO: Calculate state nodes from this.transitions + return Set.of(); + } + + public Map getConfig() { + return Map.copyOf(config); + } + + public Set getTypes() { + // TODO calculate data types from NodeTypes and MessageTypes with duplicate check. + return Set.of() ; + } + + private List extractFields(StatesParser.TypeDeclarationContext declaration) { + return declaration + .typeField() + .stream() + .map( + ctx -> + new FieldNode(ctx.fieldName().getText(), ctx.fieldType().getText()) + ) + .toList(); + } +} diff --git a/parser/src/main/java/nu/zoom/dsl/ast/TransitionNode.java b/parser/src/main/java/nu/zoom/dsl/ast/TransitionNode.java new file mode 100644 index 0000000..90ebd3e --- /dev/null +++ b/parser/src/main/java/nu/zoom/dsl/ast/TransitionNode.java @@ -0,0 +1,4 @@ +package nu.zoom.dsl.ast; + +public record TransitionNode(String message, String toState) { +} diff --git a/parser/src/main/java/nu/zoom/dsl/cli/EndpointsCLI.java b/parser/src/main/java/nu/zoom/dsl/cli/EndpointsCLI.java index b26cb9c..e5c8eda 100644 --- a/parser/src/main/java/nu/zoom/dsl/cli/EndpointsCLI.java +++ b/parser/src/main/java/nu/zoom/dsl/cli/EndpointsCLI.java @@ -26,6 +26,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.Optional; import java.util.concurrent.Callable; @Command( @@ -34,6 +35,10 @@ import java.util.concurrent.Callable; description = "Generate source code from an endpoints specification file." ) public class EndpointsCLI implements Callable { + public static enum ParserType { + Endpoints, + States + } @SuppressWarnings("unused") @Parameters(index = "0", description = "The source endpoints DSL file.") private Path file; @@ -50,6 +55,9 @@ public class EndpointsCLI implements Callable { @Option(names = {"-v", "--verbose"}, description = "Print verbose debug messages.") private Boolean verbose = false; + @Option(names = {"-p", "--parser"}, description = "Force use of a specific parser instead of determining from filename. Valid values: ${COMPLETION-CANDIDATES}.") + private ParserType parser = null; + public static void main(String[] args) { int exitCode = new CommandLine(new EndpointsCLI()).execute(args); System.exit(exitCode); @@ -61,8 +69,20 @@ public class EndpointsCLI implements Callable { validateTemplateDirectory(); validateInputFile(); validateOutputDirectory(); - verbose("Parsing " + file.toAbsolutePath()); - DocumentNode rootNode = ParserWrapper.parse(file); + verbose("Parsing: " + file.toAbsolutePath()); + if (parser == null) { + if (file.getFileName().endsWith(".states")) { + parser = ParserType.States; + } + } + final DocumentNode rootNode ; + if (parser == ParserType.States) { + verbose("using state grammar.") ; + rootNode = ParserWrapper.parseStates(file); + } else { + verbose("using endpoints grammar.") ; + rootNode = ParserWrapper.parseEndpoints(file); + } verbose("AST: " + rootNode); verbose("Generating from templates in: " + templateDir.toAbsolutePath()); Generator generator = new Generator(templateDir, rootNode, outputDir);