Skip to content

Commit

Permalink
Merge ffdc968 into a3199cf
Browse files Browse the repository at this point in the history
  • Loading branch information
RCHowell authored Oct 16, 2023
2 parents a3199cf + ffdc968 commit 0839d3c
Show file tree
Hide file tree
Showing 28 changed files with 1,894 additions and 673 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ Thank you to all who have contributed!
## [Unreleased]

### Added
- Adds top-level IR node creation functions.
- Adds `componentN` functions (destructuring) to IR nodes via Kotlin data classes
- Adds public `tag` field to IR nodes for associating metadata

### Changed

Expand All @@ -36,12 +39,14 @@ Thank you to all who have contributed!
### Fixed

### Removed
- [Breaking] Removed IR factory in favor of static top-level functions. Change `Ast.foo()`
to `foo()`

### Security

### Contributors
Thank you to all who have contributed!
- @<your-username>
- @rchowell

## [0.13.2-alpha] - 2023-09-29

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@ package org.partiql.sprout.generator.target.kotlin

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asTypeName
import net.pearx.kasechange.toCamelCase
import org.partiql.sprout.generator.Generator
import org.partiql.sprout.generator.target.kotlin.poems.KotlinBuilderPoem
import org.partiql.sprout.generator.target.kotlin.poems.KotlinFactoryPoem
import org.partiql.sprout.generator.target.kotlin.poems.KotlinJacksonPoem
import org.partiql.sprout.generator.target.kotlin.poems.KotlinListenerPoem
import org.partiql.sprout.generator.target.kotlin.poems.KotlinUtilsPoem
Expand All @@ -21,14 +19,16 @@ import org.partiql.sprout.generator.target.kotlin.spec.KotlinNodeSpec
import org.partiql.sprout.generator.target.kotlin.spec.KotlinUniverseSpec
import org.partiql.sprout.model.TypeDef
import org.partiql.sprout.model.TypeProp
import org.partiql.sprout.model.TypeRef
import org.partiql.sprout.model.Universe

