diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/CoreBindings.kt | 2 | ||||
-rw-r--r-- | src/ErrorReporter.kt | 8 | ||||
-rw-r--r-- | src/LispErrorReporter.kt | 15 | ||||
-rw-r--r-- | src/LispExecutionContext.kt | 1 | ||||
-rw-r--r-- | src/LispParser.kt | 5 | ||||
-rw-r--r-- | src/bind/AutoBinder.kt | 124 | ||||
-rw-r--r-- | src/bind/LispBinding.kt | 7 |
7 files changed, 145 insertions, 17 deletions
diff --git a/src/CoreBindings.kt b/src/CoreBindings.kt index 94b3852..b90072e 100644 --- a/src/CoreBindings.kt +++ b/src/CoreBindings.kt @@ -15,7 +15,7 @@ object CoreBindings { return@externalRawCall stackFrame.setValueLocal(name.label, context.resolveValue(stackFrame, value)) } - private fun isTruthy(data: LispData): Boolean? { + fun isTruthy(data: LispData): Boolean? { if (data == trueValue) return true if (data == falseValue) return false return null diff --git a/src/ErrorReporter.kt b/src/ErrorReporter.kt new file mode 100644 index 0000000..b4ba636 --- /dev/null +++ b/src/ErrorReporter.kt @@ -0,0 +1,8 @@ +package moe.nea.lisp + +interface ErrorReporter { + fun reportError(string: String): LispData + fun reportError(string: String, exception: Throwable): LispData { + return reportError("$string: $exception") + } +}
\ No newline at end of file diff --git a/src/LispErrorReporter.kt b/src/LispErrorReporter.kt deleted file mode 100644 index d9ad148..0000000 --- a/src/LispErrorReporter.kt +++ /dev/null @@ -1,15 +0,0 @@ -package moe.nea.lisp - -class LispErrorReporter { - - data class LispError(val name: String, val position: LispPosition) - - - val errors = listOf<LispError>() - - fun reportError(name: String, position: HasLispPosition) { - println("LISP ERROR: $name at ${position.position}") - } - - -} diff --git a/src/LispExecutionContext.kt b/src/LispExecutionContext.kt index 6398cc4..306e111 100644 --- a/src/LispExecutionContext.kt +++ b/src/LispExecutionContext.kt @@ -2,7 +2,6 @@ package moe.nea.lisp class LispExecutionContext() { - private val errorReporter = LispErrorReporter() val rootStackFrame = StackFrame(null) val unloadedModules = mutableMapOf<String, LispAst.Program>() val modules = mutableMapOf<String, Map<String, LispData>>() diff --git a/src/LispParser.kt b/src/LispParser.kt index 3341779..48d0d45 100644 --- a/src/LispParser.kt +++ b/src/LispParser.kt @@ -10,10 +10,15 @@ class LispParser private constructor(filename: String, string: String) { fun parse(filename: String, string: String): LispAst.Program { return LispParser(filename, string).program } + fun parse(file: File): LispAst.Program { return parse(file.absolutePath, file.readText()) } + fun isValidIdentifier(name: String): Boolean { + return name.isNotEmpty() && name.first() in validStartingIdentifiers && name.all { it in validIdentifiers } + } + val digits = "1234567890" val hexDigits = digits + "abcdefABCDEF" val alphabet = "abcdefghijklmnopqrstuvwxyz" 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, +) |