├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── jitpack.yml ├── inikio-ksp ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ └── com.google.devtools.ksp.processing.SymbolProcessorProvider │ │ └── kotlin │ │ └── fp │ │ └── serrano │ │ └── inikio │ │ └── ksp │ │ ├── FileSpecBuilderBuilder.kt │ │ ├── InikioProvider.kt │ │ └── InikioProcessor.kt └── build.gradle.kts ├── renovate.json ├── gradle.properties ├── docs ├── scripts │ ├── sourceset_dependencies.js │ ├── clipboard.js │ ├── symbol-parameters-wrapper_deferred.js │ ├── navigation-loader.js │ ├── pages.json │ └── platform-content-handler.js ├── ui-kit │ ├── assets │ │ ├── arrow-down.svg │ │ ├── homepage.svg │ │ ├── checkbox-off.svg │ │ ├── placeholder.svg │ │ ├── checkbox-on.svg │ │ ├── burger.svg │ │ ├── cross.svg │ │ ├── exception-class.svg │ │ ├── enum.svg │ │ ├── field-value.svg │ │ ├── interface.svg │ │ ├── field-variable.svg │ │ ├── filter.svg │ │ ├── interface-kotlin.svg │ │ ├── enum-kotlin.svg │ │ ├── typealias-kotlin.svg │ │ ├── theme-toggle.svg │ │ ├── class.svg │ │ ├── object.svg │ │ ├── class-kotlin.svg │ │ ├── function.svg │ │ ├── abstract-class.svg │ │ ├── annotation.svg │ │ ├── abstract-class-kotlin.svg │ │ └── annotation-kotlin.svg │ └── ui-kit.min.js ├── images │ ├── copy-icon.svg │ ├── footer-go-to-link.svg │ ├── copy-successful-icon.svg │ ├── go-to-top-icon.svg │ ├── logo-icon.svg │ └── anchor-copy-button.svg ├── styles │ ├── logo-styles.css │ ├── font-jb-sans-auto.css │ └── prism.css ├── navigation.html └── -inikio │ ├── package-list │ ├── fp.serrano.inikio.plugin │ ├── -fixed-result-type │ │ ├── type.html │ │ └── index.html │ ├── -initial-style-d-s-l │ │ └── index.html │ └── index.html │ └── fp.serrano.inikio │ ├── -program-builder │ ├── -state │ │ └── index.html │ ├── -program-builder.html │ ├── perform-unit.html │ └── perform.html │ └── program.html ├── settings.gradle.kts ├── .idea └── vcs.xml ├── inikio-examples ├── src │ └── commonMain │ │ └── kotlin │ │ └── fp │ │ └── serrano │ │ └── inikio │ │ └── examples │ │ ├── Dice.kt │ │ ├── State.kt │ │ └── CardGame.kt └── build.gradle.kts ├── inikio-core ├── src │ └── commonMain │ │ └── kotlin │ │ └── fp │ │ └── serrano │ │ └── inikio │ │ ├── plugin │ │ └── Annotations.kt │ │ └── ProgramBuilder.kt ├── build.gradle.kts └── README.md ├── .gitignore ├── .github └── workflows │ └── pull_request.yml ├── gradlew.bat └── gradlew /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serras/inikio/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | # https://jitpack.io/docs/BUILDING/ 2 | 3 | # https://jitpack.io/docs/BUILDING/#java-version 4 | jdk: 5 | - openjdk11 -------------------------------------------------------------------------------- /inikio-ksp/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider: -------------------------------------------------------------------------------- 1 | fp.serrano.inikio.ksp.InikioProvider -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled 3 | org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true 4 | -------------------------------------------------------------------------------- /docs/scripts/sourceset_dependencies.js: -------------------------------------------------------------------------------- 1 | sourceset_dependencies='{":inikio-core/commonMain":[],":inikio-core/jsMain":[":inikio-core/commonMain"],":inikio-core/jvmMain":[":inikio-core/commonMain"]}' -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 2 | 3 | rootProject.name = "inikio" 4 | 5 | include(":inikio-core") 6 | include(":inikio-ksp") 7 | include(":inikio-examples") 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/homepage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/checkbox-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/images/copy-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/images/footer-go-to-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/placeholder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/images/copy-successful-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/styles/logo-styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | :root { 6 | --dokka-logo-image-url: url('../images/logo-icon.svg'); 7 | --dokka-logo-height: 28px; 8 | --dokka-logo-width: 28px; 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /docs/images/go-to-top-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/checkbox-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/burger.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /inikio-examples/src/commonMain/kotlin/fp/serrano/inikio/examples/Dice.kt: -------------------------------------------------------------------------------- 1 | package fp.serrano.inikio.examples 2 | 3 | import fp.serrano.inikio.plugin.FixedResultType 4 | import fp.serrano.inikio.plugin.InitialStyleDSL 5 | 6 | @InitialStyleDSL 7 | @FixedResultType("kotlin.Int") 8 | sealed interface Dice 9 | data class Result(val result: Int): Dice 10 | data class Throw(val next: (Int) -> Dice): Dice -------------------------------------------------------------------------------- /inikio-ksp/src/main/kotlin/fp/serrano/inikio/ksp/FileSpecBuilderBuilder.kt: -------------------------------------------------------------------------------- 1 | package fp.serrano.inikio.ksp 2 | 3 | class BuilderWrapper( 4 | var current: A 5 | ) { 6 | fun `do`(f: A.() -> A) { 7 | current = f(current) 8 | } 9 | 10 | fun end(f: A.() -> B): B = f(current) 11 | 12 | companion object { 13 | operator fun invoke(x: A, end: A.() -> B, block: BuilderWrapper.() -> Unit): B = 14 | BuilderWrapper(x).also(block).end(end) 15 | } 16 | } -------------------------------------------------------------------------------- /inikio-ksp/src/main/kotlin/fp/serrano/inikio/ksp/InikioProvider.kt: -------------------------------------------------------------------------------- 1 | package fp.serrano.inikio.ksp 2 | 3 | import com.google.devtools.ksp.processing.SymbolProcessor 4 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment 5 | import com.google.devtools.ksp.processing.SymbolProcessorProvider 6 | 7 | class InikioProvider: SymbolProcessorProvider { 8 | override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = 9 | InikioProcessor(environment) 10 | } -------------------------------------------------------------------------------- /docs/ui-kit/assets/exception-class.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/enum.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/field-value.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/interface.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /inikio-core/src/commonMain/kotlin/fp/serrano/inikio/plugin/Annotations.kt: -------------------------------------------------------------------------------- 1 | package fp.serrano.inikio.plugin 2 | 3 | /** 4 | * Instructs Inikio's KSP plug-in to create a Builder based on this hierarchy. 5 | */ 6 | public annotation class InitialStyleDSL() 7 | 8 | /** 9 | * Indicates that this DSL has a fixed result type, instead of being polymorphic 10 | * on the result type. 11 | * 12 | * The [type] must be a fully-qualified name. Unfortunately we cannot use a Class 13 | * here because of limitations of KSP + Kotlin Multiplatform. 14 | */ 15 | public annotation class FixedResultType(val type: String) 16 | 17 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/field-variable.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/filter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | kotlin-js-store 7 | .kotlin 8 | 9 | ### IntelliJ IDEA ### 10 | .idea/* 11 | !.idea/vcs.xml 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store -------------------------------------------------------------------------------- /docs/images/logo-icon.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/interface-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/enum-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/images/anchor-copy-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/typealias-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /inikio-examples/src/commonMain/kotlin/fp/serrano/inikio/examples/State.kt: -------------------------------------------------------------------------------- 1 | package fp.serrano.inikio.examples 2 | 3 | import fp.serrano.inikio.plugin.InitialStyleDSL 4 | 5 | @InitialStyleDSL 6 | sealed interface State { 7 | data class Finished(val result: A) : State 8 | data class Get(val next: (S) -> State) : State 9 | data class Put(val new: S, val next: () -> State) : State 10 | } 11 | 12 | fun State.execute(initial: S): Pair = 13 | when (this) { 14 | is State.Finished -> initial to result 15 | is State.Get -> next(initial).execute(initial) 16 | is State.Put -> next().execute(new) 17 | } 18 | 19 | fun increment(): State = state { 20 | put(get() + 1) 21 | } 22 | 23 | val stateExample = increment().execute(0) 24 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/theme-toggle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlin = "2.2.10" 3 | dokka = "2.0.0" 4 | ksp = "2.2.10-2.0.2" 5 | kotlinPoet = "2.2.0" 6 | 7 | [libraries] 8 | kotlin-stdlibCommon = { module = "org.jetbrains.kotlin:kotlin-stdlib-common" } 9 | ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } 10 | kotlinPoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinPoet" } 11 | kotlinPoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlinPoet" } 12 | 13 | [plugins] 14 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 15 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 16 | kotlin-js = { id = "org.jetbrains.kotlin.js", version.ref = "kotlin" } 17 | ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } 18 | dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } 19 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/class.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/object.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/class-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/function.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /inikio-examples/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("DSL_SCOPE_VIOLATION") 2 | 3 | import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile 4 | import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile 5 | 6 | plugins { 7 | alias(libs.plugins.kotlin.multiplatform) 8 | alias(libs.plugins.ksp) 9 | idea 10 | } 11 | 12 | kotlin { 13 | jvmToolchain(8) 14 | jvm() 15 | 16 | js(IR) { 17 | browser() 18 | nodejs() 19 | } 20 | 21 | sourceSets { 22 | commonMain { 23 | dependencies { 24 | implementation(libs.kotlin.stdlibCommon) 25 | implementation(projects.inikioCore) 26 | } 27 | kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") 28 | } 29 | } 30 | } 31 | 32 | dependencies { 33 | add("kspCommonMainMetadata", projects.inikioKsp) 34 | } 35 | 36 | tasks.withType { 37 | dependsOn("kspCommonMainKotlinMetadata") 38 | } 39 | 40 | tasks.getByName("jsSourcesJar") { 41 | dependsOn("kspCommonMainKotlinMetadata") 42 | } 43 | 44 | tasks.withType { 45 | dependsOn("kspCommonMainKotlinMetadata") 46 | } 47 | 48 | idea { 49 | module { 50 | // Not using += due to https://github.com/gradle/gradle/issues/8749 51 | sourceDirs = sourceDirs + file("build/generated/ksp/metadata/commonMain/kotlin") 52 | generatedSourceDirs = generatedSourceDirs + file("build/generated/ksp/metadata/commonMain/kotlin") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /inikio-ksp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("DSL_SCOPE_VIOLATION") 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.jvm) 5 | `maven-publish` 6 | } 7 | 8 | dependencies { 9 | implementation(libs.kotlin.stdlibCommon) 10 | implementation(projects.inikioCore) 11 | implementation(libs.ksp) 12 | implementation(libs.kotlinPoet) 13 | implementation(libs.kotlinPoet.ksp) 14 | } 15 | 16 | kotlin { 17 | jvmToolchain(8) 18 | } 19 | 20 | publishing { 21 | publications.withType().configureEach { 22 | pom { 23 | description.set("Better initial-style DSLs in Kotlin ") 24 | url.set("https://serranofp.com/inikio") 25 | name.set("Inikio") 26 | licenses { 27 | license { 28 | name.set("The Apache License, Version 2.0") 29 | url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") 30 | } 31 | } 32 | developers { 33 | developer { 34 | id.set("inikio-authors") 35 | name.set("The Inikio authors") 36 | } 37 | } 38 | scm { 39 | connection.set("scm:git:git://github.com/serras/inikio.git") 40 | developerConnection.set("scm:git:ssh://git@github.com/serras/inikio.git") 41 | url.set("https://github.com/serras/inikio") 42 | } 43 | } 44 | } 45 | } 46 | 47 | plugins.withType().configureEach { 48 | publishing { 49 | publications { 50 | register("maven") { 51 | from(components["java"]) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/abstract-class.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /inikio-core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("DSL_SCOPE_VIOLATION") 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.multiplatform) 5 | alias(libs.plugins.dokka) 6 | `maven-publish` 7 | } 8 | 9 | kotlin { 10 | jvmToolchain(8) 11 | jvm() 12 | 13 | js(IR) { 14 | browser() 15 | nodejs() 16 | } 17 | 18 | explicitApi() 19 | 20 | sourceSets { 21 | commonMain { 22 | dependencies { 23 | implementation(libs.kotlin.stdlibCommon) 24 | } 25 | } 26 | } 27 | } 28 | 29 | dokka { 30 | moduleName.set("Inikio") 31 | dokkaSourceSets.commonMain { 32 | includes.from("README.md") 33 | } 34 | dokkaPublications.html { 35 | outputDirectory.set(rootDir.resolve("docs")) 36 | } 37 | } 38 | 39 | val javadocJar by tasks.registering(Jar::class) { 40 | archiveClassifier.set("javadoc") 41 | from(tasks.dokkaGenerate) 42 | } 43 | 44 | publishing { 45 | publications.withType().configureEach { 46 | artifact(javadocJar) 47 | pom { 48 | description.set("Better initial-style DSLs in Kotlin ") 49 | url.set("https://serranofp.com/inikio") 50 | name.set("Inikio") 51 | licenses { 52 | license { 53 | name.set("The Apache License, Version 2.0") 54 | url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") 55 | } 56 | } 57 | developers { 58 | developer { 59 | id.set("inikio-authors") 60 | name.set("The Inikio authors") 61 | } 62 | } 63 | scm { 64 | connection.set("scm:git:git://github.com/serras/inikio.git") 65 | developerConnection.set("scm:git:ssh://git@github.com/serras/inikio.git") 66 | url.set("https://github.com/serras/inikio") 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/annotation.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/abstract-class-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/ui-kit/assets/annotation-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /inikio-examples/src/commonMain/kotlin/fp/serrano/inikio/examples/CardGame.kt: -------------------------------------------------------------------------------- 1 | package fp.serrano.inikio.examples 2 | 3 | import fp.serrano.inikio.plugin.InitialStyleDSL 4 | import kotlin.random.Random 5 | 6 | sealed interface Card 7 | 8 | // INITIAL-STYLE DSL 9 | // ----------------- 10 | 11 | @InitialStyleDSL 12 | sealed interface Attack 13 | 14 | data class Done(val result: A): Attack 15 | data class FlipCoin( 16 | val next: (Outcome) -> Attack 17 | ): Attack { 18 | enum class Outcome { 19 | HEADS, TAILS 20 | } 21 | } 22 | data class Draw( 23 | val next: (Card?) -> Attack 24 | ): Attack 25 | 26 | // utility functions for creating attacks 27 | 28 | suspend fun AttackBuilder.drawNRecursive(n: Int): List = when { 29 | n <= 0 -> emptyList() 30 | else -> { 31 | val first: List = listOfNotNull(draw()) 32 | first + drawNRecursive(n - 1) 33 | } 34 | } 35 | 36 | suspend fun AttackBuilder.drawNUtility(n: Int): List = 37 | buildList { 38 | repeat(n) { 39 | add(draw()) 40 | } 41 | }.filterNotNull() 42 | 43 | // EXECUTION OF A PROGRAM 44 | // ---------------------- 45 | 46 | fun Random.nextFlipOutcome(): FlipCoin.Outcome = 47 | if (nextBoolean()) FlipCoin.Outcome.HEADS else FlipCoin.Outcome.TAILS 48 | 49 | tailrec fun Attack.execute(log: List = emptyList()): Pair, A> = 50 | when(this) { 51 | is Done -> log to result 52 | is FlipCoin -> next(Random.nextFlipOutcome()).execute(log) 53 | is Draw -> next(null).execute(log + "draw") 54 | } 55 | 56 | // EXAMPLES OF PROGRAMS 57 | // -------------------- 58 | 59 | val ironTailR: Attack get() = attack { ironTailWorkerR() } 60 | 61 | suspend fun AttackBuilder.ironTailWorkerR(): Int = 62 | when (flipCoin()) { 63 | FlipCoin.Outcome.HEADS -> 30 + ironTailWorkerR() 64 | FlipCoin.Outcome.TAILS -> 0 65 | } 66 | -------------------------------------------------------------------------------- /docs/styles/font-jb-sans-auto.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | /* Light weight */ 6 | @font-face { 7 | font-family: 'JetBrains Sans'; 8 | src: url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-Light.woff2') format('woff2'), url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-Light.woff') format('woff'); 9 | font-weight: 300; 10 | font-style: normal; 11 | font-display: swap; 12 | } 13 | /* Regular weight */ 14 | @font-face { 15 | font-family: 'JetBrains Sans'; 16 | src: url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-Regular.woff2') format('woff2'), url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-Regular.woff') format('woff'); 17 | font-weight: 400; 18 | font-style: normal; 19 | font-display: swap; 20 | } 21 | /* SemiBold weight */ 22 | @font-face { 23 | font-family: 'JetBrains Sans'; 24 | src: url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-SemiBold.woff2') format('woff2'), url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-SemiBold.woff') format('woff'); 25 | font-weight: 600; 26 | font-style: normal; 27 | font-display: swap; 28 | } 29 | 30 | @supports (font-variation-settings: normal) { 31 | @font-face { 32 | font-family: 'JetBrains Sans'; 33 | src: url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans.woff2') format('woff2 supports variations'), 34 | url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans.woff2') format('woff2-variations'), 35 | url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans.woff') format('woff-variations'); 36 | font-weight: 100 900; 37 | font-style: normal; 38 | font-display: swap; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/scripts/clipboard.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | window.addEventListener('load', () => { 6 | document.querySelectorAll('span.copy-icon').forEach(element => { 7 | element.addEventListener('click', (el) => copyElementsContentToClipboard(element)); 8 | }) 9 | 10 | document.querySelectorAll('span.anchor-icon').forEach(element => { 11 | element.addEventListener('click', (el) => { 12 | if(element.hasAttribute('pointing-to')){ 13 | const location = hrefWithoutCurrentlyUsedAnchor() + '#' + element.getAttribute('pointing-to') 14 | copyTextToClipboard(element, location) 15 | } 16 | }); 17 | }) 18 | }) 19 | 20 | const copyElementsContentToClipboard = (element) => { 21 | const selection = window.getSelection(); 22 | const range = document.createRange(); 23 | range.selectNodeContents(element.parentNode.parentNode); 24 | selection.removeAllRanges(); 25 | selection.addRange(range); 26 | 27 | copyAndShowPopup(element, () => selection.removeAllRanges()) 28 | } 29 | 30 | const copyTextToClipboard = (element, text) => { 31 | var textarea = document.createElement("textarea"); 32 | textarea.textContent = text; 33 | textarea.style.position = "fixed"; 34 | document.body.appendChild(textarea); 35 | textarea.select(); 36 | 37 | copyAndShowPopup(element, () => document.body.removeChild(textarea)) 38 | } 39 | 40 | const copyAndShowPopup = (element, after) => { 41 | try { 42 | document.execCommand('copy'); 43 | element.nextElementSibling.classList.add('active-popup'); 44 | setTimeout(() => { 45 | element.nextElementSibling.classList.remove('active-popup'); 46 | }, 1200); 47 | } catch (e) { 48 | console.error('Failed to write to clipboard:', e) 49 | } 50 | finally { 51 | if(after) after() 52 | } 53 | } 54 | 55 | const hrefWithoutCurrentlyUsedAnchor = () => window.location.href.split('#')[0] 56 | 57 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: "pull_request" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | env: 12 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.parallel=false -Dorg.gradle.jvmargs="-Xmx5g -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8" 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v5 21 | with: 22 | fetch-depth: 0 23 | 24 | - name: Set up Java 25 | uses: actions/setup-java@v5 26 | with: 27 | distribution: 'adopt' 28 | java-version: 17 29 | 30 | - name: Setup Gradle 31 | uses: gradle/actions/setup-gradle@v4 32 | with: 33 | cache-read-only: ${{ github.ref != 'refs/heads/main' }} 34 | 35 | - name: Build 36 | run: ./gradlew build 37 | 38 | - name: Upload reports 39 | uses: actions/upload-artifact@v4 40 | with: 41 | name: 'reports-${{ matrix.os }}' 42 | path: '**/build/reports/**' 43 | 44 | - name: Stop Gradle daemons 45 | run: ./gradlew --stop 46 | 47 | update-docs: 48 | runs-on: ubuntu-latest 49 | timeout-minutes: 60 50 | 51 | steps: 52 | - uses: actions/checkout@v5 53 | with: 54 | fetch-depth: 0 55 | 56 | - name: Set up Java 57 | uses: actions/setup-java@v5 58 | with: 59 | distribution: 'adopt' 60 | java-version: 17 61 | 62 | - name: Checkout 63 | run: rm -rf docs 64 | 65 | - name: Setup Gradle 66 | uses: gradle/actions/setup-gradle@v4 67 | with: 68 | cache-read-only: ${{ github.ref != 'refs/heads/main' }} 69 | 70 | - name: Generate update documentation 71 | run: ./gradlew :inikio-core:dokkaGenerate 72 | 73 | - name: Stop Gradle daemons 74 | run: ./gradlew --stop 75 | 76 | - name: Commit updated documentation 77 | uses: stefanzweifel/git-auto-commit-action@v6 78 | with: 79 | commit_message: Update documentation 80 | file_pattern: "docs/*" 81 | -------------------------------------------------------------------------------- /docs/scripts/symbol-parameters-wrapper_deferred.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | // helps with some corner cases where starts working already, 6 | // but the signature is not yet long enough to be wrapped 7 | (function() { 8 | const leftPaddingPx = 60; 9 | 10 | function createNbspIndent() { 11 | let indent = document.createElement("span"); 12 | indent.append(document.createTextNode("\u00A0\u00A0\u00A0\u00A0")); 13 | indent.classList.add("nbsp-indent"); 14 | return indent; 15 | } 16 | 17 | function wrapSymbolParameters(entry) { 18 | const symbol = entry.target; 19 | const symbolBlockWidth = entry.borderBoxSize && entry.borderBoxSize[0] && entry.borderBoxSize[0].inlineSize; 20 | 21 | // Even though the script is marked as `defer` and we wait for `DOMContentLoaded` event, 22 | // or if this block is a part of hidden tab, it can happen that `symbolBlockWidth` is 0, 23 | // indicating that something hasn't been loaded. 24 | // In this case, observer will be triggered onсe again when it will be ready. 25 | if (symbolBlockWidth > 0) { 26 | const node = symbol.querySelector(".parameters"); 27 | 28 | if (node) { 29 | // if window resize happened and observer was triggered, reset previously wrapped 30 | // parameters as they might not need wrapping anymore, and check again 31 | node.classList.remove("wrapped"); 32 | node.querySelectorAll(".parameter .nbsp-indent") 33 | .forEach(indent => indent.remove()); 34 | 35 | const innerTextWidth = Array.from(symbol.children) 36 | .filter(it => !it.classList.contains("block")) // blocks are usually on their own (like annotations), so ignore it 37 | .map(it => it.getBoundingClientRect().width) 38 | .reduce((a, b) => a + b, 0); 39 | 40 | // if signature text takes up more than a single line, wrap params for readability 41 | if (innerTextWidth > (symbolBlockWidth - leftPaddingPx)) { 42 | node.classList.add("wrapped"); 43 | node.querySelectorAll(".parameter").forEach(param => { 44 | // has to be a physical indent so that it can be copied. styles like 45 | // paddings and `::before { content: " " }` do not work for that 46 | param.prepend(createNbspIndent()); 47 | }); 48 | } 49 | } 50 | } 51 | } 52 | 53 | const symbolsObserver = new ResizeObserver(entries => entries.forEach(wrapSymbolParameters)); 54 | 55 | function initHandlers() { 56 | document.querySelectorAll("div.symbol").forEach(symbol => symbolsObserver.observe(symbol)); 57 | } 58 | 59 | if (document.readyState === 'loading') window.addEventListener('DOMContentLoaded', initHandlers); 60 | else initHandlers(); 61 | 62 | // ToDo: Add `unobserve` if dokka will be SPA-like: 63 | // https://github.com/w3c/csswg-drafts/issues/5155 64 | })(); 65 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /docs/scripts/navigation-loader.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | navigationPageText = fetch(pathToRoot + "navigation.html").then(response => response.text()) 6 | 7 | displayNavigationFromPage = () => { 8 | navigationPageText.then(data => { 9 | document.getElementById("sideMenu").innerHTML = data; 10 | }).then(() => { 11 | document.querySelectorAll(".toc--row > a").forEach(link => { 12 | link.setAttribute("href", pathToRoot + link.getAttribute("href")); 13 | }) 14 | }).then(() => { 15 | document.querySelectorAll(".toc--part").forEach(nav => { 16 | if (!nav.classList.contains("toc--part_hidden")) 17 | nav.classList.add("toc--part_hidden") 18 | }) 19 | }).then(() => { 20 | revealNavigationForCurrentPage() 21 | }).then(() => { 22 | scrollNavigationToSelectedElement() 23 | }) 24 | document.querySelectorAll('.footer a[href^="#"]').forEach(anchor => { 25 | anchor.addEventListener('click', function (e) { 26 | e.preventDefault(); 27 | document.querySelector(this.getAttribute('href')).scrollIntoView({ 28 | behavior: 'smooth' 29 | }); 30 | }); 31 | }); 32 | } 33 | 34 | revealNavigationForCurrentPage = () => { 35 | let pageId = document.getElementById("content").attributes["pageIds"].value.toString(); 36 | let parts = document.querySelectorAll(".toc--part"); 37 | let found = 0; 38 | do { 39 | parts.forEach(part => { 40 | if (part.attributes['pageId'].value.indexOf(pageId) !== -1 && found === 0) { 41 | found = 1; 42 | if (part.classList.contains("toc--part_hidden")) { 43 | part.classList.remove("toc--part_hidden"); 44 | part.setAttribute('data-active', ""); 45 | } 46 | revealParents(part) 47 | } 48 | }); 49 | pageId = pageId.substring(0, pageId.lastIndexOf("/")) 50 | } while (pageId.indexOf("/") !== -1 && found === 0) 51 | }; 52 | revealParents = (part) => { 53 | if (part.classList.contains("toc--part")) { 54 | if (part.classList.contains("toc--part_hidden")) 55 | part.classList.remove("toc--part_hidden"); 56 | revealParents(part.parentNode) 57 | } 58 | }; 59 | 60 | scrollNavigationToSelectedElement = () => { 61 | let selectedElement = document.querySelector('div.toc--part[data-active]') 62 | if (selectedElement == null) { // nothing selected, probably just the main page opened 63 | return 64 | } 65 | 66 | let hasIcon = selectedElement.querySelectorAll(":scope > div.toc--row span.toc--icon").length > 0 67 | 68 | // for an instance enums also have children and are expandable but are not package/module elements 69 | let isPackageElement = selectedElement.children.length > 1 && !hasIcon 70 | if (isPackageElement) { 71 | // if a package is selected or linked, it makes sense to align it to top 72 | // so that you can see all the members it contains 73 | selectedElement.scrollIntoView(true) 74 | } else { 75 | // if a member within a package is linked, it makes sense to center it since it, 76 | // this should make it easier to look at surrounding members 77 | selectedElement.scrollIntoView({ 78 | behavior: 'auto', 79 | block: 'center', 80 | inline: 'center' 81 | }) 82 | } 83 | } 84 | 85 | /* 86 | This is a work-around for safari being IE of our times. 87 | It doesn't fire a DOMContentLoaded, presumably because eventListener is added after it wants to do it 88 | */ 89 | if (document.readyState === 'loading') { 90 | window.addEventListener('DOMContentLoaded', () => { 91 | displayNavigationFromPage() 92 | }) 93 | } else { 94 | displayNavigationFromPage() 95 | } 96 | -------------------------------------------------------------------------------- /docs/navigation.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 |
8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /docs/-inikio/package-list: -------------------------------------------------------------------------------- 1 | $dokka.format:html-v1 2 | $dokka.linkExtension:html 3 | $dokka.location:fp.serrano.inikio.plugin////PointingToDeclaration/-inikio/fp.serrano.inikio.plugin/index.html 4 | $dokka.location:fp.serrano.inikio.plugin/FixedResultType///PointingToDeclaration/-inikio/fp.serrano.inikio.plugin/-fixed-result-type/index.html 5 | $dokka.location:fp.serrano.inikio.plugin/FixedResultType/type/#/PointingToDeclaration/-inikio/fp.serrano.inikio.plugin/-fixed-result-type/type.html 6 | $dokka.location:fp.serrano.inikio.plugin/InitialStyleDSL///PointingToDeclaration/-inikio/fp.serrano.inikio.plugin/-initial-style-d-s-l/index.html 7 | $dokka.location:fp.serrano.inikio////PointingToDeclaration/-inikio/fp.serrano.inikio/index.html 8 | $dokka.location:fp.serrano.inikio//program/#TypeParam(bounds=[fp.serrano.inikio.ProgramBuilder[TypeParam(bounds=[kotlin.Any?]),TypeParam(bounds=[kotlin.Any?])]])#kotlin.coroutines.SuspendFunction1[TypeParam(bounds=[fp.serrano.inikio.ProgramBuilder[TypeParam(bounds=[kotlin.Any?]),TypeParam(bounds=[kotlin.Any?])]]),TypeParam(bounds=[kotlin.Any?])]/PointingToDeclaration/-inikio/fp.serrano.inikio/program.html 9 | $dokka.location:fp.serrano.inikio//program/#kotlin.Function1[TypeParam(bounds=[kotlin.Any?]),TypeParam(bounds=[kotlin.Any?])]#kotlin.Function1[kotlin.Throwable,TypeParam(bounds=[kotlin.Any?])]#kotlin.coroutines.SuspendFunction1[fp.serrano.inikio.ProgramBuilder[TypeParam(bounds=[kotlin.Any?]),TypeParam(bounds=[kotlin.Any?])],TypeParam(bounds=[kotlin.Any?])]/PointingToDeclaration/-inikio/fp.serrano.inikio/program.html 10 | $dokka.location:fp.serrano.inikio//program/#kotlin.Function1[TypeParam(bounds=[kotlin.Any?]),TypeParam(bounds=[kotlin.Any?])]#kotlin.coroutines.SuspendFunction1[fp.serrano.inikio.ProgramBuilder[TypeParam(bounds=[kotlin.Any?]),TypeParam(bounds=[kotlin.Any?])],TypeParam(bounds=[kotlin.Any?])]/PointingToDeclaration/-inikio/fp.serrano.inikio/program.html 11 | $dokka.location:fp.serrano.inikio/ProgramBuilder.State///PointingToDeclaration/-inikio/fp.serrano.inikio/-program-builder/-state/index.html 12 | $dokka.location:fp.serrano.inikio/ProgramBuilder///PointingToDeclaration/-inikio/fp.serrano.inikio/-program-builder/index.html 13 | $dokka.location:fp.serrano.inikio/ProgramBuilder/ProgramBuilder/#kotlin.Function1[TypeParam(bounds=[kotlin.Any?]),TypeParam(bounds=[kotlin.Any?])]#kotlin.Function1[kotlin.Throwable,TypeParam(bounds=[kotlin.Any?])]/PointingToDeclaration/-inikio/fp.serrano.inikio/-program-builder/-program-builder.html 14 | $dokka.location:fp.serrano.inikio/ProgramBuilder/perform/#kotlin.Function1[kotlin.Function1[TypeParam(bounds=[kotlin.Any?]),TypeParam(bounds=[kotlin.Any?])],TypeParam(bounds=[kotlin.Any?])]/PointingToDeclaration/-inikio/fp.serrano.inikio/-program-builder/perform.html 15 | $dokka.location:fp.serrano.inikio/ProgramBuilder/perform/#kotlin.Function2[TypeParam(bounds=[kotlin.Any?]),kotlin.Function1[TypeParam(bounds=[kotlin.Any?]),TypeParam(bounds=[kotlin.Any?])],TypeParam(bounds=[kotlin.Any?])]#TypeParam(bounds=[kotlin.Any?])/PointingToDeclaration/-inikio/fp.serrano.inikio/-program-builder/perform.html 16 | $dokka.location:fp.serrano.inikio/ProgramBuilder/perform/#kotlin.Function3[TypeParam(bounds=[kotlin.Any?]),TypeParam(bounds=[kotlin.Any?]),kotlin.Function1[TypeParam(bounds=[kotlin.Any?]),TypeParam(bounds=[kotlin.Any?])],TypeParam(bounds=[kotlin.Any?])]#TypeParam(bounds=[kotlin.Any?])#TypeParam(bounds=[kotlin.Any?])/PointingToDeclaration/-inikio/fp.serrano.inikio/-program-builder/perform.html 17 | $dokka.location:fp.serrano.inikio/ProgramBuilder/performUnit/#kotlin.Function1[kotlin.Function0[TypeParam(bounds=[kotlin.Any?])],TypeParam(bounds=[kotlin.Any?])]/PointingToDeclaration/-inikio/fp.serrano.inikio/-program-builder/perform-unit.html 18 | $dokka.location:fp.serrano.inikio/ProgramBuilder/performUnit/#kotlin.Function2[TypeParam(bounds=[kotlin.Any?]),kotlin.Function0[TypeParam(bounds=[kotlin.Any?])],TypeParam(bounds=[kotlin.Any?])]#TypeParam(bounds=[kotlin.Any?])/PointingToDeclaration/-inikio/fp.serrano.inikio/-program-builder/perform-unit.html 19 | $dokka.location:fp.serrano.inikio/ProgramBuilder/performUnit/#kotlin.Function3[TypeParam(bounds=[kotlin.Any?]),TypeParam(bounds=[kotlin.Any?]),kotlin.Function0[TypeParam(bounds=[kotlin.Any?])],TypeParam(bounds=[kotlin.Any?])]#TypeParam(bounds=[kotlin.Any?])#TypeParam(bounds=[kotlin.Any?])/PointingToDeclaration/-inikio/fp.serrano.inikio/-program-builder/perform-unit.html 20 | fp.serrano.inikio 21 | fp.serrano.inikio.plugin 22 | -------------------------------------------------------------------------------- /docs/scripts/pages.json: -------------------------------------------------------------------------------- 1 | [{"name":"annotation class FixedResultType(val type: String)","description":"fp.serrano.inikio.plugin.FixedResultType","location":"-inikio/fp.serrano.inikio.plugin/-fixed-result-type/index.html","searchKeys":["FixedResultType","annotation class FixedResultType(val type: String)","fp.serrano.inikio.plugin.FixedResultType"]},{"name":"annotation class InitialStyleDSL","description":"fp.serrano.inikio.plugin.InitialStyleDSL","location":"-inikio/fp.serrano.inikio.plugin/-initial-style-d-s-l/index.html","searchKeys":["InitialStyleDSL","annotation class InitialStyleDSL","fp.serrano.inikio.plugin.InitialStyleDSL"]},{"name":"constructor(endWith: (Result) -> Action, exceptional: (Throwable) -> Action = { e -> throw e })","description":"fp.serrano.inikio.ProgramBuilder.ProgramBuilder","location":"-inikio/fp.serrano.inikio/-program-builder/-program-builder.html","searchKeys":["ProgramBuilder","constructor(endWith: (Result) -> Action, exceptional: (Throwable) -> Action = { e -> throw e })","fp.serrano.inikio.ProgramBuilder.ProgramBuilder"]},{"name":"fun > program(machine: T, f: suspend T.() -> Result): Action","description":"fp.serrano.inikio.program","location":"-inikio/fp.serrano.inikio/program.html","searchKeys":["program","fun > program(machine: T, f: suspend T.() -> Result): Action","fp.serrano.inikio.program"]},{"name":"fun program(endWith: (Result) -> Action, exceptional: (Throwable) -> Action, f: suspend ProgramBuilder.() -> Result): Action","description":"fp.serrano.inikio.program","location":"-inikio/fp.serrano.inikio/program.html","searchKeys":["program","fun program(endWith: (Result) -> Action, exceptional: (Throwable) -> Action, f: suspend ProgramBuilder.() -> Result): Action","fp.serrano.inikio.program"]},{"name":"fun program(endWith: (Result) -> Action, f: suspend ProgramBuilder.() -> Result): Action","description":"fp.serrano.inikio.program","location":"-inikio/fp.serrano.inikio/program.html","searchKeys":["program","fun program(endWith: (Result) -> Action, f: suspend ProgramBuilder.() -> Result): Action","fp.serrano.inikio.program"]},{"name":"open class ProgramBuilder(endWith: (Result) -> Action, exceptional: (Throwable) -> Action = { e -> throw e })","description":"fp.serrano.inikio.ProgramBuilder","location":"-inikio/fp.serrano.inikio/-program-builder/index.html","searchKeys":["ProgramBuilder","open class ProgramBuilder(endWith: (Result) -> Action, exceptional: (Throwable) -> Action = { e -> throw e })","fp.serrano.inikio.ProgramBuilder"]},{"name":"sealed interface State","description":"fp.serrano.inikio.ProgramBuilder.State","location":"-inikio/fp.serrano.inikio/-program-builder/-state/index.html","searchKeys":["State","sealed interface State","fp.serrano.inikio.ProgramBuilder.State"]},{"name":"suspend fun perform(f: (A, B, (R) -> Action) -> Action, x: A, y: B): R","description":"fp.serrano.inikio.ProgramBuilder.perform","location":"-inikio/fp.serrano.inikio/-program-builder/perform.html","searchKeys":["perform","suspend fun perform(f: (A, B, (R) -> Action) -> Action, x: A, y: B): R","fp.serrano.inikio.ProgramBuilder.perform"]},{"name":"suspend fun performUnit(f: (A, B, () -> Action) -> Action, x: A, y: B)","description":"fp.serrano.inikio.ProgramBuilder.performUnit","location":"-inikio/fp.serrano.inikio/-program-builder/perform-unit.html","searchKeys":["performUnit","suspend fun performUnit(f: (A, B, () -> Action) -> Action, x: A, y: B)","fp.serrano.inikio.ProgramBuilder.performUnit"]},{"name":"suspend fun perform(f: (A, (R) -> Action) -> Action, x: A): R","description":"fp.serrano.inikio.ProgramBuilder.perform","location":"-inikio/fp.serrano.inikio/-program-builder/perform.html","searchKeys":["perform","suspend fun perform(f: (A, (R) -> Action) -> Action, x: A): R","fp.serrano.inikio.ProgramBuilder.perform"]},{"name":"suspend fun performUnit(f: (A, () -> Action) -> Action, x: A)","description":"fp.serrano.inikio.ProgramBuilder.performUnit","location":"-inikio/fp.serrano.inikio/-program-builder/perform-unit.html","searchKeys":["performUnit","suspend fun performUnit(f: (A, () -> Action) -> Action, x: A)","fp.serrano.inikio.ProgramBuilder.performUnit"]},{"name":"suspend fun perform(f: ((R) -> Action) -> Action): R","description":"fp.serrano.inikio.ProgramBuilder.perform","location":"-inikio/fp.serrano.inikio/-program-builder/perform.html","searchKeys":["perform","suspend fun perform(f: ((R) -> Action) -> Action): R","fp.serrano.inikio.ProgramBuilder.perform"]},{"name":"suspend fun performUnit(f: (() -> Action) -> Action)","description":"fp.serrano.inikio.ProgramBuilder.performUnit","location":"-inikio/fp.serrano.inikio/-program-builder/perform-unit.html","searchKeys":["performUnit","suspend fun performUnit(f: (() -> Action) -> Action)","fp.serrano.inikio.ProgramBuilder.performUnit"]},{"name":"val type: String","description":"fp.serrano.inikio.plugin.FixedResultType.type","location":"-inikio/fp.serrano.inikio.plugin/-fixed-result-type/type.html","searchKeys":["type","val type: String","fp.serrano.inikio.plugin.FixedResultType.type"]}] -------------------------------------------------------------------------------- /docs/styles/prism.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Custom Dokka styles 3 | */ 4 | code .token { 5 | white-space: pre; 6 | } 7 | 8 | /** 9 | * Styles based on webhelp's prism.js styles 10 | * Changes: 11 | * - Since webhelp's styles are in .pcss, they use nesting which is not achievable in native CSS 12 | * so nested css blocks have been unrolled (like dark theme). 13 | * - Webhelp uses "Custom Class" prism.js plugin, so all of their prism classes are prefixed with "--prism". 14 | * Dokka doesn't seem to need this plugin at the moment, so all "--prism" prefixes have been removed. 15 | * - Removed all styles related to `pre` and `code` tags. Kotlinlang's resulting styles are so spread out and complicated 16 | * that it's difficult to gather in one place. Instead use code styles defined in the main Dokka styles, 17 | * which at the moment looks fairly similar. 18 | * 19 | * Based on prism.js default theme 20 | * Based on dabblet (http://dabblet.com) 21 | * @author Lea Verou 22 | */ 23 | 24 | .token.comment, 25 | .token.prolog, 26 | .token.doctype, 27 | .token.cdata { 28 | color: #8c8c8c; 29 | } 30 | 31 | .token.punctuation { 32 | color: #999; 33 | } 34 | 35 | .token.namespace { 36 | opacity: 0.7; 37 | } 38 | 39 | .token.property, 40 | .token.tag, 41 | .token.boolean, 42 | .token.number, 43 | .token.constant, 44 | .token.symbol, 45 | .token.deleted { 46 | color: #871094; 47 | } 48 | 49 | .token.selector, 50 | .token.attr-name, 51 | .token.string, 52 | .token.char, 53 | .token.builtin, 54 | .token.inserted { 55 | color: #067d17; 56 | } 57 | 58 | .token.operator, 59 | .token.entity, 60 | .token.url, 61 | .language-css .token.string, 62 | .style .token.string { 63 | color: #9a6e3a; 64 | /* This background color was intended by the author of this theme. */ 65 | background: hsla(0, 0%, 100%, 0.5); 66 | } 67 | 68 | .token.atrule, 69 | .token.attr-value, 70 | .token.keyword { 71 | font-size: inherit; /* to override .keyword */ 72 | color: #0033b3; 73 | } 74 | 75 | .token.function { 76 | color: #00627a; 77 | } 78 | 79 | .token.class-name { 80 | color: #000000; 81 | } 82 | 83 | .token.regex, 84 | .token.important, 85 | .token.variable { 86 | color: #871094; 87 | } 88 | 89 | .token.important, 90 | .token.bold { 91 | font-weight: bold; 92 | } 93 | .token.italic { 94 | font-style: italic; 95 | } 96 | 97 | .token.entity { 98 | cursor: help; 99 | } 100 | 101 | .token.operator { 102 | background: none; 103 | } 104 | 105 | /* 106 | * DARK THEME 107 | */ 108 | :root.theme-dark .token.comment, 109 | :root.theme-dark .token.prolog, 110 | :root.theme-dark .token.cdata { 111 | color: #808080; 112 | } 113 | 114 | :root.theme-dark .token.delimiter, 115 | :root.theme-dark .token.boolean, 116 | :root.theme-dark .token.keyword, 117 | :root.theme-dark .token.selector, 118 | :root.theme-dark .token.important, 119 | :root.theme-dark .token.atrule { 120 | color: #cc7832; 121 | } 122 | 123 | :root.theme-dark .token.operator, 124 | :root.theme-dark .token.punctuation, 125 | :root.theme-dark .token.attr-name { 126 | color: #a9b7c6; 127 | } 128 | 129 | :root.theme-dark .token.tag, 130 | :root.theme-dark .token.tag .punctuation, 131 | :root.theme-dark .token.doctype, 132 | :root.theme-dark .token.builtin { 133 | color: #e8bf6a; 134 | } 135 | 136 | :root.theme-dark .token.entity, 137 | :root.theme-dark .token.number, 138 | :root.theme-dark .token.symbol { 139 | color: #6897bb; 140 | } 141 | 142 | :root.theme-dark .token.property, 143 | :root.theme-dark .token.constant, 144 | :root.theme-dark .token.variable { 145 | color: #9876aa; 146 | } 147 | 148 | :root.theme-dark .token.string, 149 | :root.theme-dark .token.char { 150 | color: #6a8759; 151 | } 152 | 153 | :root.theme-dark .token.attr-value, 154 | :root.theme-dark .token.attr-value .punctuation { 155 | color: #a5c261; 156 | } 157 | 158 | :root.theme-dark .token.attr-value .punctuation:first-child { 159 | color: #a9b7c6; 160 | } 161 | 162 | :root.theme-dark .token.url { 163 | text-decoration: underline; 164 | 165 | color: #287bde; 166 | background: transparent; 167 | } 168 | 169 | :root.theme-dark .token.function { 170 | color: #ffc66d; 171 | } 172 | 173 | :root.theme-dark .token.regex { 174 | background: #364135; 175 | } 176 | 177 | :root.theme-dark .token.deleted { 178 | background: #484a4a; 179 | } 180 | 181 | :root.theme-dark .token.inserted { 182 | background: #294436; 183 | } 184 | 185 | :root.theme-dark .token.class-name { 186 | color: #a9b7c6; 187 | } 188 | 189 | :root.theme-dark .token.function { 190 | color: #ffc66d; 191 | } 192 | 193 | :root.theme-darkcode .language-css .token.property, 194 | :root.theme-darkcode .language-css, 195 | :root.theme-dark .token.property + .token.punctuation { 196 | color: #a9b7c6; 197 | } 198 | 199 | code.language-css .token.id { 200 | color: #ffc66d; 201 | } 202 | 203 | :root.theme-dark code.language-css .token.selector > .token.class, 204 | :root.theme-dark code.language-css .token.selector > .token.attribute, 205 | :root.theme-dark code.language-css .token.selector > .token.pseudo-class, 206 | :root.theme-dark code.language-css .token.selector > .token.pseudo-element { 207 | color: #ffc66d; 208 | } 209 | 210 | :root.theme-dark .language-plaintext .token { 211 | /* plaintext code should be colored as article text */ 212 | color: inherit !important; 213 | } 214 | -------------------------------------------------------------------------------- /inikio-core/src/commonMain/kotlin/fp/serrano/inikio/ProgramBuilder.kt: -------------------------------------------------------------------------------- 1 | package fp.serrano.inikio 2 | 3 | import kotlin.coroutines.Continuation 4 | import kotlin.coroutines.EmptyCoroutineContext 5 | import kotlin.coroutines.resume 6 | import kotlin.coroutines.startCoroutine 7 | import kotlin.coroutines.suspendCoroutine 8 | 9 | /** 10 | * This class provides a DSL to build initial-style programs, given the type of basic actions or instructions. 11 | * 12 | * The type arguments are a bit tricky to use, due to the restrictions Kotlin's type system: 13 | * * To create generic utilities over any program, you should use a completely polymorphic 14 | * `ProgramBuilder` as the extension receiver. 15 | * * For each particular action you usually want to instantiate [ProgramBuilder] using `, A>` 16 | * everywhere it appears. We highly recommend to create a `typealias` for it. 17 | */ 18 | @Suppress("UNCHECKED_CAST") 19 | public open class ProgramBuilder public constructor( 20 | private val endWith: (Result) -> Action, 21 | private val exceptional: (Throwable) -> Action = { e -> throw e } 22 | ) { 23 | public sealed interface State 24 | private data class DoneState( 25 | val result: Result 26 | ): State 27 | private data class ExceptionState( 28 | val result: Throwable 29 | ): State 30 | private data class ActionState( 31 | val f: ((Any?) -> Action) -> Action, 32 | val next: Continuation 33 | ): State 34 | 35 | private var current: State = ExceptionState(IllegalStateException("empty trace")) 36 | 37 | /** 38 | * Record the execution of an action which consumes a value of type `R`. 39 | */ 40 | public suspend fun perform(f: ((R) -> Action) -> Action): R = suspendCoroutine { k -> 41 | current = ActionState(f, k as Continuation) 42 | } 43 | 44 | /** 45 | * Record the execution of an action which doesn't consume any value. 46 | */ 47 | public suspend fun performUnit(f: (() -> Action) -> Action): Unit = 48 | perform { arg -> f { arg(Unit) } } 49 | 50 | /** 51 | * Record the execution of an action which doesn't consume any value, 52 | * and requires an additional argument of type `A`. 53 | */ 54 | public suspend fun performUnit(f: (A, () -> Action) -> Action, x: A): Unit = 55 | perform { arg -> f(x) { arg(Unit) } } 56 | 57 | /** 58 | * Record the execution of an action which doesn't consume any value, 59 | * and requires additional arguments of types `A` and `B`. 60 | */ 61 | public suspend fun performUnit(f: (A, B, () -> Action) -> Action, x: A, y: B): Unit = 62 | perform { arg -> f(x, y) { arg(Unit) } } 63 | 64 | /** 65 | * Record the execution of an action which consumes a value of type `R`, 66 | * and requires an additional argument of type `A`. 67 | */ 68 | public suspend fun perform(f: (A, (R) -> Action) -> Action, x: A): R = 69 | perform { arg -> f(x, arg) } 70 | 71 | /** 72 | * Record the execution of an action which consumes a value of type `R`, 73 | * and requires additional arguments of types `A` and `B`. 74 | */ 75 | public suspend fun perform(f: (A, B, (R) -> Action) -> Action, x: A, y: B): R = 76 | perform { arg -> f(x, y, arg) } 77 | 78 | internal fun done(result: Result) { 79 | current = DoneState(result) 80 | } 81 | 82 | internal fun exception(e: Throwable) { 83 | current = ExceptionState(e) 84 | } 85 | 86 | internal fun execute(): Action = when (val c = current) { 87 | is DoneState -> endWith(c.result) 88 | is ExceptionState -> exceptional(c.result) 89 | is ActionState -> c.f { x: Any? -> 90 | c.next.resume(x) 91 | execute() 92 | } 93 | } 94 | } 95 | 96 | /** 97 | * Turns the DSL provided by [ProgramBuilder] into an actual [Action]. 98 | * This version is used for "pure" programs with no exceptional cases. 99 | * 100 | * @param endWith Reference to the "final" instruction in [Action]. 101 | * 102 | * @exception Throwable If an exception is raised during the execution of [f], it will be propagated. 103 | */ 104 | public fun program( 105 | endWith: (Result) -> Action, 106 | f: suspend ProgramBuilder.() -> Result 107 | ): Action = program(endWith, { e -> throw e }, f) 108 | 109 | /** 110 | * Turns the DSL provided by [ProgramBuilder] into an actual [Action]. 111 | * 112 | * @param endWith Reference to the "final" instruction in [Action]. 113 | * @param exceptional Reference to the "error" instruction in [Action]. 114 | */ 115 | public fun program( 116 | endWith: (Result) -> Action, 117 | exceptional: (Throwable) -> Action, 118 | f: suspend ProgramBuilder.() -> Result 119 | ): Action = program(ProgramBuilder(endWith, exceptional), f) 120 | 121 | /** 122 | * Turns the DSL provided by a subclass [T] of [ProgramBuilder] into an actual [Action]. 123 | */ 124 | public fun > program( 125 | machine: T, 126 | f: suspend T.() -> Result 127 | ): Action { 128 | f.startCoroutine( 129 | machine, Continuation(EmptyCoroutineContext) { 130 | it.fold( 131 | onSuccess = { result -> machine.done(result) }, 132 | onFailure = { e -> machine.exception(e) } 133 | ) 134 | } 135 | ) 136 | return machine.execute() 137 | } 138 | 139 | 140 | -------------------------------------------------------------------------------- /inikio-ksp/src/main/kotlin/fp/serrano/inikio/ksp/InikioProcessor.kt: -------------------------------------------------------------------------------- 1 | package fp.serrano.inikio.ksp 2 | 3 | import com.google.devtools.ksp.* 4 | import com.google.devtools.ksp.processing.* 5 | import com.google.devtools.ksp.symbol.* 6 | import com.squareup.kotlinpoet.* 7 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 8 | import com.squareup.kotlinpoet.ksp.* 9 | import fp.serrano.inikio.plugin.* 10 | import fp.serrano.inikio.ProgramBuilder 11 | import kotlin.coroutines.RestrictsSuspension 12 | 13 | @OptIn(KspExperimental::class) 14 | class InikioProcessor( 15 | private val environment: SymbolProcessorEnvironment 16 | ): SymbolProcessor { 17 | override fun process(resolver: Resolver): List { 18 | resolver.getNewFiles().forEach { file -> 19 | file 20 | .declarations 21 | .filterIsInstance() 22 | .filter { it.isAnnotationPresent(InitialStyleDSL::class) } 23 | .forEach { it.process(file) } 24 | } 25 | 26 | return emptyList() 27 | } 28 | 29 | private fun KSClassDeclaration.process(originalFile: KSFile) { 30 | val klass = this 31 | val klassName = simpleName.asString() 32 | val hasTypeArgs = typeParameters.isNotEmpty() 33 | val typeArgs = typeParameters.map { TypeVariableName(it.name.asString()) } 34 | val builderName = "${klassName}Builder" 35 | val builderFullName = ClassName(packageName.asString(), builderName) 36 | 37 | val doneClass = this.findDoneClass() 38 | if (doneClass == null) { 39 | environment.logger.error("No 'done class' found") 40 | return 41 | } 42 | 43 | BuilderWrapper(FileSpec.builder(packageName.asString(), builderName), { build() }) { 44 | 45 | val originalType = 46 | if (hasTypeArgs) klass.toClassName().parameterizedBy(typeArgs) else klass.toClassName() 47 | val programBuilderType = 48 | ProgramBuilder::class.asClassName().parameterizedBy( 49 | originalType, 50 | resultType() 51 | ) 52 | 53 | `do` { 54 | addType( 55 | BuilderWrapper(TypeSpec.classBuilder(builderName), { build() }) { 56 | `do` { addAnnotation(RestrictsSuspension::class) } 57 | if (hasTypeArgs) { 58 | `do` { addTypeVariables(typeArgs) } 59 | } 60 | `do` { superclass(programBuilderType) } 61 | `do` { 62 | val doneClassQName = doneClass.qualifiedName?.asString() ?: doneClass.simpleName.asString() 63 | addSuperclassConstructorParameter( 64 | "{ result -> $doneClassQName(result) }" 65 | ) 66 | } 67 | 68 | klass.instructionClasses().forEach { instr -> 69 | val instrName = instr.simpleName.asString() 70 | val instrQName = instr.qualifiedName?.asString() ?: instrName 71 | val instrProperties = instr.getDeclaredProperties().toList() 72 | val instrArgs = instrProperties.dropLast(1) 73 | val continuation = instrProperties.last() 74 | 75 | val continuationType = when (val t = continuation.type.toTypeName(typeParameters.toTypeParameterResolver())) { 76 | is ParameterizedTypeName -> 77 | if (t.rawType.canonicalName == "kotlin.Function0") UNIT 78 | else t.typeArguments.firstOrNull() 79 | is LambdaTypeName -> t.parameters.firstOrNull()?.type 80 | else -> null 81 | } ?: UNIT 82 | 83 | `do` { 84 | addFunction( 85 | BuilderWrapper(FunSpec.builder(instrName.firstLower()), { build() }) { 86 | `do` { addModifiers(KModifier.SUSPEND) } 87 | instrArgs.forEach { property -> 88 | `do` { 89 | addParameter( 90 | ParameterSpec.builder( 91 | property.simpleName.asString(), 92 | property.type.toTypeName(typeParameters.toTypeParameterResolver()) 93 | ).build() 94 | ) 95 | } 96 | } 97 | `do` { returns(continuationType) } 98 | val args = instrArgs.map { it.simpleName.asString() } + listOf("arg") 99 | `do` { 100 | when (continuationType) { 101 | UNIT -> addStatement( 102 | "return performUnit { arg -> ${instrQName}(${args.joinToString(separator = ", ")}) }" 103 | ) 104 | else -> addStatement( 105 | "return perform { arg -> ${instrQName}(${args.joinToString(separator = ", ")}) }" 106 | ) 107 | } 108 | } 109 | } 110 | ) 111 | } 112 | } 113 | } 114 | ) 115 | } 116 | 117 | `do` { 118 | addFunction( 119 | BuilderWrapper(FunSpec.builder(klassName.firstLower()), { build() }) { 120 | if (hasTypeArgs) { 121 | `do` { addTypeVariables(typeArgs) } 122 | } 123 | `do` { 124 | addParameter( 125 | ParameterSpec.builder( 126 | "block", 127 | LambdaTypeName.get( 128 | receiver = 129 | if (hasTypeArgs) builderFullName.parameterizedBy(typeArgs) else builderFullName, 130 | returnType = resultType() 131 | ).copy(suspending = true) 132 | ).build() 133 | ) 134 | } 135 | `do` { returns(originalType) } 136 | `do` { 137 | addStatement( 138 | "return fp.serrano.inikio.program(${builderName}(), block)" 139 | ) 140 | } 141 | } 142 | ) 143 | } 144 | 145 | }.writeTo(environment.codeGenerator, true, listOf(originalFile)) 146 | } 147 | 148 | private fun KSClassDeclaration.resultType(): TypeName { 149 | val resultAnnotations = getAnnotationsByType(FixedResultType::class).toList() 150 | return when { 151 | resultAnnotations.isEmpty() -> typeParameters.last().toTypeVariableName() 152 | else -> ClassName.bestGuess(resultAnnotations.first().type) 153 | } 154 | } 155 | 156 | private fun KSClassDeclaration.findDoneClass(): KSClassDeclaration? = 157 | getSealedSubclasses().find { subclass -> 158 | val properties = subclass.getDeclaredProperties() 159 | properties.count() == 1 && 160 | properties.all { property -> 161 | !property.type.resolve().isFunctionType 162 | } 163 | } 164 | 165 | private fun KSClassDeclaration.instructionClasses(): Sequence = 166 | getSealedSubclasses().filter { subclass -> 167 | subclass.getDeclaredProperties().last().type.resolve().isFunctionType 168 | } 169 | } 170 | 171 | fun String.firstLower() = replaceFirstChar { if (it.isUpperCase()) it.lowercaseChar() else it } -------------------------------------------------------------------------------- /docs/ui-kit/ui-kit.min.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";function t(e,n){return!(!e||!e.classList.contains(n))||!!e.parentElement&&t(e.parentElement,n)}document.addEventListener("DOMContentLoaded",(function(){document.querySelectorAll("div.button").forEach((function(t){t.addEventListener("keydown",(function(e){var n=e.key;"Enter"!==n&&" "!==n||t.dispatchEvent(new MouseEvent("click"))}))}))}));function e(){return window.innerWidth<440?"mobile":window.innerWidth>=440&&window.innerWidth<900?"tablet":"desktop"}var n=function(){function t(t){this.trapElement=t,this.handleKeyDown=this.handleKeyDown.bind(this),this.trapElement.addEventListener("keydown",this.handleKeyDown)}return t.prototype.handleKeyDown=function(t){var e=Array.from(this.trapElement.querySelectorAll('[role="option"]')).filter((function(t){return"none"!==t.style.display&&-1!==t.tabIndex}));if(["Tab","ArrowDown","ArrowUp"].includes(t.key)&&0!==e.length){var n=e[0],o=e[e.length-1];if("ArrowUp"===t.key)if(document.activeElement===n)o.focus();else{var r=e.indexOf(document.activeElement);e[r-1].focus()}"ArrowDown"===t.key&&(document.activeElement===o?n.focus():(r=e.indexOf(document.activeElement),e[r+1].focus())),"Tab"===t.key&&(t.shiftKey?document.activeElement===n&&(o.focus(),t.preventDefault()):document.activeElement===o&&(n.focus(),t.preventDefault()))}},t.prototype.destroy=function(){this.trapElement.removeEventListener("keydown",this.handleKeyDown)},t}(),o='[data-role="dropdown"]',r='[data-role="dropdown-toggle"]',i='[data-role="dropdown-listbox"]';function l(t){var e,n,o=t.querySelectorAll(r);null==o||o.forEach(a),e=t.querySelector(i),n=o[0].offsetWidth,e&&(e.classList.toggle("dropdown--list_expanded"),e.classList.contains("dropdown--list_expanded")?c(e,n):c(e,void 0))}function a(t){t.classList.contains("button_dropdown")&&t.classList.toggle("button_dropdown_active")}function c(t,e){if(e){var n=parseInt(getComputedStyle(t).minWidth,10),o=isNaN(n)?e:Math.max(n,e);t.style.minWidth="".concat(o,"px")}else t.style.minWidth=""}function d(e){var n=e.target;t(n,"dropdown")&&"dropdown--overlay"!==n.className||document.querySelectorAll(o).forEach((function(t){var e,n;null===(e=t.querySelectorAll(r))||void 0===e||e.forEach((function(t){t.classList.remove("button_dropdown_active")})),null===(n=t.querySelectorAll(i))||void 0===n||n.forEach((function(t){t.classList.remove("dropdown--list_expanded"),t.style.minWidth=""}))}))}function u(t){t.tag.removeAttribute("style"),t.option.setAttribute("style","display: none")}function s(t){t.tag.setAttribute("style","display: none"),t.option.removeAttribute("style")}function f(t){var e,n=null===(e=t.querySelector(".checkbox--input"))||void 0===e?void 0:e.getAttribute("data-filter");n&&(-1===filteringContext.activeFilters.findIndex((function(t){return t===n}))?unfilterSourceset(n):filterSourceset(n)),refreshFiltering()}document.addEventListener("DOMContentLoaded",(function(){document.querySelectorAll(o).forEach((function(t){var e;null===(e=t.querySelectorAll(r))||void 0===e||e.forEach((function(e){e.addEventListener("click",(function(){return l(t)}))})),function(t){new n(t),t.addEventListener("keydown",(function(e){var n;"Escape"===e.key&&(l(t),null===(n=t.querySelector(r))||void 0===n||n.focus())}))}(t)})),document.addEventListener("click",d)})),document.addEventListener("DOMContentLoaded",(function(){var t=document.getElementById("navigation-wrapper"),n=document.getElementById("library-version"),o=document.getElementById("filter-section"),r=document.querySelector("#filter-section + .navigation-controls--btn"),i=document.getElementById("filter-section-dropdown");if(t&&n&&o&&r&&i){var l=null==o?void 0:o.querySelectorAll(".dropdown--option"),a=null==o?void 0:o.querySelectorAll(".platform-selector");if(a&&l)if(a.length===l.length){var c=Array.from({length:a.length}).map((function(t,e){return{tag:a[e],option:l[e]}})),d=c.map((function(t){return t.tag.getBoundingClientRect().width})),v=e(),m=new ResizeObserver((function(){var n=e();v!==n&&(c.forEach(u),d=function(t){return t.map((function(t){return t.tag.getBoundingClientRect().width}))}(c)),v=n,y(),m.unobserve(t)})),g=function(){m.observe(t)};y(),g(),l.forEach((function(t){t.addEventListener("click",(function(t){f(t.target)})),t.addEventListener("keydown",(function(t){var e=t.key;"Enter"!==e&&" "!==e||f(t.target)}))})),window.addEventListener("resize",g)}else console.warn("Dokka: filter section items are not equal");else console.warn("Dokka: filter section items are not found")}else console.warn("Dokka: filter section is not found");function y(){var e,l;if(t&&i){if(t.getBoundingClientRect().width<900)return c.forEach(s),void i.removeAttribute("style");var a=(n&&r?r.getBoundingClientRect().left-n.getBoundingClientRect().right:0)-44-10,f=0;i.removeAttribute("style");var v=!1;c.forEach((function(t,e){(f+=d[e]+4)=t.length&&(t=void 0),{value:t&&t[o++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},m=function(){var t=!1;try{var e="__testLocalStorageKey__";localStorage.setItem(e,e),localStorage.removeItem(e),t=!0}catch(t){console.error("Local storage is not available",t)}return{getItem:function(e){return t?localStorage.getItem(e):null},setItem:function(e,n){t&&localStorage.setItem(e,n)}}}();function g(t){var e,n=null===(e=t.getAttribute("data-togglable"))||void 0===e?void 0:e.split(",");!function(){var e,n,o,r;try{for(var i=v(document.getElementsByClassName("tabs-section")),l=i.next();!l.done;l=i.next()){var a=l.value;try{for(var c=(o=void 0,v(a.children)),d=c.next();!d.done;d=c.next()){var u=d.value;u.getAttribute("data-togglable")===t.getAttribute("data-togglable")?u.setAttribute("data-active",""):u.removeAttribute("data-active")}}catch(t){o={error:t}}finally{try{d&&!d.done&&(r=c.return)&&r.call(c)}finally{if(o)throw o.error}}}}catch(t){e={error:t}}finally{try{l&&!l.done&&(n=i.return)&&n.call(i)}finally{if(e)throw e.error}}}(),document.querySelectorAll(".tabs-section-body *[data-togglable]").forEach((function(t){var e=t.getAttribute("data-togglable");n&&e&&n.includes(e)?t.setAttribute("data-active",""):t.classList.contains("sourceset-dependent-content")||t.removeAttribute("data-active")}))}window.initTabs=function(){var t=document.querySelector(".main-content"),e="active-tab-"+(t?t.getAttribute("data-page-type"):null);document.querySelectorAll("div[tabs-section]").forEach((function(t){!function(t){var e=t.querySelector("button[data-active]");e&&g(e)}(t),t.addEventListener("click",(function(t){var n=t.target,o=n?n.getAttribute("data-togglable"):null;o&&(m.setItem(e,JSON.stringify(o)),g(n))}))}));var n=m.getItem(e);if(n){var o=document.querySelector('div[tabs-section] > button[data-togglable="'+JSON.parse(n)+'"]');o&&g(o)}},window.toggleSections=g,document.addEventListener("DOMContentLoaded",(function(){document.querySelectorAll('[data-remove-style="true"]').forEach((function(t){t.removeAttribute("style")}))}))})(); -------------------------------------------------------------------------------- /docs/-inikio/fp.serrano.inikio.plugin/-fixed-result-type/type.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | type 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 88 |
89 | 106 |
107 |
108 | 109 |
110 |

