properly implement tco and add step7:quote
[jackhill/mal.git] / groovy / step8_macros.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.size() > 0 &&
19 ast[0].class == MalSymbol &&
20 env.find(ast[0])) {
21 def obj = env.get(ast[0])
22 if (obj instanceof MalFunc && obj.ismacro) {
23 return true
24 }
25 }
26 return false
27 }
28 macroexpand = { ast, env ->
29 while (macro_Q(ast, env)) {
30 def mac = env.get(ast[0])
31 ast = mac(ast.drop(1))
32 }
33 return ast
34 }
35
36 pair_Q = { ast -> types.sequential_Q(ast) && ast.size() > 0}
37 quasiquote = { ast ->
38 if (! pair_Q(ast)) {
39 [new MalSymbol("quote"), ast]
40 } else if (ast[0] != null &&
41 ast[0].class == MalSymbol &&
42 ast[0].value == "unquote") {
43 ast[1]
44 } else if (pair_Q(ast[0]) && ast[0][0].class == MalSymbol &&
45 ast[0][0].value == "splice-unquote") {
46 [new MalSymbol("concat"), ast[0][1], quasiquote(ast.drop(1))]
47 } else {
48 [new MalSymbol("cons"), quasiquote(ast[0]), quasiquote(ast.drop(1))]
49 }
50 }
51
52 eval_ast = { ast, env ->
53 switch (ast) {
54 case MalSymbol: return env.get(ast);
55 case List: return types.vector_Q(ast) ?
56 types.vector(ast.collect { EVAL(it,env) }) :
57 ast.collect { EVAL(it,env) }
58 case Map: def new_hm = [:]
59 ast.each { k,v ->
60 new_hm[EVAL(k, env)] = EVAL(v, env)
61 }
62 return new_hm
63 default: return ast
64 }
65 }
66
67 EVAL = { ast, env ->
68 while (true) {
69 //println("EVAL: ${printer.pr_str(ast,true)}")
70 if (! types.list_Q(ast)) return eval_ast(ast, env)
71
72 ast = macroexpand(ast, env)
73 if (! types.list_Q(ast)) return eval_ast(ast, env)
74 if (ast.size() == 0) return ast
75
76 switch (ast[0]) {
77 case { it instanceof MalSymbol && it.value == "def!" }:
78 return env.set(ast[1], EVAL(ast[2], env))
79 case { it instanceof MalSymbol && it.value == "let*" }:
80 def let_env = new Env(env)
81 for (int i=0; i < ast[1].size(); i += 2) {
82 let_env.set(ast[1][i], EVAL(ast[1][i+1], let_env))
83 }
84 env = let_env
85 ast = ast[2]
86 break // TCO
87 case { it instanceof MalSymbol && it.value == "quote" }:
88 return ast[1]
89 case { it instanceof MalSymbol && it.value == "quasiquote" }:
90 ast = quasiquote(ast[1])
91 break // TCO
92 case { it instanceof MalSymbol && it.value == "defmacro!" }:
93 def f = EVAL(ast[2], env)
94 f.ismacro = true
95 return env.set(ast[1], f)
96 case { it instanceof MalSymbol && it.value == "macroexpand" }:
97 return macroexpand(ast[1], env)
98 case { it instanceof MalSymbol && it.value == "do" }:
99 ast.size() > 2 ? eval_ast(ast[1..-2], env) : null
100 ast = ast[-1]
101 break // TCO
102 case { it instanceof MalSymbol && it.value == "if" }:
103 def cond = EVAL(ast[1], env)
104 if (cond == false || cond == null) {
105 if (ast.size > 3) {
106 ast = ast[3]
107 break // TCO
108 } else {
109 return null
110 }
111 } else {
112 ast = ast[2]
113 break // TCO
114 }
115 case { it instanceof MalSymbol && it.value == "fn*" }:
116 return new MalFunc(EVAL, ast[2], env, ast[1])
117 default:
118 def el = eval_ast(ast, env)
119 def (f, args) = [el[0], el.drop(1)]
120 if (f instanceof MalFunc) {
121 env = new Env(f.env, f.params, args)
122 ast = f.ast
123 break // TCO
124 } else {
125 return f(args)
126 }
127 }
128 }
129 }
130
131 // PRINT
132 PRINT = { exp ->
133 printer.pr_str exp, true
134 }
135
136 // REPL
137 repl_env = new Env();
138 REP = { str ->
139 PRINT(EVAL(READ(str), repl_env))
140 }
141
142 // core.EXT: defined using Groovy
143 core.ns.each { k,v ->
144 repl_env.set(new MalSymbol(k), v)
145 }
146 repl_env.set(new MalSymbol("eval"), { a -> EVAL(a[0], repl_env)})
147 repl_env.set(new MalSymbol("*ARGV*"), this.args as List)
148
149 // core.mal: defined using mal itself
150 REP("(def! not (fn* (a) (if a false true)))")
151 REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))")
152 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)))))))");
153
154
155 if (this.args.size() > 0) {
156 repl_env.set(new MalSymbol("*ARGV*"), this.args.drop(1) as List)
157 REP("(load-file \"${this.args[0]}\")")
158 System.exit(0)
159 }
160
161 while (true) {
162 line = System.console().readLine 'user> '
163 if (line == null) {
164 break;
165 }
166 try {
167 println REP(line)
168 } catch(MalException ex) {
169 println "Error: ${printer.pr_str(ex.obj, true)}"
170 } catch(StackOverflowError ex) {
171 println "Error: ${ex}"
172 } catch(ex) {
173 println "Error: $ex"
174 ex.printStackTrace()
175 }
176 }