-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathKtsPreludeBuilder.kt
More file actions
123 lines (107 loc) · 5.32 KB
/
KtsPreludeBuilder.kt
File metadata and controls
123 lines (107 loc) · 5.32 KB
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package com.instancify.scriptify.kts.script.bridge
import com.instancify.scriptify.api.script.function.definition.ScriptFunctionExecutor
import com.instancify.scriptify.kts.script.KtsScript
import org.jetbrains.annotations.Nullable
/**
* This class collects the “head” of the script,
* which contains all constants and functions registered in the script.
*/
object KtsPreludeBuilder {
/**
* Builds the script "head" from KtsScript and the script itself.
*/
fun build(script: KtsScript, scriptCode: String): String {
val sb = StringBuilder()
// split the script into lines (package, imports, code)
val lines = scriptCode.lines()
val packageLine = lines.firstOrNull { it.trim().startsWith("package ") }
val imports = lines.filter { it.trim().startsWith("import ") }
val body = lines.filterNot { it.trim().startsWith("import ") || it.trim().startsWith("package ") }
packageLine?.let {
sb.append(it).append("\n\n")
}
imports.forEach { sb.append(it).append("\n") }
if (imports.isNotEmpty()) {
sb.append("\n")
}
// adding constants
for (constant in script.constantManager.constants.values) {
val name = constant.name
val type = constant.value?.let {
convertKotlinType(it.javaClass, true)
} ?: "Any?"
sb.append("val $name: $type = __bridge__.findConstant(\"$name\") as $type\n")
}
sb.append("\n")
// adding functions
for (definition in script.functionManager.functions.values) {
val name = definition.function.name
for (executor in definition.executors) {
sb.append(functionSignature(name, executor)).append(" {\n")
sb.append(" " + functionBody(executor, name)).append("\n")
sb.append("}")
sb.append("\n")
}
}
// adding other script code
sb.append("\n\n")
body.forEach { sb.append(it).append("\n") }
return sb.toString()
}
private fun functionSignature(name: String, executor: ScriptFunctionExecutor): String {
val args = executor.arguments
val params = args.mapIndexed { i, arg ->
val lastIndex = args.size - 1
val paramName = if (arg.name.isNullOrBlank()) "arg$i" else arg.name
if (i == lastIndex && executor.method.isVarArgs) {
"vararg $paramName: Any?"
} else {
val paramType = convertKotlinType(arg.type, arg.isRequired)
val default = if (arg.isRequired) "" else " = null"
"$paramName: $paramType$default"
}
}
val required = !executor.method.annotatedReturnType.isAnnotationPresent(Nullable::class.java)
return "fun $name(${params.joinToString(", ")}): ${convertKotlinType(executor.method.returnType, required)}"
}
private fun functionBody(ex: ScriptFunctionExecutor, name: String): String {
val lastIndex = ex.arguments.lastIndex
val callArgs = ex.arguments.mapIndexed { i, a ->
val paramName = if (a.name.isNullOrBlank()) "arg$i" else a.name
if (i == lastIndex && ex.method.isVarArgs) "*$paramName" else paramName
}.joinToString(", ")
val returnType = convertKotlinType(ex.method.returnType, true)
return when {
returnType == "Unit" -> "__bridge__.callFunction(\"$name\", kotlin.arrayOf($callArgs))"
else -> "return __bridge__.callFunction(\"$name\", kotlin.arrayOf($callArgs)) as $returnType"
}
}
private fun convertKotlinType(type: Class<*>, required: Boolean): String {
val t = when {
type.isArray -> when (val comp = type.componentType) {
Object::class.java -> "Array<Any?>"
String::class.java -> "Array<String>"
Int::class.java, Int::class.javaPrimitiveType -> "IntArray"
Long::class.java, Long::class.javaPrimitiveType -> "LongArray"
Double::class.java, Double::class.javaPrimitiveType -> "DoubleArray"
Float::class.java, Float::class.javaPrimitiveType -> "FloatArray"
Boolean::class.java, Boolean::class.javaPrimitiveType -> "BooleanArray"
else -> "Array<${comp.simpleName}>"
}
type == Void::class.java || type == Void::class.javaPrimitiveType -> "Unit"
type == Integer::class.java || type == Int::class.javaPrimitiveType -> "Int"
type == Long::class.java || type == Long::class.javaPrimitiveType -> "Long"
type == Double::class.java || type == Double::class.javaPrimitiveType -> "Double"
type == Float::class.java || type == Float::class.javaPrimitiveType -> "Float"
type == Boolean::class.java || type == Boolean::class.javaPrimitiveType -> "Boolean"
type == String::class.java -> "String"
Number::class.java.isAssignableFrom(type) -> "Number"
List::class.java.isAssignableFrom(type) -> "List<Any?>"
Set::class.java.isAssignableFrom(type) -> "Set<Any?>"
Map::class.java.isAssignableFrom(type) -> "Map<Any?, Any?>"
type == Object::class.java -> "Any"
else -> type.name
}
return if (required) t else "$t?"
}
}