Initial commit

This commit is contained in:
Peter Niederwieser
2016-01-19 14:51:19 +01:00
committed by Dan Chao
commit ecad035dca
2972 changed files with 211653 additions and 0 deletions

View File

@@ -0,0 +1,162 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.pkl.codegen.kotlin
import java.nio.file.Path
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import org.pkl.commons.cli.CliBaseOptions
import org.pkl.commons.readString
class CliKotlinCodeGeneratorTest {
@Test
fun `module inheritance`(@TempDir tempDir: Path) {
val module1 =
PklModule(
"org.mod1",
"""
open module org.mod1
pigeon: Person
class Person {
name: String
age: Int
}
"""
)
val module2 =
PklModule(
"org.mod2",
"""
module org.mod2
extends "mod1.pkl"
parrot: Person
"""
)
val module1File = module1.writeToDisk(tempDir.resolve("org/mod1.pkl"))
val module2File = module2.writeToDisk(tempDir.resolve("org/mod2.pkl"))
val outputDir = tempDir.resolve("output")
val generator =
CliKotlinCodeGenerator(
CliKotlinCodeGeneratorOptions(
CliBaseOptions(listOf(module1File.toUri(), module2File.toUri())),
outputDir
)
)
generator.run()
val module1KotlinFile = outputDir.resolve("kotlin/org/Mod1.kt")
assertThat(module1KotlinFile).exists()
val module2KotlinFile = outputDir.resolve("kotlin/org/Mod2.kt")
assertThat(module2KotlinFile).exists()
assertContains(
"""
open class Mod1(
open val pigeon: Person
) {
"""
.trimIndent(),
module1KotlinFile.readString()
)
assertContains(
"""
class Mod2(
pigeon: Mod1.Person,
val parrot: Mod1.Person
) : Mod1(pigeon) {
"""
.trimIndent(),
module2KotlinFile.readString()
)
}
@Test
fun `class name clashes`(@TempDir tempDir: Path) {
val module1 =
PklModule(
"org.mod1",
"""
module org.mod1
class Person {
name: String
}
"""
)
val module2 =
PklModule(
"org.mod2",
"""
module org.mod2
import "mod1.pkl"
person1: mod1.Person
person2: Person
class Person {
age: Int
}
"""
)
val module1PklFile = module1.writeToDisk(tempDir.resolve("org/mod1.pkl"))
val module2PklFile = module2.writeToDisk(tempDir.resolve("org/mod2.pkl"))
val outputDir = tempDir.resolve("output")
val generator =
CliKotlinCodeGenerator(
CliKotlinCodeGeneratorOptions(
CliBaseOptions(listOf(module1PklFile.toUri(), module2PklFile.toUri())),
outputDir
)
)
generator.run()
val module2KotlinFile = outputDir.resolve("kotlin/org/Mod2.kt")
assertContains(
"""
data class Mod2(
val person1: Mod1.Person,
val person2: Person
)
"""
.trimIndent(),
module2KotlinFile.readString()
)
}
private fun assertContains(part: String, code: String) {
val trimmedPart = part.trim().trimMargin()
if (!code.contains(trimmedPart)) {
// check for equality to get better error output (ide diff dialog)
assertThat(code).isEqualTo(trimmedPart)
}
}
}

View File

