Merge pull request #146 from dubek/fix-macro-eval
[jackhill/mal.git] / ps / step8_macros.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 /def! a0 eq { %if def!
115 /a1 ast 1 _nth def
116 /a2 ast 2 _nth def
117 env a1 a2 env EVAL env_set
118 }{ /let* a0 eq { %if let*
119 /a1 ast 1 _nth def
120 /a2 ast 2 _nth def
121 /let_env env null null env_new def
122 0 2 a1 _count 1 sub { %for each pair
123 /idx exch def
124 let_env
125 a1 idx _nth
126 a1 idx 1 add _nth let_env EVAL
127 env_set
128 pop % discard the return value
129 } for
130 a2
131 let_env
132 /loop? true def % loop
133 }{ /quote a0 eq { %if quote
134 ast 1 _nth
135 }{ /quasiquote a0 eq { %if quasiquote
136 ast 1 _nth quasiquote
137 env
138 /loop? true def % loop
139 }{ /defmacro! a0 eq { %if defmacro!
140 /a1 ast 1 _nth def
141 /a2 ast 2 _nth def
142 a2 env EVAL
143 dup /macro? true put % set macro flag
144 env exch a1 exch env_set % def! it
145 }{ /macroexpand a0 eq { %if defmacro!
146 ast 1 _nth env macroexpand
147 }{ /do a0 eq { %if do
148 ast _count 2 gt { %if ast has more than 2 elements
149 ast 1 ast _count 2 sub _slice env eval_ast pop
150 } if
151 ast ast _count 1 sub _nth % last ast becomes new ast
152 env
153 /loop? true def % loop
154 }{ /if a0 eq { %if if
155 /a1 ast 1 _nth def
156 /cond a1 env EVAL def
157 cond null eq cond false eq or { % if cond is nil or false
158 ast _count 3 gt { %if false branch with a3
159 ast 3 _nth env
160 /loop? true def
161 }{ % else false branch with no a3
162 null
163 } ifelse
164 }{ % true branch
165 ast 2 _nth env
166 /loop? true def
167 } ifelse
168 }{ /fn* a0 eq { %if fn*
169 /a1 ast 1 _nth def
170 /a2 ast 2 _nth def
171 a2 env a1 _mal_function
172 }{
173 /el ast env eval_ast def
174 el _rest el _first % stack: ast function
175 dup _mal_function? { %if user defined function
176 fload % stack: ast new_env
177 /loop? true def
178 }{ dup _function? { %else if builtin function
179 /data get exec
180 }{ %else (regular procedure/function)
181 (cannot apply native proc!\n) print quit
182 } ifelse } ifelse
183 } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse
184 } ifelse
185 } ifelse
186
187 loop? not { exit } if
188 } loop % TCO
189 end } def
190
191
192 % print
193 /PRINT {
194 true _pr_str
195 } def
196
197
198 % repl
199 /repl_env null null null env_new def
200
201 /RE { READ repl_env EVAL } def
202 /REP { READ repl_env EVAL PRINT } def
203
204 % core.ps: defined using postscript
205 /_ref { repl_env 3 1 roll env_set pop } def
206 core_ns { _function _ref } forall
207 (eval) { 0 _nth repl_env EVAL } _function _ref
208 (*ARGV*) [ ] _list_from_array _ref
209
210 % core.mal: defined using the language itself
211 (\(def! not \(fn* \(a\) \(if a false true\)\)\)) RE pop
212 (\(def! load-file \(fn* \(f\) \(eval \(read-string \(str "\(do " \(slurp f\) "\)"\)\)\)\)\)) RE pop
213 (\(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
214 (\(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\)\)\)\)\)\)\)\)) RE pop
215
216 userdict /ARGUMENTS known { %if command line arguments
217 ARGUMENTS length 0 gt { %if more than 0 arguments
218 (*ARGV*) ARGUMENTS 1 ARGUMENTS length 1 sub getinterval
219 _list_from_array _ref
220 ARGUMENTS 0 get
221 (\(load-file ") exch ("\)) concatenate concatenate RE pop
222 quit
223 } if
224 } if
225
226 % repl loop
227 { %loop
228 (user> ) _readline
229 not { exit } if % exit if EOF
230
231 { %try
232 REP print (\n) print
233 } stopped {
234 (Error: ) print
235 get_error_data false _pr_str print (\n) print
236 $error /newerror false put
237 $error /errorinfo null put
238 clear
239 cleardictstack
240 } if
241 } bind loop
242
243 (\n) print % final newline before exit for cleanliness
244 quit