type

111 |
112 | 113 |
114 | 124 |
125 |
126 |
127 | 128 | -------------------------------------------------------------------------------- /docs/-inikio/fp.serrano.inikio.plugin/-initial-style-d-s-l/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | InitialStyleDSL 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 88 |
89 | 106 |
107 |
108 | 109 |
110 |

InitialStyleDSL

111 |
annotation class InitialStyleDSL

Instructs Inikio's KSP plug-in to create a Builder based on this hierarchy.

112 |
113 |
114 |
115 |
116 |
117 |
118 | 128 |
129 |
130 |
131 | 132 | -------------------------------------------------------------------------------- /docs/-inikio/fp.serrano.inikio/-program-builder/-state/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | State 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 88 |
89 | 106 |
107 |
108 | 109 |
110 |

State

111 |
sealed interface State<Action, Result>
112 |
113 |
114 |
115 |
116 |
117 |
118 | 128 |
129 |
130 |
131 | 132 | -------------------------------------------------------------------------------- /docs/-inikio/fp.serrano.inikio/-program-builder/-program-builder.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ProgramBuilder 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 88 |
89 | 106 |
107 |
108 | 109 |
110 |

ProgramBuilder

111 |
112 |
constructor(endWith: (Result) -> Action, exceptional: (Throwable) -> Action = { e -> throw e })
113 |
114 | 124 |
125 |
126 |
127 | 128 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /inikio-core/README.md: -------------------------------------------------------------------------------- 1 | # Module Inikio 2 | 3 | > Better initial-style DSLs in Kotlin    4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | * Initial-style DSLs 12 | * Execution 13 | * suspended syntax 14 | * Compiler plug-in 15 | * Examples of DSLs 16 | 17 |

