diff options
author | Linnea Gräf <nea@nea.moe> | 2024-04-30 00:12:27 +0200 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2024-04-30 00:12:27 +0200 |
commit | b51321b27fdf513a3de09d084bbd4e69a26fb76e (patch) | |
tree | 873c433cc96e382321736fe0bcb4e2948789ced6 | |
parent | 37d5b9ce0acf6eb25162c097990983f388ec3a19 (diff) | |
download | nealisp-b51321b27fdf513a3de09d084bbd4e69a26fb76e.tar.gz nealisp-b51321b27fdf513a3de09d084bbd4e69a26fb76e.tar.bz2 nealisp-b51321b27fdf513a3de09d084bbd4e69a26fb76e.zip |
Add lists
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | build.gradle.kts | 51 | ||||
-rw-r--r-- | res/builtins.lisp | 14 | ||||
-rw-r--r-- | src/CoreBindings.kt | 612 | ||||
-rw-r--r-- | src/LispData.kt | 198 | ||||
-rw-r--r-- | src/LispExecutionContext.kt | 2 | ||||
-rw-r--r-- | test/res/test.lisp | 26 |
7 files changed, 507 insertions, 399 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a78fc8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build +.gradle + diff --git a/build.gradle.kts b/build.gradle.kts index c0e28f1..5bfff26 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,42 +3,43 @@ group = "moe.nea" version = "1.0.0" repositories { mavenCentral() } java.toolchain { languageVersion.set(JavaLanguageVersion.of(8)) } +java.withSourcesJar() sourceSets.main { java.setSrcDirs(listOf("src/")); resources.setSrcDirs(listOf("res")) } sourceSets.test { java.setSrcDirs(listOf("test/src")); resources.setSrcDirs(listOf("test/res")) } publishing { - publications { - create<MavenPublication>("maven") { - from(components["java"]) - } - } + publications { + create<MavenPublication>("maven") { + from(components["java"]) + } + } } val testReportFile = layout.buildDirectory.file("test-results/nealisp/results.xml") tasks.create("testLisps", JavaExec::class) { - javaLauncher.set(javaToolchains.launcherFor(java.toolchain)) - classpath(sourceSets.test.get().runtimeClasspath) - mainClass.set("TestMain") - dependsOn(tasks.testClasses) - dependsOn(tasks.processTestResources) - outputs.file(testReportFile) - systemProperty("test.report", testReportFile.map { it.asFile.absolutePath }.get()) - systemProperty("test.suites", "test") - systemProperty("test.imports", "secondary") - group = "verification" + javaLauncher.set(javaToolchains.launcherFor(java.toolchain)) + classpath(sourceSets.test.get().runtimeClasspath) + mainClass.set("TestMain") + dependsOn(tasks.testClasses) + dependsOn(tasks.processTestResources) + outputs.file(testReportFile) + systemProperty("test.report", testReportFile.map { it.asFile.absolutePath }.get()) + systemProperty("test.suites", "test") + systemProperty("test.imports", "secondary") + group = "verification" } tasks.create("testLispsHtml", Exec::class) { - dependsOn("testLisps") - executable("xunit-viewer") - inputs.file(testReportFile) - val testReportHtmlFile = layout.buildDirectory.file("reports/nealisp/tests/index.html") - outputs.file(testReportHtmlFile) - args( - "-r", testReportFile.map { it.asFile.absolutePath }.get(), - "-o", testReportHtmlFile.map { it.asFile.absolutePath }.get() - ) - group = "verification" + dependsOn("testLisps") + executable("xunit-viewer") + inputs.file(testReportFile) + val testReportHtmlFile = layout.buildDirectory.file("reports/nealisp/tests/index.html") + outputs.file(testReportHtmlFile) + args( + "-r", testReportFile.map { it.asFile.absolutePath }.get(), + "-o", testReportHtmlFile.map { it.asFile.absolutePath }.get() + ) + group = "verification" } diff --git a/res/builtins.lisp b/res/builtins.lisp index 19bab1f..f6e2bba 100644 --- a/res/builtins.lisp +++ b/res/builtins.lisp @@ -62,4 +62,16 @@ (def hash.get core.gethash) (export hash.new hash.merge hash.get) - +(comment "Re-export List operations") +(def list.new core.list.new) +(def list.slice core.list.slice) +(def list.length core.list.length) +(def list.join core.list.join) +(def list.at core.list.at) +(defun list.map (list func) + (if [= 0 (list.length list)] + (list.new) + (list.join + (list.new (func (list.at list 0))) + (list.map (list.slice list 1 (list.length list)) func)))) +(export list.new list.slice list.length list.join list.at list.map) diff --git a/src/CoreBindings.kt b/src/CoreBindings.kt index 2208b8c..8918691 100644 --- a/src/CoreBindings.kt +++ b/src/CoreBindings.kt @@ -1,295 +1,361 @@ package moe.nea.lisp object CoreBindings { - val def = LispData.externalRawCall("def") { context, callsite, stackFrame, args -> - if (args.size != 2) { - return@externalRawCall stackFrame.reportError("Function define expects exactly two arguments", callsite) - } - val (name, value) = args - if (name !is LispAst.Reference) { - return@externalRawCall stackFrame.reportError("Define expects a name as first argument", name) - } - if (name.label in stackFrame.variables) { - return@externalRawCall stackFrame.reportError("Cannot redefine value in local context", name) - } - return@externalRawCall stackFrame.setValueLocal(name.label, context.resolveValue(stackFrame, value)) - } + val def = LispData.externalRawCall("def") { context, callsite, stackFrame, args -> + if (args.size != 2) { + return@externalRawCall stackFrame.reportError("Function define expects exactly two arguments", callsite) + } + val (name, value) = args + if (name !is LispAst.Reference) { + return@externalRawCall stackFrame.reportError("Define expects a name as first argument", name) + } + if (name.label in stackFrame.variables) { + return@externalRawCall stackFrame.reportError("Cannot redefine value in local context", name) + } + return@externalRawCall stackFrame.setValueLocal(name.label, context.resolveValue(stackFrame, value)) + } - fun isTruthy(data: LispData): Boolean? { - if (data == trueValue) return true - if (data == falseValue) return false - return null - } + fun isTruthy(data: LispData): Boolean? { + if (data == trueValue) return true + if (data == falseValue) return false + return null + } - val trueValue = LispData.Atom("true") - val falseValue = LispData.Atom("false") + val trueValue = LispData.Atom("true") + val falseValue = LispData.Atom("false") - val ifFun = LispData.externalRawCall("if") { context, callsite, stackFrame, args -> - if (args.size != 3) { - return@externalRawCall stackFrame.reportError("if requires 3 arguments", callsite) - } - val (cond, ifTrue, ifFalse) = args + val ifFun = LispData.externalRawCall("if") { context, callsite, stackFrame, args -> + if (args.size != 3) { + return@externalRawCall stackFrame.reportError("if requires 3 arguments", callsite) + } + val (cond, ifTrue, ifFalse) = args - val c = isTruthy(context.resolveValue(stackFrame, cond)) - if (c == null) { - return@externalRawCall stackFrame.reportError("Non boolean value $c used as condition for if", cond) - } - if (c) { - return@externalRawCall context.resolveValue(stackFrame, ifTrue) - } else { - return@externalRawCall context.resolveValue(stackFrame, ifFalse) - } - } + val c = isTruthy(context.resolveValue(stackFrame, cond)) + if (c == null) { + return@externalRawCall stackFrame.reportError("Non boolean value $c used as condition for if", cond) + } + if (c) { + return@externalRawCall context.resolveValue(stackFrame, ifTrue) + } else { + return@externalRawCall context.resolveValue(stackFrame, ifFalse) + } + } - val pure = LispData.externalCall("pure") { args, reportError -> - return@externalCall args.singleOrNull()?.let { value -> - LispData.externalCall("pure.r") { args, reportError -> - if (args.isNotEmpty()) - reportError("Pure function does not expect arguments") - else - value - } - } ?: reportError("Function pure expects exactly one argument") - } + val pure = LispData.externalCall("pure") { args, reportError -> + return@externalCall args.singleOrNull()?.let { value -> + LispData.externalCall("pure.r") { args, reportError -> + if (args.isNotEmpty()) + reportError("Pure function does not expect arguments") + else + value + } + } ?: reportError("Function pure expects exactly one argument") + } - val lambda = LispData.externalRawCall("lambda") { context, callsite, stackFrame, args -> - if (args.size != 2) { - return@externalRawCall stackFrame.reportError("Lambda needs exactly 2 arguments", callsite) - } - val (argumentNames, body) = args - if (argumentNames !is LispAst.Parenthesis) { - return@externalRawCall stackFrame.reportError("Lambda has invalid argument declaration", argumentNames) - } - val argumentNamesString = argumentNames.items.map { - val ref = it as? LispAst.Reference - if (ref == null) { - return@externalRawCall stackFrame.reportError("Lambda has invalid argument declaration", it) - } - ref.label - } - if (body !is LispAst.Parenthesis) { - return@externalRawCall stackFrame.reportError("Lambda has invalid body declaration", body) - } - LispData.createLambda(stackFrame, argumentNamesString, body) - } + val lambda = LispData.externalRawCall("lambda") { context, callsite, stackFrame, args -> + if (args.size != 2) { + return@externalRawCall stackFrame.reportError("Lambda needs exactly 2 arguments", callsite) + } + val (argumentNames, body) = args + if (argumentNames !is LispAst.Parenthesis) { + return@externalRawCall stackFrame.reportError("Lambda has invalid argument declaration", argumentNames) + } + val argumentNamesString = argumentNames.items.map { + val ref = it as? LispAst.Reference + if (ref == null) { + return@externalRawCall stackFrame.reportError("Lambda has invalid argument declaration", it) + } + ref.label + } + if (body !is LispAst.Parenthesis) { + return@externalRawCall stackFrame.reportError("Lambda has invalid body declaration", body) + } + LispData.createLambda(stackFrame, argumentNamesString, body) + } - val defun = LispData.externalRawCall("defun") { context, callSite, stackFrame, lispAsts -> - if (lispAsts.size != 3) { - return@externalRawCall stackFrame.reportError("Invalid function definition", callSite) - } - val (name, args, body) = lispAsts - if (name !is LispAst.Reference) { - return@externalRawCall stackFrame.reportError("Invalid function definition name", name) - } - if (name.label in stackFrame.variables) { - return@externalRawCall stackFrame.reportError("Cannot redefine function in local context", name) - } - if (args !is LispAst.Parenthesis) { - return@externalRawCall stackFrame.reportError("Invalid function definition arguments", args) - } - val argumentNames = args.items.map { - val ref = it as? LispAst.Reference - ?: return@externalRawCall stackFrame.reportError("Invalid function definition argument name", it) - ref.label - } - if (body !is LispAst.Parenthesis) { - return@externalRawCall stackFrame.reportError("Invalid function definition body", body) - } - return@externalRawCall stackFrame.setValueLocal( - name.label, - LispData.createLambda(stackFrame, argumentNames, body, name.label) - ) - } - val seq = LispData.externalRawCall("seq") { context, callsite, stackFrame, args -> - var lastResult: LispData? = null - for (arg in args) { - lastResult = context.executeLisp(stackFrame, arg) - } - lastResult ?: stackFrame.reportError("Seq cannot be invoked with 0 argumens", callsite) - } + val defun = LispData.externalRawCall("defun") { context, callSite, stackFrame, lispAsts -> + if (lispAsts.size != 3) { + return@externalRawCall stackFrame.reportError("Invalid function definition", callSite) + } + val (name, args, body) = lispAsts + if (name !is LispAst.Reference) { + return@externalRawCall stackFrame.reportError("Invalid function definition name", name) + } + if (name.label in stackFrame.variables) { + return@externalRawCall stackFrame.reportError("Cannot redefine function in local context", name) + } + if (args !is LispAst.Parenthesis) { + return@externalRawCall stackFrame.reportError("Invalid function definition arguments", args) + } + val argumentNames = args.items.map { + val ref = it as? LispAst.Reference + ?: return@externalRawCall stackFrame.reportError("Invalid function definition argument name", it) + ref.label + } + if (body !is LispAst.Parenthesis) { + return@externalRawCall stackFrame.reportError("Invalid function definition body", body) + } + return@externalRawCall stackFrame.setValueLocal( + name.label, + LispData.createLambda(stackFrame, argumentNames, body, name.label) + ) + } + val seq = LispData.externalRawCall("seq") { context, callsite, stackFrame, args -> + var lastResult: LispData? = null + for (arg in args) { + lastResult = context.executeLisp(stackFrame, arg) + } + lastResult ?: stackFrame.reportError("Seq cannot be invoked with 0 argumens", callsite) + } - internal fun stringify(thing: LispData): String { - return when (thing) { - is LispData.Atom -> ":${thing.label}" - is LispData.JavaExecutable -> "<native function ${thing.name}>" - LispData.LispNil -> "nil" - is LispData.LispNode -> thing.node.toSource() - is LispData.LispList -> thing.elements.joinToString(", ", "[", "]") { stringify(it) } - is LispData.LispString -> thing.string - is LispData.LispHash -> thing.map.asIterable().joinToString(", ", "{", "}") { it.key + ": " + it.value } - is LispData.LispNumber -> thing.value.toString() - is LispData.ForeignObject<*> -> "<foreign ${thing.obj}>" - is LispData.LispInterpretedCallable -> "<function ${thing.name ?: "<anonymous>"} ${thing.argNames} ${thing.body.toSource()}>" - } - } + internal fun stringify(thing: LispData): String { + return when (thing) { + is LispData.Atom -> ":${thing.label}" + is LispData.JavaExecutable -> "<native function ${thing.name}>" + LispData.LispNil -> "nil" + is LispData.LispNode -> thing.node.toSource() + is LispData.LispList -> thing.elements.joinToString(", ", "[", "]") { stringify(it) } + is LispData.LispString -> thing.string + is LispData.LispHash -> thing.map.asIterable().joinToString(", ", "{", "}") { it.key + ": " + it.value } + is LispData.LispNumber -> thing.value.toString() + is LispData.ForeignObject<*> -> "<foreign ${thing.obj}>" + is LispData.LispInterpretedCallable -> "<function ${thing.name ?: "<anonymous>"} ${thing.argNames} ${thing.body.toSource()}>" + } + } - val tostring = LispData.externalCall("tostring") { args, reportError -> - LispData.LispString(args.joinToString(" ") { stringify(it) }) - } + val tostring = LispData.externalCall("tostring") { args, reportError -> + LispData.LispString(args.joinToString(" ") { stringify(it) }) + } - val debuglog = LispData.externalRawCall("debuglog") { context, callsite, stackFrame, args -> - OutputCapture.print( - stackFrame, - args.joinToString(" ", postfix = "\n") { stringify(context.resolveValue(stackFrame, it)) }) - LispData.LispNil - } - val add = LispData.externalCall("add") { args, reportError -> - if (args.size == 0) { - return@externalCall reportError("Cannot call add without at least 1 argument") - } - LispData.LispNumber(args.fold(0.0) { a, b -> - a + (b as? LispData.LispNumber - ?: return@externalCall reportError("Unexpected argument $b, expected number")).value - }) - } - val sub = LispData.externalCall("sub") { args, reportError -> - if (args.size == 0) { - return@externalCall reportError("Cannot call sub without at least 1 argument") - } - val c = args.map { - (it as? LispData.LispNumber - ?: return@externalCall reportError("Unexpected argument $it, expected number") - ).value - } - LispData.LispNumber(c.drop(1).fold(c.first()) { a, b -> - a - b - }) - } - val mul = LispData.externalCall("mul") { args, reportError -> - if (args.size == 0) { - return@externalCall reportError("Cannot call mul without at least 1 argument") - } - LispData.LispNumber(args.fold(1.0) { a, b -> - a * (b as? LispData.LispNumber - ?: return@externalCall reportError("Unexpected argument $b, expected number")).value - }) - } - val eq = LispData.externalCall("eq") { args, reportError -> - if (args.size == 0) { - return@externalCall reportError("Cannot call eq without at least 1 argument") - } - LispData.boolean(!args.zipWithNext().any { it.first != it.second }) - } - val div = LispData.externalCall("div") { args, reportError -> - if (args.size == 0) { - return@externalCall reportError("Cannot call div without at least 1 argument") - } - val c = args.map { - (it as? LispData.LispNumber - ?: return@externalCall reportError("Unexpected argument $it, expected number") - ).value - } - LispData.LispNumber(c.drop(1).fold(c.first()) { a, b -> - a / b - }) - } - val less = LispData.externalCall("less") { args, reportError -> - if (args.size != 2) { - return@externalCall reportError("Cannot call less without exactly 2 arguments") - } - val (left, right) = args.map { - (it as? LispData.LispNumber - ?: return@externalCall reportError("Unexpected argument $it, expected number") - ).value - } - LispData.boolean(left < right) - } + val debuglog = LispData.externalRawCall("debuglog") { context, callsite, stackFrame, args -> + OutputCapture.print( + stackFrame, + args.joinToString(" ", postfix = "\n") { stringify(context.resolveValue(stackFrame, it)) }) + LispData.LispNil + } + val add = LispData.externalCall("add") { args, reportError -> + if (args.size == 0) { + return@externalCall reportError("Cannot call add without at least 1 argument") + } + LispData.LispNumber(args.fold(0.0) { a, b -> + a + (b as? LispData.LispNumber + ?: return@externalCall reportError("Unexpected argument $b, expected number")).value + }) + } + val sub = LispData.externalCall("sub") { args, reportError -> + if (args.size == 0) { + return@externalCall reportError("Cannot call sub without at least 1 argument") + } + val c = args.map { + (it as? LispData.LispNumber + ?: return@externalCall reportError("Unexpected argument $it, expected number") + ).value + } + LispData.LispNumber(c.drop(1).fold(c.first()) { a, b -> + a - b + }) + } + val mul = LispData.externalCall("mul") { args, reportError -> + if (args.size == 0) { + return@externalCall reportError("Cannot call mul without at least 1 argument") + } + LispData.LispNumber(args.fold(1.0) { a, b -> + a * (b as? LispData.LispNumber + ?: return@externalCall reportError("Unexpected argument $b, expected number")).value + }) + } + val eq = LispData.externalCall("eq") { args, reportError -> + if (args.size == 0) { + return@externalCall reportError("Cannot call eq without at least 1 argument") + } + LispData.boolean(!args.zipWithNext().any { it.first != it.second }) + } + val div = LispData.externalCall("div") { args, reportError -> + if (args.size == 0) { + return@externalCall reportError("Cannot call div without at least 1 argument") + } + val c = args.map { + (it as? LispData.LispNumber + ?: return@externalCall reportError("Unexpected argument $it, expected number") + ).value + } + LispData.LispNumber(c.drop(1).fold(c.first()) { a, b -> + a / b + }) + } + val less = LispData.externalCall("less") { args, reportError -> + if (args.size != 2) { + return@externalCall reportError("Cannot call less without exactly 2 arguments") + } + val (left, right) = args.map { + (it as? LispData.LispNumber + ?: return@externalCall reportError("Unexpected argument $it, expected number") + ).value + } + LispData.boolean(left < right) + } - private fun atomOrStringToString(thing: LispData): String? { - return when (thing) { - is LispData.Atom -> thing.label - is LispData.LispString -> thing.string - else -> null - } - } + private fun atomOrStringToString(thing: LispData): String? { + return when (thing) { + is LispData.Atom -> thing.label + is LispData.LispString -> thing.string + else -> null + } + } - val import = LispData.externalRawCall("import") { context, callsite, stackFrame, args -> - if (args.size != 1) { - return@externalRawCall stackFrame.reportError("import needs at least one argument", callsite) - } - // TODO: aliased / namespaced imports - val moduleName = atomOrStringToString(context.resolveValue(stackFrame, args[0])) - ?: return@externalRawCall stackFrame.reportError("import needs a string or atom as argument", callsite) - context.importModule(moduleName, stackFrame, callsite) - return@externalRawCall LispData.LispNil - } + val import = LispData.externalRawCall("import") { context, callsite, stackFrame, args -> + if (args.size != 1) { + return@externalRawCall stackFrame.reportError("import needs at least one argument", callsite) + } + // TODO: aliased / namespaced imports + val moduleName = atomOrStringToString(context.resolveValue(stackFrame, args[0])) + ?: return@externalRawCall stackFrame.reportError("import needs a string or atom as argument", callsite) + context.importModule(moduleName, stackFrame, callsite) + return@externalRawCall LispData.LispNil + } - val reflect = LispData.externalCall("reflect.type") { args, reportError -> - if (args.size != 1) { - return@externalCall reportError("reflect.type can only return the type for one argument") - } + val reflect = LispData.externalCall("reflect.type") { args, reportError -> + if (args.size != 1) { + return@externalCall reportError("reflect.type can only return the type for one argument") + } - return@externalCall when (args[0]) { - is LispData.Atom -> LispData.Atom("atom") - is LispData.LispExecutable -> LispData.Atom("callable") - is LispData.LispList -> LispData.Atom("list") - LispData.LispNil -> LispData.Atom("nil") - is LispData.LispHash -> LispData.Atom("hash") - is LispData.LispNode -> LispData.Atom("ast") - is LispData.ForeignObject<*> -> LispData.Atom("foreign") - is LispData.LispNumber -> LispData.Atom("number") - is LispData.LispString -> LispData.Atom("string") - } - } + return@externalCall when (args[0]) { + is LispData.Atom -> LispData.Atom("atom") + is LispData.LispExecutable -> LispData.Atom("callable") + is LispData.LispList -> LispData.Atom("list") + LispData.LispNil -> LispData.Atom("nil") + is LispData.LispHash -> LispData.Atom("hash") + is LispData.LispNode -> LispData.Atom("ast") + is LispData.ForeignObject<*> -> LispData.Atom("foreign") + is LispData.LispNumber -> LispData.Atom("number") + is LispData.LispString -> LispData.Atom("string") + } + } - fun offerArithmeticTo(bindings: StackFrame) { - bindings.setValueLocal("core.arith.add", add) - bindings.setValueLocal("core.arith.div", div) - bindings.setValueLocal("core.arith.mul", mul) - bindings.setValueLocal("core.arith.sub", sub) - bindings.setValueLocal("core.arith.less", less) - bindings.setValueLocal("core.arith.eq", eq) - } + val makeList = LispData.externalCall("list.make") { args, reportError -> + return@externalCall LispData.LispList(args) + } - fun offerHashesTo(bindings: StackFrame) { - bindings.setValueLocal("core.newhash", LispData.externalCall("newhash") { args, reportError -> - if (args.size % 2 != 0) { - return@externalCall reportError("Hash creation needs to have an even number of arguments") - } + val indexList = LispData.externalCall("list.at") { args, reportError -> + if (args.size != 2) { + return@externalCall reportError("list.at takes exactly two arguments") + } + val list = + args[0] as? LispData.LispList ?: return@externalCall reportError("the first argument to list.at is a list") + val index = (args[1] as? LispData.LispNumber)?.value?.toInt() + ?: return@externalCall reportError("the second argument to list.at is a number") + if (index !in list.elements.indices) + return@externalCall reportError("index out of bounds: $list[$index]") + return@externalCall list.elements[index] + } - LispData.LispHash( - args.chunked(2).associate { (a, b) -> - (atomOrStringToString(a) - ?: return@externalCall reportError("Hash key needs to be string or atom")) to b - }) - }) - bindings.setValueLocal("core.gethash", LispData.externalCall("gethash") { args, reportError -> - if (args.size != 2) { - return@externalCall reportError("Hash access needs 2 arguments") - } - val (hash, name) = args - if (hash !is LispData.LispHash) { - return@externalCall reportError("$hash is not a hash") - } - val nameS = - atomOrStringToString(name) ?: return@externalCall reportError("$name is not an atom or a string") - hash.map[nameS] ?: LispData.LispNil - }) - bindings.setValueLocal("core.mergehash", LispData.externalCall("mergehash") { args, reportError -> - val m = mutableMapOf<String, LispData>() - for (arg in args) { - if (arg !is LispData.LispHash) { - return@externalCall reportError("$arg is not a hash") - } - m.putAll(arg.map) - } - LispData.LispHash(m) - }) - } + val joinList = LispData.externalCall("list.join") { args, reportError -> + if (args.size != 2) { + return@externalCall reportError("list.join takes exactly two arguments") + } + val listA = + args[0] as? LispData.LispList + ?: return@externalCall reportError("the first argument to list.join is a list") + val listB = + args[1] as? LispData.LispList + ?: return@externalCall reportError("the second argument to list.join is a list") + return@externalCall LispData.LispList(listA.elements + listB.elements) + } - fun offerAllTo(bindings: StackFrame) { - bindings.setValueLocal("core.if", ifFun) - bindings.setValueLocal("core.nil", LispData.LispNil) - bindings.setValueLocal("core.def", def) - bindings.setValueLocal("core.tostring", tostring) - bindings.setValueLocal("core.pure", pure) - bindings.setValueLocal("core.lambda", lambda) - bindings.setValueLocal("core.defun", defun) - bindings.setValueLocal("core.seq", seq) - bindings.setValueLocal("core.import", import) - bindings.setValueLocal("core.reflect.type", reflect) - bindings.setValueLocal("core.debuglog", debuglog) - offerArithmeticTo(bindings) - offerHashesTo(bindings) - } + val sliceList = LispData.externalCall("list.slice") { args, reportError -> + if (args.size != 3) { + return@externalCall reportError("list.slice takes exactly three arguments") + } + val list = + args[0] as? LispData.LispList + ?: return@externalCall reportError("the first argument to list.slice is a list") + val start = + (args[1] as? LispData.LispNumber)?.value?.toInt() + ?: return@externalCall reportError("the second argument to list.slice is the start index") + val end = + (args[2] as? LispData.LispNumber)?.value?.toInt() + ?: return@externalCall reportError("the third argument to list.slice is the end index") + return@externalCall LispData.LispList(list.elements.slice(start until end)) + } + + val lengthList = LispData.externalCall("list.length") { args, reportError -> + if (args.size != 1) { + return@externalCall reportError("list.length takes exactly three arguments") + } + val list = + args[0] as? LispData.LispList + ?: return@externalCall reportError("the first argument to list.length is a list") + return@externalCall LispData.LispNumber(list.elements.size.toDouble()) + } + + fun offerListTo(bindings: StackFrame) { + bindings.setValueLocal("core.list.length", lengthList) + bindings.setValueLocal("core.list.slice", sliceList) + bindings.setValueLocal("core.list.join", joinList) + bindings.setValueLocal("core.list.at", indexList) + bindings.setValueLocal("core.list.new", makeList) + } + + + fun offerArithmeticTo(bindings: StackFrame) { + bindings.setValueLocal("core.arith.add", add) + bindings.setValueLocal("core.arith.div", div) + bindings.setValueLocal("core.arith.mul", mul) + bindings.setValueLocal("core.arith.sub", sub) + bindings.setValueLocal("core.arith.less", less) + bindings.setValueLocal("core.arith.eq", eq) + } + + fun offerHashesTo(bindings: StackFrame) { + bindings.setValueLocal("core.newhash", LispData.externalCall("newhash") { args, reportError -> + if (args.size % 2 != 0) { + return@externalCall reportError("Hash creation needs to have an even number of arguments") + } + + LispData.LispHash( + args.chunked(2).associate { (a, b) -> + (atomOrStringToString(a) + ?: return@externalCall reportError("Hash key needs to be string or atom")) to b + }) + }) + bindings.setValueLocal("core.gethash", LispData.externalCall("gethash") { args, reportError -> + if (args.size != 2) { + return@externalCall reportError("Hash access needs 2 arguments") + } + val (hash, name) = args + if (hash !is LispData.LispHash) { + return@externalCall reportError("$hash is not a hash") + } + val nameS = + atomOrStringToString(name) ?: return@externalCall reportError("$name is not an atom or a string") + hash.map[nameS] ?: LispData.LispNil + }) + bindings.setValueLocal("core.mergehash", LispData.externalCall("mergehash") { args, reportError -> + val m = mutableMapOf<String, LispData>() + for (arg in args) { + if (arg !is LispData.LispHash) { + return@externalCall reportError("$arg is not a hash") + } + m.putAll(arg.map) + } + LispData.LispHash(m) + }) + } + + fun offerAllTo(bindings: StackFrame) { + bindings.setValueLocal("core.if", ifFun) + bindings.setValueLocal("core.nil", LispData.LispNil) + bindings.setValueLocal("core.def", def) + bindings.setValueLocal("core.tostring", tostring) + bindings.setValueLocal("core.pure", pure) + bindings.setValueLocal("core.lambda", lambda) + bindings.setValueLocal("core.defun", defun) + bindings.setValueLocal("core.seq", seq) + bindings.setValueLocal("core.import", import) + bindings.setValueLocal("core.reflect.type", reflect) + bindings.setValueLocal("core.debuglog", debuglog) + offerArithmeticTo(bindings) + offerListTo(bindings) + offerHashesTo(bindings) + } }
\ No newline at end of file diff --git a/src/LispData.kt b/src/LispData.kt index 61c2b76..8e858f3 100644 --- a/src/LispData.kt +++ b/src/LispData.kt @@ -2,111 +2,111 @@ package moe.nea.lisp sealed class LispData { - object LispNil : LispData() - data class Atom(val label: String) : LispData() - data class LispString(val string: String) : LispData() - data class LispNumber(val value: Double) : LispData() - data class LispNode(val node: LispAst.LispNode) : LispData() - data class LispHash(val map: Map<String, LispData>) : LispData() - data class ForeignObject<T : Any?>(val obj: T) : LispData() - class LispList(val elements: List<LispData>) : LispData() - sealed class LispExecutable() : LispData() { - abstract fun execute( - executionContext: LispExecutionContext, - callsite: LispAst.LispNode, - stackFrame: StackFrame, - args: List<LispAst.LispNode> - ): LispData - } + object LispNil : LispData() + data class Atom(val label: String) : LispData() + data class LispString(val string: String) : LispData() + data class LispNumber(val value: Double) : LispData() + data class LispNode(val node: LispAst.LispNode) : LispData() + data class LispHash(val map: Map<String, LispData>) : LispData() + data class ForeignObject<T : Any?>(val obj: T) : LispData() + data class LispList(val elements: List<LispData>) : LispData() + sealed class LispExecutable() : LispData() { + abstract fun execute( + executionContext: LispExecutionContext, + callsite: LispAst.LispNode, + stackFrame: StackFrame, + args: List<LispAst.LispNode> + ): LispData + } - abstract class JavaExecutable(val name: String) : LispExecutable() + abstract class JavaExecutable(val name: String) : LispExecutable() - data class LispInterpretedCallable( - val declarationStackFrame: StackFrame, - val argNames: List<String>, - val body: LispAst.Parenthesis, - val name: String?, - ) : LispExecutable() { - override fun execute( - executionContext: LispExecutionContext, - callsite: LispAst.LispNode, - stackFrame: StackFrame, - args: List<LispAst.LispNode> - ): LispData { + data class LispInterpretedCallable( + val declarationStackFrame: StackFrame, + val argNames: List<String>, + val body: LispAst.Parenthesis, + val name: String?, + ) : LispExecutable() { + override fun execute( + executionContext: LispExecutionContext, + callsite: LispAst.LispNode, + stackFrame: StackFrame, + args: List<LispAst.LispNode> + ): LispData { - val invocationFrame = declarationStackFrame.fork() - if (argNames.lastOrNull() == "...") { - for ((name, value) in argNames.dropLast(1).zip(args)) { - invocationFrame.setValueLocal(name, executionContext.resolveValue(stackFrame, value)) - } - invocationFrame.setValueLocal( - "...", - LispList( - args.drop(argNames.size - 1).map { executionContext.resolveValue(stackFrame, it) }) - ) - } else if (argNames.size != args.size) { - return stackFrame.reportError( - "Expected ${argNames.size} arguments, got ${args.size} instead", - callsite - ) - } else - for ((name, value) in argNames.zip(args)) { - invocationFrame.setValueLocal(name, executionContext.resolveValue(stackFrame, value)) - } - return executionContext.executeLisp(invocationFrame, body) - } - } + val invocationFrame = declarationStackFrame.fork() + if (argNames.lastOrNull() == "...") { + for ((name, value) in argNames.dropLast(1).zip(args)) { + invocationFrame.setValueLocal(name, executionContext.resolveValue(stackFrame, value)) + } + invocationFrame.setValueLocal( + "...", + LispList( + args.drop(argNames.size - 1).map { executionContext.resolveValue(stackFrame, it) }) + ) + } else if (argNames.size != args.size) { + return stackFrame.reportError( + "Expected ${argNames.size} arguments, got ${args.size} instead", + callsite + ) + } else + for ((name, value) in argNames.zip(args)) { + invocationFrame.setValueLocal(name, executionContext.resolveValue(stackFrame, value)) + } + return executionContext.executeLisp(invocationFrame, body) + } + } - companion object { - fun externalRawCall( - name: String, - callable: (context: LispExecutionContext, callsite: LispAst.LispNode, stackFrame: StackFrame, args: List<LispAst.LispNode>) -> LispData - ): LispExecutable { - return object : JavaExecutable(name) { - override fun execute( - executionContext: LispExecutionContext, - callsite: LispAst.LispNode, - stackFrame: StackFrame, - args: List<LispAst.LispNode> - ): LispData { - return callable.invoke(executionContext, callsite, stackFrame, args) - } - } - } + companion object { + fun externalRawCall( + name: String, + callable: (context: LispExecutionContext, callsite: LispAst.LispNode, stackFrame: StackFrame, args: List<LispAst.LispNode>) -> LispData + ): LispExecutable { + return object : JavaExecutable(name) { + override fun execute( + executionContext: LispExecutionContext, + callsite: LispAst.LispNode, + stackFrame: StackFrame, + args: List<LispAst.LispNode> + ): LispData { + return callable.invoke(executionContext, callsite, stackFrame, args) + } + } + } - fun externalCall( - name: String, - callable: (args: List<LispData>, reportError: (String) -> LispData) -> LispData - ): LispExecutable { - return object : JavaExecutable(name) { - override fun execute( - executionContext: LispExecutionContext, - callsite: LispAst.LispNode, - stackFrame: StackFrame, - args: List<LispAst.LispNode> - ): LispData { - val mappedArgs = args.map { executionContext.resolveValue(stackFrame, it) } - return callable.invoke(mappedArgs) { stackFrame.reportError(it, callsite) } - } - } - } + fun externalCall( + name: String, + callable: (args: List<LispData>, reportError: (String) -> LispData) -> LispData + ): LispExecutable { + return object : JavaExecutable(name) { + override fun execute( + executionContext: LispExecutionContext, + callsite: LispAst.LispNode, + stackFrame: StackFrame, + args: List<LispAst.LispNode> + ): LispData { + val mappedArgs = args.map { executionContext.resolveValue(stackFrame, it) } + return callable.invoke(mappedArgs) { stackFrame.reportError(it, callsite) } + } + } + } - fun createLambda( - declarationStackFrame: StackFrame, - args: List<String>, - body: LispAst.Parenthesis, - nameHint: String? = null, - ): LispExecutable { - return LispInterpretedCallable(declarationStackFrame, args, body, nameHint) - } + fun createLambda( + declarationStackFrame: StackFrame, + args: List<String>, + body: LispAst.Parenthesis, + nameHint: String? = null, + ): LispExecutable { + return LispInterpretedCallable(declarationStackFrame, args, body, nameHint) + } - fun boolean(b: Boolean): Atom { - return if (b) { - CoreBindings.trueValue - } else { - CoreBindings.falseValue - } - } - } + fun boolean(b: Boolean): Atom { + return if (b) { + CoreBindings.trueValue + } else { + CoreBindings.falseValue + } + } + } } diff --git a/src/LispExecutionContext.kt b/src/LispExecutionContext.kt index 0a1dc41..70df965 100644 --- a/src/LispExecutionContext.kt +++ b/src/LispExecutionContext.kt @@ -78,7 +78,7 @@ class LispExecutionContext() { fun executeProgram(stackFrame: StackFrame, program: LispAst.Program): LispData? { var lastValue: LispData? = null for (node in program.nodes) { - lastValue = executeLisp(stackFrame, node) + lastValue = resolveValue(stackFrame, node) } return lastValue } diff --git a/test/res/test.lisp b/test/res/test.lisp index 39c6d82..713ee2a 100644 --- a/test/res/test.lisp +++ b/test/res/test.lisp @@ -36,3 +36,29 @@ ((test.assert-eqd 0xFFFFFFFF 4294967295 0.0001)) )) +(test.test "List joining" (seq + ((test.assert-eq + (list.new 1 2 3 4) + (list.join [list.new 1 2] [list.new 3 4]) + )) + )) + +(test.test "List slicing" (seq + ((test.assert-eq + (list.slice [list.new 1 2 3 4] 2 4) + (list.new 3 4) + )) + )) + +(test.test "List length" + ((test.assert-eq [list.length (list.new 1 2 3 4)] 4)) + ) + +(test.test "List mapping" + ((test.assert-eq + [list.map (list.new 1 2 3 4) (lambda (x) (+ 1 x))] + [list.new 2 3 4 5])) + ) + + + |