1 REM Step A of mal in BBC BASIC
11 repl_env% = FNalloc_environment(FNnil)
12 PROCcore_ns : REM This sets the data pointer
16 PROCenv_set(repl_env%, FNalloc_symbol(sym$), FNalloc_corefn(i%))
20 REM Initial forms to evaluate
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))))))))
26 DATA (def! *host-language* "BBC BASIC V")
27 DATA (def! notimpl (fn* (& _) (throw "Not implemented")))
28 DATA (def! seq notimpl)
32 IF form$ <> "" THEN val$ = FNrep(form$)
37 IF FNis_empty(argv%) THEN
38 PROCenv_set(repl_env%, FNalloc_symbol("*ARGV*"), FNempty)
40 PROCenv_set(repl_env%, FNalloc_symbol("*ARGV*"), FNrest(argv%))
41 val$ = FNrep("(load-file " + FNpr_str(FNfirst(argv%), TRUE) + ")")
45 val$ = FNrep("(println (str ""Mal ["" *host-language* ""]""))")
47 REM Catch all errors apart from "Escape".
48 ON ERROR LOCAL PRINT REPORT$:IF ERR = 17 THEN END ELSE PROCgc_restore(sav%)
62 =FNis_seq(val%) AND NOT FNis_empty(val%)
64 DEF FNquasiquote(ast%)
66 IF NOT FNis_pair(ast%) THEN =FNalloc_list2(FNalloc_symbol("quote"), ast%)
68 IF FNis_symbol(car%) THEN
69 IF FNunbox_symbol(car%) = "unquote" THEN =FNnth(ast%, 1)
71 IF FNis_pair(car%) THEN
73 IF FNis_symbol(caar%) THEN
74 IF FNunbox_symbol(caar%) = "splice-unquote" THEN
75 =FNalloc_list3(FNalloc_symbol("concat"), FNnth(car%, 1), FNquasiquote(FNrest(ast%)))
79 =FNalloc_list3(FNalloc_symbol("cons"), FNquasiquote(car%), FNquasiquote(FNrest(ast%)))
81 DEF FNis_macro_call(ast%, env%)
83 IF NOT FNis_list(ast%) THEN =FALSE
85 IF NOT FNis_symbol(car%) THEN =FALSE
86 IF FNis_nil(FNenv_find(env%, car%)) THEN =FALSE
87 val% = FNenv_get(env%, car%)
90 DEF FNmacroexpand(ast%, env%)
91 LOCAL mac%, macenv%, macast%
92 WHILE FNis_macro_call(ast%, env%)
93 REM PRINT "expanded ";FNpr_str(ast%, TRUE);
94 mac% = FNenv_get(env%, FNfirst(ast%))
95 macenv% = FNnew_env(FNfn_env(mac%), FNfn_params(mac%), FNrest(ast%))
96 macast% = FNfn_ast(mac%)
97 ast% = FNEVAL(macast%, macenv%)
98 REM PRINT " to ";FNpr_str(ast%, TRUE)
102 DEF FNtry_catch(ast%, env%)
103 LOCAL is_error%, ret%
104 REM If there's no 'catch*' clause then we just evaluate the 'try*'.
105 IF FNcount(ast%) < 3 THEN =FNEVAL(FNnth(ast%, 1), env%)
106 IF FNunbox_symbol(FNfirst(FNnth(ast%, 2))) <> "catch*" THEN
107 ERROR &40E80924, "Invalid 'catch*' clause"
109 ret% = FNtry(FNnth(ast%, 1), env%, is_error%)
110 IF is_error% THEN =FNcatch(FNnth(ast%, 2), env%, ret%)
113 REM Evaluate an expression, returning either the result or an exception
114 REM raised during evaluation. is_error% indicates which it was.
115 DEF FNtry(ast%, env%, RETURN is_error%)
120 ON ERROR LOCAL is_error% = TRUE : =FNgc_restore(trysav%, FNwrap_exception)
121 =FNgc_restore(trysav%, FNEVAL(ast%, env%))
123 REM Return a mal value corresponding to the most-recently thrown exception.
125 IF ERR = &40E80900 THEN =MAL_ERR% : REM Error generated by 'throw'
126 =FNalloc_string(REPORT$) : REM OS error or error generated by mal
128 DEF FNcatch(ast%, env%, err%)
130 binds% = FNalloc_pair(FNnth(ast%, 1), FNempty)
131 exprs% = FNalloc_pair(err%, FNempty)
132 env% = FNnew_env(env%, binds%, exprs%)
133 =FNEVAL(FNnth(ast%, 2), env%)
135 DEF FNEVAL(ast%, env%)
137 =FNgc_exit(FNEVAL_(ast%, env%))
139 DEF FNEVAL_(ast%, env%)
140 LOCAL car%, specialform%, val%, bindings%
142 PROCgc_keep_only2(ast%, env%)
143 IF NOT FNis_list(ast%) THEN =FNeval_ast(ast%, env%)
144 IF FNis_empty(ast%) THEN =ast%
145 ast% = FNmacroexpand(ast%, env%)
146 IF NOT FNis_list(ast%) THEN =FNeval_ast(ast%, env%)
149 IF FNis_symbol(car%) THEN
151 CASE FNunbox_symbol(car%) OF
154 val% = FNEVAL(FNnth(ast%, 2), env%)
155 PROCenv_set(env%, FNnth(ast%, 1), val%)
158 val% = FNEVAL(FNnth(ast%, 2), env%)
159 IF FNis_fn(val%) THEN PROCmake_macro(val%)
160 PROCenv_set(env%, FNnth(ast%, 1), val%)
163 env% = FNalloc_environment(env%)
164 bindings% = FNnth(ast%, 1)
165 WHILE NOT FNis_empty(bindings%)
166 PROCenv_set(env%, FNfirst(bindings%), FNEVAL(FNnth(bindings%, 1), env%))
167 bindings% = FNrest(FNrest(bindings%))
169 ast% = FNnth(ast%, 2)
170 REM Loop round for tail-call optimisation.
172 REM The guide has us call FNeval_ast on the sub-list that excludes
173 REM the last element of ast%, but that's a bit painful without
174 REM native list slicing, so it's easier to just re-implement the
175 REM bit of FNeval_ast that we need.
177 WHILE NOT FNis_empty(FNrest(ast%))
178 val% = FNEVAL(FNfirst(ast%), env%)
183 IF FNis_truish(FNEVAL(FNnth(ast%, 1), env%)) THEN
184 ast% = FNnth(ast%, 2)
186 IF FNcount(ast%) = 3 THEN =FNnil ELSE ast% = FNnth(ast%, 3)
188 REM Loop round for tail-call optimisation.
190 =FNalloc_fn(FNnth(ast%, 2), FNnth(ast%, 1), env%)
194 ast% = FNquasiquote(FNnth(ast%, 1))
195 REM Loop round for tail-call optimisation
197 =FNmacroexpand(FNnth(ast%, 1), env%)
199 =FNtry_catch(ast%, env%)
204 IF NOT specialform% THEN
205 REM This is the "apply" part.
206 ast% = FNeval_ast(ast%, env%)
208 IF FNis_corefn(car%) THEN
209 =FNcore_call(FNunbox_corefn(car%), FNrest(ast%))
211 IF FNis_fn(car%) THEN
212 env% = FNnew_env(FNfn_env(car%), FNfn_params(car%), FNrest(ast%))
213 ast% = FNfn_ast(car%)
214 REM Loop round for tail-call optimisation.
216 ERROR &40E80918, "Not a function"
225 =FNPRINT(FNEVAL(FNREAD(a$), repl_env%))
227 DEF FNeval_ast(ast%, env%)
228 LOCAL val%, car%, cdr%, map%, keys%, key$
229 IF FNis_symbol(ast%) THEN =FNenv_get(env%, ast%)
230 IF FNis_seq(ast%) THEN
231 IF FNis_empty(ast%) THEN =ast%
232 car% = FNEVAL(FNfirst(ast%), env%)
233 cdr% = FNeval_ast(FNrest(ast%), env%)
234 IF FNis_vector(ast%) THEN =FNalloc_vector_pair(car%, cdr%)
235 =FNalloc_pair(car%, cdr%)
237 IF FNis_hashmap(ast%) THEN
238 map% = FNempty_hashmap
239 keys% = FNhashmap_keys(ast%)
240 WHILE NOT FNis_empty(keys%)
241 key$ = FNunbox_string(FNfirst(keys%))
242 map% = FNhashmap_set(map%, key$, FNEVAL(FNhashmap_get(ast%, key$), env%))
243 keys% = FNrest(keys%)
251 LOCAL argv%, rargv%, cmdptr%, arg$, len%
253 IF !PAGE = &D7C1C7C5 THEN
254 REM Running under Brandy, so ARGC and ARGV$ are usable.
256 FOR i% = ARGC TO 1 STEP -1
257 argv% = FNalloc_pair(FNalloc_string(ARGV$(i%)), argv%)
261 IF (INKEY(-256) AND &F0) = &A0 THEN
263 REM Running under RISC OS
264 REM Vexingly, we can only get the command line that was passed to
265 REM the BASIC interpreter. This means that we need to extract
266 REM the arguments from that. Typically, we will have been started
267 REM with "BASIC -quit <filename> <args>".
270 SYS "OS_GetEnv" TO cmdptr%
272 SYS "OS_GSTrans", cmdptr%, q%, &20000000 + 256 TO cmdptr%, , len%
274 rargv% = FNalloc_pair(FNalloc_string($q%), rargv%)
276 REM Put argv back into the right order.
277 WHILE NOT FNis_empty(rargv%)
278 argv% = FNalloc_pair(FNfirst(rargv%), argv%)
279 rargv% = FNrest(rargv%)
281 IF FNis_empty(argv%) THEN =FNgc_exit(argv%)
282 argv% = FNrest(argv%) : REM skip "BASIC"
283 IF FNis_empty(argv%) THEN =FNgc_exit(argv%)
284 IF FNunbox_string(FNfirst(argv%)) <> "-quit" THEN =FNgc_exit(argv%)
285 argv% = FNrest(argv%) : REM skip "-quit"
286 IF FNis_empty(argv%) THEN =FNgc_exit(argv%)
287 argv% = FNrest(argv%) : REM skip filename
294 REM indent-tabs-mode: nil