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 (def! inc (fn* [x] (+ x 1)))
26 DATA (def! gensym (let* [counter (atom 0)] (fn* [] (symbol (str "G__" (swap! counter inc))))))
27 DATA (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))
28 DATA (def! *host-language* "BBC BASIC V")
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 " + FNunbox_string(FNpr_str(FNfirst(argv%), TRUE)) + ")")
45 val$ = FNrep("(println (str ""Mal ["" *host-language* ""]""))")
48 REM Catch all errors apart from "Escape".
49 ON ERROR LOCAL IF ERR = 17 ON ERROR OFF: ERROR ERR, REPORT$ ELSE PRINT REPORT$
60 =FNread_str(FNalloc_string(a$))
63 =FNis_seq(val%) AND NOT FNis_empty(val%)
65 DEF FNquasiquote(ast%)
67 IF NOT FNis_pair(ast%) THEN =FNalloc_list2(FNalloc_symbol("quote"), ast%)
69 IF FNis_symbol(car%) THEN
70 IF FNunbox_symbol(car%) = "unquote" THEN =FNnth(ast%, 1)
72 IF FNis_pair(car%) THEN
74 IF FNis_symbol(caar%) THEN
75 IF FNunbox_symbol(caar%) = "splice-unquote" THEN
76 =FNalloc_list3(FNalloc_symbol("concat"), FNnth(car%, 1), FNquasiquote(FNrest(ast%)))
80 =FNalloc_list3(FNalloc_symbol("cons"), FNquasiquote(car%), FNquasiquote(FNrest(ast%)))
82 DEF FNis_macro_call(ast%, env%)
84 IF NOT FNis_list(ast%) THEN =FALSE
86 IF NOT FNis_symbol(car%) THEN =FALSE
87 IF FNis_nil(FNenv_find(env%, car%)) THEN =FALSE
88 val% = FNenv_get(env%, car%)
91 DEF FNmacroexpand(ast%, env%)
92 LOCAL mac%, macenv%, macast%
93 WHILE FNis_macro_call(ast%, env%)
94 REM PRINT "expanded ";FNpr_str(ast%, TRUE);
95 mac% = FNenv_get(env%, FNfirst(ast%))
96 macenv% = FNnew_env(FNfn_env(mac%), FNfn_params(mac%), FNrest(ast%))
97 macast% = FNfn_ast(mac%)
98 ast% = FNEVAL(macast%, macenv%)
99 REM PRINT " to ";FNpr_str(ast%, TRUE)
103 DEF FNtry_catch(ast%, env%)
104 LOCAL is_error%, ret%
105 REM If there's no 'catch*' clause then we just evaluate the 'try*'.
106 IF FNcount(ast%) < 3 THEN =FNEVAL(FNnth(ast%, 1), env%)
107 IF FNunbox_symbol(FNfirst(FNnth(ast%, 2))) <> "catch*" THEN
108 ERROR &40E80924, "Invalid 'catch*' clause"
110 ret% = FNtry(FNnth(ast%, 1), env%, is_error%)
111 IF is_error% THEN =FNcatch(FNnth(ast%, 2), env%, ret%)
114 REM Evaluate an expression, returning either the result or an exception
115 REM raised during evaluation. is_error% indicates which it was.
116 DEF FNtry(ast%, env%, RETURN is_error%)
121 ON ERROR LOCAL is_error% = TRUE : =FNgc_restore(trysav%, FNwrap_exception)
122 =FNgc_restore(trysav%, FNEVAL(ast%, env%))
124 REM Return a mal value corresponding to the most-recently thrown exception.
126 REM There are three cases to handle. When the error was generated
127 REM by 'throw', we should return the value that 'throw' stashed in
128 REM MAL_ERR%. When the error was generated by mal, we should just
129 REM return the error message. When the error was generated by BASIC
130 REM or the OS, we should wrap the message and the error number in
132 IF ERR = &40E80900 THEN =MAL_ERR% : REM Error generated by 'throw'
133 IF (ERR AND &FFFFFF00) = &40E80900 THEN =FNalloc_string(REPORT$)
135 e% = FNhashmap_set(FNempty_hashmap, "err", FNalloc_int(ERR))
136 =FNhashmap_set(e%, "message", FNalloc_string(REPORT$))
138 DEF FNcatch(ast%, env%, err%)
140 binds% = FNalloc_pair(FNnth(ast%, 1), FNempty)
141 exprs% = FNalloc_pair(err%, FNempty)
142 env% = FNnew_env(env%, binds%, exprs%)
143 =FNEVAL(FNnth(ast%, 2), env%)
145 DEF FNEVAL(ast%, env%)
147 =FNgc_exit(FNEVAL_(ast%, env%))
149 DEF FNEVAL_(ast%, env%)
150 LOCAL car%, specialform%, val%, bindings%
152 PROCgc_keep_only2(ast%, env%)
153 IF NOT FNis_list(ast%) THEN =FNeval_ast(ast%, env%)
154 IF FNis_empty(ast%) THEN =ast%
155 ast% = FNmacroexpand(ast%, env%)
156 IF NOT FNis_list(ast%) THEN =FNeval_ast(ast%, env%)
159 IF FNis_symbol(car%) THEN
161 CASE FNunbox_symbol(car%) OF
164 val% = FNEVAL(FNnth(ast%, 2), env%)
165 PROCenv_set(env%, FNnth(ast%, 1), val%)
168 val% = FNEVAL(FNnth(ast%, 2), env%)
169 IF FNis_fn(val%) THEN val% = FNas_macro(val%)
170 PROCenv_set(env%, FNnth(ast%, 1), val%)
173 env% = FNalloc_environment(env%)
174 bindings% = FNnth(ast%, 1)
175 WHILE NOT FNis_empty(bindings%)
176 PROCenv_set(env%, FNfirst(bindings%), FNEVAL(FNnth(bindings%, 1), env%))
177 bindings% = FNrest(FNrest(bindings%))
179 ast% = FNnth(ast%, 2)
180 REM Loop round for tail-call optimisation.
182 REM The guide has us call FNeval_ast on the sub-list that excludes
183 REM the last element of ast%, but that's a bit painful without
184 REM native list slicing, so it's easier to just re-implement the
185 REM bit of FNeval_ast that we need.
187 WHILE NOT FNis_empty(FNrest(ast%))
188 val% = FNEVAL(FNfirst(ast%), env%)
193 IF FNis_truish(FNEVAL(FNnth(ast%, 1), env%)) THEN
194 ast% = FNnth(ast%, 2)
196 IF FNcount(ast%) = 3 THEN =FNnil ELSE ast% = FNnth(ast%, 3)
198 REM Loop round for tail-call optimisation.
200 =FNalloc_fn(FNnth(ast%, 2), FNnth(ast%, 1), env%)
204 ast% = FNquasiquote(FNnth(ast%, 1))
205 REM Loop round for tail-call optimisation
207 =FNmacroexpand(FNnth(ast%, 1), env%)
209 =FNtry_catch(ast%, env%)
214 IF NOT specialform% THEN
215 REM This is the "apply" part.
216 ast% = FNeval_ast(ast%, env%)
218 IF FNis_corefn(car%) THEN
219 =FNcore_call(FNunbox_corefn(car%), FNrest(ast%))
221 IF FNis_fn(car%) THEN
222 env% = FNnew_env(FNfn_env(car%), FNfn_params(car%), FNrest(ast%))
223 ast% = FNfn_ast(car%)
224 REM Loop round for tail-call optimisation.
226 ERROR &40E80918, "Not a function"
232 =FNunbox_string(FNpr_str(a%, TRUE))
235 =FNPRINT(FNEVAL(FNREAD(a$), repl_env%))
237 DEF FNeval_ast(ast%, env%)
238 LOCAL val%, car%, cdr%, map%, keys%, key$
239 IF FNis_symbol(ast%) THEN =FNenv_get(env%, ast%)
240 IF FNis_seq(ast%) THEN
241 IF FNis_empty(ast%) THEN =ast%
242 car% = FNEVAL(FNfirst(ast%), env%)
243 cdr% = FNeval_ast(FNrest(ast%), env%)
244 IF FNis_vector(ast%) THEN =FNalloc_vector_pair(car%, cdr%)
245 =FNalloc_pair(car%, cdr%)
247 IF FNis_hashmap(ast%) THEN
248 map% = FNempty_hashmap
249 keys% = FNhashmap_keys(ast%)
250 WHILE NOT FNis_empty(keys%)
251 key$ = FNunbox_string(FNfirst(keys%))
252 map% = FNhashmap_set(map%, key$, FNEVAL(FNhashmap_get(ast%, key$), env%))
253 keys% = FNrest(keys%)
261 LOCAL argv%, rargv%, cmdptr%, arg$, len%
263 IF !PAGE = &D7C1C7C5 THEN
264 REM Running under Brandy, so ARGC and ARGV$ are usable.
266 FOR i% = ARGC TO 1 STEP -1
267 argv% = FNalloc_pair(FNalloc_string(ARGV$(i%)), argv%)
271 IF (INKEY(-256) AND &F0) = &A0 THEN
273 REM Running under RISC OS
274 REM Vexingly, we can only get the command line that was passed to
275 REM the BASIC interpreter. This means that we need to extract
276 REM the arguments from that. Typically, we will have been started
277 REM with "BASIC -quit <filename> <args>".
280 SYS "OS_GetEnv" TO cmdptr%
282 SYS "OS_GSTrans", cmdptr%, q%, &20000000 + 256 TO cmdptr%, , len%
284 rargv% = FNalloc_pair(FNalloc_string($q%), rargv%)
286 REM Put argv back into the right order.
287 WHILE NOT FNis_empty(rargv%)
288 argv% = FNalloc_pair(FNfirst(rargv%), argv%)
289 rargv% = FNrest(rargv%)
291 IF FNis_empty(argv%) THEN =FNgc_exit(argv%)
292 argv% = FNrest(argv%) : REM skip "BASIC"
293 IF FNis_empty(argv%) THEN =FNgc_exit(argv%)
294 IF FNunbox_string(FNfirst(argv%)) <> "-quit" THEN =FNgc_exit(argv%)
295 argv% = FNrest(argv%) : REM skip "-quit"
296 IF FNis_empty(argv%) THEN =FNgc_exit(argv%)
297 argv% = FNrest(argv%) : REM skip filename
304 REM indent-tabs-mode: nil