summaryrefslogtreecommitdiff
path: root/src/bind
diff options
context:
space:
mode:
authornea <nea@nea.moe>2023-08-21 16:15:59 +0200
committernea <nea@nea.moe>2023-08-21 16:15:59 +0200
commit4bbfede4060d38083aafd8a6c7ab2847e0e55ebf (patch)
treef0ab50180d2fa1c872ba8c20a4b055cd87bed3c9 /src/bind
parentde694f30b5e2f6fd62a00e5a622abe0a2411afb9 (diff)
downloadnealisp-4bbfede4060d38083aafd8a6c7ab2847e0e55ebf.tar.gz
nealisp-4bbfede4060d38083aafd8a6c7ab2847e0e55ebf.tar.bz2
nealisp-4bbfede4060d38083aafd8a6c7ab2847e0e55ebf.zip
method binding
Diffstat (limited to 'src/bind')
-rw-r--r--src/bind/AutoBinder.kt124
-rw-r--r--src/bind/LispBinding.kt7
2 files changed, 131 insertions, 0 deletions
diff --git a/src/bind/AutoBinder.kt b/src/bind/AutoBinder.kt
new file mode 100644
index 0000000..83d5d86
--- /dev/null
+++ b/src/bind/AutoBinder.kt
@@ -0,0 +1,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))
+ }
+} \ No newline at end of file
diff --git a/src/bind/LispBinding.kt b/src/bind/LispBinding.kt
new file mode 100644
index 0000000..7af0e67
--- /dev/null
+++ b/src/bind/LispBinding.kt
@@ -0,0 +1,7 @@
+package moe.nea.lisp.bind
+
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION)
+annotation class LispBinding(
+ val name: String,
+)