-
Notifications
You must be signed in to change notification settings - Fork 437
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* fix AP tests * Add OpticsProcessor check to verify unnamed companion object * Generate single file per annotated element * Re-enabled grained control for DSL generation * Small clean up Optics processors * Remove unused reflection usage * Update formatting generated code Lens & Iso * Fix tests * Update DSLTest with new syntax * Update Snippet so it belongs to an annotated target * Generate file per annotated target in corresponding directory * Test code generation in correct folder structure
- Loading branch information
Showing
16 changed files
with
334 additions
and
401 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
modules/core/arrow-annotations-processor/src/main/java/arrow/optics/DslSnippetGenerator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package arrow.optics | ||
|
||
import arrow.common.utils.simpleName | ||
|
||
fun generateLensDsl(ele: AnnotatedElement, optic: DataClassDsl) = Snippet( | ||
`package` = ele.packageName, | ||
name = ele.classData.simpleName, | ||
content = processLensSyntax(ele, optic.foci) | ||
) | ||
|
||
fun generateOptionalDsl(ele: AnnotatedElement, optic: DataClassDsl) = Snippet( | ||
`package` = ele.packageName, | ||
name = ele.classData.simpleName, | ||
content = processOptionalSyntax(ele, optic) | ||
) | ||
|
||
fun generatePrismDsl(ele: AnnotatedElement, isoOptic: SealedClassDsl) = Snippet( | ||
`package` = ele.packageName, | ||
name = ele.classData.simpleName, | ||
content = processPrismSyntax(ele, isoOptic) | ||
) | ||
|
||
private fun processLensSyntax(ele: AnnotatedElement, foci: List<Focus>): String = foci.joinToString(separator = "\n") { focus -> | ||
""" | ||
|inline val <S> $Iso<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Lens<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()} | ||
|inline val <S> $Lens<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Lens<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()} | ||
|inline val <S> $Optional<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Optional<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()} | ||
|inline val <S> $Prism<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Optional<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()} | ||
|inline val <S> $Getter<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Getter<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()} | ||
|inline val <S> $Setter<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Setter<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()} | ||
|inline val <S> $Traversal<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Traversal<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()} | ||
|inline val <S> $Fold<S, ${ele.sourceClassName}>.${focus.lensParamName()}: $Fold<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.lensParamName()} | ||
|""".trimMargin() | ||
} | ||
|
||
private fun processOptionalSyntax(ele: AnnotatedElement, optic: DataClassDsl) = optic.foci.filterNot { it is NonNullFocus }.joinToString(separator = "\n") { focus -> | ||
val targetClassName = when (focus) { | ||
is NullableFocus -> focus.nonNullClassName | ||
is OptionFocus -> focus.nestedClassName | ||
is NonNullFocus -> "" | ||
} | ||
|
||
""" | ||
|inline val <S> $Iso<S, ${ele.sourceClassName}>.${focus.paramName}: $Optional<S, $targetClassName> inline get() = this + ${ele.sourceClassName}.${focus.paramName} | ||
|inline val <S> $Lens<S, ${ele.sourceClassName}>.${focus.paramName}: $Optional<S, $targetClassName> inline get() = this + ${ele.sourceClassName}.${focus.paramName} | ||
|inline val <S> $Optional<S, ${ele.sourceClassName}>.${focus.paramName}: $Optional<S, $targetClassName> inline get() = this + ${ele.sourceClassName}.${focus.paramName} | ||
|inline val <S> $Prism<S, ${ele.sourceClassName}>.${focus.paramName}: $Optional<S, $targetClassName> inline get() = this + ${ele.sourceClassName}.${focus.paramName} | ||
|inline val <S> $Setter<S, ${ele.sourceClassName}>.${focus.paramName}: $Setter<S, $targetClassName> inline get() = this + ${ele.sourceClassName}.${focus.paramName} | ||
|inline val <S> $Traversal<S, ${ele.sourceClassName}>.${focus.paramName}: $Traversal<S, $targetClassName> inline get() = this + ${ele.sourceClassName}.${focus.paramName} | ||
|inline val <S> $Fold<S, ${ele.sourceClassName}>.${focus.paramName}: $Fold<S, $targetClassName> inline get() = this + ${ele.sourceClassName}.${focus.paramName} | ||
|""".trimMargin() | ||
} | ||
|
||
private fun processPrismSyntax(ele: AnnotatedElement, dsl: SealedClassDsl): String = dsl.foci.joinToString(separator = "\n\n") { focus -> | ||
""" | ||
|inline val <S> $Iso<S, ${ele.sourceClassName}>.${focus.paramName}: $Prism<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.paramName} | ||
|inline val <S> $Lens<S, ${ele.sourceClassName}>.${focus.paramName}: $Optional<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.paramName} | ||
|inline val <S> $Optional<S, ${ele.sourceClassName}>.${focus.paramName}: $Optional<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.paramName} | ||
|inline val <S> $Prism<S, ${ele.sourceClassName}>.${focus.paramName}: $Prism<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.paramName} | ||
|inline val <S> $Setter<S, ${ele.sourceClassName}>.${focus.paramName}: $Setter<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.paramName} | ||
|inline val <S> $Traversal<S, ${ele.sourceClassName}>.${focus.paramName}: $Traversal<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.paramName} | ||
|inline val <S> $Fold<S, ${ele.sourceClassName}>.${focus.paramName}: $Fold<S, ${focus.className}> inline get() = this + ${ele.sourceClassName}.${focus.paramName} | ||
|""".trimMargin() | ||
} |
80 changes: 33 additions & 47 deletions
80
modules/core/arrow-annotations-processor/src/main/java/arrow/optics/IsosFileGenerator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,52 +1,38 @@ | ||
package arrow.optics | ||
|
||
import me.eugeniomarletti.kotlin.metadata.escapedClassName | ||
import arrow.common.utils.simpleName | ||
import me.eugeniomarletti.kotlin.metadata.plusIfNotBlank | ||
import java.io.File | ||
|
||
class IsosFileGenerator( | ||
private val annotatedList: Collection<AnnotatedOptic>, | ||
private val generatedDir: File | ||
) { | ||
|
||
private val filePrefix = "isos" | ||
private val tuple = "arrow.core.Tuple" | ||
private val letters = ('a'..'v').toList() | ||
|
||
fun generate() = buildIsos(annotatedList) | ||
|
||
private fun buildIsos(optics: Collection<AnnotatedOptic>) = | ||
optics.map(this::processElement) | ||
.forEach { (element, funString) -> | ||
File(generatedDir, "$filePrefix.${element.classData.`package`}.${element.sourceName}.kt").printWriter().use { w -> | ||
w.println(funString) | ||
} | ||
} | ||
|
||
private fun processElement(iso: AnnotatedOptic): Pair<AnnotatedOptic, String> = iso to """ | ||
|package ${iso.classData.`package`.escapedClassName} | ||
| | ||
|inline val ${iso.sourceClassName}.Companion.iso: $Iso<${iso.sourceClassName}, ${focusType(iso)}> get()= $Iso( | ||
| get = { ${iso.sourceName}: ${iso.sourceClassName} -> ${getFunction(iso)} }, | ||
| reverseGet = { ${reverseGetFunction(iso)} } | ||
|)""".trimMargin() | ||
|
||
private fun getFunction(iso: AnnotatedOptic) = | ||
if (iso.hasTupleFocus) tupleConstructor(iso) | ||
else "${iso.sourceName}.${iso.targets.first().paramName}" | ||
|
||
private fun reverseGetFunction(iso: AnnotatedOptic) = | ||
if (iso.hasTupleFocus) "tuple: ${focusType(iso)} -> ${classConstructorFromTuple(iso.sourceClassName, iso.focusSize)}" | ||
else "${iso.sourceClassName}(it)" | ||
|
||
private fun tupleConstructor(iso: AnnotatedOptic) = | ||
iso.targets.joinToString(prefix = "$tuple${iso.focusSize}(", postfix = ")", transform = { "${iso.sourceName}.${it.paramName.plusIfNotBlank(prefix = "`", postfix = "`")}" }) | ||
|
||
private fun focusType(iso: AnnotatedOptic) = | ||
if (iso.hasTupleFocus) iso.targetNames.joinToString(prefix = "$tuple${iso.targets.size}<", postfix = ">") | ||
else iso.targetNames.first() | ||
|
||
private fun classConstructorFromTuple(sourceClassName: String, propertiesSize: Int) = | ||
|
||
fun generateIsos(ele: AnnotatedElement, target: IsoTarget) = Snippet( | ||
`package` = ele.packageName, | ||
name = ele.classData.simpleName, | ||
content = processElement(ele, target) | ||
) | ||
|
||
inline val Target.targetNames inline get() = foci.map(Focus::className) | ||
|
||
private fun processElement(iso: AnnotatedElement, target: Target): String { | ||
val foci = target.foci | ||
val hasTupleFocus = foci.size > 1 | ||
val letters = ('a'..'v').toList() | ||
|
||
fun tupleConstructor() = | ||
foci.joinToString(prefix = "$Tuple${foci.size}(", postfix = ")", transform = { "${iso.sourceName}.${it.paramName.plusIfNotBlank(prefix = "`", postfix = "`")}" }) | ||
|
||
fun focusType() = | ||
if (hasTupleFocus) target.targetNames.joinToString(prefix = "$Tuple${foci.size}<", postfix = ">") | ||
else target.targetNames.first() | ||
|
||
fun classConstructorFromTuple(sourceClassName: String, propertiesSize: Int) = | ||
(0 until propertiesSize).joinToString(prefix = "$sourceClassName(", postfix = ")", transform = { "tuple.${letters[it]}" }) | ||
|
||
} | ||
val get = if (hasTupleFocus) tupleConstructor() else "${iso.sourceName}.${foci.first().paramName}" | ||
val reverseGet = if (hasTupleFocus) "tuple: ${focusType()} -> ${classConstructorFromTuple(iso.sourceClassName, foci.size)}" else "${iso.sourceClassName}(it)" | ||
|
||
return """ | ||
|inline val ${iso.sourceClassName}.Companion.iso: $Iso<${iso.sourceClassName}, ${focusType()}> inline get()= $Iso( | ||
| get = { ${iso.sourceName}: ${iso.sourceClassName} -> $get }, | ||
| reverseGet = { $reverseGet } | ||
|) | ||
|""".trimMargin() | ||
} |
75 changes: 25 additions & 50 deletions
75
modules/core/arrow-annotations-processor/src/main/java/arrow/optics/LensesFileGenerator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,56 +1,31 @@ | ||
package arrow.optics | ||
|
||
import arrow.common.utils.fullName | ||
import me.eugeniomarletti.kotlin.metadata.escapedClassName | ||
import arrow.common.utils.simpleName | ||
import me.eugeniomarletti.kotlin.metadata.plusIfNotBlank | ||
import java.io.File | ||
|
||
class LensesFileGenerator( | ||
private val annotatedList: Collection<AnnotatedOptic>, | ||
private val generatedDir: File | ||
) { | ||
|
||
private val filePrefix = "lenses" | ||
|
||
fun generate() = annotatedList.map(this::processElement) | ||
.map { (element, funs) -> | ||
"$filePrefix.${element.classData.`package`}.${element.type.simpleName.toString().toLowerCase()}.kt" to | ||
funs.joinToString(prefix = "package ${element.classData.`package`.escapedClassName}\n\n", separator = "\n") | ||
}.forEach { (name, fileString) -> File(generatedDir, name).writeText(fileString) } | ||
|
||
private fun String.toUpperCamelCase(): String = split(" ").joinToString("", transform = String::capitalize) | ||
|
||
private fun processElement(annotatedOptic: AnnotatedOptic): Pair<AnnotatedOptic, List<String>> = | ||
annotatedOptic to annotatedOptic.targets.map { variable -> | ||
val sourceClassName = annotatedOptic.classData.fullName.escapedClassName | ||
val sourceName = annotatedOptic.type.simpleName.toString().decapitalize() | ||
val targetClassName = variable.fullName | ||
val targetName = variable.paramName | ||
val lensType = when (variable) { | ||
is Target.NullableTarget -> "nullable${targetName.toUpperCamelCase()}" | ||
is Target.OptionTarget -> "option${targetName.toUpperCamelCase()}" | ||
is Target.NonNullTarget -> targetName | ||
} | ||
|
||
""" | ||
|inline val $sourceClassName.Companion.$lensType: $Lens<$sourceClassName, $targetClassName> get()= $Lens( | ||
| get = { $sourceName: $sourceClassName -> $sourceName.${targetName.plusIfNotBlank(prefix = "`", postfix = "`")} }, | ||
| set = { value: $targetClassName -> | ||
| { $sourceName: $sourceClassName -> | ||
| $sourceName.copy(${targetName.plusIfNotBlank(prefix = "`", postfix = "`")} = value) | ||
| } | ||
| } | ||
|) | ||
| | ||
|inline val <S> $Iso<S, $sourceClassName>.$lensType: $Lens<S, $targetClassName> inline get() = this + $sourceClassName.$lensType | ||
|inline val <S> $Lens<S, $sourceClassName>.$lensType: $Lens<S, $targetClassName> inline get() = this + $sourceClassName.$lensType | ||
|inline val <S> $Optional<S, $sourceClassName>.$lensType: $Optional<S, $targetClassName> inline get() = this + $sourceClassName.$lensType | ||
|inline val <S> $Prism<S, $sourceClassName>.$lensType: $Optional<S, $targetClassName> inline get() = this + $sourceClassName.$lensType | ||
|inline val <S> $Getter<S, $sourceClassName>.$lensType: $Getter<S, $targetClassName> inline get() = this + $sourceClassName.$lensType | ||
|inline val <S> $Setter<S, $sourceClassName>.$lensType: $Setter<S, $targetClassName> inline get() = this + $sourceClassName.$lensType | ||
|inline val <S> $Traversal<S, $sourceClassName>.$lensType: $Traversal<S, $targetClassName> inline get() = this + $sourceClassName.$lensType | ||
|inline val <S> $Fold<S, $sourceClassName>.$lensType: $Fold<S, $targetClassName> inline get() = this + $sourceClassName.$lensType | ||
|""".trimMargin() | ||
} | ||
fun generateLenses(ele: AnnotatedElement, target: LensTarget) = Snippet( | ||
`package` = ele.packageName, | ||
name = ele.classData.simpleName, | ||
content = processElement(ele, target.foci) | ||
) | ||
|
||
private fun String.toUpperCamelCase(): String = split(" ").joinToString("", transform = String::capitalize) | ||
|
||
private fun processElement(ele: AnnotatedElement, foci: List<Focus>): String = foci.joinToString(separator = "\n") { focus -> | ||
""" | ||
|inline val ${ele.sourceClassName}.Companion.${focus.lensParamName()}: $Lens<${ele.sourceClassName}, ${focus.className}> inline get()= $Lens( | ||
| get = { ${ele.sourceName}: ${ele.sourceClassName} -> ${ele.sourceName}.${focus.paramName.plusIfNotBlank(prefix = "`", postfix = "`")} }, | ||
| set = { value: ${focus.className} -> | ||
| { ${ele.sourceName}: ${ele.sourceClassName} -> | ||
| ${ele.sourceName}.copy(${focus.paramName.plusIfNotBlank(prefix = "`", postfix = "`")} = value) | ||
| } | ||
| } | ||
|) | ||
|""".trimMargin() | ||
} | ||
|
||
fun Focus.lensParamName(): String = when (this) { | ||
is NullableFocus -> "nullable${paramName.toUpperCamelCase()}" | ||
is OptionFocus -> "option${paramName.toUpperCamelCase()}" | ||
is NonNullFocus -> paramName | ||
} |
Oops, something went wrong.