tests: make throw of non-strings optional/soft.
[jackhill/mal.git] / vimscript / step5_tco.vim
1 source readline.vim
2 source types.vim
3 source reader.vim
4 source printer.vim
5 source env.vim
6 source core.vim
7
8 function READ(str)
9 return ReadStr(a:str)
10 endfunction
11
12 function EvalAst(ast, env)
13 if SymbolQ(a:ast)
14 let varname = ObjValue(a:ast)
15 return a:env.get(varname)
16 elseif ListQ(a:ast)
17 let ret = []
18 for e in ObjValue(a:ast)
19 call add(ret, EVAL(e, a:env))
20 endfor
21 return ListNew(ret)
22 elseif VectorQ(a:ast)
23 let ret = []
24 for e in ObjValue(a:ast)
25 call add(ret, EVAL(e, a:env))
26 endfor
27 return VectorNew(ret)
28 elseif HashQ(a:ast)
29 let ret = {}
30 for [k,v] in items(ObjValue(a:ast))
31 let keyobj = HashParseKey(k)
32 let newkey = EVAL(keyobj, a:env)
33 let newval = EVAL(v, a:env)
34 let keystring = HashMakeKey(newkey)
35 let ret[keystring] = newval
36 endfor
37 return HashNew(ret)
38 else
39 return a:ast
40 end
41 endfunction
42
43 function EVAL(ast, env)
44 let ast = a:ast
45 let env = a:env
46
47 while 1
48 if !ListQ(ast)
49 return EvalAst(ast, env)
50 end
51 if EmptyQ(ast)
52 return ast
53 endif
54
55 let first = ListFirst(ast)
56 let first_symbol = SymbolQ(first) ? ObjValue(first) : ""
57 if first_symbol == "def!"
58 let a1 = ObjValue(ast)[1]
59 let a2 = ObjValue(ast)[2]
60 let ret = env.set(ObjValue(a1), EVAL(a2, env))
61 return ret
62 elseif first_symbol == "let*"
63 let a1 = ObjValue(ast)[1]
64 let a2 = ObjValue(ast)[2]
65 let env = NewEnv(env)
66 let let_binds = ObjValue(a1)
67 let i = 0
68 while i < len(let_binds)
69 call env.set(ObjValue(let_binds[i]), EVAL(let_binds[i+1], env))
70 let i = i + 2
71 endwhile
72 let ast = a2
73 " TCO
74 elseif first_symbol == "if"
75 let condvalue = EVAL(ObjValue(ast)[1], env)
76 if FalseQ(condvalue) || NilQ(condvalue)
77 if len(ObjValue(ast)) < 4
78 return g:MalNil
79 else
80 let ast = ObjValue(ast)[3]
81 endif
82 else
83 let ast = ObjValue(ast)[2]
84 endif
85 " TCO
86 elseif first_symbol == "do"
87 let astlist = ObjValue(ast)
88 call EvalAst(ListNew(astlist[1:-2]), env)
89 let ast = astlist[-1]
90 " TCO
91 elseif first_symbol == "fn*"
92 let fn = NewFn(ListNth(ast, 2), env, ListNth(ast, 1))
93 return fn
94 else
95 " apply list
96 let el = EvalAst(ast, env)
97 let funcobj = ListFirst(el)
98 let args = ListRest(el)
99 if NativeFunctionQ(funcobj)
100 return NativeFuncInvoke(funcobj, args)
101 elseif FunctionQ(funcobj)
102 let fn = ObjValue(funcobj)
103 let ast = fn.ast
104 let env = NewEnvWithBinds(fn.env, fn.params, args)
105 " TCO
106 else
107 throw "Not a function"
108 endif
109 endif
110 endwhile
111 endfunction
112
113 function PRINT(exp)
114 return PrStr(a:exp, 1)
115 endfunction
116
117 function REP(str, env)
118 return PRINT(EVAL(READ(a:str), a:env))
119 endfunction
120
121 set maxfuncdepth=10000
122 let repl_env = NewEnv("")
123
124 for [k, v] in items(CoreNs)
125 call repl_env.set(k, v)
126 endfor
127
128 call REP("(def! not (fn* (a) (if a false true)))", repl_env)
129
130 while 1
131 let [eof, line] = Readline("user> ")
132 if eof
133 break
134 endif
135 if line == ""
136 continue
137 endif
138 try
139 call PrintLn(REP(line, repl_env))
140 catch
141 call PrintLn("Error: " . v:exception)
142 endtry
143 endwhile
144 qall!