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