Add gensym and clean `or` macro to stepA of 19 implementations (part 3)
[jackhill/mal.git] / groovy / stepA_mal.groovy
1 import reader
2 import printer
3 import types
4 import types.MalException
5 import types.MalSymbol
6 import types.MalFunc
7 import env.Env
8 import core
9
10 // READ
11 READ = { str ->
12 reader.read_str str
13 }
14
15 // EVAL
16 macro_Q = { ast, env ->
17 if (types.list_Q(ast) &&
18 ast[0].class == MalSymbol &&
19 env.find(ast[0])) {
20 def obj = env.get(ast[0])
21 if (obj instanceof MalFunc && obj.ismacro) {
22 return true
23 }
24 }
25 return false
26 }
27 macroexpand = { ast, env ->
28 while (macro_Q(ast, env)) {
29 def mac = env.get(ast[0])
30 ast = mac(ast.drop(1))
31 }
32 return ast
33 }
34
35 pair_Q = { ast -> types.sequential_Q(ast) && ast.size() > 0}
36 quasiquote = { ast ->
37 if (! pair_Q(ast)) {
38 [new MalSymbol("quote"), ast]
39 } else if (ast[0] != null &&
40 ast[0].class == MalSymbol &&
41 ast[0].value == "unquote") {
42 ast[1]
43 } else if (pair_Q(ast[0]) && ast[0][0].class == MalSymbol &&
44 ast[0][0].value == "splice-unquote") {
45 [new MalSymbol("concat"), ast[0][1], quasiquote(ast.drop(1))]
46 } else {
47 [new MalSymbol("cons"), quasiquote(ast[0]), quasiquote(ast.drop(1))]
48 }
49 }
50
51 eval_ast = { ast, env ->
52 switch (ast) {
53 case MalSymbol: return env.get(ast);
54 case List: return types.vector_Q(ast) ?
55 types.vector(ast.collect { EVAL(it,env) }) :
56 ast.collect { EVAL(it,env) }
57 case Map: def new_hm = [:]
58 ast.each { k,v ->
59 new_hm[EVAL(k, env)] = EVAL(v, env)
60 }
61 return new_hm
62 default: return ast
63 }
64 }
65
66 EVAL = { ast, env ->
67 while (true) {
68 //println("EVAL: ${printer.pr_str(ast,true)}")
69 if (! types.list_Q(ast)) return eval_ast(ast, env)
70
71 ast = macroexpand(ast, env)
72 if (! types.list_Q(ast)) return ast
73
74 switch (ast[0]) {
75 case { it instanceof MalSymbol && it.value == "def!" }:
76 return env.set(ast[1], EVAL(ast[2], env))
77 case { it instanceof MalSymbol && it.value == "let*" }:
78 def let_env = new Env(env)
79 for (int i=0; i < ast[1].size(); i += 2) {
80 let_env.set(ast[1][i], EVAL(ast[1][i+1], let_env))
81 }
82 env = let_env
83 ast = ast[2]
84 break // TCO
85 case { it instanceof MalSymbol && it.value == "quote" }:
86 return ast[1]
87 case { it instanceof MalSymbol && it.value == "quasiquote" }:
88 ast = quasiquote(ast[1])
89 break // TCO
90 case { it instanceof MalSymbol && it.value == "defmacro!" }:
91 def f = EVAL(ast[2], env)
92 f.ismacro = true
93 return env.set(ast[1], f)
94 case { it instanceof MalSymbol && it.value == "macroexpand" }:
95 return macroexpand(ast[1], env)
96 case { it instanceof MalSymbol && it.value == "try*" }:
97 try {
98 return EVAL(ast[1], env)
99 } catch(exc) {
100 if (ast.size() > 2 &&
101 ast[2][0] instanceof MalSymbol &&
102 ast[2][0].value == "catch*") {
103 def e = null
104 if (exc instanceof MalException) {
105 e = exc.obj
106 } else {
107 e = exc.message
108 }
109 return EVAL(ast[2][2], new Env(env, [ast[2][1]], [e]))
110 } else {
111 throw exc
112 }
113 }
114 case { it instanceof MalSymbol && it.value == "do" }:
115 ast.size() > 2 ? eval_ast(ast[1..-2], env) : null
116 ast = ast[-1]
117 break // TCO
118 case { it instanceof MalSymbol && it.value == "if" }:
119 def cond = EVAL(ast[1], env)
120 if (cond == false || cond == null) {
121 if (ast.size > 3) {
122 ast = ast[3]
123 break // TCO
124 } else {
125 return null
126 }
127 } else {
128 ast = ast[2]
129 break // TCO
130 }
131 case { it instanceof MalSymbol && it.value == "fn*" }:
132 return new MalFunc(EVAL, ast[2], env, ast[1])
133 default:
134 def el = eval_ast(ast, env)
135 def (f, args) = [el[0], el.drop(1)]
136 if (f instanceof MalFunc) {
137 env = new Env(f.env, f.params, args)
138 ast = f.ast
139 break // TCO
140 } else {
141 return f(args)
142 }
143 }
144 }
145 }
146
147 // PRINT
148 PRINT = { exp ->
149 printer.pr_str exp, true
150 }
151
152 // REPL
153 repl_env = new Env();
154 REP = { str ->
155 PRINT(EVAL(READ(str), repl_env))
156 }
157
158 // core.EXT: defined using Groovy
159 core.ns.each { k,v ->
160 repl_env.set(new MalSymbol(k), v)
161 }
162 repl_env.set(new MalSymbol("eval"), { a -> EVAL(a[0], repl_env)})
163 repl_env.set(new MalSymbol("*ARGV*"), this.args as List)
164
165 // core.mal: defined using mal itself
166 REP("(def! *host-language* \"groovy\")")
167 REP("(def! not (fn* (a) (if a false true)))")
168 REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
169 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)))))))");
170 REP("(def! *gensym-counter* (atom 0))");
171 REP("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))");
172 REP("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))");
173
174
175 if (this.args.size() > 0) {
176 repl_env.set(new MalSymbol("*ARGV*"), this.args.drop(1) as List)
177 REP("(load-file \"${this.args[0]}\")")
178 System.exit(0)
179 }
180
181 REP("(println (str \"Mal [\" *host-language* \"]\"))")
182 while (true) {
183 line = System.console().readLine 'user> '
184 if (line == null) {
185 break;
186 }
187 try {
188 println REP(line)
189 } catch(MalException ex) {
190 println "Error: ${ex.message}"
191 } catch(StackOverflowError ex) {
192 println "Error: ${ex}"
193 } catch(ex) {
194 println "Error: $ex"
195 ex.printStackTrace()
196 }
197 }