From 2ec7fa0b5701a3eca23bcddc266ea96d61f9bcdc Mon Sep 17 00:00:00 2001 From: Javier Fernandez-Ivern Date: Mon, 26 Oct 2015 10:37:17 -0500 Subject: [PATCH] Implemented conj, (badly) implemented meta--needs to work for all objects and not mutate them --- kotlin/Makefile | 2 +- kotlin/src/mal/core.kt | 5 +- kotlin/src/mal/reader.kt | 13 +++ kotlin/src/mal/stepA_mal.kt | 188 ++++++++++++++++++++++++++++++++++++ kotlin/src/mal/types.kt | 14 +++ 5 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 kotlin/src/mal/stepA_mal.kt diff --git a/kotlin/Makefile b/kotlin/Makefile index 40c28875..a59a6f78 100644 --- a/kotlin/Makefile +++ b/kotlin/Makefile @@ -1,7 +1,7 @@ SOURCES = reader.kt printer.kt types.kt env.kt core.kt readline.kt SRCS = step0_repl.kt step1_read_print.kt step2_eval.kt step3_env.kt step4_if_fn_do.kt \ - step5_tco.kt step6_file.kt step7_quote.kt step8_macros.kt step9_try.kt + step5_tco.kt step6_file.kt step7_quote.kt step8_macros.kt step9_try.kt stepA_mal.kt JARS = $(SRCS:%.kt=%.jar) all: $(JARS) diff --git a/kotlin/src/mal/core.kt b/kotlin/src/mal/core.kt index c60696d5..869ec1aa 100644 --- a/kotlin/src/mal/core.kt +++ b/kotlin/src/mal/core.kt @@ -160,7 +160,10 @@ val ns = hashMapOf( val seq = a.nth(0) as? ISeq if (seq != null) MalInteger(seq.seq().count()) else ZERO })), - Pair(MalSymbol("sequential?"), MalFunction({ a: ISeq -> if (a.nth(0) is ISeq) TRUE else FALSE })) + Pair(MalSymbol("sequential?"), MalFunction({ a: ISeq -> if (a.nth(0) is ISeq) TRUE else FALSE })), + + Pair(MalSymbol("meta"), MalFunction({ a: ISeq -> (a.first() as? MalFunction)?.metadata ?: NIL })), + Pair(MalSymbol("conj"), MalFunction({ a: ISeq -> (a.first() as ISeq).conj(a.rest()) })) ) fun pairwiseEquals(s: ISeq): MalConstant = diff --git a/kotlin/src/mal/reader.kt b/kotlin/src/mal/reader.kt index 3031a175..d1721f73 100644 --- a/kotlin/src/mal/reader.kt +++ b/kotlin/src/mal/reader.kt @@ -46,6 +46,19 @@ fun read_form(reader: Reader): MalType = "`" -> read_shorthand(reader, "quasiquote") "~" -> read_shorthand(reader, "unquote") "~@" -> read_shorthand(reader, "splice-unquote") + "^" -> { + reader.next() + + val meta = read_form(reader) + val obj = read_form(reader) + + val list = MalList() + list.conj_BANG(MalSymbol("with-meta")) + list.conj_BANG(obj) + list.conj_BANG(meta) + + list + } else -> read_atom(reader) } diff --git a/kotlin/src/mal/stepA_mal.kt b/kotlin/src/mal/stepA_mal.kt new file mode 100644 index 00000000..dbf63492 --- /dev/null +++ b/kotlin/src/mal/stepA_mal.kt @@ -0,0 +1,188 @@ +package mal + +fun read(input: String?): MalType = read_str(input) + +fun eval(_ast: MalType, _env: Env): MalType { + var ast = _ast + var env = _env + + while (true) { + ast = macroexpand(ast, env) + + if (ast is MalList) { + val first = ast.first() + + if (first is MalSymbol && first.value == "def!") { + return env.set(ast.nth(1) as MalSymbol, eval(ast.nth(2), env)) + } else if (first is MalSymbol && first.value == "let*") { + val childEnv = Env(env) + val bindings = ast.nth(1) as? ISeq ?: throw MalException("expected sequence as the first parameter to let*") + + val it = bindings.seq().iterator() + while (it.hasNext()) { + val key = it.next() + if (!it.hasNext()) throw MalException("odd number of binding elements in let*") + childEnv.set(key as MalSymbol, eval(it.next(), childEnv)) + } + + env = childEnv + ast = ast.nth(2) + } else if (first is MalSymbol && first.value == "fn*") { + val binds = ast.nth(1) as? ISeq ?: throw MalException("fn* requires a binding list as first parameter") + val params = binds.seq().filterIsInstance() // TODO error if any non-symbols? + val body = ast.nth(2) + + return MalFnFunction(body, params, env, { s: ISeq -> + eval(body, Env(env, params, s.seq())) + }) + } else if (first is MalSymbol && first.value == "do") { + eval_ast(ast.slice(1, ast.seq().count() - 1), env) + ast = ast.seq().last() + } else if (first is MalSymbol && first.value == "if") { + val check = eval(ast.nth(1), env) + + if (check != NIL && check != FALSE) { + ast = ast.nth(2) + } else if (ast.seq().asSequence().count() > 3) { + ast = ast.nth(3) + } else return NIL + } else if (first is MalSymbol && first.value == "quote") { + return ast.nth(1) + } else if (first is MalSymbol && first.value == "quasiquote") { + ast = quasiquote(ast.nth(1)) + } else if (first is MalSymbol && first.value == "defmacro!") { + val macro = eval(ast.nth(2), env) as MalFunction + macro.is_macro = true + return env.set(ast.nth(1) as MalSymbol, macro) + } else if (first is MalSymbol && first.value == "macroexpand") { + return macroexpand(ast.nth(1), env) + } else if (first is MalSymbol && first.value == "try*") { + val body = ast.nth(1) + try { + return eval(body, env) + } catch (e: Exception) { + val thrown = if (e is MalException) e else MalException(e.message) + val symbol = (ast.nth(2) as MalList).nth(1) as MalSymbol + val catchBody = (ast.nth(2) as MalList).nth(2) + val catchEnv = Env(env) + catchEnv.set(symbol, thrown) + return eval(catchBody, catchEnv) + } + } else if (first is MalSymbol && first.value == "with-meta") { + val function = eval(ast.nth(1), env) as MalFunction + function.metadata = eval(ast.nth(2), env) + return function + } else { + val evaluated = eval_ast(ast, env) as ISeq + val firstEval = evaluated.first() + + if (firstEval is MalFnFunction) { + ast = firstEval.ast + env = Env(firstEval.env, firstEval.params, evaluated.rest().seq()) + } else if (firstEval is MalFunction) { + return firstEval.apply(evaluated.rest()) + } else throw MalException("cannot execute non-function") + } + } else return eval_ast(ast, env) + } +} + +fun eval_ast(ast: MalType, env: Env): MalType = + if (ast is MalSymbol) { + env.get(ast) + } else if (ast is MalList) { + ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a }) + } else if (ast is MalVector) { + ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a }) + } else if (ast is MalHashMap) { + ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a }) + } else ast + +private fun is_pair(ast: MalType): Boolean = ast is ISeq && ast.seq().any() + +private fun quasiquote(ast: MalType): MalType { + if (!is_pair(ast)) { + val quoted = MalList() + quoted.conj_BANG(MalSymbol("quote")) + quoted.conj_BANG(ast) + return quoted + } + + val seq = ast as ISeq + var first = seq.first() + + if ((first as? MalSymbol)?.value == "unquote") { + return seq.nth(1) + } + + if (is_pair(first) && ((first as ISeq).first() as? MalSymbol)?.value == "splice-unquote") { + val spliced = MalList() + spliced.conj_BANG(MalSymbol("concat")) + spliced.conj_BANG((first as ISeq).nth(1)) + spliced.conj_BANG(quasiquote(MalList(seq.seq().drop(1).toLinkedList()))) + return spliced + } + + val consed = MalList() + consed.conj_BANG(MalSymbol("cons")) + consed.conj_BANG(quasiquote(ast.first())) + consed.conj_BANG(quasiquote(MalList(seq.seq().drop(1).toLinkedList()))) + return consed +} + +private fun is_macro_call(ast: MalType, env: Env): Boolean { + val symbol = (ast as? MalList)?.first() as? MalSymbol ?: return false + val function = env.find(symbol) as? MalFunction ?: return false + return function.is_macro +} + +private fun macroexpand(_ast: MalType, env: Env): MalType { + var ast = _ast + while (is_macro_call(ast, env)) { + val symbol = (ast as MalList).first() as MalSymbol + val function = env.find(symbol) as MalFunction + ast = function.apply(ast.rest()) + } + return ast +} + +fun print(result: MalType) = pr_str(result, print_readably = true) + +fun rep(input: String, env: Env): String = + print(eval(read(input), env)) + +fun main(args: Array) { + val repl_env = Env() + ns.forEach({ it -> repl_env.set(it.key, it.value) }) + + repl_env.set(MalSymbol("*host-language*"), MalString("kotlin")) + + // Need to cast the strings explicitly to MalType to get this to compile. Looks like a bug in kotlinc, + // and it results in a warning. + repl_env.set(MalSymbol("*ARGV*"), MalList(args.drop(1).map({ it -> MalString(it) as MalType }).toLinkedList())) + repl_env.set(MalSymbol("eval"), MalFunction({ a: ISeq -> eval(a.first(), repl_env) })) + + rep("(def! not (fn* (a) (if a false true)))", repl_env) + rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env) + rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", repl_env) + rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", repl_env) + + if (args.any()) { + rep("(load-file \"${args[0]}\")", repl_env) + return + } + + while (true) { + val input = readline("user> ") ?: break + + try { + println(rep(input, repl_env)) + } catch (e: MalContinue) { + } catch (e: MalException) { + println("Error: " + e.message) + } catch (t: Throwable) { + println("Uncaught " + t + ": " + t.message) + t.printStackTrace() + } + } +} diff --git a/kotlin/src/mal/types.kt b/kotlin/src/mal/types.kt index c7a94887..ca81093d 100644 --- a/kotlin/src/mal/types.kt +++ b/kotlin/src/mal/types.kt @@ -41,7 +41,9 @@ interface ILambda : MalType { } open class MalFunction(val lambda: (ISeq) -> MalType) : MalType, ILambda { + // TODO make this stuff immutable? var is_macro: Boolean = false + var metadata: MalType = NIL override fun apply(seq: ISeq): MalType = lambda(seq) } @@ -54,8 +56,10 @@ interface ISeq : MalType { fun rest(): ISeq fun nth(n: Int): MalType fun slice(fromIndex: Int, toIndex: Int): ISeq + fun conj(s: ISeq): ISeq } +// TODO could we get rid of this and make conj work on immutables? interface IMutableSeq : ISeq { fun conj_BANG(form: MalType) } @@ -68,6 +72,8 @@ class MalSequence(val elements : Sequence) : MalType, ISeq { override fun slice(fromIndex: Int, toIndex: Int): MalList = MalList(elements.toLinkedList().subList(fromIndex, toIndex)) + + override fun conj(s: ISeq): ISeq = MalList(elements.toLinkedList()).conj(s) } class MalList(val elements: MutableList) : MalType, IMutableSeq { @@ -90,6 +96,12 @@ class MalList(val elements: MutableList) : MalType, IMutableSeq { override fun slice(fromIndex: Int, toIndex: Int): MalList = MalList(elements.subList(fromIndex, toIndex)) + + override fun conj(s: ISeq): ISeq { + val list = LinkedList(elements) + s.seq().forEach({ it -> list.addFirst(it) }) + return MalList(list) + } } class MalVector(val elements: MutableList) : MalType, IMutableSeq { @@ -112,6 +124,8 @@ class MalVector(val elements: MutableList) : MalType, IMutableSeq { override fun slice(fromIndex: Int, toIndex: Int): MalVector = MalVector(elements.subList(fromIndex, toIndex)) + + override fun conj(s: ISeq): ISeq = MalVector(elements.plus(s.seq()).toArrayList()) } class MalHashMap() : MalType { -- 2.20.1