From 3871b4f911faacf67203d6a8400166a5267e87f7 Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Fri, 16 Dec 2022 15:22:15 -0800 Subject: [PATCH 1/6] Updates generated sources and replaces hacky publish plugin with an actual plugin --- buildSrc/build.gradle.kts | 1 + .../pig/gradle/publish/PigPublishPlugin.kt | 112 ++++++++++++++++++ .../src/main/kotlin/pig.publish.gradle.kts | 99 ---------------- .../org.partiql.pig.gradle.publish.properties | 1 + pig-runtime/build.gradle.kts | 9 +- .../pig/tests/generated/DomainA.generated.kt | 6 - .../pig/tests/generated/DomainB.generated.kt | 7 -- .../generated/MultiWordDomain.generated.kt | 11 -- .../tests/generated/PartiqlBasic.generated.kt | 4 - pig/build.gradle.kts | 7 +- 10 files changed, 125 insertions(+), 132 deletions(-) create mode 100644 buildSrc/src/main/kotlin/org/partiql/pig/gradle/publish/PigPublishPlugin.kt delete mode 100644 buildSrc/src/main/kotlin/pig.publish.gradle.kts create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/org.partiql.pig.gradle.publish.properties diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 6158007..d503e8d 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -15,6 +15,7 @@ plugins { `kotlin-dsl` + id("java-gradle-plugin") } repositories { diff --git a/buildSrc/src/main/kotlin/org/partiql/pig/gradle/publish/PigPublishPlugin.kt b/buildSrc/src/main/kotlin/org/partiql/pig/gradle/publish/PigPublishPlugin.kt new file mode 100644 index 0000000..bf1621c --- /dev/null +++ b/buildSrc/src/main/kotlin/org/partiql/pig/gradle/publish/PigPublishPlugin.kt @@ -0,0 +1,112 @@ +package org.partiql.pig.gradle.publish + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.publish.maven.plugins.MavenPublishPlugin +import org.gradle.api.publish.maven.tasks.PublishToMavenRepository +import org.gradle.kotlin.dsl.create +import org.gradle.kotlin.dsl.get +import org.gradle.kotlin.dsl.getByName +import org.gradle.kotlin.dsl.provideDelegate +import org.gradle.plugins.signing.SigningExtension +import org.gradle.plugins.signing.SigningPlugin +import org.jetbrains.dokka.gradle.DokkaPlugin +import org.jetbrains.dokka.gradle.DokkaTask +import java.io.File + +/** + * Gradle plugin to consolidates the following publishing logic + * - Maven Publising + * - Signing + * - SourcesJar + * - Dokka + JavadocJar + */ +abstract class PigPublishPlugin : Plugin { + + override fun apply(target: Project): Unit = with(target) { + pluginManager.apply(JavaPlugin::class.java) + pluginManager.apply(MavenPublishPlugin::class.java) + pluginManager.apply(SigningPlugin::class.java) + pluginManager.apply(DokkaPlugin::class.java) + val ext = extensions.create("publish", PublishExtension::class.java) + target.afterEvaluate { publish(ext) } + } + + private fun Project.publish(ext: PublishExtension) { + val releaseVersion = !version.toString().endsWith("-SNAPSHOT") + + // Generate "javadoc" + tasks.getByName("dokkaHtml") { + outputDirectory.set(File("${buildDir}/javadoc")) + } + + // Include "sources" and "javadoc" in the JAR + extensions.getByType(JavaPluginExtension::class.java).run { + withSourcesJar() + withJavadocJar() + } + + // Setup Maven Central Publishing + val publishing = extensions.getByType(PublishingExtension::class.java).apply { + publications { + create("maven") { + artifactId = ext.artifactId + from(components["java"]) + pom { + packaging = "jar" + name.set(ext.name) + description.set("The P.I.G. is a code generator for domain models such ASTs and execution plans.") + url.set("git@github.com:partiql/partiql-ir-generator.git") + scm { + connection.set("scm:git@github.com:partiql/partiql-ir-generator.git") + developerConnection.set("scm:git@github.com:partiql/partiql-ir-generator.git") + url.set("git@github.com:partiql/partiql-ir-generator.git") + } + licenses { + license { + name.set("The Apache License, Version 2.0") + url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + developers { + developer { + name.set("PartiQL Team") + email.set("partiql-dev@amazon.com") + organization.set("PartiQL") + organizationUrl.set("https://github.com/partiql") + } + } + } + } + } + repositories { + maven { + url = uri("https://aws.oss.sonatype.org/service/local/staging/deploy/maven2") + credentials { + val ossrhUsername: String by rootProject + val ossrhPassword: String by rootProject + username = ossrhUsername + password = ossrhPassword + } + } + } + } + + // Sign only if publishing to Maven Central + extensions.getByType(SigningExtension::class.java).run { + setRequired { + releaseVersion && gradle.taskGraph.allTasks.any { it is PublishToMavenRepository } + } + sign(publishing.publications["maven"]) + } + } +} + +abstract class PublishExtension { + var artifactId: String = "" + var name: String = "" +} diff --git a/buildSrc/src/main/kotlin/pig.publish.gradle.kts b/buildSrc/src/main/kotlin/pig.publish.gradle.kts deleted file mode 100644 index 6d81e32..0000000 --- a/buildSrc/src/main/kotlin/pig.publish.gradle.kts +++ /dev/null @@ -1,99 +0,0 @@ -import gradle.kotlin.dsl.accessors._4ba0e6f3f9855dd979d4ade2b5984a53.java - -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file 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. - */ - -plugins { - `maven-publish` - id("signing") - id("org.jetbrains.dokka") -} - -tasks.dokkaHtml.configure { - outputDirectory.set(buildDir.resolve("javadoc")) -} - -java { - withJavadocJar() - withSourcesJar() -} - -fun String.mavenName(): String = when (this) { - "pig-runtime" -> "PartiQL I.R. Generator (a.k.a P.I.G.) Runtime Library" - else -> "PartiQL I.R. Generator (a.k.a P.I.G.)" -} - -fun String.artifactId(): String = name.replace("pig", "partiql-ir-generator") - -publishing { - publications { - create(name) { - val module = name - artifactId = module.artifactId() - from(components["java"]) - pom { - url.set("https://partiql.org/") - packaging = "jar" - name.set(module.mavenName()) - description.set("The P.I.G. is a code generator for domain models such ASTs and execution plans.") - scm { - connection.set("scm:git@github.com:partiql/partiql-ir-generator.git") - developerConnection.set("scm:git@github.com:partiql/partiql-ir-generator.git") - url.set("git@github.com:partiql/partiql-ir-generator.git") - licenses { - license { - name.set("The Apache License, Version 2.0") - url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") - } - } - developers { - developer { - name.set("PartiQL Team") - email.set("partiql-team@amazon.com") - organization.set("PartiQL") - organizationUrl.set("https://github.com/partiql") - } - } - } - } - } - repositories { - maven { - url = uri("https://aws.oss.sonatype.org/service/local/staging/deploy/maven2") - credentials { - val ossrhUsername: String by rootProject - val ossrhPassword: String by rootProject - username = ossrhUsername - password = ossrhPassword - } - } - } - } -} - -val isReleaseVersion = !rootProject.version.toString().endsWith("SNAPSHOT") - -signing { - sign(publishing.publications[name]) - isRequired = isReleaseVersion - // TODO figure out how to enable this - // && gradle.taskGraph.hasTask("publish") -} - -tasks.withType(Sign::class) { - onlyIf { - isReleaseVersion - } -} diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/org.partiql.pig.gradle.publish.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/org.partiql.pig.gradle.publish.properties new file mode 100644 index 0000000..3f4950f --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/org.partiql.pig.gradle.publish.properties @@ -0,0 +1 @@ +implementation-class=org.partiql.pig.gradle.publish.PigPublishPlugin diff --git a/pig-runtime/build.gradle.kts b/pig-runtime/build.gradle.kts index d189ec7..d450e16 100644 --- a/pig-runtime/build.gradle.kts +++ b/pig-runtime/build.gradle.kts @@ -15,11 +15,14 @@ plugins { id("pig.conventions") - id("pig.publish") + id("org.partiql.pig.gradle.publish") } -project.description = "The P.I.G. is a code generator for domain models such ASTs and execution plans." - dependencies { api("com.amazon.ion:ion-element:1.0.0") } + +publish { + artifactId = "pig-runtime" + name = "PartiQL I.R. Generator (a.k.a P.I.G.) Runtime Library" +} diff --git a/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/DomainA.generated.kt b/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/DomainA.generated.kt index 9b6cd57..8d1bbe0 100644 --- a/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/DomainA.generated.kt +++ b/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/DomainA.generated.kt @@ -1551,41 +1551,35 @@ class DomainA private constructor() { walkSymbolPrimitive(node.whatever) walkMetas(node.metas) } - open fun walkRecordToRemove(node: DomainA.RecordToRemove) { visitRecordToRemove(node) walkLongPrimitive(node.irrelevant) walkMetas(node.metas) } - open fun walkProductA(node: DomainA.ProductA) { visitProductA(node) walkLongPrimitive(node.one) walkProductToRemove(node.two) walkMetas(node.metas) } - open fun walkRecordA(node: DomainA.RecordA) { visitRecordA(node) walkLongPrimitive(node.one) walkProductToRemove(node.two) walkMetas(node.metas) } - open fun walkUnpermutedProduct(node: DomainA.UnpermutedProduct) { visitUnpermutedProduct(node) walkSymbolPrimitive(node.foo) walkLongPrimitive(node.bar) walkMetas(node.metas) } - open fun walkUnpermutedRecord(node: DomainA.UnpermutedRecord) { visitUnpermutedRecord(node) walkSymbolPrimitive(node.foo) walkLongPrimitive(node.bar) walkMetas(node.metas) } - ////////////////////////////////////// // Sum Type: SumToRemove ////////////////////////////////////// diff --git a/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/DomainB.generated.kt b/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/DomainB.generated.kt index e9e0c49..48a5b5c 100644 --- a/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/DomainB.generated.kt +++ b/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/DomainB.generated.kt @@ -1245,44 +1245,37 @@ class DomainB private constructor() { walkLongPrimitive(node.bar) walkMetas(node.metas) } - open fun walkUnpermutedRecord(node: DomainB.UnpermutedRecord) { visitUnpermutedRecord(node) walkSymbolPrimitive(node.foo) walkLongPrimitive(node.bar) walkMetas(node.metas) } - open fun walkProductA(node: DomainB.ProductA) { visitProductA(node) walkSymbolPrimitive(node.one) walkMetas(node.metas) } - open fun walkRecordA(node: DomainB.RecordA) { visitRecordA(node) walkSymbolPrimitive(node.one) walkMetas(node.metas) } - open fun walkSumToReplaceWithProduct(node: DomainB.SumToReplaceWithProduct) { visitSumToReplaceWithProduct(node) walkSymbolPrimitive(node.foo) walkMetas(node.metas) } - open fun walkNewProduct(node: DomainB.NewProduct) { visitNewProduct(node) walkLongPrimitive(node.foo) walkMetas(node.metas) } - open fun walkNewRecord(node: DomainB.NewRecord) { visitNewRecord(node) walkLongPrimitive(node.foo) walkMetas(node.metas) } - ////////////////////////////////////// // Sum Type: UnpermutedSum ////////////////////////////////////// diff --git a/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/MultiWordDomain.generated.kt b/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/MultiWordDomain.generated.kt index 554f894..60962f2 100644 --- a/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/MultiWordDomain.generated.kt +++ b/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/MultiWordDomain.generated.kt @@ -1505,39 +1505,33 @@ class MultiWordDomain private constructor() { visitAaaAaa(node) walkMetas(node.metas) } - open fun walkAaaAab(node: MultiWordDomain.AaaAab) { visitAaaAab(node) node.dField?.let { walkLongPrimitive(it) } walkMetas(node.metas) } - open fun walkAaaAac(node: MultiWordDomain.AaaAac) { visitAaaAac(node) node.dField?.let { walkLongPrimitive(it) } node.eField?.let { walkSymbolPrimitive(it) } walkMetas(node.metas) } - open fun walkAaaAad(node: MultiWordDomain.AaaAad) { visitAaaAad(node) node.dField.map { walkLongPrimitive(it) } walkMetas(node.metas) } - open fun walkAaaAae(node: MultiWordDomain.AaaAae) { visitAaaAae(node) node.dField.map { walkLongPrimitive(it) } walkMetas(node.metas) } - open fun walkAabAaa(node: MultiWordDomain.AabAaa) { visitAabAaa(node) walkLongPrimitive(node.bField) walkSymbolPrimitive(node.cField) walkMetas(node.metas) } - open fun walkAabAab(node: MultiWordDomain.AabAab) { visitAabAab(node) walkLongPrimitive(node.bField) @@ -1545,7 +1539,6 @@ class MultiWordDomain private constructor() { node.dField?.let { walkLongPrimitive(it) } walkMetas(node.metas) } - open fun walkAabAac(node: MultiWordDomain.AabAac) { visitAabAac(node) walkLongPrimitive(node.bField) @@ -1554,7 +1547,6 @@ class MultiWordDomain private constructor() { node.eField?.let { walkSymbolPrimitive(it) } walkMetas(node.metas) } - open fun walkAabAad(node: MultiWordDomain.AabAad) { visitAabAad(node) walkLongPrimitive(node.bField) @@ -1562,7 +1554,6 @@ class MultiWordDomain private constructor() { node.dField.map { walkLongPrimitive(it) } walkMetas(node.metas) } - open fun walkAabAae(node: MultiWordDomain.AabAae) { visitAabAae(node) walkLongPrimitive(node.bField) @@ -1570,14 +1561,12 @@ class MultiWordDomain private constructor() { node.dField.map { walkLongPrimitive(it) } walkMetas(node.metas) } - open fun walkRrr(node: MultiWordDomain.Rrr) { visitRrr(node) walkLongPrimitive(node.aField) walkLongPrimitive(node.bbbField) walkMetas(node.metas) } - ////////////////////////////////////// // Sum Type: SssTtt ////////////////////////////////////// diff --git a/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/PartiqlBasic.generated.kt b/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/PartiqlBasic.generated.kt index 8ff2d35..c6030be 100644 --- a/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/PartiqlBasic.generated.kt +++ b/pig-tests/src/main/kotlin/org/partiql/pig/tests/generated/PartiqlBasic.generated.kt @@ -3891,27 +3891,23 @@ class PartiqlBasic private constructor() { walkExpr(node.second) walkMetas(node.metas) } - open fun walkGroupByItem(node: PartiqlBasic.GroupByItem) { visitGroupByItem(node) walkExpr(node.value) node.asAlias?.let { walkSymbolPrimitive(it) } walkMetas(node.metas) } - open fun walkGroupByList(node: PartiqlBasic.GroupByList) { visitGroupByList(node) node.items.map { walkGroupByItem(it) } walkMetas(node.metas) } - open fun walkGroupBy(node: PartiqlBasic.GroupBy) { visitGroupBy(node) walkGroupByList(node.items) node.groupAsAlias?.let { walkSymbolPrimitive(it) } walkMetas(node.metas) } - ////////////////////////////////////// // Sum Type: Projection ////////////////////////////////////// diff --git a/pig/build.gradle.kts b/pig/build.gradle.kts index 6c83308..2d34215 100644 --- a/pig/build.gradle.kts +++ b/pig/build.gradle.kts @@ -18,10 +18,13 @@ import java.util.Properties plugins { id("application") id("pig.conventions") - id("pig.publish") + id("org.partiql.pig.gradle.publish") } -project.description = "The P.I.G. is a code generator for domain models such ASTs and execution plans." +publish { + artifactId = "pig" + name = "PartiQL I.R. Generator (a.k.a P.I.G.)" +} val propertiesDir = "$buildDir/properties" From ccf94c6a3d328991657b6677de73dbf4cbc6551a Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Tue, 25 Apr 2023 11:45:12 -0700 Subject: [PATCH 2/6] Initialize PIG 1.0 --- buildSrc/build.gradle.kts | 6 +- .../pig/gradle/publish/PigPublishPlugin.kt | 22 +- .../main/kotlin/pig.conventions.gradle.kts | 132 +++++++- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 0 gradlew.bat | 89 ----- pig/build.gradle.kts | 95 ++---- pig/src/main/kotlin/org/partiql/pig/Pig.kt | 47 +++ .../org/partiql/pig/errors/Exceptions.kt | 2 +- .../kotlin/org/partiql/pig/errors/PigError.kt | 2 +- .../org/partiql/pig/generator/Generator.kt | 26 ++ .../partiql/pig/generator/target/dot/.gitkeep | 0 .../generator/target/kotlin/KotlinCommand.kt | 81 +++++ .../target/kotlin/KotlinGenerator.kt | 142 ++++++++ .../generator/target/kotlin/KotlinOptions.kt | 26 ++ .../pig/generator/target/kotlin/KotlinPoem.kt | 28 ++ .../generator/target/kotlin/KotlinResult.kt | 8 + .../generator/target/kotlin/KotlinSymbols.kt | 201 ++++++++++++ .../target/kotlin/poems/KotlinBuilderPoem.kt | 235 +++++++++++++ .../target/kotlin/poems/KotlinJacksonPoem.kt | 227 +++++++++++++ .../target/kotlin/poems/KotlinListenerPoem.kt | 121 +++++++ .../target/kotlin/poems/KotlinMetasPoem.kt | 25 ++ .../target/kotlin/poems/KotlinVisitorPoem.kt | 247 ++++++++++++++ .../target/kotlin/spec/KotlinFileSpec.kt | 35 ++ .../target/kotlin/spec/KotlinNodeSpec.kt | 114 +++++++ .../target/kotlin/spec/KotlinPackageSpec.kt | 29 ++ .../target/kotlin/spec/KotlinUniverseSpec.kt | 82 +++++ .../target/kotlin/types/Annotations.kt | 25 ++ .../target/kotlin/types/JacksonTypes.kt | 69 ++++ .../target/kotlin/types/Parameters.kt | 27 ++ .../partiql/pig/{domain => legacy}/Utils.kt | 0 .../pig/{ => legacy}/cmdline/Command.kt | 0 .../{ => legacy}/cmdline/CommandLineParser.kt | 0 .../{ => legacy}/cmdline/TargetLanguage.kt | 0 .../{ => legacy}/generator/FreeMarkerUtils.kt | 2 +- .../{ => legacy}/generator/IndentDirective.kt | 2 +- .../generator/custom/CTypeDomain.kt | 2 +- .../custom/CustomFreeMarkerGlobals.kt | 2 +- .../generator/custom/generator.kt | 2 +- .../{ => legacy}/generator/html/generator.kt | 2 +- .../{ => legacy}/generator/ion/generator.kt | 4 +- .../generator/kotlin/KTypeDomain.kt | 2 +- .../generator/kotlin/KTypeDomainConverter.kt | 2 +- .../KotlinCrossDomainFreeMarkerGlobals.kt | 2 +- .../kotlin/KotlinDomainFreeMarkerGlobals.kt | 2 +- .../generator/kotlin/generator.kt | 2 +- .../org/partiql/pig/{ => legacy}/main.kt | 0 .../pig/{domain => legacy}/model/Arity.kt | 0 .../pig/{domain => legacy}/model/DataType.kt | 0 .../{domain => legacy}/model/NamedElement.kt | 0 .../model/SemanticErrorContext.kt | 0 .../pig/{domain => legacy}/model/Statement.kt | 0 .../pig/{domain => legacy}/model/TupleType.kt | 0 .../model/TypeAnnotation.kt | 0 .../model/TypeDomainSemanticChecker.kt | 0 .../pig/{domain => legacy}/model/TypeRef.kt | 0 .../{domain => legacy}/model/TypeUniverse.kt | 0 .../parser/ParserErrorContext.kt | 0 .../parser/TypeDomainParser.kt | 0 .../pig/{ => legacy}/util/Capitalizers.kt | 0 .../pig/{ => legacy}/util/IonElement.kt | 0 .../kotlin/org/partiql/pig/model/Model.kt | 187 +++++++++++ .../org/partiql/pig/parser/SproutParser.kt | 37 +++ .../partiql/pig/parser/ion/IonExtensions.kt | 216 ++++++++++++ .../org/partiql/pig/parser/ion/IonImports.kt | 70 ++++ .../org/partiql/pig/parser/ion/IonSymbols.kt | 143 ++++++++ .../partiql/pig/parser/ion/IonTypeParser.kt | 308 ++++++++++++++++++ .../org/partiql/pig/parser/ion/IonVisitor.kt | 105 ++++++ .../org/partiql/pig/parser/ion/note.txt | 0 70 files changed, 3049 insertions(+), 190 deletions(-) delete mode 100644 gradle/wrapper/gradle-wrapper.jar mode change 100755 => 100644 gradlew create mode 100644 pig/src/main/kotlin/org/partiql/pig/Pig.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/Generator.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/dot/.gitkeep create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinCommand.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinGenerator.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinOptions.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinPoem.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinResult.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinSymbols.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinBuilderPoem.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinJacksonPoem.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinListenerPoem.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinMetasPoem.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinVisitorPoem.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinFileSpec.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinNodeSpec.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinPackageSpec.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinUniverseSpec.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/Annotations.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/JacksonTypes.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/Parameters.kt rename pig/src/main/kotlin/org/partiql/pig/{domain => legacy}/Utils.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/cmdline/Command.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/cmdline/CommandLineParser.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/cmdline/TargetLanguage.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/generator/FreeMarkerUtils.kt (98%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/generator/IndentDirective.kt (98%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/generator/custom/CTypeDomain.kt (98%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/generator/custom/CustomFreeMarkerGlobals.kt (94%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/generator/custom/generator.kt (96%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/generator/html/generator.kt (96%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/generator/ion/generator.kt (75%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/generator/kotlin/KTypeDomain.kt (98%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/generator/kotlin/KTypeDomainConverter.kt (99%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/generator/kotlin/KotlinCrossDomainFreeMarkerGlobals.kt (88%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/generator/kotlin/KotlinDomainFreeMarkerGlobals.kt (95%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/generator/kotlin/generator.kt (98%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/main.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{domain => legacy}/model/Arity.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{domain => legacy}/model/DataType.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{domain => legacy}/model/NamedElement.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{domain => legacy}/model/SemanticErrorContext.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{domain => legacy}/model/Statement.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{domain => legacy}/model/TupleType.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{domain => legacy}/model/TypeAnnotation.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{domain => legacy}/model/TypeDomainSemanticChecker.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{domain => legacy}/model/TypeRef.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{domain => legacy}/model/TypeUniverse.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{domain => legacy}/parser/ParserErrorContext.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{domain => legacy}/parser/TypeDomainParser.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/util/Capitalizers.kt (100%) rename pig/src/main/kotlin/org/partiql/pig/{ => legacy}/util/IonElement.kt (100%) create mode 100644 pig/src/main/kotlin/org/partiql/pig/model/Model.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/parser/SproutParser.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/parser/ion/IonExtensions.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/parser/ion/IonImports.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/parser/ion/IonSymbols.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/parser/ion/IonTypeParser.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/parser/ion/IonVisitor.kt create mode 100644 pig/src/main/kotlin/org/partiql/pig/parser/ion/note.txt diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index d503e8d..62ce519 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -23,9 +23,9 @@ repositories { } dependencies { - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0") - implementation("org.jlleitschuh.gradle:ktlint-gradle:10.3.0") - implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.4.0") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10") + implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.8.10") + implementation("org.jlleitschuh.gradle:ktlint-gradle:11.3.2") } allprojects { diff --git a/buildSrc/src/main/kotlin/org/partiql/pig/gradle/publish/PigPublishPlugin.kt b/buildSrc/src/main/kotlin/org/partiql/pig/gradle/publish/PigPublishPlugin.kt index bf1621c..9fd4449 100644 --- a/buildSrc/src/main/kotlin/org/partiql/pig/gradle/publish/PigPublishPlugin.kt +++ b/buildSrc/src/main/kotlin/org/partiql/pig/gradle/publish/PigPublishPlugin.kt @@ -8,6 +8,7 @@ import org.gradle.api.publish.PublishingExtension import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.publish.maven.plugins.MavenPublishPlugin import org.gradle.api.publish.maven.tasks.PublishToMavenRepository +import org.gradle.jvm.tasks.Jar import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.get import org.gradle.kotlin.dsl.getByName @@ -20,7 +21,7 @@ import java.io.File /** * Gradle plugin to consolidates the following publishing logic - * - Maven Publising + * - Maven Publishing * - Signing * - SourcesJar * - Dokka + JavadocJar @@ -39,10 +40,8 @@ abstract class PigPublishPlugin : Plugin { private fun Project.publish(ext: PublishExtension) { val releaseVersion = !version.toString().endsWith("-SNAPSHOT") - // Generate "javadoc" - tasks.getByName("dokkaHtml") { - outputDirectory.set(File("${buildDir}/javadoc")) - } + // Run dokka unless the environment explicitly specifies false + val runDokka = (System.getenv()["DOKKA"] != "false") || releaseVersion // Include "sources" and "javadoc" in the JAR extensions.getByType(JavaPluginExtension::class.java).run { @@ -50,6 +49,19 @@ abstract class PigPublishPlugin : Plugin { withJavadocJar() } + tasks.getByName("dokkaHtml") { + onlyIf { runDokka } + outputDirectory.set(File("${buildDir}/javadoc")) + } + + // Add dokkaHtml output to the javadocJar + tasks.getByName("javadocJar") { + onlyIf { runDokka } + dependsOn(JavaPlugin.CLASSES_TASK_NAME) + archiveClassifier.set("javadoc") + from(tasks.named("dokkaHtml")) + } + // Setup Maven Central Publishing val publishing = extensions.getByType(PublishingExtension::class.java).apply { publications { diff --git a/buildSrc/src/main/kotlin/pig.conventions.gradle.kts b/buildSrc/src/main/kotlin/pig.conventions.gradle.kts index d21497c..ef0da8f 100644 --- a/buildSrc/src/main/kotlin/pig.conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/pig.conventions.gradle.kts @@ -1,3 +1,11 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.jlleitschuh.gradle.ktlint.KtlintExtension +import java.io.ByteArrayOutputStream +import java.io.FileOutputStream +import java.util.Properties +import java.util.random.RandomGeneratorFactory.all + /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * @@ -24,35 +32,131 @@ repositories { } object Versions { - val kotlin = "1.4.0" - val ion = "1.9.4" - val jupiter = "5.6.2" - val jvmTarget = JavaVersion.VERSION_1_8 + // Language + const val kotlin = "1.5.31" + const val kotlinTarget = "1.4" + const val javaTarget = "1.8" + + // Dependencies + const val ionJava = "1.9.4" + const val ionElement = "1.0.0" + const val jline = "3.21.0" + const val kasechange = "1.3.0" + const val kotlinPoet = "1.8.0" // Kotlin 1.5 + const val picoCli = "4.7.0" + // Testing +} + +object Deps { + // Language + const val kotlin = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlin}" + + // Dependencies + const val ionElement = "com.amazon.ion:ion-element:${Versions.ionElement}" + const val kasechange = "net.pearx.kasechange:kasechange:${Versions.kasechange}" + const val kotlinPoet = "com.squareup:kotlinpoet:${Versions.kotlinPoet}" + const val kotlinReflect = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlin}" + const val picoCli = "info.picocli:picocli:${Versions.picoCli}" + + // Testing + const val kotlinTest = "org.jetbrains.kotlin:kotlin-test:${Versions.kotlin}" + const val kotlinTestJunit = "org.jetbrains.kotlin:kotlin-test-junit5:${Versions.kotlin}" +} + +object Plugins { + // PIG + const val conventions = "pig.conventions" + const val publish = "org.partiql.pig.gradle.plugin.publish" + + // 3P + const val application = "org.gradle.application" + const val detekt = "io.gitlab.arturbosch.detekt" + const val dokka = "org.jetbrains.dokka" + const val ktlint = "org.jlleitschuh.gradle.ktlint" + const val library = "org.gradle.java-library" } val buildDir = File(rootProject.projectDir, "gradle-build/" + project.name) dependencies { - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlin}") - implementation("org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlin}") - - testImplementation("org.jetbrains.kotlin:kotlin-test:${Versions.kotlin}") - testImplementation("org.junit.jupiter:junit-jupiter:${Versions.jupiter}") + implementation(Deps.kotlin) + testImplementation(Deps.kotlinTest) + testImplementation(Deps.kotlinTestJunit) } +val generatedSrc = "$buildDir/generated-src" +val generatedVersion = "$buildDir/generated-version" + java { - sourceCompatibility = Versions.jvmTarget - targetCompatibility = Versions.jvmTarget + sourceCompatibility = JavaVersion.toVersion(Versions.javaTarget) + targetCompatibility = JavaVersion.toVersion(Versions.javaTarget) } tasks.test { - useJUnitPlatform() + useJUnitPlatform() // Enable JUnit5 + jvmArgs!!.addAll(listOf("-Duser.language=en", "-Duser.country=US")) + maxHeapSize = "4g" + testLogging { + events.add(TestLogEvent.FAILED) + exceptionFormat = TestExceptionFormat.FULL + } + dependsOn(tasks.ktlintCheck) } tasks.compileKotlin { - kotlinOptions.jvmTarget = Versions.jvmTarget.toString() + kotlinOptions.jvmTarget = Versions.javaTarget + kotlinOptions.apiVersion = Versions.kotlinTarget + kotlinOptions.languageVersion = Versions.kotlinTarget } tasks.compileTestKotlin { - kotlinOptions.jvmTarget = Versions.jvmTarget.toString() + kotlinOptions.jvmTarget = Versions.javaTarget + kotlinOptions.apiVersion = Versions.kotlinTarget + kotlinOptions.languageVersion = Versions.kotlinTarget +} + +configure { + filter { + exclude { it.file.path.contains(generatedSrc) } + } +} + +sourceSets { + main { + java.srcDir(generatedSrc) + output.dir(generatedVersion) + } +} + +kotlin.sourceSets { + all { + // languageSettings.optIn("kotlin.RequiresOptIn") + } + main { + kotlin.srcDir(generatedSrc) + } +} + +tasks.processResources { + dependsOn(tasks.findByName("generateVersionAndHash")) +} + +tasks.create("generateVersionAndHash") { + val propertiesFile = file("$generatedVersion/partiql.properties") + propertiesFile.parentFile.mkdirs() + val properties = Properties() + // Version + val version = version.toString() + properties.setProperty("version", version) + // Commit Hash + val commit = ByteArrayOutputStream().apply { + exec { + commandLine = listOf("git", "rev-parse", "--short", "HEAD") + standardOutput = this@apply + } + }.toString().trim() + properties.setProperty("commit", commit) + // Write file + val out = FileOutputStream(propertiesFile) + properties.store(out, null) } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ffed3a2..d26fed9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ +# https://gradle.org/releases/ +# https://docs.gradle.org/current/userguide/compatibility.html distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew old mode 100755 new mode 100644 diff --git a/gradlew.bat b/gradlew.bat index 107acd3..e69de29 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,89 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/pig/build.gradle.kts b/pig/build.gradle.kts index 2d34215..ac62375 100644 --- a/pig/build.gradle.kts +++ b/pig/build.gradle.kts @@ -12,68 +12,37 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import java.io.FileOutputStream -import java.util.Properties plugins { - id("application") - id("pig.conventions") - id("org.partiql.pig.gradle.publish") -} - -publish { - artifactId = "pig" - name = "PartiQL I.R. Generator (a.k.a P.I.G.)" -} - -val propertiesDir = "$buildDir/properties" - -dependencies { - implementation("org.freemarker:freemarker:2.3.30") - implementation("net.sf.jopt-simple:jopt-simple:5.0.4") - implementation("com.amazon.ion:ion-element:1.0.0") -} - -application { - mainClass.set("org.partiql.pig.MainKt") -} - -tasks.jar { - manifest { - attributes["Main-Class"] = "org.partiql.pig.MainKt" - } - from( - configurations.compile.get().map { - if (it.isDirectory) { - it - } else { - zipTree(it) - } - } - ) -} - -tasks.register("generateProperties") { - doLast { - val propertiesFile = file("$propertiesDir/pig.properties") - propertiesFile.parentFile.mkdirs() - val properties = Properties() - properties.setProperty("version", version.toString()) - val out = FileOutputStream(propertiesFile) - properties.store(out, null) - } -} - -tasks.named("processResources") { - dependsOn("generateProperties") -} - -sourceSets { - main { - output.dir(propertiesDir) - } -} - -tasks.build { - finalizedBy(tasks.installDist) -} + id(Pig_conventions_gradle.Plugins.conventions) + // id(Plugins.application) + // id(Plugins.publish) +} + +// dependencies { +// implementation(Deps.dotlin) +// implementation(Deps.ionElement) +// implementation(Deps.kasechange) +// implementation(Deps.kotlinPoet) +// implementation(Deps.picoCli) +// } +// +// application { +// applicationName = "pig" +// mainClass.set("org.partiql.pig.PigKt") +// } +// +// distributions { +// main { +// distributionBaseName.set("pig") +// } +// } +// +// tasks.register("install") { +// tasks = listOf("assembleDist", "distZip", "installDist") +// } +// +// publish { +// artifactId = "pig" +// name = "PartiQL I.R. Generator (a.k.a P.I.G.)" +// } diff --git a/pig/src/main/kotlin/org/partiql/pig/Pig.kt b/pig/src/main/kotlin/org/partiql/pig/Pig.kt new file mode 100644 index 0000000..f965265 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/Pig.kt @@ -0,0 +1,47 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package org.partiql.pig + +import org.partiql.pig.generator.target.kotlin.KotlinCommand +import picocli.CommandLine +import kotlin.system.exitProcess + +fun main(args: Array) { + val command = CommandLine(Sprout()) + exitProcess(command.execute(*args)) +} + +@CommandLine.Command( + name = "sprout", + mixinStandardHelpOptions = true, + subcommands = [Generate::class], +) +class Sprout : Runnable { + + override fun run() {} +} + +@CommandLine.Command( + name = "generate", + mixinStandardHelpOptions = true, + subcommands = [ + KotlinCommand::class, + ], +) +class Generate : Runnable { + + override fun run() {} +} diff --git a/pig/src/main/kotlin/org/partiql/pig/errors/Exceptions.kt b/pig/src/main/kotlin/org/partiql/pig/errors/Exceptions.kt index 73a0f95..c1f3d4f 100644 --- a/pig/src/main/kotlin/org/partiql/pig/errors/Exceptions.kt +++ b/pig/src/main/kotlin/org/partiql/pig/errors/Exceptions.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.errors +package org.partiql.pig.legacy.errors class PigException(val error: PigError, cause: Throwable? = null) : Exception(error.toString(), cause) diff --git a/pig/src/main/kotlin/org/partiql/pig/errors/PigError.kt b/pig/src/main/kotlin/org/partiql/pig/errors/PigError.kt index 8d08784..9f95c81 100644 --- a/pig/src/main/kotlin/org/partiql/pig/errors/PigError.kt +++ b/pig/src/main/kotlin/org/partiql/pig/errors/PigError.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.errors +package org.partiql.pig.legacy.errors import com.amazon.ionelement.api.IonLocation import com.amazon.ionelement.api.locationToString diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/Generator.kt b/pig/src/main/kotlin/org/partiql/pig/generator/Generator.kt new file mode 100644 index 0000000..07b0548 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/Generator.kt @@ -0,0 +1,26 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package org.partiql.pig.generator + +import org.partiql.pig.model.Universe + +/** + * Interface for a Sprout generator + */ +interface Generator { + + fun generate(universe: Universe): T +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/dot/.gitkeep b/pig/src/main/kotlin/org/partiql/pig/generator/target/dot/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinCommand.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinCommand.kt new file mode 100644 index 0000000..c449a36 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinCommand.kt @@ -0,0 +1,81 @@ +package org.partiql.pig.generator.target.kotlin + +import org.partiql.pig.parser.SproutParser +import picocli.CommandLine +import picocli.CommandLine.Command +import java.io.BufferedReader +import java.io.File +import java.io.FileInputStream +import java.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.Callable + +@CommandLine.Command( + name = "kotlin", + mixinStandardHelpOptions = true, + description = ["Generates Kotlin sources from type universe definitions"] +) +class KotlinCommand : Callable { + + @CommandLine.Parameters( + index = "0", + description = ["Type definition file"] + ) + lateinit var file: File + + @CommandLine.Option( + names = ["-p", "--package"], + description = ["Package root"] + ) + lateinit var packageRoot: String + + @CommandLine.Option( + names = ["-u", "--universe"], + description = ["Universe identifier"] + ) + lateinit var id: String + + @CommandLine.Option( + names = ["-o", "--out"], + description = ["Generated source output directory"] + ) + lateinit var out: Path + + @CommandLine.Option( + names = ["--poems"], + description = ["Poem templates to apply"], + ) + var poems: List = emptyList() + + @CommandLine.Option( + names = ["-m", "--modifier"], + description = ["Generated node class modifier. Options \${COMPLETION-CANDIDATES}"], + defaultValue = "DATA" + ) + lateinit var modifier: KotlinNodeOptions.Modifier + + override fun call(): Int { + val input = BufferedReader(FileInputStream(file).reader()).readText() + val parser = SproutParser.default() + val universe = parser.parse(id, input) + val options = KotlinOptions( + packageRoot = packageRoot, + poems = poems, + node = KotlinNodeOptions( + modifier = modifier, + ), + ) + val generator = KotlinGenerator(options) + val result = generator.generate(universe) + // Write all generated files + result.write { + val p = it.packageName.replace(".", "/") + val dir = out.resolve(p).toAbsolutePath() + Files.createDirectories(dir) + val file = Files.newBufferedWriter(dir.resolve("${it.name}.kt")) + it.writeTo(file) + file.close() + } + return 0 + } +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinGenerator.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinGenerator.kt new file mode 100644 index 0000000..b167c4f --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinGenerator.kt @@ -0,0 +1,142 @@ +package org.partiql.pig.generator.target.kotlin + +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import net.pearx.kasechange.toCamelCase +import org.partiql.pig.generator.Generator +import org.partiql.pig.generator.target.kotlin.poems.KotlinBuilderPoem +import org.partiql.pig.generator.target.kotlin.poems.KotlinJacksonPoem +import org.partiql.pig.generator.target.kotlin.poems.KotlinListenerPoem +import org.partiql.pig.generator.target.kotlin.poems.KotlinMetasPoem +import org.partiql.pig.generator.target.kotlin.poems.KotlinVisitorPoem +import org.partiql.pig.generator.target.kotlin.spec.KotlinFileSpec +import org.partiql.pig.generator.target.kotlin.spec.KotlinNodeSpec +import org.partiql.pig.generator.target.kotlin.spec.KotlinUniverseSpec +import org.partiql.pig.model.TypeDef +import org.partiql.pig.model.TypeProp +import org.partiql.pig.model.Universe + +/** + * Generates and applies + */ +class KotlinGenerator(private val options: KotlinOptions) : Generator { + + override fun generate(universe: Universe): KotlinResult { + + // --- Initialize an empty symbol table(?) + val symbols = KotlinSymbols.init(universe, options) + + // Still not sure if poems should be registered with an injector + // This is sufficient for now + val poems = options.poems.map { + when (it) { + "visitor" -> KotlinVisitorPoem(symbols) + "builder" -> KotlinBuilderPoem(symbols) + "listener" -> KotlinListenerPoem(symbols) + "metas" -> KotlinMetasPoem(symbols) + "jackson" -> KotlinJacksonPoem(symbols) + else -> error("unknown poem $it, expected: visitor, builder, listener, metas, jackson") + } + } + + // --- Generate skeleton + val spec = KotlinUniverseSpec( + universe = universe, + nodes = universe.nodes(symbols), + base = TypeSpec.classBuilder(symbols.base).addModifiers(KModifier.ABSTRACT), + types = universe.types(symbols) + ) + val specs = with(spec) { + // Apply each poem + poems.forEach { it.apply(this) } + // Finalize each spec/builder + build(options.packageRoot).map { KotlinFileSpec(it) } + } + return KotlinResult(specs) + } + + // --- Internal ----------------------------------- + + /** + * Generate a NodeSpec for each Type in the given Universe + */ + private fun Universe.nodes(symbols: KotlinSymbols): List = types.mapNotNull { it.generate(symbols) } + .map { + it.builder.superclass(symbols.base) + it + } + + /** + * Generate all top-level enums as these are not children of a product definition + */ + private fun Universe.types(symbols: KotlinSymbols) = types.filterIsInstance() + .map { it.generate(symbols) } + .toMutableList() + + /** + * Entry point for node generation. + */ + private fun TypeDef.generate(symbols: KotlinSymbols): KotlinNodeSpec? = when (this) { + is TypeDef.Product -> this.generate(symbols) + is TypeDef.Sum -> this.generate(symbols) + is TypeDef.Enum -> null // enums are constants, not nodes + } + + /** + * Product Node Generation + */ + private fun TypeDef.Product.generate(symbols: KotlinSymbols) = KotlinNodeSpec.Product( + product = this, + props = props.map { KotlinNodeSpec.Prop(it.name.toCamelCase(), symbols.typeNameOf(it.ref)) }, + nodes = children.mapNotNull { it.generate(symbols) }, + clazz = symbols.clazz(ref), + ext = (props.enumProps(symbols) + types.enums(symbols)).toMutableList(), + ).apply { + props.forEach { + val para = ParameterSpec.builder(it.name, it.type).build() + val prop = PropertySpec.builder(it.name, it.type).initializer(it.name).build() + builder.addProperty(prop) + constructor.addParameter(para) + } + when (options.node.modifier) { + KotlinNodeOptions.Modifier.FINAL -> {} + KotlinNodeOptions.Modifier.DATA -> if (props.isNotEmpty()) builder.addModifiers(KModifier.DATA) + KotlinNodeOptions.Modifier.OPEN -> builder.addModifiers(KModifier.OPEN) + } + nodes.forEach { it.builder.superclass(symbols.base) } + } + + /** + * Sum node generation + */ + private fun TypeDef.Sum.generate(symbols: KotlinSymbols) = KotlinNodeSpec.Sum( + sum = this, + variants = variants.mapNotNull { it.generate(symbols) }, + nodes = types.mapNotNull { it.generate(symbols) }, + clazz = symbols.clazz(ref), + ext = types.enums(symbols).toMutableList(), + ).apply { + variants.forEach { it.builder.superclass(clazz) } + nodes.forEach { it.builder.superclass(symbols.base) } + } + + /** + * Enum constant generation + */ + private fun TypeDef.Enum.generate(symbols: KotlinSymbols) = TypeSpec.enumBuilder(symbols.clazz(ref)) + .apply { values.forEach { addEnumConstant(it) } } + .build() + + private fun List.enumProps(symbols: KotlinSymbols) = filterIsInstance().mapNotNull { + when (it.def) { + is TypeDef.Enum -> it.def.generate(symbols) + else -> null + } + } + + private fun List.enums(symbols: KotlinSymbols) = filterIsInstance().map { + it.generate(symbols) + } +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinOptions.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinOptions.kt new file mode 100644 index 0000000..1b25a66 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinOptions.kt @@ -0,0 +1,26 @@ +package org.partiql.pig.generator.target.kotlin + +/** + * Generator options are entirely independent of the type definitions + * + * @property packageRoot + */ +class KotlinOptions( + val packageRoot: String, + val poems: List, + val node: KotlinNodeOptions = KotlinNodeOptions(), +) + +/** + * Consider other options as this is Kotlin specific + */ +class KotlinNodeOptions( + val modifier: Modifier = Modifier.FINAL, +) { + + enum class Modifier { + FINAL, + DATA, + OPEN, + } +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinPoem.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinPoem.kt new file mode 100644 index 0000000..d6d3842 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinPoem.kt @@ -0,0 +1,28 @@ +package org.partiql.pig.generator.target.kotlin + +import org.partiql.pig.generator.target.kotlin.spec.KotlinNodeSpec +import org.partiql.pig.generator.target.kotlin.spec.KotlinUniverseSpec + +abstract class KotlinPoem(val symbols: KotlinSymbols) { + + abstract val id: String + + open fun apply(universe: KotlinUniverseSpec) { + universe.nodes.forEach { apply(it) } + } + + open fun apply(node: KotlinNodeSpec) { + when (node) { + is KotlinNodeSpec.Product -> apply(node) + is KotlinNodeSpec.Sum -> apply(node) + } + } + + open fun apply(node: KotlinNodeSpec.Product) { + node.children.forEach { apply(it) } + } + + open fun apply(node: KotlinNodeSpec.Sum) { + node.children.forEach { apply(it) } + } +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinResult.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinResult.kt new file mode 100644 index 0000000..c2f2107 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinResult.kt @@ -0,0 +1,8 @@ +package org.partiql.pig.generator.target.kotlin + +import org.partiql.pig.generator.target.kotlin.spec.KotlinFileSpec + +class KotlinResult(private val specs: List) { + + fun write(action: (KotlinFileSpec) -> Unit) = specs.forEach(action) +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinSymbols.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinSymbols.kt new file mode 100644 index 0000000..dba3300 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinSymbols.kt @@ -0,0 +1,201 @@ +package org.partiql.pig.generator.target.kotlin + +import com.squareup.kotlinpoet.BOOLEAN +import com.squareup.kotlinpoet.BYTE_ARRAY +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.DOUBLE +import com.squareup.kotlinpoet.FLOAT +import com.squareup.kotlinpoet.INT +import com.squareup.kotlinpoet.LIST +import com.squareup.kotlinpoet.LONG +import com.squareup.kotlinpoet.MAP +import com.squareup.kotlinpoet.MUTABLE_LIST +import com.squareup.kotlinpoet.MUTABLE_MAP +import com.squareup.kotlinpoet.MUTABLE_SET +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.SET +import com.squareup.kotlinpoet.STRING +import com.squareup.kotlinpoet.TypeName +import net.pearx.kasechange.toCamelCase +import net.pearx.kasechange.toPascalCase +import org.partiql.pig.model.ScalarType +import org.partiql.pig.model.TypeDef +import org.partiql.pig.model.TypeRef +import org.partiql.pig.model.Universe + +/** + * A lookup for type references in code generation + * + * @property universe + * @property options + * + * TODO consider thread safe memoization — that was removed because of iterating the refs while updating the backing map + */ +class KotlinSymbols private constructor( + private val universe: Universe, + private val options: KotlinOptions, +) { + + /** + * Kotlin/Java package name + */ + val rootPackage = options.packageRoot + + /** + * Universe identifier often prefixed to generated classes + */ + val rootId = universe.id.toPascalCase() + + /** + * Base node for the Universe + */ + val base: ClassName = ClassName(rootPackage, "${rootId}Node") + + /** + * Memoize converting a TypeRef.Path to a camel case identifier to be used as method/function names + */ + private val camels: MutableMap = mutableMapOf() + + /** + * Memoize converting a TypeRef.Path to a pascal case identifier + */ + private val pascals: MutableMap = mutableMapOf() + + /** + * Map all type references back to their definitions. Use `id` as to not include `nullable` in the key `equals` + */ + private val defs: Map by lazy { + val d = mutableMapOf() + universe.forEachType { d[it.ref.id] = it } + d + } + + companion object { + + /** + * Named constructor somewhat hints at the initial emptiness (when memoized) of the symbol table + */ + fun init(universe: Universe, options: KotlinOptions): KotlinSymbols = KotlinSymbols(universe, options) + } + + /** + * Returns the base ClassName for the given TypeRef.Path + */ + fun clazz(ref: TypeRef.Path) = ClassName( + packageName = rootPackage, + simpleNames = ref.path.map { it.toPascalCase() } + ) + + /** + * Returns a camel-case representation of the path. + * - This causes naming conflicts, i.e. the path `a_b` conflicts with the path `a.b` + * - I'd prefer to start with this naive approach + */ + fun camel(ref: TypeRef.Path): String = camels.computeIfAbsent(ref) { + ref.path.joinToString("_").toCamelCase() + } + + /** + * Again, this causes naming conflicts for names like `a_b` and `a.b` + */ + fun pascal(ref: TypeRef.Path): String = pascals.computeIfAbsent(ref) { + ref.path.joinToString("_").toPascalCase() + } + + // fun def(ref: TypeRef.Path): TypeDef = defs[ref.id] ?: error("no definition found for type `$ref`") + fun def(ref: TypeRef.Path): TypeDef { + val def = defs[ref.id] + if (def == null) { + error("no definition found for type `$ref`") + } else { + return def + } + } + + /** + * Computes a type name for the given [TypeRef] + */ + fun typeNameOf(ref: TypeRef, mutable: Boolean = false): TypeName = when (ref) { + is TypeRef.Scalar -> typeNameOf(ref) + is TypeRef.Path -> clazz(ref) + is TypeRef.List -> typeNameOf(ref, mutable) + is TypeRef.Set -> typeNameOf(ref, mutable) + is TypeRef.Map -> typeNameOf(ref, mutable) + is TypeRef.Import -> import(ref.symbol) + }.copy(ref.nullable) + + // --- Internal ------------------------------- + + private fun typeNameOf(ref: TypeRef.Scalar) = when (ref.type) { + ScalarType.BOOL -> BOOLEAN + ScalarType.INT -> INT + ScalarType.LONG -> LONG + ScalarType.FLOAT -> FLOAT + ScalarType.DOUBLE -> DOUBLE + ScalarType.BYTES -> BYTE_ARRAY + ScalarType.STRING -> STRING + } + + private fun typeNameOf(ref: TypeRef.List, mutable: Boolean = false): TypeName { + val t = typeNameOf(ref.type, mutable) + val list = if (mutable) MUTABLE_LIST else LIST + return list.parameterizedBy(t) + } + + private fun typeNameOf(ref: TypeRef.Set, mutable: Boolean = false): TypeName { + val t = typeNameOf(ref.type, mutable) + val set = if (mutable) MUTABLE_SET else SET + return set.parameterizedBy(t) + } + + private fun typeNameOf(ref: TypeRef.Map, mutable: Boolean = false): TypeName { + val kt = typeNameOf(ref.keyType) + val vt = typeNameOf(ref.valType) + val map = if (mutable) MUTABLE_MAP else MAP + return map.parameterizedBy(kt, vt) + } + + /** + * Determine the appropriate mapping method from a JsonNode to the Kotlin value; this could certainly be improved. + * https://fasterxml.github.io/jackson-databind/javadoc/2.7/com/fasterxml/jackson/databind/JsonNode.html + */ + fun valueMapping(ref: TypeRef, v: String): String = when (ref) { + is TypeRef.List -> "$v.map { n -> ${valueMapping(ref.type, "n")} }" + is TypeRef.Map -> "$v.fields().asSequence().associate { e -> e.key to ${valueMapping(ref.valType, "e.value")} }" + is TypeRef.Set -> "$v.map { n -> ${valueMapping(ref.type, "n")} }.toSet()" + is TypeRef.Scalar -> when (ref.type) { + ScalarType.BOOL -> "$v.asBoolean()" + ScalarType.INT -> "$v.asInt()" + ScalarType.LONG -> "$v.asLong()" + ScalarType.FLOAT -> "$v.floatValue()" + ScalarType.DOUBLE -> "$v.asDouble()" + ScalarType.BYTES -> "$v.binaryValue()" + ScalarType.STRING -> "$v.asText()" + } + is TypeRef.Path -> { + when (def(ref)) { + is TypeDef.Enum -> "${clazz(ref).canonicalName}.valueOf($v.asText().uppercase())" + is TypeDef.Product, + is TypeDef.Sum -> "_${camel(ref)}($v)" + } + } + // invoke the default deserializer + is TypeRef.Import -> "ctxt.readValue($v, ${import(ref.symbol).canonicalName}.javaClass)" + } + + /** + * Parse the ClassLoader string to a KotlinPoet ClassName. Could improve error handling here.. + */ + private fun import(symbol: String): ClassName { + if (!universe.imports.containsKey("kotlin")) { + error("Missing `kotlin` target from imports") + } else if (!universe.imports["kotlin"]!!.containsKey(symbol)) { + error("Missing `kotlin` target for `$symbol` in imports") + } + val path = universe.imports["kotlin"]!![symbol]!! + val i = path.lastIndexOf(".") + val packageName = path.substring(0, i) + val simpleNames = path.substring(i + 1).split("$") + return ClassName(packageName, simpleNames) + } +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinBuilderPoem.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinBuilderPoem.kt new file mode 100644 index 0000000..c91b61c --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinBuilderPoem.kt @@ -0,0 +1,235 @@ +package org.partiql.pig.generator.target.kotlin.poems + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.LambdaTypeName +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.TypeVariableName +import com.squareup.kotlinpoet.asTypeName +import net.pearx.kasechange.toCamelCase +import net.pearx.kasechange.toPascalCase +import org.partiql.pig.generator.target.kotlin.KotlinPoem +import org.partiql.pig.generator.target.kotlin.KotlinSymbols +import org.partiql.pig.generator.target.kotlin.spec.KotlinNodeSpec +import org.partiql.pig.generator.target.kotlin.spec.KotlinPackageSpec +import org.partiql.pig.generator.target.kotlin.spec.KotlinUniverseSpec +import org.partiql.pig.generator.target.kotlin.types.Annotations +import org.partiql.pig.model.TypeRef + +/** + * Poem which creates a DSL for instantiation + */ +class KotlinBuilderPoem(symbols: KotlinSymbols) : KotlinPoem(symbols) { + + override val id: String = "builder" + + private val builderPackageName = "${symbols.rootPackage}.builder" + + // Abstract factory which can be used by DSL blocks + private val factoryName = "${symbols.rootId}Factory" + private val factoryClass = ClassName(builderPackageName, factoryName) + private val factory = TypeSpec.classBuilder(factoryClass).addModifiers(KModifier.ABSTRACT) + private val factoryParamDefault = ParameterSpec.builder("factory", factoryClass) + .defaultValue("%T.DEFAULT", factoryClass) + .build() + + // Java style builders, used by the DSL + private val buildersName = "${symbols.rootId}Builders" + private val buildersFile = FileSpec.builder(builderPackageName, buildersName) + + // Top-Level DSL holder, so that was close on the factory + private val dslName = "${symbols.rootId}Builder" + private val dslClass = ClassName(builderPackageName, dslName) + private val dslSpec = TypeSpec.classBuilder(dslClass) + .addProperty( + PropertySpec.builder("factory", factoryClass) + .addModifiers(KModifier.PRIVATE) + .initializer("factory") + .build() + ) + .primaryConstructor(FunSpec.constructorBuilder().addParameter(factoryParamDefault).build()) + + // T : FooNode + private val boundedT = TypeVariableName("T", symbols.base) + + // Static top-level entry point for DSL + private val dslFunc = FunSpec.builder(symbols.rootId.toCamelCase()) + .addTypeVariable(boundedT) + .addParameter(factoryParamDefault) + .addParameter( + ParameterSpec.builder( + "block", + LambdaTypeName.get( + receiver = dslClass, + returnType = boundedT, + ) + ).build() + ) + .addStatement("return %T(factory).block()", dslClass) + .build() + + // Static companion object entry point for factory, similar to PIG "build" + private val factoryFunc = FunSpec.builder("create") + .addAnnotation(Annotations.jvmStatic) + .addTypeVariable(boundedT) + .addParameter( + ParameterSpec.builder( + "block", + LambdaTypeName.get( + receiver = factoryClass, + returnType = boundedT, + ) + ).build() + ) + .addStatement("return %T.DEFAULT.block()", factoryClass) + .build() + + override fun apply(universe: KotlinUniverseSpec) { + super.apply(universe) + universe.packages.add( + KotlinPackageSpec( + name = builderPackageName, + files = mutableListOf( + // Factory + FileSpec.builder(builderPackageName, factoryName) + .addType(factory.addType(factoryCompanion()).build()) + .build(), + // Java Builders + buildersFile.build(), + // DSL + FileSpec.builder(builderPackageName, dslName) + .addFunction(dslFunc) + .addType(dslSpec.build()) + .build(), + ) + ) + ) + } + + override fun apply(node: KotlinNodeSpec.Product) { + // Simple `create` functions + factory.addFunction( + FunSpec.builder(symbols.camel(node.product.ref)) + .addModifiers(KModifier.OPEN) + .apply { + node.props.forEach { addParameter(it.name, it.type) } + addStatement("return %T(${node.props.joinToString { it.name }})", node.clazz) + } + .build() + ) + // DSL Receiver and Function + val (builder, func) = node.builderToFunc() + buildersFile.addType(builder) + dslSpec.addFunction(func) + super.apply(node) + } + + // --- Internal ------------------- + + /** + * Returns a Pair of the Java builder and the Kotlin builder receiver function + * This could be split for clarity, but it could be repetitive. + */ + private fun KotlinNodeSpec.Product.builderToFunc(): Pair { + // Java Builder, empty constructor + val builderName = symbols.camel(product.ref) + val builderType = ClassName(builderPackageName, "${builderName.toPascalCase()}Builder") + val builder = TypeSpec.classBuilder(builderType) + + // DSL Function + val funcDsl = FunSpec.builder(builderName).returns(clazz) + funcDsl.addStatement("val builder = %T()", builderType) + + // Java builder `build(factory: Factory = DEFAULT): T` + val funcBuild = FunSpec.builder("build").addParameter(factoryParamDefault).returns(clazz) + val args = mutableListOf() + + companion.addFunction( + FunSpec.builder("builder") + .addAnnotation(Annotations.jvmStatic) + .returns(builderType) + .addStatement("return %T()", builderType) + .build() + ) + + // Add all props to Java builder, DSL function, and Factory call + product.props.forEachIndexed { i, it -> + var type = symbols.typeNameOf(it.ref, mutable = true) + val name = props[i].name + val default = when (it.ref) { + is TypeRef.List -> "mutableListOf()" + is TypeRef.Set -> "mutableSetOf()" + is TypeRef.Map -> "mutableMapOf()" + else -> { + type = type.copy(nullable = true) + "null" + } + } + // t: T = default + val para = ParameterSpec.builder(name, type).build() + funcDsl.addParameter(para.toBuilder().defaultValue(default).build()) + // public var t: T + val prop = PropertySpec.builder(name, type).initializer(default).mutable().build() + builder.addProperty(prop) + + // Fluent builder method, only setters for now, can add collection manipulation later + builder.addFunction( + FunSpec.builder(name) + .returns(builderType) + .addParameter(para) + .beginControlFlow("return this.apply") + .addStatement("this.%N = %N", para, para) + .endControlFlow() + .build() + ) + + // Add parameter to `build(factory: Factory =)` me + val assertion = if (!it.ref.nullable && default == "null") "!!" else "" + args += "$name = $name$assertion" + } + + // Add block as last parameter + funcDsl.addParameter( + ParameterSpec.builder( + "block", + LambdaTypeName.get( + receiver = builderType, + returnType = Unit::class.asTypeName() + ) + ) + .defaultValue("{}") + .build() + ) + + // End of factory.foo call + funcBuild.addStatement("return factory.$builderName(${args.joinToString()})") + + // Finalize Java builder + builder.addFunction( + FunSpec.builder("build") + .returns(clazz) + .addStatement("return build(%T.DEFAULT)", factoryClass) + .build() + ) + builder.addFunction(funcBuild.build()) + + // Finalize DSL function + funcDsl.addStatement("builder.block()") + funcDsl.addStatement("return builder.build(factory)") + + return Pair(builder.build(), funcDsl.build()) + } + + private fun factoryCompanion() = TypeSpec.companionObjectBuilder() + .addProperty( + PropertySpec.builder("DEFAULT", factoryClass) + .initializer("object : %T() {}", factoryClass) + .build() + ) + .addFunction(factoryFunc) + .build() +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinJacksonPoem.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinJacksonPoem.kt new file mode 100644 index 0000000..f29c000 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinJacksonPoem.kt @@ -0,0 +1,227 @@ +package org.partiql.pig.generator.target.kotlin.poems + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.NOTHING +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.STRING +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.TypeVariableName +import org.partiql.pig.generator.target.kotlin.KotlinPoem +import org.partiql.pig.generator.target.kotlin.KotlinSymbols +import org.partiql.pig.generator.target.kotlin.spec.KotlinNodeSpec +import org.partiql.pig.generator.target.kotlin.spec.KotlinPackageSpec +import org.partiql.pig.generator.target.kotlin.spec.KotlinUniverseSpec +import org.partiql.pig.generator.target.kotlin.types.JacksonTypes +import org.partiql.pig.generator.target.kotlin.types.Parameters + +/** + * Poem for Jackson Databind + */ +class KotlinJacksonPoem(symbols: KotlinSymbols) : KotlinPoem(symbols) { + + override val id: String = "jackson" + + private val databindPackageName = "${symbols.rootPackage}.databind" + + // Consider making a separate Jackson+Builder poem; this is not an ideal solution + private val factoryName = ClassName("${symbols.rootPackage}.builder", "${symbols.rootId}Factory") + + private val moduleName = "${symbols.rootId}Module" + private val moduleClass = ClassName(databindPackageName, moduleName) + private val module = TypeSpec.classBuilder(moduleClass) + .superclass(JacksonTypes.Databind.simpleModule) + .addProperty( + PropertySpec.builder("factory", factoryName) + .addModifiers(KModifier.PRIVATE) + .initializer("factory") + .build() + ) + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameter( + ParameterSpec.builder("factory", factoryName) + .defaultValue("%T.DEFAULT", factoryName) + .build() + ) + .build() + ) + + private val inheritedProps = mutableSetOf() + + private val mappingInterfaceName = moduleClass.nestedClass("Mapping") + private val mappingInterface = TypeSpec.interfaceBuilder(mappingInterfaceName) + .addModifiers(KModifier.PRIVATE, KModifier.FUN) + .addTypeVariable(TypeVariableName("T", listOf(symbols.base), KModifier.OUT)) + .addFunction( + FunSpec.builder("invoke") + .returns(Parameters.T) + .addModifiers(KModifier.OPERATOR, KModifier.ABSTRACT) + .addParameter("node", JacksonTypes.Databind.jsonNode) + .build() + ) + .build() + private val initBlock = CodeBlock.builder() + + override fun apply(universe: KotlinUniverseSpec) { + // Jackson will serialize inherited properties; collect and ignore these before descending + universe.base.propertySpecs.forEach { inheritedProps.add(it.name) } + universe.addBaseDeserializer() + super.apply(universe) + module.addHelpers() + module.addInitializerBlock(initBlock.build()) + universe.packages.add( + KotlinPackageSpec( + name = databindPackageName, + files = mutableListOf( + FileSpec.builder(databindPackageName, moduleName) + .addType(module.build()) + .build() + ) + ) + ) + } + + override fun apply(node: KotlinNodeSpec.Product) { + + // --- Serialization + + // Ignore all properties not in the type definition, with the caveat that the Jackson poem must be last + val allProps = inheritedProps + node.builder.propertySpecs.map { it.name }.toSet() + val definedProps = node.props.map { it.name }.toSet() + val extraneous = allProps - definedProps + if (extraneous.isNotEmpty()) { + node.builder.addAnnotation(JacksonTypes.Annotation.ignore(extraneous)) + } + // Preserve the definition's path without a custom serializer + val cairn = "_id" + node.builder.addProperty( + PropertySpec.builder(cairn, STRING) + .addModifiers(KModifier.PRIVATE) + .addAnnotation(JacksonTypes.Annotation.property(cairn)) + .initializer("%S", node.product.ref.id) + .build() + ) + node.builder.addAnnotation(JacksonTypes.Annotation.order(cairn)) + + // --- Deserialization + node.addDeserializer { + val method = symbols.camel(node.product.ref) + addStatement("factory.$method(") + node.props.forEachIndexed { i, prop -> + val name = prop.name + val ref = node.product.props[i].ref + addStatement("$name = ${symbols.valueMapping(ref, "it[\"$name\"]")},") + } + addStatement(")") + } + super.apply(node) + } + + override fun apply(node: KotlinNodeSpec.Sum) { + node.addDeserializer { + beginControlFlow("when (val id = it.id())") + node.variants.forEach { + addStatement("%S -> _${symbols.camel(it.def.ref)}(it)", it.def.ref.id) + } + addStatement("else -> err(id)") + endControlFlow() + } + super.apply(node) + } + + private fun KotlinNodeSpec.addDeserializer(mapping: CodeBlock.Builder.() -> Unit) { + val method = symbols.camel(def.ref) + // Add node mapping function + module.addProperty( + PropertySpec.Companion.builder("_$method", mappingInterfaceName.parameterizedBy(clazz)) + .addModifiers(KModifier.PRIVATE) + .initializer( + CodeBlock.builder() + .beginControlFlow("Mapping") + .apply(mapping) + .endControlFlow() + .build() + ) + .build() + ) + // Register deserializer + initBlock.addStatement("addDeserializer(%T::class.java, map(_$method))", clazz) + } + + private fun TypeSpec.Builder.addHelpers() { + addFunction( + FunSpec.builder("id") + .addModifiers(KModifier.PRIVATE) + .receiver(JacksonTypes.Databind.jsonNode) + .returns(STRING) + .addStatement("return get(%S).asText()", "_id") + .build() + ) + addFunction( + FunSpec.builder("err") + .addModifiers(KModifier.INLINE, KModifier.PRIVATE) + .addParameter("id", STRING) + .returns(NOTHING) + .addStatement("return error(%P)", "no deserializer registered for _id `\$id`") + .build() + ) + + val deserializer = JacksonTypes.Databind.jsonDeserializer.parameterizedBy(Parameters.T) + addFunction( + FunSpec.builder("map") + .addModifiers(KModifier.PRIVATE) + .addTypeVariable(TypeVariableName("T", listOf(symbols.base))) + .addParameter(ParameterSpec("mapping", mappingInterfaceName.parameterizedBy(Parameters.T))) + .returns(deserializer) + .addStatement( + "return %L", + TypeSpec.anonymousClassBuilder() + .superclass(deserializer) + .addFunction( + FunSpec.builder("deserialize") + .addModifiers(KModifier.OVERRIDE) + .addParameter("p", JacksonTypes.Core.jsonParser) + .addParameter("ctxt", JacksonTypes.Databind.deserializationContext) + .returns(Parameters.T) + .addStatement("return mapping(ctxt.readTree(p)!!)") + .build() + ) + .build() + ) + .build() + ) + addType(mappingInterface) + } + + /** + * Map every type definition (except enums) to its class + */ + private fun KotlinUniverseSpec.addBaseDeserializer() { + module.addProperty( + PropertySpec.builder("_base", mappingInterfaceName.parameterizedBy(symbols.base)) + .addModifiers(KModifier.PRIVATE) + .initializer( + CodeBlock.builder() + .beginControlFlow("Mapping") + .beginControlFlow("when (val id = it.id())") + .apply { + forEachNode { + addStatement("%S -> _${symbols.camel(it.def.ref)}(it)", it.def.ref.id) + } + } + .addStatement("else -> err(id)") + .endControlFlow() + .endControlFlow() + .build() + ) + .build() + ) + initBlock.addStatement("addDeserializer(%T::class.java, map(_base))", symbols.base) + } +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinListenerPoem.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinListenerPoem.kt new file mode 100644 index 0000000..07c450d --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinListenerPoem.kt @@ -0,0 +1,121 @@ +package org.partiql.pig.generator.target.kotlin.poems + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.TypeSpec +import org.partiql.pig.generator.target.kotlin.KotlinPoem +import org.partiql.pig.generator.target.kotlin.KotlinSymbols +import org.partiql.pig.generator.target.kotlin.spec.KotlinNodeSpec +import org.partiql.pig.generator.target.kotlin.spec.KotlinPackageSpec +import org.partiql.pig.generator.target.kotlin.spec.KotlinUniverseSpec + +class KotlinListenerPoem(symbols: KotlinSymbols) : KotlinPoem(symbols) { + + override val id = "listener" + + private val listenerPackageName = "${symbols.rootPackage}.listener" + private val baseListenerName = "${symbols.rootId}Listener" + private val baseListenerClass = ClassName(listenerPackageName, baseListenerName) + + private val enter = FunSpec.builder("enter").addParameter("listener", baseListenerClass).build() + private val exit = FunSpec.builder("exit").addParameter("listener", baseListenerClass).build() + + /** + * Defines the open `children` property and the abstract`accept` method on the base node + */ + override fun apply(universe: KotlinUniverseSpec) { + universe.base.addFunction(enter.toBuilder().addModifiers(KModifier.ABSTRACT).build()) + universe.base.addFunction(exit.toBuilder().addModifiers(KModifier.ABSTRACT).build()) + universe.packages.add( + KotlinPackageSpec( + name = listenerPackageName, + files = mutableListOf(universe.listener(), walker()), + ) + ) + super.apply(universe) + } + + override fun apply(node: KotlinNodeSpec) { + val name = node.simpleName() + node.builder.addFunction( + enter.toBuilder() + .addModifiers(KModifier.OVERRIDE) + .addStatement("listener.enter$name(this)") + .build() + ) + node.builder.addFunction( + exit.toBuilder() + .addModifiers(KModifier.OVERRIDE) + .addStatement("listener.exit$name(this)") + .build() + ) + super.apply(node) + } + + // --- Internal ----------------------------------- + + private fun KotlinUniverseSpec.listener(): FileSpec { + val listener = TypeSpec.classBuilder(baseListenerClass) + .addModifiers(KModifier.ABSTRACT) + .apply { + forEachNode { + val name = it.simpleName() + addFunction( + FunSpec.builder("enter$name") + .addModifiers(KModifier.OPEN) + .addParameter("node", it.clazz) + .build() + ) + addFunction( + FunSpec.builder("exit$name") + .addModifiers(KModifier.OPEN) + .addParameter("node", it.clazz) + .build() + ) + } + } + .addFunction( + FunSpec.builder("enterEveryNode") + .addModifiers(KModifier.OPEN) + .addParameter("node", symbols.base) + .build() + ) + .addFunction( + FunSpec.builder("exitEveryNode") + .addModifiers(KModifier.OPEN) + .addParameter("node", symbols.base) + .build() + ) + .build() + return FileSpec.builder(listenerPackageName, baseListenerName) + .addType(listener) + .build() + } + + private fun walker(): FileSpec { + val walkerName = "${symbols.rootId}Walker" + val walkerClass = ClassName(listenerPackageName, walkerName) + val walk = FunSpec.builder("walk") + .addParameter(ParameterSpec("listener", baseListenerClass)) + .addParameter(ParameterSpec("node", symbols.base)) + .apply { + addStatement("listener.enterEveryNode(node)") + addStatement("node.enter(listener)") + addStatement("node.children.forEach { walk(listener, it) }") + addStatement("node.exit(listener)") + addStatement("listener.exitEveryNode(node)") + } + .build() + val walker = TypeSpec.objectBuilder(walkerClass) + .addFunction(walk) + .build() + return FileSpec.builder(listenerPackageName, walkerName) + .addType(walker) + .build() + } + + private fun KotlinNodeSpec.simpleName() = clazz.simpleNames.joinToString("") +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinMetasPoem.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinMetasPoem.kt new file mode 100644 index 0000000..cc60bb4 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinMetasPoem.kt @@ -0,0 +1,25 @@ +package org.partiql.pig.generator.target.kotlin.poems + +import com.squareup.kotlinpoet.ANY +import com.squareup.kotlinpoet.MUTABLE_MAP +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.STRING +import org.partiql.pig.generator.target.kotlin.KotlinPoem +import org.partiql.pig.generator.target.kotlin.KotlinSymbols +import org.partiql.pig.generator.target.kotlin.spec.KotlinUniverseSpec + +class KotlinMetasPoem(symbols: KotlinSymbols) : KotlinPoem(symbols) { + + override val id = "metas" + + // Consider a TypeAlias + private val stringMapOfAny = MUTABLE_MAP.parameterizedBy(STRING, ANY) + + private val metas = PropertySpec.Companion.builder("metadata", stringMapOfAny).initializer("mutableMapOf()").build() + + override fun apply(universe: KotlinUniverseSpec) { + universe.base.addProperty(metas.toBuilder().build()) + super.apply(universe) + } +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinVisitorPoem.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinVisitorPoem.kt new file mode 100644 index 0000000..de4af00 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinVisitorPoem.kt @@ -0,0 +1,247 @@ +package org.partiql.pig.generator.target.kotlin.poems + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.LIST +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import org.partiql.pig.generator.target.kotlin.KotlinPoem +import org.partiql.pig.generator.target.kotlin.KotlinSymbols +import org.partiql.pig.generator.target.kotlin.spec.KotlinNodeSpec +import org.partiql.pig.generator.target.kotlin.spec.KotlinPackageSpec +import org.partiql.pig.generator.target.kotlin.spec.KotlinUniverseSpec +import org.partiql.pig.generator.target.kotlin.types.Parameters +import org.partiql.pig.model.TypeDef +import org.partiql.pig.model.TypeRef + +/** + * Poem which makes nodes traversable via `children` and a `Visitor` + */ +class KotlinVisitorPoem(symbols: KotlinSymbols) : KotlinPoem(symbols) { + + override val id = "visitor" + + private val children = PropertySpec.Companion.builder("children", LIST.parameterizedBy(symbols.base)).build() + + private val visitorPackageName = "${symbols.rootPackage}.visitor" + + // Interface visitor + private val visitorName = "${symbols.rootId}Visitor" + private val visitorClass = ClassName(visitorPackageName, visitorName).parameterizedBy(Parameters.R, Parameters.C) + + // Abstract visitor with default walking + private val baseVisitorName = "${symbols.rootId}BaseVisitor" + + private val accept = FunSpec.builder("accept") + .addTypeVariable(Parameters.R) + .addTypeVariable(Parameters.C) + .addParameter("visitor", visitorClass) + .addParameter("ctx", Parameters.C) + .returns(Parameters.R) + .build() + + private val baseVisit = FunSpec.builder("visit") + .addParameter("node", symbols.base) + .addParameter("ctx", Parameters.C) + .returns(Parameters.R) + .build() + + /** + * Defines the open `children` property and the abstract`accept` method on the base node + */ + override fun apply(universe: KotlinUniverseSpec) { + universe.base.addProperty( + children.toBuilder() + .addModifiers(KModifier.OPEN) + .initializer("emptyList()") + .build() + ) + universe.base.addFunction( + accept.toBuilder() + .addModifiers(KModifier.ABSTRACT) + .build() + ) + universe.packages.add( + KotlinPackageSpec( + name = visitorPackageName, + files = universe.visitors().toMutableList(), + ) + ) + super.apply(universe) + } + + /** + * Overrides `children` and `accept` for this product node + */ + override fun apply(node: KotlinNodeSpec.Product) { + val kids = node.kids() + if (kids != null) { + node.builder.addProperty( + children.toBuilder() + .addModifiers(KModifier.OVERRIDE) + .delegate( + CodeBlock.builder() + .beginControlFlow("lazy") + .add(kids) + .endControlFlow() + .build() + ) + .build() + ) + } + node.builder.addFunction( + accept.toBuilder() + .addModifiers(KModifier.OVERRIDE) + .addStatement("return visitor.%L(this, ctx)", node.product.ref.visitMethodName()) + .build() + ) + super.apply(node) + } + + /** + * Overrides `accept` for this sum node + */ + override fun apply(node: KotlinNodeSpec.Sum) { + node.builder.addFunction( + accept.toBuilder() + .addModifiers(KModifier.OVERRIDE) + .apply { + beginControlFlow("return when (this)") + node.sum.variants.forEach { + addStatement("is %T -> visitor.%L(this, ctx)", symbols.clazz(it.ref), it.ref.visitMethodName()) + } + endControlFlow() + } + .build() + ) + super.apply(node) + } + + // --- Internal ---------------------------------------------------- + + /** + * Returns a CodeBlock which represents a list of all nodes + */ + private fun KotlinNodeSpec.Product.kids(): CodeBlock? { + var n = product.props.size + val isNode: (ref: TypeRef) -> Boolean = { (it is TypeRef.Path) && (symbols.def(it) !is TypeDef.Enum) } + val block = CodeBlock.builder() + .addStatement("val kids = mutableListOf<%T?>()", symbols.base) + .apply { + product.props.forEachIndexed { i, prop -> + val kid = prop.ref + val name = props[i].name + when { + isNode(kid) -> addStatement("kids.add($name)") + (kid is TypeRef.List && isNode(kid.type)) -> addStatement("kids.addAll($name)") + (kid is TypeRef.Set && isNode(kid.type)) -> addStatement("kids.addAll($name)") + else -> n -= 1 + } + } + } + .addStatement("kids.filterNotNull()") + .build() + return if (n != 0) block else null + } + + /** + * Generate all visitors for this universe + */ + private fun KotlinUniverseSpec.visitors(): List = listOf(visitor(), baseVisitor()) + + /** + * Generates the visitor interface for this universe + */ + private fun KotlinUniverseSpec.visitor(): FileSpec { + val visitor = TypeSpec.interfaceBuilder(visitorName) + .addTypeVariable(Parameters.R) + .addTypeVariable(Parameters.C) + .apply { + addFunction(baseVisit.toBuilder().addModifiers(KModifier.ABSTRACT).build()) + forEachNode { + val visit = it.visit().addModifiers(KModifier.ABSTRACT).build() + addFunction(visit) + } + } + .build() + return FileSpec.builder(visitorPackageName, visitorName).addType(visitor).build() + } + + /** + * Generates the base visitor for this universe + */ + private fun KotlinUniverseSpec.baseVisitor(): FileSpec { + val defaultVisit = FunSpec.builder("defaultVisit") + .addModifiers(KModifier.OPEN) + .addParameter(ParameterSpec("node", symbols.base)) + .addParameter(ParameterSpec("ctx", Parameters.C)) + .returns(Parameters.R) + .beginControlFlow("for (child in node.children)") + .addStatement("child.accept(this, ctx)") + .endControlFlow() + .addStatement("return defaultReturn(node, ctx)") + .build() + val defaultReturn = FunSpec.builder("defaultReturn") + .addModifiers(KModifier.ABSTRACT) + .addParameter(ParameterSpec("node", symbols.base)) + .addParameter(ParameterSpec("ctx", Parameters.C)) + .returns(Parameters.R) + .build() + val visitor = TypeSpec.classBuilder(baseVisitorName) + .addSuperinterface(visitorClass) + .addModifiers(KModifier.ABSTRACT) + .addTypeVariable(Parameters.R) + .addTypeVariable(Parameters.C) + .apply { + addFunction( + baseVisit.toBuilder() + .addModifiers(KModifier.OVERRIDE) + .addCode("return node.accept(this, ctx)") + .build() + ) + forEachNode { + addFunction(it.defaultVisit()) + } + } + .addFunction(defaultVisit) + .addFunction(defaultReturn) + .build() + return FileSpec.builder(visitorPackageName, baseVisitorName).addType(visitor).build() + } + + /** + * Visit interface method + */ + private fun KotlinNodeSpec.visit() = FunSpec.builder(def.ref.visitMethodName()) + .addParameter(ParameterSpec("node", clazz)) + .addParameter(ParameterSpec("ctx", Parameters.C)) + .returns(Parameters.R) + + /** + * Visit default method + */ + private fun KotlinNodeSpec.defaultVisit() = visit() + .addModifiers(KModifier.OVERRIDE) + .apply { + when (this@defaultVisit) { + is KotlinNodeSpec.Product -> addStatement("return defaultVisit(node, ctx)") + is KotlinNodeSpec.Sum -> { + beginControlFlow("return when (node)") + sum.variants.forEach { + addStatement("is %T -> %L(node, ctx)", symbols.clazz(it.ref), it.ref.visitMethodName()) + } + endControlFlow() + } + } + }.build() + + /** + * Returns the visit method name of the given TypeRef.Path + */ + private fun TypeRef.Path.visitMethodName() = "visit${symbols.pascal(this)}" +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinFileSpec.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinFileSpec.kt new file mode 100644 index 0000000..88b10f5 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinFileSpec.kt @@ -0,0 +1,35 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package org.partiql.pig.generator.target.kotlin.spec + +import java.io.IOException + +/** + * For now, this just inverts the KotlinPoet FileSpec dependency + */ +class KotlinFileSpec internal constructor( + private val file: com.squareup.kotlinpoet.FileSpec +) { + + val name: String = file.name + + val packageName: String = file.packageName + + @Throws(IOException::class) + fun writeTo(out: Appendable) { + file.writeTo(out) + } +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinNodeSpec.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinNodeSpec.kt new file mode 100644 index 0000000..aab61cd --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinNodeSpec.kt @@ -0,0 +1,114 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package org.partiql.pig.generator.target.kotlin.spec + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec +import org.partiql.pig.model.TypeDef + +/** + * Wraps a [TypeDef] with KotlinPoet builders + * + * @property def TypeDef + * @property clazz Type ClassName + * @property builder Type builder + * @property constructor Implementation constructor + * @property companion A place for static methods on nodes + * @property nodes Node types defined within this node + */ +sealed class KotlinNodeSpec( + val def: TypeDef, + val clazz: ClassName, + val builder: TypeSpec.Builder, + val constructor: FunSpec.Builder, + val companion: TypeSpec.Builder, + val ext: MutableList = mutableListOf(), +) { + + /** + * Nodes defined within this node, but don't inherit from this class + */ + abstract val nodes: List + + /** + * All types defined within this node + */ + abstract val children: List + + /** + * Returns the built Pair + */ + open fun build(): TypeSpec = with(builder) { + primaryConstructor(constructor.build()) + ext.forEach { addType(it) } + children.forEach { addType(it.build()) } + if (companion.propertySpecs.isNotEmpty() || companion.funSpecs.isNotEmpty()) { + addType(companion.build()) + } + build() + } + + /** + * Wraps a [TypeDef.Product] with codegen builders + */ + class Product( + val product: TypeDef.Product, + val props: List, + override val nodes: List, + clazz: ClassName, + ext: MutableList = mutableListOf(), + ) : KotlinNodeSpec( + def = product, + clazz = clazz, + builder = TypeSpec.classBuilder(clazz), + constructor = FunSpec.constructorBuilder(), + companion = TypeSpec.companionObjectBuilder(), + ext = ext, + ) { + override val children: List = nodes + } + + /** + * Wraps a [TypeDef.Sum] with a codegen builders + */ + class Sum( + val sum: TypeDef.Sum, + val variants: List, + override val nodes: List, + clazz: ClassName, + ext: MutableList = mutableListOf(), + ) : KotlinNodeSpec( + def = sum, + clazz = clazz, + builder = TypeSpec.classBuilder(clazz).addModifiers(KModifier.SEALED), + constructor = FunSpec.constructorBuilder(), + companion = TypeSpec.companionObjectBuilder(), + ext = ext, + ) { + override val children: List = variants + nodes + } + + /** + * Derived from a [TypeProp], but replaced the ref with a ClassName + */ + class Prop( + val name: String, + val type: TypeName + ) +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinPackageSpec.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinPackageSpec.kt new file mode 100644 index 0000000..89be096 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinPackageSpec.kt @@ -0,0 +1,29 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package org.partiql.pig.generator.target.kotlin.spec + +import com.squareup.kotlinpoet.FileSpec + +/** + * A place to define a new package within a domain or universe + * + * @property name + * @property files + */ +class KotlinPackageSpec( + val name: String, + val files: MutableList = mutableListOf(), +) diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinUniverseSpec.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinUniverseSpec.kt new file mode 100644 index 0000000..0218c48 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinUniverseSpec.kt @@ -0,0 +1,82 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package org.partiql.pig.generator.target.kotlin.spec + +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.TypeSpec +import org.partiql.pig.model.Universe + +/** + * Wraps a [Universe] with KotlinPoet builders + * + * @property universe Universe definition + * @property nodes Node builders + * @property base Base node builder + * @property packages Additional packages to include in the universe package + * @property types Additional non-node types to include in the universe package + * @property files Additional files to include in the universe package + */ +class KotlinUniverseSpec( + val universe: Universe, + val nodes: List, + val base: TypeSpec.Builder, + val packages: MutableList = mutableListOf(), + val types: MutableList = mutableListOf(), + val files: MutableList = mutableListOf() +) { + + /** + * Build the Kotlin files + * + * + * ├── Types.kt + * ├── ... + * ├── builder + * │ └── Builder.kt + * ├── listener + * │ └── Listener.kt + * └── visitor + * └── Visitor.kt + */ + fun build(root: String): List { + val files = mutableListOf() + val nodes = nodes.map { it.build() } + + // /Types.kt + files += with(FileSpec.builder(root, "Types")) { + addType(base.build()) + nodes.forEach { addType(it) } + types.forEach { addType(it) } + build() + } + + // /.kt + files += this.files + // //... + files += packages.flatMap { it.files } + return files + } + + fun forEachNode(action: (KotlinNodeSpec) -> Unit) { + fun List.applyToAll() { + forEach { + action(it) + it.children.applyToAll() + } + } + nodes.applyToAll() + } +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/Annotations.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/Annotations.kt new file mode 100644 index 0000000..636ae06 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/Annotations.kt @@ -0,0 +1,25 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package org.partiql.pig.generator.target.kotlin.types + +import com.squareup.kotlinpoet.AnnotationSpec + +object Annotations { + + val jvmStatic = AnnotationSpec.builder(JvmStatic::class).build() + + fun suppress(what: String) = AnnotationSpec.builder(Suppress::class).addMember("\"$what\"").build() +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/JacksonTypes.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/JacksonTypes.kt new file mode 100644 index 0000000..4ba7a70 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/JacksonTypes.kt @@ -0,0 +1,69 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package org.partiql.pig.generator.target.kotlin.types + +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName + +object JacksonTypes { + + private const val root = "com.fasterxml.jackson" + + object Core { + + const val packageName = "$root.core" + + val jsonParser = ClassName(packageName, "JsonParser") + } + + object Databind { + + const val packageName = "$root.databind" + + val objectMapper = ClassName(packageName, "ObjectMapper") + + val simpleModule = ClassName("$packageName.module", "SimpleModule") + + val jsonDeserializer = ClassName(packageName, "JsonDeserializer") + + val jsonNode = ClassName(packageName, "JsonNode") + + val deserializationContext = ClassName(packageName, "DeserializationContext") + } + + object Annotation { + + const val packageName = "$root.annotation" + + val ignoreProperties = ClassName(packageName, "JsonIgnoreProperties") + + val property = ClassName(packageName, "JsonProperty") + + val propertyOrder = ClassName(packageName, "JsonPropertyOrder") + + fun ignore(members: Iterable) = AnnotationSpec.builder(ignoreProperties) + .addMember(members.joinToString { "\"$it\"" }) + .build() + + fun property(name: String) = AnnotationSpec.builder(property) + .addMember("%S", name) + .build() + + fun order(vararg members: String) = AnnotationSpec.builder(propertyOrder) + .addMember(members.joinToString { "\"$it\"" }) + .build() + } +} diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/Parameters.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/Parameters.kt new file mode 100644 index 0000000..961b860 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/Parameters.kt @@ -0,0 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package org.partiql.pig.generator.target.kotlin.types + +import com.squareup.kotlinpoet.TypeVariableName + +object Parameters { + + val R = TypeVariableName("R") + + val C = TypeVariableName("C") + + val T = TypeVariableName("T") +} diff --git a/pig/src/main/kotlin/org/partiql/pig/domain/Utils.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/Utils.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/domain/Utils.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/Utils.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/cmdline/Command.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/Command.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/cmdline/Command.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/Command.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/cmdline/CommandLineParser.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/CommandLineParser.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/cmdline/CommandLineParser.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/CommandLineParser.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/cmdline/TargetLanguage.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/TargetLanguage.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/cmdline/TargetLanguage.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/TargetLanguage.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/FreeMarkerUtils.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/FreeMarkerUtils.kt similarity index 98% rename from pig/src/main/kotlin/org/partiql/pig/generator/FreeMarkerUtils.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/FreeMarkerUtils.kt index 6f91247..aeef8f4 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/FreeMarkerUtils.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/FreeMarkerUtils.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.generator +package org.partiql.pig.legacy.generator import freemarker.template.Configuration import freemarker.template.TemplateExceptionHandler diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/IndentDirective.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/IndentDirective.kt similarity index 98% rename from pig/src/main/kotlin/org/partiql/pig/generator/IndentDirective.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/IndentDirective.kt index 7617a78..d5504bf 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/IndentDirective.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/IndentDirective.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.generator +package org.partiql.pig.legacy.generator import freemarker.core.Environment import freemarker.template.TemplateDirectiveBody diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/custom/CTypeDomain.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/CTypeDomain.kt similarity index 98% rename from pig/src/main/kotlin/org/partiql/pig/generator/custom/CTypeDomain.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/CTypeDomain.kt index bf543da..fe44a48 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/custom/CTypeDomain.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/CTypeDomain.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.generator.custom +package org.partiql.pig.legacy.generator.custom import org.partiql.pig.domain.model.Arity import org.partiql.pig.domain.model.DataType diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/custom/CustomFreeMarkerGlobals.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/CustomFreeMarkerGlobals.kt similarity index 94% rename from pig/src/main/kotlin/org/partiql/pig/generator/custom/CustomFreeMarkerGlobals.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/CustomFreeMarkerGlobals.kt index 4bcb38b..0290679 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/custom/CustomFreeMarkerGlobals.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/CustomFreeMarkerGlobals.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.generator.custom +package org.partiql.pig.legacy.generator.custom import java.time.OffsetDateTime diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/custom/generator.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/generator.kt similarity index 96% rename from pig/src/main/kotlin/org/partiql/pig/generator/custom/generator.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/generator.kt index 506d444..42e18d5 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/custom/generator.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/generator.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.generator.custom +package org.partiql.pig.legacy.generator.custom import org.partiql.pig.domain.model.TypeDomain import org.partiql.pig.generator.createDefaultFreeMarkerConfiguration diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/html/generator.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/html/generator.kt similarity index 96% rename from pig/src/main/kotlin/org/partiql/pig/generator/html/generator.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/html/generator.kt index 8a2a84f..400f5fc 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/html/generator.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/html/generator.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.generator.html +package org.partiql.pig.legacy.generator.html import freemarker.template.Template import org.partiql.pig.domain.model.TypeDomain diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/ion/generator.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/ion/generator.kt similarity index 75% rename from pig/src/main/kotlin/org/partiql/pig/generator/ion/generator.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/ion/generator.kt index b144094..053f690 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/ion/generator.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/ion/generator.kt @@ -1,7 +1,7 @@ -package org.partiql.pig.generator.ion +package org.partiql.pig.legacy.generator.ion import org.partiql.pig.domain.model.TypeDomain -import org.partiql.pig.domain.toIonElement +import org.partiql.pig.legacy.domain.toIonElement import java.io.PrintWriter /** diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/kotlin/KTypeDomain.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomain.kt similarity index 98% rename from pig/src/main/kotlin/org/partiql/pig/generator/kotlin/KTypeDomain.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomain.kt index 9f2d3c0..a55f024 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/kotlin/KTypeDomain.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomain.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.generator.kotlin +package org.partiql.pig.legacy.generator.kotlin /* Note a big design consideration for the classes in this file is that they are easy to consume by the diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/kotlin/KTypeDomainConverter.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomainConverter.kt similarity index 99% rename from pig/src/main/kotlin/org/partiql/pig/generator/kotlin/KTypeDomainConverter.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomainConverter.kt index 2cc962f..43d04ae 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/kotlin/KTypeDomainConverter.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomainConverter.kt @@ -19,7 +19,7 @@ * Note that this file should not be throwing any exceptions of any kind. Any error conditions should be * detected by [TypeDomain]'s error checking. */ -package org.partiql.pig.generator.kotlin +package org.partiql.pig.legacy.generator.kotlin import org.partiql.pig.domain.model.Arity import org.partiql.pig.domain.model.DataType diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/kotlin/KotlinCrossDomainFreeMarkerGlobals.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KotlinCrossDomainFreeMarkerGlobals.kt similarity index 88% rename from pig/src/main/kotlin/org/partiql/pig/generator/kotlin/KotlinCrossDomainFreeMarkerGlobals.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KotlinCrossDomainFreeMarkerGlobals.kt index 8ff4497..8ae448e 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/kotlin/KotlinCrossDomainFreeMarkerGlobals.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KotlinCrossDomainFreeMarkerGlobals.kt @@ -1,4 +1,4 @@ -package org.partiql.pig.generator.kotlin +package org.partiql.pig.legacy.generator.kotlin import java.time.OffsetDateTime diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/kotlin/KotlinDomainFreeMarkerGlobals.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KotlinDomainFreeMarkerGlobals.kt similarity index 95% rename from pig/src/main/kotlin/org/partiql/pig/generator/kotlin/KotlinDomainFreeMarkerGlobals.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KotlinDomainFreeMarkerGlobals.kt index 11b2c72..7fbbbbb 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/kotlin/KotlinDomainFreeMarkerGlobals.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KotlinDomainFreeMarkerGlobals.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.generator.kotlin +package org.partiql.pig.legacy.generator.kotlin import java.time.OffsetDateTime diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/kotlin/generator.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/generator.kt similarity index 98% rename from pig/src/main/kotlin/org/partiql/pig/generator/kotlin/generator.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/generator.kt index d2ac1bc..70355cb 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/kotlin/generator.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/generator.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.generator.kotlin +package org.partiql.pig.legacy.generator.kotlin import freemarker.template.Configuration import org.partiql.pig.generator.createDefaultFreeMarkerConfiguration diff --git a/pig/src/main/kotlin/org/partiql/pig/main.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/main.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/main.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/main.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/domain/model/Arity.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/Arity.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/domain/model/Arity.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/model/Arity.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/domain/model/DataType.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/DataType.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/domain/model/DataType.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/model/DataType.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/domain/model/NamedElement.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/NamedElement.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/domain/model/NamedElement.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/model/NamedElement.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/domain/model/SemanticErrorContext.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/SemanticErrorContext.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/domain/model/SemanticErrorContext.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/model/SemanticErrorContext.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/domain/model/Statement.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/Statement.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/domain/model/Statement.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/model/Statement.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/domain/model/TupleType.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/TupleType.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/domain/model/TupleType.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/model/TupleType.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/domain/model/TypeAnnotation.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeAnnotation.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/domain/model/TypeAnnotation.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeAnnotation.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/domain/model/TypeDomainSemanticChecker.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeDomainSemanticChecker.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/domain/model/TypeDomainSemanticChecker.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeDomainSemanticChecker.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/domain/model/TypeRef.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeRef.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/domain/model/TypeRef.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeRef.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/domain/model/TypeUniverse.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeUniverse.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/domain/model/TypeUniverse.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeUniverse.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/domain/parser/ParserErrorContext.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/parser/ParserErrorContext.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/domain/parser/ParserErrorContext.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/parser/ParserErrorContext.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/domain/parser/TypeDomainParser.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/parser/TypeDomainParser.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/domain/parser/TypeDomainParser.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/parser/TypeDomainParser.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/util/Capitalizers.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/util/Capitalizers.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/util/Capitalizers.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/util/Capitalizers.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/util/IonElement.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/util/IonElement.kt similarity index 100% rename from pig/src/main/kotlin/org/partiql/pig/util/IonElement.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/util/IonElement.kt diff --git a/pig/src/main/kotlin/org/partiql/pig/model/Model.kt b/pig/src/main/kotlin/org/partiql/pig/model/Model.kt new file mode 100644 index 0000000..b568240 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/model/Model.kt @@ -0,0 +1,187 @@ +package org.partiql.pig.model + +import net.pearx.kasechange.toPascalCase + +typealias Imports = Map + +/** + * Top-level model of a Sprout grammar + */ +class Universe( + val id: String, + val types: List, + val imports: Map, +) { + + fun forEachType(action: (TypeDef) -> Unit) { + fun List.applyToAll() { + forEach { + action(it) + it.children.applyToAll() + } + } + types.applyToAll() + } +} + +/** + * Definition of some type + */ +sealed class TypeDef(val ref: TypeRef.Path) { + + /** + * All types defined within this node + */ + abstract val children: List + + /** + * Types defined within this node which are not related to this node "algebraically" such as inline definitions + */ + abstract val types: List + + /** + * TypeDef.Sum represents a list of type variants + */ + class Sum(ref: TypeRef.Path, val variants: List, override val types: List) : TypeDef(ref) { + + override val children: List = variants + types + + override fun toString() = "sum::$ref" + } + + /** + * TypeDef.Product represents a structure of name/value pairs + */ + class Product(ref: TypeRef.Path, val props: List, override val types: List) : TypeDef(ref) { + + override val children: List = props.filterIsInstance().map { it.def } + types + + override fun toString() = "product::$ref(${props.joinToString()})" + } + + /** + * TypeDef.Enum represents a set of named constants — explicitly not modelled with [Sum] + * + * Enums routinely required special treatment.. :upside_down_face: + */ + class Enum(ref: TypeRef.Path, val values: List) : TypeDef(ref) { + + override val types: List = emptyList() + + override val children: List = emptyList() + + override fun toString() = "enum::$ref::[${values.joinToString()}]" + } + + /** + * Copy this definition, but make the reference nullable. + */ + fun nullable(): TypeDef { + val ref = TypeRef.Path(nullable = true, *ref.path.toTypedArray()) + return when (this) { + is Sum -> Sum(ref, variants, children) + is Product -> Product(ref, props, children) + is Enum -> Enum(ref, values) + } + } +} + +/** + * Reference to some type + */ +sealed class TypeRef( + val id: String, + val nullable: Boolean, +) { + + override fun equals(other: Any?) = + if (other !is TypeRef) false else (id == other.id && nullable == other.nullable) + + override fun hashCode() = id.hashCode() + + override fun toString() = if (nullable) "$id?" else id + + class Scalar( + val type: ScalarType, + nullable: Boolean = false, + ) : TypeRef( + id = type.toString().toLowerCase(), + nullable = nullable, + ) + + class List( + val type: TypeRef, + nullable: Boolean = false, + ) : TypeRef( + id = "list<$type>", + nullable = nullable, + ) + + class Set( + val type: TypeRef, + nullable: Boolean = false, + ) : TypeRef( + id = "set<$type>", + nullable = nullable, + ) + + class Map( + val keyType: Scalar, + val valType: TypeRef, + nullable: Boolean = false, + ) : TypeRef( + id = "map<$keyType, $valType>", + nullable = nullable, + ) + + class Path( + nullable: Boolean = false, + vararg ids: String, + ) : TypeRef( + id = ids.joinToString("."), + nullable = nullable, + ) { + val path = ids.asList() + val name = ids.last().toPascalCase() + } + + class Import( + val symbol: String, + nullable: Boolean = false, + ) : TypeRef( + id = "import<$symbol>", + nullable = nullable + ) +} + +/** + * Product type property + */ +sealed class TypeProp( + val name: String, + val ref: TypeRef, +) { + + override fun toString() = "$name: $ref" + + class Ref(name: String, ref: TypeRef) : TypeProp(name, ref) + + class Inline(name: String, val def: TypeDef) : TypeProp(name, def.ref) +} + +/** + * References + * - https://docs.oracle.com/cd/E26161_02/html/GettingStartedGuide/avroschemas.html#avro-primitivedatatypes + * - https://developers.google.com/protocol-buffers/docs/proto3#scalar + * - https://en.wikipedia.org/wiki/JSON#Data_types (scalars only) + * - https://amzn.github.io/ion-docs/docs/spec.html (scalars only) + */ +enum class ScalarType { + BOOL, // binary value + INT, // int32 + LONG, // int64 + FLOAT, // IEEE 754 (32 bit) + DOUBLE, // IEEE 754 (64 bit) + BYTES, // Array of unsigned bytes + STRING, // Unicode char sequence +} diff --git a/pig/src/main/kotlin/org/partiql/pig/parser/SproutParser.kt b/pig/src/main/kotlin/org/partiql/pig/parser/SproutParser.kt new file mode 100644 index 0000000..e9948dd --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/parser/SproutParser.kt @@ -0,0 +1,37 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package org.partiql.pig.parser + +import org.partiql.pig.model.Universe +import org.partiql.pig.parser.ion.IonTypeParser + +/** + * Returns a model of the input type universe definition. + */ +interface SproutParser { + + /** + * @param id Type Universe identifier + * @param input Type Universe grammar + * @return + */ + fun parse(id: String, input: String): Universe + + companion object { + + fun default(): SproutParser = IonTypeParser + } +} diff --git a/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonExtensions.kt b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonExtensions.kt new file mode 100644 index 0000000..1f09008 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonExtensions.kt @@ -0,0 +1,216 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package org.partiql.pig.parser.ion + +import com.amazon.ion.IonList +import com.amazon.ion.IonStruct +import com.amazon.ion.IonSymbol +import com.amazon.ion.IonType +import com.amazon.ion.IonValue +import com.amazon.ion.UnknownSymbolException +import java.util.LinkedList +import java.util.Stack + +/** + * Called in the context of parsing a type definition. + */ +internal fun IonValue.id(): String { + assert(typeAnnotations.size == 1) { "A type definition requires a single identifier annotation, $this" } + return typeAnnotations[0] +} + +/** + * True iff every value is a Symbol matching [A-Z][A-Z0-9_]* + */ +internal fun IonValue.isEnum(): Boolean { + if (this !is IonList) return false + val pattern = Regex("[A-Z][A-Z0-9_]*") + this.forEach { + if (it !is IonSymbol || !pattern.matches(it.stringValue())) { + return false + } + } + return true +} + +/** + * These are all the inline forms; where the inline type identifiers are not "list", "set", or "map". + * + * foo::{ + * a: [...], // inline sum foo.a + * b: v::[...], // inline sum foo.v + * c: optional::[...], // inline sum foo.c, optional field of foo + * d: optional::x::[...], // inline sum foo.v, optional field of foo + * e: {...}, // inline product foo.e + * f: y::{...}, // inline product foo.y + * g: optional::{...}, // inline product foo.g, optional field of foo + * h: optional::z::{...}, // inline product foo.z, optional field of foo + * } + */ +internal fun IonValue.isInline(): Boolean { + if (fieldName == null) { + return false + } + if (this is IonList || this is IonStruct) { + return when (typeAnnotations.size) { + 0 -> true + 1 -> typeAnnotations[0] !in IonSymbols.COLLECTIONS + 2 -> typeAnnotations[1] !in IonSymbols.COLLECTIONS + else -> false + } + } + return false +} + +/** + * Returns true if this value is of the form + * - _: [ ... ] + * - _::[ ... ] + * + * Which represent nested definitions in product and sum types respectively. + */ +internal fun IonValue.isContainer(): Boolean { + if (type != IonType.LIST) { + return false + } + val symbol = try { + // _: [ ] + fieldName + } catch (_: UnknownSymbolException) { + // _::[] + typeAnnotations[0] + } + return symbol == "_" +} + +/** + * Called in the context of parsing a type reference; these are all the type reference forms. + * + * foo::{ + * a: x, // ref to type x + * b: optional::x, // ref to type x, optional field of foo + * c: [...], // ref to inline sum foo.c + * d: t::[...], // ref to inline sum foo.t or collection (depends on value of t) + * e: optional::t::[...], // ref to inline sum foo.t or collection (depends on value of t), optional field of foo + * f: {...}, // ref to inline product foo.c + * g: s::{...}, // ref to inline product foo.s + * h: optional::s::{...}, // ref to inline product foo.s, optional field of foo + * } + * + * This method intentionally does not make a distinction between refs to inline sum defs and collection type refs. + */ +internal fun IonValue.ref(): Pair = when (this) { + is IonSymbol -> { + val prefix = typeAnnotations.joinToString("::") + val suffix = stringValue() + when (typeAnnotations.size) { + 0 -> Pair(suffix, false) + 1 -> { + if (typeAnnotations[0] != "optional") { + err("optional::$suffix", "$prefix::$suffix") + } + Pair(suffix, true) + } + else -> err( + expected = "optional::$suffix or $suffix", + found = "$prefix::$suffix", + ) + } + } + is IonList, is IonStruct -> { + val prefix = typeAnnotations.joinToString("::") + val suffix = if (this is IonList) "[...]" else "{...}" + when (typeAnnotations.size) { + 0 -> Pair(fieldName, false) + 1 -> when (val n = typeAnnotations[0]) { + "optional" -> Pair(fieldName, true) + else -> Pair(n, false) + } + 2 -> { + val o = typeAnnotations[0] + val n = typeAnnotations[1] + if (o != "optional") err("optional::$n::$suffix", "$prefix::$suffix") + Pair(n, true) + } + else -> err( + expected = "optional::$suffix or $suffix", + found = "$prefix::$suffix", + ) + } + } + else -> err("symbol, list, or struct", "$type") +} + +private fun err(expected: String, found: String): Nothing { + error("invalid type reference, must be of the form $expected, but found `$found`") +} + +/** + * Depth-first tree walk + */ +internal inline fun IonSymbols.Node.walk(action: (parent: IonSymbols.Node, child: IonSymbols.Node) -> Unit) { + val seen = mutableSetOf() + val stack = Stack() + stack.push(this) + while (stack.isNotEmpty()) { + val parent = stack.pop() + if (seen.contains(parent)) { + continue + } + seen.add(parent) + parent.children.forEach { child -> + action.invoke(parent, child) + stack.push(child) + } + } +} + +/** + * Breadth-first search + */ +internal fun IonSymbols.Node.search(id: String): IonSymbols.Node? { + val seen = mutableSetOf() + val queue = LinkedList() + queue.add(this) + while (queue.isNotEmpty()) { + val node = queue.pop() + if (node.id == id) { + return node + } + if (seen.contains(node)) { + continue + } + seen.add(node) + if (node.parent != null) { + queue.add(node.parent) + } + queue.addAll(node.children) + } + return null +} + +/** + * Search path starting from this + */ +internal fun IonSymbols.Node.search(path: List): IonSymbols.Node? { + var i = 0 + var node: IonSymbols.Node? = this + while (node != null && i < path.size) { + node = node.children.find { it.id == path[i] } + i += 1 + } + return node +} diff --git a/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonImports.kt b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonImports.kt new file mode 100644 index 0000000..c8da182 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonImports.kt @@ -0,0 +1,70 @@ +package org.partiql.pig.parser.ion + +import com.amazon.ion.IonList +import com.amazon.ion.IonStruct +import com.amazon.ion.IonSymbol +import org.partiql.pig.model.Imports + +/** + * Holds the map of targets to import statements to be used by generators + * + * @property symbols All valid import symbols + * @property map Map a target to its import statements + * + * Note you could use a map entry instead of symbols, but the logic is simpler this way + */ +internal class IonImports private constructor( + val symbols: Set, + val map: Map, +) { + + companion object { + + fun build(value: IonStruct?): IonImports { + if (value == null) { + return IonImports(emptySet(), emptyMap()) + } + val symbols = mutableSetOf() + val map = value.associate { list -> + if (list !is IonList) { + error("import map values must be of type IonList") + } + val target = list.fieldName + val imports = mutableMapOf() + list.forEach { + assert(it is IonSymbol) + val v = it as IonSymbol + val symbol = v.id() + if (imports.containsKey(symbol)) { + error("target `$target` defines the import `$symbol` more than once") + } + symbols.add(symbol) + imports[symbol] = v.stringValue() + } + target to imports + } + validateImports(symbols, map) + return IonImports(symbols, map) + } + + /** + * Validates that each import block defines all types + * + * @param symbols + * @param map + */ + private fun validateImports(symbols: Set, map: Map) { + val errs = mutableListOf() + map.forEach { (target, imports) -> + val missing = symbols - imports.keys + if (missing.isNotEmpty()) { + val err = "Import `$target` missing definitions for: ${missing.joinToString()}" + errs.add(err) + } + } + if (errs.isNotEmpty()) { + error(errs.joinToString("; ")) + } + } + } +} diff --git a/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonSymbols.kt b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonSymbols.kt new file mode 100644 index 0000000..bf6070b --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonSymbols.kt @@ -0,0 +1,143 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package org.partiql.pig.parser.ion + +import com.amazon.ion.IonContainer +import com.amazon.ion.IonList +import com.amazon.ion.IonStruct +import com.amazon.ion.IonValue +import io.github.rchowell.dotlin.graph + +/** + * Produce a graph of type identifiers from a list of type definitions. + */ +internal class IonSymbols private constructor(val root: Node) { + + companion object { + + val COLLECTIONS = setOf("map", "list", "set") + + fun build(definitions: List) = IonSymbols( + root = Node(id = "_root").apply { + definitions.forEach { type -> + val child = Visitor().visit(type, this) + children.add(child) + } + } + ) + + /** + * Consider asserting more thorough type definition naming rules ie enforce lower snake case + */ + fun assertNonReserved(id: String, context: String) = assert(!COLLECTIONS.contains(id)) { + "Cannot used reserved name `$id` for a type definition, $context." + } + } + + @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") + private class Visitor : IonVisitor { + + override fun visit(v: IonList, parent: Node?): Node { + val id = v.id() + assertNonReserved(id, if (parent != null) "child of $parent" else "top-level type") + var node = Node( + id = id, + parent = parent + ) + // Skip this node by linking parent; add all symbols in special container syntax _::[ ... ] + if (id == "_") { + node = parent ?: error("unexpected container _ at top-level") + } + if (!v.isEnum()) { + v.forEach { node.children.add(visit(it, node)) } + } + return node + } + + override fun visit(v: IonStruct, parent: Node?): Node { + val id = v.id() + assertNonReserved(id, if (parent != null) "child of $parent" else "top-level type") + val node = Node( + id = id, + parent = parent, + ) + v.forEach { field -> + when { + field.isContainer() -> { + val children = (field as IonContainer).map { visit(it, node) } + node.children.addAll(children) + } + field.isInline() -> { + val (symbol, nullable) = field.ref() + // DANGER! Mutate annotations to set the definition id as if it weren't an inline + field.setTypeAnnotations(symbol) + // Parse as any other definition + val child = visit(field, node) + // DANGER! Add back the dropped "optional" annotation + if (nullable) field.setTypeAnnotations("optional", symbol) + // Include the inline definition as a child of this node + node.children.add(child) + } + } + } + return node + } + + override fun defaultVisit(v: IonValue, parent: Node?) = error("cannot parse value $v") + } + + /** + * For debugging, consider prefixing node names since names must be globally unique in DOT + */ + override fun toString() = graph { + root.children.forEach { + +subgraph(it.id) { + label = it.id + it.walk { parent, child -> + // connection + parent.id - child.id + } + } + } + }.dot() + + internal class Node( + val id: String, + val parent: Node? = null, + val children: MutableList = mutableListOf() + ) { + + val path: List + get() { + val path = mutableListOf() + var node: Node? = this + while (node != null) { + path.add(node.id) + node = node.parent + } + // Use [1, path.size) so that `_root` is excluded in the path + return path.reversed().subList(1, path.size) + } + + override fun toString() = path.joinToString(".") + + override fun hashCode() = path.hashCode() + override fun equals(other: Any?) = when (other) { + is Node -> this.path == other.path + else -> false + } + } +} diff --git a/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonTypeParser.kt b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonTypeParser.kt new file mode 100644 index 0000000..26c8c46 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonTypeParser.kt @@ -0,0 +1,308 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package org.partiql.pig.parser.ion + +import com.amazon.ion.IonContainer +import com.amazon.ion.IonList +import com.amazon.ion.IonStruct +import com.amazon.ion.IonSymbol +import com.amazon.ion.IonType +import com.amazon.ion.IonValue +import com.amazon.ion.system.IonSystemBuilder +import org.partiql.pig.model.ScalarType +import org.partiql.pig.model.TypeDef +import org.partiql.pig.model.TypeProp +import org.partiql.pig.model.TypeRef +import org.partiql.pig.model.Universe +import org.partiql.pig.parser.SproutParser + +/** + * Parser for the prototype .ion grammar + */ +internal object IonTypeParser : SproutParser { + + private val ion = IonSystemBuilder.standard().build() + + override fun parse(id: String, input: String): Universe { + val reader = ion.newReader(input) + var type = reader.next() + + var importsValue: IonStruct? = null + val definitions: MutableList = mutableListOf() + + while (type != null) { + val value = ion.newValue(reader) + when (val identifier = value.id()) { + "imports" -> { + if (importsValue != null) { + error("`imports` has already been set") + } + if (type != IonType.STRUCT) { + error("`imports` must be a struct") + } + if (definitions.isNotEmpty()) { + error("`imports` must appear before all definitions") + } + importsValue = value as IonStruct + } + else -> { + if (type != IonType.LIST && type != IonType.STRUCT) { + error("type definition `$identifier` must be an IonList or IonStruct") + } + definitions.add(value) + } + } + type = reader.next() + } + + // Build the symbol graph prior to parsing definitions + val symbols = IonSymbols.build(definitions) + val imports = IonImports.build(importsValue) + + return Universe( + id = id, + types = definitions.map { + val ctx = Context(symbols.root, imports) + Visitor.visit(it, ctx) + }, + imports = imports.map, + ) + } + + /** + * Visitor builds a [TypeDef] graph while tracking scope (`ctx.scope`) in the [IonSymbols]. + * + * Instead of tracking in the symbol graph, we could just search from the root, then do the BFS. That is simpler. + */ + private object Visitor : IonVisitor { + + /** + * Parse a [TypeDef.Sum] or [TypeDef.Enum] + */ + override fun visit(v: IonList, ctx: Context): TypeDef = ctx.scope(v) { + val ref = ctx.ref() + val type = when { + v.isEnum() -> TypeDef.Enum( + ref = ref, + values = v.map { (it as IonSymbol).stringValue() } + ) + else -> { + val (variants, types) = visitSumVariants(v, ctx) + TypeDef.Sum(ref, variants, types) + } + } + ctx.define(type) + } + + /** + * Parse a [TypeDef.Product] + */ + override fun visit(v: IonStruct, ctx: Context): TypeDef = ctx.scope(v) { + val ref = ctx.ref() + val (props, types) = visitProductProps(v, ctx) + val type = TypeDef.Product(ref, props, types) + ctx.define(type) + } + + /** + * Returns a pair of the product def properties and children + */ + private fun visitProductProps(v: IonStruct, ctx: Context): Pair, List> { + val props = mutableListOf() + val types = mutableListOf() + v.forEach { field -> + when { + field.isContainer() -> { + // Add all definitions in special container field _: [ ] + val subtypes = (field as IonContainer).map { visit(it, ctx) } + types.addAll(subtypes) + } + field.isInline() -> { + val (symbol, nullable) = field.ref() + // DANGER! Mutate annotations to set the definition id as if it weren't an inline + field.setTypeAnnotations(symbol) + var def = visit(field, ctx) + // DANGER! Add back the dropped "optional" annotation + if (nullable) { + field.setTypeAnnotations("optional", symbol) + def = def.nullable() + } + val prop = TypeProp.Inline(field.fieldName, def) + props.add(prop) + } + else -> { + val prop = TypeProp.Ref(field.fieldName, ctx.resolve(field)) + props.add(prop) + } + } + } + return props to types + } + + /** + * Returns a pair of the sum variants and children + */ + private fun visitSumVariants(v: IonList, ctx: Context): Pair, List> { + val variants = mutableListOf() + val types = mutableListOf() + v.forEach { item -> + when { + item.isContainer() -> { + // Add all definitions in special container entry _::[ ] + val subtypes = (item as IonContainer).map { visit(it, ctx) } + types.addAll(subtypes) + } + else -> { + val variant = visit(item, ctx) + variants.add(variant) + } + } + } + return variants to types + } + + override fun defaultVisit(v: IonValue, ctx: Context) = error("cannot parse value $v, expect 'struct' or 'list'") + } + + /** + * Context tracks the visitor scope to keep position in the [IonSymbols] graph. + */ + private class Context( + private val root: IonSymbols.Node, + private val imports: IonImports, + ) { + + private var tip: IonSymbols.Node = root + private val defs: MutableList = mutableListOf() + + /** + * Produce a [TypeRef] for the current position in the [IonSymbols] + */ + fun ref() = TypeRef.Path(ids = tip.path.toTypedArray()) + + fun define(type: TypeDef): TypeDef { + defs.add(type) + return type + } + + /** + * Track position in the [IonSymbols] for type reference searches + */ + fun scope(v: IonValue, block: Context.() -> TypeDef): TypeDef { + val id = v.id() + tip = tip.children.find { it.id == id } ?: error("could not find symbol `$id`") + val def = block.invoke(this) + tip = tip.parent!! // never pop root + return def + } + + /** + * Create a TypeRef by searching the symbol graph + */ + fun resolve(v: IonValue): TypeRef = when (v) { + is IonSymbol -> resolve(v) + is IonList -> resolve(v) + is IonStruct -> resolve(v) + else -> error("cannot resolve type for IonType $v") + } + + /** + * Resolve a symbolic reference with the given rules + */ + private fun resolve(v: IonSymbol): TypeRef { + val (symbol, nullable) = v.ref() + val absolute = symbol.startsWith(".") + // 1. If absolute, search or err + if (absolute) { + val path = symbol.trimStart('.').split(".") + val node = root.search(path) + return when { + node != null -> { + TypeRef.Path( + nullable = nullable, + ids = (node.path.toTypedArray()), + ) + } + path.size == 1 && imports.symbols.contains(path.first()) -> { + // Import type reference using '.' + TypeRef.Import(path.first(), nullable) + } + else -> { + error("type reference `$symbol` not found") + } + } + } + // 2. Attempt as scalar + try { + return TypeRef.Scalar( + type = ScalarType.valueOf(symbol.toUpperCase()), + nullable = nullable, + ) + } catch (_: IllegalArgumentException) { + } + // 3. Attempt to find the symbol relative to the current position + val node = tip.search(symbol) + if (node != null) { + return TypeRef.Path( + nullable = nullable, + ids = (node.path.toTypedArray()), + ) + } + // 4. Attempt to find the symbol in the imports + if (imports.symbols.contains(symbol)) { + return TypeRef.Import(symbol, nullable) + } + // 5. Error nothing found + error("type reference `$symbol` not found") + } + + /** + * Resolve the collection type + */ + private fun resolve(v: IonList): TypeRef { + val (symbol, nullable) = v.ref() + return when (symbol.toLowerCase()) { + "list" -> { + assert(v.size == 1) { "list must have exactly one type" } + val t = resolve(v[0]) + TypeRef.List(t, nullable) + } + "set" -> { + assert(v.size == 1) { "set must have exactly one type" } + val t = resolve(v[0]) + TypeRef.Set(t, nullable) + } + "map" -> { + assert(v.size == 2) { "map must have exactly two types" } + assert(v[0] is IonSymbol) { "map key type parameter must be a symbol" } + val kt = resolve(v[0]) + val vt = resolve(v[1]) + assert(kt is TypeRef.Scalar) { "map key type `$kt` must a scalar" } + assert(!kt.nullable) { "map key type `$kt` cannot be nullable" } + TypeRef.Map(kt as TypeRef.Scalar, vt, nullable) + } + else -> error("invalid collection type $symbol; must be one of `list`, `set`, or `map`") + } + } + + /** + * Resolve the fully specified property definition + */ + private fun resolve(v: IonStruct): TypeRef { + TODO("fully specified property definitions have not been implemented: $v") + } + } +} diff --git a/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonVisitor.kt b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonVisitor.kt new file mode 100644 index 0000000..644c206 --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonVisitor.kt @@ -0,0 +1,105 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package org.partiql.pig.parser.ion + +import com.amazon.ion.IonBlob +import com.amazon.ion.IonBool +import com.amazon.ion.IonClob +import com.amazon.ion.IonDatagram +import com.amazon.ion.IonDecimal +import com.amazon.ion.IonFloat +import com.amazon.ion.IonInt +import com.amazon.ion.IonList +import com.amazon.ion.IonLob +import com.amazon.ion.IonNull +import com.amazon.ion.IonNumber +import com.amazon.ion.IonSequence +import com.amazon.ion.IonSexp +import com.amazon.ion.IonString +import com.amazon.ion.IonStruct +import com.amazon.ion.IonSymbol +import com.amazon.ion.IonText +import com.amazon.ion.IonTimestamp +import com.amazon.ion.IonValue + +/** + * Not using ValueVisitor here since it does not have a parameterized return type or visitor context. + */ +interface IonVisitor { + + /* + * This could be ` IonValue.accept(v: Visitor, ctx: C): R`, but this is slightly cleaner. + */ + fun visit(v: IonValue, ctx: C): R = when (v) { + is IonBlob -> visit(v, ctx) + is IonBool -> visit(v, ctx) + is IonClob -> visit(v, ctx) + is IonDatagram -> visit(v, ctx) + is IonDecimal -> visit(v, ctx) + is IonFloat -> visit(v, ctx) + is IonInt -> visit(v, ctx) + is IonList -> visit(v, ctx) + is IonLob -> visit(v, ctx) + is IonNull -> visit(v, ctx) + is IonNumber -> visit(v, ctx) + is IonSexp -> visit(v, ctx) + is IonSequence -> visit(v, ctx) + is IonString -> visit(v, ctx) + is IonStruct -> visit(v, ctx) + is IonSymbol -> visit(v, ctx) + is IonText -> visit(v, ctx) + is IonTimestamp -> visit(v, ctx) + else -> throw IllegalArgumentException("unknown IonValue $v") + } + + fun visit(v: IonBool, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonBlob, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonClob, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonDatagram, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonDecimal, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonFloat, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonInt, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonList, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonLob, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonNull, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonNumber, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonSexp, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonSequence, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonString, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonStruct, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonSymbol, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonText, ctx: C): R = defaultVisit(v, ctx) + + fun visit(v: IonTimestamp, ctx: C): R = defaultVisit(v, ctx) + + fun defaultVisit(v: IonValue, ctx: C): R +} diff --git a/pig/src/main/kotlin/org/partiql/pig/parser/ion/note.txt b/pig/src/main/kotlin/org/partiql/pig/parser/ion/note.txt new file mode 100644 index 0000000..e69de29 From 2d829ba25a2c8369ad62b4740aa4fa3451c89ead Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Tue, 25 Apr 2023 17:16:13 -0700 Subject: [PATCH 3/6] Adds legacy command to pig cli --- .../main/kotlin/pig.conventions.gradle.kts | 55 +-- buildSrc/src/main/kotlin/pig.versions.kt | 61 +++ gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 25 +- gradlew.bat | 92 +++++ pig-runtime/build.gradle.kts | 6 +- .../org/partiql/pig/runtime/ErrorHelpers.kt | 3 +- .../partiql/pig/runtime/MetaContainingNode.kt | 6 +- .../partiql/pig/runtime/ErrorHelpersTests.kt | 1 - .../pig/runtime/IntermediateRecordTests.kt | 1 - pig/build.gradle.kts | 61 +-- pig/src/main/kotlin/org/partiql/pig/Pig.kt | 14 +- .../org/partiql/pig/generator/Generator.kt | 2 +- .../partiql/pig/generator/target/dot/.gitkeep | 0 .../generator/target/kotlin/KotlinCommand.kt | 12 +- .../target/kotlin/KotlinGenerator.kt | 5 +- .../generator/target/kotlin/KotlinOptions.kt | 6 +- .../generator/target/kotlin/KotlinSymbols.kt | 2 +- .../target/kotlin/poems/KotlinBuilderPoem.kt | 6 +- .../target/kotlin/poems/KotlinJacksonPoem.kt | 1 - .../target/kotlin/poems/KotlinListenerPoem.kt | 2 +- .../target/kotlin/poems/KotlinVisitorPoem.kt | 2 +- .../target/kotlin/spec/KotlinNodeSpec.kt | 10 +- .../target/kotlin/spec/KotlinPackageSpec.kt | 2 +- .../target/kotlin/types/JacksonTypes.kt | 2 +- .../org/partiql/pig/legacy/LegacyCommand.kt | 384 ++++++++++++++++++ .../kotlin/org/partiql/pig/legacy/Utils.kt | 20 +- .../org/partiql/pig/legacy/cmdline/Command.kt | 2 +- .../pig/legacy/cmdline/CommandLineParser.kt | 261 ------------ .../pig/legacy/cmdline/TargetLanguage.kt | 2 +- .../pig/legacy/generator/FreeMarkerUtils.kt | 8 +- .../legacy/generator/custom/CTypeDomain.kt | 10 +- .../custom/{generator.kt => Generator.kt} | 4 +- .../html/{generator.kt => Generator.kt} | 8 +- .../ion/{generator.kt => Generator.kt} | 4 +- .../kotlin/{generator.kt => Generator.kt} | 4 +- .../legacy/generator/kotlin/KTypeDomain.kt | 4 +- .../generator/kotlin/KTypeDomainConverter.kt | 29 +- .../kotlin/org/partiql/pig/legacy/main.kt | 126 ------ .../org/partiql/pig/legacy/model/Arity.kt | 2 +- .../org/partiql/pig/legacy/model/DataType.kt | 9 +- .../partiql/pig/legacy/model/NamedElement.kt | 6 +- .../pig/legacy/model/SemanticErrorContext.kt | 9 +- .../org/partiql/pig/legacy/model/Statement.kt | 24 +- .../org/partiql/pig/legacy/model/TupleType.kt | 4 +- .../pig/legacy/model/TypeAnnotation.kt | 2 +- .../legacy/model/TypeDomainSemanticChecker.kt | 5 +- .../org/partiql/pig/legacy/model/TypeRef.kt | 6 +- .../partiql/pig/legacy/model/TypeUniverse.kt | 4 +- .../pig/legacy/parser/ParserErrorContext.kt | 10 +- .../pig/legacy/parser/TypeDomainParser.kt | 34 +- .../partiql/pig/legacy/util/Capitalizers.kt | 2 +- .../org/partiql/pig/legacy/util/IonElement.kt | 2 +- .../kotlin/org/partiql/pig/model/Model.kt | 32 +- .../parser/{SproutParser.kt => PigParser.kt} | 4 +- .../partiql/pig/parser/ion/IonExtensions.kt | 4 +- .../org/partiql/pig/parser/ion/IonImports.kt | 2 +- .../org/partiql/pig/parser/ion/IonSymbols.kt | 2 +- .../partiql/pig/parser/ion/IonTypeParser.kt | 14 +- .../pig/cmdline/CommandLineParserTests.kt | 195 --------- .../legacy/cmdline/CommandLineParserTests.kt | 340 ++++++++++++++++ .../{ => legacy}/domain/PermuteDomainTests.kt | 10 +- .../domain/TypeAnnotationParserTests.kt | 12 +- .../domain/TypeDomainParserErrorsTest.kt | 11 +- .../domain/TypeDomainParserTests.kt | 6 +- .../domain/TypeDomainSemanticCheckerTests.kt | 11 +- .../partiql/pig/{ => legacy}/domain/Util.kt | 6 +- .../generator/FreeMarkerFunctionsTest.kt | 2 +- .../CreateCustomFreeMarkerGlobalsTest.kt | 16 +- .../generator/ion/GenerateIonTest.kt | 4 +- .../kotlin/KTypeDomainConverterKtTest.kt | 4 +- .../pig/{ => legacy}/util/CapitalizerTests.kt | 3 +- settings.gradle.kts | 4 +- 73 files changed, 1148 insertions(+), 899 deletions(-) create mode 100644 buildSrc/src/main/kotlin/pig.versions.kt mode change 100644 => 100755 gradlew delete mode 100644 pig/src/main/kotlin/org/partiql/pig/generator/target/dot/.gitkeep create mode 100644 pig/src/main/kotlin/org/partiql/pig/legacy/LegacyCommand.kt delete mode 100644 pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/CommandLineParser.kt rename pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/{generator.kt => Generator.kt} (91%) rename pig/src/main/kotlin/org/partiql/pig/legacy/generator/html/{generator.kt => Generator.kt} (80%) rename pig/src/main/kotlin/org/partiql/pig/legacy/generator/ion/{generator.kt => Generator.kt} (75%) rename pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/{generator.kt => Generator.kt} (95%) delete mode 100644 pig/src/main/kotlin/org/partiql/pig/legacy/main.kt rename pig/src/main/kotlin/org/partiql/pig/parser/{SproutParser.kt => PigParser.kt} (92%) delete mode 100644 pig/src/test/kotlin/org/partiql/pig/cmdline/CommandLineParserTests.kt create mode 100644 pig/src/test/kotlin/org/partiql/pig/legacy/cmdline/CommandLineParserTests.kt rename pig/src/test/kotlin/org/partiql/pig/{ => legacy}/domain/PermuteDomainTests.kt (95%) rename pig/src/test/kotlin/org/partiql/pig/{ => legacy}/domain/TypeAnnotationParserTests.kt (92%) rename pig/src/test/kotlin/org/partiql/pig/{ => legacy}/domain/TypeDomainParserErrorsTest.kt (94%) rename pig/src/test/kotlin/org/partiql/pig/{ => legacy}/domain/TypeDomainParserTests.kt (96%) rename pig/src/test/kotlin/org/partiql/pig/{ => legacy}/domain/TypeDomainSemanticCheckerTests.kt (98%) rename pig/src/test/kotlin/org/partiql/pig/{ => legacy}/domain/Util.kt (86%) rename pig/src/test/kotlin/org/partiql/pig/{ => legacy}/generator/FreeMarkerFunctionsTest.kt (97%) rename pig/src/test/kotlin/org/partiql/pig/{ => legacy}/generator/custom/CreateCustomFreeMarkerGlobalsTest.kt (93%) rename pig/src/test/kotlin/org/partiql/pig/{ => legacy}/generator/ion/GenerateIonTest.kt (96%) rename pig/src/test/kotlin/org/partiql/pig/{ => legacy}/generator/kotlin/KTypeDomainConverterKtTest.kt (96%) rename pig/src/test/kotlin/org/partiql/pig/{ => legacy}/util/CapitalizerTests.kt (98%) diff --git a/buildSrc/src/main/kotlin/pig.conventions.gradle.kts b/buildSrc/src/main/kotlin/pig.conventions.gradle.kts index ef0da8f..bc887ed 100644 --- a/buildSrc/src/main/kotlin/pig.conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/pig.conventions.gradle.kts @@ -4,7 +4,6 @@ import org.jlleitschuh.gradle.ktlint.KtlintExtension import java.io.ByteArrayOutputStream import java.io.FileOutputStream import java.util.Properties -import java.util.random.RandomGeneratorFactory.all /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. @@ -31,70 +30,26 @@ repositories { mavenCentral() } -object Versions { - // Language - const val kotlin = "1.5.31" - const val kotlinTarget = "1.4" - const val javaTarget = "1.8" - - // Dependencies - const val ionJava = "1.9.4" - const val ionElement = "1.0.0" - const val jline = "3.21.0" - const val kasechange = "1.3.0" - const val kotlinPoet = "1.8.0" // Kotlin 1.5 - const val picoCli = "4.7.0" - // Testing -} - -object Deps { - // Language - const val kotlin = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlin}" - - // Dependencies - const val ionElement = "com.amazon.ion:ion-element:${Versions.ionElement}" - const val kasechange = "net.pearx.kasechange:kasechange:${Versions.kasechange}" - const val kotlinPoet = "com.squareup:kotlinpoet:${Versions.kotlinPoet}" - const val kotlinReflect = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlin}" - const val picoCli = "info.picocli:picocli:${Versions.picoCli}" - - // Testing - const val kotlinTest = "org.jetbrains.kotlin:kotlin-test:${Versions.kotlin}" - const val kotlinTestJunit = "org.jetbrains.kotlin:kotlin-test-junit5:${Versions.kotlin}" -} - -object Plugins { - // PIG - const val conventions = "pig.conventions" - const val publish = "org.partiql.pig.gradle.plugin.publish" - - // 3P - const val application = "org.gradle.application" - const val detekt = "io.gitlab.arturbosch.detekt" - const val dokka = "org.jetbrains.dokka" - const val ktlint = "org.jlleitschuh.gradle.ktlint" - const val library = "org.gradle.java-library" -} - val buildDir = File(rootProject.projectDir, "gradle-build/" + project.name) dependencies { implementation(Deps.kotlin) testImplementation(Deps.kotlinTest) testImplementation(Deps.kotlinTestJunit) + testImplementation(Deps.jupiter) } val generatedSrc = "$buildDir/generated-src" val generatedVersion = "$buildDir/generated-version" java { - sourceCompatibility = JavaVersion.toVersion(Versions.javaTarget) - targetCompatibility = JavaVersion.toVersion(Versions.javaTarget) + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } tasks.test { useJUnitPlatform() // Enable JUnit5 - jvmArgs!!.addAll(listOf("-Duser.language=en", "-Duser.country=US")) + jvmArgs.addAll(listOf("-Duser.language=en", "-Duser.country=US")) maxHeapSize = "4g" testLogging { events.add(TestLogEvent.FAILED) @@ -142,7 +97,7 @@ tasks.processResources { } tasks.create("generateVersionAndHash") { - val propertiesFile = file("$generatedVersion/partiql.properties") + val propertiesFile = file("$generatedVersion/pig.properties") propertiesFile.parentFile.mkdirs() val properties = Properties() // Version diff --git a/buildSrc/src/main/kotlin/pig.versions.kt b/buildSrc/src/main/kotlin/pig.versions.kt new file mode 100644 index 0000000..052d149 --- /dev/null +++ b/buildSrc/src/main/kotlin/pig.versions.kt @@ -0,0 +1,61 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +object Versions { + // Language + const val kotlin = "1.5.31" + const val kotlinTarget = "1.4" + const val javaTarget = "1.8" + // Dependencies + const val dotlin = "1.0.2" + const val freemarker = "2.3.30" + const val ionJava = "1.9.4" + const val ionElement = "1.0.0" + const val jline = "3.21.0" + const val kasechange = "1.3.0" + const val kotlinPoet = "1.8.0" // Kotlin 1.5 + const val picoCli = "4.7.0" + // Testing + const val jupiter = "5.6.2" +} + +object Deps { + // Language + const val kotlin = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlin}" + // Dependencies + const val dotlin = "io.github.rchowell:dotlin:${Versions.dotlin}" + const val ionElement = "com.amazon.ion:ion-element:${Versions.ionElement}" + const val freemarker = "org.freemarker:freemarker:${Versions.freemarker}" + const val kasechange = "net.pearx.kasechange:kasechange:${Versions.kasechange}" + const val kotlinPoet = "com.squareup:kotlinpoet:${Versions.kotlinPoet}" + const val kotlinReflect = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlin}" + const val picoCli = "info.picocli:picocli:${Versions.picoCli}" + // Testing + const val kotlinTest = "org.jetbrains.kotlin:kotlin-test:${Versions.kotlin}" + const val kotlinTestJunit = "org.jetbrains.kotlin:kotlin-test-junit5:${Versions.kotlin}" + const val jupiter = "org.junit.jupiter:junit-jupiter:${Versions.jupiter}" +} + +object Plugins { + // PIG + const val conventions = "pig.conventions" + const val publish = "org.partiql.pig.gradle.publish" + // 3P + const val application = "org.gradle.application" + const val detekt = "io.gitlab.arturbosch.detekt" + const val dokka = "org.jetbrains.dokka" + const val ktlint = "org.jlleitschuh.gradle.ktlint" + const val library = "org.gradle.java-library" +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d26fed9..75668a3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,6 @@ -# https://gradle.org/releases/ -# https://docs.gradle.org/current/userguide/compatibility.html distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 index 1b6c787..aeb74cb --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -143,12 +140,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,6 +194,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in @@ -205,6 +210,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index e69de29..93e3f59 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pig-runtime/build.gradle.kts b/pig-runtime/build.gradle.kts index d450e16..a5e47d9 100644 --- a/pig-runtime/build.gradle.kts +++ b/pig-runtime/build.gradle.kts @@ -14,12 +14,12 @@ */ plugins { - id("pig.conventions") - id("org.partiql.pig.gradle.publish") + id(Plugins.conventions) + id(Plugins.publish) } dependencies { - api("com.amazon.ion:ion-element:1.0.0") + api(Deps.ionElement) } publish { diff --git a/pig-runtime/src/main/kotlin/org/partiql/pig/runtime/ErrorHelpers.kt b/pig-runtime/src/main/kotlin/org/partiql/pig/runtime/ErrorHelpers.kt index e946263..a753317 100644 --- a/pig-runtime/src/main/kotlin/org/partiql/pig/runtime/ErrorHelpers.kt +++ b/pig-runtime/src/main/kotlin/org/partiql/pig/runtime/ErrorHelpers.kt @@ -90,6 +90,7 @@ fun SexpElement.getOptional(i: Int): AnyElement? { } private fun SexpElement.argIndexInBoundOrMalformed(i: Int) { - if (i + 1 >= this.size) + if (i + 1 >= this.size) { errMalformed(this.metas.location, "Argument index $i is out of bounds (max=${size - 2})") + } } diff --git a/pig-runtime/src/main/kotlin/org/partiql/pig/runtime/MetaContainingNode.kt b/pig-runtime/src/main/kotlin/org/partiql/pig/runtime/MetaContainingNode.kt index 2a6944e..37b3404 100644 --- a/pig-runtime/src/main/kotlin/org/partiql/pig/runtime/MetaContainingNode.kt +++ b/pig-runtime/src/main/kotlin/org/partiql/pig/runtime/MetaContainingNode.kt @@ -7,9 +7,9 @@ * * http://www.apache.org/licenses/LICENSE-2.0 * - * or in the "license" file accompanying this file. This file 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 + * or in the "license" file accompanying this file. This file 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. */ diff --git a/pig-runtime/src/test/kotlin/org/partiql/pig/runtime/ErrorHelpersTests.kt b/pig-runtime/src/test/kotlin/org/partiql/pig/runtime/ErrorHelpersTests.kt index 6824d10..7a79665 100644 --- a/pig-runtime/src/test/kotlin/org/partiql/pig/runtime/ErrorHelpersTests.kt +++ b/pig-runtime/src/test/kotlin/org/partiql/pig/runtime/ErrorHelpersTests.kt @@ -33,7 +33,6 @@ class ErrorHelpersTests { @Test fun requireArityOrMalformed() { - assertThrows { someSexp.requireArityOrMalformed(1) }.also { diff --git a/pig-runtime/src/test/kotlin/org/partiql/pig/runtime/IntermediateRecordTests.kt b/pig-runtime/src/test/kotlin/org/partiql/pig/runtime/IntermediateRecordTests.kt index 7a0d16e..a12e357 100644 --- a/pig-runtime/src/test/kotlin/org/partiql/pig/runtime/IntermediateRecordTests.kt +++ b/pig-runtime/src/test/kotlin/org/partiql/pig/runtime/IntermediateRecordTests.kt @@ -34,7 +34,6 @@ class IntermediateRecordTests { @Test fun happyPath() { - val someRecord = """ (some_record (foo 1) diff --git a/pig/build.gradle.kts b/pig/build.gradle.kts index ac62375..840e482 100644 --- a/pig/build.gradle.kts +++ b/pig/build.gradle.kts @@ -14,35 +14,36 @@ */ plugins { - id(Pig_conventions_gradle.Plugins.conventions) - // id(Plugins.application) - // id(Plugins.publish) + id(Plugins.conventions) + id(Plugins.application) + id(Plugins.publish) } -// dependencies { -// implementation(Deps.dotlin) -// implementation(Deps.ionElement) -// implementation(Deps.kasechange) -// implementation(Deps.kotlinPoet) -// implementation(Deps.picoCli) -// } -// -// application { -// applicationName = "pig" -// mainClass.set("org.partiql.pig.PigKt") -// } -// -// distributions { -// main { -// distributionBaseName.set("pig") -// } -// } -// -// tasks.register("install") { -// tasks = listOf("assembleDist", "distZip", "installDist") -// } -// -// publish { -// artifactId = "pig" -// name = "PartiQL I.R. Generator (a.k.a P.I.G.)" -// } +dependencies { + implementation(Deps.dotlin) + implementation(Deps.freemarker) + implementation(Deps.ionElement) + implementation(Deps.kasechange) + implementation(Deps.kotlinPoet) + implementation(Deps.picoCli) +} + +application { + applicationName = "pig" + mainClass.set("org.partiql.pig.PigKt") +} + +distributions { + main { + distributionBaseName.set("pig") + } +} + +tasks.register("install") { + tasks = listOf("assembleDist", "distZip", "installDist") +} + +publish { + artifactId = "pig" + name = "PartiQL I.R. Generator (a.k.a P.I.G.)" +} diff --git a/pig/src/main/kotlin/org/partiql/pig/Pig.kt b/pig/src/main/kotlin/org/partiql/pig/Pig.kt index f965265..74bd540 100644 --- a/pig/src/main/kotlin/org/partiql/pig/Pig.kt +++ b/pig/src/main/kotlin/org/partiql/pig/Pig.kt @@ -16,20 +16,21 @@ package org.partiql.pig import org.partiql.pig.generator.target.kotlin.KotlinCommand +import org.partiql.pig.legacy.LegacyCommand import picocli.CommandLine import kotlin.system.exitProcess fun main(args: Array) { - val command = CommandLine(Sprout()) + val command = CommandLine(Pig()).setCaseInsensitiveEnumValuesAllowed(true) exitProcess(command.execute(*args)) } @CommandLine.Command( - name = "sprout", + name = "pig", mixinStandardHelpOptions = true, - subcommands = [Generate::class], + subcommands = [Generate::class, LegacyCommand::class] ) -class Sprout : Runnable { +class Pig : Runnable { override fun run() {} } @@ -37,9 +38,8 @@ class Sprout : Runnable { @CommandLine.Command( name = "generate", mixinStandardHelpOptions = true, - subcommands = [ - KotlinCommand::class, - ], + subcommands = [KotlinCommand::class], + description = ["PartiQL IR Generator 1.x"] ) class Generate : Runnable { diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/Generator.kt b/pig/src/main/kotlin/org/partiql/pig/generator/Generator.kt index 07b0548..254aa8a 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/Generator.kt +++ b/pig/src/main/kotlin/org/partiql/pig/generator/Generator.kt @@ -18,7 +18,7 @@ package org.partiql.pig.generator import org.partiql.pig.model.Universe /** - * Interface for a Sprout generator + * Interface for a Pig generator */ interface Generator { diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/dot/.gitkeep b/pig/src/main/kotlin/org/partiql/pig/generator/target/dot/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinCommand.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinCommand.kt index c449a36..dfe8838 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinCommand.kt +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinCommand.kt @@ -1,8 +1,7 @@ package org.partiql.pig.generator.target.kotlin -import org.partiql.pig.parser.SproutParser +import org.partiql.pig.parser.PigParser import picocli.CommandLine -import picocli.CommandLine.Command import java.io.BufferedReader import java.io.File import java.io.FileInputStream @@ -43,7 +42,8 @@ class KotlinCommand : Callable { @CommandLine.Option( names = ["--poems"], - description = ["Poem templates to apply"], + split = ",", + description = ["Poem templates to apply"] ) var poems: List = emptyList() @@ -56,14 +56,14 @@ class KotlinCommand : Callable { override fun call(): Int { val input = BufferedReader(FileInputStream(file).reader()).readText() - val parser = SproutParser.default() + val parser = PigParser.default() val universe = parser.parse(id, input) val options = KotlinOptions( packageRoot = packageRoot, poems = poems, node = KotlinNodeOptions( - modifier = modifier, - ), + modifier = modifier + ) ) val generator = KotlinGenerator(options) val result = generator.generate(universe) diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinGenerator.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinGenerator.kt index b167c4f..1e0ee87 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinGenerator.kt +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinGenerator.kt @@ -24,7 +24,6 @@ import org.partiql.pig.model.Universe class KotlinGenerator(private val options: KotlinOptions) : Generator { override fun generate(universe: Universe): KotlinResult { - // --- Initialize an empty symbol table(?) val symbols = KotlinSymbols.init(universe, options) @@ -92,7 +91,7 @@ class KotlinGenerator(private val options: KotlinOptions) : Generator, - val node: KotlinNodeOptions = KotlinNodeOptions(), + val node: KotlinNodeOptions = KotlinNodeOptions() ) /** * Consider other options as this is Kotlin specific */ class KotlinNodeOptions( - val modifier: Modifier = Modifier.FINAL, + val modifier: Modifier = Modifier.FINAL ) { enum class Modifier { FINAL, DATA, - OPEN, + OPEN } } diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinSymbols.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinSymbols.kt index dba3300..2ac138e 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinSymbols.kt +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/KotlinSymbols.kt @@ -33,7 +33,7 @@ import org.partiql.pig.model.Universe */ class KotlinSymbols private constructor( private val universe: Universe, - private val options: KotlinOptions, + private val options: KotlinOptions ) { /** diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinBuilderPoem.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinBuilderPoem.kt index c91b61c..52964fc 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinBuilderPoem.kt +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinBuilderPoem.kt @@ -65,7 +65,7 @@ class KotlinBuilderPoem(symbols: KotlinSymbols) : KotlinPoem(symbols) { "block", LambdaTypeName.get( receiver = dslClass, - returnType = boundedT, + returnType = boundedT ) ).build() ) @@ -81,7 +81,7 @@ class KotlinBuilderPoem(symbols: KotlinSymbols) : KotlinPoem(symbols) { "block", LambdaTypeName.get( receiver = factoryClass, - returnType = boundedT, + returnType = boundedT ) ).build() ) @@ -104,7 +104,7 @@ class KotlinBuilderPoem(symbols: KotlinSymbols) : KotlinPoem(symbols) { FileSpec.builder(builderPackageName, dslName) .addFunction(dslFunc) .addType(dslSpec.build()) - .build(), + .build() ) ) ) diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinJacksonPoem.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinJacksonPoem.kt index f29c000..8a60543 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinJacksonPoem.kt +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinJacksonPoem.kt @@ -88,7 +88,6 @@ class KotlinJacksonPoem(symbols: KotlinSymbols) : KotlinPoem(symbols) { } override fun apply(node: KotlinNodeSpec.Product) { - // --- Serialization // Ignore all properties not in the type definition, with the caveat that the Jackson poem must be last diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinListenerPoem.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinListenerPoem.kt index 07c450d..fbb9d5e 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinListenerPoem.kt +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinListenerPoem.kt @@ -32,7 +32,7 @@ class KotlinListenerPoem(symbols: KotlinSymbols) : KotlinPoem(symbols) { universe.packages.add( KotlinPackageSpec( name = listenerPackageName, - files = mutableListOf(universe.listener(), walker()), + files = mutableListOf(universe.listener(), walker()) ) ) super.apply(universe) diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinVisitorPoem.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinVisitorPoem.kt index de4af00..54a52de 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinVisitorPoem.kt +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/poems/KotlinVisitorPoem.kt @@ -69,7 +69,7 @@ class KotlinVisitorPoem(symbols: KotlinSymbols) : KotlinPoem(symbols) { universe.packages.add( KotlinPackageSpec( name = visitorPackageName, - files = universe.visitors().toMutableList(), + files = universe.visitors().toMutableList() ) ) super.apply(universe) diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinNodeSpec.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinNodeSpec.kt index aab61cd..f0f20ec 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinNodeSpec.kt +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinNodeSpec.kt @@ -38,7 +38,7 @@ sealed class KotlinNodeSpec( val builder: TypeSpec.Builder, val constructor: FunSpec.Builder, val companion: TypeSpec.Builder, - val ext: MutableList = mutableListOf(), + val ext: MutableList = mutableListOf() ) { /** @@ -72,14 +72,14 @@ sealed class KotlinNodeSpec( val props: List, override val nodes: List, clazz: ClassName, - ext: MutableList = mutableListOf(), + ext: MutableList = mutableListOf() ) : KotlinNodeSpec( def = product, clazz = clazz, builder = TypeSpec.classBuilder(clazz), constructor = FunSpec.constructorBuilder(), companion = TypeSpec.companionObjectBuilder(), - ext = ext, + ext = ext ) { override val children: List = nodes } @@ -92,14 +92,14 @@ sealed class KotlinNodeSpec( val variants: List, override val nodes: List, clazz: ClassName, - ext: MutableList = mutableListOf(), + ext: MutableList = mutableListOf() ) : KotlinNodeSpec( def = sum, clazz = clazz, builder = TypeSpec.classBuilder(clazz).addModifiers(KModifier.SEALED), constructor = FunSpec.constructorBuilder(), companion = TypeSpec.companionObjectBuilder(), - ext = ext, + ext = ext ) { override val children: List = variants + nodes } diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinPackageSpec.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinPackageSpec.kt index 89be096..92357f4 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinPackageSpec.kt +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/spec/KotlinPackageSpec.kt @@ -25,5 +25,5 @@ import com.squareup.kotlinpoet.FileSpec */ class KotlinPackageSpec( val name: String, - val files: MutableList = mutableListOf(), + val files: MutableList = mutableListOf() ) diff --git a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/JacksonTypes.kt b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/JacksonTypes.kt index 4ba7a70..1a41f9b 100644 --- a/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/JacksonTypes.kt +++ b/pig/src/main/kotlin/org/partiql/pig/generator/target/kotlin/types/JacksonTypes.kt @@ -1,6 +1,6 @@ /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/LegacyCommand.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/LegacyCommand.kt new file mode 100644 index 0000000..23fb1ef --- /dev/null +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/LegacyCommand.kt @@ -0,0 +1,384 @@ +package org.partiql.pig.legacy + +import com.amazon.ion.system.IonReaderBuilder +import org.partiql.pig.legacy.cmdline.Command +import org.partiql.pig.legacy.cmdline.TargetLanguage +import org.partiql.pig.legacy.errors.PigException +import org.partiql.pig.legacy.generator.custom.applyCustomTemplate +import org.partiql.pig.legacy.generator.html.applyHtmlTemplate +import org.partiql.pig.legacy.generator.ion.generateIon +import org.partiql.pig.legacy.generator.kotlin.convertToKTypeUniverse +import org.partiql.pig.legacy.generator.kotlin.generateKotlinCode +import org.partiql.pig.legacy.model.TypeUniverse +import org.partiql.pig.legacy.parser.parseTypeUniverse +import picocli.CommandLine +import picocli.CommandLine.Option +import java.io.File +import java.io.FileInputStream +import java.io.PrintStream +import java.io.PrintWriter +import java.util.Optional +import java.util.Properties +import java.util.concurrent.Callable + +/** + * PIG 0.x command line. + * + * Optionals are not ideal, but are necessary coming from jopt since subcommands weren't used. + */ +@CommandLine.Command( + name = "legacy", + description = ["PartiQL IR Generator 0.x"] +) +class LegacyCommand : Callable { + + private val properties = Properties() + + init { + properties.load(this.javaClass.getResourceAsStream("/pig.properties")) + } + + companion object { + + // Previous command hack for backwards compatibility testing. + // Picocli parses a command for us, but we want to recreate the old jopt style here. + // Could be a list, but `pig` is only ever invoked once + internal var previous: Command? = null + } + + @Option( + names = ["--capture"], + description = ["Parse arguments to previous command, but do not execute"] + ) + var capture: Boolean = false + + @Option( + names = ["--help", "-h", "-?"], + description = ["Prints current version"] + ) + var help: Boolean = false + + @Option( + names = ["--version", "-v"], + description = ["Prints current version"] + ) + var version: Boolean = false + + @Option( + names = ["--universe", "-u"], + description = ["Type universe input file"], + defaultValue = Option.NULL_VALUE + ) + lateinit var universe: Optional + + @Option( + names = ["--target", "-t"], + description = ["Type universe input file"], + defaultValue = Option.NULL_VALUE + ) + lateinit var target: Optional + + @Option( + names = ["--output-file", "-o"], + description = ["Generated source output file"], + defaultValue = Option.NULL_VALUE + ) + lateinit var outputFile: Optional + + @Option( + names = ["--output-directory", "-d"], + description = ["Generated source output directory"], + defaultValue = Option.NULL_VALUE + ) + lateinit var outputDirectory: Optional + + @Option( + names = ["--namespace", "-n"], + description = ["Namespace for generated code"], + defaultValue = Option.NULL_VALUE + ) + lateinit var namespace: Optional + + @Option( + names = ["--template", "-e"], + description = ["Path to an Apache FreeMarker template"], + defaultValue = Option.NULL_VALUE + ) + lateinit var template: Optional + + @Option( + names = ["--domains", "-f"], + split = ",", + description = ["List of domains to generate (comma separated)"] + ) + var domains: List = emptyList() + + override fun call(): Int { + val command = parse() + // short-circuit on capture + if (capture) { + previous = command + return 0 + } + // execute, legacy main.kt + when (command) { + is Command.ShowHelp -> printHelp(System.out) + is Command.ShowVersion -> printVersion(System.out) + is Command.InvalidCommandLineArguments -> { + System.err.println(command.message) + return -1 + } + is Command.Generate -> { + try { + generateCode(command) + } catch (e: PigException) { + System.err.println("pig: ${e.error.location}: ${e.error.context.message}\n${e.stackTrace}") + return -1 + } + } + } + return 0 + } + + /** + * Prints help to the specified [PrintStream]. + */ + private fun printHelp(out: PrintStream) { + val help = CommandLine.Help(this) + out.println(help) + out.println( + """|Each target requires certain arguments: + | + | --target=kotlin requires --namespace= and --output-directory= + | --target=custom requires --template= and --output-file= + | --target=html requires --output-file= + | --target=ion requires --output-file= + | + |Notes: + | + | If -d or --output-directory is specified and the directory does not exist, it will be created. + | + |Examples: + | + | pig --target=kotlin \ + | --universe=universe.ion \ + | --output-directory=generated-src \ + | --namespace=org.example.domain + | + | pig --target=custom \ + | --universe=universe.ion \ + | --output-file=example.txt \ + | --template=template.ftl + | + | pig --target=ion \ + | --universe=universe.ion \ + | --output-file=example.ion + | + """.trimMargin() + ) + } + + /** + * Prints version specified in the package root build.gradle + */ + private fun printVersion(out: PrintStream) { + out.println("PartiQL I.R. Generator Version ${properties.getProperty("version")}") + } + + private fun parse(): Command { + return try { + when { + help -> Command.ShowHelp + version -> Command.ShowVersion + else -> { + if (!universe.isPresent) { + return Command.InvalidCommandLineArguments("Missing required option(s) [u/universe]") + } + val typeUniverseFile: File = universe.get() + + if (!target.isPresent) { + return Command.InvalidCommandLineArguments("Missing required option(s) [t/target]") + } + val targetType = target.get() + + // --namespace + if (targetType.requireNamespace) { + if (!namespace.isPresent) { + return Command.InvalidCommandLineArguments( + "The selected language target $targetType requires the --namespace argument" + ) + } + } else if (namespace.isPresent) { + return Command.InvalidCommandLineArguments( + "The selected language target $targetType does not allow the --namespace argument" + ) + } + + // --output-file + if (targetType.requireOutputFile) { + if (!outputFile.isPresent) { + return Command.InvalidCommandLineArguments( + "The selected language target $targetType requires the --output-file argument" + ) + } + } else if (outputFile.isPresent) { + return Command.InvalidCommandLineArguments( + "The selected language target $targetType does not allow the --output-file argument" + ) + } + + // --output-directory + if (targetType.requireOutputDirectory) { + if (!outputDirectory.isPresent) { + return Command.InvalidCommandLineArguments( + "The selected language target $targetType requires the --output-directory argument" + ) + } + } else if (outputDirectory.isPresent) { + return Command.InvalidCommandLineArguments( + "The selected language target $targetType does not allow the --output-directory argument" + ) + } + + // --template + if (targetType.requireTemplateFile) { + if (!template.isPresent) { + return Command.InvalidCommandLineArguments("The selected language target $targetType requires the --template argument") + } + } else if (template.isPresent) { + return Command.InvalidCommandLineArguments( + "The selected language target $targetType does not allow the --template argument" + ) + } + + val domainSet: Set? = when (domains.isNotEmpty()) { + true -> domains.toSet() + false -> null + } + + val target = when (targetType) { + LanguageTargetType.HTML -> TargetLanguage.Html( + outputFile = outputFile.get(), + domains = domainSet + ) + LanguageTargetType.KOTLIN -> TargetLanguage.Kotlin( + namespace = namespace.get(), + outputDirectory = outputDirectory.get(), + domains = domainSet + ) + LanguageTargetType.CUSTOM -> TargetLanguage.Custom( + templateFile = template.get(), + outputFile = outputFile.get(), + domains = domainSet + ) + LanguageTargetType.ION -> TargetLanguage.Ion( + outputFile = outputFile.get(), + domains = domainSet + ) + } + + Command.Generate(typeUniverseFile, target) + } + } + } catch (ex: NoSuchElementException) { + return Command.InvalidCommandLineArguments(ex.message!!) + } + } + + enum class LanguageTargetType( + val requireNamespace: Boolean, + val requireTemplateFile: Boolean, + val requireOutputFile: Boolean, + val requireOutputDirectory: Boolean + ) { + KOTLIN( + requireNamespace = true, + requireTemplateFile = false, + requireOutputFile = false, + requireOutputDirectory = true + ), + CUSTOM( + requireNamespace = false, + requireTemplateFile = true, + requireOutputFile = true, + requireOutputDirectory = false + ), + HTML( + requireNamespace = false, + requireTemplateFile = false, + requireOutputFile = true, + requireOutputDirectory = false + ), + ION( + requireNamespace = false, + requireTemplateFile = false, + requireOutputFile = true, + requireOutputDirectory = false + ) + } +} + +private fun progress(msg: String) = println("pig: $msg") + +/** + * Gradle projects such as the PartiQL reference implementation take a dependency on this project + * in their `buildSrc` directory and can then use this entry point to generate code direclty without + * having to `exec` pig as a separate process. + */ +fun generateCode(command: Command.Generate) { + progress("universe file: ${command.typeUniverseFile}") + + progress("parsing the universe...") + val typeUniverse: TypeUniverse = FileInputStream(command.typeUniverseFile).use { inputStream -> + IonReaderBuilder.standard().build(inputStream).use { ionReader -> parseTypeUniverse(ionReader) } + } + + progress("permuting domains...") + + val computedDomains = typeUniverse.computeTypeDomains() + val filteredDomains = + command.target.domains?.let { computedDomains.filter { domain -> domain.tag in it } } ?: computedDomains + + when (command.target) { + is TargetLanguage.Kotlin -> { + progress("applying Kotlin pre-processing") + val kotlinTypeUniverse = typeUniverse.convertToKTypeUniverse(computedDomains, filteredDomains, command.target.domains) + val dir = command.target.outputDirectory + if (dir.exists()) { + if (!dir.isDirectory) { + error("The path specified as the output directory exists but is not a directory.") + } + } else { + dir.mkdirs() + } + progress("applying the Kotlin template once for each domain...") + generateKotlinCode(command.target.namespace, kotlinTypeUniverse, command.target.outputDirectory) + } + is TargetLanguage.Custom -> { + progress("output file : ${command.target.outputFile}") + progress("applying ${command.target.templateFile}") + + PrintWriter(command.target.outputFile).use { printWriter -> + applyCustomTemplate(command.target.templateFile, filteredDomains, printWriter) + } + } + is TargetLanguage.Html -> { + progress("output file : ${command.target.outputFile}") + progress("applying the HTML template") + + PrintWriter(command.target.outputFile).use { printWriter -> + applyHtmlTemplate(filteredDomains, printWriter) + } + } + is TargetLanguage.Ion -> { + progress("output file : ${command.target.outputFile}") + progress("applying the Ion template") + + PrintWriter(command.target.outputFile).use { printWriter -> + generateIon(filteredDomains, printWriter) + } + } + } + + progress("universe generation complete!") +} diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/Utils.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/Utils.kt index c02e118..b2afbd5 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/Utils.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/Utils.kt @@ -1,17 +1,17 @@ -package org.partiql.pig.domain +package org.partiql.pig.legacy import com.amazon.ionelement.api.IonElement import com.amazon.ionelement.api.ionSexpOf import com.amazon.ionelement.api.ionSymbol -import org.partiql.pig.domain.model.DataType -import org.partiql.pig.domain.model.NamedElement -import org.partiql.pig.domain.model.PermutedDomain -import org.partiql.pig.domain.model.PermutedSum -import org.partiql.pig.domain.model.Statement -import org.partiql.pig.domain.model.Transform -import org.partiql.pig.domain.model.TupleType -import org.partiql.pig.domain.model.TypeDomain -import org.partiql.pig.domain.model.TypeUniverse +import org.partiql.pig.legacy.model.DataType +import org.partiql.pig.legacy.model.NamedElement +import org.partiql.pig.legacy.model.PermutedDomain +import org.partiql.pig.legacy.model.PermutedSum +import org.partiql.pig.legacy.model.Statement +import org.partiql.pig.legacy.model.Transform +import org.partiql.pig.legacy.model.TupleType +import org.partiql.pig.legacy.model.TypeDomain +import org.partiql.pig.legacy.model.TypeUniverse /* * The [toIonElement] functions below generate an s-expression representation of a [TypeUniverse]. diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/Command.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/Command.kt index befbc71..72643c1 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/Command.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/Command.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.cmdline +package org.partiql.pig.legacy.cmdline import java.io.File diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/CommandLineParser.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/CommandLineParser.kt deleted file mode 100644 index 75aacd2..0000000 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/CommandLineParser.kt +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file 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. - */ - -package org.partiql.pig.cmdline - -import joptsimple.BuiltinHelpFormatter -import joptsimple.OptionDescriptor -import joptsimple.OptionException -import joptsimple.OptionParser -import joptsimple.ValueConversionException -import joptsimple.ValueConverter -import java.io.File -import java.io.PrintStream -import java.util.Properties - -class CommandLineParser { - private enum class LanguageTargetType( - val requireNamespace: Boolean, - val requireTemplateFile: Boolean, - val requireOutputFile: Boolean, - val requireOutputDirectory: Boolean - ) { - KOTLIN(requireNamespace = true, requireTemplateFile = false, requireOutputFile = false, requireOutputDirectory = true), - CUSTOM(requireNamespace = false, requireTemplateFile = true, requireOutputFile = true, requireOutputDirectory = false), - HTML(requireNamespace = false, requireTemplateFile = false, requireOutputFile = true, requireOutputDirectory = false), - ION(requireNamespace = false, requireTemplateFile = false, requireOutputFile = true, requireOutputDirectory = false) - } - - private object LanguageTargetTypeValueConverter : ValueConverter { - private val lookup = LanguageTargetType.values().associateBy { it.name.toLowerCase() } - - override fun convert(value: String?): LanguageTargetType { - if (value == null) throw ValueConversionException("Value was null") - return lookup[value] ?: throw ValueConversionException("Invalid language target type: $value") - } - - override fun valueType(): Class { - return LanguageTargetType::class.java - } - - override fun valuePattern(): String { - return LanguageTargetType.values().map { it.name.toLowerCase() }.joinToString("|") - } - } - - private object DomainFilterValueConverter : ValueConverter> { - override fun convert(value: String?): Set { - if (value.isNullOrBlank()) throw ValueConversionException("Value was empty") - return value.split(',').map(String::trim).toSet() - } - - override fun valueType(): Class>? = null - - override fun valuePattern(): String { - return ",,..." - } - } - - private val formatter = object : BuiltinHelpFormatter(120, 2) { - override fun format(options: MutableMap?): String { - return """PartiQL I.R. Generator - | - |${super.format(options)} - |Each target requires certain arguments: - | - | --target=kotlin requires --namespace= and --output-directory= - | --target=custom requires --template= and --output-file= - | --target=html requires --output-file= - | --target=ion requires --output-file= - | - |Notes: - | - | If -d or --output-directory is specified and the directory does not exist, it will be created. - | - |Examples: - | - | pig --target=kotlin \ - | --universe=universe.ion \ - | --output-directory=generated-src \ - | --namespace=org.example.domain - | - | pig --target=custom \ - | --universe=universe.ion \ - | --output-file=example.txt \ - | --template=template.ftl - | - | pig --target=ion \ - | --universe=universe.ion \ - | --output-file=example.ion - | - """.trimMargin() - } - } - private val optParser = OptionParser().also { it.formatHelpWith(formatter) } - - private val helpOpt = optParser.acceptsAll(listOf("help", "h", "?"), "Prints this help") - .forHelp() - - private val versionOpt = optParser.acceptsAll(listOf("version", "v"), "Prints current version") - .forHelp() - - private val universeOpt = optParser.acceptsAll(listOf("universe", "u"), "Type universe input file") - .withRequiredArg() - .ofType(File::class.java) - .required() - - private val targetTypeOpt = optParser.acceptsAll(listOf("target", "t"), "Target language") - .withRequiredArg() - .withValuesConvertedBy(LanguageTargetTypeValueConverter) - .required() - - private val outputFileOpt = optParser.acceptsAll(listOf("output-file", "o"), "Generated output file (for targets that output a single file)") - .withRequiredArg() - .ofType(File::class.java) - - private val outputDirectoryOpt = optParser.acceptsAll(listOf("output-directory", "d"), "Generated output directory (for targets that output multiple files)") - .withRequiredArg() - .ofType(File::class.java) - - private val namespaceOpt = optParser.acceptsAll(listOf("namespace", "n"), "Namespace for generated code") - .withOptionalArg() - .ofType(String::class.java) - - private val templateOpt = optParser.acceptsAll(listOf("template", "e"), "Path to an Apache FreeMarker template") - .withOptionalArg() - .ofType(File::class.java) - - private val properties = Properties() - - private val domainsOpt = optParser.acceptsAll(listOf("domains", "f"), "List of domains to generate (comma separated)") - .withOptionalArg() - .withValuesConvertedBy(DomainFilterValueConverter) - - init { - properties.load(this.javaClass.getResourceAsStream("/pig.properties")) - } - - /** - * Prints help to the specified [PrintStream]. - */ - fun printHelp(out: PrintStream) { - optParser.printHelpOn(out) - } - - /** - * Prints version specified in the package root build.gradle - */ - fun printVersion(out: PrintStream) { - out.println("PartiQL I.R. Generator Version ${properties.getProperty("version")}") - } - - /** - * Parses the command-line arguments. - * - * Returns `null` if the command-line arguments were invalid or - */ - fun parse(args: Array): Command { - return try { - val optSet = optParser.parse(*args) - - when { - optSet.has(helpOpt) -> Command.ShowHelp - optSet.has(versionOpt) -> Command.ShowVersion - else -> { - // !! is fine in this case since we define these options as .required() above. - val typeUniverseFile: File = optSet.valueOf(universeOpt)!! - val targetType = optSet.valueOf(targetTypeOpt)!! - - // --namespace - if (targetType.requireNamespace) { - if (!optSet.has(namespaceOpt)) { - return Command.InvalidCommandLineArguments( - "The selected language target requires the --namespace argument" - ) - } - } else if (optSet.has(namespaceOpt)) { - return Command.InvalidCommandLineArguments( - "The selected language target does not allow the --namespace argument" - ) - } - - // --output-file - if (targetType.requireOutputFile) { - if (!optSet.has(outputFileOpt)) { - return Command.InvalidCommandLineArguments( - "The selected language target requires the --output-file argument" - ) - } - } else if (optSet.has(outputFileOpt)) { - return Command.InvalidCommandLineArguments( - "The selected language target does not allow the --output-file argument" - ) - } - - // --output-directory - if (targetType.requireOutputDirectory) { - if (!optSet.has(outputDirectoryOpt)) { - return Command.InvalidCommandLineArguments( - "The selected language target requires the --output-directory argument" - ) - } - } else if (optSet.has(outputDirectoryOpt)) { - return Command.InvalidCommandLineArguments( - "The selected language target does not allow the --output-directory argument" - ) - } - - // --template - if (targetType.requireTemplateFile) { - if (!optSet.has(templateOpt)) { - return Command.InvalidCommandLineArguments("The selected language target requires the --template argument") - } - } else if (optSet.has(templateOpt)) { - return Command.InvalidCommandLineArguments( - "The selected language target does not allow the --template argument" - ) - } - - val domains = optSet.valueOf(domainsOpt) - - val target = when (targetType) { - LanguageTargetType.HTML -> TargetLanguage.Html( - outputFile = optSet.valueOf(outputFileOpt) as File, - domains = domains - ) - LanguageTargetType.KOTLIN -> TargetLanguage.Kotlin( - namespace = optSet.valueOf(namespaceOpt) as String, - outputDirectory = optSet.valueOf(outputDirectoryOpt) as File, - domains = domains - ) - LanguageTargetType.CUSTOM -> TargetLanguage.Custom( - templateFile = optSet.valueOf(templateOpt), - outputFile = optSet.valueOf(outputFileOpt) as File, - domains = domains - ) - LanguageTargetType.ION -> TargetLanguage.Ion( - outputFile = optSet.valueOf(outputFileOpt) as File, - domains = domains - ) - } - - Command.Generate(typeUniverseFile, target) - } - } - } catch (ex: OptionException) { - Command.InvalidCommandLineArguments(ex.message!!) - } - } -} diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/TargetLanguage.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/TargetLanguage.kt index d1efa8a..04a6fc2 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/TargetLanguage.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/cmdline/TargetLanguage.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.cmdline +package org.partiql.pig.legacy.cmdline import java.io.File diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/FreeMarkerUtils.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/FreeMarkerUtils.kt index aeef8f4..fc7a372 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/FreeMarkerUtils.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/FreeMarkerUtils.kt @@ -19,9 +19,9 @@ import freemarker.template.Configuration import freemarker.template.TemplateExceptionHandler import freemarker.template.TemplateMethodModelEx import freemarker.template.TemplateModelException -import org.partiql.pig.generator.kotlin.KotlinDomainFreeMarkerGlobals -import org.partiql.pig.util.snakeToCamelCase -import org.partiql.pig.util.snakeToPascalCase +import org.partiql.pig.legacy.generator.kotlin.KotlinDomainFreeMarkerGlobals +import org.partiql.pig.legacy.util.snakeToCamelCase +import org.partiql.pig.legacy.util.snakeToPascalCase /** * When applying a template for a pre-packaged language target such as Kotlin, we @@ -33,7 +33,7 @@ internal fun Configuration.setClassLoaderForTemplates() { // the classloader of [TopLevelFreeMarkerGlobals] and set the root package. this.setClassLoaderForTemplateLoading( KotlinDomainFreeMarkerGlobals::class.java.classLoader, - "/org/partiql/pig/templates" + "/org.partiql.pig.legacy.templates" ) } diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/CTypeDomain.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/CTypeDomain.kt index fe44a48..3475951 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/CTypeDomain.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/CTypeDomain.kt @@ -15,11 +15,11 @@ package org.partiql.pig.legacy.generator.custom -import org.partiql.pig.domain.model.Arity -import org.partiql.pig.domain.model.DataType -import org.partiql.pig.domain.model.NamedElement -import org.partiql.pig.domain.model.TupleType -import org.partiql.pig.domain.model.TypeDomain +import org.partiql.pig.legacy.model.Arity +import org.partiql.pig.legacy.model.DataType +import org.partiql.pig.legacy.model.NamedElement +import org.partiql.pig.legacy.model.TupleType +import org.partiql.pig.legacy.model.TypeDomain // // Types in this file are exposed to FreeMarker templates at run-time. diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/generator.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/Generator.kt similarity index 91% rename from pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/generator.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/Generator.kt index 42e18d5..d76282c 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/generator.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/custom/Generator.kt @@ -15,8 +15,8 @@ package org.partiql.pig.legacy.generator.custom -import org.partiql.pig.domain.model.TypeDomain -import org.partiql.pig.generator.createDefaultFreeMarkerConfiguration +import org.partiql.pig.legacy.generator.createDefaultFreeMarkerConfiguration +import org.partiql.pig.legacy.model.TypeDomain import java.io.File import java.io.PrintWriter import java.time.OffsetDateTime diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/html/generator.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/html/Generator.kt similarity index 80% rename from pig/src/main/kotlin/org/partiql/pig/legacy/generator/html/generator.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/html/Generator.kt index 400f5fc..166f633 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/html/generator.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/html/Generator.kt @@ -16,10 +16,10 @@ package org.partiql.pig.legacy.generator.html import freemarker.template.Template -import org.partiql.pig.domain.model.TypeDomain -import org.partiql.pig.generator.createDefaultFreeMarkerConfiguration -import org.partiql.pig.generator.custom.createCustomFreeMarkerGlobals -import org.partiql.pig.generator.setClassLoaderForTemplates +import org.partiql.pig.legacy.generator.createDefaultFreeMarkerConfiguration +import org.partiql.pig.legacy.generator.custom.createCustomFreeMarkerGlobals +import org.partiql.pig.legacy.generator.setClassLoaderForTemplates +import org.partiql.pig.legacy.model.TypeDomain import java.io.PrintWriter fun applyHtmlTemplate( diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/ion/generator.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/ion/Generator.kt similarity index 75% rename from pig/src/main/kotlin/org/partiql/pig/legacy/generator/ion/generator.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/ion/Generator.kt index 053f690..c728ba8 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/ion/generator.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/ion/Generator.kt @@ -1,7 +1,7 @@ package org.partiql.pig.legacy.generator.ion -import org.partiql.pig.domain.model.TypeDomain -import org.partiql.pig.legacy.domain.toIonElement +import org.partiql.pig.legacy.model.TypeDomain +import org.partiql.pig.legacy.toIonElement import java.io.PrintWriter /** diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/generator.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/Generator.kt similarity index 95% rename from pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/generator.kt rename to pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/Generator.kt index 70355cb..2ad3f48 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/generator.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/Generator.kt @@ -16,8 +16,8 @@ package org.partiql.pig.legacy.generator.kotlin import freemarker.template.Configuration -import org.partiql.pig.generator.createDefaultFreeMarkerConfiguration -import org.partiql.pig.generator.setClassLoaderForTemplates +import org.partiql.pig.legacy.generator.createDefaultFreeMarkerConfiguration +import org.partiql.pig.legacy.generator.setClassLoaderForTemplates import java.io.File import java.io.PrintWriter import java.time.OffsetDateTime diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomain.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomain.kt index a55f024..9b005ac 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomain.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomain.kt @@ -96,7 +96,7 @@ data class KTuple( */ val isTransformAbstract: Boolean, val hasVariadicElement: Boolean, - val annotations: List, + val annotations: List ) data class KSum( @@ -109,5 +109,5 @@ data class KSum( * generated visitor transform `transform*` method will be `abstract`. */ val isTransformAbstract: Boolean, - val annotations: List, + val annotations: List ) diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomainConverter.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomainConverter.kt index 43d04ae..eba5130 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomainConverter.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomainConverter.kt @@ -21,17 +21,17 @@ */ package org.partiql.pig.legacy.generator.kotlin -import org.partiql.pig.domain.model.Arity -import org.partiql.pig.domain.model.DataType -import org.partiql.pig.domain.model.NamedElement -import org.partiql.pig.domain.model.Transform -import org.partiql.pig.domain.model.TupleType -import org.partiql.pig.domain.model.TypeAnnotation -import org.partiql.pig.domain.model.TypeDomain -import org.partiql.pig.domain.model.TypeRef -import org.partiql.pig.domain.model.TypeUniverse -import org.partiql.pig.util.snakeToCamelCase -import org.partiql.pig.util.snakeToPascalCase +import org.partiql.pig.legacy.model.Arity +import org.partiql.pig.legacy.model.DataType +import org.partiql.pig.legacy.model.NamedElement +import org.partiql.pig.legacy.model.Transform +import org.partiql.pig.legacy.model.TupleType +import org.partiql.pig.legacy.model.TypeAnnotation +import org.partiql.pig.legacy.model.TypeDomain +import org.partiql.pig.legacy.model.TypeRef +import org.partiql.pig.legacy.model.TypeUniverse +import org.partiql.pig.legacy.util.snakeToCamelCase +import org.partiql.pig.legacy.util.snakeToPascalCase /** * Turns a [TypeUniverse] into a [KTypeUniverse]. If only a subset of domains is requested via the [domainFilters], only those will be present in the output [KTypeUniverse] @@ -177,7 +177,6 @@ private class KTypeDomainConverter( tuple: DataType.UserType.Tuple, useKotlinPrimitives: Boolean ): KBuilderFunction { - return KBuilderFunction( kotlinName = tuple.tag.snakeToCamelCase() + if (!useKotlinPrimitives) "_" else "", parameters = tuple.namedElements @@ -412,9 +411,9 @@ private class KTypeDomainConverter( private fun TypeRef.getBaseKotlinTypeName(kotlinPrimitives: Boolean, useAnyElement: Boolean = true): String { return when (typeName) { "ion" -> "com.amazon.ionelement.api." + if (useAnyElement) "AnyElement" else "IonElement" - "int" -> if (kotlinPrimitives) "Long" else "org.partiql.pig.runtime.LongPrimitive" - "bool" -> if (kotlinPrimitives) "Boolean" else "org.partiql.pig.runtime.BoolPrimitive" - "symbol" -> if (kotlinPrimitives) "String" else "org.partiql.pig.runtime.SymbolPrimitive" + "int" -> if (kotlinPrimitives) "Long" else "org.partiql.pig.legacy.runtime.LongPrimitive" + "bool" -> if (kotlinPrimitives) "Boolean" else "org.partiql.pig.legacy.runtime.BoolPrimitive" + "symbol" -> if (kotlinPrimitives) "String" else "org.partiql.pig.legacy.runtime.SymbolPrimitive" else -> this.typeName.snakeToPascalCase() } } diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/main.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/main.kt deleted file mode 100644 index db26a86..0000000 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/main.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file 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. - */ - -package org.partiql.pig - -import com.amazon.ion.system.IonReaderBuilder -import org.partiql.pig.cmdline.Command -import org.partiql.pig.cmdline.CommandLineParser -import org.partiql.pig.cmdline.TargetLanguage -import org.partiql.pig.domain.model.TypeUniverse -import org.partiql.pig.domain.parser.parseTypeUniverse -import org.partiql.pig.errors.PigException -import org.partiql.pig.generator.custom.applyCustomTemplate -import org.partiql.pig.generator.html.applyHtmlTemplate -import org.partiql.pig.generator.ion.generateIon -import org.partiql.pig.generator.kotlin.convertToKTypeUniverse -import org.partiql.pig.generator.kotlin.generateKotlinCode -import java.io.File -import java.io.FileInputStream -import java.io.PrintWriter -import kotlin.system.exitProcess - -fun progress(msg: String) = - println("pig: $msg") - -/** - * Entry point for when pig is being invoked from the command-line. - */ -fun main(args: Array) { - val cmdParser = CommandLineParser() - - when (val command = cmdParser.parse(args)) { - is Command.ShowHelp -> cmdParser.printHelp(System.out) - is Command.ShowVersion -> cmdParser.printVersion(System.out) - is Command.InvalidCommandLineArguments -> { - System.err.println(command.message) - exitProcess(-1) - } - is Command.Generate -> { - try { - generateCode(command) - } catch (e: PigException) { - System.err.println("pig: ${e.error.location}: ${e.error.context.message}\n${e.stackTrace}") - exitProcess(-1) - } - } - } -} - -/** - * Gradle projects such as the PartiQL reference implementation take a dependency on this project - * in their `buildSrc` directory and can then use this entry point to generate code direclty without - * having to `exec` pig as a separate process. - */ -fun generateCode(command: Command.Generate) { - progress("universe file: ${command.typeUniverseFile}") - - progress("parsing the universe...") - val typeUniverse: TypeUniverse = FileInputStream(command.typeUniverseFile).use { inputStream -> - IonReaderBuilder.standard().build(inputStream).use { ionReader -> parseTypeUniverse(ionReader) } - } - - progress("permuting domains...") - - val computedDomains = typeUniverse.computeTypeDomains() - val filteredDomains = command.target.domains?.let { computedDomains.filter { domain -> domain.tag in it } } ?: computedDomains - - when (command.target) { - is TargetLanguage.Kotlin -> { - progress("applying Kotlin pre-processing") - val kotlinTypeUniverse = typeUniverse.convertToKTypeUniverse(computedDomains, filteredDomains, command.target.domains) - prepareOutputDirectory(command.target.outputDirectory) - progress("applying the Kotlin template once for each domain...") - - generateKotlinCode(command.target.namespace, kotlinTypeUniverse, command.target.outputDirectory) - } - is TargetLanguage.Custom -> { - progress("output file : ${command.target.outputFile}") - progress("applying ${command.target.templateFile}") - - PrintWriter(command.target.outputFile).use { printWriter -> - applyCustomTemplate(command.target.templateFile, filteredDomains, printWriter) - } - } - is TargetLanguage.Html -> { - progress("output file : ${command.target.outputFile}") - progress("applying the HTML template") - - PrintWriter(command.target.outputFile).use { printWriter -> - applyHtmlTemplate(filteredDomains, printWriter) - } - } - is TargetLanguage.Ion -> { - progress("output file : ${command.target.outputFile}") - progress("applying the Ion template") - - PrintWriter(command.target.outputFile).use { printWriter -> - generateIon(filteredDomains, printWriter) - } - } - } - - progress("universe generation complete!") -} - -private fun prepareOutputDirectory(dir: File) { - if (dir.exists()) { - if (!dir.isDirectory) { - error("The path specified as the output directory exists but is not a directory.") - } - } else { - dir.mkdirs() - } -} diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/model/Arity.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/Arity.kt index 08bbe4a..6a24575 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/model/Arity.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/model/Arity.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain.model +package org.partiql.pig.legacy.model /** Base class representing an element's arity. */ sealed class Arity { diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/model/DataType.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/DataType.kt index f40d964..9f86fae 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/model/DataType.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/model/DataType.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain.model +package org.partiql.pig.legacy.model import com.amazon.ionelement.api.MetaContainer import com.amazon.ionelement.api.emptyMetaContainer @@ -154,9 +154,8 @@ sealed class DataType { if (tag != other.tag) return false if (tupleType != other.tupleType) return false if (namedElements != other.namedElements) return false - if (isDifferent != other.isDifferent) return false + return isDifferent == other.isDifferent // Metas intentionally omitted here - return true } override fun hashCode(): kotlin.Int { @@ -191,10 +190,8 @@ sealed class DataType { if (tag != other.tag) return false if (variants != other.variants) return false - if (isDifferent != other.isDifferent) return false + return isDifferent == other.isDifferent // Metas intentionally omitted here - - return true } override fun hashCode(): kotlin.Int { diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/model/NamedElement.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/NamedElement.kt index d614762..16a2014 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/model/NamedElement.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/model/NamedElement.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain.model +package org.partiql.pig.legacy.model import com.amazon.ionelement.api.MetaContainer @@ -33,10 +33,8 @@ data class NamedElement( if (identifier != other.identifier) return false if (tag != other.tag) return false - if (typeReference != other.typeReference) return false + return typeReference == other.typeReference // Metas intentionally omitted here - - return true } override fun hashCode(): Int { diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/model/SemanticErrorContext.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/SemanticErrorContext.kt index 543c09c..b47b34b 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/model/SemanticErrorContext.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/model/SemanticErrorContext.kt @@ -13,14 +13,14 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain.model +package org.partiql.pig.legacy.model import com.amazon.ionelement.api.IonLocation import com.amazon.ionelement.api.MetaContainer import com.amazon.ionelement.api.location -import org.partiql.pig.errors.ErrorContext -import org.partiql.pig.errors.PigError -import org.partiql.pig.errors.PigException +import org.partiql.pig.legacy.errors.ErrorContext +import org.partiql.pig.legacy.errors.PigError +import org.partiql.pig.legacy.errors.PigException /** * Encapsulates all error context information in an easily testable way. @@ -110,6 +110,7 @@ sealed class SemanticErrorContext(val msgFormatter: () -> String) : ErrorContext */ fun semanticError(blame: MetaContainer, context: ErrorContext): Nothing = semanticError(blame.location, context) + /** * Shortcut for throwing [PigException] with the specified metas and [PigError]. */ diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/model/Statement.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/Statement.kt index e686d3c..f983498 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/model/Statement.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/model/Statement.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain.model +package org.partiql.pig.legacy.model import com.amazon.ionelement.api.MetaContainer import com.amazon.ionelement.api.emptyMetaContainer @@ -61,7 +61,6 @@ class TypeDomain( * data types. */ fun computeTransform(destination: TypeDomain): TypeDomain { - val destUserTypes = destination.userTypes val transformedTypes = userTypes @@ -106,10 +105,8 @@ class TypeDomain( if (tag != other.tag) return false if (userTypes != other.userTypes) return false - if (types != other.types) return false + return types == other.types // Metas intentionally omitted here - - return true } override fun hashCode(): Int { @@ -202,9 +199,10 @@ data class PermutedDomain( metas = metas ) - if (!newTypes.remove(typeToAlter)) - // If this happens it's a bug + if (!newTypes.remove(typeToAlter)) { + // If this happens it's a bug error("Failed to remove altered type '${typeToAlter.tag}' for some reason") + } newTypes.add(newSumType) } @@ -225,10 +223,8 @@ data class PermutedDomain( if (permutesDomain != other.permutesDomain) return false if (excludedTypes != other.excludedTypes) return false if (includedTypes != other.includedTypes) return false - if (permutedSums != other.permutedSums) return false + return permutedSums == other.permutedSums // Metas intentionally omitted here - - return true } override fun hashCode(): Int { @@ -256,10 +252,8 @@ data class PermutedSum( if (tag != other.tag) return false if (removedVariants != other.removedVariants) return false - if (addedVariants != other.addedVariants) return false + return addedVariants == other.addedVariants // Metas intentionally omitted here - - return true } override fun hashCode(): Int { @@ -286,9 +280,7 @@ data class Transform( if (other !is Transform) return false if (sourceDomainTag != other.sourceDomainTag) return false - if (destinationDomainTag != other.destinationDomainTag) return false - - return true + return destinationDomainTag == other.destinationDomainTag } override fun hashCode(): Int { diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/model/TupleType.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/TupleType.kt index eeb3a1a..45f5521 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/model/TupleType.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/model/TupleType.kt @@ -13,10 +13,10 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain.model +package org.partiql.pig.legacy.model /** Indicates the type of tuple. */ enum class TupleType { PRODUCT, - RECORD, + RECORD } diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeAnnotation.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeAnnotation.kt index f32fdde..fc4ea33 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeAnnotation.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeAnnotation.kt @@ -1,4 +1,4 @@ -package org.partiql.pig.domain.model +package org.partiql.pig.legacy.model /** * Annotations that are allowed on a UserType definition diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeDomainSemanticChecker.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeDomainSemanticChecker.kt index ae7b6ad..27d91b9 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeDomainSemanticChecker.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeDomainSemanticChecker.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain.model +package org.partiql.pig.legacy.model /** * Performs semantic checking for [TypeDomain]. @@ -221,7 +221,8 @@ private class TypeDomainSemanticChecker(private val typeDomain: TypeDomain) { private fun checkProductIonFieldArity(p: DataType.UserType.Tuple) = p.namedElements.forEach { element -> val dataType = typeDomain.resolveTypeRef(element.typeReference) - if (dataType == DataType.Ion && element.typeReference.arity == Arity.Optional) + if (dataType == DataType.Ion && element.typeReference.arity == Arity.Optional) { semanticError(element.metas, SemanticErrorContext.OptionalIonTypeElement) + } } } diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeRef.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeRef.kt index f4987cd..08533ca 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeRef.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeRef.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain.model +package org.partiql.pig.legacy.model import com.amazon.ionelement.api.IonElement import com.amazon.ionelement.api.MetaContainer @@ -40,10 +40,8 @@ data class TypeRef(val typeName: String, val arity: Arity, val metas: MetaContai if (other !is TypeRef) return false if (typeName != other.typeName) return false - if (arity != other.arity) return false + return arity == other.arity // Metas intentionally omitted here - - return true } override fun hashCode(): Int { diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeUniverse.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeUniverse.kt index 7d8721f..79e3e0c 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeUniverse.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/model/TypeUniverse.kt @@ -13,9 +13,9 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain.model +package org.partiql.pig.legacy.model -import org.partiql.pig.errors.PigException +import org.partiql.pig.legacy.errors.PigException data class TypeUniverse(val statements: List) { diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/parser/ParserErrorContext.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/parser/ParserErrorContext.kt index a3cf009..4907027 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/parser/ParserErrorContext.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/parser/ParserErrorContext.kt @@ -13,17 +13,17 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain.parser +package org.partiql.pig.legacy.parser import com.amazon.ionelement.api.ElementType import com.amazon.ionelement.api.IonElement import com.amazon.ionelement.api.IonElementException import com.amazon.ionelement.api.IonLocation import com.amazon.ionelement.api.location -import org.partiql.pig.domain.model.TypeAnnotation -import org.partiql.pig.errors.ErrorContext -import org.partiql.pig.errors.PigError -import org.partiql.pig.errors.PigException +import org.partiql.pig.legacy.errors.ErrorContext +import org.partiql.pig.legacy.errors.PigError +import org.partiql.pig.legacy.errors.PigException +import org.partiql.pig.legacy.model.TypeAnnotation /** * Variants of [ParserErrorContext] contain details about various parse errors that can be encountered diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/parser/TypeDomainParser.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/parser/TypeDomainParser.kt index 4f7d031..54377e1 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/parser/TypeDomainParser.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/parser/TypeDomainParser.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain.parser +package org.partiql.pig.legacy.parser import com.amazon.ion.IonReader import com.amazon.ion.system.IonReaderBuilder @@ -25,21 +25,21 @@ import com.amazon.ionelement.api.MetaContainer import com.amazon.ionelement.api.SexpElement import com.amazon.ionelement.api.SymbolElement import com.amazon.ionelement.api.createIonElementLoader -import org.partiql.pig.domain.model.Arity -import org.partiql.pig.domain.model.DataType -import org.partiql.pig.domain.model.NamedElement -import org.partiql.pig.domain.model.PermutedDomain -import org.partiql.pig.domain.model.PermutedSum -import org.partiql.pig.domain.model.Statement -import org.partiql.pig.domain.model.Transform -import org.partiql.pig.domain.model.TupleType -import org.partiql.pig.domain.model.TypeAnnotation -import org.partiql.pig.domain.model.TypeDomain -import org.partiql.pig.domain.model.TypeRef -import org.partiql.pig.domain.model.TypeUniverse -import org.partiql.pig.util.head -import org.partiql.pig.util.tag -import org.partiql.pig.util.tail +import org.partiql.pig.legacy.model.Arity +import org.partiql.pig.legacy.model.DataType +import org.partiql.pig.legacy.model.NamedElement +import org.partiql.pig.legacy.model.PermutedDomain +import org.partiql.pig.legacy.model.PermutedSum +import org.partiql.pig.legacy.model.Statement +import org.partiql.pig.legacy.model.Transform +import org.partiql.pig.legacy.model.TupleType +import org.partiql.pig.legacy.model.TypeAnnotation +import org.partiql.pig.legacy.model.TypeDomain +import org.partiql.pig.legacy.model.TypeRef +import org.partiql.pig.legacy.model.TypeUniverse +import org.partiql.pig.legacy.util.head +import org.partiql.pig.legacy.util.tag +import org.partiql.pig.legacy.util.tail /** Parses a type universe contained in [universeText]. */ fun parseTypeUniverse(universeText: String) = @@ -129,7 +129,7 @@ private fun parseDomainLevelStatement(sexp: SexpElement): DataType.UserType { private fun parseVariant( bodyArguments: List, metas: MetaContainer, - annotations: List, + annotations: List ): DataType.UserType.Tuple { val elements = bodyArguments.tail diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/util/Capitalizers.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/util/Capitalizers.kt index ba6503b..0721f8d 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/util/Capitalizers.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/util/Capitalizers.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.util +package org.partiql.pig.legacy.util fun String.snakeToPascalCase(): String = this.split('_') diff --git a/pig/src/main/kotlin/org/partiql/pig/legacy/util/IonElement.kt b/pig/src/main/kotlin/org/partiql/pig/legacy/util/IonElement.kt index ae72ca6..11cdacd 100644 --- a/pig/src/main/kotlin/org/partiql/pig/legacy/util/IonElement.kt +++ b/pig/src/main/kotlin/org/partiql/pig/legacy/util/IonElement.kt @@ -1,4 +1,4 @@ -package org.partiql.pig.util +package org.partiql.pig.legacy.util import com.amazon.ionelement.api.AnyElement import com.amazon.ionelement.api.IonElementException diff --git a/pig/src/main/kotlin/org/partiql/pig/model/Model.kt b/pig/src/main/kotlin/org/partiql/pig/model/Model.kt index b568240..2929145 100644 --- a/pig/src/main/kotlin/org/partiql/pig/model/Model.kt +++ b/pig/src/main/kotlin/org/partiql/pig/model/Model.kt @@ -5,12 +5,12 @@ import net.pearx.kasechange.toPascalCase typealias Imports = Map /** - * Top-level model of a Sprout grammar + * Top-level model of a Pig grammar */ class Universe( val id: String, val types: List, - val imports: Map, + val imports: Map ) { fun forEachType(action: (TypeDef) -> Unit) { @@ -91,7 +91,7 @@ sealed class TypeDef(val ref: TypeRef.Path) { */ sealed class TypeRef( val id: String, - val nullable: Boolean, + val nullable: Boolean ) { override fun equals(other: Any?) = @@ -103,43 +103,43 @@ sealed class TypeRef( class Scalar( val type: ScalarType, - nullable: Boolean = false, + nullable: Boolean = false ) : TypeRef( id = type.toString().toLowerCase(), - nullable = nullable, + nullable = nullable ) class List( val type: TypeRef, - nullable: Boolean = false, + nullable: Boolean = false ) : TypeRef( id = "list<$type>", - nullable = nullable, + nullable = nullable ) class Set( val type: TypeRef, - nullable: Boolean = false, + nullable: Boolean = false ) : TypeRef( id = "set<$type>", - nullable = nullable, + nullable = nullable ) class Map( val keyType: Scalar, val valType: TypeRef, - nullable: Boolean = false, + nullable: Boolean = false ) : TypeRef( id = "map<$keyType, $valType>", - nullable = nullable, + nullable = nullable ) class Path( nullable: Boolean = false, - vararg ids: String, + vararg ids: String ) : TypeRef( id = ids.joinToString("."), - nullable = nullable, + nullable = nullable ) { val path = ids.asList() val name = ids.last().toPascalCase() @@ -147,7 +147,7 @@ sealed class TypeRef( class Import( val symbol: String, - nullable: Boolean = false, + nullable: Boolean = false ) : TypeRef( id = "import<$symbol>", nullable = nullable @@ -159,7 +159,7 @@ sealed class TypeRef( */ sealed class TypeProp( val name: String, - val ref: TypeRef, + val ref: TypeRef ) { override fun toString() = "$name: $ref" @@ -183,5 +183,5 @@ enum class ScalarType { FLOAT, // IEEE 754 (32 bit) DOUBLE, // IEEE 754 (64 bit) BYTES, // Array of unsigned bytes - STRING, // Unicode char sequence + STRING // Unicode char sequence } diff --git a/pig/src/main/kotlin/org/partiql/pig/parser/SproutParser.kt b/pig/src/main/kotlin/org/partiql/pig/parser/PigParser.kt similarity index 92% rename from pig/src/main/kotlin/org/partiql/pig/parser/SproutParser.kt rename to pig/src/main/kotlin/org/partiql/pig/parser/PigParser.kt index e9948dd..56df863 100644 --- a/pig/src/main/kotlin/org/partiql/pig/parser/SproutParser.kt +++ b/pig/src/main/kotlin/org/partiql/pig/parser/PigParser.kt @@ -21,7 +21,7 @@ import org.partiql.pig.parser.ion.IonTypeParser /** * Returns a model of the input type universe definition. */ -interface SproutParser { +interface PigParser { /** * @param id Type Universe identifier @@ -32,6 +32,6 @@ interface SproutParser { companion object { - fun default(): SproutParser = IonTypeParser + fun default(): PigParser = IonTypeParser } } diff --git a/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonExtensions.kt b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonExtensions.kt index 1f09008..24bfc24 100644 --- a/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonExtensions.kt +++ b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonExtensions.kt @@ -126,7 +126,7 @@ internal fun IonValue.ref(): Pair = when (this) { } else -> err( expected = "optional::$suffix or $suffix", - found = "$prefix::$suffix", + found = "$prefix::$suffix" ) } } @@ -147,7 +147,7 @@ internal fun IonValue.ref(): Pair = when (this) { } else -> err( expected = "optional::$suffix or $suffix", - found = "$prefix::$suffix", + found = "$prefix::$suffix" ) } } diff --git a/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonImports.kt b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonImports.kt index c8da182..d048ec3 100644 --- a/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonImports.kt +++ b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonImports.kt @@ -15,7 +15,7 @@ import org.partiql.pig.model.Imports */ internal class IonImports private constructor( val symbols: Set, - val map: Map, + val map: Map ) { companion object { diff --git a/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonSymbols.kt b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonSymbols.kt index bf6070b..f1aa35b 100644 --- a/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonSymbols.kt +++ b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonSymbols.kt @@ -72,7 +72,7 @@ internal class IonSymbols private constructor(val root: Node) { assertNonReserved(id, if (parent != null) "child of $parent" else "top-level type") val node = Node( id = id, - parent = parent, + parent = parent ) v.forEach { field -> when { diff --git a/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonTypeParser.kt b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonTypeParser.kt index 26c8c46..682928a 100644 --- a/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonTypeParser.kt +++ b/pig/src/main/kotlin/org/partiql/pig/parser/ion/IonTypeParser.kt @@ -27,12 +27,12 @@ import org.partiql.pig.model.TypeDef import org.partiql.pig.model.TypeProp import org.partiql.pig.model.TypeRef import org.partiql.pig.model.Universe -import org.partiql.pig.parser.SproutParser +import org.partiql.pig.parser.PigParser /** * Parser for the prototype .ion grammar */ -internal object IonTypeParser : SproutParser { +internal object IonTypeParser : PigParser { private val ion = IonSystemBuilder.standard().build() @@ -78,7 +78,7 @@ internal object IonTypeParser : SproutParser { val ctx = Context(symbols.root, imports) Visitor.visit(it, ctx) }, - imports = imports.map, + imports = imports.map ) } @@ -182,7 +182,7 @@ internal object IonTypeParser : SproutParser { */ private class Context( private val root: IonSymbols.Node, - private val imports: IonImports, + private val imports: IonImports ) { private var tip: IonSymbols.Node = root @@ -233,7 +233,7 @@ internal object IonTypeParser : SproutParser { node != null -> { TypeRef.Path( nullable = nullable, - ids = (node.path.toTypedArray()), + ids = (node.path.toTypedArray()) ) } path.size == 1 && imports.symbols.contains(path.first()) -> { @@ -249,7 +249,7 @@ internal object IonTypeParser : SproutParser { try { return TypeRef.Scalar( type = ScalarType.valueOf(symbol.toUpperCase()), - nullable = nullable, + nullable = nullable ) } catch (_: IllegalArgumentException) { } @@ -258,7 +258,7 @@ internal object IonTypeParser : SproutParser { if (node != null) { return TypeRef.Path( nullable = nullable, - ids = (node.path.toTypedArray()), + ids = (node.path.toTypedArray()) ) } // 4. Attempt to find the symbol in the imports diff --git a/pig/src/test/kotlin/org/partiql/pig/cmdline/CommandLineParserTests.kt b/pig/src/test/kotlin/org/partiql/pig/cmdline/CommandLineParserTests.kt deleted file mode 100644 index 19e88cc..0000000 --- a/pig/src/test/kotlin/org/partiql/pig/cmdline/CommandLineParserTests.kt +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file 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. - */ - -package org.partiql.pig.cmdline - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.MethodSource -import java.io.File - -class CommandLineParserTests { - - @ParameterizedTest - @MethodSource("parametersForTests") - fun tests(tc: TestCase) { - val parser = CommandLineParser() - val action = parser.parse(tc.args.toTypedArray()) - - assertEquals(tc.expected, action) - } - - companion object { - - class TestCase(val expected: Command, val args: List) { - constructor(expected: Command, vararg args: String) : this(expected, args.toList()) - } - - @JvmStatic - @Suppress("unused") - fun parametersForTests() = listOf( - // Help - TestCase(Command.ShowHelp, "-h"), - TestCase(Command.ShowHelp, "--help"), - - // ////////////////////////////////////////////////////// - // Missing parameters required for all language targets - // ////////////////////////////////////////////////////// - // No --universe - TestCase( - Command.InvalidCommandLineArguments("Missing required option(s) [u/universe]"), - "--target=kotlin", "--output-directory=out_dir", "--namespace=some.package" - ), - - // No --target - TestCase( - Command.InvalidCommandLineArguments("Missing required option(s) [t/target]"), - "--universe=input.ion", "--output-directory=out_dir", "--namespace=some.package" - ), - - // No --output-file argument - TestCase( - Command.InvalidCommandLineArguments("The selected language target requires the --output-file argument"), - "--universe=input.ion", "--target=custom", "--template=some.template" - ), - - // No --output-directory argument - TestCase( - Command.InvalidCommandLineArguments("The selected language target requires the --output-directory argument"), - "--universe=input.ion", "--target=kotlin", "--namespace=some.package" - ), - - // ////////////////////////////////////////////////////// - // Kotlin target - // ////////////////////////////////////////////////////// - // long parameter names - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Kotlin("some.package", File("out_dir"))), - "--universe=input.ion", "--target=kotlin", "--output-directory=out_dir", "--namespace=some.package" - ), - - // short parameter names - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Kotlin("some.package", File("out_dir"))), - "-u=input.ion", "-t=kotlin", "-d=out_dir", "-n=some.package" - ), - - // accepts --domains to filter for specific domains - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Kotlin("some.package", File("out_dir"), domains = setOf("foo", "bar"))), - "--universe=input.ion", "--target=kotlin", "--output-directory=out_dir", "--namespace=some.package", "--domains=foo,bar" - ), - - // accepts -f to filter for specific domains - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Kotlin("some.package", File("out_dir"), domains = setOf("foo", "bar"))), - "-u=input.ion", "-t=kotlin", "-d=out_dir", "-n=some.package", "-f=foo,bar" - ), - - // missing the --namespace argument - TestCase( - Command.InvalidCommandLineArguments("The selected language target requires the --namespace argument"), - "-u=input.ion", "-t=kotlin", "-o=out_dir" - ), - - // ////////////////////////////////////////////////////// - // Html target - // ////////////////////////////////////////////////////// - // long parameter names - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Html(File("output.html"))), - "--universe=input.ion", "--target=html", "--output-file=output.html" - ), - - // short parameter names - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Html(File("output.html"))), - "-u=input.ion", "-target=html", "--output-file=output.html" - ), - - // accepts --domains to filter for specific domains - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Html(File("output.html"), domains = setOf("foo", "bar"))), - "--universe=input.ion", "--target=html", "--output-file=output.html", "--domains=foo,bar" - ), - - // accepts -f to filter for specific domains - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Html(File("output.html"), domains = setOf("foo", "bar"))), - "-u=input.ion", "-target=html", "--output-file=output.html", "-f=foo,bar" - ), - - // ////////////////////////////////////////////////////// - // Ion target - // ////////////////////////////////////////////////////// - // long parameter names - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Ion(File("output.ion"))), - "--universe=input.ion", "--target=ion", "--output-file=output.ion" - ), - - // short parameter names - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Ion(File("output.ion"))), - "-u=input.ion", "-target=ion", "--output-file=output.ion" - ), - - // accepts --domains to filter for specific domains - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Ion(File("output.ion"), domains = setOf("foo", "bar"))), - "--universe=input.ion", "--target=ion", "--output-file=output.ion", "--domains=foo,bar" - ), - - // accepts -f to filter for specific domains - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Ion(File("output.ion"), domains = setOf("foo", "bar"))), - "-u=input.ion", "-target=ion", "--output-file=output.ion", "-f=foo,bar" - ), - - // ////////////////////////////////////////////////////// - // Custom target - // ////////////////////////////////////////////////////// - // long parameter names - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Custom(File("template.ftl"), File("output.txt"))), - "--universe=input.ion", "--target=custom", "--output-file=output.txt", "--template=template.ftl" - ), - - // short parameter names - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Custom(File("template.ftl"), File("output.txt"))), - "-u=input.ion", "-t=custom", "-o=output.txt", "-e=template.ftl" - ), - - // accepts --domains to filter for specific domains - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Custom(File("template.ftl"), File("output.txt"), domains = setOf("foo", "bar"))), - "--universe=input.ion", "--target=custom", "--output-file=output.txt", "--template=template.ftl", "--domains=foo,bar" - ), - - // accepts -f to filter for specific domains - TestCase( - Command.Generate(File("input.ion"), TargetLanguage.Custom(File("template.ftl"), File("output.txt"), domains = setOf("foo", "bar"))), - "-u=input.ion", "-target=custom", "--output-file=output.txt", "-e=template.ftl", "-f=foo,bar" - ), - - // missing the --template argument - TestCase( - Command.InvalidCommandLineArguments("The selected language target requires the --template argument"), - "-u=input.ion", "-t=custom", "-o=output.kt" - ) - ) - } -} diff --git a/pig/src/test/kotlin/org/partiql/pig/legacy/cmdline/CommandLineParserTests.kt b/pig/src/test/kotlin/org/partiql/pig/legacy/cmdline/CommandLineParserTests.kt new file mode 100644 index 0000000..770725c --- /dev/null +++ b/pig/src/test/kotlin/org/partiql/pig/legacy/cmdline/CommandLineParserTests.kt @@ -0,0 +1,340 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +package org.partiql.pig.legacy.cmdline + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import org.partiql.pig.Pig +import org.partiql.pig.legacy.LegacyCommand +import picocli.CommandLine +import java.io.File + +class CommandLineParserTests { + + class CommandLineParser { + + fun parse(args: Array): Command { + val cli = CommandLine(Pig()).setCaseInsensitiveEnumValuesAllowed(true) + cli.execute(*args) + return LegacyCommand.previous!! + } + } + + @ParameterizedTest(name = "TestCase: {index} : {0}") + @MethodSource("parametersForTests") + fun tests(tc: TestCase) { + val parser = CommandLineParser() + val action = parser.parse(tc.args.toTypedArray()) + + assertEquals(tc.expected, action) + } + + companion object { + + class TestCase(val expected: Command, val args: List) { + constructor(expected: Command, vararg args: String) : this(expected, args.toList()) + + override fun toString() = "pig ${args.joinToString(" ")}" + } + + @JvmStatic + @Suppress("unused") + fun parametersForTests() = listOf( + // // 1. Help + // TestCase(Command.ShowHelp, "legacy", "--capture", "-h"), + // // 2. Help + // TestCase(Command.ShowHelp, "legacy", "--capture", "--help"), + // + // // ////////////////////////////////////////////////////// + // // Missing parameters required for all language targets + // // ////////////////////////////////////////////////////// + // // 3. No --universe + // TestCase( + // Command.InvalidCommandLineArguments("Missing required option(s) [u/universe]"), + // "legacy", + // "--capture", + // "--target=kotlin", + // "--output-directory=out_dir", + // "--namespace=some.package" + // ), + // + // // 4. No --target + // TestCase( + // Command.InvalidCommandLineArguments("Missing required option(s) [t/target]"), + // "legacy", + // "--capture", + // "--universe=input.ion", + // "--output-directory=out_dir", + // "--namespace=some.package" + // ), + // + // // 5. No --output-file argument + // TestCase( + // Command.InvalidCommandLineArguments("The selected language target CUSTOM requires the --output-file argument"), + // "legacy", + // "--capture", + // "--universe=input.ion", + // "--target=custom", + // "--template=some.template" + // ), + // + // // 6. No --output-directory argument + // TestCase( + // Command.InvalidCommandLineArguments("The selected language target KOTLIN requires the --output-directory argument"), + // "legacy", + // "--capture", + // "--universe=input.ion", + // "--target=kotlin", + // "--namespace=some.package" + // ), + + // ////////////////////////////////////////////////////// + // Kotlin target + // ////////////////////////////////////////////////////// + + // 7. long parameter names + TestCase( + Command.Generate(File("input.ion"), TargetLanguage.Kotlin("some.package", File("out_dir"))), + "legacy", + "--capture", + "--universe=input.ion", + "--target=kotlin", + "--output-directory=out_dir", + "--namespace=some.package" + ), + + // 8. short parameter names + TestCase( + Command.Generate(File("input.ion"), TargetLanguage.Kotlin("some.package", File("out_dir"))), + "legacy", + "--capture", + "-u=input.ion", + "-t=kotlin", + "-d=out_dir", + "-n=some.package" + ), + + // 9. accepts --domains to filter for specific domains + TestCase( + Command.Generate( + File("input.ion"), + TargetLanguage.Kotlin("some.package", File("out_dir"), domains = setOf("foo", "bar")) + ), + "legacy", + "--capture", + "--universe=input.ion", + "--target=kotlin", + "--output-directory=out_dir", + "--namespace=some.package", + "--domains=foo,bar" + ), + + // 10. accepts -f to filter for specific domains + TestCase( + Command.Generate( + File("input.ion"), + TargetLanguage.Kotlin("some.package", File("out_dir"), domains = setOf("foo", "bar")) + ), + "legacy", + "--capture", + "-u=input.ion", + "-t=kotlin", + "-d=out_dir", + "-n=some.package", + "-f=foo,bar" + ), + + // 11. missing the --namespace argument + TestCase( + Command.InvalidCommandLineArguments("The selected language target KOTLIN requires the --namespace argument"), + "legacy", + "--capture", + "-u=input.ion", + "-t=kotlin", + "-o=out_dir" + ), + + // ////////////////////////////////////////////////////// + // Html target + // ////////////////////////////////////////////////////// + + // 12. long parameter names + TestCase( + Command.Generate(File("input.ion"), TargetLanguage.Html(File("output.html"))), + "legacy", + "--capture", + "--universe=input.ion", + "--target=html", + "--output-file=output.html" + ), + + // 13. short parameter names + TestCase( + Command.Generate(File("input.ion"), TargetLanguage.Html(File("output.html"))), + "legacy", + "--capture", + "-u=input.ion", + "--target=html", + "--output-file=output.html" + ), + + // 14. accepts --domains to filter for specific domains + TestCase( + Command.Generate( + File("input.ion"), + TargetLanguage.Html(File("output.html"), domains = setOf("foo", "bar")) + ), + "legacy", + "--capture", + "--universe=input.ion", + "--target=html", + "--output-file=output.html", + "--domains=foo,bar" + ), + + // 15. accepts -f to filter for specific domains + TestCase( + Command.Generate( + File("input.ion"), + TargetLanguage.Html(File("output.html"), domains = setOf("foo", "bar")) + ), + "legacy", + "--capture", + "-u=input.ion", + "--target=html", + "--output-file=output.html", + "-f=foo,bar" + ), + + // ////////////////////////////////////////////////////// + // Ion target + // ////////////////////////////////////////////////////// + // long parameter names + TestCase( + Command.Generate(File("input.ion"), TargetLanguage.Ion(File("output.ion"))), + "legacy", + "--capture", + "--universe=input.ion", + "--target=ion", + "--output-file=output.ion" + ), + + // short parameter names + TestCase( + Command.Generate(File("input.ion"), TargetLanguage.Ion(File("output.ion"))), + "legacy", + "--capture", + "-u=input.ion", + "--target=ion", + "--output-file=output.ion" + ), + + // accepts --domains to filter for specific domains + TestCase( + Command.Generate( + File("input.ion"), + TargetLanguage.Ion(File("output.ion"), domains = setOf("foo", "bar")) + ), + "legacy", + "--capture", + "--universe=input.ion", + "--target=ion", + "--output-file=output.ion", + "--domains=foo,bar" + ), + + // accepts -f to filter for specific domains + TestCase( + Command.Generate( + File("input.ion"), + TargetLanguage.Ion(File("output.ion"), domains = setOf("foo", "bar")) + ), + "legacy", + "--capture", + "-u=input.ion", + "--target=ion", + "--output-file=output.ion", + "-f=foo,bar" + ), + + // ////////////////////////////////////////////////////// + // Custom target + // ////////////////////////////////////////////////////// + // long parameter names + TestCase( + Command.Generate(File("input.ion"), TargetLanguage.Custom(File("template.ftl"), File("output.txt"))), + "legacy", + "--capture", + "--universe=input.ion", + "--target=custom", + "--output-file=output.txt", + "--template=template.ftl" + ), + + // short parameter names + TestCase( + Command.Generate(File("input.ion"), TargetLanguage.Custom(File("template.ftl"), File("output.txt"))), + "legacy", + "--capture", + "-u=input.ion", + "-t=custom", + "-o=output.txt", + "-e=template.ftl" + ), + + // accepts --domains to filter for specific domains + TestCase( + Command.Generate( + File("input.ion"), + TargetLanguage.Custom(File("template.ftl"), File("output.txt"), domains = setOf("foo", "bar")) + ), + "legacy", + "--capture", + "--universe=input.ion", + "--target=custom", + "--output-file=output.txt", + "--template=template.ftl", + "--domains=foo,bar" + ), + + // accepts -f to filter for specific domains + TestCase( + Command.Generate( + File("input.ion"), + TargetLanguage.Custom(File("template.ftl"), File("output.txt"), domains = setOf("foo", "bar")) + ), + "legacy", + "--capture", + "-u=input.ion", + "--target=custom", + "--output-file=output.txt", + "-e=template.ftl", + "-f=foo,bar" + ), + + // missing the --template argument + TestCase( + Command.InvalidCommandLineArguments("The selected language target CUSTOM requires the --template argument"), + "legacy", + "--capture", + "-u=input.ion", + "-t=custom", + "-o=output.kt" + ) + ) + } +} diff --git a/pig/src/test/kotlin/org/partiql/pig/domain/PermuteDomainTests.kt b/pig/src/test/kotlin/org/partiql/pig/legacy/domain/PermuteDomainTests.kt similarity index 95% rename from pig/src/test/kotlin/org/partiql/pig/domain/PermuteDomainTests.kt rename to pig/src/test/kotlin/org/partiql/pig/legacy/domain/PermuteDomainTests.kt index b116877..5e9b16f 100644 --- a/pig/src/test/kotlin/org/partiql/pig/domain/PermuteDomainTests.kt +++ b/pig/src/test/kotlin/org/partiql/pig/legacy/domain/PermuteDomainTests.kt @@ -13,16 +13,16 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain +package org.partiql.pig.legacy.domain import com.amazon.ion.system.IonReaderBuilder import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test -import org.partiql.pig.domain.model.Arity -import org.partiql.pig.domain.model.DataType -import org.partiql.pig.domain.model.TypeUniverse -import org.partiql.pig.domain.parser.parseTypeUniverse +import org.partiql.pig.legacy.model.Arity +import org.partiql.pig.legacy.model.DataType +import org.partiql.pig.legacy.model.TypeUniverse +import org.partiql.pig.legacy.parser.parseTypeUniverse class PermuteDomainTests { /** diff --git a/pig/src/test/kotlin/org/partiql/pig/domain/TypeAnnotationParserTests.kt b/pig/src/test/kotlin/org/partiql/pig/legacy/domain/TypeAnnotationParserTests.kt similarity index 92% rename from pig/src/test/kotlin/org/partiql/pig/domain/TypeAnnotationParserTests.kt rename to pig/src/test/kotlin/org/partiql/pig/legacy/domain/TypeAnnotationParserTests.kt index baccf02..501bdd6 100644 --- a/pig/src/test/kotlin/org/partiql/pig/domain/TypeAnnotationParserTests.kt +++ b/pig/src/test/kotlin/org/partiql/pig/legacy/domain/TypeAnnotationParserTests.kt @@ -1,12 +1,12 @@ -package org.partiql.pig.domain +package org.partiql.pig.legacy.domain import com.amazon.ion.system.IonReaderBuilder import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource -import org.partiql.pig.domain.model.DataType -import org.partiql.pig.domain.model.TypeAnnotation -import org.partiql.pig.domain.parser.parseTypeUniverse +import org.partiql.pig.legacy.model.DataType +import org.partiql.pig.legacy.model.TypeAnnotation +import org.partiql.pig.legacy.parser.parseTypeUniverse class TypeAnnotationParserTests { @@ -57,7 +57,7 @@ class TypeAnnotationParserTests { data class TestCase( val definition: String, - val assertion: (type: DataType.UserType) -> Unit, + val assertion: (type: DataType.UserType) -> Unit ) @JvmStatic @@ -82,7 +82,7 @@ class TypeAnnotationParserTests { "other" -> assert(v.annotations.isEmpty()) } } - }, + } ) } } diff --git a/pig/src/test/kotlin/org/partiql/pig/domain/TypeDomainParserErrorsTest.kt b/pig/src/test/kotlin/org/partiql/pig/legacy/domain/TypeDomainParserErrorsTest.kt similarity index 94% rename from pig/src/test/kotlin/org/partiql/pig/domain/TypeDomainParserErrorsTest.kt rename to pig/src/test/kotlin/org/partiql/pig/legacy/domain/TypeDomainParserErrorsTest.kt index 7e64a2c..ec97282 100644 --- a/pig/src/test/kotlin/org/partiql/pig/domain/TypeDomainParserErrorsTest.kt +++ b/pig/src/test/kotlin/org/partiql/pig/legacy/domain/TypeDomainParserErrorsTest.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain +package org.partiql.pig.legacy.domain import com.amazon.ionelement.api.ElementType import com.amazon.ionelement.api.IonElementException @@ -23,10 +23,11 @@ import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource -import org.partiql.pig.domain.parser.ParserErrorContext -import org.partiql.pig.domain.parser.parseTypeUniverse -import org.partiql.pig.errors.PigError -import org.partiql.pig.errors.PigException +import org.partiql.pig.legacy.errors.PigError +import org.partiql.pig.legacy.errors.PigException +import org.partiql.pig.legacy.parser.ParserErrorContext +import org.partiql.pig.legacy.parser.parseTypeUniverse +import org.partiql.pig.legacy.toIonElement class TypeDomainParserErrorsTest { diff --git a/pig/src/test/kotlin/org/partiql/pig/domain/TypeDomainParserTests.kt b/pig/src/test/kotlin/org/partiql/pig/legacy/domain/TypeDomainParserTests.kt similarity index 96% rename from pig/src/test/kotlin/org/partiql/pig/domain/TypeDomainParserTests.kt rename to pig/src/test/kotlin/org/partiql/pig/legacy/domain/TypeDomainParserTests.kt index 1171b00..c68ae13 100644 --- a/pig/src/test/kotlin/org/partiql/pig/domain/TypeDomainParserTests.kt +++ b/pig/src/test/kotlin/org/partiql/pig/legacy/domain/TypeDomainParserTests.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain +package org.partiql.pig.legacy.domain import com.amazon.ion.system.IonReaderBuilder import com.amazon.ionelement.api.IonElementLoaderOptions @@ -23,7 +23,8 @@ import com.amazon.ionelement.api.ionSymbol import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow -import org.partiql.pig.domain.parser.parseTypeUniverse +import org.partiql.pig.legacy.parser.parseTypeUniverse +import org.partiql.pig.legacy.toIonElement class TypeDomainParserTests { private val loader = createIonElementLoader(IonElementLoaderOptions(includeLocationMeta = true)) @@ -37,6 +38,7 @@ class TypeDomainParserTests { (product bar a::bat b::(? baz) c::(* blargh 10)))) """ ) + @Test fun testTransform() = runTestCase("(transform domain_a domain_b)") diff --git a/pig/src/test/kotlin/org/partiql/pig/domain/TypeDomainSemanticCheckerTests.kt b/pig/src/test/kotlin/org/partiql/pig/legacy/domain/TypeDomainSemanticCheckerTests.kt similarity index 98% rename from pig/src/test/kotlin/org/partiql/pig/domain/TypeDomainSemanticCheckerTests.kt rename to pig/src/test/kotlin/org/partiql/pig/legacy/domain/TypeDomainSemanticCheckerTests.kt index 0c4ede3..2b3efd5 100644 --- a/pig/src/test/kotlin/org/partiql/pig/domain/TypeDomainSemanticCheckerTests.kt +++ b/pig/src/test/kotlin/org/partiql/pig/legacy/domain/TypeDomainSemanticCheckerTests.kt @@ -13,16 +13,16 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain +package org.partiql.pig.legacy.domain import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource -import org.partiql.pig.domain.model.SemanticErrorContext -import org.partiql.pig.domain.parser.parseTypeUniverse -import org.partiql.pig.errors.PigError -import org.partiql.pig.errors.PigException +import org.partiql.pig.legacy.errors.PigError +import org.partiql.pig.legacy.errors.PigException +import org.partiql.pig.legacy.model.SemanticErrorContext +import org.partiql.pig.legacy.parser.parseTypeUniverse class TypeDomainSemanticCheckerTests { @@ -205,6 +205,7 @@ class TypeDomainSemanticCheckerTests { makeErr(1, 53, SemanticErrorContext.NameAlreadyUsed("dup_tag", "some_domain")) ) ) + @JvmStatic @Suppress("unused") fun parametersForNameErrorsTest2() = listOf( diff --git a/pig/src/test/kotlin/org/partiql/pig/domain/Util.kt b/pig/src/test/kotlin/org/partiql/pig/legacy/domain/Util.kt similarity index 86% rename from pig/src/test/kotlin/org/partiql/pig/domain/Util.kt rename to pig/src/test/kotlin/org/partiql/pig/legacy/domain/Util.kt index d6dbd05..b6a135d 100644 --- a/pig/src/test/kotlin/org/partiql/pig/domain/Util.kt +++ b/pig/src/test/kotlin/org/partiql/pig/legacy/domain/Util.kt @@ -13,11 +13,11 @@ * permissions and limitations under the License. */ -package org.partiql.pig.domain +package org.partiql.pig.legacy.domain import com.amazon.ionelement.api.IonTextLocation -import org.partiql.pig.errors.ErrorContext -import org.partiql.pig.errors.PigError +import org.partiql.pig.legacy.errors.ErrorContext +import org.partiql.pig.legacy.errors.PigError fun makeErr(line: Int, col: Int, errorContext: ErrorContext) = PigError(IonTextLocation(line.toLong(), col.toLong()), errorContext) diff --git a/pig/src/test/kotlin/org/partiql/pig/generator/FreeMarkerFunctionsTest.kt b/pig/src/test/kotlin/org/partiql/pig/legacy/generator/FreeMarkerFunctionsTest.kt similarity index 97% rename from pig/src/test/kotlin/org/partiql/pig/generator/FreeMarkerFunctionsTest.kt rename to pig/src/test/kotlin/org/partiql/pig/legacy/generator/FreeMarkerFunctionsTest.kt index 079368b..1a13331 100644 --- a/pig/src/test/kotlin/org/partiql/pig/generator/FreeMarkerFunctionsTest.kt +++ b/pig/src/test/kotlin/org/partiql/pig/legacy/generator/FreeMarkerFunctionsTest.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.generator +package org.partiql.pig.legacy.generator import freemarker.cache.StringTemplateLoader import org.junit.jupiter.api.Test diff --git a/pig/src/test/kotlin/org/partiql/pig/generator/custom/CreateCustomFreeMarkerGlobalsTest.kt b/pig/src/test/kotlin/org/partiql/pig/legacy/generator/custom/CreateCustomFreeMarkerGlobalsTest.kt similarity index 93% rename from pig/src/test/kotlin/org/partiql/pig/generator/custom/CreateCustomFreeMarkerGlobalsTest.kt rename to pig/src/test/kotlin/org/partiql/pig/legacy/generator/custom/CreateCustomFreeMarkerGlobalsTest.kt index 7ec4c1f..e0bf602 100644 --- a/pig/src/test/kotlin/org/partiql/pig/generator/custom/CreateCustomFreeMarkerGlobalsTest.kt +++ b/pig/src/test/kotlin/org/partiql/pig/legacy/generator/custom/CreateCustomFreeMarkerGlobalsTest.kt @@ -13,18 +13,18 @@ * permissions and limitations under the License. */ -package org.partiql.pig.generator.custom +package org.partiql.pig.legacy.generator.custom import com.amazon.ionelement.api.emptyMetaContainer import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.partiql.pig.domain.model.Arity -import org.partiql.pig.domain.model.DataType -import org.partiql.pig.domain.model.NamedElement -import org.partiql.pig.domain.model.TupleType -import org.partiql.pig.domain.model.TypeDomain -import org.partiql.pig.domain.model.TypeRef -import org.partiql.pig.domain.model.TypeUniverse +import org.partiql.pig.legacy.model.Arity +import org.partiql.pig.legacy.model.DataType +import org.partiql.pig.legacy.model.NamedElement +import org.partiql.pig.legacy.model.TupleType +import org.partiql.pig.legacy.model.TypeDomain +import org.partiql.pig.legacy.model.TypeRef +import org.partiql.pig.legacy.model.TypeUniverse import java.time.OffsetDateTime class CreateCustomFreeMarkerGlobalsTest { diff --git a/pig/src/test/kotlin/org/partiql/pig/generator/ion/GenerateIonTest.kt b/pig/src/test/kotlin/org/partiql/pig/legacy/generator/ion/GenerateIonTest.kt similarity index 96% rename from pig/src/test/kotlin/org/partiql/pig/generator/ion/GenerateIonTest.kt rename to pig/src/test/kotlin/org/partiql/pig/legacy/generator/ion/GenerateIonTest.kt index 2da9f00..f648cd0 100644 --- a/pig/src/test/kotlin/org/partiql/pig/generator/ion/GenerateIonTest.kt +++ b/pig/src/test/kotlin/org/partiql/pig/legacy/generator/ion/GenerateIonTest.kt @@ -1,8 +1,8 @@ -package org.partiql.pig.generator.ion +package org.partiql.pig.legacy.generator.ion import com.amazon.ion.system.IonReaderBuilder import org.junit.jupiter.api.Test -import org.partiql.pig.domain.parser.parseTypeUniverse +import org.partiql.pig.legacy.parser.parseTypeUniverse import java.io.PrintWriter import java.io.StringWriter import kotlin.test.assertEquals diff --git a/pig/src/test/kotlin/org/partiql/pig/generator/kotlin/KTypeDomainConverterKtTest.kt b/pig/src/test/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomainConverterKtTest.kt similarity index 96% rename from pig/src/test/kotlin/org/partiql/pig/generator/kotlin/KTypeDomainConverterKtTest.kt rename to pig/src/test/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomainConverterKtTest.kt index 46be685..7c85029 100644 --- a/pig/src/test/kotlin/org/partiql/pig/generator/kotlin/KTypeDomainConverterKtTest.kt +++ b/pig/src/test/kotlin/org/partiql/pig/legacy/generator/kotlin/KTypeDomainConverterKtTest.kt @@ -1,8 +1,8 @@ -package org.partiql.pig.generator.kotlin +package org.partiql.pig.legacy.generator.kotlin import com.amazon.ion.system.IonReaderBuilder import org.junit.jupiter.api.Test -import org.partiql.pig.domain.parser.parseTypeUniverse +import org.partiql.pig.legacy.parser.parseTypeUniverse import kotlin.test.assertEquals import kotlin.test.assertTrue diff --git a/pig/src/test/kotlin/org/partiql/pig/util/CapitalizerTests.kt b/pig/src/test/kotlin/org/partiql/pig/legacy/util/CapitalizerTests.kt similarity index 98% rename from pig/src/test/kotlin/org/partiql/pig/util/CapitalizerTests.kt rename to pig/src/test/kotlin/org/partiql/pig/legacy/util/CapitalizerTests.kt index 055b2fd..0fa5da7 100644 --- a/pig/src/test/kotlin/org/partiql/pig/util/CapitalizerTests.kt +++ b/pig/src/test/kotlin/org/partiql/pig/legacy/util/CapitalizerTests.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package org.partiql.pig.util +package org.partiql.pig.legacy.util import org.junit.jupiter.api.Test import kotlin.test.assertEquals @@ -33,6 +33,7 @@ class CapitalizerTests { assertEquals("OneTwoThree", "one_two_three".snakeToPascalCase()) assertEquals("OneTwo", "one__two".snakeToPascalCase()) } + @Test fun snakeToCamelCase() { assertEquals("one", "one".snakeToCamelCase()) diff --git a/settings.gradle.kts b/settings.gradle.kts index 5e9117b..fb9f964 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,7 +16,7 @@ rootProject.name = "PIG" pluginManagement { - includeBuild("pig-gradle-plugin") + // includeBuild("pig-gradle-plugin") repositories { gradlePluginPortal() } @@ -25,5 +25,5 @@ pluginManagement { include( "pig", "pig-runtime", - "pig-tests" + // "pig-tests" ) From 362a1ba05b61f15403a9a185875cf1e715e522d4 Mon Sep 17 00:00:00 2001 From: "R. C. Howell" Date: Wed, 26 Apr 2023 14:34:21 -0700 Subject: [PATCH 4/6] Updates README, pig-tests, and tests backwards compatibility --- README.adoc | 483 ++++++++++++++++++ README.md | 96 ---- .../main/kotlin/pig.conventions.gradle.kts | 33 +- gradle.properties | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- pig-gradle-plugin/README.adoc | 33 ++ pig-gradle-plugin/build.gradle.kts | 2 +- .../pig/{ => legacy}/runtime/BoolPrimitive.kt | 18 +- .../pig/{ => legacy}/runtime/DomainNode.kt | 2 +- .../{ => legacy}/runtime/DomainVisitorBase.kt | 6 +- .../runtime/DomainVisitorFoldBase.kt | 6 +- .../runtime/DomainVisitorTransformBase.kt | 10 +- .../pig/{ => legacy}/runtime/ErrorHelpers.kt | 2 +- .../pig/{ => legacy}/runtime/Experimental.kt | 2 +- .../runtime/IntermediateRecord.kt | 2 +- .../{ => legacy}/runtime/IonElementHelpers.kt | 2 +- .../runtime/IonElementTransformerBase.kt | 2 +- .../pig/{ => legacy}/runtime/LongPrimitive.kt | 2 +- .../runtime/MalformedDomainDataException.kt | 2 +- .../runtime/MetaContainingNode.kt | 2 +- .../{ => legacy}/runtime/PrimitiveUtils.kt | 6 +- .../{ => legacy}/runtime/SymbolPrimitive.kt | 2 +- .../{ => legacy}/runtime/ErrorHelpersTests.kt | 2 +- .../runtime/IntermediateRecordTests.kt | 2 +- .../runtime/IonElementTransformerBaseTests.kt | 2 +- .../runtime/LongPrimitiveTests.kt | 2 +- .../runtime/SymbolPrimitiveTests.kt | 2 +- pig-tests/build.gradle.kts | 74 ++- .../generated/CalculatorAst.generated.kt | 10 +- .../tests/generated/DomainA.generated.kt | 82 +-- .../generated/DomainAToDomainB.generated.kt | 4 +- .../tests/generated/DomainB.generated.kt | 88 ++-- .../EnhancedCalculatorAst.generated.kt | 22 +- .../generated/MultiWordDomain.generated.kt | 172 +++---- .../tests/generated/PartiqlBasic.generated.kt | 66 +-- .../tests/generated/TestDomain.generated.kt | 376 +++++++------- .../tests/generated/ToyLang.generated.kt | 22 +- .../generated/ToyLangIndexed.generated.kt | 34 +- .../ToyLangIndexedToToyLang.generated.kt | 4 +- .../ToyLangToToyLangIndexed.generated.kt | 4 +- .../tests/ArgumentsProviderBase.kt | 2 +- .../pig/{ => legacy}/tests/CopyTests.kt | 8 +- .../{ => legacy}/tests/CustomMetasTests.kt | 4 +- .../pig/{ => legacy}/tests/EqualityTests.kt | 4 +- .../tests/IonElementTransformerErrorTests.kt | 8 +- .../tests/IonElementTransformerTests.kt | 8 +- .../tests/MultiWordDomainNamingTest.kt | 5 +- .../tests/PermuteTransformSmokeTests.kt | 12 +- .../tests/PermuteTransformTests.kt | 8 +- .../partiql/pig/{ => legacy}/tests/Scope.kt | 2 +- .../pig/{ => legacy}/tests/SmokeTests.kt | 6 +- .../{ => legacy}/tests/SumConverterTests.kt | 6 +- .../{ => legacy}/tests/VisitorFoldTests.kt | 6 +- .../pig/{ => legacy}/tests/VisitorTests.kt | 8 +- .../tests/VisitorTransformTests.kt | 25 +- pig/build.gradle.kts | 35 ++ pig/src/main/kotlin/org/partiql/pig/Pig.kt | 26 +- .../generator/target/kotlin/KotlinCommand.kt | 4 +- .../org/partiql/pig/legacy/LegacyCommand.kt | 22 +- .../pig/legacy/generator/FreeMarkerUtils.kt | 2 +- .../org/partiql/pig/legacy/model/Statement.kt | 1 + .../pig/{ => legacy}/templates/html.ftl | 0 .../kotlin-cross-domain-transform.ftl | 0 .../{ => legacy}/templates/kotlin-domain.ftl | 2 +- .../{ => legacy}/templates/kotlin-header.ftl | 2 +- .../templates/kotlin-visitor-fold.ftl | 2 +- .../templates/kotlin-visitor-transform.ftl | 2 +- .../{ => legacy}/templates/kotlin-visitor.ftl | 2 +- settings.gradle.kts | 9 +- 69 files changed, 1190 insertions(+), 716 deletions(-) create mode 100644 README.adoc delete mode 100644 README.md create mode 100644 pig-gradle-plugin/README.adoc rename pig-runtime/src/main/kotlin/org/partiql/pig/{ => legacy}/runtime/BoolPrimitive.kt (73%) rename pig-runtime/src/main/kotlin/org/partiql/pig/{ => legacy}/runtime/DomainNode.kt (97%) rename pig-runtime/src/main/kotlin/org/partiql/pig/{ => legacy}/runtime/DomainVisitorBase.kt (88%) rename pig-runtime/src/main/kotlin/org/partiql/pig/{ => legacy}/runtime/DomainVisitorFoldBase.kt (89%) rename pig-runtime/src/main/kotlin/org/partiql/pig/{ => legacy}/runtime/DomainVisitorTransformBase.kt (84%) rename pig-runtime/src/main/kotlin/org/partiql/pig/{ => legacy}/runtime/ErrorHelpers.kt (98%) rename pig-runtime/src/main/kotlin/org/partiql/pig/{ => legacy}/runtime/Experimental.kt (79%) rename pig-runtime/src/main/kotlin/org/partiql/pig/{ => legacy}/runtime/IntermediateRecord.kt (99%) rename pig-runtime/src/main/kotlin/org/partiql/pig/{ => legacy}/runtime/IonElementHelpers.kt (98%) rename pig-runtime/src/main/kotlin/org/partiql/pig/{ => legacy}/runtime/IonElementTransformerBase.kt (97%) rename pig-runtime/src/main/kotlin/org/partiql/pig/{ => legacy}/runtime/LongPrimitive.kt (98%) rename pig-runtime/src/main/kotlin/org/partiql/pig/{ => legacy}/runtime/MalformedDomainDataException.kt (95%) rename pig-runtime/src/main/kotlin/org/partiql/pig/{ => legacy}/runtime/MetaContainingNode.kt (95%) rename pig-runtime/src/main/kotlin/org/partiql/pig/{ => legacy}/runtime/PrimitiveUtils.kt (92%) rename pig-runtime/src/main/kotlin/org/partiql/pig/{ => legacy}/runtime/SymbolPrimitive.kt (98%) rename pig-runtime/src/test/kotlin/org/partiql/pig/{ => legacy}/runtime/ErrorHelpersTests.kt (98%) rename pig-runtime/src/test/kotlin/org/partiql/pig/{ => legacy}/runtime/IntermediateRecordTests.kt (99%) rename pig-runtime/src/test/kotlin/org/partiql/pig/{ => legacy}/runtime/IonElementTransformerBaseTests.kt (99%) rename pig-runtime/src/test/kotlin/org/partiql/pig/{ => legacy}/runtime/LongPrimitiveTests.kt (98%) rename pig-runtime/src/test/kotlin/org/partiql/pig/{ => legacy}/runtime/SymbolPrimitiveTests.kt (98%) rename pig-tests/src/main/kotlin/org/partiql/pig/{ => legacy}/tests/generated/CalculatorAst.generated.kt (99%) rename pig-tests/src/main/kotlin/org/partiql/pig/{ => legacy}/tests/generated/DomainA.generated.kt (97%) rename pig-tests/src/main/kotlin/org/partiql/pig/{ => legacy}/tests/generated/DomainAToDomainB.generated.kt (98%) rename pig-tests/src/main/kotlin/org/partiql/pig/{ => legacy}/tests/generated/DomainB.generated.kt (95%) rename pig-tests/src/main/kotlin/org/partiql/pig/{ => legacy}/tests/generated/EnhancedCalculatorAst.generated.kt (98%) rename pig-tests/src/main/kotlin/org/partiql/pig/{ => legacy}/tests/generated/MultiWordDomain.generated.kt (92%) rename pig-tests/src/main/kotlin/org/partiql/pig/{ => legacy}/tests/generated/PartiqlBasic.generated.kt (98%) rename pig-tests/src/main/kotlin/org/partiql/pig/{ => legacy}/tests/generated/TestDomain.generated.kt (95%) rename pig-tests/src/main/kotlin/org/partiql/pig/{ => legacy}/tests/generated/ToyLang.generated.kt (98%) rename pig-tests/src/main/kotlin/org/partiql/pig/{ => legacy}/tests/generated/ToyLangIndexed.generated.kt (97%) rename pig-tests/src/main/kotlin/org/partiql/pig/{ => legacy}/tests/generated/ToyLangIndexedToToyLang.generated.kt (98%) rename pig-tests/src/main/kotlin/org/partiql/pig/{ => legacy}/tests/generated/ToyLangToToyLangIndexed.generated.kt (98%) rename pig-tests/src/test/kotlin/org/partiql/pig/{ => legacy}/tests/ArgumentsProviderBase.kt (97%) rename pig-tests/src/test/kotlin/org/partiql/pig/{ => legacy}/tests/CopyTests.kt (94%) rename pig-tests/src/test/kotlin/org/partiql/pig/{ => legacy}/tests/CustomMetasTests.kt (94%) rename pig-tests/src/test/kotlin/org/partiql/pig/{ => legacy}/tests/EqualityTests.kt (99%) rename pig-tests/src/test/kotlin/org/partiql/pig/{ => legacy}/tests/IonElementTransformerErrorTests.kt (88%) rename pig-tests/src/test/kotlin/org/partiql/pig/{ => legacy}/tests/IonElementTransformerTests.kt (99%) rename pig-tests/src/test/kotlin/org/partiql/pig/{ => legacy}/tests/MultiWordDomainNamingTest.kt (96%) rename pig-tests/src/test/kotlin/org/partiql/pig/{ => legacy}/tests/PermuteTransformSmokeTests.kt (91%) rename pig-tests/src/test/kotlin/org/partiql/pig/{ => legacy}/tests/PermuteTransformTests.kt (96%) rename pig-tests/src/test/kotlin/org/partiql/pig/{ => legacy}/tests/Scope.kt (97%) rename pig-tests/src/test/kotlin/org/partiql/pig/{ => legacy}/tests/SmokeTests.kt (93%) rename pig-tests/src/test/kotlin/org/partiql/pig/{ => legacy}/tests/SumConverterTests.kt (96%) rename pig-tests/src/test/kotlin/org/partiql/pig/{ => legacy}/tests/VisitorFoldTests.kt (95%) rename pig-tests/src/test/kotlin/org/partiql/pig/{ => legacy}/tests/VisitorTests.kt (94%) rename pig-tests/src/test/kotlin/org/partiql/pig/{ => legacy}/tests/VisitorTransformTests.kt (94%) rename pig/src/main/resources/org/partiql/pig/{ => legacy}/templates/html.ftl (100%) rename pig/src/main/resources/org/partiql/pig/{ => legacy}/templates/kotlin-cross-domain-transform.ftl (100%) rename pig/src/main/resources/org/partiql/pig/{ => legacy}/templates/kotlin-domain.ftl (99%) rename pig/src/main/resources/org/partiql/pig/{ => legacy}/templates/kotlin-header.ftl (91%) rename pig/src/main/resources/org/partiql/pig/{ => legacy}/templates/kotlin-visitor-fold.ftl (96%) rename pig/src/main/resources/org/partiql/pig/{ => legacy}/templates/kotlin-visitor-transform.ftl (97%) rename pig/src/main/resources/org/partiql/pig/{ => legacy}/templates/kotlin-visitor.ftl (96%) diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..3e13c71 --- /dev/null +++ b/README.adoc @@ -0,0 +1,483 @@ += PartiQL IR Generator +:toc: + +== About + +PIG is a compiler framework, domain modeling tool and code generator for tree data structures such as ASTs (Abstract Syntax Tree), database logical plans, database physical plans, and other intermediate representations. +Using PIG, the developer concisely defines the structure of a tree by specifying named constraints for every node and its attributes. +Check out the https://github.com/partiql/partiql-ir-generator/wiki[wiki]! + +== CLI + +PIG can be used as a command line tool. + +=== Installation + +* Clone this repository. +* Check out the tag of the [release](https://github.com/partiql/partiql-ir-generator/releases) you wish to utilize, e.g. `git checkout v1.0.0` +* Execute `./gradlew install` + +After the build completes, the `pig` executable and dependencies will be located in `./pig/build/install/pig/bin/pig`. +You can move the install to a permanent location and add the executable script to your path. +For this doc, we'll add an alias for the local path `alias pig=./pig/build/install/pig/bin/pig`. + +You can check the version with `pig --version`. + +=== PIG 1.x + +PIG 1.x introduces a new modeling language and generator which enable features such as: + +.Modeling Additions +* List, Map, and Set Types +* Int, Float, Double, Bytes, String types +* Enum Types +* Imported Types +* Sum type definition as the variant of a sum type +* Inline type definitions +* Scoped type definitions +* Scoped type names + +.Kotlin Specific Features +* No runtime library +* Primitives nodes are optional, #79 +* Explicit library mode, #64 +* Visitors use conventional style, #123 #66 +* Nodes have a children construct, enabling recursion without a visitor. +* Dynamic code generation via poems rather than templating. +* Generated DSL now uses builders rather than just factory methods +* Generated DSL allows for a custom factory +* Jackson databind integration for serializing a tree to arbitrary Jackson formats, #119 + +==== Breaking Changes + +PIG 1.x currently does not support the 0.x modeling language or permuted domains. +The 0.x language and permuted domains are still accessible via the `legacy` subcommand. +If an issue is raised, permuted domains may get added to the 1.x language. + +==== Usage + +[source,shell] +---- +pig --help + + Usage: pig [-hv] [COMMAND] + -h, --help display this help message + -v, --version Prints current version + Commands: + generate PartiQL IR Generator 1.x + legacy PartiQL IR Generator 0.x + +pig generate --help + + Usage: pig generate [-h] [COMMAND] + PartiQL IR Generator 1.x + -h, --help display this help message + Commands: + kotlin Generates Kotlin sources from type universe definitions + + +pig generate kotlin --help + + Usage: pig generate kotlin [-h] [-m=] [-o=] [-p=] + [-u=] [--poems=[,...]]... + Generates Kotlin sources from type universe definitions + Type definition file + -h, --help display this help message + -m, --modifier= + Generated node class modifier. Options FINAL, DATA, OPEN + -o, --out= Generated source output directory + -p, --package= + Package root + --poems=[,...] + Poem templates to apply + -u, --universe= Universe identifier +---- + +=== PIG 0.x + +PIG 0.x uses a Nanopass style domain modeling language and notably has the ability to define permuted domains. +If you wish to use these features, the latest version of PIG maintains them under the `legacy` sub-command. +The https://github.com/partiql/partiql-ir-generator/wiki[wiki] has documentation on the 0.x modeling language and Kotlin target. + +The options and command behavior remains the same, only the `legacy` keyword needs to be added as the first argument. + +==== Usage + +[source,shell] +---- +pig legacy --help + +Usage: legacy [-hv] [-d=] [-e=