ps: Fix handling of exceptions thrown from catch* clause
[jackhill/mal.git] / impls / ps / step9_try.ps
1 /runlibfile where { pop }{ /runlibfile { run } def } ifelse %
2 (types.ps) runlibfile
3 (reader.ps) runlibfile
4 (printer.ps) runlibfile
5 (env.ps) runlibfile
6 (core.ps) runlibfile
7
8 % read
9 /_readline { print flush (%stdin) (r) file 1024 string readline } def
10
11 /READ {
12 /str exch def
13 str read_str
14 } def
15
16
17 % eval
18 % is_pair?: ast -> is_pair? -> bool
19 % return true if non-empty list, otherwise false
20 /is_pair? {
21 dup _sequential? { _count 0 gt }{ pop false } ifelse
22 } def
23
24 % ast -> quasiquote -> new_ast
25 /quasiquote { 3 dict begin
26 /ast exch def
27 ast is_pair? not { %if not is_pair?
28 /quote ast 2 _list
29 }{
30 /a0 ast 0 _nth def
31 a0 /unquote eq { %if a0 unquote symbol
32 ast 1 _nth
33 }{ a0 is_pair? { %elseif a0 is_pair?
34 /a00 a0 0 _nth def
35 a00 /splice-unquote eq { %if splice-unquote
36 /concat a0 1 _nth ast _rest quasiquote 3 _list
37 }{ %else not splice-unquote
38 /cons a0 quasiquote ast _rest quasiquote 3 _list
39 } ifelse
40 }{ % else not a0 is_pair?
41 /cons a0 quasiquote ast _rest quasiquote 3 _list
42 } ifelse } ifelse
43 } ifelse
44 end } def
45
46 /is_macro_call? { 3 dict begin
47 /env exch def
48 /ast exch def
49 ast _list? {
50 /a0 ast 0 _nth def
51 a0 _symbol? { %if a0 is symbol
52 env a0 env_find null ne { %if a0 is in env
53 env a0 env_get _mal_function? { %if user defined function
54 env a0 env_get /macro? get true eq %if marked as macro
55 }{ false } ifelse
56 }{ false } ifelse
57 }{ false } ifelse
58 }{ false } ifelse
59 end } def
60
61 /macroexpand { 3 dict begin
62 /env exch def
63 /ast exch def
64 {
65 ast env is_macro_call? {
66 /mac env ast 0 _nth env_get def
67 /ast ast _rest mac fload EVAL def
68 }{
69 exit
70 } ifelse
71 } loop
72 ast
73 end } def
74
75 /eval_ast { 2 dict begin
76 /env exch def
77 /ast exch def
78 %(eval_ast: ) print ast ==
79 ast _symbol? { %if symbol
80 env ast env_get
81 }{ ast _sequential? { %elseif list or vector
82 [
83 ast /data get { %forall items
84 env EVAL
85 } forall
86 ] ast _list? { _list_from_array }{ _vector_from_array } ifelse
87 }{ ast _hash_map? { %elseif list or vector
88 <<
89 ast /data get { %forall entries
90 env EVAL
91 } forall
92 >> _hash_map_from_dict
93 }{ % else
94 ast
95 } ifelse } ifelse } ifelse
96 end } def
97
98 /EVAL { 13 dict begin
99 { %loop (TCO)
100
101 /env exch def
102 /ast exch def
103 /loop? false def
104
105 %(EVAL: ) print ast true _pr_str print (\n) print
106 ast _list? not { %if not a list
107 ast env eval_ast
108 }{ %else apply the list
109 /ast ast env macroexpand def
110 ast _list? not { %if no longer a list
111 ast env eval_ast
112 }{ %else still a list
113 /a0 ast 0 _nth def
114 a0 _nil? { %if ()
115 ast
116 }{ /def! a0 eq { %if def!
117 /a1 ast 1 _nth def
118 /a2 ast 2 _nth def
119 env a1 a2 env EVAL env_set
120 }{ /let* a0 eq { %if let*
121 /a1 ast 1 _nth def
122 /a2 ast 2 _nth def
123 /let_env env null null env_new def
124 0 2 a1 _count 1 sub { %for each pair
125 /idx exch def
126 let_env
127 a1 idx _nth
128 a1 idx 1 add _nth let_env EVAL
129 env_set
130 pop % discard the return value
131 } for
132 a2
133 let_env
134 /loop? true def % loop
135 }{ /quote a0 eq { %if quote
136 ast 1 _nth
137 }{ /quasiquote a0 eq { %if quasiquote
138 ast 1 _nth quasiquote
139 env
140 /loop? true def % loop
141 }{ /defmacro! a0 eq { %if defmacro!
142 /a1 ast 1 _nth def
143 /a2 ast 2 _nth def
144 a2 env EVAL
145 dup /macro? true put % set macro flag
146 env exch a1 exch env_set % def! it
147 }{ /macroexpand a0 eq { %if defmacro!
148 ast 1 _nth env macroexpand
149 }{ /do a0 eq { %if do
150 ast _count 2 gt { %if ast has more than 2 elements
151 ast 1 ast _count 2 sub _slice env eval_ast pop
152 } if
153 ast ast _count 1 sub _nth % last ast becomes new ast
154 env
155 /loop? true def % loop
156 }{ /try* a0 eq { %if try*
157 ast _count 2 gt { %if has catch* block
158 { %try
159 2 dict begin % special dict for dict stack count
160 countdictstack /dictcnt exch def
161 count /stackcnt exch def
162 ast 1 _nth env EVAL
163 end
164 } stopped { %catch
165 % clean up the dictionary stack
166 1 1 countdictstack dictcnt sub { %foreach added dict
167 %(popping dict\n) print
168 pop end % pop idx and pop dict
169 %(new ast: ) print ast true _pr_str print (\n) print
170 } for
171 % clean up the operand stack
172 count 1 exch 1 exch stackcnt sub { %foreach added operand
173 %(op stack: ) print pstack
174 pop pop % pop idx and operand
175 %(popped op stack\n) print pstack
176 } for
177 end % remove special dict
178 % get error data and reset $error dict
179 /errdata get_error_data def
180 $error /newerror false put
181 $error /errorinfo null put
182
183 ast _count 3 lt { %if no third (catch*) form
184 errdata throw
185 } if
186 ast 2 _nth 0 _nth (catch*) eq not { %if third form not catch*
187 (No catch* in throw form) _throw
188 } if
189 ast 2 _nth 2 _nth
190 env
191 ast 2 _nth 1 _nth 1 _list
192 errdata 1 _list
193 env_new
194 EVAL
195 } if
196 }{ % else no catch* block
197 ast 1 _nth env EVAL
198 } ifelse
199 }{ /if a0 eq { %if if
200 /a1 ast 1 _nth def
201 /cond a1 env EVAL def
202 cond null eq cond false eq or { % if cond is nil or false
203 ast _count 3 gt { %if false branch with a3
204 ast 3 _nth env
205 /loop? true def
206 }{ % else false branch with no a3
207 null
208 } ifelse
209 }{ % true branch
210 ast 2 _nth env
211 /loop? true def
212 } ifelse
213 }{ /fn* a0 eq { %if fn*
214 /a1 ast 1 _nth def
215 /a2 ast 2 _nth def
216 a2 env a1 _mal_function
217 }{
218 /el ast env eval_ast def
219 el _rest el _first % stack: ast function
220 dup _mal_function? { %if user defined function
221 fload % stack: ast new_env
222 /loop? true def
223 }{ dup _function? { %else if builtin function
224 /data get exec
225 }{ %else (regular procedure/function)
226 (cannot apply native proc!\n) print quit
227 } ifelse } ifelse
228 } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse
229 } ifelse
230 } ifelse
231
232 loop? not { exit } if
233 } loop % TCO
234 end } def
235
236
237 % print
238 /PRINT {
239 true _pr_str
240 } def
241
242
243 % repl
244 /repl_env null null null env_new def
245
246 /RE { READ repl_env EVAL } def
247 /REP { READ repl_env EVAL PRINT } def
248
249 % core.ps: defined using postscript
250 /_ref { repl_env 3 1 roll env_set pop } def
251 core_ns { _function _ref } forall
252 (eval) { 0 _nth repl_env EVAL } _function _ref
253 (*ARGV*) [ ] _list_from_array _ref
254
255 % core.mal: defined using the language itself
256 (\(def! not \(fn* \(a\) \(if a false true\)\)\)) RE pop
257 (\(def! load-file \(fn* \(f\) \(eval \(read-string \(str "\(do " \(slurp f\) "\nnil\)"\)\)\)\)\)) RE pop
258 (\(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\)\)\)\)\)\)\)) RE pop
259
260 userdict /ARGUMENTS known { %if command line arguments
261 ARGUMENTS length 0 gt { %if more than 0 arguments
262 (*ARGV*) ARGUMENTS 1 ARGUMENTS length 1 sub getinterval
263 _list_from_array _ref
264 ARGUMENTS 0 get
265 (\(load-file ") exch ("\)) concatenate concatenate RE pop
266 quit
267 } if
268 } if
269
270 % repl loop
271 { %loop
272 (user> ) _readline
273 not { exit } if % exit if EOF
274
275 { %try
276 REP print (\n) print
277 } stopped {
278 (Error: ) print
279 get_error_data false _pr_str print (\n) print
280 $error /newerror false put
281 $error /errorinfo null put
282 clear
283 cleardictstack
284 } if
285 } bind loop
286
287 (\n) print % final newline before exit for cleanliness
288 quit