DISABLE FDs (REMOVE ME).
[jackhill/mal.git] / bbc-basic / stepA_mal.bas
CommitLineData
159ebed9
BH
1REM Step A 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)))
e6d41de4 23DATA (def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))
159ebed9 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)))))))
ae5bbd45 25DATA (def! *host-language* "BBC BASIC V")
159ebed9
BH
26DATA ""
27REPEAT
28 READ form$
29 IF form$ <> "" THEN val$ = FNrep(form$)
30UNTIL form$ = ""
31
32argv% = FNget_argv
33
34IF FNis_empty(argv%) THEN
35 PROCenv_set(repl_env%, FNalloc_symbol("*ARGV*"), FNempty)
36ELSE
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
40ENDIF
41
ae5bbd45 42val$ = FNrep("(println (str ""Mal ["" *host-language* ""]""))")
d4b2e8b2 43sav% = FNgc_save
159ebed9
BH
44REPEAT
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
52UNTIL FALSE
53
54END
55
56DEF FNREAD(a$)
0f97a00d 57=FNread_str(FNalloc_string(a$))
159ebed9
BH
58
59DEF FNis_pair(val%)
60=FNis_seq(val%) AND NOT FNis_empty(val%)
61
62DEF 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
79DEF 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
88DEF 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
100DEF 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
111REM Evaluate an expression, returning either the result or an exception
112REM raised during evaluation. is_error% indicates which it was.
113DEF 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
121REM Return a mal value corresponding to the most-recently thrown exception.
122DEF 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
135DEF 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
142DEF FNEVAL(ast%, env%)
143 PROCgc_enter
144=FNgc_exit(FNEVAL_(ast%, env%))
145
146DEF 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
228DEF FNPRINT(a%)
0f97a00d 229=FNunbox_string(FNpr_str(a%, TRUE))
159ebed9
BH
230
231DEF FNrep(a$)
232=FNPRINT(FNEVAL(FNREAD(a$), repl_env%))
233
234DEF 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
256DEF 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
300REM Local Variables:
301REM indent-tabs-mode: nil
302REM End: