bbc-basic: Add 'seq' core function.
[jackhill/mal.git] / bbc-basic / stepA_mal.bbc
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)))
23DATA (def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))
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))))))))
ae5bbd45 26DATA (def! *host-language* "BBC BASIC V")
159ebed9
BH
27DATA ""
28REPEAT
29 READ form$
30 IF form$ <> "" THEN val$ = FNrep(form$)
31UNTIL form$ = ""
32
33argv% = FNget_argv
34
35IF FNis_empty(argv%) THEN
36 PROCenv_set(repl_env%, FNalloc_symbol("*ARGV*"), FNempty)
37ELSE
38 PROCenv_set(repl_env%, FNalloc_symbol("*ARGV*"), FNrest(argv%))
39 val$ = FNrep("(load-file " + FNpr_str(FNfirst(argv%), TRUE) + ")")
40 END
41ENDIF
42
ae5bbd45 43val$ = FNrep("(println (str ""Mal ["" *host-language* ""]""))")
159ebed9
BH
44REPEAT
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%)
52UNTIL FALSE
53
54END
55
56DEF FNREAD(a$)
57=FNread_str(a$)
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
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
126DEF 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
133DEF FNEVAL(ast%, env%)
134 PROCgc_enter
135=FNgc_exit(FNEVAL_(ast%, env%))
136
137DEF 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
219DEF FNPRINT(a%)
220=FNpr_str(a%, TRUE)
221
222DEF FNrep(a$)
223=FNPRINT(FNEVAL(FNREAD(a$), repl_env%))
224
225DEF 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
247DEF 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
291REM Local Variables:
292REM indent-tabs-mode: nil
293REM End: