Commit | Line | Data |
---|---|---|
159ebed9 BH |
1 | REM Step A of mal in BBC BASIC |
2 | ||
3 | LIBRARY "types" | |
4 | LIBRARY "reader" | |
5 | LIBRARY "printer" | |
6 | LIBRARY "env" | |
7 | LIBRARY "core" | |
8 | ||
9 | PROCtypes_init | |
10 | ||
11 | repl_env% = FNalloc_environment(FNnil) | |
12 | PROCcore_ns : REM This sets the data pointer | |
13 | REPEAT | |
14 | READ sym$, i% | |
15 | IF sym$ <> "" THEN | |
16 | PROCenv_set(repl_env%, FNalloc_symbol(sym$), FNalloc_corefn(i%)) | |
17 | ENDIF | |
18 | UNTIL sym$ = "" | |
19 | ||
20 | REM Initial forms to evaluate | |
21 | RESTORE +0 | |
22 | DATA (def! not (fn* (a) (if a false true))) | |
e6d41de4 | 23 | DATA (def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)"))))) |
159ebed9 | 24 | DATA (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))))))) |
ae5bbd45 | 25 | DATA (def! *host-language* "BBC BASIC V") |
159ebed9 BH |
26 | DATA "" |
27 | REPEAT | |
28 | READ form$ | |
29 | IF form$ <> "" THEN val$ = FNrep(form$) | |
30 | UNTIL form$ = "" | |
31 | ||
32 | argv% = FNget_argv | |
33 | ||
34 | IF FNis_empty(argv%) THEN | |
35 | PROCenv_set(repl_env%, FNalloc_symbol("*ARGV*"), FNempty) | |
36 | ELSE | |
37 | PROCenv_set(repl_env%, FNalloc_symbol("*ARGV*"), FNrest(argv%)) | |
0f97a00d | 38 | val$ = FNrep("(load-file " + FNunbox_string(FNpr_str(FNfirst(argv%), TRUE)) + ")") |
159ebed9 BH |
39 | END |
40 | ENDIF | |
41 | ||
ae5bbd45 | 42 | val$ = FNrep("(println (str ""Mal ["" *host-language* ""]""))") |
d4b2e8b2 | 43 | sav% = FNgc_save |
159ebed9 BH |
44 | REPEAT |
45 | REM Catch all errors apart from "Escape". | |
d4b2e8b2 BH |
46 | ON ERROR LOCAL IF ERR = 17 ON ERROR OFF: ERROR ERR, REPORT$ ELSE PRINT REPORT$ |
47 | PROCgc_restore(sav%) | |
159ebed9 BH |
48 | sav% = FNgc_save |
49 | PRINT "user> "; | |
50 | LINE INPUT "" line$ | |
51 | PRINT FNrep(line$) | |
159ebed9 BH |
52 | UNTIL FALSE |
53 | ||
54 | END | |
55 | ||
56 | DEF FNREAD(a$) | |
0f97a00d | 57 | =FNread_str(FNalloc_string(a$)) |
159ebed9 BH |
58 | |
59 | DEF FNis_pair(val%) | |
60 | =FNis_seq(val%) AND NOT FNis_empty(val%) | |
61 | ||
62 | DEF FNquasiquote(ast%) | |
63 | LOCAL car%, caar% | |
64 | IF NOT FNis_pair(ast%) THEN =FNalloc_list2(FNalloc_symbol("quote"), ast%) | |
65 | car% = FNfirst(ast%) | |
66 | IF FNis_symbol(car%) THEN | |
67 | IF FNunbox_symbol(car%) = "unquote" THEN =FNnth(ast%, 1) | |
68 | ENDIF | |
69 | IF FNis_pair(car%) THEN | |
70 | caar% = FNfirst(car%) | |
71 | IF FNis_symbol(caar%) THEN | |
72 | IF FNunbox_symbol(caar%) = "splice-unquote" THEN | |
73 | =FNalloc_list3(FNalloc_symbol("concat"), FNnth(car%, 1), FNquasiquote(FNrest(ast%))) | |
74 | ENDIF | |
75 | ENDIF | |
76 | ENDIF | |
77 | =FNalloc_list3(FNalloc_symbol("cons"), FNquasiquote(car%), FNquasiquote(FNrest(ast%))) | |
78 | ||
79 | DEF FNis_macro_call(ast%, env%) | |
80 | LOCAL car%, val% | |
81 | IF NOT FNis_list(ast%) THEN =FALSE | |
82 | car% = FNfirst(ast%) | |
83 | IF NOT FNis_symbol(car%) THEN =FALSE | |
84 | IF FNis_nil(FNenv_find(env%, car%)) THEN =FALSE | |
85 | val% = FNenv_get(env%, car%) | |
86 | =FNis_macro(val%) | |
87 | ||
88 | DEF FNmacroexpand(ast%, env%) | |
89 | LOCAL mac%, macenv%, macast% | |
90 | WHILE FNis_macro_call(ast%, env%) | |
91 | REM PRINT "expanded ";FNpr_str(ast%, TRUE); | |
92 | mac% = FNenv_get(env%, FNfirst(ast%)) | |
93 | macenv% = FNnew_env(FNfn_env(mac%), FNfn_params(mac%), FNrest(ast%)) | |
94 | macast% = FNfn_ast(mac%) | |
95 | ast% = FNEVAL(macast%, macenv%) | |
96 | REM PRINT " to ";FNpr_str(ast%, TRUE) | |
97 | ENDWHILE | |
98 | =ast% | |
99 | ||
100 | DEF FNtry_catch(ast%, env%) | |
101 | LOCAL is_error%, ret% | |
102 | REM If there's no 'catch*' clause then we just evaluate the 'try*'. | |
103 | IF FNcount(ast%) < 3 THEN =FNEVAL(FNnth(ast%, 1), env%) | |
104 | IF FNunbox_symbol(FNfirst(FNnth(ast%, 2))) <> "catch*" THEN | |
105 | ERROR &40E80924, "Invalid 'catch*' clause" | |
106 | ENDIF | |
107 | ret% = FNtry(FNnth(ast%, 1), env%, is_error%) | |
108 | IF is_error% THEN =FNcatch(FNnth(ast%, 2), env%, ret%) | |
109 | =ret% | |
110 | ||
111 | REM Evaluate an expression, returning either the result or an exception | |
112 | REM raised during evaluation. is_error% indicates which it was. | |
113 | DEF FNtry(ast%, env%, RETURN is_error%) | |
114 | LOCAL trysav% | |
115 | trysav% = FNgc_save | |
116 | is_error% = FALSE | |
117 | LOCAL ERROR | |
118 | ON ERROR LOCAL is_error% = TRUE : =FNgc_restore(trysav%, FNwrap_exception) | |
119 | =FNgc_restore(trysav%, FNEVAL(ast%, env%)) | |
120 | ||
121 | REM Return a mal value corresponding to the most-recently thrown exception. | |
122 | DEF FNwrap_exception | |
347af951 BH |
123 | REM There are three cases to handle. When the error was generated |
124 | REM by 'throw', we should return the value that 'throw' stashed in | |
125 | REM MAL_ERR%. When the error was generated by mal, we should just | |
126 | REM return the error message. When the error was generated by BASIC | |
127 | REM or the OS, we should wrap the message and the error number in | |
128 | REM a hash-map. | |
159ebed9 | 129 | IF ERR = &40E80900 THEN =MAL_ERR% : REM Error generated by 'throw' |
347af951 BH |
130 | IF (ERR AND &FFFFFF00) = &40E80900 THEN =FNalloc_string(REPORT$) |
131 | LOCAL e% | |
132 | e% = FNhashmap_set(FNempty_hashmap, "err", FNalloc_int(ERR)) | |
133 | =FNhashmap_set(e%, "message", FNalloc_string(REPORT$)) | |
159ebed9 BH |
134 | |
135 | DEF FNcatch(ast%, env%, err%) | |
136 | LOCAL binds%, exprs% | |
137 | binds% = FNalloc_pair(FNnth(ast%, 1), FNempty) | |
138 | exprs% = FNalloc_pair(err%, FNempty) | |
139 | env% = FNnew_env(env%, binds%, exprs%) | |
140 | =FNEVAL(FNnth(ast%, 2), env%) | |
141 | ||
142 | DEF FNEVAL(ast%, env%) | |
143 | PROCgc_enter | |
144 | =FNgc_exit(FNEVAL_(ast%, env%)) | |
145 | ||
146 | DEF FNEVAL_(ast%, env%) | |
147 | LOCAL car%, specialform%, val%, bindings% | |
148 | REPEAT | |
149 | PROCgc_keep_only2(ast%, env%) | |
150 | IF NOT FNis_list(ast%) THEN =FNeval_ast(ast%, env%) | |
151 | IF FNis_empty(ast%) THEN =ast% | |
152 | ast% = FNmacroexpand(ast%, env%) | |
153 | IF NOT FNis_list(ast%) THEN =FNeval_ast(ast%, env%) | |
154 | car% = FNfirst(ast%) | |
155 | specialform% = FALSE | |
156 | IF FNis_symbol(car%) THEN | |
157 | specialform% = TRUE | |
158 | CASE FNunbox_symbol(car%) OF | |
159 | REM Special forms | |
160 | WHEN "def!" | |
161 | val% = FNEVAL(FNnth(ast%, 2), env%) | |
162 | PROCenv_set(env%, FNnth(ast%, 1), val%) | |
163 | =val% | |
164 | WHEN "defmacro!" | |
165 | val% = FNEVAL(FNnth(ast%, 2), env%) | |
90d90dfd | 166 | IF FNis_fn(val%) THEN val% = FNas_macro(val%) |
159ebed9 BH |
167 | PROCenv_set(env%, FNnth(ast%, 1), val%) |
168 | =val% | |
169 | WHEN "let*" | |
170 | env% = FNalloc_environment(env%) | |
171 | bindings% = FNnth(ast%, 1) | |
172 | WHILE NOT FNis_empty(bindings%) | |
173 | PROCenv_set(env%, FNfirst(bindings%), FNEVAL(FNnth(bindings%, 1), env%)) | |
174 | bindings% = FNrest(FNrest(bindings%)) | |
175 | ENDWHILE | |
176 | ast% = FNnth(ast%, 2) | |
177 | REM Loop round for tail-call optimisation. | |
178 | WHEN "do" | |
179 | REM The guide has us call FNeval_ast on the sub-list that excludes | |
180 | REM the last element of ast%, but that's a bit painful without | |
181 | REM native list slicing, so it's easier to just re-implement the | |
182 | REM bit of FNeval_ast that we need. | |
183 | ast% = FNrest(ast%) | |
184 | WHILE NOT FNis_empty(FNrest(ast%)) | |
185 | val% = FNEVAL(FNfirst(ast%), env%) | |
186 | ast% = FNrest(ast%) | |
187 | ENDWHILE | |
188 | ast% = FNfirst(ast%) | |
189 | WHEN "if" | |
190 | IF FNis_truish(FNEVAL(FNnth(ast%, 1), env%)) THEN | |
191 | ast% = FNnth(ast%, 2) | |
192 | ELSE | |
193 | IF FNcount(ast%) = 3 THEN =FNnil ELSE ast% = FNnth(ast%, 3) | |
194 | ENDIF | |
195 | REM Loop round for tail-call optimisation. | |
196 | WHEN "fn*" | |
197 | =FNalloc_fn(FNnth(ast%, 2), FNnth(ast%, 1), env%) | |
198 | WHEN "quote" | |
199 | =FNnth(ast%, 1) | |
200 | WHEN "quasiquote" | |
201 | ast% = FNquasiquote(FNnth(ast%, 1)) | |
202 | REM Loop round for tail-call optimisation | |
203 | WHEN "macroexpand" | |
204 | =FNmacroexpand(FNnth(ast%, 1), env%) | |
205 | WHEN "try*" | |
206 | =FNtry_catch(ast%, env%) | |
207 | OTHERWISE | |
208 | specialform% = FALSE | |
209 | ENDCASE | |
210 | ENDIF | |
211 | IF NOT specialform% THEN | |
212 | REM This is the "apply" part. | |
213 | ast% = FNeval_ast(ast%, env%) | |
214 | car% = FNfirst(ast%) | |
215 | IF FNis_corefn(car%) THEN | |
216 | =FNcore_call(FNunbox_corefn(car%), FNrest(ast%)) | |
217 | ENDIF | |
218 | IF FNis_fn(car%) THEN | |
219 | env% = FNnew_env(FNfn_env(car%), FNfn_params(car%), FNrest(ast%)) | |
220 | ast% = FNfn_ast(car%) | |
221 | REM Loop round for tail-call optimisation. | |
222 | ELSE | |
223 | ERROR &40E80918, "Not a function" | |
224 | ENDIF | |
225 | ENDIF | |
226 | UNTIL FALSE | |
227 | ||
228 | DEF FNPRINT(a%) | |
0f97a00d | 229 | =FNunbox_string(FNpr_str(a%, TRUE)) |
159ebed9 BH |
230 | |
231 | DEF FNrep(a$) | |
232 | =FNPRINT(FNEVAL(FNREAD(a$), repl_env%)) | |
233 | ||
234 | DEF FNeval_ast(ast%, env%) | |
235 | LOCAL val%, car%, cdr%, map%, keys%, key$ | |
236 | IF FNis_symbol(ast%) THEN =FNenv_get(env%, ast%) | |
237 | IF FNis_seq(ast%) THEN | |
238 | IF FNis_empty(ast%) THEN =ast% | |
239 | car% = FNEVAL(FNfirst(ast%), env%) | |
240 | cdr% = FNeval_ast(FNrest(ast%), env%) | |
241 | IF FNis_vector(ast%) THEN =FNalloc_vector_pair(car%, cdr%) | |
242 | =FNalloc_pair(car%, cdr%) | |
243 | ENDIF | |
244 | IF FNis_hashmap(ast%) THEN | |
245 | map% = FNempty_hashmap | |
246 | keys% = FNhashmap_keys(ast%) | |
247 | WHILE NOT FNis_empty(keys%) | |
248 | key$ = FNunbox_string(FNfirst(keys%)) | |
249 | map% = FNhashmap_set(map%, key$, FNEVAL(FNhashmap_get(ast%, key$), env%)) | |
250 | keys% = FNrest(keys%) | |
251 | ENDWHILE | |
252 | =map% | |
253 | ENDIF | |
254 | =ast% | |
255 | ||
256 | DEF FNget_argv | |
257 | PROCgc_enter | |
258 | LOCAL argv%, rargv%, cmdptr%, arg$, len% | |
259 | argv% = FNempty | |
260 | IF !PAGE = &D7C1C7C5 THEN | |
261 | REM Running under Brandy, so ARGC and ARGV$ are usable. | |
262 | IF ARGC >= 1 THEN | |
263 | FOR i% = ARGC TO 1 STEP -1 | |
264 | argv% = FNalloc_pair(FNalloc_string(ARGV$(i%)), argv%) | |
265 | NEXT i% | |
266 | ENDIF | |
267 | ELSE | |
268 | IF (INKEY(-256) AND &F0) = &A0 THEN | |
269 | rargv% = FNempty | |
270 | REM Running under RISC OS | |
271 | REM Vexingly, we can only get the command line that was passed to | |
272 | REM the BASIC interpreter. This means that we need to extract | |
273 | REM the arguments from that. Typically, we will have been started | |
274 | REM with "BASIC -quit <filename> <args>". | |
275 | ||
276 | DIM q% 256 | |
277 | SYS "OS_GetEnv" TO cmdptr% | |
278 | WHILE ?cmdptr% >= 32 | |
279 | SYS "OS_GSTrans", cmdptr%, q%, &20000000 + 256 TO cmdptr%, , len% | |
280 | q%?len% = 13 | |
281 | rargv% = FNalloc_pair(FNalloc_string($q%), rargv%) | |
282 | ENDWHILE | |
283 | REM Put argv back into the right order. | |
284 | WHILE NOT FNis_empty(rargv%) | |
285 | argv% = FNalloc_pair(FNfirst(rargv%), argv%) | |
286 | rargv% = FNrest(rargv%) | |
287 | ENDWHILE | |
288 | IF FNis_empty(argv%) THEN =FNgc_exit(argv%) | |
289 | argv% = FNrest(argv%) : REM skip "BASIC" | |
290 | IF FNis_empty(argv%) THEN =FNgc_exit(argv%) | |
291 | IF FNunbox_string(FNfirst(argv%)) <> "-quit" THEN =FNgc_exit(argv%) | |
292 | argv% = FNrest(argv%) : REM skip "-quit" | |
293 | IF FNis_empty(argv%) THEN =FNgc_exit(argv%) | |
294 | argv% = FNrest(argv%) : REM skip filename | |
295 | ENDIF | |
296 | ENDIF | |
297 | =FNgc_exit(argv%) | |
298 | ||
299 | ||
300 | REM Local Variables: | |
301 | REM indent-tabs-mode: nil | |
302 | REM End: |