WIP visitor transformer

This commit is contained in:
Johan Maasing 2025-03-26 21:35:59 +01:00
parent cfce3e4dbb
commit d4b6714229
Signed by: johan
GPG key ID: FFD31BABEE2DEED2
15 changed files with 200 additions and 20 deletions

View file

@ -17,7 +17,7 @@
<dependency> <dependency>
<groupId>nu.zoom.dsl</groupId> <groupId>nu.zoom.dsl</groupId>
<artifactId>parser</artifactId> <artifactId>parser</artifactId>
<version>${parent.version}</version> <version>${project.parent.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>

View file

@ -18,6 +18,11 @@
<artifactId>antlr4-runtime</artifactId> <artifactId>antlr4-runtime</artifactId>
<version>4.13.0</version> <version>4.13.0</version>
</dependency> </dependency>
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
<version>4.7.6</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
@ -42,6 +47,9 @@
<groupId>org.antlr</groupId> <groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId> <artifactId>antlr4-maven-plugin</artifactId>
<version>4.13.1</version> <version>4.13.1</version>
<configuration>
<visitor>true</visitor>
</configuration>
<executions> <executions>
<execution> <execution>
<goals> <goals>

View file

@ -1,23 +1,24 @@
grammar Endpoints; grammar Endpoints;
document : generatorconfig? (compoundType|endpoint)* ;
generatorconfig : '{' (configitem)? (',' configitem)* '}'; generatorconfig : '{' (configitem)? (',' configitem)* '}';
configitem : configkey ':' configvalue ; configitem : configkey ':' configvalue ;
configkey : IDENTIFIER ; configkey : IDENTIFIER ;
configvalue : (IDENTIFIER|VALUE) ; configvalue : (IDENTIFIER|VALUE) ;
compoundType : compoundTypeName compoundFields ;
compoundTypeName : IDENTIFIER ;
compoundFields : '(' compoundField (',' compoundField)* ')' ;
compoundField : fieldName ':' fieldType ;
fieldName : IDENTIFIER ; fieldName : IDENTIFIER ;
fieldType : IDENTIFIER ; fieldType : IDENTIFIER ;
compoundTypeName : IDENTIFIER ; endpoint : path '<' (compoundType | IDENTIFIER) ;
compoundField : fieldName ':' fieldType ;
compoundFields : '(' compoundField (',' compoundField)* ')' ;
compoundType : compoundTypeName compoundFields ;
pathSegment : '/' (IDENTIFIER|VALUE) ;
path : (pathSegment)+ ; path : (pathSegment)+ ;
endpoint : path '<' (compoundFields | IDENTIFIER) ; pathSegment : '/' (IDENTIFIER|VALUE) ;
fragment DIGIT : [0-9] ; fragment DIGIT : [0-9] ;
fragment LOWERCASE : [a-z] ; fragment LOWERCASE : [a-z] ;
fragment UPPERCASE : [A-Z] ; fragment UPPERCASE : [A-Z] ;
COMMENT : '/*' (IDENTIFIER|VALUE)* '*/' -> skip ;
WS : [ \t\n\r]+ -> skip; WS : [ \t\n\r]+ -> skip;
IDENTIFIER : (LOWERCASE | UPPERCASE) (LOWERCASE | UPPERCASE | DIGIT)* ; IDENTIFIER : (LOWERCASE | UPPERCASE) (LOWERCASE | UPPERCASE | DIGIT)* ;
VALUE : ~[ ,{}:()\n\t\r/<>"']+ ; VALUE : ~[ ,{}:()\n\t\r/<>"#';*]+ ;

View file

@ -1,7 +0,0 @@
package nu.zoom.dsl;
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}

View file

@ -0,0 +1,6 @@
package nu.zoom.dsl.ast;
import java.util.List;
public record CompoundTypeNode(String name, List<FieldNode> fields) {
}

View file

@ -0,0 +1,4 @@
package nu.zoom.dsl.ast;
public record ConfigItemNode(String key, String value) {
}

View file

@ -0,0 +1,9 @@
package nu.zoom.dsl.ast;
import java.util.List;
public record DocumentNode(
List<ConfigItemNode> configItems,
List<CompoundTypeNode> typeDefinitions,
List<EndpointNode> endpoints) {
}

View file

@ -0,0 +1,8 @@
package nu.zoom.dsl.ast;
import java.util.Optional;
public record EndpointNode(
PathsNode paths,
String inputType) {
}

View file

@ -0,0 +1,38 @@
package nu.zoom.dsl.ast;
import nu.zoom.dsl.parser.EndpointsBaseVisitor;
import nu.zoom.dsl.parser.EndpointsParser;
import java.util.ArrayList;
public class EndpointsVisitorTransformer extends EndpointsBaseVisitor<EndpointsParser.DocumentContext> {
private ArrayList<EndpointNode> endpoints = new ArrayList<>();
private ArrayList<ConfigItemNode> config = new ArrayList<>();
private ArrayList<CompoundTypeNode> dataTypes = new ArrayList<>();
public EndpointsVisitorTransformer() {
}
@Override
public EndpointsParser.DocumentContext visitConfigitem(EndpointsParser.ConfigitemContext ctx) {
String configKey = ctx.configkey().IDENTIFIER().getText() ;
String configValue = getText(ctx.configvalue()) ;
this.config.add(new ConfigItemNode(configKey, configValue));
return super.visitConfigitem(ctx) ;
}
@Override
public EndpointsParser.DocumentContext visitCompoundType(EndpointsParser.CompoundTypeContext ctx) {
return super.visitCompoundType(ctx);
}
@Override
public EndpointsParser.DocumentContext visitEndpoint(EndpointsParser.EndpointContext ctx) {
return super.visitEndpoint(ctx);
}
private String getText(EndpointsParser.ConfigvalueContext ctx) {
String identifierText = (ctx.IDENTIFIER() != null) ? ctx.IDENTIFIER().getText() : "" ;
String valueText = (ctx.VALUE() != null) ? ctx.VALUE().getText() : "" ;
return identifierText + valueText;
}
}

View file

@ -0,0 +1,4 @@
package nu.zoom.dsl.ast;
public record FieldNode(String name, String type) {
}

View file

@ -0,0 +1,22 @@
package nu.zoom.dsl.ast;
import nu.zoom.dsl.parser.EndpointsLexer;
import nu.zoom.dsl.parser.EndpointsParser;
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.Files;
import java.nio.file.Path;
public class ParserWrapper {
public DocumentNode parse(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() ;
new EndpointsVisitorTransformer().visit(document);
return null ;
}
}

View file

@ -0,0 +1,6 @@
package nu.zoom.dsl.ast;
import java.util.List;
public record PathsNode(List<String> paths) {
}

View file

@ -0,0 +1,75 @@
package nu.zoom.dsl.cli;
import nu.zoom.dsl.ast.ParserWrapper;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.Callable;
@Command(
name = "EndpointsCLI",
mixinStandardHelpOptions = true,
description = "Generate source code from an endpoints specification file."
)
public class EndpointsCLI implements Callable<Integer> {
@Parameters(index = "0", description = "The source endpoints DSL file.")
private Path file;
@Option(names = {"-t", "--template"}, description = "The template directory. Default is ~/endpoints-templates")
private Path templateDir = Paths.get(System.getProperty("user.dir"), "endpoints-templates");
@Option(names = {"-o", "--output"}, description = "The directory to write the generated code to. Default is ~/endpoints-output")
private Path outputDir = Paths.get(System.getProperty("user.dir"), "endpoints-output");
@Option(names = {"-v", "--verbose"}, description = "Print verbose debug messages.")
private Boolean verbose = false;
public static void main(String[] args) {
int exitCode = new CommandLine(new EndpointsCLI()).execute(args);
System.exit(exitCode);
}
@Override
public Integer call() {
try {
validateTemplateDirectory();
validateInputFile();
validateOutputDirectory();
new ParserWrapper().parse(file);
return 0;
} catch (Exception e) {
System.err.println(e.getMessage());
return 1;
}
}
private void validateOutputDirectory() throws IOException {
if (Files.notExists(this.outputDir)) {
Files.createDirectories(this.outputDir);
}
if (!Files.isDirectory(this.outputDir)) {
throw new IllegalArgumentException("Output directory: '" + this.outputDir + " 'is not a directory.");
}
}
private void validateTemplateDirectory() throws IOException {
if (!Files.isDirectory(this.templateDir)) {
throw new IllegalArgumentException("Template directory '" + this.templateDir + "' is not a directory.");
}
}
private void validateInputFile() throws IOException {
if (Files.notExists(this.file)) {
throw new IllegalArgumentException("Input file '" + this.file + "' does not exist.");
}
if (!Files.isReadable(this.file) || !Files.isRegularFile(this.file)) {
throw new IllegalArgumentException("Input file '" + this.file + "' is not a readable file.");
}
}
}

View file

@ -1,4 +0,0 @@
package nu.zoom.dsl.parser;
public class ParserFactory {
}

10
test01.endpoints Normal file
View file

@ -0,0 +1,10 @@
{
config:foo
}
SomeType(
foo:bar
)
/some/endpoint < SomeType
/some/other/endpoint < SomeOtherType(bar:String)