@@ -0,0 +1,87 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.pkl.codegen.kotlin
import javax.script.ScriptEngineManager
import javax.script.ScriptException
import kotlin.reflect.KClass
import kotlin.text.RegexOption.MULTILINE
import kotlin.text.RegexOption.UNIX_LINES
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
class CompilationFailedException(msg: String?, cause: Throwable? = null) :
RuntimeException(msg, cause)
object InMemoryKotlinCompiler {
init {
// prevent "Unable to load JNA library" warning
setIdeaIoUseFallback()
}
// Implementation notes:
// * all [sourceFiles] are currently combined into a single file
// * implementation makes assumptions about structure of generated source files
fun compile(sourceFiles: Map<String, String>): Map<String, KClass<*>> {
fun String.findClasses(
prefix: String = "",
nameGroup: Int = 2,
bodyGroup: Int = 4,
regex: String =
"^(data |open |enum )?class\\s+(\\w+) *(\\([^)]*\\))?.*$\\n((^ .*\\n|^$\\n)*)",
transform: (String, String) -> Sequence<Pair<String, String>> = { name, body ->
sequenceOf(Pair(name, prefix + name)) + body.findClasses("$prefix$name.")
}
): Sequence<Pair<String, String>> = // (simpleName1, qualifiedName1), ...
Regex(regex, setOf(MULTILINE, UNIX_LINES)).findAll(this).flatMap {
transform(it.groupValues[nameGroup], it.groupValues[bodyGroup].trimIndent())
}
fun String.findOuterObjects(): Sequence<Pair<String, String>> = // (simpleName, qualifiedName)
findClasses("", 1, 2, "^object\\s+(\\w+).*$\n((^ .*$\n|^$\n)*)") { name, body ->
body.findClasses("$name.")
}
val (importLines, remainder) =
sourceFiles.entries
.flatMap { (_, text) -> text.lines() }
.partition { it.startsWith("import") }
val importBlock = importLines.sorted().distinct()
val (packageLines, code) = remainder.partition { it.startsWith("package") }
val packageBlock = packageLines.distinct()
assert(
packageBlock.size <= 1
) // everything is in the same package and/or there is no package line
val sourceText = listOf(packageBlock, importBlock, code).flatten().joinToString("\n")
val (simpleNames, qualifiedNames) =
sourceText.findClasses().plus(sourceText.findOuterObjects()).unzip()
val instrumentation =
"listOf<kotlin.reflect.KClass<*>>(${qualifiedNames.joinToString(",") { "$it::class" }})"
// create new engine for each compilation
// (otherwise we sometimes get kotlin compiler exceptions)
val engine = ScriptEngineManager().getEngineByExtension("kts")!!
val classes =
try {
@Suppress("UNCHECKED_CAST")
engine.eval("$sourceText\n\n$instrumentation") as List<KClass<*>>
} catch (e: ScriptException) {
throw CompilationFailedException(e.message, e)
}
return simpleNames.zip(classes).toMap()
}
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright © 2024 Apple Inc. and the Pkl project authors. 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.pkl.codegen.kotlin
import java.nio.file.Path
import org.pkl.commons.createParentDirectories
import org.pkl.commons.writeString
data class PklModule(val name: String, val content: String) {
fun writeToDisk(path: Path): Path {
return path.createParentDirectories().writeString(content)
}
}

View File

@@ -0,0 +1 @@
org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory

View File

@@ -0,0 +1,159 @@
package my
import java.util.Objects
import kotlin.Any
import kotlin.Boolean
import kotlin.Int
import kotlin.Long
import kotlin.String
import kotlin.text.StringBuilder
import org.pkl.core.Duration
object Mod {
private fun appendProperty(
builder: StringBuilder,
name: String,
value: Any?
) {
builder.append("\n ").append(name).append(" = ")
val lines = value.toString().split("\n")
builder.append(lines[0])
for (i in 1..lines.lastIndex) {
builder.append("\n ").append(lines[i])
}
}
open class Foo(
open val one: Long
) {
open fun copy(one: Long = this.one): Foo = Foo(one)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (this.javaClass != other?.javaClass) return false
other as Foo
if (this.one != other.one) return false
return true
}
override fun hashCode(): Int {
var result = 1
result = 31 * result + Objects.hashCode(this.one)
return result
}
override fun toString(): String {
val builder = StringBuilder(100)
builder.append(Foo::class.java.simpleName).append(" {")
appendProperty(builder, "one", this.one)
builder.append("\n}")
return builder.toString()
}
}
open class None(
one: Long
) : Foo(one) {
open override fun copy(one: Long): None = None(one)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (this.javaClass != other?.javaClass) return false
other as None
if (this.one != other.one) return false
return true
}
override fun hashCode(): Int {
var result = 1
result = 31 * result + Objects.hashCode(this.one)
return result
}
override fun toString(): String {
val builder = StringBuilder(100)
builder.append(None::class.java.simpleName).append(" {")
appendProperty(builder, "one", this.one)
builder.append("\n}")
return builder.toString()
}
}
open class Bar(
one: Long,
open val two: String
) : None(one) {
open fun copy(one: Long = this.one, two: String = this.two): Bar = Bar(one, two)
open override fun copy(one: Long): Bar = Bar(one, two)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (this.javaClass != other?.javaClass) return false
other as Bar
if (this.one != other.one) return false
if (this.two != other.two) return false
return true
}
override fun hashCode(): Int {
var result = 1
result = 31 * result + Objects.hashCode(this.one)
result = 31 * result + Objects.hashCode(this.two)
return result
}
override fun toString(): String {
val builder = StringBuilder(150)
builder.append(Bar::class.java.simpleName).append(" {")
appendProperty(builder, "one", this.one)
appendProperty(builder, "two", this.two)
builder.append("\n}")
return builder.toString()
}
}
class Baz(
one: Long,
two: String,
val three: Duration
) : Bar(one, two) {
fun copy(
one: Long = this.one,
two: String = this.two,
three: Duration = this.three
): Baz = Baz(one, two, three)
override fun copy(one: Long, two: String): Baz = Baz(one, two, three)
override fun copy(one: Long): Baz = Baz(one, two, three)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (this.javaClass != other?.javaClass) return false
other as Baz
if (this.one != other.one) return false
if (this.two != other.two) return false
if (this.three != other.three) return false
return true
}
override fun hashCode(): Int {
var result = 1
result = 31 * result + Objects.hashCode(this.one)
result = 31 * result + Objects.hashCode(this.two)
result = 31 * result + Objects.hashCode(this.three)
return result
}
override fun toString(): String {
val builder = StringBuilder(200)
builder.append(Baz::class.java.simpleName).append(" {")
appendProperty(builder, "one", this.one)
appendProperty(builder, "two", this.two)
appendProperty(builder, "three", this.three)
builder.append("\n}")
return builder.toString()
}
}
}

