aboutsummaryrefslogtreecommitdiff
path: root/ps/step8_macros.ps
blob: 32ca3afddac18acc41b66daf894911c1271f94fa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/runlibfile where { pop }{ /runlibfile { run } def } ifelse % 
(types.ps) runlibfile
(reader.ps) runlibfile
(printer.ps) runlibfile
(env.ps) runlibfile
(core.ps) runlibfile

% read
/_readline { print flush (%stdin) (r) file 99 string readline } def

/READ {
    /str exch def
    str read_str
} def


% eval
% is_pair?: ast -> is_pair? -> bool
% return true if non-empty list, otherwise false
/is_pair? { 
    dup _sequential? { _count 0 gt }{ pop false } ifelse
} def

% ast -> quasiquote -> new_ast
/quasiquote { 3 dict begin
    /ast exch def
    ast is_pair? not { %if not is_pair?
        /quote ast 2 _list
    }{ 
        /a0 ast 0 _nth def
        a0 /unquote eq { %if a0 unquote symbol
            ast 1 _nth
        }{ a0 is_pair? { %elseif a0 is_pair?
            /a00 a0 0 _nth def
            a00 /splice-unquote eq { %if splice-unquote
                /concat a0 1 _nth ast _rest quasiquote 3 _list
            }{ %else not splice-unquote
                /cons a0 quasiquote ast _rest quasiquote 3 _list
            } ifelse
        }{ % else not a0 is_pair?
            /cons a0 quasiquote ast _rest quasiquote 3 _list
        } ifelse } ifelse
    } ifelse
end } def

/is_macro_call? { 3 dict begin
    /env exch def
    /ast exch def
    ast _list? {
        /a0 ast 0 _nth def
        a0 _symbol? { %if a0 is symbol
            env a0 env_find null ne { %if a0 is in env
                env a0 env_get _mal_function? { %if user defined function
                    env a0 env_get /macro? get true eq %if marked as macro
                }{ false } ifelse
            }{ false } ifelse
        }{ false } ifelse
    }{ false } ifelse
end } def

/macroexpand { 3 dict begin
    /env exch def
    /ast exch def
    {
        ast env is_macro_call? {
            /mac env   ast 0 _nth   env_get def
            /ast ast _rest mac fload EVAL def
        }{
            exit
        } ifelse
    } loop 
    ast
end } def

/eval_ast { 2 dict begin
    /env exch def
    /ast exch def
    %(eval_ast: ) print ast ==
    ast _symbol? { %if symbol
        env ast env_get
    }{ ast _sequential? { %elseif list or vector
        [
            ast /data get { %forall items
                env EVAL
            } forall
        ] ast _list? { _list_from_array }{ _vector_from_array } ifelse
    }{ ast _hash_map? { %elseif list or vector
        <<
            ast /data get { %forall entries
                env EVAL
            } forall
        >> _hash_map_from_dict
    }{ % else
        ast
    } ifelse } ifelse } ifelse
end } def

/EVAL { 13 dict begin
    { %loop (TCO) 

    /env exch def
    /ast exch def
    /loop? false def

    %(EVAL: ) print ast true _pr_str print (\n) print
    ast _list? not { %if not a list
        ast env eval_ast
    }{ %else apply the list
      /ast ast env macroexpand def
      ast _list? not { %if no longer a list
          ast
      }{ %else still a list
        /a0 ast 0 _nth def
        /def! a0 eq { %if def!
            /a1 ast 1 _nth def
            /a2 ast 2 _nth def
            env a1  a2 env EVAL  env_set
        }{ /let* a0 eq { %if let*
            /a1 ast 1 _nth def
            /a2 ast 2 _nth def
            /let_env env null null env_new def
            0 2 a1 _count 1 sub { %for each pair
                /idx exch def
                let_env
                    a1 idx _nth
                    a1 idx 1 add _nth let_env EVAL
                    env_set
                    pop % discard the return value
            } for
            a2
            let_env
            /loop? true def % loop
        }{ /quote a0 eq { %if quote
            ast 1 _nth
        }{ /quasiquote a0 eq { %if quasiquote
            ast 1 _nth quasiquote
            env
            /loop? true def % loop
        }{ /defmacro! a0 eq { %if defmacro!
            /a1 ast 1 _nth def
            /a2 ast 2 _nth def
            a2 env EVAL
            dup /macro? true put % set macro flag
            env exch a1 exch env_set % def! it
        }{ /macroexpand a0 eq { %if defmacro!
            ast 1 _nth env macroexpand
        }{ /do a0 eq { %if do
            ast _count 2 gt { %if ast has more than 2 elements
                ast 1 ast _count 2 sub _slice env eval_ast pop
            } if
            ast ast _count 1 sub _nth % last ast becomes new ast
            env
            /loop? true def % loop
        }{ /if a0 eq { %if if
            /a1 ast 1 _nth def
            /cond a1 env EVAL def
            cond null eq cond false eq or { % if cond is nil or false
                ast _count 3 gt { %if false branch with a3
                    ast 3 _nth env
                    /loop? true def
                }{ % else false branch with no a3
                    null
                } ifelse
            }{ % true branch
                ast 2 _nth env
                /loop? true def
            } ifelse
        }{ /fn* a0 eq { %if fn*
            /a1 ast 1 _nth def
            /a2 ast 2 _nth def
            a2 env a1 _mal_function
        }{
            /el ast env eval_ast def
            el _rest el _first % stack: ast function
            dup _mal_function? { %if user defined function
                fload % stack: ast new_env
                /loop? true def
            }{ dup _function? { %else if builtin function
                /data get exec
            }{ %else (regular procedure/function)
                (cannot apply native proc!\n) print quit
            } ifelse } ifelse
        } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse
      } ifelse
    } ifelse

    loop? not { exit } if
    } loop % TCO
end } def


% print
/PRINT {
    true _pr_str
} def


% repl
/repl_env null null null env_new def

/RE { READ repl_env EVAL } def
/REP { READ repl_env EVAL PRINT } def

% core.ps: defined using postscript
/_ref { repl_env 3 1 roll env_set pop } def
core_ns { _function _ref } forall
(eval) { 0 _nth repl_env EVAL } _function _ref
(*ARGV*) [ ] _list_from_array _ref

% core.mal: defined using the language itself
(\(def! not \(fn* \(a\) \(if a false true\)\)\)) RE pop
(\(def! load-file \(fn* \(f\) \(eval \(read-string \(str "\(do " \(slurp f\) "\)"\)\)\)\)\)) RE pop
(\(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
(\(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

userdict /ARGUMENTS known { %if command line arguments
    ARGUMENTS length 0 gt { %if more than 0 arguments
        (*ARGV*) ARGUMENTS 1 ARGUMENTS length 1 sub getinterval
        _list_from_array _ref
        ARGUMENTS 0 get 
        (\(load-file ") exch ("\)) concatenate concatenate RE pop
        quit
    } if
} if

% repl loop
{ %loop
    (user> ) _readline
    not { exit } if  % exit if EOF

    { %try
        REP print (\n) print
    } stopped {
        (Error: ) print
        get_error_data false _pr_str print (\n) print
        $error /newerror false put
        $error /errorinfo null put
        clear
        cleardictstack
    } if
} bind loop

(\n) print  % final newline before exit for cleanliness
quit