crystal: Fix exception on literal empty list in step3
[jackhill/mal.git] / groovy / step9_try.groovy
CommitLineData
a9cd6543
JM
1import reader
2import printer
3import types
4import types.MalException
5import types.MalSymbol
6import types.MalFunc
7import env.Env
8import core
9
10// READ
11READ = { str ->
12 reader.read_str str
13}
14
15// EVAL
16macro_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}
27macroexpand = { 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
35pair_Q = { ast -> types.sequential_Q(ast) && ast.size() > 0}
36quasiquote = { ast ->
37 if (! pair_Q(ast)) {
38 [new MalSymbol("quote"), ast]
c963be7a
JM
39 } else if (ast[0] != null &&
40 ast[0].class == MalSymbol &&
a9cd6543
JM
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
51eval_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
66EVAL = { 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)
44aef1f4 72 if (! types.list_Q(ast)) return eval_ast(ast, env)
a9cd6543
JM
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
148PRINT = { exp ->
149 printer.pr_str exp, true
150}
151
152// REPL
153repl_env = new Env();
154REP = { str ->
155 PRINT(EVAL(READ(str), repl_env))
156}
157
158// core.EXT: defined using Groovy
159core.ns.each { k,v ->
160 repl_env.set(new MalSymbol(k), v)
161}
162repl_env.set(new MalSymbol("eval"), { a -> EVAL(a[0], repl_env)})
163repl_env.set(new MalSymbol("*ARGV*"), this.args as List)
164
165// core.mal: defined using mal itself
166REP("(def! not (fn* (a) (if a false true)))")
167REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
168REP("(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)))))))");
169REP("(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))))))))");
170
171
172if (this.args.size() > 0) {
173 repl_env.set(new MalSymbol("*ARGV*"), this.args.drop(1) as List)
174 REP("(load-file \"${this.args[0]}\")")
175 System.exit(0)
176}
177
178while (true) {
179 line = System.console().readLine 'user> '
180 if (line == null) {
181 break;
182 }
183 try {
184 println REP(line)
185 } catch(MalException ex) {
186 println "Error: ${ex.message}"
187 } catch(StackOverflowError ex) {
188 println "Error: ${ex}"
189 } catch(ex) {
190 println "Error: $ex"
191 ex.printStackTrace()
192 }
193}