diff options
Diffstat (limited to 'src/CoreBindings.kt')
-rw-r--r-- | src/CoreBindings.kt | 612 |
1 files changed, 339 insertions, 273 deletions
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 |