Added types declarations

This commit is contained in:
Johan Maasing 2025-03-18 21:27:26 +01:00
parent 620999a992
commit 857f9c63a6
Signed by: johan
GPG key ID: FFD31BABEE2DEED2
6 changed files with 165 additions and 53 deletions

View file

@ -32,6 +32,7 @@
--> -->
<execution> <execution>
<id>jjtree-javacc</id> <id>jjtree-javacc</id>
<phase>generate-sources</phase>
<goals> <goals>
<goal>jjtree-javacc</goal> <goal>jjtree-javacc</goal>
</goals> </goals>

View file

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

View file

@ -39,19 +39,25 @@ public class Generator implements Callable<Integer> {
validateTemplateDirectory(); validateTemplateDirectory();
validateInputFile(); validateInputFile();
validateOutputDirectory(); validateOutputDirectory();
var rootNode = new TapirParser(Files.newBufferedReader(this.file)).endpoints(); var rootNode = new TapirParser(Files.newBufferedReader(this.file)).specification();
if (this.verbose) { if (this.verbose) {
System.out.println("====== Parse Tree ======"); System.out.println("====== Parse Tree ======");
rootNode.dump(""); rootNode.dump("");
} }
var endpoints = NodeTransformer.transform(rootNode); NodeTransformer transformer = new NodeTransformer();
if (endpoints.isEmpty()) { transformer.transform(rootNode);
if (transformer.getEndpoints().isEmpty()) {
System.err.println("No tapir endpoints found."); System.err.println("No tapir endpoints found.");
return 2; return 2;
} }
if (this.verbose) { if (this.verbose) {
System.out.println("\n====== AST ======"); System.out.println("\n====== AST ======");
endpoints.forEach(endpoint -> { System.out.println("\n====== Types ======");
transformer.getDataTypes().forEach(type -> {
System.out.println(type);
});
System.out.println("\n====== Endpoints ======");
transformer.getEndpoints().forEach(endpoint -> {
System.out.println(endpoint); System.out.println(endpoint);
}); });
} }
@ -59,7 +65,7 @@ public class Generator implements Callable<Integer> {
this.verbose, this.verbose,
this.outputDir, this.outputDir,
this.templateDir, this.templateDir,
endpoints transformer.getEndpoints()
); );
targetGenerator.generate(); targetGenerator.generate();
return 0; return 0;

View file

