Cleanup, added stats targets
[jackhill/mal.git] / kotlin / src / mal / step9_try.kt
1 package mal
2
3 fun read(input: String?): MalType = read_str(input)
4
5 fun eval(_ast: MalType, _env: Env): MalType {
6 var ast = _ast
7 var env = _env
8
9 while (true) {
10 ast = macroexpand(ast, env)
11
12 if (ast is MalList) {
13 val first = ast.first()
14
15 if (first is MalSymbol && first.value == "def!") {
16 return env.set(ast.nth(1) as MalSymbol, eval(ast.nth(2), env))
17 } else if (first is MalSymbol && first.value == "let*") {
18 val childEnv = Env(env)
19 val bindings = ast.nth(1) as? ISeq ?: throw MalException("expected sequence as the first parameter to let*")
20
21 val it = bindings.seq().iterator()
22 while (it.hasNext()) {
23 val key = it.next()
24 if (!it.hasNext()) throw MalException("odd number of binding elements in let*")
25 childEnv.set(key as MalSymbol, eval(it.next(), childEnv))
26 }
27
28 env = childEnv
29 ast = ast.nth(2)
30 } else if (first is MalSymbol && first.value == "fn*") {
31 val binds = ast.nth(1) as? ISeq ?: throw MalException("fn* requires a binding list as first parameter")
32 val params = binds.seq().filterIsInstance<MalSymbol>()
33 val body = ast.nth(2)
34
35 return MalFnFunction(body, params, env, { s: ISeq ->
36 eval(body, Env(env, params, s.seq()))
37 })
38 } else if (first is MalSymbol && first.value == "do") {
39 eval_ast(ast.slice(1, ast.seq().count() - 1), env)
40 ast = ast.seq().last()
41 } else if (first is MalSymbol && first.value == "if") {
42 val check = eval(ast.nth(1), env)
43
44 if (check != NIL && check != FALSE) {
45 ast = ast.nth(2)
46 } else if (ast.seq().asSequence().count() > 3) {
47 ast = ast.nth(3)
48 } else return NIL
49 } else if (first is MalSymbol && first.value == "quote") {
50 return ast.nth(1)
51 } else if (first is MalSymbol && first.value == "quasiquote") {
52 ast = quasiquote(ast.nth(1))
53 } else if (first is MalSymbol && first.value == "defmacro!") {
54 val macro = eval(ast.nth(2), env) as MalFunction
55 macro.is_macro = true
56 return env.set(ast.nth(1) as MalSymbol, macro)
57 } else if (first is MalSymbol && first.value == "macroexpand") {
58 return macroexpand(ast.nth(1), env)
59 } else if (first is MalSymbol && first.value == "try*") {
60 val body = ast.nth(1)
61 try {
62 return eval(body, env)
63 } catch (e: Exception) {
64 val thrown = if (e is MalException) e else MalException(e.message)
65 val symbol = (ast.nth(2) as MalList).nth(1) as MalSymbol
66 val catchBody = (ast.nth(2) as MalList).nth(2)
67 val catchEnv = Env(env)
68 catchEnv.set(symbol, thrown)
69 return eval(catchBody, catchEnv)
70 }
71 } else {
72 val evaluated = eval_ast(ast, env) as ISeq
73 val firstEval = evaluated.first()
74
75 if (firstEval is MalFnFunction) {
76 ast = firstEval.ast
77 env = Env(firstEval.env, firstEval.params, evaluated.rest().seq())
78 } else if (firstEval is MalFunction) {
79 return firstEval.apply(evaluated.rest())
80 } else throw MalException("cannot execute non-function")
81 }
82 } else return eval_ast(ast, env)
83 }
84 }
85
86 fun eval_ast(ast: MalType, env: Env): MalType =
87 if (ast is MalSymbol) {
88 env.get(ast)
89 } else if (ast is MalList) {
90 ast.elements.fold(MalList(), { a, b -> a.conj_BANG(eval(b, env)); a })
91 } else if (ast is MalVector) {
92 ast.elements.fold(MalVector(), { a, b -> a.conj_BANG(eval(b, env)); a })
93 } else if (ast is MalHashMap) {
94 ast.elements.entries.fold(MalHashMap(), { a, b -> a.assoc_BANG(b.key, eval(b.value, env)); a })
95 } else ast
96
97 private fun is_pair(ast: MalType): Boolean = ast is ISeq && ast.seq().any()
98
99 private fun quasiquote(ast: MalType): MalType {
100 if (!is_pair(ast)) {
101 val quoted = MalList()
102 quoted.conj_BANG(MalSymbol("quote"))
103 quoted.conj_BANG(ast)
104 return quoted
105 }
106
107 val seq = ast as ISeq
108 var first = seq.first()
109
110 if ((first as? MalSymbol)?.value == "unquote") {
111 return seq.nth(1)
112 }
113
114 if (is_pair(first) && ((first as ISeq).first() as? MalSymbol)?.value == "splice-unquote") {
115 val spliced = MalList()
116 spliced.conj_BANG(MalSymbol("concat"))
117 spliced.conj_BANG((first as ISeq).nth(1))
118 spliced.conj_BANG(quasiquote(MalList(seq.seq().drop(1).toLinkedList())))
119 return spliced
120 }
121
122 val consed = MalList()
123 consed.conj_BANG(MalSymbol("cons"))
124 consed.conj_BANG(quasiquote(ast.first()))
125 consed.conj_BANG(quasiquote(MalList(seq.seq().drop(1).toLinkedList())))
126 return consed
127 }
128
129 private fun is_macro_call(ast: MalType, env: Env): Boolean {
130 val symbol = (ast as? MalList)?.first() as? MalSymbol ?: return false
131 val function = env.find(symbol) as? MalFunction ?: return false
132 return function.is_macro
133 }
134
135 private fun macroexpand(_ast: MalType, env: Env): MalType {
136 var ast = _ast
137 while (is_macro_call(ast, env)) {
138 val symbol = (ast as MalList).first() as MalSymbol
139 val function = env.find(symbol) as MalFunction
140 ast = function.apply(ast.rest())
141 }
142 return ast
143 }
144
145 fun print(result: MalType) = pr_str(result, print_readably = true)
146
147 fun rep(input: String, env: Env): String =
148 print(eval(read(input), env))
149
150 fun main(args: Array<String>) {
151 val repl_env = Env()
152 ns.forEach({ it -> repl_env.set(it.key, it.value) })
153
154 // Need to cast the strings explicitly to MalType to get this to compile. Looks like a bug in kotlinc,
155 // and it results in a warning.
156 repl_env.set(MalSymbol("*ARGV*"), MalList(args.drop(1).map({ it -> MalString(it) as MalType }).toLinkedList()))
157 repl_env.set(MalSymbol("eval"), MalFunction({ a: ISeq -> eval(a.first(), repl_env) }))
158
159 rep("(def! not (fn* (a) (if a false true)))", repl_env)
160 rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env)
161 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)
162 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)
163
164 if (args.any()) {
165 rep("(load-file \"${args[0]}\")", repl_env)
166 return
167 }
168
169 while (true) {
170 val input = readline("user> ")
171
172 try {
173 println(rep(input, repl_env))
174 } catch (e: EofException) {
175 break
176 } catch (e: MalContinue) {
177 } catch (e: MalException) {
178 println("Error: " + e.message)
179 } catch (t: Throwable) {
180 println("Uncaught " + t + ": " + t.message)
181 t.printStackTrace()
182 }
183 }
184 }