/**
* Generates and applies
*/
class KotlinGenerator(private val options: KotlinOptions) : Generator<KotlinResult> {

// @JvmField
private val jvmField = AnnotationSpec.builder(JvmField::class).build()

override fun generate(universe: Universe): KotlinResult {

// --- Initialize an empty symbol table(?)
Expand All @@ -39,6 +39,7 @@ class KotlinGenerator(private val options: KotlinOptions) : Generator<KotlinResu
val poems = options.poems.map {
when (it) {
"visitor" -> KotlinVisitorPoem(symbols)
"factory" -> KotlinFactoryPoem(symbols)
"builder" -> KotlinBuilderPoem(symbols)
"listener" -> KotlinListenerPoem(symbols)
"jackson" -> KotlinJacksonPoem(symbols)
Expand All @@ -51,12 +52,19 @@ class KotlinGenerator(private val options: KotlinOptions) : Generator<KotlinResu
val spec = KotlinUniverseSpec(
universe = universe,
nodes = universe.nodes(symbols),
base = TypeSpec.interfaceBuilder(symbols.base),
base = TypeSpec.classBuilder(symbols.base).addModifiers(KModifier.ABSTRACT),
types = universe.types(symbols)
)
val specs = with(spec) {
// Add identifiers
base.addProperty(PropertySpec.builder("_id", String::class).addModifiers(KModifier.ABSTRACT).build())
// Add optional tags
base.addProperty(
PropertySpec.builder("tag", String::class)
.addAnnotation(jvmField)
.mutable(true)
.initializer("\"${symbols.rootId}-\${%S.format(%T.nextInt())}\"", "%06x", ClassName("kotlin.random", "Random"))
.build()
)

// Apply each poem
poems.forEach { it.apply(this) }
// Finalize each spec/builder
Expand Down Expand Up @@ -85,7 +93,7 @@ class KotlinGenerator(private val options: KotlinOptions) : Generator<KotlinResu
*/
private fun Universe.nodes(symbols: KotlinSymbols): List<KotlinNodeSpec> =
types.mapNotNull { it.generate(symbols) }.map {
it.builder.addSuperinterface(symbols.base)
it.builder.superclass(symbols.base)
it
}

Expand All @@ -108,34 +116,41 @@ class KotlinGenerator(private val options: KotlinOptions) : Generator<KotlinResu
* Product Node Generation
*/
private fun TypeDef.Product.generate(symbols: KotlinSymbols): KotlinNodeSpec {
val clazz = symbols.clazz(ref)
val clazzImpl = ClassName(
packageName = clazz.packageName + ".impl",
simpleNames = listOf(symbols.pascal(ref) + "Impl"),
)
return KotlinNodeSpec.Product(
product = this,
props = props.map { KotlinNodeSpec.Prop(it.name.toCamelCase(), symbols.typeNameOf(it.ref)) },
implClazz = clazzImpl,
impl = TypeSpec.classBuilder(clazzImpl),
nodes = children.mapNotNull { it.generate(symbols) },
clazz = symbols.clazz(ref),
ext = (props.enumProps(symbols) + types.enums(symbols)).toMutableList(),
).apply {
// Add id to impl
impl.addProperty(symbols.idProp)
constructor.addParameter(symbols.idPara)

props.forEach {
val para = ParameterSpec.builder(it.name, it.type).build()
val prop = PropertySpec.builder(it.name, it.type).build()
builder.addProperty(prop.toBuilder().addModifiers(KModifier.ABSTRACT).build())
impl.addProperty(prop.toBuilder().addModifiers(KModifier.OVERRIDE).initializer(it.name).build())
val prop = PropertySpec.builder(it.name, it.type)
.initializer(it.name)
.addAnnotation(jvmField)
.build()
builder.addProperty(prop)
constructor.addParameter(para)
}

// Add `tag` field to all nodes

// HACK FOR EMPTY DATA CLASSES
if (props.isEmpty()) {
val name = " "
val type = Char::class
val para = ParameterSpec.builder(name, type).defaultValue("' '").build()
val prop = PropertySpec.builder(name, type)
.initializer("` `")
.addAnnotation(jvmField)
.build()
builder.addProperty(prop)
constructor.addParameter(para)
}
// impls are open
impl.superclass(clazz)
nodes.forEach { it.builder.addSuperinterface(symbols.base) }
this.addDataClassMethods(symbols, ref)

nodes.forEach { it.builder.superclass(symbols.base) }
builder.primaryConstructor(constructor.build())
}
}

Expand All @@ -149,16 +164,15 @@ class KotlinGenerator(private val options: KotlinOptions) : Generator<KotlinResu
clazz = symbols.clazz(ref),
ext = types.enums(symbols).toMutableList(),
).apply {
variants.forEach { it.builder.addSuperinterface(clazz) }
nodes.forEach { it.builder.addSuperinterface(symbols.base) }
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 {
addFunction(enumToStringSpec(symbols.pascal(ref)))
values.forEach { addEnumConstant(it) }
}.build()

Expand All @@ -172,121 +186,4 @@ class KotlinGenerator(private val options: KotlinOptions) : Generator<KotlinResu
private fun List<TypeDef>.enums(symbols: KotlinSymbols) = filterIsInstance<TypeDef.Enum>().map {
it.generate(symbols)
}

// TODO generate hashCode, equals, componentN so we can have OPEN internal implementations
private fun KotlinNodeSpec.Product.addDataClassMethods(symbols: KotlinSymbols, ref: TypeRef.Path) {
impl.addModifiers(KModifier.INTERNAL, KModifier.OPEN)
addEqualsMethod()
addHashCodeMethod()
addToStringMethod(symbols, ref)
val args = listOf("_id") + props.map { it.name }
val copy = FunSpec.builder("copy").addModifiers(KModifier.ABSTRACT).returns(clazz)
val copyImpl = FunSpec.builder("copy")
.addModifiers(KModifier.OVERRIDE)
.returns(clazz)
.addStatement("return %T(${args.joinToString()})", implClazz)
props.forEach {
val para = ParameterSpec.builder(it.name, it.type).build()
copy.addParameter(para.toBuilder().defaultValue("this.${it.name}").build())
copyImpl.addParameter(para)
}
builder.addFunction(copy.build())
impl.addFunction(copyImpl.build())
}

/**
* Adds `equals` method to the core abstract class
*/
private fun KotlinNodeSpec.Product.addEqualsMethod() {
val equalsFunctionBodyBuilder = CodeBlock.builder().let { body ->
body.addStatement("if (this === other) return true")
body.addStatement("if (other !is %T) return false", this.clazz)
this.props.forEach { prop ->
body.addStatement("if (%N != other.%N) return false", prop.name, prop.name)
}
body.addStatement("return true")
}
builder.addFunction(
FunSpec.builder("equals").addModifiers(KModifier.OVERRIDE).returns(Boolean::class)
.addParameter(ParameterSpec.builder("other", Any::class.asTypeName().copy(nullable = true)).build())
.addCode(equalsFunctionBodyBuilder.build())
.build()
)
}

/**
* Adds `hashCode` method to the core abstract class
*/
private fun KotlinNodeSpec.Product.addHashCodeMethod() {
val hashcodeBodyBuilder = CodeBlock.builder().let { body ->
when (this.props.size) {
0 -> body.addStatement("return 0")
1 -> body.addStatement("return %N.hashCode()", this.props.first().name)
else -> {
body.addStatement("var result = %N.hashCode()", this.props.first().name)
this.props.subList(1, this.props.size).forEach { prop ->
body.addStatement("result = 31 * result + %N.hashCode()", prop.name)
}
body.addStatement("return result")
}
}
body
}
builder.addFunction(
FunSpec.builder("hashCode")
.addModifiers(KModifier.OVERRIDE)
.returns(Int::class)
.addCode(hashcodeBodyBuilder.build())
.build()
)
}

private fun enumToStringSpec(base: String): FunSpec {
val bodyBuilder = CodeBlock.builder().let { body ->
val str = "$base::\${super.toString()}"
body.addStatement("return %P", str)
body
}
return FunSpec.builder("toString")
.addModifiers(KModifier.OVERRIDE)
.returns(String::class)
.addCode(bodyBuilder.build())
.build()
}

/**
* Adds `toString` method to the core abstract class. We write it in Ion syntax, however, it is NOT a contract
* and therefore subject to failure.
*
* Notably, the following don't format to Ion:
* - Maps
* - Imported Types
* - Escape Characters
*/
private fun KotlinNodeSpec.Product.addToStringMethod(symbols: KotlinSymbols, ref: TypeRef.Path) {
val annotation = symbols.pascal(ref)
val thiz = this
val bodyBuilder = CodeBlock.builder().let { body ->
val returnString = buildString {
append("$annotation::{")
thiz.props.forEach { prop ->
if (String::class.asTypeName() == prop.type) {
append("${prop.name}: \"\$${prop.name}\",")
} else {
append("${prop.name}: \$${prop.name},")
}
}
append("}")
}
body.addStatement("return %P", returnString)
body
}
builder.addFunction(
FunSpec.builder("toString")
.addModifiers(KModifier.OVERRIDE)
.returns(String::class)
.addCode(bodyBuilder.build())
.build()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@ import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.DOUBLE
import com.squareup.kotlinpoet.FLOAT
import com.squareup.kotlinpoet.INT
import com.squareup.kotlinpoet.KModifier
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.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.SET
import com.squareup.kotlinpoet.STRING
import com.squareup.kotlinpoet.TypeName
Expand Down Expand Up @@ -54,16 +51,6 @@ class KotlinSymbols private constructor(
*/
val base: ClassName = ClassName(rootPackage, "${rootId}Node")

/**
* Id Property for interfaces and classes
*/
val idProp = PropertySpec.builder("_id", String::class).addModifiers(KModifier.OVERRIDE).initializer("_id").build()

/**
* Id Parameter for internal constructors
*/
val idPara = ParameterSpec.builder("_id", String::class).build()

/**
* Memoize converting a TypeRef.Path to a camel case identifier to be used as method/function names
*/
Expand Down
Loading

0 comments on commit 0839d3c

Please sign in to comment.