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 Arbitrarily use a quarter of BASIC's heap as the mal heap.
52 REM Each Z%() entry is sixteen bytes. Each Z$() entry is about
54 DIM Z%((HIMEM-LOMEM)/64,3), Z$((HIMEM-LOMEM)/64)
55 DIM sS%((HIMEM-LOMEM)/64)
56 Z%(1,0) = &04 : REM false
57 Z%(2,0) = &04 : Z%(2,1) = TRUE : REM true
58 Z%(3,0) = &49 : Z%(3,1) = 3 : REM empty list
59 Z%(4,0) = &C9 : Z%(4,1) = 4 : REM empty vector
60 Z%(5,0) = &51 : REM empty hashmap
75 REM PRINT " >>> ";sFP%
78 REM FNgc_save is equivalent to PROCgc_enter except that it returns a
79 REM value that can be passed to PROCgc_restore to pop all the stack
80 REM frames back to (and including) the one pushed by FNgc_save.
86 REM PRINT ;sS%(sFP%);" <<< ";sFP%
91 DEF PROCgc_restore(oldFP%)
93 REM PRINT "!!! FP reset"
106 DEF FNgc_restore(oldFP%, val%)
107 PROCgc_restore(oldFP%)
110 DEF PROCgc_keep_only2(val1%, val2%)
113 val1% = FNref_local(val1%)
114 val2% = FNref_local(val2%)
119 REM If the heap is full, collect garbage first.
120 IF F% = 0 AND next_Z% > DIM(Z%(),1) THEN
122 IF F% = 0 ERROR &40E80950, "Out of mal heap memory"
144 REM PRINT "** START GC **"
147 REM PRINT "** FINISH GC **"
153 REM PRINT ">>marking...";
154 FOR sp% = sSP% - 1 TO 0 STEP -1
158 ELSE PROCgc_mark(sS%(sp%))
164 DEF PROCgc_mark(val%)
165 IF (Z%(val%,0) AND &100) = 0 THEN
168 IF (Z%(val%,0) AND &01) THEN PROCgc_mark(Z%(val%,1))
169 PROCgc_mark(Z%(val%,2))
170 PROCgc_mark(Z%(val%,3))
176 REM PRINT ">>sweeping ...";
177 FOR val% = 6 TO next_Z% - 1
178 IF FNtype_of(val%) <> &05 AND (Z%(val%,0) AND &100) = 0 THEN
191 DEF FNwith_meta(val%, meta%)
193 newval% = FNmalloc(Z%(val%,0))
194 Z%(newval%,1) = Z%(val%,1)
195 Z%(newval%,2) = Z%(val%,2)
196 Z%(newval%,3) = meta%
197 Z$(newval%) = Z$(val%)
210 REM Z%(x,1) = TRUE or FALSE
212 DEF FNis_boolean(val%)
213 =FNtype_of(val%) = &04
215 DEF FNalloc_boolean(bval%)
219 DEF FNunbox_boolean(val%)
220 IF NOT FNis_boolean(val%) THEN ERROR &40E80911, "Not a boolean"
223 DEF FNis_truish(val%)
224 IF FNis_nil(val%) THEN =FALSE
225 IF FNis_boolean(val%) THEN =FNunbox_boolean(val%)
230 REM Z%(x,1) = integer value
233 =FNtype_of(val%) = &08
235 DEF FNalloc_int(ival%)
241 DEF FNunbox_int(val%)
242 IF NOT FNis_int(val%) THEN ERROR &40E80912, "Not an integer"
245 REM ** Strings and keywords **
247 REM Z$(x) is the string value
248 REM Z%(x,2) points to the next part of the string
249 REM A keyword is a string with first character CHR$(127).
251 DEF FNis_string(val%)
252 =FNtype_of(val%) = &02
254 DEF FNalloc_string(sval$)
260 DEF FNunbox_string(val%)
261 IF NOT FNis_string(val%) THEN ERROR &40E80914, "Not a string"
262 IF NOT FNis_nil(Z%(val%,2)) ERROR &40E80914, "Cannot unbox a long string"
265 DEF FNstring_append(val%, add$)
267 IF NOT FNis_string(val%) THEN ERROR &40E80914, "Not a string"
268 newval% = FNalloc_string(Z$(val%))
269 IF FNis_nil(Z%(val%,2)) THEN
270 IF LEN(Z$(newval%)) + LEN(add$) <= 255 THEN
273 Z%(newval%,2) = FNalloc_string(add$)
276 Z%(newval%,2) = FNstring_append(Z%(val%,2), add$)
280 DEF FNstring_concat(val%, add%)
282 IF NOT FNis_string(val%) THEN ERROR &40E80914, "Not a string"
283 IF NOT FNis_string(add%) THEN ERROR &40E80914, "Not a string"
284 newval% = FNalloc_string(Z$(val%))
285 IF FNis_nil(Z%(val%,2)) THEN
286 IF LEN(Z$(newval%)) + LEN(Z$(add%)) <= 255 THEN
287 Z$(newval%) += Z$(add%)
288 Z%(newval%,2) = Z%(add%,2)
293 Z%(newval%,2) = FNstring_concat(Z%(val%,2), add%)
297 DEF FNstring_len(val%)
299 WHILE NOT FNis_nil(val%)
300 len% += LEN(Z$(val%))
305 DEF FNstring_chr(val%, pos%)
306 WHILE pos% > LEN(Z$(val%))
307 pos% -= LEN(Z$(val%))
309 IF FNis_nil(val%) THEN =""
311 =MID$(Z$(val%), pos%, 1)
315 REM Z$(x) = value of the symbol
317 DEF FNis_symbol(val%)
318 =FNtype_of(val%) = &06
320 DEF FNalloc_symbol(sval$)
326 DEF FNunbox_symbol(val%)
327 IF NOT FNis_symbol(val%) THEN ERROR &40E80915, "Not a symbol"
330 REM ** Lists and vectors **
332 REM Lists and vectors are both represented as linked lists: the only
333 REM difference is in the state of the is_vector flag in the head cell
334 REM of the list. Note that this means that the tail of a list may be
335 REM a vector, and vice versa. FNas_list and FNas_vector can be used
336 REM to convert a sequence to a particular type as necessary.
338 REM Z%(x,0) AND &80 = is_vector flag
339 REM Z%(x,1) = index in Z%() of next pair
340 REM Z%(x,2) = index in Z%() of first element
342 REM The empty list is a distinguished value, with elements that match
343 REM the spec of 'first' and 'rest'.
351 DEF FNalloc_pair(car%, cdr%)
358 DEF FNalloc_vector_pair(car%, cdr%)
360 val% = FNalloc_pair(car%, cdr%)
361 Z%(val%,0) = Z%(val%,0) OR &80
365 =(Z%(val%,0) AND &40) = &40
368 =FNtype_of(val%) = &09
371 =FNtype_of(val%) = &09 AND (Z%(val%, 0) AND &80) = &00
373 DEF FNis_vector(val%)
374 =FNtype_of(val%) = &09 AND (Z%(val%, 0) AND &80) = &80
377 IF FNis_list(val%) THEN =val%
378 IF FNis_empty(val%) THEN =FNempty
379 =FNalloc_pair(FNfirst(val%), FNrest(val%))
381 DEF FNas_vector(val%)
382 IF FNis_vector(val%) THEN =val%
383 IF FNis_empty(val%) THEN =FNempty_vector
384 =FNalloc_vector_pair(FNfirst(val%), FNrest(val%))
387 IF NOT FNis_seq(val%) THEN ERROR &40E80916, "Can't get car of non-sequence"
388 =FNref_local(Z%(val%,2))
391 IF NOT FNis_seq(val%) THEN ERROR &40E80916, "Can't get cdr of non-sequence"
392 =FNref_local(Z%(val%,1))
394 DEF FNalloc_list2(val0%, val1%)
395 =FNalloc_pair(val0%, FNalloc_pair(val1%, FNempty))
397 DEF FNalloc_list3(val0%, val1%, val2%)
398 =FNalloc_pair(val0%, FNalloc_pair(val1%, FNalloc_pair(val2%, FNempty)))
402 WHILE NOT FNis_empty(val%)
410 IF FNis_empty(val%) THEN ERROR &40E80923, "Subscript out of range"
414 IF FNis_empty(val%) THEN ERROR &40E80923, "Subscript out of range"
417 REM ** Core functions **
419 REM Z%(x,1) = index of function in FNcore_call
421 DEF FNis_corefn(val%)
422 =FNtype_of(val%) = &0C
424 DEF FNalloc_corefn(fn%)
430 DEF FNunbox_corefn(val%)
431 IF NOT FNis_corefn(val%) THEN ERROR &40E80919, "Not a core function"
436 REM Hash-maps are represented as a crit-bit tree.
438 REM An internal node has:
439 REM Z%(x,0) >> 16 = next bit of key to check
440 REM Z%(x,1) = index in Z%() of left child (if next bit of key is 0)
441 REM Z%(x,2) = index in Z%() of right child (if next bit of key is 1)
445 REM Z%(x,2) = index in Z%() of value
447 REM The empty hash-map is a special value containing no data.
452 DEF FNhashmap_alloc_leaf(key$, val%)
454 entry% = FNmalloc(&0A)
459 DEF FNhashmap_alloc_node(bit%, left%, right%)
461 entry% = FNmalloc(&11)
462 Z%(entry%,0) += (bit% << 16)
464 Z%(entry%,2) = right%
467 DEF FNis_hashmap(val%)
470 =t% = &11 OR t% = &0A
472 DEF FNkey_bit(key$, bit%)
475 IF cnum% >= LEN(key$) THEN =FALSE
476 =ASC(MID$(key$, cnum% + 1, 1)) AND (&80 >> (bit% AND 7))
478 DEF FNkey_bitdiff(key1$, key2$)
480 WHILE FNkey_bit(key1$, bit%) = FNkey_bit(key2$, bit%)
485 DEF FNhashmap_set(map%, key$, val%)
487 IF FNis_empty(map%) THEN =FNhashmap_alloc_leaf(key$, val%)
488 nearest% = FNhashmap_find(map%, key$)
489 IF Z$(nearest%) = key$ THEN =FNhashmap_replace(map%, key$, val%)
490 bit% = FNkey_bitdiff(key$, Z$(nearest%))
491 =FNhashmap_insert(map%, bit%, key$, val%)
493 DEF FNhashmap_insert(map%, bit%, key$, val%)
495 IF FNtype_of(map%) = &11 AND (Z%(map%,0) >> 16) < bit% THEN
496 IF FNkey_bit(key$, Z%(map%,0) >> 16) THEN
498 right% = FNhashmap_insert(Z%(map%,2), bit%, key$, val%)
500 left% = FNhashmap_insert(Z%(map%,1), bit%, key$, val%)
503 =FNhashmap_alloc_node(Z%(map%,0)>>16, left%, right%)
505 IF FNkey_bit(key$, bit%) THEN
507 right% = FNhashmap_alloc_leaf(key$, val%)
509 left% = FNhashmap_alloc_leaf(key$, val%)
512 =FNhashmap_alloc_node(bit%, left%, right%)
515 REM Replace a known-present key in a non-empty hashmap.
516 DEF FNhashmap_replace(map%, key$, val%)
518 IF FNtype_of(map%) = &0A THEN =FNhashmap_alloc_leaf(key$, val%)
519 IF FNkey_bit(key$, Z%(map%,0) >> 16) THEN
521 right% = FNhashmap_replace(Z%(map%,2), key$, val%)
523 left% = FNhashmap_replace(Z%(map%,1), key$, val%)
526 =FNhashmap_alloc_node(Z%(map%,0)>>16, left%, right%)
528 DEF FNhashmap_remove(map%, key$)
530 IF FNis_empty(map%) THEN =map%
531 IF FNtype_of(map%) = &0A THEN
532 IF Z$(map%) = key$ THEN =FNempty_hashmap
534 IF FNkey_bit(key$, Z%(map%,0) >> 16) THEN
535 child% = FNhashmap_remove(Z%(map%,2), key$)
536 IF FNis_empty(child%) THEN =Z%(map%,1)
537 =FNhashmap_alloc_node(Z%(map%,0)>>16, Z%(map%,1), child%)
539 child% = FNhashmap_remove(Z%(map%,1), key$)
540 IF FNis_empty(child%) THEN =Z%(map%,2)
541 =FNhashmap_alloc_node(Z%(map%,0)>>16, child%, Z%(map%,2))
544 REM FNhashmap_find finds the nearest entry in a non-empty hash-map to
545 REM the key requested, and returns the entire entry.
546 DEF FNhashmap_find(map%, key$)
547 WHILE FNtype_of(map%) = &11
548 IF FNkey_bit(key$, Z%(map%,0) >> 16) THEN map% = Z%(map%,2) ELSE map% = Z%(map%,1)
552 DEF FNhashmap_get(map%, key$)
553 IF NOT FNis_hashmap(map%) THEN ERROR &40E80918, "Can't get item from a non-hashmap"
554 IF FNis_empty(map%) THEN =FNnil
555 map% = FNhashmap_find(map%, key$)
556 IF Z$(map%) = key$ THEN =FNref_local(Z%(map%,2)) ELSE =FNnil
558 DEF FNhashmap_contains(map%, key$)
559 IF NOT FNis_hashmap(map%) THEN ERROR &40E80918, "Can't get item from a non-hashmap"
560 IF FNis_empty(map%) THEN =FALSE
561 map% = FNhashmap_find(map%, key$)
564 DEF FNhashmap_keys(map%)
565 =FNhashmap_keys1(map%, FNempty)
567 DEF FNhashmap_keys1(map%, acc%)
568 IF FNis_empty(map%) THEN =acc%
569 IF FNtype_of(map%) = &0A THEN
570 =FNalloc_pair(FNalloc_string(Z$(map%)), acc%)
572 =FNhashmap_keys1(Z%(map%,1), FNhashmap_keys1(Z%(map%,2), acc%))
574 DEF FNhashmap_vals(map%)
575 =FNhashmap_vals1(map%, FNempty)
577 DEF FNhashmap_vals1(map%, acc%)
578 IF FNis_empty(map%) THEN =acc%
579 IF FNtype_of(map%) = &0A THEN
580 =FNalloc_pair(Z%(map%,2), acc%)
582 =FNhashmap_vals1(Z%(map%,1), FNhashmap_vals1(Z%(map%,2), acc%))
584 DEF PROChashmap_dump(map%)
585 IF FNis_empty(map%) THEN
589 PROChashmap_dump_internal(map%, "")
593 DEF PROChashmap_dump_internal(map%, prefix$)
594 IF FNtype_of(map%) = &0A PRINT prefix$;Z$(map%)
595 IF FNtype_of(map%) = &11 THEN
596 PRINT prefix$;"<";Z%(map%,0) >> 16;">"
597 PROChashmap_dump_internal(Z%(map%,1), prefix$ + "L ")
598 PROChashmap_dump_internal(Z%(map%,2), prefix$ + "R ")
604 REM A function is represented by two cells:
605 REM Z%(x,0) AND &80 = is_macro flag
606 REM Z%(x,1) = index in Z%() of ast
609 REM Z%(y,1) = index in Z%() of params
610 REM Z%(y,2) = index in Z%() of env
613 =FNtype_of(val%) = &15
615 DEF FNis_nonmacro_fn(val%)
616 =FNtype_of(val%) = &15 AND (Z%(val%, 0) AND &80) = &00
619 =FNtype_of(val%) = &15 AND (Z%(val%, 0) AND &80) = &80
621 DEF FNalloc_fn(ast%, params%, env%)
623 val1% = FNmalloc(&15)
625 val2% = FNmalloc(&19)
627 Z%(val2%,1) = params%
632 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
634 newval% = FNmalloc(Z%(val%,0) OR &80)
635 Z%(newval%,1) = Z%(val%,1)
636 Z%(newval%,2) = Z%(val%,2)
637 Z%(newval%,3) = Z%(val%,3)
641 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
642 =FNref_local(Z%(val%,1))
644 DEF FNfn_params(val%)
645 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
646 =FNref_local(Z%(Z%(val%,2),1))
649 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
650 =FNref_local(Z%(Z%(val%,2),2))
654 REM Z%(x,1) = index in Z% of current referent
657 =FNtype_of(val%) = &01
659 DEF FNalloc_atom(contents%)
662 Z%(val%,1) = contents%
665 DEF FNatom_deref(val%)
666 =FNref_local(Z%(val%,1))
668 DEF PROCatom_reset(val%, contents%)
669 Z%(val%,1) = contents%
672 REM ** Environments **
674 REM Z%(x,1) = index in Z% of hash-map
675 REM Z%(x,2) = index in Z% of outer environment
677 DEF FNis_environment(val%)
678 =FNtype_of(val%) = &0D
680 DEF FNalloc_environment(outer%)
683 Z%(val%,1) = FNempty_hashmap
687 DEF FNenvironment_data(val%)
688 IF NOT FNis_environment(val%) THEN ERROR &40E8091D, "Not an environment"
689 =FNref_local(Z%(val%,1))
691 DEF PROCenvironment_set_data(val%, data%)
692 IF NOT FNis_environment(val%) THEN ERROR &40E8091D, "Not an environment"
696 DEF FNenvironment_outer(val%)
697 IF NOT FNis_environment(val%) THEN ERROR &40E8091D, "Not an environment"
698 =FNref_local(Z%(val%,2))
701 REM indent-tabs-mode: nil