diff options
Diffstat (limited to 'src/bind')
-rw-r--r-- | src/bind/AutoBinder.kt | 73 |
1 files changed, 53 insertions, 20 deletions
diff --git a/src/bind/AutoBinder.kt b/src/bind/AutoBinder.kt index 53a4ebb..6d2cac2 100644 --- a/src/bind/AutoBinder.kt +++ b/src/bind/AutoBinder.kt @@ -6,20 +6,29 @@ import java.lang.reflect.Method import java.lang.reflect.Modifier import java.lang.reflect.Parameter +typealias ObjectMapper = (() -> LispData, () -> LispAst, ErrorReporter, () -> Boolean) -> (Any?) + class AutoBinder { + companion object { + val Parameter.effectiveType get() = if (this.isVarArgs) this.type.componentType else this.type + } - private fun mapLispData(parameter: Parameter): ((() -> LispData, () -> LispAst, ErrorReporter) -> Any)? { - if (LispData::class.java.isAssignableFrom(parameter.type)) return { a, b, c -> parameter.type.cast(a()) } + private fun mapLispData(parameter: Parameter): ObjectMapper? { + if (LispData::class.java.isAssignableFrom(parameter.effectiveType)) return { a, b, c, d -> + parameter.effectiveType.cast( + a() + ) + } return null } - private fun mapErrorReporter(parameter: Parameter): ((() -> LispData, () -> LispAst, ErrorReporter) -> Any)? { - if (ErrorReporter::class.java.isAssignableFrom(parameter.type)) return { a, b, c -> c } + private fun mapErrorReporter(parameter: Parameter): ObjectMapper? { + if (ErrorReporter::class.java.isAssignableFrom(parameter.effectiveType)) return { a, b, c, d -> c } return null } - private fun mapString(parameter: Parameter): ((() -> LispData, () -> LispAst, ErrorReporter) -> Any?)? { - if (String::class.java == parameter.type) return { a, b, c -> + private fun mapString(parameter: Parameter): ObjectMapper? { + if (String::class.java == parameter.effectiveType) return { a, b, c, d -> when (val x = a()) { is LispData.LispString -> x.string is LispData.Atom -> x.label @@ -29,8 +38,8 @@ class AutoBinder { return null } - private fun mapBoolean(parameter: Parameter): ((() -> LispData, () -> LispAst, ErrorReporter) -> Any?)? { - if (Boolean::class.java.isAssignableFrom(parameter.type)) return { a, b, c -> + private fun mapBoolean(parameter: Parameter): ObjectMapper? { + if (Boolean::class.java.isAssignableFrom(parameter.effectiveType)) return { a, b, c, d -> val x = a() val y = CoreBindings.isTruthy(x) if (y == null) { @@ -41,31 +50,35 @@ class AutoBinder { return null } - private fun mapAST(parameter: Parameter): ((() -> LispData, () -> LispAst, ErrorReporter) -> Any?)? { - if (LispAst::class.java.isAssignableFrom(parameter.type)) return { a, b, c -> parameter.type.cast(b()) } + private fun mapAST(parameter: Parameter): ObjectMapper? { + if (LispAst::class.java.isAssignableFrom(parameter.effectiveType)) return { a, b, c, d -> + parameter.effectiveType.cast( + b() + ) + } return null } - private fun mapNumber(parameter: Parameter): ((() -> LispData, () -> LispAst, ErrorReporter) -> Any?)? { - if (Double::class.java.isAssignableFrom(parameter.type)) return { a, b, c -> + private fun mapNumber(parameter: Parameter): ObjectMapper? { + if (Double::class.java.isAssignableFrom(parameter.effectiveType)) return { a, b, c, d -> when (val x = a()) { is LispData.LispNumber -> x.value else -> null.also { c.reportError("Could not coerce $x to number") } } } - if (Float::class.java.isAssignableFrom(parameter.type)) return { a, b, c -> + if (Float::class.java.isAssignableFrom(parameter.effectiveType)) return { a, b, c, d -> when (val x = a()) { is LispData.LispNumber -> x.value.toFloat() else -> null.also { c.reportError("Could not coerce $x to number") } } } - if (Int::class.java.isAssignableFrom(parameter.type)) return { a, b, c -> + if (Int::class.java.isAssignableFrom(parameter.effectiveType)) return { a, b, c, d -> when (val x = a()) { is LispData.LispNumber -> x.value.toInt() else -> null.also { c.reportError("Could not coerce $x to number") } } } - if (Long::class.java.isAssignableFrom(parameter.type)) return { a, b, c -> + if (Long::class.java.isAssignableFrom(parameter.effectiveType)) return { a, b, c, d -> when (val x = a()) { is LispData.LispNumber -> x.value.toLong() else -> null.also { c.reportError("Could not coerce $x to number") } @@ -75,7 +88,7 @@ class AutoBinder { } - val objectMappers = mutableListOf<((Parameter) -> (((() -> LispData, () -> LispAst, ErrorReporter) -> Any?)?))>( + val objectMappers = mutableListOf<((Parameter) -> ObjectMapper?)>( ::mapLispData, ::mapErrorReporter, ::mapNumber, @@ -88,8 +101,7 @@ class AutoBinder { 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 + val annotation = method.getAnnotation(LispBinding::class.java) ?: continue require(LispParser.isValidIdentifier(annotation.name)) bindings[annotation.name] = wrapMethod(obj, annotation.name, method) } @@ -97,6 +109,21 @@ class AutoBinder { return bindings } + open fun makeVarArgMapper( + parameter: Parameter, + baseMapper: ObjectMapper + ): ObjectMapper? { + return { a, b, c, d -> + val l = buildList { + while (d()) + add(baseMapper(a, b, c, d)!!) + } + val a = java.lang.reflect.Array.newInstance(parameter.type.componentType, l.size) as Array<Any> + l.withIndex().forEach { a[it.index] = it.value } + a + } + } + private val lookup = MethodHandles.publicLookup() fun wrapMethod(obj: Any, name: String, method: Method): LispData.LispExecutable { var mh = lookup.unreflect(method) @@ -104,8 +131,13 @@ class AutoBinder { mh = mh.bindTo(obj) } val objectMappers = method.parameters.map { param -> - objectMappers.firstNotNullOfOrNull { it.invoke(param) } + val baseMapper = objectMappers.firstNotNullOfOrNull { it.invoke(param) } ?: error("Could not find object mapper for parameter $param") + if (param.isVarArgs) { + makeVarArgMapper(param, baseMapper) + ?: error("Could not transform object mapper to vararg object mapper") + } else + baseMapper } return LispData.externalRawCall(name) { context: LispExecutionContext, callsite: LispAst.LispNode, stackFrame: StackFrame, args: List<LispAst.LispNode> -> val e = object : ErrorReporter { @@ -115,8 +147,9 @@ class AutoBinder { } try { val iterator = args.iterator() + val (a, b, c) = Triple({ context.resolveValue(stackFrame, iterator.next()) }, { iterator.next() }, e) val p = objectMappers.map { - it.invoke({ context.resolveValue(stackFrame, iterator.next()) }, { iterator.next() }, e) + it.invoke(a, b, c, { iterator.hasNext() }) ?: return@externalRawCall LispData.LispNil } if (iterator.hasNext()) return@externalRawCall e.reportError("Too many arguments") |