summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TestOutput.xml2
-rw-r--r--src/bind/AutoBinder.kt73
-rw-r--r--test/res/scratch.lisp2
-rw-r--r--test/src/TestLisp.kt4
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 @@
-<?xml version="1.0" ?><testsuites><testsuite name="Test" tests="0" skipped="0" failures="0" errors="0" timestamp="2023-08-21T04:26:56"><properties></properties><system-out><![CDATA[Hello, World, here is an atom: :iamanatom
+<?xml version="1.0" ?><testsuites><testsuite name="Test" tests="0" skipped="0" failures="0" errors="0" timestamp="2023-08-21T07:24:22"><properties></properties><system-out><![CDATA[Hello, World, here is an atom: :iamanatom
:myfunworks
:atom
a :test
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")
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())
}