diff --git a/parser/pom.xml b/parser/pom.xml
new file mode 100755
index 0000000..5062a5f
--- /dev/null
+++ b/parser/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+
+ nu.zoom.tapir
+ tapir-generator
+ 1.0-SNAPSHOT
+
+
+ parser
+
+
+
+
+ org.codehaus.mojo
+ javacc-maven-plugin
+ 3.0.1
+
+
+
+ jjtree-javacc
+
+ jjtree-javacc
+
+
+ false
+
+
+
+
+
+
+
+
+
+ net.java.dev.javacc
+ javacc
+
+
+ info.picocli
+ picocli
+
+
+
diff --git a/parser/src/main/jjtree/tapir.jjt b/parser/src/main/jjtree/tapir.jjt
new file mode 100755
index 0000000..83babd3
--- /dev/null
+++ b/parser/src/main/jjtree/tapir.jjt
@@ -0,0 +1,93 @@
+
+PARSER_BEGIN(TapirParser)
+package nu.zoom.tapir.parser;
+
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+
+public class TapirParser
+{
+}
+
+PARSER_END(TapirParser)
+
+// white space
+SKIP: {
+ "\n"
+ | "\t"
+ | "\r"
+ | " "
+}
+
+TOKEN : {
+
+ |
+ | ">
+ |
+ |
+ |
+ |
+ | )+ >
+}
+
+void path() :
+{Token t;}
+{
+ t={jjtThis.value = t.image;}
+}
+
+void paths() :
+{}
+{
+ path() (path())*
+}
+
+void payloadFieldName() :
+{Token t;}
+{
+ t={jjtThis.value = t.image;}
+}
+
+void payloadFieldType() :
+{Token t;}
+{
+ t={jjtThis.value = t.image;}
+}
+
+void payloadField() :
+{}
+{
+ payloadFieldName() payloadFieldType()
+}
+
+void payloadFields() :
+{}
+{
+ payloadField() ( payloadField() )*
+}
+
+void handlerName() :
+{Token t;}
+{
+ t={jjtThis.value = t.image;}
+}
+
+void handlerSpec() :
+{}
+{
+ handlerName() payloadFields()
+}
+
+void endpoint() :
+{}
+{
+ paths() handlerSpec()
+}
+
+SimpleNode endpoints() :
+{}
+{
+ (endpoint() )*
+ { return jjtThis; }
+}
diff --git a/pom.xml b/pom.xml
new file mode 100755
index 0000000..0e81479
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,36 @@
+
+
+ 4.0.0
+
+ nu.zoom.tapir
+ tapir-generator
+ 1.0-SNAPSHOT
+ pom
+
+ parser
+
+
+
+ 23
+ 23
+ UTF-8
+
+
+
+
+
+ net.java.dev.javacc
+ javacc
+ 7.0.13
+
+
+ info.picocli
+ picocli
+ 4.7.6
+
+
+
+
+