1 REM > core function library for mal in BBC BASIC
3 REM BBC BASIC doesn't have function pointers. There are essentially
4 REM two ways to work around this. One is to use the BASIC EVAL function,
5 REM constructing a string that will call an arbitrary function with the
6 REM specified arguments. The other is to us a big CASE statement.
7 REM Following the suggestion in Hints.md, this code takes the latter
12 REM The actual DATA statements are embedded in the dispatch table below.
15 REM Call a core function, taking the function number and a mal list of
16 REM objects to pass as arguments.
17 DEF
FNcore_call(fn
%, args
%)
23 PROCcore_prepare_args("ii", "+")
24 =FNalloc_int(args
%(0) + args
%(1))
27 PROCcore_prepare_args("ii", "-")
28 =FNalloc_int(args
%(0) - args
%(1))
31 PROCcore_prepare_args("ii", "*")
32 =FNalloc_int(args
%(0) * args
%(1))
35 PROCcore_prepare_args("ii", "/")
36 =FNalloc_int(args
%(0) DIV args
%(1))
42 PROCcore_prepare_args("?", "list?")
43 =FNalloc_boolean(FNis_list(args
%(0)))
46 PROCcore_prepare_args("l", "empty?")
47 =FNalloc_boolean(FNis_empty(args
%(0)))
50 PROCcore_prepare_args("C", "count")
51 IF FNis_nil(args
%(0)) THEN =FNalloc_int(0)
52 =FNalloc_int(FNcount(args
%(0)))
55 PROCcore_prepare_args("??", "=")
56 =FNalloc_boolean(FNcore_equal(args
%(0), args
%(1)))
59 PROCcore_prepare_args("ii", "<")
60 =FNalloc_boolean(args
%(0) < args
%(1))
63 PROCcore_prepare_args("ii", "<=")
64 =FNalloc_boolean(args
%(0) <= args
%(1))
67 PROCcore_prepare_args("ii", ">")
68 =FNalloc_boolean(args
%(0) > args
%(1))
71 PROCcore_prepare_args("ii", ">=")
72 =FNalloc_boolean(args
%(0) >= args
%(1))
75 PROCcore_prepare_args("t", "read-string")
79 PROCcore_prepare_args("s", "slurp")
83 PROCcore_prepare_args("?", "eval")
84 =FNEVAL(args
%(0), repl_env
%)
87 =FNcore_print(TRUE, " ", args
%)
90 =FNcore_print(FALSE, "", args
%)
93 PRINT
FNunbox_string(FNcore_print(TRUE, " ", args
%))
97 PRINT
FNunbox_string(FNcore_print(FALSE, " ", args
%))
101 PROCcore_prepare_args("?", "atom")
102 =FNalloc_atom(args
%(0))
105 PROCcore_prepare_args("?", "atom?")
106 =FNalloc_boolean(FNis_atom(args
%(0)))
109 PROCcore_prepare_args("a", "deref")
110 =FNatom_deref(args
%(0))
113 PROCcore_prepare_args("a?", "reset!")
114 PROCatom_reset(args
%(0), args
%(1))
118 PROCcore_prepare_args("af*", "swap!")
119 PROCatom_reset(args
%(0), FNcore_apply(args
%(1), FNalloc_pair(FNatom_deref(args
%(0)), args
%)))
120 =FNatom_deref(args
%(0))
123 PROCcore_prepare_args("?l", "cons")
124 =FNalloc_pair(args
%(0), args
%(1))
127 =FNcore_concat(args
%)
130 PROCcore_prepare_args("li", "nth")
131 =FNnth(args
%(0), args
%(1))
134 PROCcore_prepare_args("C", "first")
135 IF FNis_nil(args
%(0)) THEN =FNnil
139 PROCcore_prepare_args("C", "rest")
140 IF FNis_nil(args
%(0)) THEN =FNempty
141 =FNas_list(FNrest(args
%(0)))
144 PROCcore_prepare_args("?", "throw")
146 ERROR &40E80900
, "Mal exception: " + FNunbox_string(FNpr_str(args
%(0), FALSE))
149 PROCcore_prepare_args("f?*", "apply")
150 =FNcore_apply(args
%(0), FNcore_apply_args(FNalloc_pair(args
%(1), args
%)))
153 PROCcore_prepare_args("fl", "map")
154 =FNcore_map(args
%(0), args
%(1))
157 PROCcore_prepare_args("?", "nil?")
158 =FNalloc_boolean(FNis_nil(args
%(0)))
161 PROCcore_prepare_args("?", "true?")
162 IF NOT FNis_boolean(args
%(0)) THEN =FNalloc_boolean(FALSE)
166 PROCcore_prepare_args("?", "false?")
167 IF NOT FNis_boolean(args
%(0)) THEN =FNalloc_boolean(FALSE)
168 =FNalloc_boolean(NOT FNunbox_boolean(args
%(0)))
171 PROCcore_prepare_args("?", "symbol?")
172 =FNalloc_boolean(FNis_symbol(args
%(0)))
175 PROCcore_prepare_args("s", "symbol")
176 =FNalloc_symbol(arg
$)
179 PROCcore_prepare_args("s", "keyword")
180 IF LEFT
$(arg$, 1) <> CHR
$(127) THEN arg$
= CHR
$(127) + arg$
181 =FNalloc_string(arg
$)
184 PROCcore_prepare_args("?", "keyword?")
185 IF FNis_string(args
%(0)) THEN
186 =FNalloc_boolean(LEFT
$(FNunbox_string(args%(0)), 1) = CHR
$(127))
188 =FNalloc_boolean(FALSE)
194 PROCcore_prepare_args("?", "vector?")
195 =FNalloc_boolean(FNis_vector(args
%(0)))
198 PROCcore_prepare_args("?", "sequential?")
199 =FNalloc_boolean(FNis_seq(args
%(0)))
202 =FNcore_assoc(FNempty_hashmap
, args
%)
205 PROCcore_prepare_args("?", "map?")
206 =FNalloc_boolean(FNis_hashmap(args
%(0)))
209 PROCcore_prepare_args("h*", "assoc")
210 =FNcore_assoc(args
%(0), args
%)
213 PROCcore_prepare_args("h*", "dissoc")
214 WHILE NOT FNis_empty(args
%)
215 args
%(0) = FNhashmap_remove(args
%(0), FNunbox_string(FNfirst(args
%)))
216 args
% = FNrest(args
%)
221 IF FNis_nil(FNfirst(args
%)) THEN =FNnil
222 PROCcore_prepare_args("hs", "get")
223 =FNhashmap_get(args
%(0), arg
$)
226 PROCcore_prepare_args("hs", "contains?")
227 =FNalloc_boolean(FNhashmap_contains(args
%(0), arg
$))
230 PROCcore_prepare_args("h", "keys")
231 =FNhashmap_keys(args
%(0))
234 PROCcore_prepare_args("h", "vals")
235 =FNhashmap_vals(args
%(0))
238 PROCcore_prepare_args("s", "readline")
241 =FNalloc_string(arg
$)
244 PROCcore_prepare_args("?", "meta")
248 PROCcore_prepare_args("??", "with-meta")
249 =FNwith_meta(args
%(0), args
%(1))
252 PROCcore_prepare_args("", "time-ms")
253 =FNalloc_int(TIME
* 10)
256 PROCcore_prepare_args("l*", "conj")
257 IF FNis_list(args
%(0)) THEN
258 WHILE NOT FNis_empty(args
%)
259 args
%(0) = FNalloc_pair(FNfirst(args
%), args
%(0))
260 args
% = FNrest(args
%)
263 ELSE : REM args%(0) is a vector
264 =FNas_vector(FNcore_concat1(args
%(0), args
%))
268 PROCcore_prepare_args("?", "string?")
269 IF FNis_string(args
%(0)) THEN
270 =FNalloc_boolean(LEFT
$(FNunbox_string(args%(0)), 1) <> CHR
$(127))
272 =FNalloc_boolean(FALSE)
275 PROCcore_prepare_args("?", "number?")
276 =FNalloc_boolean(FNis_int(args
%(0)))
279 PROCcore_prepare_args("?", "fn?")
280 =FNalloc_boolean(FNis_nonmacro_fn(args
%(0)) OR FNis_corefn(args
%(0)))
283 PROCcore_prepare_args("?", "macro?")
284 =FNalloc_boolean(FNis_macro(args
%(0)))
287 PROCcore_prepare_args("?", "seq")
288 =FNcore_seq(args
%(0))
291 ERROR &40E809F1
, "Call to non-existent core function"
293 DEF
PROCcore_prepare_args(spec
$, fn
$)
294 REM Check that a core function is being provided with the correct
295 REM number and type of arguments and unbox them as appropriate.
296 REM spec$ is the argument specification as a string. Each character
297 REM represents an argument:
299 REM "i" - Must be an integer; unbox into args%()
300 REM "s" - Must be a string; unbox into arg$
301 REM "t" - Must be a string; stuff into args%()
302 REM "l" - Must be a sequence; stuff into args%()
303 REM "f" - Must be a function; stuff into args%()
304 REM "a" - Must be an atom; stuff into args%()
305 REM "h" - Must be a hash-map; stuff into args%()
306 REM "C" - Must be 'count'able stuff into args%()
307 REM "?" - Any single argument stuff into args%()
308 REM "*" - Any number of (trailing) arguments; leave in args%
310 REM This function shares some local variables with FNcore_call.
314 IF RIGHT
$(spec$) = "*" THEN
316 IF FNcount(args
%) < LEN(spec
$) THEN
317 ERROR &40E80921
, "Core function '"+fn
$+"' requires at least
"+STR$(LEN(spec$))+" arguments"
320 IF FNcount(args
%) <> LEN(spec
$) THEN
321 ERROR &40E80921
, "Core function '"+fn
$+"' requires
"+STR$(LEN(spec$))+" arguments"
324 FOR i
% = 1 TO LEN(spec
$)
325 val
% = FNfirst(args
%)
326 CASE MID$(spec$, i
%, 1) OF
328 IF NOT FNis_int(val
%) THEN
329 ERROR &40E80911
, "Argument "+STR
$(i%)+" to core
function '"+fn$+"' must be an integer"
331 args
%(i
% - 1) = FNunbox_int(val
%)
333 IF NOT FNis_string(val
%) THEN
334 ERROR &40E80914
, "Argument "+STR
$(i%)+" to core
function '"+fn$+"' must be a string"
336 arg$
= FNunbox_string(val
%)
338 IF NOT FNis_string(val
%) THEN
339 ERROR &40E80914
, "Argument "+STR
$(i%)+" to core
function '"+fn$+"' must be a string"
343 IF NOT FNis_seq(val
%) THEN
344 ERROR &40E80916
, "Argument "+STR
$(i%)+" to core
function '"+fn$+"' must be a sequence"
348 IF NOT FNis_fn(val
%) AND NOT FNis_corefn(val
%) THEN
349 ERROR &40E80919
, "Argument "+STR
$(i%)+" to core
function '"+fn$+"' must be a function"
353 IF NOT FNis_atom(val
%) THEN
354 ERROR &40E8091C
, "Argument "+STR
$(i%)+" to core
function '"+fn$+"' must be an atom"
358 IF NOT FNis_hashmap(val
%) THEN
359 ERROR &40E8091D
, "Argument "+STR
$(i%)+" to core
function '"+fn$+"' must be a hash-map"
363 IF NOT FNis_seq(val
%) AND NOT FNis_nil(val
%) THEN
364 ERROR &40E8091F
, "Argument "+STR
$(i%)+" to core
function '"+fn$+"' must be a countable value"
370 args
% = FNrest(args
%)
374 REM Innards of the '=' function.
375 DEF
FNcore_equal(a
%, b
%)
376 IF a
% = b
% THEN =TRUE
377 IF FNis_int(a
%) AND FNis_int(b
%) THEN =FNunbox_int(a
%) = FNunbox_int(b
%)
378 IF FNis_symbol(a
%) AND FNis_symbol(b
%) THEN
379 =FNunbox_symbol(a
%) = FNunbox_symbol(b
%)
381 IF FNis_string(a
%) AND FNis_string(b
%) THEN
382 =FNunbox_string(a
%) = FNunbox_string(b
%)
384 IF FNis_seq(a
%) AND FNis_seq(b
%) THEN
385 IF FNis_empty(a
%) AND FNis_empty(b
%) THEN =TRUE
386 IF FNis_empty(a
%) <> FNis_empty(b
%) THEN =FALSE
387 IF NOT FNcore_equal(FNfirst(a
%), FNfirst(b
%)) THEN =FALSE
388 =FNcore_equal(FNrest(a
%), FNrest(b
%))
390 IF FNis_hashmap(a
%) AND FNis_hashmap(b
%) THEN
391 REM Take advantage of the sorted keys in our hash-maps.
392 IF FNcore_equal(FNhashmap_keys(a
%), FNhashmap_keys(b
%)) THEN
393 IF FNcore_equal(FNhashmap_vals(a
%), FNhashmap_vals(b
%)) THEN =TRUE
398 REM Innards of the 'slurp' function.
399 DEF
FNcore_slurp(file
$)
402 IF f
% = 0 THEN ERROR &40E80940
, "File '"+file
$+"' not found
"
403 out% = FNcore_slurp_channel(f%)
407 DEF FNcore_slurp_channel(f%)
409 IF EOF#f% THEN =FNalloc_string("")
410 REM GET$# doesn't include a trailing newline.
411 this% = FNalloc_string(GET$#f% + CHR$(10))
412 =FNstring_concat(this%, FNcore_slurp_channel(f%))
414 REM General-purpose printing function
415 DEF FNcore_print(print_readably%, sep$, args%)
417 IF FNis_empty(args%) THEN =FNalloc_string("")
418 out% = FNpr_str(FNfirst(args%), print_readably%)
419 args% = FNrest(args%)
420 WHILE NOT FNis_empty(args%)
421 out% = FNstring_append(out%, sep$)
422 out% = FNstring_concat(out%, FNpr_str(FNfirst(args%), print_readably%))
423 args% = FNrest(args%)
427 REM Innards of the 'apply' function, also used by 'swap!'
428 DEF FNcore_apply(fn%, args%)
430 IF FNis_corefn(fn%) THEN =FNcore_call(FNunbox_corefn(fn%), args%)
433 env% = FNnew_env(FNfn_env(fn%), FNfn_params(fn%), args%)
436 ERROR &40E80918, "Not a
function"
438 REM Innards of 'concat' function
439 DEF FNcore_concat(args%)
441 IF FNis_empty(args%) THEN =FNempty
442 tail% = FNcore_concat(FNrest(args%))
443 =FNcore_concat1(FNfirst(args%), tail%)
445 DEF FNcore_concat1(prefix%, tail%)
446 IF FNis_empty(prefix%) THEN =tail%
447 =FNalloc_pair(FNfirst(prefix%), FNcore_concat1(FNrest(prefix%), tail%))
449 REM Recursively assemble the argument list for 'apply'
450 DEF FNcore_apply_args(args%)
451 IF FNis_empty(FNrest(args%)) THEN =FNfirst(args%)
452 =FNalloc_pair(FNfirst(args%), FNcore_apply_args(FNrest(args%)))
454 REM Innards of the 'map' function
455 DEF FNcore_map(fn%, args%)
457 IF FNis_empty(args%) THEN =args%
458 car% = FNcore_apply(fn%, FNalloc_pair(FNfirst(args%), FNempty))
459 cdr% = FNcore_map(fn%, FNrest(args%))
460 =FNalloc_pair(car%, cdr%)
462 REM Innards of the 'hash-map' function
463 DEF FNcore_assoc(map%, args%)
466 WHILE NOT FNis_empty(args%)
467 PROCcore_prepare_args("s?
*", "hash
-map
")
468 map% = FNhashmap_set(map%, arg$, args%(1))
472 REM Innards of the 'seq' function
475 IF FNis_empty(val%) OR FNis_nil(val%) THEN =FNnil
476 IF FNis_list(val%) THEN =val%
477 IF FNis_vector(val%) THEN =FNas_list(val%)
478 IF FNis_string(val%) THEN
479 s$ = FNunbox_string(val%)
480 IF s$ = "" THEN =FNnil
482 FOR i% = LEN(s$) TO 1 STEP -1
483 val% = FNalloc_pair(FNalloc_string(MID$(s$, i%, 1)), val%)
487 ERROR &40E8091F, "Argument
to 'seq' must be list, vector, string, or nil"
490 REM indent-tabs-mode: nil