View File

@@ -0,0 +1,89 @@
package my
import java.util.Objects
import kotlin.Any
import kotlin.Boolean
import kotlin.Int
import kotlin.String
import kotlin.text.StringBuilder
/**
* type alias comment.
* *emphasized* `code`.
*/
typealias Email = String
/**
* module comment.
* *emphasized* `code`.
*/
data class Mod(
/**
* module property comment.
* *emphasized* `code`.
*/
val pigeon: Person
) {
/**
* class comment.
* *emphasized* `code`.
*/
open class Product(
/**
* class property comment.
* *emphasized* `code`.
*/
open val price: String
) {
open fun copy(price: String = this.price): Product = Product(price)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (this.javaClass != other?.javaClass) return false
other as Product
if (this.price != other.price) return false
return true
}
override fun hashCode(): Int {
var result = 1
result = 31 * result + Objects.hashCode(this.price)
return result
}
override fun toString(): String {
val builder = StringBuilder(100)
builder.append(Product::class.java.simpleName).append(" {")
appendProperty(builder, "price", this.price)
builder.append("\n}")
return builder.toString()
}
}
/**
* class comment.
* *emphasized* `code`.
*/
data class Person(
/**
* class property comment.
* *emphasized* `code`.
*/
val name: String
)
companion object {
private fun appendProperty(
builder: StringBuilder,
name: String,
value: Any?
) {
builder.append("\n ").append(name).append(" = ")
val lines = value.toString().split("\n")
builder.append(lines[0])
for (i in 1..lines.lastIndex) {
builder.append("\n ").append(lines[i])
}
}
}
}

View File

