bbc-basic: Adjust error message from FNenv_get to match tests.
[jackhill/mal.git] / bbc-basic / step8_macros.bbc
CommitLineData
24951719
BH
1REM Step 8 of mal in BBC BASIC
2
3LIBRARY "types"
4LIBRARY "reader"
5LIBRARY "printer"
6LIBRARY "env"
7LIBRARY "core"
8
9PROCtypes_init
10
11repl_env% = FNalloc_environment(FNnil)
12PROCcore_ns : REM This sets the data pointer
13REPEAT
14 READ sym$, i%
15 IF sym$ <> "" THEN
16 PROCenv_set(repl_env%, FNalloc_symbol(sym$), FNalloc_corefn(i%))
17 ENDIF
18UNTIL sym$ = ""
19
20REM Initial forms to evaluate
21RESTORE +0
22DATA (def! not (fn* (a) (if a false true)))
23DATA (def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))
938f8c02
BH
24DATA (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)))))))
25DATA (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))))))))
24951719
BH
26DATA ""
27REPEAT
28 READ form$
29 IF form$ <> "" THEN val$ = FNrep(form$)
30UNTIL form$ = ""
31
32REPEAT
33 PROCgc_enter
34 sav% = FNgc_save
35 REM Catch all errors apart from "Escape".
36 ON ERROR LOCAL PRINT REPORT$:IF ERR = 17 THEN END ELSE PROCgc_restore(sav%)
37 PRINT "user> ";
38 LINE INPUT "" line$
39 PRINT FNrep(line$)
40 PROCgc_exit
41UNTIL FALSE
42
43END
44
45DEF FNREAD(a$)
46=FNread_str(a$)
47
48DEF FNis_pair(val%)
49=FNis_list(val%) AND NOT FNis_empty(val%)
50
51DEF FNquasiquote(ast%)
52 LOCAL car%, caar%
53 IF NOT FNis_pair(ast%) THEN =FNalloc_list2(FNalloc_symbol("quote"), ast%)
54 car% = FNlist_car(ast%)
55 IF FNis_symbol(car%) THEN
56 IF FNunbox_symbol(car%) = "unquote" THEN =FNlist_nth(ast%, 1)
57 ENDIF
58 IF FNis_pair(car%) THEN
59 caar% = FNlist_car(car%)
60 IF FNis_symbol(caar%) THEN
61 IF FNunbox_symbol(caar%) = "splice-unquote" THEN
62 =FNalloc_list3(FNalloc_symbol("concat"), FNlist_nth(car%, 1), FNquasiquote(FNlist_cdr(ast%)))
63 ENDIF
64 ENDIF
65 ENDIF
66=FNalloc_list3(FNalloc_symbol("cons"), FNquasiquote(car%), FNquasiquote(FNlist_cdr(ast%)))
67
00164f7e
BH
68DEF FNis_macro_call(ast%, env%)
69 LOCAL car%, val%
70 IF NOT FNis_list(ast%) THEN =FALSE
71 car% = FNlist_car(ast%)
72 IF NOT FNis_symbol(car%) THEN =FALSE
73 IF FNis_nil(FNenv_find(env%, car%)) THEN =FALSE
74 val% = FNenv_get(env%, car%)
75=FNis_macro(val%)
76
77DEF FNmacroexpand(ast%, env%)
78 LOCAL mac%, macenv%, macast%
79 WHILE FNis_macro_call(ast%, env%)
80 REM PRINT "expanded ";FNpr_str(ast%, TRUE);
81 mac% = FNenv_get(env%, FNlist_car(ast%))
82 macenv% = FNnew_env(FNfn_env(mac%), FNfn_params(mac%), FNlist_cdr(ast%))
83 macast% = FNfn_ast(mac%)
84 ast% = FNEVAL(macast%, macenv%)
85 REM PRINT " to ";FNpr_str(ast%, TRUE)
86 ENDWHILE
87=ast%
88
24951719
BH
89DEF FNEVAL(ast%, env%)
90 PROCgc_enter
91=FNgc_exit(FNEVAL_(ast%, env%))
92
93DEF FNEVAL_(ast%, env%)
94 LOCAL car%, specialform%, val%, bindings%
95 REPEAT
96 PROCgc_keep_only2(ast%, env%)
97 IF NOT FNis_list(ast%) THEN =FNeval_ast(ast%, env%)
98 IF FNis_empty(ast%) THEN =ast%
00164f7e
BH
99 ast% = FNmacroexpand(ast%, env%)
100 IF NOT FNis_list(ast%) THEN =FNeval_ast(ast%, env%)
24951719
BH
101 car% = FNlist_car(ast%)
102 specialform% = FALSE
103 IF FNis_symbol(car%) THEN
104 specialform% = TRUE
105 CASE FNunbox_symbol(car%) OF
106 REM Special forms
107 WHEN "def!"
108 val% = FNEVAL(FNlist_nth(ast%, 2), env%)
109 PROCenv_set(env%, FNlist_nth(ast%, 1), val%)
110 =val%
111 WHEN "defmacro!"
112 val% = FNEVAL(FNlist_nth(ast%, 2), env%)
113 IF FNis_fn(val%) THEN PROCmake_macro(val%)
114 PROCenv_set(env%, FNlist_nth(ast%, 1), val%)
115 =val%
116 WHEN "let*"
117 env% = FNalloc_environment(env%)
118 bindings% = FNlist_nth(ast%, 1)
119 WHILE NOT FNis_empty(bindings%)
120 PROCenv_set(env%, FNlist_car(bindings%), FNEVAL(FNlist_nth(bindings%, 1), env%))
121 bindings% = FNlist_cdr(FNlist_cdr(bindings%))
122 ENDWHILE
123 ast% = FNlist_nth(ast%, 2)
124 REM Loop round for tail-call optimisation.
125 WHEN "do"
126 REM The guide has us call FNeval_ast on the sub-list that excludes
127 REM the last element of ast%, but that's a bit painful without
128 REM native list slicing, so it's easier to just re-implement the
129 REM bit of FNeval_ast that we need.
130 ast% = FNlist_cdr(ast%)
131 WHILE NOT FNis_empty(FNlist_cdr(ast%))
132 val% = FNEVAL(FNlist_car(ast%), env%)
133 ast% = FNlist_cdr(ast%)
134 ENDWHILE
135 ast% = FNlist_car(ast%)
136 WHEN "if"
137 IF FNis_truish(FNEVAL(FNlist_nth(ast%, 1), env%)) THEN
138 ast% = FNlist_nth(ast%, 2)
139 ELSE
140 IF FNlist_len(ast%) = 3 THEN =FNnil ELSE ast% = FNlist_nth(ast%, 3)
141 ENDIF
142 REM Loop round for tail-call optimisation.
143 WHEN "fn*"
144 =FNalloc_fn(FNlist_nth(ast%, 2), FNlist_nth(ast%, 1), env%)
145 WHEN "quote"
146 =FNlist_nth(ast%, 1)
147 WHEN "quasiquote"
148 ast% = FNquasiquote(FNlist_nth(ast%, 1))
149 REM Loop round for tail-call optimisation
00164f7e
BH
150 WHEN "macroexpand"
151 =FNmacroexpand(FNlist_nth(ast%, 1), env%)
24951719
BH
152 OTHERWISE
153 specialform% = FALSE
154 ENDCASE
155 ENDIF
156 IF NOT specialform% THEN
157 REM This is the "apply" part.
158 ast% = FNeval_ast(ast%, env%)
159 car% = FNlist_car(ast%)
160 IF FNis_corefn(car%) THEN
161 =FNcore_call(FNunbox_corefn(car%), FNlist_cdr(ast%))
162 ENDIF
163 IF FNis_fn(car%) THEN
164 env% = FNnew_env(FNfn_env(car%), FNfn_params(car%), FNlist_cdr(ast%))
165 ast% = FNfn_ast(car%)
166 REM Loop round for tail-call optimisation.
167 ELSE
168 ERROR &40E80918, "Not a function"
169 ENDIF
170 ENDIF
171 UNTIL FALSE
172
173DEF FNPRINT(a%)
174=FNpr_str(a%, TRUE)
175
176DEF FNrep(a$)
177=FNPRINT(FNEVAL(FNREAD(a$), repl_env%))
178
179DEF FNeval_ast(ast%, env%)
180 LOCAL val%, car%, cdr%
181 IF FNis_symbol(ast%) THEN =FNenv_get(env%, ast%)
182 IF FNis_list(ast%) THEN
183 IF FNis_empty(ast%) THEN =ast%
184 car% = FNEVAL(FNlist_car(ast%), env%)
185 cdr% = FNeval_ast(FNlist_cdr(ast%), env%)
186 =FNalloc_pair(car%, cdr%)
187 ENDIF
188=ast%
189
190REM Local Variables:
191REM indent-tabs-mode: nil
192REM End: