Move implementations into impls/ dir
[jackhill/mal.git] / impls / tcl / step8_macros.tcl
1 source mal_readline.tcl
2 source types.tcl
3 source reader.tcl
4 source printer.tcl
5 source env.tcl
6 source core.tcl
7
8 proc READ str {
9 read_str $str
10 }
11
12 proc is_pair {ast} {
13 expr {[sequential_q $ast] && [llength [obj_val $ast]] > 0}
14 }
15
16 proc 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
33 proc is_macro_call {ast env} {
34 if {![list_q $ast]} {
35 return 0
36 }
37 set a0 [lindex [obj_val $ast] 0]
38 if {$a0 == "" || ![symbol_q $a0]} {
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
49 proc 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
66 proc 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
97 proc 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]} {
105 return [eval_ast $ast $env]
106 }
107
108 lassign [obj_val $ast] a0 a1 a2 a3
109 if {$a0 == ""} {
110 return $ast
111 }
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 "do" {
146 set el [list_new [lrange [obj_val $ast] 1 end-1]]
147 eval_ast $el $env
148 set ast [lindex [obj_val $ast] end]
149 # TCO: Continue loop
150 }
151 "if" {
152 set condval [EVAL $a1 $env]
153 if {[false_q $condval] || [nil_q $condval]} {
154 if {$a3 == ""} {
155 return $::mal_nil
156 }
157 set ast $a3
158 } else {
159 set ast $a2
160 }
161 # TCO: Continue loop
162 }
163 "fn*" {
164 set binds {}
165 foreach v [obj_val $a1] {
166 lappend binds [obj_val $v]
167 }
168 return [function_new $a2 $env $binds]
169 }
170 default {
171 set lst_obj [eval_ast $ast $env]
172 set lst [obj_val $lst_obj]
173 set f [lindex $lst 0]
174 set call_args [lrange $lst 1 end]
175 switch [obj_type $f] {
176 function {
177 set fn [obj_val $f]
178 set ast [dict get $fn body]
179 set env [Env new [dict get $fn env] [dict get $fn binds] $call_args]
180 # TCO: Continue loop
181 }
182 nativefunction {
183 set body [concat [list [obj_val $f]] {$a}]
184 set lambda [list {a} $body]
185 return [apply $lambda $call_args]
186 }
187 default {
188 error "Not a function"
189 }
190 }
191 }
192 }
193 }
194 }
195
196 proc PRINT exp {
197 pr_str $exp 1
198 }
199
200 proc REP {str env} {
201 PRINT [EVAL [READ $str] $env]
202 }
203
204 proc RE {str env} {
205 EVAL [READ $str] $env
206 }
207
208 proc mal_eval {a} {
209 global repl_env
210 EVAL [lindex $a 0] $repl_env
211 }
212
213 set repl_env [Env new]
214 dict for {k v} $core_ns {
215 $repl_env set $k $v
216 }
217
218 $repl_env set "eval" [nativefunction_new mal_eval]
219
220 set argv_list {}
221 foreach arg [lrange $argv 1 end] {
222 lappend argv_list [string_new $arg]
223 }
224 $repl_env set "*ARGV*" [list_new $argv_list]
225
226 # core.mal: defined using the language itself
227 RE "(def! not (fn* (a) (if a false true)))" $repl_env
228 RE "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\\nnil)\")))))" $repl_env
229 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
230
231 fconfigure stdout -translation binary
232
233 set DEBUG_MODE 0
234 if { [array names env DEBUG] != "" && $env(DEBUG) != "0" } {
235 set DEBUG_MODE 1
236 }
237
238 if {$argc > 0} {
239 REP "(load-file \"[lindex $argv 0]\")" $repl_env
240 exit
241 }
242
243 # repl loop
244 while {true} {
245 set res [_readline "user> "]
246 if {[lindex $res 0] == "EOF"} {
247 break
248 }
249 set line [lindex $res 1]
250 if {$line == ""} {
251 continue
252 }
253 if { [catch { puts [REP $line $repl_env] } exception] } {
254 puts "Error: $exception"
255 if { $DEBUG_MODE } {
256 puts $::errorInfo
257 }
258 }
259 }
260 puts ""