summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CoreBindings.kt2
-rw-r--r--src/ErrorReporter.kt8
-rw-r--r--src/LispErrorReporter.kt15
-rw-r--r--src/LispExecutionContext.kt1
-rw-r--r--src/LispParser.kt5
-rw-r--r--src/bind/AutoBinder.kt124
-rw-r--r--src/bind/LispBinding.kt7
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,
+)