@@ -0,0 +1,239 @@
package my
import java.util.Objects
import kotlin.Any
import kotlin.Boolean
import kotlin.Double
import kotlin.Int
import kotlin.Long
import kotlin.Pair
import kotlin.String
import kotlin.collections.Collection
import kotlin.collections.List
import kotlin.collections.Map
import kotlin.collections.Set
import kotlin.text.Regex
import kotlin.text.StringBuilder
import org.pkl.core.DataSize
import org.pkl.core.DataSizeUnit
import org.pkl.core.Duration
import org.pkl.core.DurationUnit
object Mod {
private fun appendProperty(
builder: StringBuilder,
name: String,
value: Any?
) {
builder.append("\n ").append(name).append(" = ")
val lines = value.toString().split("\n")
builder.append(lines[0])
for (i in 1..lines.lastIndex) {
builder.append("\n ").append(lines[i])
}
}
open class PropertyTypes(
open val boolean: Boolean,
open val int: Long,
open val float: Double,
open val string: String,
open val duration: Duration,
open val durationUnit: DurationUnit,
open val dataSize: DataSize,
open val dataSizeUnit: DataSizeUnit,
open val nullable: String?,
open val nullable2: String?,
open val pair: Pair<Any?, Any?>,
open val pair2: Pair<String, Other>,
open val coll: Collection<Any?>,
open val coll2: Collection<Other>,
open val list: List<Any?>,
open val list2: List<Other>,
open val set: Set<Any?>,
open val set2: Set<Other>,
open val map: Map<Any?, Any?>,
open val map2: Map<String, Other>,
open val container: Map<Any?, Any?>,
open val container2: Map<String, Other>,
open val other: Other,
open val regex: Regex,
open val any: Any?,
open val nonNull: Any,
open val enum: Direction
) {
open fun copy(
boolean: Boolean = this.boolean,
int: Long = this.int,
float: Double = this.float,
string: String = this.string,
duration: Duration = this.duration,
durationUnit: DurationUnit = this.durationUnit,
dataSize: DataSize = this.dataSize,
dataSizeUnit: DataSizeUnit = this.dataSizeUnit,
nullable: String? = this.nullable,
nullable2: String? = this.nullable2,
pair: Pair<Any?, Any?> = this.pair,
pair2: Pair<String, Other> = this.pair2,
coll: Collection<Any?> = this.coll,
coll2: Collection<Other> = this.coll2,
list: List<Any?> = this.list,
list2: List<Other> = this.list2,
set: Set<Any?> = this.set,
set2: Set<Other> = this.set2,
map: Map<Any?, Any?> = this.map,
map2: Map<String, Other> = this.map2,
container: Map<Any?, Any?> = this.container,
container2: Map<String, Other> = this.container2,
other: Other = this.other,
regex: Regex = this.regex,
any: Any? = this.any,
nonNull: Any = this.nonNull,
enum: Direction = this.enum
): PropertyTypes = PropertyTypes(boolean, int, float, string, duration, durationUnit, dataSize,
dataSizeUnit, nullable, nullable2, pair, pair2, coll, coll2, list, list2, set, set2, map,
map2, container, container2, other, regex, any, nonNull, enum)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (this.javaClass != other?.javaClass) return false
other as PropertyTypes
if (this.boolean != other.boolean) return false
if (this.int != other.int) return false
if (this.float != other.float) return false
if (this.string != other.string) return false
if (this.duration != other.duration) return false
if (this.durationUnit != other.durationUnit) return false
if (this.dataSize != other.dataSize) return false
if (this.dataSizeUnit != other.dataSizeUnit) return false
if (this.nullable != other.nullable) return false
if (this.nullable2 != other.nullable2) return false
if (this.pair != other.pair) return false
if (this.pair2 != other.pair2) return false
if (this.coll != other.coll) return false
if (this.coll2 != other.coll2) return false
if (this.list != other.list) return false
if (this.list2 != other.list2) return false
if (this.set != other.set) return false
if (this.set2 != other.set2) return false
if (this.map != other.map) return false
if (this.map2 != other.map2) return false
if (this.container != other.container) return false
if (this.container2 != other.container2) return false
if (this.other != other.other) return false
if (this.regex.pattern != other.regex.pattern) return false
if (this.any != other.any) return false
if (this.nonNull != other.nonNull) return false
if (this.enum != other.enum) return false
return true
}
override fun hashCode(): Int {
var result = 1
result = 31 * result + Objects.hashCode(this.boolean)
result = 31 * result + Objects.hashCode(this.int)
result = 31 * result + Objects.hashCode(this.float)
result = 31 * result + Objects.hashCode(this.string)
result = 31 * result + Objects.hashCode(this.duration)
result = 31 * result + Objects.hashCode(this.durationUnit)
result = 31 * result + Objects.hashCode(this.dataSize)
result = 31 * result + Objects.hashCode(this.dataSizeUnit)
result = 31 * result + Objects.hashCode(this.nullable)
result = 31 * result + Objects.hashCode(this.nullable2)
result = 31 * result + Objects.hashCode(this.pair)
result = 31 * result + Objects.hashCode(this.pair2)
result = 31 * result + Objects.hashCode(this.coll)
result = 31 * result + Objects.hashCode(this.coll2)
result = 31 * result + Objects.hashCode(this.list)
result = 31 * result + Objects.hashCode(this.list2)
result = 31 * result + Objects.hashCode(this.set)
result = 31 * result + Objects.hashCode(this.set2)
result = 31 * result + Objects.hashCode(this.map)
result = 31 * result + Objects.hashCode(this.map2)
result = 31 * result + Objects.hashCode(this.container)
result = 31 * result + Objects.hashCode(this.container2)
result = 31 * result + Objects.hashCode(this.other)
result = 31 * result + Objects.hashCode(this.regex)
result = 31 * result + Objects.hashCode(this.any)
result = 31 * result + Objects.hashCode(this.nonNull)
result = 31 * result + Objects.hashCode(this.enum)
return result
}
override fun toString(): String {
val builder = StringBuilder(1400)
builder.append(PropertyTypes::class.java.simpleName).append(" {")
appendProperty(builder, "boolean", this.boolean)
appendProperty(builder, "int", this.int)
appendProperty(builder, "float", this.float)
appendProperty(builder, "string", this.string)
appendProperty(builder, "duration", this.duration)
appendProperty(builder, "durationUnit", this.durationUnit)
appendProperty(builder, "dataSize", this.dataSize)
appendProperty(builder, "dataSizeUnit", this.dataSizeUnit)
appendProperty(builder, "nullable", this.nullable)
appendProperty(builder, "nullable2", this.nullable2)
appendProperty(builder, "pair", this.pair)
appendProperty(builder, "pair2", this.pair2)
appendProperty(builder, "coll", this.coll)
appendProperty(builder, "coll2", this.coll2)
appendProperty(builder, "list", this.list)
appendProperty(builder, "list2", this.list2)
appendProperty(builder, "set", this.set)
appendProperty(builder, "set2", this.set2)
appendProperty(builder, "map", this.map)
appendProperty(builder, "map2", this.map2)
appendProperty(builder, "container", this.container)
appendProperty(builder, "container2", this.container2)
appendProperty(builder, "other", this.other)
appendProperty(builder, "regex", this.regex)
appendProperty(builder, "any", this.any)
appendProperty(builder, "nonNull", this.nonNull)
appendProperty(builder, "enum", this.enum)
builder.append("\n}")
return builder.toString()
}
}
open class Other(
open val name: String
) {
open fun copy(name: String = this.name): Other = Other(name)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (this.javaClass != other?.javaClass) return false
other as Other
if (this.name != other.name) return false
return true
}
override fun hashCode(): Int {
var result = 1
result = 31 * result + Objects.hashCode(this.name)
return result
}
override fun toString(): String {
val builder = StringBuilder(100)
builder.append(Other::class.java.simpleName).append(" {")
appendProperty(builder, "name", this.name)
builder.append("\n}")
return builder.toString()
}
}
enum class Direction(
val value: String
) {
NORTH("north"),
EAST("east"),
SOUTH("south"),
WEST("west");
override fun toString() = value
}
}