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))) | |
23 | DATA (def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")"))))) | |
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))))))) | |
25 | DATA (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)))))))) | |
ae5bbd45 | 26 | DATA (def! *host-language* "BBC BASIC V") |
159ebed9 BH |
27 | DATA "" |
28 | REPEAT | |
29 | READ form$ | |
30 | IF form$ <> "" THEN val$ = FNrep(form$) | |
31 | UNTIL form$ = "" | |
32 | ||
33 | argv% = FNget_argv | |
34 | ||
35 | IF FNis_empty(argv%) THEN | |
36 | PROCenv_set(repl_env%, FNalloc_symbol("*ARGV*"), FNempty) | |
37 | ELSE | |
38 | PROCenv_set(repl_env%, FNalloc_symbol("*ARGV*"), FNrest(argv%)) | |
39 | val$ = FNrep("(load-file " + FNpr_str(FNfirst(argv%), TRUE) + ")") | |
40 | END | |
41 | ENDIF | |
42 | ||
ae5bbd45 | 43 | val$ = FNrep("(println (str ""Mal ["" *host-language* ""]""))") |
159ebed9 BH |
44 | REPEAT |
45 | REM Catch all errors apart from "Escape". | |
46 | ON ERROR LOCAL PRINT REPORT$:IF ERR = 17 THEN END ELSE PROCgc_restore(sav%) | |
47 | sav% = FNgc_save | |
48 | PRINT "user> "; | |
49 | LINE INPUT "" line$ | |
50 | PRINT FNrep(line$) | |
51 | PROCgc_restore(sav%) | |
52 | UNTIL FALSE | |
53 | ||
54 | END | |
55 | ||
56 | DEF FNREAD(a$) | |
57 | =FNread_str(a$) | |
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 | |
123 | IF ERR = &40E80900 THEN =MAL_ERR% : REM Error generated by 'throw' | |
124 | =FNalloc_string(REPORT$) : REM OS error or error generated by mal | |
125 | ||
126 | DEF FNcatch(ast%, env%, err%) | |
127 | LOCAL binds%, exprs% | |
128 | binds% = FNalloc_pair(FNnth(ast%, 1), FNempty) | |
129 | exprs% = FNalloc_pair(err%, FNempty) | |
130 | env% = FNnew_env(env%, binds%, exprs%) | |
131 | =FNEVAL(FNnth(ast%, 2), env%) | |
132 | ||
133 | DEF FNEVAL(ast%, env%) | |
134 | PROCgc_enter | |
135 | =FNgc_exit(FNEVAL_(ast%, env%)) | |
136 | ||
137 | DEF FNEVAL_(ast%, env%) | |
138 | LOCAL car%, specialform%, val%, bindings% | |
139 | REPEAT | |
140 | PROCgc_keep_only2(ast%, env%) | |
141 | IF NOT FNis_list(ast%) THEN =FNeval_ast(ast%, env%) | |
142 | IF FNis_empty(ast%) THEN =ast% | |
143 | ast% = FNmacroexpand(ast%, env%) | |
144 | IF NOT FNis_list(ast%) THEN =FNeval_ast(ast%, env%) | |
145 | car% = FNfirst(ast%) | |
146 | specialform% = FALSE | |
147 | IF FNis_symbol(car%) THEN | |
148 | specialform% = TRUE | |
149 | CASE FNunbox_symbol(car%) OF | |
150 | REM Special forms | |
151 | WHEN "def!" | |
152 | val% = FNEVAL(FNnth(ast%, 2), env%) | |
153 | PROCenv_set(env%, FNnth(ast%, 1), val%) | |
154 | =val% | |
155 | WHEN "defmacro!" | |
156 | val% = FNEVAL(FNnth(ast%, 2), env%) | |
157 | IF FNis_fn(val%) THEN PROCmake_macro(val%) | |
158 | PROCenv_set(env%, FNnth(ast%, 1), val%) | |
159 | =val% | |
160 | WHEN "let*" | |
161 | env% = FNalloc_environment(env%) | |
162 | bindings% = FNnth(ast%, 1) | |
163 | WHILE NOT FNis_empty(bindings%) | |
164 | PROCenv_set(env%, FNfirst(bindings%), FNEVAL(FNnth(bindings%, 1), env%)) | |
165 | bindings% = FNrest(FNrest(bindings%)) | |
166 | ENDWHILE | |
167 | ast% = FNnth(ast%, 2) | |
168 | REM Loop round for tail-call optimisation. | |
169 | WHEN "do" | |
170 | REM The guide has us call FNeval_ast on the sub-list that excludes | |
171 | REM the last element of ast%, but that's a bit painful without | |
172 | REM native list slicing, so it's easier to just re-implement the | |
173 | REM bit of FNeval_ast that we need. | |
174 | ast% = FNrest(ast%) | |
175 | WHILE NOT FNis_empty(FNrest(ast%)) | |
176 | val% = FNEVAL(FNfirst(ast%), env%) | |
177 | ast% = FNrest(ast%) | |
178 | ENDWHILE | |
179 | ast% = FNfirst(ast%) | |
180 | WHEN "if" | |
181 | IF FNis_truish(FNEVAL(FNnth(ast%, 1), env%)) THEN | |
182 | ast% = FNnth(ast%, 2) | |
183 | ELSE | |
184 | IF FNcount(ast%) = 3 THEN =FNnil ELSE ast% = FNnth(ast%, 3) | |
185 | ENDIF | |
186 | REM Loop round for tail-call optimisation. | |
187 | WHEN "fn*" | |
188 | =FNalloc_fn(FNnth(ast%, 2), FNnth(ast%, 1), env%) | |
189 | WHEN "quote" | |
190 | =FNnth(ast%, 1) | |
191 | WHEN "quasiquote" | |
192 | ast% = FNquasiquote(FNnth(ast%, 1)) | |
193 | REM Loop round for tail-call optimisation | |
194 | WHEN "macroexpand" | |
195 | =FNmacroexpand(FNnth(ast%, 1), env%) | |
196 | WHEN "try*" | |
197 | =FNtry_catch(ast%, env%) | |
198 | OTHERWISE | |
199 | specialform% = FALSE | |
200 | ENDCASE | |
201 | ENDIF | |
202 | IF NOT specialform% THEN | |
203 | REM This is the "apply" part. | |
204 | ast% = FNeval_ast(ast%, env%) | |
205 | car% = FNfirst(ast%) | |
206 | IF FNis_corefn(car%) THEN | |
207 | =FNcore_call(FNunbox_corefn(car%), FNrest(ast%)) | |
208 | ENDIF | |
209 | IF FNis_fn(car%) THEN | |
210 | env% = FNnew_env(FNfn_env(car%), FNfn_params(car%), FNrest(ast%)) | |
211 | ast% = FNfn_ast(car%) | |
212 | REM Loop round for tail-call optimisation. | |
213 | ELSE | |
214 | ERROR &40E80918, "Not a function" | |
215 | ENDIF | |
216 | ENDIF | |
217 | UNTIL FALSE | |
218 | ||
219 | DEF FNPRINT(a%) | |
220 | =FNpr_str(a%, TRUE) | |
221 | ||
222 | DEF FNrep(a$) | |
223 | =FNPRINT(FNEVAL(FNREAD(a$), repl_env%)) | |
224 | ||
225 | DEF FNeval_ast(ast%, env%) | |
226 | LOCAL val%, car%, cdr%, map%, keys%, key$ | |
227 | IF FNis_symbol(ast%) THEN =FNenv_get(env%, ast%) | |
228 | IF FNis_seq(ast%) THEN | |
229 | IF FNis_empty(ast%) THEN =ast% | |
230 | car% = FNEVAL(FNfirst(ast%), env%) | |
231 | cdr% = FNeval_ast(FNrest(ast%), env%) | |
232 | IF FNis_vector(ast%) THEN =FNalloc_vector_pair(car%, cdr%) | |
233 | =FNalloc_pair(car%, cdr%) | |
234 | ENDIF | |
235 | IF FNis_hashmap(ast%) THEN | |
236 | map% = FNempty_hashmap | |
237 | keys% = FNhashmap_keys(ast%) | |
238 | WHILE NOT FNis_empty(keys%) | |
239 | key$ = FNunbox_string(FNfirst(keys%)) | |
240 | map% = FNhashmap_set(map%, key$, FNEVAL(FNhashmap_get(ast%, key$), env%)) | |
241 | keys% = FNrest(keys%) | |
242 | ENDWHILE | |
243 | =map% | |
244 | ENDIF | |
245 | =ast% | |
246 | ||
247 | DEF FNget_argv | |
248 | PROCgc_enter | |
249 | LOCAL argv%, rargv%, cmdptr%, arg$, len% | |
250 | argv% = FNempty | |
251 | IF !PAGE = &D7C1C7C5 THEN | |
252 | REM Running under Brandy, so ARGC and ARGV$ are usable. | |
253 | IF ARGC >= 1 THEN | |
254 | FOR i% = ARGC TO 1 STEP -1 | |
255 | argv% = FNalloc_pair(FNalloc_string(ARGV$(i%)), argv%) | |
256 | NEXT i% | |
257 | ENDIF | |
258 | ELSE | |
259 | IF (INKEY(-256) AND &F0) = &A0 THEN | |
260 | rargv% = FNempty | |
261 | REM Running under RISC OS | |
262 | REM Vexingly, we can only get the command line that was passed to | |
263 | REM the BASIC interpreter. This means that we need to extract | |
264 | REM the arguments from that. Typically, we will have been started | |
265 | REM with "BASIC -quit <filename> <args>". | |
266 | ||
267 | DIM q% 256 | |
268 | SYS "OS_GetEnv" TO cmdptr% | |
269 | WHILE ?cmdptr% >= 32 | |
270 | SYS "OS_GSTrans", cmdptr%, q%, &20000000 + 256 TO cmdptr%, , len% | |
271 | q%?len% = 13 | |
272 | rargv% = FNalloc_pair(FNalloc_string($q%), rargv%) | |
273 | ENDWHILE | |
274 | REM Put argv back into the right order. | |
275 | WHILE NOT FNis_empty(rargv%) | |
276 | argv% = FNalloc_pair(FNfirst(rargv%), argv%) | |
277 | rargv% = FNrest(rargv%) | |
278 | ENDWHILE | |
279 | IF FNis_empty(argv%) THEN =FNgc_exit(argv%) | |
280 | argv% = FNrest(argv%) : REM skip "BASIC" | |
281 | IF FNis_empty(argv%) THEN =FNgc_exit(argv%) | |
282 | IF FNunbox_string(FNfirst(argv%)) <> "-quit" THEN =FNgc_exit(argv%) | |
283 | argv% = FNrest(argv%) : REM skip "-quit" | |
284 | IF FNis_empty(argv%) THEN =FNgc_exit(argv%) | |
285 | argv% = FNrest(argv%) : REM skip filename | |
286 | ENDIF | |
287 | ENDIF | |
288 | =FNgc_exit(argv%) | |
289 | ||
290 | ||
291 | REM Local Variables: | |
292 | REM indent-tabs-mode: nil | |
293 | REM End: |