From d1f49743a0a00a1156272d174e8b6b5c4bb9acec Mon Sep 17 00:00:00 2001 From: nea Date: Sat, 12 Aug 2023 02:36:05 +0200 Subject: Add hash support --- res/builtins.lisp | 8 ++++++++ src/CoreBindings.kt | 55 ++++++++++++++++++++++++++++++++++++++++++++++++----- src/LispData.kt | 1 + test/res/test.lisp | 11 +++++++++++ 4 files changed, 70 insertions(+), 5 deletions(-) diff --git a/res/builtins.lisp b/res/builtins.lisp index d1828f6..64c59d3 100644 --- a/res/builtins.lisp +++ b/res/builtins.lisp @@ -52,3 +52,11 @@ (defun not (v) (if v false true)) (defun ^ (l r) (if l (not r) r)) (export | & not ^) + +(comment "Re-export hashes") +(def hash.new core.newhash) +(def hash.merge core.mergehash) +(def hash.get core.gethash) +(export hash.new hash.merge hash.get) + + diff --git a/src/CoreBindings.kt b/src/CoreBindings.kt index 3691744..94b3852 100644 --- a/src/CoreBindings.kt +++ b/src/CoreBindings.kt @@ -116,6 +116,7 @@ object CoreBindings { 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.LispInterpretedCallable -> ""} ${thing.argNames} ${thing.body.toSource()}>" } @@ -192,16 +193,22 @@ object CoreBindings { } 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 + } + } + 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 = when (val moduleObject = context.resolveValue(stackFrame, args[0])) { - is LispData.Atom -> moduleObject.label - is LispData.LispString -> moduleObject.string - else -> return@externalRawCall stackFrame.reportError("import needs a string or atom as argument", callsite) - } + 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 } @@ -216,6 +223,7 @@ object CoreBindings { 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.LispNumber -> LispData.Atom("number") is LispData.LispString -> LispData.Atom("string") @@ -231,6 +239,42 @@ object CoreBindings { 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() + 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) @@ -244,5 +288,6 @@ object CoreBindings { bindings.setValueLocal("core.reflect.type", reflect) bindings.setValueLocal("core.debuglog", debuglog) offerArithmeticTo(bindings) + offerHashesTo(bindings) } } \ No newline at end of file diff --git a/src/LispData.kt b/src/LispData.kt index 97fcc36..ea3af32 100644 --- a/src/LispData.kt +++ b/src/LispData.kt @@ -7,6 +7,7 @@ sealed class 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) : LispData() class LispList(val elements: List) : LispData() sealed class LispExecutable() : LispData() { abstract fun execute( diff --git a/test/res/test.lisp b/test/res/test.lisp index 19594b3..b5a9f65 100644 --- a/test/res/test.lisp +++ b/test/res/test.lisp @@ -19,3 +19,14 @@ ((test.assert-eq (| true false) true)) ((test.assert-eq (| false true) true)) ((test.assert-eq (| false false) false)))) + +(test.test "Hashes" (seq + (def funnyhash (hash.new :test1 1 :test2 2)) + ((test.assert-eq + (hash.merge funnyhash (hash.new :test1 2)) + (hash.new :test1 2 :test2 2))) + ((test.assert-eq funnyhash (hash.new :test1 1 :test2 2))) + ((test.assert-eq (hash.get funnyhash :test1) 1)) + ((test.assert-eq (hash.get funnyhash :tesst3) nil)) + )) + -- cgit