1 REM Step 9 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))))))))
29 IF form$ <> "" THEN val$ = FNrep(form$)
34 IF FNis_empty(argv%) THEN
35 PROCenv_set(repl_env%, FNalloc_symbol("*ARGV*"), FNempty)
37 PROCenv_set(repl_env%, FNalloc_symbol("*ARGV*"), FNrest(argv%))
38 val$ = FNrep("(load-file " + FNunbox_string(FNpr_str(FNfirst(argv%), TRUE)) + ")")
44 REM Catch all errors apart from "Escape".
45 ON ERROR LOCAL IF ERR = 17 ON ERROR OFF: ERROR ERR, REPORT$ ELSE PRINT REPORT$
56 =FNread_str(FNalloc_string(a$))
59 =FNis_seq(val%) AND NOT FNis_empty(val%)
61 DEF FNquasiquote(ast%)
63 IF NOT FNis_pair(ast%) THEN =FNalloc_list2(FNalloc_symbol("quote"), ast%)
65 IF FNis_symbol(car%) THEN
66 IF FNunbox_symbol(car%) = "unquote" THEN =FNnth(ast%, 1)
68 IF FNis_pair(car%) THEN
70 IF FNis_symbol(caar%) THEN
71 IF FNunbox_symbol(caar%) = "splice-unquote" THEN
72 =FNalloc_list3(FNalloc_symbol("concat"), FNnth(car%, 1), FNquasiquote(FNrest(ast%)))
76 =FNalloc_list3(FNalloc_symbol("cons"), FNquasiquote(car%), FNquasiquote(FNrest(ast%)))
78 DEF FNis_macro_call(ast%, env%)
80 IF NOT FNis_list(ast%) THEN =FALSE
82 IF NOT FNis_symbol(car%) THEN =FALSE
83 IF FNis_nil(FNenv_find(env%, car%)) THEN =FALSE
84 val% = FNenv_get(env%, car%)
87 DEF FNmacroexpand(ast%, env%)
88 LOCAL mac%, macenv%, macast%
89 WHILE FNis_macro_call(ast%, env%)
90 REM PRINT "expanded ";FNpr_str(ast%, TRUE);
91 mac% = FNenv_get(env%, FNfirst(ast%))
92 macenv% = FNnew_env(FNfn_env(mac%), FNfn_params(mac%), FNrest(ast%))
93 macast% = FNfn_ast(mac%)
94 ast% = FNEVAL(macast%, macenv%)
95 REM PRINT " to ";FNpr_str(ast%, TRUE)
99 DEF FNtry_catch(ast%, env%)
100 LOCAL is_error%, ret%
101 REM If there's no 'catch*' clause then we just evaluate the 'try*'.
102 IF FNcount(ast%) < 3 THEN =FNEVAL(FNnth(ast%, 1), env%)
103 IF FNunbox_symbol(FNfirst(FNnth(ast%, 2))) <> "catch*" THEN
104 ERROR &40E80924, "Invalid 'catch*' clause"
106 ret% = FNtry(FNnth(ast%, 1), env%, is_error%)
107 IF is_error% THEN =FNcatch(FNnth(ast%, 2), env%, ret%)
110 REM Evaluate an expression, returning either the result or an exception
111 REM raised during evaluation. is_error% indicates which it was.
112 DEF FNtry(ast%, env%, RETURN is_error%)
117 ON ERROR LOCAL is_error% = TRUE : =FNgc_restore(trysav%, FNwrap_exception)
118 =FNgc_restore(trysav%, FNEVAL(ast%, env%))
120 REM Return a mal value corresponding to the most-recently thrown exception.
122 REM There are three cases to handle. When the error was generated
123 REM by 'throw', we should return the value that 'throw' stashed in
124 REM MAL_ERR%. When the error was generated by mal, we should just
125 REM return the error message. When the error was generated by BASIC
126 REM or the OS, we should wrap the message and the error number in
128 IF ERR = &40E80900 THEN =MAL_ERR% : REM Error generated by 'throw'
129 IF (ERR AND &FFFFFF00) = &40E80900 THEN =FNalloc_string(REPORT$)
131 e% = FNhashmap_set(FNempty_hashmap, "err", FNalloc_int(ERR))
132 =FNhashmap_set(e%, "message", FNalloc_string(REPORT$))
134 DEF FNcatch(ast%, env%, err%)
136 binds% = FNalloc_pair(FNnth(ast%, 1), FNempty)
137 exprs% = FNalloc_pair(err%, FNempty)
138 env% = FNnew_env(env%, binds%, exprs%)
139 =FNEVAL(FNnth(ast%, 2), env%)
141 DEF FNEVAL(ast%, env%)
143 =FNgc_exit(FNEVAL_(ast%, env%))
145 DEF FNEVAL_(ast%, env%)
146 LOCAL car%, specialform%, val%, bindings%
148 PROCgc_keep_only2(ast%, env%)
149 IF NOT FNis_list(ast%) THEN =FNeval_ast(ast%, env%)
150 IF FNis_empty(ast%) THEN =ast%
151 ast% = FNmacroexpand(ast%, env%)
152 IF NOT FNis_list(ast%) THEN =FNeval_ast(ast%, env%)
155 IF FNis_symbol(car%) THEN
157 CASE FNunbox_symbol(car%) OF
160 val% = FNEVAL(FNnth(ast%, 2), env%)
161 PROCenv_set(env%, FNnth(ast%, 1), val%)
164 val% = FNEVAL(FNnth(ast%, 2), env%)
165 IF FNis_fn(val%) THEN val% = FNas_macro(val%)
166 PROCenv_set(env%, FNnth(ast%, 1), val%)
169 env% = FNalloc_environment(env%)
170 bindings% = FNnth(ast%, 1)
171 WHILE NOT FNis_empty(bindings%)
172 PROCenv_set(env%, FNfirst(bindings%), FNEVAL(FNnth(bindings%, 1), env%))
173 bindings% = FNrest(FNrest(bindings%))
175 ast% = FNnth(ast%, 2)
176 REM Loop round for tail-call optimisation.
178 REM The guide has us call FNeval_ast on the sub-list that excludes
179 REM the last element of ast%, but that's a bit painful without
180 REM native list slicing, so it's easier to just re-implement the
181 REM bit of FNeval_ast that we need.
183 WHILE NOT FNis_empty(FNrest(ast%))
184 val% = FNEVAL(FNfirst(ast%), env%)
189 IF FNis_truish(FNEVAL(FNnth(ast%, 1), env%)) THEN
190 ast% = FNnth(ast%, 2)
192 IF FNcount(ast%) = 3 THEN =FNnil ELSE ast% = FNnth(ast%, 3)
194 REM Loop round for tail-call optimisation.
196 =FNalloc_fn(FNnth(ast%, 2), FNnth(ast%, 1), env%)
200 ast% = FNquasiquote(FNnth(ast%, 1))
201 REM Loop round for tail-call optimisation
203 =FNmacroexpand(FNnth(ast%, 1), env%)
205 =FNtry_catch(ast%, env%)
210 IF NOT specialform% THEN
211 REM This is the "apply" part.
212 ast% = FNeval_ast(ast%, env%)
214 IF FNis_corefn(car%) THEN
215 =FNcore_call(FNunbox_corefn(car%), FNrest(ast%))
217 IF FNis_fn(car%) THEN
218 env% = FNnew_env(FNfn_env(car%), FNfn_params(car%), FNrest(ast%))
219 ast% = FNfn_ast(car%)
220 REM Loop round for tail-call optimisation.
222 ERROR &40E80918, "Not a function"
228 =FNunbox_string(FNpr_str(a%, TRUE))
231 =FNPRINT(FNEVAL(FNREAD(a$), repl_env%))
233 DEF FNeval_ast(ast%, env%)
234 LOCAL val%, car%, cdr%, map%, keys%, key$
235 IF FNis_symbol(ast%) THEN =FNenv_get(env%, ast%)
236 IF FNis_seq(ast%) THEN
237 IF FNis_empty(ast%) THEN =ast%
238 car% = FNEVAL(FNfirst(ast%), env%)
239 cdr% = FNeval_ast(FNrest(ast%), env%)
240 IF FNis_vector(ast%) THEN =FNalloc_vector_pair(car%, cdr%)
241 =FNalloc_pair(car%, cdr%)
243 IF FNis_hashmap(ast%) THEN
244 map% = FNempty_hashmap
245 keys% = FNhashmap_keys(ast%)
246 WHILE NOT FNis_empty(keys%)
247 key$ = FNunbox_string(FNfirst(keys%))
248 map% = FNhashmap_set(map%, key$, FNEVAL(FNhashmap_get(ast%, key$), env%))
249 keys% = FNrest(keys%)
257 LOCAL argv%, rargv%, cmdptr%, arg$, len%
259 IF !PAGE = &D7C1C7C5 THEN
260 REM Running under Brandy, so ARGC and ARGV$ are usable.
262 FOR i% = ARGC TO 1 STEP -1
263 argv% = FNalloc_pair(FNalloc_string(ARGV$(i%)), argv%)
267 IF (INKEY(-256) AND &F0) = &A0 THEN
269 REM Running under RISC OS
270 REM Vexingly, we can only get the command line that was passed to
271 REM the BASIC interpreter. This means that we need to extract
272 REM the arguments from that. Typically, we will have been started
273 REM with "BASIC -quit <filename> <args>".
276 SYS "OS_GetEnv" TO cmdptr%
278 SYS "OS_GSTrans", cmdptr%, q%, &20000000 + 256 TO cmdptr%, , len%
280 rargv% = FNalloc_pair(FNalloc_string($q%), rargv%)
282 REM Put argv back into the right order.
283 WHILE NOT FNis_empty(rargv%)
284 argv% = FNalloc_pair(FNfirst(rargv%), argv%)
285 rargv% = FNrest(rargv%)
287 IF FNis_empty(argv%) THEN =FNgc_exit(argv%)
288 argv% = FNrest(argv%) : REM skip "BASIC"
289 IF FNis_empty(argv%) THEN =FNgc_exit(argv%)
290 IF FNunbox_string(FNfirst(argv%)) <> "-quit" THEN =FNgc_exit(argv%)
291 argv% = FNrest(argv%) : REM skip "-quit"
292 IF FNis_empty(argv%) THEN =FNgc_exit(argv%)
293 argv% = FNrest(argv%) : REM skip filename
300 REM indent-tabs-mode: nil