@ -9,14 +9,95 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
public class NodeTransformer { public class NodeTransformer {
private final List<EndpointNode> endpoints = new ArrayList<>();
private final List<DataTypeNode> dataTypes = new ArrayList<>();
public static List<EndpointNode> transform(SimpleNode rootNode) throws ParseException { public List<EndpointNode> getEndpoints() {
ArrayList<EndpointNode> endpoints = new ArrayList<>(); return endpoints;
for (int i = 0; i < rootNode.jjtGetNumChildren(); i++) { }
SimpleNode endpoint = assertSimpleNode(rootNode.jjtGetChild(i));
endpoints.add(handleEndpoint(endpoint)); public List<DataTypeNode> getDataTypes() {
return dataTypes;
}
public void transform(SimpleNode rootNode) throws ParseException {
assertSimpleNodeType(rootNode, TapirParserTreeConstants.JJTSPECIFICATION);
int numChildren = rootNode.jjtGetNumChildren();
if (numChildren == 2) {
this.dataTypes.addAll(
handleDataTypes(
assertSimpleNodeType(
rootNode.jjtGetChild(0),
TapirParserTreeConstants.JJTDATATYPES
)
)
);
this.endpoints.addAll(
handleEndpoints(
assertSimpleNodeType(
rootNode.jjtGetChild(1),
TapirParserTreeConstants.JJTENDPOINTS
)
)
);
} else if (numChildren == 1) {
this.endpoints.addAll(
handleEndpoints(
assertSimpleNodeType(
rootNode.jjtGetChild(1),
TapirParserTreeConstants.JJTENDPOINTS
)
)
);
} else {
throw new ParseException("Expected specification to have 1 or 2 children but had " + numChildren);
} }
return endpoints ; }
private static List<EndpointNode> handleEndpoints(SimpleNode endpoints) throws ParseException {
ArrayList<EndpointNode> endpointNodes = new ArrayList<>();
for (int i = 0; i < endpoints.jjtGetNumChildren(); i++) {
endpointNodes.add(
handleEndpoint(
assertSimpleNodeType(
endpoints.jjtGetChild(i),
TapirParserTreeConstants.JJTENDPOINT
)
)
);
}
return endpointNodes;
}
private static List<DataTypeNode> handleDataTypes(SimpleNode dataTypesDeclaration) throws ParseException {
List<DataTypeNode> dataTypes = new ArrayList<>();
for (int i = 0; i < dataTypesDeclaration.jjtGetNumChildren(); i++) {
dataTypes.add(
handleCompoundDataType(
assertSimpleNodeType(
dataTypesDeclaration.jjtGetChild(i),
TapirParserTreeConstants.JJTCOMPOUNDDATATYPE
)
)
);
}
return dataTypes;
}
private static DataTypeNode handleCompoundDataType(SimpleNode dataTypeNode) throws ParseException {
String typename = getStringValue(
assertSimpleNodeType(
dataTypeNode.jjtGetChild(0),
TapirParserTreeConstants.JJTCOMPUNDDATATYPENAME
)
);
List<FieldNode> fields = handleFields(
assertSimpleNodeType(
dataTypeNode.jjtGetChild(1),
TapirParserTreeConstants.JJTDATATYPEFIELDS
)
);
return new DataTypeNode(typename, fields);
} }
private static EndpointNode handleEndpoint(SimpleNode node) throws ParseException { private static EndpointNode handleEndpoint(SimpleNode node) throws ParseException {
@ -27,13 +108,13 @@ public class NodeTransformer {
SimpleNode pathsParseNode = SimpleNode pathsParseNode =
assertSimpleNodeType( assertSimpleNodeType(
node.jjtGetChild(0), node.jjtGetChild(0),
TapirParserTreeConstants.JJTPATHS TapirParserTreeConstants.JJTPATH
); );
PathsNode pathsNode = handlePaths(pathsParseNode); PathsNode pathsNode = handlePaths(pathsParseNode);
SimpleNode handlerParseNode = SimpleNode handlerParseNode =
assertSimpleNodeType( assertSimpleNodeType(
node.jjtGetChild(1), node.jjtGetChild(1),
TapirParserTreeConstants.JJTHANDLERSPEC TapirParserTreeConstants.JJTCOMPOUNDDATATYPE
); );
HandlerNode handlerNode = handleHandler(handlerParseNode); HandlerNode handlerNode = handleHandler(handlerParseNode);
return new EndpointNode(pathsNode, handlerNode); return new EndpointNode(pathsNode, handlerNode);
@ -47,25 +128,25 @@ public class NodeTransformer {
String handlerName = getStringValue( String handlerName = getStringValue(
assertSimpleNodeType( assertSimpleNodeType(
handlerSpec.jjtGetChild(0), handlerSpec.jjtGetChild(0),
TapirParserTreeConstants.JJTHANDLERNAME TapirParserTreeConstants.JJTCOMPUNDDATATYPENAME
) )
); );
SimpleNode payloadFieldsParseNode = SimpleNode payloadFieldsParseNode =
assertSimpleNodeType( assertSimpleNodeType(
handlerSpec.jjtGetChild(1), handlerSpec.jjtGetChild(1),
TapirParserTreeConstants.JJTPAYLOADFIELDS TapirParserTreeConstants.JJTDATATYPEFIELDS
); );
List<FieldNode> fields = handleFields(payloadFieldsParseNode); List<FieldNode> fields = handleFields(payloadFieldsParseNode);
return new HandlerNode(handlerName, fields); return new HandlerNode(handlerName, fields);
} }
private static List<FieldNode> handleFields(SimpleNode payloadFieldsParseNode) throws ParseException { private static List<FieldNode> handleFields(SimpleNode compoundDatatTypeFields) throws ParseException {
ArrayList<FieldNode> fields = new ArrayList<>(); ArrayList<FieldNode> fields = new ArrayList<>();
for (int i = 0; i < payloadFieldsParseNode.jjtGetNumChildren(); i++) { for (int i = 0; i < compoundDatatTypeFields.jjtGetNumChildren(); i++) {
SimpleNode payloadFieldParseNode = SimpleNode payloadFieldParseNode =
assertSimpleNodeType( assertSimpleNodeType(
payloadFieldsParseNode.jjtGetChild(i), compoundDatatTypeFields.jjtGetChild(i),
TapirParserTreeConstants.JJTPAYLOADFIELD TapirParserTreeConstants.JJTDATATYPEFIELDS
); );
int numFieldNodes = payloadFieldParseNode.jjtGetNumChildren(); int numFieldNodes = payloadFieldParseNode.jjtGetNumChildren();
if (numFieldNodes != 2) { if (numFieldNodes != 2) {
@ -74,13 +155,13 @@ public class NodeTransformer {
String fieldName = getStringValue( String fieldName = getStringValue(
assertSimpleNodeType( assertSimpleNodeType(
payloadFieldParseNode.jjtGetChild(0), payloadFieldParseNode.jjtGetChild(0),
TapirParserTreeConstants.JJTPAYLOADFIELDNAME TapirParserTreeConstants.JJTDATATYPEFIELDNAME
) )
); );
String fieldType = getStringValue( String fieldType = getStringValue(
assertSimpleNodeType( assertSimpleNodeType(
payloadFieldParseNode.jjtGetChild(1), payloadFieldParseNode.jjtGetChild(1),
TapirParserTreeConstants.JJTPAYLOADFIELDTYPE TapirParserTreeConstants.JJTDATATYPEFIELDTYPE
) )
); );
fields.add(new FieldNode(fieldName, fieldType)); fields.add(new FieldNode(fieldName, fieldType));

View file

@ -24,6 +24,7 @@ public class TargetGenerator {
public TargetGeneratorException(String message) { public TargetGeneratorException(String message) {
super(message); super(message);
} }
public TargetGeneratorException(Exception cause) { public TargetGeneratorException(Exception cause) {
super(cause); super(cause);
} }
@ -44,26 +45,30 @@ public class TargetGenerator {
templatePath, templatePath,
"Template path is required" "Template path is required"
); );
this.endpoints = Objects.requireNonNull(endpoints) ; this.endpoints = Objects.requireNonNull(endpoints);
} }
public void generate() throws TargetGeneratorException, IOException, TemplateException { public void generate() throws TargetGeneratorException {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_34); try {
cfg.setDirectoryForTemplateLoading(this.templatePath.toFile()); Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);
cfg.setDefaultEncoding("UTF-8"); cfg.setDirectoryForTemplateLoading(this.templatePath.toFile());
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); cfg.setDefaultEncoding("UTF-8");
cfg.setLogTemplateExceptions(false); cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
cfg.setWrapUncheckedExceptions(true); cfg.setLogTemplateExceptions(false);
cfg.setFallbackOnNullLoopVariable(false); cfg.setWrapUncheckedExceptions(true);
Template temp = cfg.getTemplate(ENDPOINTS_TEMPLATE_NAME); cfg.setFallbackOnNullLoopVariable(false);
try (var outputFile = Files.newBufferedWriter( Template temp = cfg.getTemplate(ENDPOINTS_TEMPLATE_NAME);
outputPath.resolve("endpoints.scala"), try (var outputFile = Files.newBufferedWriter(
StandardOpenOption.CREATE, outputPath.resolve("endpoints.scala"),
StandardOpenOption.TRUNCATE_EXISTING StandardOpenOption.CREATE,
)) { StandardOpenOption.TRUNCATE_EXISTING
HashMap<String, List<EndpointNode>> templateData = new HashMap<>(); )) {
templateData.put("endpoints", endpoints); HashMap<String, List<EndpointNode>> templateData = new HashMap<>();
temp.process(templateData, outputFile); templateData.put("endpoints", endpoints);
temp.process(templateData, outputFile);
}
} catch (TemplateException | IOException ex) {
throw new TargetGeneratorException(ex);
} }
} }
} }

View file

@ -23,6 +23,7 @@ SKIP: {
TOKEN : { TOKEN : {
<OPENPARANTHESIS: "("> <OPENPARANTHESIS: "(">
| <CLOSEPARANTHESIS: ")"> | <CLOSEPARANTHESIS: ")">
| <SEMICOLON: ";">
| <TRANSITION: "->"> | <TRANSITION: "->">
| <SLASH: "/"> | <SLASH: "/">
| <COLON: ":"> | <COLON: ":">
@ -32,63 +33,75 @@ TOKEN : {
| <IDENTIFIER: <FIRST_LETTER> (<LETTER>)* > | <IDENTIFIER: <FIRST_LETTER> (<LETTER>)* >
} }
void path() : void pathSegment() :
{Token t;} {Token t;}
{ {
t=<IDENTIFIER>{jjtThis.value = t.image;} <SLASH> t=<IDENTIFIER>{jjtThis.value = t.image;} <SLASH>
} }
void paths() : void path() :
{} {}
{ {
path() (path())* pathSegment() (pathSegment())*
} }
void payloadFieldName() : void dataTypeFieldType() :
{Token t;} {Token t;}
{ {
t=<IDENTIFIER>{jjtThis.value = t.image;} t=<IDENTIFIER>{jjtThis.value = t.image;}
} }
void payloadFieldType() : void dataTypeFieldName() :
{Token t;} {Token t;}
{ {
t=<IDENTIFIER>{jjtThis.value = t.image;} t=<IDENTIFIER>{jjtThis.value = t.image;}
} }
void payloadField() : void dataTypeField() :
{} {}
{ {
payloadFieldName() <COLON> payloadFieldType() dataTypeFieldName() <COLON> dataTypeFieldType()
} }
void payloadFields() : void dataTypeFields() :
{} {}
{ {
payloadField() (<COMMA> payloadField() )* dataTypeField() (<COMMA> dataTypeField() )*
} }
void handlerName() : void compundDataTypeName() :
{Token t;} {Token t;}
{ {
t=<IDENTIFIER>{jjtThis.value = t.image;} t=<IDENTIFIER>{jjtThis.value = t.image;}
} }
void handlerSpec() : void compoundDataType() :
{} {}
{ {
handlerName() <OPENPARANTHESIS> payloadFields() <CLOSEPARANTHESIS> compundDataTypeName() <OPENPARANTHESIS> dataTypeFields() <CLOSEPARANTHESIS>
}
void dataTypes() :
{}
{
(compoundDataType() )*
} }
void endpoint() : void endpoint() :
{} {}
{ {
paths() <TRANSITION> handlerSpec() path() <TRANSITION> compoundDataType()
} }
SimpleNode endpoints() : void endpoints() :
{} {}
{ {
(endpoint() )* (endpoint() )*
}
SimpleNode specification() :
{}
{
dataTypes() endpoints()
{ return jjtThis; } { return jjtThis; }
} }