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
124
|
package moe.nea.lisp.bind
import moe.nea.lisp.*
import java.lang.invoke.MethodHandles
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.lang.reflect.Parameter
class AutoBinder {
private fun mapLispData(parameter: Parameter): ((Iterator<LispData>, ErrorReporter) -> Any)? {
if (LispData::class.java.isAssignableFrom(parameter.type)) return { a, b -> parameter.type.cast(a.next()) }
return null
}
private fun mapErrorReporter(parameter: Parameter): ((Iterator<LispData>, ErrorReporter) -> Any)? {
if (ErrorReporter::class.java.isAssignableFrom(parameter.type)) return { a, b -> b }
return null
}
private fun mapString(parameter: Parameter): ((Iterator<LispData>, ErrorReporter) -> Any?)? {
if (String::class.java == parameter.type) return { a, b ->
when (val x = a.next()) {
is LispData.LispString -> x.string
is LispData.Atom -> x.label
else -> null.also { b.reportError("Could not coerce $x to string") }
}
}
return null
}
private fun mapBoolean(parameter: Parameter): ((Iterator<LispData>, ErrorReporter) -> Any?)? {
if (Boolean::class.java.isAssignableFrom(parameter.type)) return { a, b ->
val x = a.next()
val y = CoreBindings.isTruthy(x)
if (y == null) {
b.reportError("Could not coerce $x to a boolean")
}
y
}
return null
}
private fun mapNumber(parameter: Parameter): ((Iterator<LispData>, ErrorReporter) -> Any?)? {
if (Double::class.java.isAssignableFrom(parameter.type)) return { a, b ->
when (val x = a.next()) {
is LispData.LispNumber -> x.value
else -> null.also { b.reportError("Could not coerce $x to number") }
}
}
if (Float::class.java.isAssignableFrom(parameter.type)) return { a, b ->
when (val x = a.next()) {
is LispData.LispNumber -> x.value.toFloat()
else -> null.also { b.reportError("Could not coerce $x to number") }
}
}
if (Int::class.java.isAssignableFrom(parameter.type)) return { a, b ->
when (val x = a.next()) {
is LispData.LispNumber -> x.value.toInt()
else -> null.also { b.reportError("Could not coerce $x to number") }
}
}
if (Long::class.java.isAssignableFrom(parameter.type)) return { a, b ->
when (val x = a.next()) {
is LispData.LispNumber -> x.value.toLong()
else -> null.also { b.reportError("Could not coerce $x to number") }
}
}
return null
}
val objectMappers = mutableListOf<((Parameter) -> (((Iterator<LispData>, ErrorReporter) -> Any?)?))>(
::mapLispData,
::mapErrorReporter,
::mapNumber,
::mapString,
::mapBoolean,
)
fun generateInstanceBindings(obj: Any): Map<String, LispData> {
val bindings = mutableMapOf<String, LispData>()
for (method in obj.javaClass.methods) {
val annotation = method.getAnnotation(LispBinding::class.java)
if (annotation == null) continue
require(LispParser.isValidIdentifier(annotation.name))
bindings[annotation.name] = wrapMethod(obj, annotation.name, method)
}
// TODO: field bindings
return bindings
}
private val lookup = MethodHandles.publicLookup()
fun wrapMethod(obj: Any, name: String, method: Method): LispData.LispExecutable {
var mh = lookup.unreflect(method)
if (method.modifiers and Modifier.STATIC == 0) {
mh = mh.bindTo(obj)
}
val objectMappers = method.parameters.map { param ->
objectMappers.firstNotNullOfOrNull { it.invoke(param) }
?: error("Could not find object mapper for parameter $param")
}
return LispData.externalCall(name) { args, fReportError ->
try {
val iterator = args.iterator()
val e = object : ErrorReporter {
override fun reportError(string: String): LispData {
return fReportError(string)
}
}
val p = objectMappers.map { it.invoke(iterator, e) ?: return@externalCall LispData.LispNil }
if (iterator.hasNext()) return@externalCall fReportError("Too many arguments")
mh.invokeWithArguments(p) as LispData
} catch (e: Exception) {
fReportError("$name threw an exception: $e")
}
}
}
fun bindTo(obj: Any, frame: StackFrame) {
frame.variables.putAll(generateInstanceBindings(obj))
}
}
|