summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--build.gradle.kts51
-rw-r--r--res/builtins.lisp14
-rw-r--r--src/CoreBindings.kt612
-rw-r--r--src/LispData.kt198
-rw-r--r--src/LispExecutionContext.kt2
-rw-r--r--test/res/test.lisp26
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]))
+ )
+
+
+