properly implement tco and add step7:quote
[jackhill/mal.git] / groovy / step5_tco.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 eval_ast = { ast, env ->
17 switch (ast) {
18 case MalSymbol: return env.get(ast);
19 case List: return types.vector_Q(ast) ?
20 types.vector(ast.collect { EVAL(it,env) }) :
21 ast.collect { EVAL(it,env) }
22 case Map: def new_hm = [:]
23 ast.each { k,v ->
24 new_hm[EVAL(k, env)] = EVAL(v, env)
25 }
26 return new_hm
27 default: return ast
28 }
29 }
30
31 EVAL = { ast, env ->
32 while (true) {
33 //println("EVAL: ${printer.pr_str(ast,true)}")
34 if (! types.list_Q(ast)) return eval_ast(ast, env)
35 if (ast.size() == 0) return ast
36
37 switch (ast[0]) {
38 case { it instanceof MalSymbol && it.value == "def!" }:
39 return env.set(ast[1], EVAL(ast[2], env))
40 case { it instanceof MalSymbol && it.value == "let*" }:
41 def let_env = new Env(env)
42 for (int i=0; i < ast[1].size(); i += 2) {
43 let_env.set(ast[1][i], EVAL(ast[1][i+1], let_env))
44 }
45 env = let_env
46 ast = ast[2]
47 break // TCO
48 case { it instanceof MalSymbol && it.value == "do" }:
49 ast.size() > 2 ? eval_ast(ast[1..-2], env) : null
50 ast = ast[-1]
51 break // TCO
52 case { it instanceof MalSymbol && it.value == "if" }:
53 def cond = EVAL(ast[1], env)
54 if (cond == false || cond == null) {
55 if (ast.size > 3) {
56 ast = ast[3]
57 break // TCO
58 } else {
59 return null
60 }
61 } else {
62 ast = ast[2]
63 break // TCO
64 }
65 case { it instanceof MalSymbol && it.value == "fn*" }:
66 return new MalFunc(EVAL, ast[2], env, ast[1])
67 default:
68 def el = eval_ast(ast, env)
69 def (f, args) = [el[0], el.size() > 1 ? el[1..-1] : []]
70 if (f instanceof MalFunc) {
71 env = new Env(f.env, f.params, args)
72 ast = f.ast
73 break // TCO
74 } else {
75 return f(args)
76 }
77 }
78 }
79 }
80
81 // PRINT
82 PRINT = { exp ->
83 printer.pr_str exp, true
84 }
85
86 // REPL
87 repl_env = new Env();
88 REP = { str ->
89 PRINT(EVAL(READ(str), repl_env))
90 }
91
92 // core.EXT: defined using Groovy
93 core.ns.each { k,v ->
94 repl_env.set(new MalSymbol(k), v)
95 }
96
97 // core.mal: defined using mal itself
98 REP("(def! not (fn* (a) (if a false true)))")
99
100
101 while (true) {
102 line = System.console().readLine 'user> '
103 if (line == null) {
104 break;
105 }
106 try {
107 println REP(line)
108 } catch(MalException ex) {
109 println "Error: ${printer.pr_str(ex.obj, true)}"
110 } catch(StackOverflowError ex) {
111 println "Error: ${ex}"
112 } catch(ex) {
113 println "Error: $ex"
114 ex.printStackTrace()
115 }
116 }