tcl: Fix exception on literal empty list
[jackhill/mal.git] / vimscript / stepA_mal.vim
CommitLineData
50a964ce
DM
1source readline.vim
2source types.vim
3source reader.vim
4source printer.vim
5source env.vim
6source core.vim
7
8let MalExceptionObj = ""
9
10function READ(str)
11 return ReadStr(a:str)
12endfunction
13
14function PairQ(obj)
15 return SequentialQ(a:obj) && !EmptyQ(a:obj)
16endfunction
17
18function Quasiquote(ast)
19 if !PairQ(a:ast)
20 return ListNew([SymbolNew("quote"), a:ast])
21 endif
22 let a0 = ListFirst(a:ast)
23 if SymbolQ(a0) && ObjValue(a0) == "unquote"
24 return ListNth(a:ast, 1)
25 elseif PairQ(a0) && SymbolQ(ListFirst(a0)) && ObjValue(ListFirst(a0)) == "splice-unquote"
26 return ListNew([SymbolNew("concat"), ListNth(a0, 1), Quasiquote(ListRest(a:ast))])
27 else
28 return ListNew([SymbolNew("cons"), Quasiquote(a0), Quasiquote(ListRest(a:ast))])
29 end
30endfunction
31
32function IsMacroCall(ast, env)
33 if !ListQ(a:ast)
34 return 0
35 endif
36 let a0 = ListFirst(a:ast)
37 if !SymbolQ(a0)
38 return 0
39 endif
40 let macroname = ObjValue(a0)
41 if empty(a:env.find(macroname))
42 return 0
43 endif
44 return MacroQ(a:env.get(macroname))
45endfunction
46
47function MacroExpand(ast, env)
48 let ast = a:ast
49 while IsMacroCall(ast, a:env)
50 let macroobj = a:env.get(ObjValue(ListFirst(ast)))
51 let macroargs = ListRest(ast)
52 let ast = FuncInvoke(macroobj, macroargs)
53 endwhile
54 return ast
55endfunction
56
57function EvalAst(ast, env)
58 if SymbolQ(a:ast)
59 let varname = ObjValue(a:ast)
60 return a:env.get(varname)
61 elseif ListQ(a:ast)
62 let ret = []
63 for e in ObjValue(a:ast)
64 call add(ret, EVAL(e, a:env))
65 endfor
66 return ListNew(ret)
67 elseif VectorQ(a:ast)
68 let ret = []
69 for e in ObjValue(a:ast)
70 call add(ret, EVAL(e, a:env))
71 endfor
72 return VectorNew(ret)
73 elseif HashQ(a:ast)
74 let ret = {}
75 for [k,v] in items(ObjValue(a:ast))
76 let keyobj = HashParseKey(k)
77 let newkey = EVAL(keyobj, a:env)
78 let newval = EVAL(v, a:env)
79 let keystring = HashMakeKey(newkey)
80 let ret[keystring] = newval
81 endfor
82 return HashNew(ret)
83 else
84 return a:ast
85 end
86endfunction
87
88function GetCatchClause(ast)
89 if ListCount(a:ast) < 3
90 return ""
91 end
92 let catch_clause = ListNth(a:ast, 2)
93 if ListFirst(catch_clause) == SymbolNew("catch*")
94 return catch_clause
95 else
96 return ""
97 end
98endfunction
99
100function EVAL(ast, env)
101 let ast = a:ast
102 let env = a:env
103
104 while 1
105 if !ListQ(ast)
106 return EvalAst(ast, env)
107 end
108
109 let ast = MacroExpand(ast, env)
110 if !ListQ(ast)
6c94cd3e 111 return EvalAst(ast, env)
50a964ce
DM
112 end
113
114 let first = ListFirst(ast)
115 let first_symbol = SymbolQ(first) ? ObjValue(first) : ""
116 if first_symbol == "def!"
117 let a1 = ObjValue(ast)[1]
118 let a2 = ObjValue(ast)[2]
119 return env.set(ObjValue(a1), EVAL(a2, env))
120 elseif first_symbol == "let*"
121 let a1 = ObjValue(ast)[1]
122 let a2 = ObjValue(ast)[2]
123 let env = NewEnv(env)
124 let let_binds = ObjValue(a1)
125 let i = 0
126 while i < len(let_binds)
127 call env.set(ObjValue(let_binds[i]), EVAL(let_binds[i+1], env))
128 let i = i + 2
129 endwhile
130 let ast = a2
131 " TCO
132 elseif first_symbol == "quote"
133 return ListNth(ast, 1)
134 elseif first_symbol == "quasiquote"
135 let ast = Quasiquote(ListNth(ast, 1))
136 " TCO
137 elseif first_symbol == "defmacro!"
138 let a1 = ListNth(ast, 1)
139 let a2 = ListNth(ast, 2)
140 let macro = MarkAsMacro(EVAL(a2, env))
141 return env.set(ObjValue(a1), macro)
142 elseif first_symbol == "macroexpand"
143 return MacroExpand(ListNth(ast, 1), env)
144 elseif first_symbol == "if"
145 let condvalue = EVAL(ObjValue(ast)[1], env)
146 if FalseQ(condvalue) || NilQ(condvalue)
147 if len(ObjValue(ast)) < 4
148 return g:MalNil
149 else
150 let ast = ObjValue(ast)[3]
151 endif
152 else
153 let ast = ObjValue(ast)[2]
154 endif
155 " TCO
156 elseif first_symbol == "try*"
157 try
158 return EVAL(ListNth(ast, 1), env)
159 catch
160 let catch_clause = GetCatchClause(ast)
161 if empty(catch_clause)
162 throw v:exception
163 endif
164
165 let exc_var = ObjValue(ListNth(catch_clause, 1))
166 if v:exception == "__MalException__"
167 let exc_value = g:MalExceptionObj
168 else
169 let exc_value = StringNew(v:exception)
170 endif
171 let catch_env = NewEnvWithBinds(env, ListNew([SymbolNew(exc_var)]), ListNew([exc_value]))
172 return EVAL(ListNth(catch_clause, 2), catch_env)
173 endtry
174 elseif first_symbol == "do"
175 let astlist = ObjValue(ast)
176 call EvalAst(ListNew(astlist[1:-2]), env)
177 let ast = astlist[-1]
178 " TCO
179 elseif first_symbol == "fn*"
180 let fn = NewFn(ListNth(ast, 2), env, ListNth(ast, 1))
181 return fn
182 elseif first_symbol == "eval"
183 let ast = EVAL(ListNth(ast, 1), env)
184 let env = env.root()
185 " TCO
186 else
187 " apply list
188 let el = EvalAst(ast, env)
189 let funcobj = ListFirst(el)
190 let args = ListRest(el)
191 if NativeFunctionQ(funcobj)
192 return NativeFuncInvoke(funcobj, args)
193 elseif FunctionQ(funcobj)
194 let fn = ObjValue(funcobj)
195 let ast = fn.ast
196 let env = NewEnvWithBinds(fn.env, fn.params, args)
197 " TCO
198 else
199 throw "Not a function"
200 endif
201 endif
202 endwhile
203endfunction
204
205function PRINT(exp)
206 return PrStr(a:exp, 1)
207endfunction
208
209function RE(str, env)
210 return EVAL(READ(a:str), a:env)
211endfunction
212
213function REP(str, env)
214 return PRINT(EVAL(READ(a:str), a:env))
215endfunction
216
217function GetArgvList()
218 let args = argv()
219 let list = []
220 for arg in args[1:]
221 call add(list, StringNew(arg))
222 endfor
223 return ListNew(list)
224endfunction
225
226set maxfuncdepth=10000
227let repl_env = NewEnv("")
228
229for [k, v] in items(CoreNs)
230 call repl_env.set(k, v)
231endfor
232
233call repl_env.set("*ARGV*", GetArgvList())
234
235call RE("(def! *host-language* \"vimscript\")", repl_env)
236call RE("(def! not (fn* (a) (if a false true)))", repl_env)
237call RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env)
238call RE("(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)))))))", repl_env)
48572759
DM
239call RE("(def! *gensym-counter* (atom 0))", repl_env)
240call RE("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))", repl_env)
241call RE("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))", repl_env)
50a964ce
DM
242
243if !empty(argv())
244 try
245 call RE('(load-file "' . argv(0) . '")', repl_env)
246 catch
247 call PrintLn("Error: " . v:exception)
248 endtry
249 qall!
250endif
251
252call REP("(println (str \"Mal [\" *host-language* \"]\"))", repl_env)
253
254while 1
255 let [eof, line] = Readline("user> ")
256 if eof
257 break
258 endif
259 if line == ""
260 continue
261 endif
262 try
263 call PrintLn(REP(line, repl_env))
264 catch
265 call PrintLn("Error: " . v:exception)
266 endtry
267endwhile
268qall!