1 REM > types library for mal in BBC BASIC
3 REM This library should be the only thing that understands the
4 REM implementation of mal data types in BBC BASIC. All other
5 REM code should use routines in this library to access them.
7 REM As far as other code is concerned, a mal object is just an
8 REM opaque 32-bit integer, which might be a pointer, or might not.
10 REM All mal objects live in an array, Z%(), with string values held
11 REM in a parallel array, Z$(). There's one row in Z%(), and one
12 REM entry in Z$(), for each mal object.
14 REM Z%(x,0) holds the type of an object and other small amounts of
15 REM information. The bottom bit indicates the semantics of Z%(x,1):
17 REM &01 : Z%(x,1) is a pointer into Z%()
19 REM Z%(x,2) and Z%(x,3) are always pointers into Z%(), to 'nil' if nothing
22 REM The &40 bit is used to distinguish empty lists, vectors and hash-maps.
23 REM The &80 bit distinguishes vectors from lists and macros from functions.
25 REM sS%() is a shadow stack, used to keep track of which mal values might
26 REM be referenced from local variables at a given depth of the BASIC call
27 REM stack. It grows upwards. sSP% points to the first unused word. sFP%
28 REM points to the start of the current shadow stack frame. The first word
29 REM of each shadow stack frame is the saved value of sFP%. The rest are
39 REM &09 list/vector (each object is a cons cell)
41 REM &11 hash-map internal node
42 REM &15 mal function (first part)
43 REM &19 mal function (second part)
44 REM &02 string/keyword
46 REM &0A hash-map leaf node
48 REM Formats of individual objects are defined below.
51 REM Mal's heap has to be statically dimensioned, but we also
52 REM need to leave enough space for BASIC's stack and heap.
53 REM The BASIC heap is where all strings live.
55 REM Each row of Z%() consumes 16 bytes. The size of each entry
56 REM in Z$() varies by platform: 5 bytes in ARM BBC BASIC V,
57 REM 8 bytes in Brandy on a 32-bit system, 16 bytes in Brandy on
60 DIM Z
%((HIMEM
-LOMEM
)/110,3), Z
$((HIMEM-LOMEM)/110)
61 DIM sS
%((HIMEM
-LOMEM
)/64)
63 Z
%(1,0) = &04 : REM false
64 Z
%(2,0) = &04 : Z
%(2,1) = TRUE : REM true
65 Z
%(3,0) = &49 : Z
%(3,1) = 3 : REM empty list
66 Z
%(4,0) = &C9
: Z
%(4,1) = 4 : REM empty vector
67 Z
%(5,0) = &51 : REM empty hashmap
82 REM PRINT " >>> ";sFP%
85 REM FNgc_save is equivalent to PROCgc_enter except that it returns a
86 REM value that can be passed to PROCgc_restore to pop all the stack
87 REM frames back to (and including) the one pushed by FNgc_save.
93 REM PRINT ;sS%(sFP%);" <<< ";sFP%
98 DEF
PROCgc_restore(oldFP
%)
100 REM PRINT "!!! FP reset"
104 DEF
FNref_local(val
%)
113 DEF
FNgc_restore(oldFP
%, val
%)
114 PROCgc_restore(oldFP
%)
117 DEF
PROCgc_keep_only2(val1
%, val2
%)
120 val1
% = FNref_local(val1
%)
121 val2
% = FNref_local(val2
%)
126 REM If the heap is full, collect garbage first.
127 IF F
% = 0 AND next_Z
% > DIM(Z
%(),1) THEN
129 IF F
% = 0 ERROR &40E80950
, "Out of mal heap memory"
151 REM PRINT "** START GC **"
154 REM PRINT "** FINISH GC **"
160 REM PRINT ">>marking...";
161 FOR sp
% = sSP
% - 1 TO 0 STEP
-1
165 ELSE PROCgc_mark(sS
%(sp
%))
171 DEF
PROCgc_mark(val
%)
172 IF (Z
%(val
%,0) AND &100) = 0 THEN
175 IF (Z
%(val
%,0) AND &01) THEN PROCgc_mark(Z
%(val
%,1))
176 PROCgc_mark(Z
%(val
%,2))
177 PROCgc_mark(Z
%(val
%,3))
183 REM PRINT ">>sweeping ...";
184 FOR val
% = 6 TO next_Z
% - 1
185 IF FNtype_of(val
%) <> &05 AND (Z
%(val
%,0) AND &100) = 0 THEN
198 DEF
FNwith_meta(val
%, meta
%)
200 newval
% = FNmalloc(Z
%(val
%,0))
201 Z
%(newval
%,1) = Z
%(val
%,1)
202 Z
%(newval
%,2) = Z
%(val
%,2)
203 Z
%(newval
%,3) = meta
%
204 Z
$(newval%) = Z
$(val%)
217 REM Z%(x,1) = TRUE or FALSE
219 DEF
FNis_boolean(val
%)
220 =FNtype_of(val
%) = &04
222 DEF
FNalloc_boolean(bval
%)
226 DEF
FNunbox_boolean(val
%)
227 IF NOT FNis_boolean(val
%) THEN ERROR &40E80911
, "Not a boolean"
230 DEF
FNis_truish(val
%)
231 IF FNis_nil(val
%) THEN =FALSE
232 IF FNis_boolean(val
%) THEN =FNunbox_boolean(val
%)
237 REM Z%(x,1) = integer value
240 =FNtype_of(val
%) = &08
242 DEF
FNalloc_int(ival
%)
248 DEF
FNunbox_int(val
%)
249 IF NOT FNis_int(val
%) THEN ERROR &40E80912
, "Not an integer"
252 REM ** Strings and keywords **
254 REM Z$(x) is the string value
255 REM Z%(x,2) points to the next part of the string
256 REM A keyword is a string with first character CHR$(127).
258 DEF
FNis_string(val
%)
259 =FNtype_of(val
%) = &02
261 DEF
FNalloc_string(sval
$)
267 DEF
FNunbox_string(val
%)
268 IF NOT FNis_string(val
%) THEN ERROR &40E80914
, "Not a string"
269 IF NOT FNis_nil(Z
%(val
%,2)) ERROR &40E80914
, "Cannot unbox a long string"
272 DEF
FNstring_append(val
%, add
$)
274 IF NOT FNis_string(val
%) THEN ERROR &40E80914
, "Not a string"
275 newval
% = FNalloc_string(Z
$(val%))
276 IF FNis_nil(Z
%(val
%,2)) THEN
277 IF LEN(Z
$(newval%)) + LEN(add
$) <= 255 THEN
280 Z
%(newval
%,2) = FNalloc_string(add
$)
283 Z
%(newval
%,2) = FNstring_append(Z
%(val
%,2), add
$)
287 DEF
FNstring_concat(val
%, add
%)
289 IF NOT FNis_string(val
%) THEN ERROR &40E80914
, "Not a string"
290 IF NOT FNis_string(add
%) THEN ERROR &40E80914
, "Not a string"
291 newval
% = FNalloc_string(Z
$(val%))
292 IF FNis_nil(Z
%(val
%,2)) THEN
293 IF LEN(Z
$(newval%)) + LEN(Z
$(add%)) <= 255 THEN
294 Z
$(newval%) += Z
$(add%)
295 Z
%(newval
%,2) = Z
%(add
%,2)
300 Z
%(newval
%,2) = FNstring_concat(Z
%(val
%,2), add
%)
304 DEF
FNstring_len(val
%)
306 WHILE NOT FNis_nil(val
%)
307 len
% += LEN(Z
$(val%))
312 DEF
FNstring_chr(val
%, pos
%)
313 WHILE pos
% > LEN(Z
$(val%))
314 pos
% -= LEN(Z
$(val%))
316 IF FNis_nil(val
%) THEN =""
318 =MID$(Z$(val%), pos
%, 1)
322 REM Z$(x) = value of the symbol
324 DEF
FNis_symbol(val
%)
325 =FNtype_of(val
%) = &06
327 DEF
FNalloc_symbol(sval
$)
333 DEF
FNunbox_symbol(val
%)
334 IF NOT FNis_symbol(val
%) THEN ERROR &40E80915
, "Not a symbol"
337 REM ** Lists and vectors **
339 REM Lists and vectors are both represented as linked lists: the only
340 REM difference is in the state of the is_vector flag in the head cell
341 REM of the list. Note that this means that the tail of a list may be
342 REM a vector, and vice versa. FNas_list and FNas_vector can be used
343 REM to convert a sequence to a particular type as necessary.
345 REM Z%(x,0) AND &80 = is_vector flag
346 REM Z%(x,1) = index in Z%() of next pair
347 REM Z%(x,2) = index in Z%() of first element
349 REM The empty list is a distinguished value, with elements that match
350 REM the spec of 'first' and 'rest'.
358 DEF
FNalloc_pair(car
%, cdr
%)
365 DEF
FNalloc_vector_pair(car
%, cdr
%)
367 val
% = FNalloc_pair(car
%, cdr
%)
368 Z
%(val
%,0) = Z
%(val
%,0) OR &80
372 =(Z
%(val
%,0) AND &40) = &40
375 =FNtype_of(val
%) = &09
378 =FNtype_of(val
%) = &09 AND (Z
%(val
%, 0) AND &80) = &00
380 DEF
FNis_vector(val
%)
381 =FNtype_of(val
%) = &09 AND (Z
%(val
%, 0) AND &80) = &80
384 IF FNis_list(val
%) THEN =val
%
385 IF FNis_empty(val
%) THEN =FNempty
386 =FNalloc_pair(FNfirst(val
%), FNrest(val
%))
388 DEF
FNas_vector(val
%)
389 IF FNis_vector(val
%) THEN =val
%
390 IF FNis_empty(val
%) THEN =FNempty_vector
391 =FNalloc_vector_pair(FNfirst(val
%), FNrest(val
%))
394 IF NOT FNis_seq(val
%) THEN ERROR &40E80916
, "Can't get car of non-sequence"
395 =FNref_local(Z
%(val
%,2))
398 IF NOT FNis_seq(val
%) THEN ERROR &40E80916
, "Can't get cdr of non-sequence"
399 =FNref_local(Z
%(val
%,1))
401 DEF
FNalloc_list2(val0
%, val1
%)
402 =FNalloc_pair(val0
%, FNalloc_pair(val1
%, FNempty
))
404 DEF
FNalloc_list3(val0
%, val1
%, val2
%)
405 =FNalloc_pair(val0
%, FNalloc_pair(val1
%, FNalloc_pair(val2
%, FNempty
)))
409 WHILE NOT FNis_empty(val
%)
417 IF FNis_empty(val
%) THEN ERROR &40E80923
, "Subscript out of range"
421 IF FNis_empty(val
%) THEN ERROR &40E80923
, "Subscript out of range"
424 REM ** Core functions **
426 REM Z%(x,1) = index of function in FNcore_call
428 DEF
FNis_corefn(val
%)
429 =FNtype_of(val
%) = &0C
431 DEF
FNalloc_corefn(fn
%)
437 DEF
FNunbox_corefn(val
%)
438 IF NOT FNis_corefn(val
%) THEN ERROR &40E80919
, "Not a core function"
443 REM Hash-maps are represented as a crit-bit tree.
445 REM An internal node has:
446 REM Z%(x,0) >> 16 = next bit of key to check
447 REM Z%(x,1) = index in Z%() of left child (if next bit of key is 0)
448 REM Z%(x,2) = index in Z%() of right child (if next bit of key is 1)
452 REM Z%(x,2) = index in Z%() of value
454 REM The empty hash-map is a special value containing no data.
459 DEF
FNhashmap_alloc_leaf(key
$, val
%)
461 entry
% = FNmalloc(&0A
)
466 DEF
FNhashmap_alloc_node(bit
%, left
%, right
%)
468 entry
% = FNmalloc(&11)
469 Z
%(entry
%,0) += (bit
% << 16)
471 Z
%(entry
%,2) = right
%
474 DEF
FNis_hashmap(val
%)
477 =t
% = &11 OR t
% = &0A
479 DEF
FNkey_bit(key
$, bit
%)
482 IF cnum
% >= LEN(key
$) THEN =FALSE
483 =ASC(MID$(key$, cnum
% + 1, 1)) AND (&80 >> (bit
% AND 7))
485 DEF
FNkey_bitdiff(key1
$, key2
$)
487 WHILE FNkey_bit(key1
$, bit
%) = FNkey_bit(key2
$, bit
%)
492 DEF
FNhashmap_set(map
%, key
$, val
%)
494 IF FNis_empty(map
%) THEN =FNhashmap_alloc_leaf(key
$, val
%)
495 nearest
% = FNhashmap_find(map
%, key
$)
496 IF Z
$(nearest%) = key$
THEN =FNhashmap_replace(map
%, key
$, val
%)
497 bit
% = FNkey_bitdiff(key
$, Z
$(nearest%))
498 =FNhashmap_insert(map
%, bit
%, key
$, val
%)
500 DEF
FNhashmap_insert(map
%, bit
%, key
$, val
%)
502 IF FNtype_of(map
%) = &11 AND (Z
%(map
%,0) >> 16) < bit
% THEN
503 IF FNkey_bit(key
$, Z
%(map
%,0) >> 16) THEN
505 right
% = FNhashmap_insert(Z
%(map
%,2), bit
%, key
$, val
%)
507 left
% = FNhashmap_insert(Z
%(map
%,1), bit
%, key
$, val
%)
510 =FNhashmap_alloc_node(Z
%(map
%,0)>>16, left
%, right
%)
512 IF FNkey_bit(key
$, bit
%) THEN
514 right
% = FNhashmap_alloc_leaf(key
$, val
%)
516 left
% = FNhashmap_alloc_leaf(key
$, val
%)
519 =FNhashmap_alloc_node(bit
%, left
%, right
%)
522 REM Replace a known-present key in a non-empty hashmap.
523 DEF
FNhashmap_replace(map
%, key
$, val
%)
525 IF FNtype_of(map
%) = &0A
THEN =FNhashmap_alloc_leaf(key
$, val
%)
526 IF FNkey_bit(key
$, Z
%(map
%,0) >> 16) THEN
528 right
% = FNhashmap_replace(Z
%(map
%,2), key
$, val
%)
530 left
% = FNhashmap_replace(Z
%(map
%,1), key
$, val
%)
533 =FNhashmap_alloc_node(Z
%(map
%,0)>>16, left
%, right
%)
535 DEF
FNhashmap_remove(map
%, key
$)
537 IF FNis_empty(map
%) THEN =map
%
538 IF FNtype_of(map
%) = &0A
THEN
539 IF Z
$(map%) = key$
THEN =FNempty_hashmap
541 IF FNkey_bit(key
$, Z
%(map
%,0) >> 16) THEN
542 child
% = FNhashmap_remove(Z
%(map
%,2), key
$)
543 IF FNis_empty(child
%) THEN =Z
%(map
%,1)
544 =FNhashmap_alloc_node(Z
%(map
%,0)>>16, Z
%(map
%,1), child
%)
546 child
% = FNhashmap_remove(Z
%(map
%,1), key
$)
547 IF FNis_empty(child
%) THEN =Z
%(map
%,2)
548 =FNhashmap_alloc_node(Z
%(map
%,0)>>16, child
%, Z
%(map
%,2))
551 REM FNhashmap_find finds the nearest entry in a non-empty hash-map to
552 REM the key requested, and returns the entire entry.
553 DEF
FNhashmap_find(map
%, key
$)
554 WHILE FNtype_of(map
%) = &11
555 IF FNkey_bit(key
$, Z
%(map
%,0) >> 16) THEN map
% = Z
%(map
%,2) ELSE map
% = Z
%(map
%,1)
559 DEF
FNhashmap_get(map
%, key
$)
560 IF NOT FNis_hashmap(map
%) THEN ERROR &40E80918
, "Can't get item from a non-hashmap"
561 IF FNis_empty(map
%) THEN =FNnil
562 map
% = FNhashmap_find(map
%, key
$)
563 IF Z
$(map%) = key$
THEN =FNref_local(Z
%(map
%,2)) ELSE =FNnil
565 DEF
FNhashmap_contains(map
%, key
$)
566 IF NOT FNis_hashmap(map
%) THEN ERROR &40E80918
, "Can't get item from a non-hashmap"
567 IF FNis_empty(map
%) THEN =FALSE
568 map
% = FNhashmap_find(map
%, key
$)
571 DEF
FNhashmap_keys(map
%)
572 =FNhashmap_keys1(map
%, FNempty
)
574 DEF
FNhashmap_keys1(map
%, acc
%)
575 IF FNis_empty(map
%) THEN =acc
%
576 IF FNtype_of(map
%) = &0A
THEN
577 =FNalloc_pair(FNalloc_string(Z
$(map%)), acc
%)
579 =FNhashmap_keys1(Z
%(map
%,1), FNhashmap_keys1(Z
%(map
%,2), acc
%))
581 DEF
FNhashmap_vals(map
%)
582 =FNhashmap_vals1(map
%, FNempty
)
584 DEF
FNhashmap_vals1(map
%, acc
%)
585 IF FNis_empty(map
%) THEN =acc
%
586 IF FNtype_of(map
%) = &0A
THEN
587 =FNalloc_pair(Z
%(map
%,2), acc
%)
589 =FNhashmap_vals1(Z
%(map
%,1), FNhashmap_vals1(Z
%(map
%,2), acc
%))
591 DEF
PROChashmap_dump(map
%)
592 IF FNis_empty(map
%) THEN
596 PROChashmap_dump_internal(map
%, "")
600 DEF
PROChashmap_dump_internal(map
%, prefix
$)
601 IF FNtype_of(map
%) = &0A PRINT prefix
$;Z$(map%)
602 IF FNtype_of(map
%) = &11 THEN
603 PRINT prefix
$;"<";Z%(map%,0) >> 16;">"
604 PROChashmap_dump_internal(Z
%(map
%,1), prefix$
+ "L ")
605 PROChashmap_dump_internal(Z
%(map
%,2), prefix$
+ "R ")
611 REM A function is represented by two cells:
612 REM Z%(x,0) AND &80 = is_macro flag
613 REM Z%(x,1) = index in Z%() of ast
616 REM Z%(y,1) = index in Z%() of params
617 REM Z%(y,2) = index in Z%() of env
620 =FNtype_of(val
%) = &15
622 DEF
FNis_nonmacro_fn(val
%)
623 =FNtype_of(val
%) = &15 AND (Z
%(val
%, 0) AND &80) = &00
626 =FNtype_of(val
%) = &15 AND (Z
%(val
%, 0) AND &80) = &80
628 DEF
FNalloc_fn(ast
%, params
%, env
%)
630 val1
% = FNmalloc(&15)
632 val2
% = FNmalloc(&19)
634 Z
%(val2
%,1) = params
%
639 IF NOT FNis_fn(val
%) THEN ERROR &40E8091A
, "Not a function"
641 newval
% = FNmalloc(Z
%(val
%,0) OR &80)
642 Z
%(newval
%,1) = Z
%(val
%,1)
643 Z
%(newval
%,2) = Z
%(val
%,2)
644 Z
%(newval
%,3) = Z
%(val
%,3)
648 IF NOT FNis_fn(val
%) THEN ERROR &40E8091A
, "Not a function"
649 =FNref_local(Z
%(val
%,1))
651 DEF
FNfn_params(val
%)
652 IF NOT FNis_fn(val
%) THEN ERROR &40E8091A
, "Not a function"
653 =FNref_local(Z
%(Z
%(val
%,2),1))
656 IF NOT FNis_fn(val
%) THEN ERROR &40E8091A
, "Not a function"
657 =FNref_local(Z
%(Z
%(val
%,2),2))
661 REM Z%(x,1) = index in Z% of current referent
664 =FNtype_of(val
%) = &01
666 DEF
FNalloc_atom(contents
%)
669 Z
%(val
%,1) = contents
%
672 DEF
FNatom_deref(val
%)
673 =FNref_local(Z
%(val
%,1))
675 DEF
PROCatom_reset(val
%, contents
%)
676 Z
%(val
%,1) = contents
%
679 REM ** Environments **
681 REM Z%(x,1) = index in Z% of hash-map
682 REM Z%(x,2) = index in Z% of outer environment
684 DEF
FNis_environment(val
%)
685 =FNtype_of(val
%) = &0D
687 DEF
FNalloc_environment(outer
%)
690 Z
%(val
%,1) = FNempty_hashmap
694 DEF
FNenvironment_data(val
%)
695 IF NOT FNis_environment(val
%) THEN ERROR &40E8091D
, "Not an environment"
696 =FNref_local(Z
%(val
%,1))
698 DEF
PROCenvironment_set_data(val
%, data
%)
699 IF NOT FNis_environment(val
%) THEN ERROR &40E8091D
, "Not an environment"
703 DEF
FNenvironment_outer(val
%)
704 IF NOT FNis_environment(val
%) THEN ERROR &40E8091D
, "Not an environment"
705 =FNref_local(Z
%(val
%,2))
708 REM indent-tabs-mode: nil