tcl: Support catchless try*
[jackhill/mal.git] / tcl / step9_try.tcl
CommitLineData
54d9903c
DM
1source mal_readline.tcl
2source types.tcl
3source reader.tcl
4source printer.tcl
5source env.tcl
6source core.tcl
7
8proc READ str {
9 read_str $str
10}
11
12proc is_pair {ast} {
13 expr {[sequential_q $ast] && [llength [obj_val $ast]] > 0}
14}
15
16proc quasiquote {ast} {
17 if {![is_pair $ast]} {
18 return [list_new [list [symbol_new "quote"] $ast]]
19 }
20 lassign [obj_val $ast] a0 a1
21 if {[symbol_q $a0] && [obj_val $a0] == "unquote"} {
22 return $a1
23 }
24 lassign [obj_val $a0] a00 a01
25 set rest [list_new [lrange [obj_val $ast] 1 end]]
26 if {[is_pair $a0] && [symbol_q $a00] && [obj_val $a00] == "splice-unquote"} {
27 return [list_new [list [symbol_new "concat"] $a01 [quasiquote $rest]]]
28 } else {
29 return [list_new [list [symbol_new "cons"] [quasiquote $a0] [quasiquote $rest]]]
30 }
31}
32
33proc is_macro_call {ast env} {
34 if {![list_q $ast]} {
35 return 0
36 }
37 set a0 [lindex [obj_val $ast] 0]
181a55ad 38 if {$a0 == "" || ![symbol_q $a0]} {
54d9903c
DM
39 return 0
40 }
41 set varname [obj_val $a0]
42 set foundenv [$env find $varname]
43 if {$foundenv == 0} {
44 return 0
45 }
46 macro_q [$env get $varname]
47}
48
49proc macroexpand {ast env} {
50 while {[is_macro_call $ast $env]} {
51 set a0 [mal_first [list $ast]]
52 set macro_name [obj_val $a0]
53 set macro_obj [$env get $macro_name]
54 set macro_args [obj_val [mal_rest [list $ast]]]
55
56 set funcdict [obj_val $macro_obj]
57 set body [dict get $funcdict body]
58 set env [dict get $funcdict env]
59 set binds [dict get $funcdict binds]
60 set funcenv [Env new $env $binds $macro_args]
61 set ast [EVAL $body $funcenv]
62 }
63 return $ast
64}
65
66proc eval_ast {ast env} {
67 switch [obj_type $ast] {
68 "symbol" {
69 set varname [obj_val $ast]
70 return [$env get $varname]
71 }
72 "list" {
73 set res {}
74 foreach element [obj_val $ast] {
75 lappend res [EVAL $element $env]
76 }
77 return [list_new $res]
78 }
79 "vector" {
80 set res {}
81 foreach element [obj_val $ast] {
82 lappend res [EVAL $element $env]
83 }
84 return [vector_new $res]
85 }
86 "hashmap" {
87 set res [dict create]
88 dict for {k v} [obj_val $ast] {
89 dict set res $k [EVAL $v $env]
90 }
91 return [hashmap_new $res]
92 }
93 default { return $ast }
94 }
95}
96
97proc EVAL {ast env} {
98 while {true} {
99 if {![list_q $ast]} {
100 return [eval_ast $ast $env]
101 }
102
103 set ast [macroexpand $ast $env]
104 if {![list_q $ast]} {
6c94cd3e 105 return [eval_ast $ast $env]
54d9903c
DM
106 }
107
108 lassign [obj_val $ast] a0 a1 a2 a3
181a55ad
DM
109 if {$a0 == ""} {
110 return $ast
111 }
54d9903c
DM
112 switch [obj_val $a0] {
113 "def!" {
114 set varname [obj_val $a1]
115 set value [EVAL $a2 $env]
116 return [$env set $varname $value]
117 }
118 "let*" {
119 set letenv [Env new $env]
120 set bindings_list [obj_val $a1]
121 foreach {varnameobj varvalobj} $bindings_list {
122 $letenv set [obj_val $varnameobj] [EVAL $varvalobj $letenv]
123 }
124 set ast $a2
125 set env $letenv
126 # TCO: Continue loop
127 }
128 "quote" {
129 return $a1
130 }
131 "quasiquote" {
132 set ast [quasiquote $a1]
133 }
134 "defmacro!" {
135 set varname [obj_val $a1]
136 set value [EVAL $a2 $env]
137 set fn [obj_val $value]
138 dict set fn is_macro 1
139 obj_set_val $value $fn
140 return [$env set $varname $value]
141 }
142 "macroexpand" {
143 return [macroexpand $a1 $env]
144 }
145 "try*" {
625a6847
DM
146 if {$a2 == ""} {
147 return [EVAL $a1 $env]
148 }
54d9903c
DM
149 set res {}
150 if { [catch { set res [EVAL $a1 $env] } exception] } {
151 set exc_var [obj_val [lindex [obj_val $a2] 1]]
152 if {$exception == "__MalException__"} {
153 set exc_value $::mal_exception_obj
154 } else {
155 set exc_value [string_new $exception]
156 }
157 set catch_env [Env new $env [list $exc_var] [list $exc_value]]
158 return [EVAL [lindex [obj_val $a2] 2] $catch_env]
159 } else {
160 return $res
161 }
162 }
163 "do" {
164 set el [list_new [lrange [obj_val $ast] 1 end-1]]
165 eval_ast $el $env
166 set ast [lindex [obj_val $ast] end]
167 # TCO: Continue loop
168 }
169 "if" {
170 set condval [EVAL $a1 $env]
171 if {[false_q $condval] || [nil_q $condval]} {
172 if {$a3 == ""} {
173 return $::mal_nil
174 }
175 set ast $a3
176 } else {
177 set ast $a2
178 }
179 # TCO: Continue loop
180 }
181 "fn*" {
182 set binds {}
183 foreach v [obj_val $a1] {
184 lappend binds [obj_val $v]
185 }
186 return [function_new $a2 $env $binds]
187 }
188 default {
189 set lst_obj [eval_ast $ast $env]
190 set lst [obj_val $lst_obj]
191 set f [lindex $lst 0]
192 set call_args [lrange $lst 1 end]
193 switch [obj_type $f] {
194 function {
195 set fn [obj_val $f]
196 set ast [dict get $fn body]
197 set env [Env new [dict get $fn env] [dict get $fn binds] $call_args]
198 # TCO: Continue loop
199 }
200 nativefunction {
201 set body [concat [list [obj_val $f]] {$a}]
202 set lambda [list {a} $body]
203 return [apply $lambda $call_args]
204 }
205 default {
206 error "Not a function"
207 }
208 }
209 }
210 }
211 }
212}
213
214proc PRINT exp {
215 pr_str $exp 1
216}
217
218proc REP {str env} {
219 PRINT [EVAL [READ $str] $env]
220}
221
222proc RE {str env} {
223 EVAL [READ $str] $env
224}
225
226proc mal_eval {a} {
227 global repl_env
228 EVAL [lindex $a 0] $repl_env
229}
230
231set repl_env [Env new]
232dict for {k v} $core_ns {
233 $repl_env set $k $v
234}
235
236$repl_env set "eval" [nativefunction_new mal_eval]
237
238set argv_list {}
239foreach arg [lrange $argv 1 end] {
240 lappend argv_list [string_new $arg]
241}
242$repl_env set "*ARGV*" [list_new $argv_list]
243
244# core.mal: defined using the language itself
245RE "(def! not (fn* (a) (if a false true)))" $repl_env
246RE "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))" $repl_env
247RE "(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
248RE "(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))))))))" $repl_env
249
250fconfigure stdout -translation binary
251
252set DEBUG_MODE 0
253if { [array names env DEBUG] != "" && $env(DEBUG) != "0" } {
254 set DEBUG_MODE 1
255}
256
257if {$argc > 0} {
258 REP "(load-file \"[lindex $argv 0]\")" $repl_env
259 exit
260}
261
262# repl loop
263while {true} {
264 set res [_readline "user> "]
265 if {[lindex $res 0] == "EOF"} {
266 break
267 }
268 set line [lindex $res 1]
269 if {$line == ""} {
270 continue
271 }
272 if { [catch { puts [REP $line $repl_env] } exception] } {
dd7a4f55
JM
273 if {$exception == "__MalException__"} {
274 set res [pr_str $::mal_exception_obj 1]
275 puts "Error: $res"
276 } else {
277 puts "Error: $exception"
278 }
54d9903c
DM
279 if { $DEBUG_MODE } {
280 puts $::errorInfo
281 }
282 }
283}
284puts ""