From ad9429d1cae70baf9b561090bb33ab2941d2a3a5 Mon Sep 17 00:00:00 2001 From: nea Date: Mon, 21 Aug 2023 19:24:30 +0200 Subject: Varargs support --- TestOutput.xml | 2 +- src/bind/AutoBinder.kt | 73 ++++++++++++++++++++++++++++++++++++-------------- test/res/scratch.lisp | 2 +- test/src/TestLisp.kt | 4 +-- 4 files changed, 57 insertions(+), 24 deletions(-) diff --git a/TestOutput.xml b/TestOutput.xml index f935a7d..87243da 100644 --- a/TestOutput.xml +++ b/TestOutput.xml @@ -1,4 +1,4 @@ - 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 { val bindings = mutableMapOf() 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 + 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 -> 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") diff --git a/test/res/scratch.lisp b/test/res/scratch.lisp index 00517a8..3fc6ed7 100644 --- a/test/res/scratch.lisp +++ b/test/res/scratch.lisp @@ -25,4 +25,4 @@ (debuglog "============") (debuglog "Running tests") -(debuglog "This should be 1.0" (funny-method 1.1 "test" false (test 1 2 3 4 /))) \ No newline at end of file +(debuglog "This should be 1.0" (funny-method 1.1 "test" false (test 1 2 3 4 /) :test)) \ No newline at end of file diff --git a/test/src/TestLisp.kt b/test/src/TestLisp.kt index 5f56d68..0e5d2d8 100644 --- a/test/src/TestLisp.kt +++ b/test/src/TestLisp.kt @@ -9,10 +9,10 @@ object T object TestBindings { @LispBinding("funny-method") - fun funnyMethod(arg: Int, test: String, boolean: Boolean, ast: LispAst): LispData { + fun funnyMethod(arg: Int, test: String, boolean: Boolean, vararg ast: LispAst): LispData { if (boolean) println("From java: $test") - println(ast.toSource()) + ast.forEach { println(it.toSource()) } return LispData.LispNumber(arg.toDouble()) } -- cgit