-
Notifications
You must be signed in to change notification settings - Fork 47
feat!: introduce a Kotlin DSL to configure simulations #4978
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 54 commits
8488842
725e116
cfa4c7f
a09052c
e20e2b2
dc58811
ff2019d
658e527
3fa5ab5
ce0a83a
73f8ade
4a3fe19
4fd2023
99c66b3
2c2e61b
0cc43a9
728c00d
45af007
3377814
1a00e1b
3632937
58075d0
05388de
b0c0279
10197cf
eab8346
0ac359f
93e7191
4feae4c
0af86c8
4afa89a
d794bf8
7de533b
b604010
8439b64
e4be08e
19cf438
f9043ca
66a245c
e15e314
209b760
ef11fad
2207683
417dc6a
65f756f
fc36e72
985a729
29ef41e
63b8124
20da4d7
fac688c
c46b844
bc0ca1c
1f809f0
bd82f9f
9830c28
420e2a2
3cbeef6
4abc0a8
5a8a67c
f3ff0ec
2441330
4e5ec58
45c929b
9b5f96a
3661269
7e62d79
751172a
3351ab4
7ed5e23
49a2306
674f211
2215f61
b6a2d28
caff28f
7ec3895
a89e9ff
4052d6f
4128c8e
bbedd33
f4f2b60
9ac4062
6c1fd15
98d5ed1
84a35af
5e8dded
8df2401
8a2f3bf
f2a4f3b
93936d3
c07c910
4f5d195
1c6f04b
f24e3dd
cdd5144
1ed4f35
fae4a11
6edb8ac
f312b55
577b5fc
7682742
474d9f0
5670a7f
a1e5383
0c32587
a991691
70629ad
fbe7bda
4ca02f4
d0f788c
127731c
5a0c4f6
a2caf85
1d5977c
ca3c1aa
414fec4
5ab4dba
73be28b
b2782c2
2c69253
9d7e889
609179d
e4f8700
b494dec
d5d3a6c
136808a
0f3745b
68cd5c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| /* | ||
| * Copyright (C) 2010-2025, Danilo Pianini and contributors | ||
| * listed, for each module, in the respective subproject's build.gradle.kts file. | ||
| * | ||
| * This file is part of Alchemist, and is distributed under the terms of the | ||
| * GNU General Public License, with a linking exception, | ||
| * as described in the file LICENSE in the Alchemist distribution's top directory. | ||
| */ | ||
|
|
||
| package it.unibo.alchemist.boundary.dsl | ||
|
|
||
| /** | ||
| * Annotation used to mark classes that should have DSL builder functions generated. | ||
| * When applied to a class, the DSL processor will generate a builder function | ||
| * that can be used in Alchemist DSL scripts to create instances of the annotated class. | ||
| * | ||
| * @param functionName Custom name for the generated DSL function. If empty, defaults to | ||
| * the lowercase version of the class name with the first character lowercased. | ||
| * @param scope Manual override for the context type. Valid values (case-insensitive): | ||
| * - "SIMULATION" or "SIMULATION_CONTEXT" → SimulationContext | ||
| * - "EXPORTER" or "EXPORTER_CONTEXT" → ExporterContext | ||
| * - "GLOBAL_PROGRAMS" or "GLOBAL_PROGRAMS_CONTEXT" → GlobalProgramsContext | ||
| * - "OUTPUT_MONITORS" or "OUTPUT_MONITORS_CONTEXT" → OutputMonitorsContext | ||
| * - "TERMINATORS" or "TERMINATORS_CONTEXT" → TerminatorsContext | ||
| * - "DEPLOYMENT" or "DEPLOYMENTS_CONTEXT" → DeploymentsContext | ||
| * - "DEPLOYMENT_CONTEXT" → DeploymentContext (singular) | ||
| * - "PROGRAM" or "PROGRAM_CONTEXT" → ProgramContext | ||
| * - "PROPERTY" or "PROPERTY_CONTEXT" → PropertyContext | ||
| * If empty, the context type is automatically determined based on the constructor parameters. | ||
| * This allows manual context passing to override the default behavior. | ||
| * @param injectEnvironment Whether to inject an Environment parameter into the generated builder function. | ||
| * @param injectGenerator Whether to inject a Generator parameter into the generated builder function. | ||
| * @param injectNode Whether to inject a Node parameter into the generated builder function. | ||
| * @param injectReaction Whether to inject a Reaction parameter into the generated builder function. | ||
| */ | ||
| @Target(AnnotationTarget.CLASS) | ||
| @Retention(AnnotationRetention.SOURCE) | ||
| annotation class BuildDsl( | ||
|
||
| val functionName: String = "", | ||
| val scope: String = "", | ||
| val injectEnvironment: Boolean = true, | ||
| val injectGenerator: Boolean = true, | ||
| val injectNode: Boolean = true, | ||
| val injectReaction: Boolean = true, | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import Libs.alchemist | ||
|
|
||
| plugins { | ||
| id("kotlin-multiplatform-convention") | ||
| } | ||
| kotlin { | ||
DanySK marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| jvm() | ||
| sourceSets { | ||
| val jvmMain by getting { | ||
| dependencies { | ||
| api(alchemist("api")) | ||
| implementation(libs.ksp.api) | ||
| } | ||
| kotlin.srcDir("src/main/kotlin") | ||
| resources.srcDir("src/main/resources") | ||
DanySK marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| val jvmTest by getting { | ||
| dependencies { | ||
| implementation(libs.bundles.testing.compile) | ||
| runtimeOnly(libs.bundles.testing.runtimeOnly) | ||
| } | ||
| kotlin.srcDir("src/test/kotlin") | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| package it.unibo.alchemist.boundary.dsl.processor | ||
|
|
||
| import com.google.devtools.ksp.symbol.KSTypeArgument | ||
| import com.google.devtools.ksp.symbol.KSTypeReference | ||
| import com.google.devtools.ksp.symbol.Variance | ||
|
|
||
| /** | ||
| * Processes type bounds for type parameters, cleaning up internal Kotlin type representations. | ||
| */ | ||
| object BoundProcessor { | ||
| /** | ||
| * Processes a type bound reference, cleaning up internal Kotlin type representations and variance annotations. | ||
| * | ||
| * @param bound The type reference to process | ||
| * @param classTypeParamNames List of class type parameter names to replace in the bound | ||
| * @return A cleaned string representation of the bound with fully qualified names | ||
| */ | ||
| fun processBound(bound: KSTypeReference, classTypeParamNames: List<String> = emptyList()): String { | ||
| val resolved = bound.resolve() | ||
| val decl = resolved.declaration | ||
| val qualifiedName = decl.qualifiedName?.asString() | ||
|
|
||
| if (qualifiedName != null) { | ||
| val arguments = resolved.arguments | ||
| val typeString = if (arguments.isNotEmpty()) { | ||
| val typeArgs = arguments.joinToString(", ") { arg -> | ||
| formatTypeArgument(arg, classTypeParamNames) | ||
| } | ||
| "$qualifiedName<$typeArgs>" | ||
| } else { | ||
| qualifiedName | ||
| } | ||
|
|
||
DanySK marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| val nullableSuffix = if (resolved.isMarkedNullable) "?" else "" | ||
| val result = "$typeString$nullableSuffix" | ||
| return replaceClassTypeParamReferences(result, classTypeParamNames) | ||
| } | ||
|
|
||
| val result = TypeExtractor.extractTypeString(bound, emptyList()) | ||
| return replaceClassTypeParamReferences(result, classTypeParamNames) | ||
| } | ||
|
|
||
| private fun formatTypeArgument(arg: KSTypeArgument, classTypeParamNames: List<String>): String = when { | ||
| arg.type == null -> "*" | ||
| arg.variance == Variance.STAR -> "*" | ||
| arg.variance == Variance.CONTRAVARIANT -> { | ||
| arg.type?.let { | ||
| val typeStr = TypeExtractor.extractTypeString(it, emptyList()) | ||
| val replaced = replaceClassTypeParamReferences(typeStr, classTypeParamNames) | ||
| "in $replaced" | ||
| } ?: "*" | ||
| } | ||
| arg.variance == Variance.COVARIANT -> { | ||
| arg.type?.let { | ||
| val typeStr = TypeExtractor.extractTypeString(it, emptyList()) | ||
| val replaced = replaceClassTypeParamReferences(typeStr, classTypeParamNames) | ||
| "out $replaced" | ||
| } ?: "*" | ||
| } | ||
| else -> { | ||
| arg.type?.let { | ||
| val typeStr = TypeExtractor.extractTypeString(it, emptyList()) | ||
| replaceClassTypeParamReferences(typeStr, classTypeParamNames) | ||
| } ?: "*" | ||
| } | ||
| } | ||
|
|
||
| private fun replaceClassTypeParamReferences(boundStr: String, classTypeParamNames: List<String>): String { | ||
| if (classTypeParamNames.isEmpty()) { | ||
| return boundStr | ||
| } | ||
| var result = boundStr | ||
| classTypeParamNames.forEach { paramName -> | ||
| val pattern = Regex("""\b[\w.]+\.$paramName\b""") | ||
| result = pattern.replace(result) { matchResult -> | ||
| val matched = matchResult.value | ||
| val prefix = matched.substringBefore(".$paramName") | ||
| if (prefix.contains(".")) { | ||
| paramName | ||
| } else { | ||
| matched | ||
| } | ||
| } | ||
| } | ||
| return result | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package it.unibo.alchemist.boundary.dsl.processor | ||
|
|
||
| import com.google.devtools.ksp.symbol.KSClassDeclaration | ||
| import com.google.devtools.ksp.symbol.KSFunctionDeclaration | ||
| import com.google.devtools.ksp.symbol.Modifier | ||
|
|
||
| /** | ||
| * Finds a suitable constructor for a class declaration. | ||
| */ | ||
| object ConstructorFinder { | ||
| /** | ||
| * Finds a public constructor for the given class declaration. | ||
| * Prefers the primary constructor, otherwise returns the constructor with the most parameters. | ||
| * | ||
| * @param classDecl The class declaration to find a constructor for | ||
| * @return The found constructor, or null if no suitable constructor exists | ||
| */ | ||
| fun findConstructor(classDecl: KSClassDeclaration): KSFunctionDeclaration? { | ||
| val primaryConstructor = classDecl.primaryConstructor | ||
| if (primaryConstructor != null && isPublicConstructor(primaryConstructor)) { | ||
| return primaryConstructor | ||
| } | ||
|
|
||
| val constructors = classDecl.getAllFunctions() | ||
| .filter { function -> | ||
| val simpleName = function.simpleName.asString() | ||
| (simpleName == "<init>" || simpleName == classDecl.simpleName.asString()) && | ||
| isPublicConstructor(function) | ||
| } | ||
| .sortedByDescending { it.parameters.size } | ||
|
|
||
| return constructors.firstOrNull() | ||
| } | ||
|
|
||
| private fun isPublicConstructor(function: KSFunctionDeclaration): Boolean { | ||
| val modifiers = function.modifiers | ||
| return !modifiers.contains(Modifier.PRIVATE) && | ||
| !modifiers.contains(Modifier.PROTECTED) && | ||
| !modifiers.contains(Modifier.INTERNAL) | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.