Gradle set-up

18 | 19 | Inikio is available through [Jitpack](https://jitpack.io/). 20 | 21 | ```kotlin 22 | repositories { 23 | maven(url = "https://jitpack.io") 24 | } 25 | dependencies { 26 | implementation("com.github.serras.inikio:inikio-core:$inikioVersion") 27 | } 28 | ``` 29 | 30 |

Initial-style DSLs

31 | 32 | Sometimes you need to model actions or behaviors as part of your model. For example, [rules for filtering data](https://engineering.fb.com/2015/06/26/security/fighting-spam-with-haskell/), [smart contracts](https://github.com/epfl-lara/smart/blob/master/core/src/sphinx/smartcontracts.rst), or [trading card games](https://serranofp.com/zurihac-workshop/). In most cases these actions come from a particular language, called a _domain-specific language_ (DSL for short). Some people separate those actions from the programming language were they are used by means tools like [JetBrains MPS](https://www.jetbrains.com/mps/), but here we're concerned with actually representing those actions in our favorite programming language, Kotlin. 33 | 34 | There are different patterns to embed DSLs in an existing language. _Initial-style_ is one of them, referring to the case in which the actions are represented as _data_. Here's an example showing the basic elements of this pattern: 35 | 36 | ```kotlin 37 | sealed interface Casino 38 | 39 | data class Done(val result: A): Casino 40 | data class FlipCoin(val next: (Outcome) -> Casino): Casino { 41 | enum class Outcome { HEADS, TAILS } 42 | } 43 | ``` 44 | 45 | 1. An interface `Casino` which forms the top of the sealed hierarchy. 46 | 2. A few _primitive actions_; in this case only one, `FlipCoin`. Those actions from the basis of what you can express using your language. 47 | 3. A data class which "ends" the actions; in this case `Done`. 48 | 49 | Each of the primitive actions in (2) also follow a particular shape. Their last argument is a _continuation_, a function which specifies the "next" action to be executed after this one. That function has as argument the type of data which is "consumed" by the action, and always refers back to the top of the sealed hierarchy as result. 50 | 51 | Using `Casino` we can express different games which depend on flipping coins. For example, here's a game in which you flip two coins, and you win whenever both are heads. Kotlin's ability to drop parentheses for a block argument gives us nice syntax to express what to do next after each `FlipCoin`. 52 | 53 | ```kotlin 54 | val doubleCoin = 55 | FlipCoin { o1 -> 56 | FlipCoin { o2 -> 57 | if (o1 == Outcome.HEADS && o2 == Outcome.HEADS) 58 | Done(WIN) 59 | else 60 | Done(LOSE) 61 | } 62 | } 63 | ``` 64 | 65 |

Execution

66 | 67 | Values of that initial-style DSL can be _executed_ in different ways (sometimes we also say they are _interpreted_). In fact, the main advantage of describing actions using this pattern is that writing those interpretations is much simpler than in others. In most cases, you have a big `when` to handle each of the primitive instructions, which calls itself (tail recursively) with the continuation. 68 | 69 | ```kotlin 70 | tailrec fun
Casino.execute(): A = 71 | when(this) { 72 | is Done -> result 73 | is FlipCoin -> next(Random.nextFlipOutcome()).execute() 74 | } 75 | 76 | fun Random.nextFlipOutcome(): FlipCoin.Outcome = 77 | if (nextBoolean()) FlipCoin.Outcome.HEADS else FlipCoin.Outcome.TAILS 78 | ``` 79 | 80 |

suspended syntax

81 | 82 | As useful as it is for writing interpretations, initial-style DSLs suffer from a not-so-nice interface for creating values. The `doubleCoin` above is a prime example: you need to nest everytime you use a primitive action, and remember to wrap the final result with `Done`. Conceptually, though, that problem is a _sequence_ of operations, and we would like that to be reflected in the way we write our code. 83 | 84 | Fortunately, [Kotlin's coroutine system](https://kotlinlang.org/docs/coroutines-overview.html) gives us enough tools to fulfill our wishes. Inikio just wraps them in a nice package, and provides a small compiler plug-in for the boilerplate we need to write. The main idea is to have a `Builder` in which each primitive operation is represented as a `suspend`ed method, and a runner which turns the `Builder` into the actual initial-style DSL. 85 | 86 | ```kotlin 87 | // the builder class 88 | class CasinoBuilder
: ProgramBuilder, A> { 89 | suspend fun flipCoin(): FlipCoin.Outcome 90 | } 91 | // the runner 92 | fun casino(block: CasinoBuilder.() -> A): Casino 93 | ``` 94 | 95 | The `doubleCoin` example can be re-written as follows. 96 | 97 | ```kotlin 98 | val doubleCoin = casino { 99 | val o1 = flipCoin() 100 | val o2 = flipCoin() 101 | if (o1 == Outcome.HEADS && o2 == Outcome.HEADS) WIN 102 | else LOSE 103 | } 104 | ``` 105 | 106 | At the core of this technique we have the `ProgramBuilder` class, which implements a state machine on top of coroutines (thanks to [Simon Vergauwen](https://twitter.com/vergauwen_simon/) and [Raúl Raja](https://twitter.com/raulraja/) for teaching it to me!). The details are not important, but the general idea is that by turning each primitive operation into a `suspend`ed function, the runner can "detect" when it's called, and produce the corresponding data class instance from the initial-style DSL. 107 | 108 | The great news is that you can get all the benefits of this nicer style without having to write anything else than the initial-style DSL. The compiler plug-in generates the `Builder` and the runner for you. 109 | 110 | # Package fp.serrano.inikio 111 | 112 | The core `ProgramBuilder` and corresponding runner. Every DSL generated by the plug-in is based on these two. 113 | 114 | # Package fp.serrano.inikio.plugin 115 | 116 | Information about Inikio's compiler plug-in, that creates `Builder`s automatically for your initial-style DSLs. 117 | 118 | ### Step 1, add the plug-in to your build 119 | 120 | The plug-in is based on [KSP](https://kotlinlang.org/docs/ksp-overview.html). If you are using Gradle you need to add the following to your build file. 121 | 122 | 123 | 124 | ```kotlin 125 | repositories { 126 | mavenCentral() 127 | maven(url = "https://jitpack.io") 128 | } 129 | plugins { 130 | id("com.google.devtools.ksp") version "2.0.20-1.0.24" 131 | } 132 | dependencies { 133 | implementation("com.github.serras.inikio:inikio-core:$inikioVersion") 134 | ksp("com.github.serras.inikio:inikio-ksp:$inikioVersion") 135 | } 136 | ``` 137 | 138 | If IntelliJ is your IDE of choice, we recommend [configuring your build to make it aware of KSP](https://kotlinlang.org/docs/ksp-quickstart.html#make-ide-aware-of-generated-code). 139 | 140 | ### Step 2, annotate your DSLs 141 | 142 | You only need to add the `@InitialStyleDSL` annotation to the top of your hierarchy. Remember that you need to have one "finished" variant, in the example below is `Done`. 143 | 144 | ```kotlin 145 | @InitialStyleDSL 146 | sealed interface Casino 147 | data class Done(val result: A): Casino 148 | data class FlipCoin(val next: (Outcome) -> Casino): Casino { 149 | enum class Outcome { HEADS, TAILS } 150 | } 151 | ``` 152 | 153 | ### Step 3, enjoy your new `Builder` 154 | 155 | From the definition above the plug-in generates a `Builder` class and a runner function. 156 | 157 | - The `Builder` class contains a method for each variant in the DSL, that is, for each basic instruction in your DSL. 158 | 159 | ```kotlin 160 | class CasinoBuilder { 161 | suspend fun flipCoin(): FlipCoin.Outcome 162 | } 163 | ``` 164 | 165 | - The runner function takes a block with the `Builder` as receiver, and converts it into the initial-style DSL. 166 | 167 | ```kotlin 168 | fun casino(block: CasinoBuilder.() -> A): Casino 169 | ``` 170 | 171 | You can use the combination of the runner and the `Builder` methods to create values of your initial-style DSL. For example, the following defines a game when only two heads win. 172 | 173 | ```kotlin 174 | val doubleCoin = casino { 175 | val o1 = flipCoin() 176 | val o2 = flipCoin() 177 | if (o1 == Outcome.HEADS && o2 == Outcome.HEADS) WIN 178 | else LOSE 179 | } 180 | ``` 181 | 182 | Note the much nicer syntax with `suspend` that what you'd get with the data classes themselves. In particular, all the nesting is gone, and there's no need to call the final `Done`. The code above is equivalent to the following. 183 | 184 | ```kotlin 185 | val casino = 186 | FlipCoin { o1 -> 187 | FlipCoin { o2 -> 188 | if (o1 == Outcome.HEADS && o2 == Outcome.HEADS) 189 | Done(WIN) 190 | else 191 | Done(LOSE) 192 | } 193 | } 194 | ``` -------------------------------------------------------------------------------- /docs/-inikio/fp.serrano.inikio.plugin/-fixed-result-type/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FixedResultType 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 88 |
89 | 106 |
107 |
108 | 109 |
110 |

FixedResultType

111 |
annotation class FixedResultType(val type: String)

Indicates that this DSL has a fixed result type, instead of being polymorphic on the result type.

The type must be a fully-qualified name. Unfortunately we cannot use a Class here because of limitations of KSP + Kotlin Multiplatform.

112 |
113 |
114 |
115 |
116 |
117 |

Properties

118 |
119 |
120 |
121 |
122 | 123 | 124 |
Link copied to clipboard
125 |
126 |
127 |
128 | 129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | 148 |
149 |
150 |
151 | 152 | -------------------------------------------------------------------------------- /docs/-inikio/fp.serrano.inikio/-program-builder/perform-unit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | performUnit 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 88 |
89 | 106 |
107 |
108 | 109 |
110 |

performUnit

111 |
112 |
suspend fun performUnit(f: (() -> Action) -> Action)

Record the execution of an action which doesn't consume any value.


suspend fun <A> performUnit(f: (A, () -> Action) -> Action, x: A)

Record the execution of an action which doesn't consume any value, and requires an additional argument of type A.


suspend fun <A, B> performUnit(f: (A, B, () -> Action) -> Action, x: A, y: B)

Record the execution of an action which doesn't consume any value, and requires additional arguments of types A and B.

113 |
114 | 124 |
125 |
126 |
127 | 128 | -------------------------------------------------------------------------------- /docs/-inikio/fp.serrano.inikio/-program-builder/perform.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | perform 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 88 |
89 | 106 |
107 |
108 | 109 |
110 |

perform

111 |
112 |
suspend fun <R> perform(f: ((R) -> Action) -> Action): R

Record the execution of an action which consumes a value of type R.


suspend fun <A, R> perform(f: (A, (R) -> Action) -> Action, x: A): R

Record the execution of an action which consumes a value of type R, and requires an additional argument of type A.


suspend fun <A, B, R> perform(f: (A, B, (R) -> Action) -> Action, x: A, y: B): R

Record the execution of an action which consumes a value of type R, and requires additional arguments of types A and B.

113 |
114 | 124 |
125 |
126 |
127 | 128 | -------------------------------------------------------------------------------- /docs/-inikio/fp.serrano.inikio/program.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | program 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 88 |
89 | 106 |
107 |
108 | 109 |
110 |

program

111 |
112 |
fun <Action, Result> program(endWith: (Result) -> Action, f: suspend ProgramBuilder<Action, Result>.() -> Result): Action

Turns the DSL provided by ProgramBuilder into an actual Action. This version is used for "pure" programs with no exceptional cases.

Parameters

endWith

Reference to the "final" instruction in Action.

Throws

If an exception is raised during the execution of f, it will be propagated.


fun <Action, Result> program(endWith: (Result) -> Action, exceptional: (Throwable) -> Action, f: suspend ProgramBuilder<Action, Result>.() -> Result): Action

Turns the DSL provided by ProgramBuilder into an actual Action.

Parameters

endWith

Reference to the "final" instruction in Action.

exceptional

Reference to the "error" instruction in Action.


fun <Action, Result, T : ProgramBuilder<Action, Result>> program(machine: T, f: suspend T.() -> Result): Action

Turns the DSL provided by a subclass T of ProgramBuilder into an actual Action.

113 |
114 | 124 |
125 |
126 |
127 | 128 | -------------------------------------------------------------------------------- /docs/scripts/platform-content-handler.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | /** When Dokka is viewed via iframe, local storage could be inaccessible (see https://github.com/Kotlin/dokka/issues/3323) 5 | * This is a wrapper around local storage to prevent errors in such cases 6 | * */ 7 | const safeLocalStorage = (() => { 8 | let isLocalStorageAvailable = false; 9 | try { 10 | const testKey = '__testLocalStorageKey__'; 11 | localStorage.setItem(testKey, testKey); 12 | localStorage.removeItem(testKey); 13 | isLocalStorageAvailable = true; 14 | } catch (e) { 15 | console.error('Local storage is not available', e); 16 | } 17 | 18 | return { 19 | getItem: (key) => { 20 | if (!isLocalStorageAvailable) { 21 | return null; 22 | } 23 | return localStorage.getItem(key); 24 | }, 25 | setItem: (key, value) => { 26 | if (!isLocalStorageAvailable) { 27 | return; 28 | } 29 | localStorage.setItem(key, value); 30 | } 31 | }; 32 | })(); 33 | 34 | filteringContext = { 35 | dependencies: {}, 36 | restrictedDependencies: [], 37 | activeFilters: [] 38 | } 39 | let highlightedAnchor; 40 | let topNavbarOffset; 41 | let instances = []; 42 | let sourcesetNotification; 43 | 44 | const samplesDarkThemeName = 'darcula' 45 | const samplesLightThemeName = 'idea' 46 | 47 | window.addEventListener('load', () => { 48 | document.querySelectorAll("div[data-platform-hinted]") 49 | .forEach(elem => elem.addEventListener('click', (event) => togglePlatformDependent(event, elem))) 50 | const filterSection = document.getElementById('filter-section') 51 | if (filterSection) { 52 | filterSection.addEventListener('click', (event) => filterButtonHandler(event)) 53 | initializeFiltering() 54 | } 55 | if (typeof initTabs === 'function') { 56 | initTabs() // initTabs comes from ui-kit/tabs 57 | } 58 | handleAnchor() 59 | topNavbarOffset = document.getElementById('navigation-wrapper') 60 | darkModeSwitch() 61 | }) 62 | 63 | const darkModeSwitch = () => { 64 | const localStorageKey = "dokka-dark-mode" 65 | const storage = safeLocalStorage.getItem(localStorageKey) 66 | const osDarkSchemePreferred = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches 67 | const darkModeEnabled = storage ? JSON.parse(storage) : osDarkSchemePreferred 68 | const element = document.getElementById("theme-toggle-button") 69 | initPlayground(darkModeEnabled ? samplesDarkThemeName : samplesLightThemeName) 70 | 71 | element.addEventListener('click', () => { 72 | const enabledClasses = document.getElementsByTagName("html")[0].classList 73 | enabledClasses.toggle("theme-dark") 74 | 75 | //if previously we had saved dark theme then we set it to light as this is what we save in local storage 76 | const darkModeEnabled = enabledClasses.contains("theme-dark") 77 | if (darkModeEnabled) { 78 | initPlayground(samplesDarkThemeName) 79 | } else { 80 | initPlayground(samplesLightThemeName) 81 | } 82 | safeLocalStorage.setItem(localStorageKey, JSON.stringify(darkModeEnabled)) 83 | }) 84 | } 85 | 86 | const initPlayground = (theme) => { 87 | if (!samplesAreEnabled()) return 88 | instances.forEach(instance => instance.destroy()) 89 | instances = [] 90 | 91 | // Manually tag code fragments as not processed by playground since we also manually destroy all of its instances 92 | document.querySelectorAll('code.runnablesample').forEach(node => { 93 | node.removeAttribute("data-kotlin-playground-initialized"); 94 | }) 95 | 96 | KotlinPlayground('code.runnablesample', { 97 | getInstance: playgroundInstance => { 98 | instances.push(playgroundInstance) 99 | }, 100 | theme: theme 101 | }); 102 | } 103 | 104 | // We check if type is accessible from the current scope to determine if samples script is present 105 | // As an alternative we could extract this samples-specific script to new js file but then we would handle dark mode in 2 separate files which is not ideal 106 | const samplesAreEnabled = () => { 107 | try { 108 | if (typeof KotlinPlayground === 'undefined') { 109 | // KotlinPlayground is exported universally as a global variable or as a module 110 | // Due to possible interaction with other js scripts KotlinPlayground may not be accessible directly from `window`, so we need an additional check 111 | KotlinPlayground = exports.KotlinPlayground; 112 | } 113 | return typeof KotlinPlayground === 'function'; 114 | } catch (e) { 115 | return false 116 | } 117 | } 118 | 119 | // Hash change is needed in order to allow for linking inside the same page with anchors 120 | // If this is not present user is forced to refresh the site in order to use an anchor 121 | window.onhashchange = handleAnchor 122 | 123 | function scrollToElementInContent(element) { 124 | const scrollToElement = () => document.getElementById('main').scrollTo({ 125 | top: element.offsetTop - topNavbarOffset.offsetHeight, 126 | behavior: "smooth" 127 | }) 128 | 129 | const waitAndScroll = () => { 130 | setTimeout(() => { 131 | if (topNavbarOffset) { 132 | scrollToElement() 133 | } else { 134 | waitForScroll() 135 | } 136 | }, 50) 137 | } 138 | 139 | if (topNavbarOffset) { 140 | scrollToElement() 141 | } else { 142 | waitAndScroll() 143 | } 144 | } 145 | 146 | 147 | function handleAnchor() { 148 | if (highlightedAnchor) { 149 | highlightedAnchor.classList.remove('anchor-highlight') 150 | highlightedAnchor = null; 151 | } 152 | 153 | let searchForContentTarget = function (element) { 154 | if (element && element.hasAttribute) { 155 | if (element.hasAttribute("data-togglable")) return element.getAttribute("data-togglable"); 156 | else return searchForContentTarget(element.parentNode) 157 | } else return null 158 | } 159 | 160 | let findAnyTab = function (target) { 161 | let result = null 162 | document.querySelectorAll('div[tabs-section] > button[data-togglable]') 163 | .forEach(node => { 164 | if(node.getAttribute("data-togglable").split(",").includes(target)) { 165 | result = node 166 | } 167 | }) 168 | return result 169 | } 170 | 171 | let anchor = window.location.hash 172 | if (anchor !== "") { 173 | anchor = anchor.substring(1) 174 | let element = document.querySelector('a[data-name="' + anchor + '"]') 175 | 176 | if (element) { 177 | const content = element.nextElementSibling 178 | const contentStyle = window.getComputedStyle(content) 179 | if(contentStyle.display === 'none') { 180 | let tab = findAnyTab(searchForContentTarget(content)) 181 | if (tab) { 182 | toggleSections(tab) // toggleSections comes from ui-kit/tabs 183 | } 184 | } 185 | 186 | if (content) { 187 | content.classList.add('anchor-highlight') 188 | highlightedAnchor = content 189 | } 190 | 191 | scrollToElementInContent(element) 192 | } 193 | } 194 | } 195 | 196 | function filterButtonHandler(event) { 197 | if (event.target.tagName === "BUTTON" && event.target.hasAttribute("data-filter")) { 198 | let sourceset = event.target.getAttribute("data-filter") 199 | if (filteringContext.activeFilters.indexOf(sourceset) !== -1) { 200 | filterSourceset(sourceset) 201 | } else { 202 | unfilterSourceset(sourceset) 203 | } 204 | } 205 | } 206 | 207 | function initializeFiltering() { 208 | filteringContext.dependencies = JSON.parse(sourceset_dependencies) 209 | document.querySelectorAll("#filter-section > button") 210 | .forEach(p => filteringContext.restrictedDependencies.push(p.getAttribute("data-filter"))) 211 | Object.keys(filteringContext.dependencies).forEach(p => { 212 | filteringContext.dependencies[p] = filteringContext.dependencies[p] 213 | .filter(q => -1 !== filteringContext.restrictedDependencies.indexOf(q)) 214 | }) 215 | let cached = safeLocalStorage.getItem('inactive-filters') 216 | if (cached) { 217 | let parsed = JSON.parse(cached) 218 | filteringContext.activeFilters = filteringContext.restrictedDependencies 219 | .filter(q => parsed.indexOf(q) === -1) 220 | } else { 221 | filteringContext.activeFilters = filteringContext.restrictedDependencies 222 | } 223 | refreshFiltering() 224 | } 225 | 226 | function filterSourceset(sourceset) { 227 | filteringContext.activeFilters = filteringContext.activeFilters.filter(p => p !== sourceset) 228 | refreshFiltering() 229 | addSourcesetFilterToCache(sourceset) 230 | } 231 | 232 | function unfilterSourceset(sourceset) { 233 | if (filteringContext.activeFilters.length === 0) { 234 | filteringContext.activeFilters = filteringContext.dependencies[sourceset].concat([sourceset]) 235 | refreshFiltering() 236 | filteringContext.dependencies[sourceset].concat([sourceset]).forEach(p => removeSourcesetFilterFromCache(p)) 237 | } else { 238 | filteringContext.activeFilters.push(sourceset) 239 | refreshFiltering() 240 | removeSourcesetFilterFromCache(sourceset) 241 | } 242 | 243 | } 244 | 245 | function addSourcesetFilterToCache(sourceset) { 246 | let cached = safeLocalStorage.getItem('inactive-filters') 247 | if (cached) { 248 | let parsed = JSON.parse(cached) 249 | safeLocalStorage.setItem('inactive-filters', JSON.stringify(parsed.concat([sourceset]))) 250 | } else { 251 | safeLocalStorage.setItem('inactive-filters', JSON.stringify([sourceset])) 252 | } 253 | } 254 | 255 | function removeSourcesetFilterFromCache(sourceset) { 256 | let cached = safeLocalStorage.getItem('inactive-filters') 257 | if (cached) { 258 | let parsed = JSON.parse(cached) 259 | safeLocalStorage.setItem('inactive-filters', JSON.stringify(parsed.filter(p => p !== sourceset))) 260 | } 261 | } 262 | 263 | function refreshSourcesetsCache() { 264 | safeLocalStorage.setItem('inactive-filters', JSON.stringify(filteringContext.restrictedDependencies.filter(p => -1 === filteringContext.activeFilters.indexOf(p)))) 265 | } 266 | 267 | 268 | function togglePlatformDependent(e, container) { 269 | let target = e.target 270 | if (target.tagName !== 'BUTTON') return; 271 | let index = target.getAttribute('data-toggle') 272 | 273 | for (let child of container.children) { 274 | if (child.hasAttribute('data-toggle-list')) { 275 | for (let bm of child.children) { 276 | if (bm === target) { 277 | bm.setAttribute('data-active', "") 278 | } else if (bm !== target) { 279 | bm.removeAttribute('data-active') 280 | } 281 | } 282 | } else if (child.getAttribute('data-togglable') === index) { 283 | child.setAttribute('data-active', "") 284 | } else { 285 | child.removeAttribute('data-active') 286 | } 287 | } 288 | } 289 | 290 | function refreshFiltering() { 291 | let sourcesetList = filteringContext.activeFilters 292 | document.querySelectorAll("[data-filterable-set]") 293 | .forEach( 294 | elem => { 295 | let platformList = elem.getAttribute("data-filterable-set").split(',').filter(v => -1 !== sourcesetList.indexOf(v)) 296 | elem.setAttribute("data-filterable-current", platformList.join(',')) 297 | } 298 | ) 299 | refreshFilterButtons() 300 | refreshPlatformTabs() 301 | refreshNoContentNotification() 302 | refreshPlaygroundSamples() 303 | } 304 | 305 | function refreshPlaygroundSamples() { 306 | document.querySelectorAll('code.runnablesample').forEach(node => { 307 | const playground = node.KotlinPlayground; 308 | /* Some samples may be hidden by filter, they have 0px height for visible code area 309 | * after rendering. Call this method for re-calculate code area height */ 310 | playground && playground.view.codemirror.refresh(); 311 | }); 312 | } 313 | 314 | function refreshNoContentNotification() { 315 | const element = document.getElementsByClassName("main-content")[0] 316 | const filteredMessage = document.querySelector(".filtered-message") 317 | 318 | if(filteringContext.activeFilters.length === 0){ 319 | element.style.display = "none"; 320 | 321 | if (!filteredMessage) { 322 | const appended = document.createElement("div") 323 | appended.className = "filtered-message" 324 | appended.innerText = "All documentation is filtered, please adjust your source set filters in top-right corner of the screen" 325 | sourcesetNotification = appended 326 | element.parentNode.prepend(appended) 327 | } 328 | } else { 329 | if(sourcesetNotification) sourcesetNotification.remove() 330 | element.style.display = "block" 331 | } 332 | } 333 | 334 | function refreshPlatformTabs() { 335 | document.querySelectorAll(".platform-hinted > .platform-bookmarks-row").forEach( 336 | p => { 337 | let active = false; 338 | let firstAvailable = null 339 | p.childNodes.forEach( 340 | element => { 341 | if (element.getAttribute("data-filterable-current") !== '') { 342 | if (firstAvailable === null) { 343 | firstAvailable = element 344 | } 345 | if (element.hasAttribute("data-active")) { 346 | active = true; 347 | } 348 | } 349 | } 350 | ) 351 | if (active === false && firstAvailable) { 352 | firstAvailable.click() 353 | } 354 | } 355 | ) 356 | } 357 | 358 | function refreshFilterButtons() { 359 | document.querySelectorAll("#filter-section > button") 360 | .forEach(f => { 361 | if (filteringContext.activeFilters.indexOf(f.getAttribute("data-filter")) !== -1) { 362 | f.setAttribute("data-active", "") 363 | } else { 364 | f.removeAttribute("data-active") 365 | } 366 | }) 367 | document.querySelectorAll("#filter-section .checkbox--input") 368 | .forEach(f => { 369 | f.checked = filteringContext.activeFilters.indexOf(f.getAttribute("data-filter")) !== -1; 370 | }) 371 | } 372 | -------------------------------------------------------------------------------- /docs/-inikio/fp.serrano.inikio.plugin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | fp.serrano.inikio.plugin 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 88 |
89 | 106 |
107 |
108 | 109 |
110 |

Package-level declarations

111 |

Information about Inikio's compiler plug-in, that creates Builders automatically for your initial-style DSLs.

Step 1, add the plug-in to your build

The plug-in is based on KSP. If you are using Gradle you need to add the following to your build file.

repositories {
mavenCentral()
maven(url = "https://jitpack.io")
}
plugins {
id("com.google.devtools.ksp") version "2.0.20-1.0.24"
}
dependencies {
implementation("com.github.serras.inikio:inikio-core:$inikioVersion")
ksp("com.github.serras.inikio:inikio-ksp:$inikioVersion")
}

If IntelliJ is your IDE of choice, we recommend configuring your build to make it aware of KSP.

Step 2, annotate your DSLs

You only need to add the @InitialStyleDSL annotation to the top of your hierarchy. Remember that you need to have one "finished" variant, in the example below is Done.

@InitialStyleDSL
sealed interface Casino<out A>
data class Done<out A>(val result: A): Casino<A>
data class FlipCoin<out A>(val next: (Outcome) -> Casino<A>): Casino<A> {
enum class Outcome { HEADS, TAILS }
}

Step 3, enjoy your new Builder

From the definition above the plug-in generates a Builder class and a runner function.

  • The Builder class contains a method for each variant in the DSL, that is, for each basic instruction in your DSL.

      class CasinoBuilder<A> {
    suspend fun flipCoin(): FlipCoin.Outcome
    }
  • The runner function takes a block with the Builder as receiver, and converts it into the initial-style DSL.

      fun <A> casino(block: CasinoBuilder<A>.() -> A): Casino<A>

You can use the combination of the runner and the Builder methods to create values of your initial-style DSL. For example, the following defines a game when only two heads win.

val doubleCoin = casino {
val o1 = flipCoin()
val o2 = flipCoin()
if (o1 == Outcome.HEADS && o2 == Outcome.HEADS) WIN
else LOSE
}

Note the much nicer syntax with suspend that what you'd get with the data classes themselves. In particular, all the nesting is gone, and there's no need to call the final Done. The code above is equivalent to the following.

val casino =
FlipCoin { o1 ->
FlipCoin { o2 ->
if (o1 == Outcome.HEADS && o2 == Outcome.HEADS)
Done(WIN)
else
Done(LOSE)
}
}
112 |
113 |
114 |
115 |
116 |
117 |

Types

118 |
119 |
120 |
121 |
122 | 123 | 124 |
Link copied to clipboard
125 |
126 |
127 |
128 |
annotation class FixedResultType(val type: String)

Indicates that this DSL has a fixed result type, instead of being polymorphic on the result type.

129 |
130 |
131 |
132 |
133 | 134 |
135 |
136 |
137 | 138 | 139 |
Link copied to clipboard
140 |
141 |
142 |
143 |
annotation class InitialStyleDSL

Instructs Inikio's KSP plug-in to create a Builder based on this hierarchy.

144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | 163 |
164 |
165 |
166 | 167 | --------------------------------------------------------------------------------