Merge pull request #156 from omarrayward/explain-regexp-tokenizer
[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[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 eval_ast(ast, env)
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 == "do" }:
97 ast.size() > 2 ? eval_ast(ast[1..-2], env) : null
98 ast = ast[-1]
99 break // TCO
100 case { it instanceof MalSymbol && it.value == "if" }:
101 def cond = EVAL(ast[1], env)
102 if (cond == false || cond == null) {
103 if (ast.size > 3) {
104 ast = ast[3]
105 break // TCO
106 } else {
107 return null
108 }
109 } else {
110 ast = ast[2]
111 break // TCO
112 }
113 case { it instanceof MalSymbol && it.value == "fn*" }:
114 return new MalFunc(EVAL, ast[2], env, ast[1])
115 default:
116 def el = eval_ast(ast, env)
117 def (f, args) = [el[0], el.drop(1)]
118 if (f instanceof MalFunc) {
119 env = new Env(f.env, f.params, args)
120 ast = f.ast
121 break // TCO
122 } else {
123 return f(args)
124 }
125 }
126 }
127 }
128
129 // PRINT
130 PRINT = { exp ->
131 printer.pr_str exp, true
132 }
133
134 // REPL
135 repl_env = new Env();
136 REP = { str ->
137 PRINT(EVAL(READ(str), repl_env))
138 }
139
140 // core.EXT: defined using Groovy
141 core.ns.each { k,v ->
142 repl_env.set(new MalSymbol(k), v)
143 }
144 repl_env.set(new MalSymbol("eval"), { a -> EVAL(a[0], repl_env)})
145 repl_env.set(new MalSymbol("*ARGV*"), this.args as List)
146
147 // core.mal: defined using mal itself
148 REP("(def! not (fn* (a) (if a false true)))")
149 REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
150 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)))))))");
151 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))))))))");
152
153
154 if (this.args.size() > 0) {
155 repl_env.set(new MalSymbol("*ARGV*"), this.args.drop(1) as List)
156 REP("(load-file \"${this.args[0]}\")")
157 System.exit(0)
158 }
159
160 while (true) {
161 line = System.console().readLine 'user> '
162 if (line == null) {
163 break;
164 }
165 try {
166 println REP(line)
167 } catch(MalException ex) {
168 println "Error: ${ex.message}"
169 } catch(StackOverflowError ex) {
170 println "Error: ${ex}"
171 } catch(ex) {
172 println "Error: $ex"
173 ex.printStackTrace()
174 }
175 }