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 Following the 8-bit BASIC implementation, we currently have two
11 REM arrays, Z%() containing most objects and S$() containing strings
12 REM (referenced from Z%()). Unlike that implementation, we use a
13 REM two-dimensional array where each object is a whole row. This
14 REM is inefficient but should make memory management simpler.
16 REM S%() holds reference counts for the strings in S$(). At present
17 REM these are all 0 or 1.
19 REM Z%(x,0) holds the type of an object and other small amounts of
20 REM information. The bottom 2 bits indicate the semantics of Z%(x,1):
22 REM &01 : Z%(x,1) is a pointer into Z%()
23 REM &02 : Z%(x,1) is a pointer into S$()
25 REM Z%(x,2) and Z%(x,3) are always pointers into Z%(), to 'nil' if nothing
28 REM The &40 bit is used to distinguish empty lists, vectors and hash-maps.
29 REM The &80 bit distinguishes vectors from lists and macros from functions.
31 REM sS%() is a shadow stack, used to keep track of which mal values might
32 REM be referenced from local variables at a given depth of the BASIC call
33 REM stack. It grows upwards. sSP% points to the first unused word. sFP%
34 REM points to the start of the current shadow stack frame. The first word
35 REM of each shadow stack frame is the saved value of sFP%. The rest are
45 REM &09 list/vector (each object is a cons cell)
47 REM &11 hash-map internal node
48 REM &15 mal function (first part)
49 REM &19 mal function (second part)
50 REM &02 string/keyword
52 REM &0A hash-map leaf node
54 REM Formats of individual objects are defined below.
57 REM Arbitrarily use a quarter of BASIC's heap as the mal heap, with a bit
58 REM more for strings. Each heap entry is sixteen bytes.
59 DIM Z%((HIMEM-LOMEM)/64,3)
60 DIM S$((HIMEM-LOMEM)/128), S%((HIMEM-LOMEM)/128)
61 DIM sS%((HIMEM-LOMEM)/64)
62 Z%(1,0) = &04 : REM false
63 Z%(2,0) = &04 : Z%(2,1) = TRUE : REM true
64 Z%(3,0) = &49 : Z%(3,1) = 3 : REM empty list
65 Z%(4,0) = &C9 : Z%(4,1) = 4 : REM empty vector
66 Z%(5,0) = &51 : REM empty hashmap
83 REM PRINT " >>> ";sFP%
86 REM FNgc_save is equivalent to PROCgc_enter except that it returns a
87 REM value that can be passed to PROCgc_restore to pop all the stack
88 REM frames back to (and including) the one pushed by FNgc_save.
94 REM PRINT ;sS%(sFP%);" <<< ";sFP%
99 DEF PROCgc_restore(oldFP%)
101 REM PRINT "!!! FP reset"
105 DEF FNref_local(val%)
114 DEF FNgc_restore(oldFP%, val%)
115 PROCgc_restore(oldFP%)
118 DEF PROCgc_keep_only2(val1%, val2%)
121 val1% = FNref_local(val1%)
122 val2% = FNref_local(val2%)
127 REM If the heap is full, collect garbage first.
128 IF F% = 0 AND next_Z% > DIM(Z%(),1) THEN PROCgc
152 IF (Z%(val%,0) AND &02) THEN PROCsfree(Z%(val%,1))
167 REM PRINT "** START GC **"
170 REM PRINT "** FINISH GC **"
176 REM PRINT ">>marking...";
177 FOR sp% = sSP% - 1 TO 0 STEP -1
181 ELSE PROCgc_mark(sS%(sp%))
187 DEF PROCgc_mark(val%)
188 IF (Z%(val%,0) AND &100) = 0 THEN
191 IF (Z%(val%,0) AND &01) THEN PROCgc_mark(Z%(val%,1))
192 PROCgc_mark(Z%(val%,2))
193 PROCgc_mark(Z%(val%,3))
199 REM PRINT ">>sweeping ...";
200 FOR val% = 6 TO next_Z% - 1
201 IF FNtype_of(val%) <> &05 AND (Z%(val%,0) AND &100) = 0 THEN
214 DEF FNwith_meta(val%, meta%)
216 newval% = FNmalloc(Z%(val%,0))
217 Z%(newval%,1) = Z%(val%,1)
218 Z%(newval%,2) = Z%(val%,2)
219 Z%(newval%,3) = meta%
232 REM Z%(x,1) = TRUE or FALSE
234 DEF FNis_boolean(val%)
235 =FNtype_of(val%) = &04
237 DEF FNalloc_boolean(bval%)
241 DEF FNunbox_boolean(val%)
242 IF NOT FNis_boolean(val%) THEN ERROR &40E80911, "Not a boolean"
245 DEF FNis_truish(val%)
246 IF FNis_nil(val%) THEN =FALSE
247 IF FNis_boolean(val%) THEN =FNunbox_boolean(val%)
252 REM Z%(x,1) = integer value
255 =FNtype_of(val%) = &08
257 DEF FNalloc_int(ival%)
263 DEF FNunbox_int(val%)
264 IF NOT FNis_int(val%) THEN ERROR &40E80912, "Not an integer"
267 REM ** Strings and keywords **
269 REM A keyword is a string with first character CHR$(127).
271 DEF FNis_string(val%)
272 =FNtype_of(val%) = &02
274 DEF FNalloc_string(sval$)
277 Z%(val%,1) = FNsalloc(sval$)
280 DEF FNunbox_string(val%)
281 IF NOT FNis_string(val%) THEN ERROR &40E80914, "Not a string"
286 REM Z%(x,1) = index in S$() of the value of the symbol
288 DEF FNis_symbol(val%)
289 =FNtype_of(val%) = &06
291 DEF FNalloc_symbol(sval$)
294 Z%(val%,1) = FNsalloc(sval$)
297 DEF FNunbox_symbol(val%)
298 IF NOT FNis_symbol(val%) THEN ERROR &40E80915, "Not a symbol"
301 REM ** Lists and vectors **
303 REM Lists and vectors are both represented as linked lists: the only
304 REM difference is in the state of the is_vector flag in the head cell
305 REM of the list. Note that this means that the tail of a list may be
306 REM a vector, and vice versa. FNas_list and FNas_vector can be used
307 REM to convert a sequence to a particular type as necessary.
309 REM Z%(x,0) AND &80 = is_vector flag
310 REM Z%(x,1) = index in Z%() of next pair
311 REM Z%(x,2) = index in Z%() of first element
313 REM The empty list is a distinguished value, with elements that match
314 REM the spec of 'first' and 'rest'.
322 DEF FNalloc_pair(car%, cdr%)
329 DEF FNalloc_vector_pair(car%, cdr%)
331 val% = FNalloc_pair(car%, cdr%)
332 Z%(val%,0) = Z%(val%,0) OR &80
336 =(Z%(val%,0) AND &40) = &40
339 =FNtype_of(val%) = &09
342 =FNtype_of(val%) = &09 AND (Z%(val%, 0) AND &80) = &00
344 DEF FNis_vector(val%)
345 =FNtype_of(val%) = &09 AND (Z%(val%, 0) AND &80) = &80
348 IF FNis_list(val%) THEN =val%
349 IF FNis_empty(val%) THEN =FNempty
350 =FNalloc_pair(FNfirst(val%), FNrest(val%))
352 DEF FNas_vector(val%)
353 IF FNis_vector(val%) THEN =val%
354 IF FNis_empty(val%) THEN =FNempty_vector
355 =FNalloc_vector_pair(FNfirst(val%), FNrest(val%))
358 IF NOT FNis_seq(val%) THEN ERROR &40E80916, "Can't get car of non-sequence"
359 =FNref_local(Z%(val%,2))
362 IF NOT FNis_seq(val%) THEN ERROR &40E80916, "Can't get cdr of non-sequence"
363 =FNref_local(Z%(val%,1))
365 DEF FNalloc_list2(val0%, val1%)
366 =FNalloc_pair(val0%, FNalloc_pair(val1%, FNempty))
368 DEF FNalloc_list3(val0%, val1%, val2%)
369 =FNalloc_pair(val0%, FNalloc_pair(val1%, FNalloc_pair(val2%, FNempty)))
373 WHILE NOT FNis_empty(val%)
381 IF FNis_empty(val%) THEN ERROR &40E80923, "Subscript out of range"
385 IF FNis_empty(val%) THEN ERROR &40E80923, "Subscript out of range"
388 REM ** Core functions **
390 REM Z%(x,1) = index of function in FNcore_call
392 DEF FNis_corefn(val%)
393 =FNtype_of(val%) = &0C
395 DEF FNalloc_corefn(fn%)
401 DEF FNunbox_corefn(val%)
402 IF NOT FNis_corefn(val%) THEN ERROR &40E80919, "Not a core function"
407 REM Hash-maps are represented as a crit-bit tree.
409 REM An internal node has:
410 REM Z%(x,0) >> 16 = next bit of key to check
411 REM Z%(x,1) = index in Z%() of left child (if next bit of key is 0)
412 REM Z%(x,2) = index in Z%() of right child (if next bit of key is 1)
415 REM Z%(x,1) = index in S$() of key
416 REM Z%(x,2) = index in Z%() of value
418 REM The empty hash-map is a special value containing no data.
423 DEF FNhashmap_alloc_leaf(key$, val%)
425 entry% = FNmalloc(&0A)
426 Z%(entry%,1) = FNsalloc(key$)
430 DEF FNhashmap_alloc_node(bit%, left%, right%)
432 entry% = FNmalloc(&11)
433 Z%(entry%,0) += (bit% << 16)
435 Z%(entry%,2) = right%
438 DEF FNis_hashmap(val%)
441 =t% = &11 OR t% = &0A
443 DEF FNkey_bit(key$, bit%)
446 IF cnum% >= LEN(key$) THEN =FALSE
447 =ASC(MID$(key$, cnum% + 1, 1)) AND (1 << (bit% AND 7))
449 DEF FNkey_bitdiff(key1$, key2$)
451 WHILE FNkey_bit(key1$, bit%) = FNkey_bit(key2$, bit%)
456 DEF FNhashmap_set(map%, key$, val%)
457 LOCAL bit%, left%, right%
458 IF FNis_empty(map%) THEN =FNhashmap_alloc_leaf(key$, val%)
459 IF FNtype_of(map%) = &0A THEN
460 IF S$(Z%(map%,1)) = key$ THEN =FNhashmap_alloc_leaf(key$, val%)
461 bit% = FNkey_bitdiff(key$, S$(Z%(map%,1)))
462 IF FNkey_bit(key$, bit%) THEN
464 right% = FNhashmap_alloc_leaf(key$, val%)
467 left% = FNhashmap_alloc_leaf(key$, val%)
469 =FNhashmap_alloc_node(bit%, left%, right%)
471 IF FNkey_bit(key$, Z%(map%,0) >> 16) THEN
472 =FNhashmap_alloc_node(Z%(map%,0)>>16, Z%(map%,1), FNhashmap_set(Z%(map%,2), key$, val%))
474 =FNhashmap_alloc_node(Z%(map%,0)>>16, FNhashmap_set(Z%(map%,1), key$, val%), Z%(map%,2))
477 DEF FNhashmap_remove(map%, key$)
479 IF FNis_empty(map%) THEN =map%
480 IF FNtype_of(map%) = &0A THEN
481 IF S$(Z%(map%,1)) = key$ THEN =FNempty_hashmap
483 IF FNkey_bit(key$, Z%(map%,0) >> 16) THEN
484 child% = FNhashmap_remove(Z%(map%,2), key$)
485 IF FNis_empty(child%) THEN =Z%(map%,1)
486 =FNhashmap_alloc_node(Z%(map%,0)>>16, Z%(map%,1), child%)
488 child% = FNhashmap_remove(Z%(map%,1), key$)
489 IF FNis_empty(child%) THEN =Z%(map%,2)
490 =FNhashmap_alloc_node(Z%(map%,0)>>16, child%, Z%(map%,2))
494 DEF FNhashmap_get(map%, key$)
495 IF NOT FNis_hashmap(map%) THEN ERROR &40E80918, "Can't get item from a non-hashmap"
496 IF FNis_empty(map%) THEN =FNnil
497 IF FNtype_of(map%) = &0A THEN
498 IF S$(Z%(map%,1)) = key$ THEN =FNref_local(Z%(map%,2)) ELSE =FNnil
500 IF FNkey_bit(key$, Z%(map%,0) >> 16) THEN =FNhashmap_get(Z%(map%,2), key$)
501 =FNhashmap_get(Z%(map%,1), key$)
503 DEF FNhashmap_contains(map%, key$)
504 IF NOT FNis_hashmap(map%) THEN ERROR &40E80918, "Can't get item from a non-hashmap"
505 IF FNis_empty(map%) THEN =FALSE
506 IF FNtype_of(map%) = &0A THEN
507 IF S$(Z%(map%,1)) = key$ THEN =TRUE ELSE =FALSE
509 IF FNkey_bit(key$, Z%(map%,0) >> 16) THEN =FNhashmap_contains(Z%(map%,2), key$)
510 =FNhashmap_contains(Z%(map%,1), key$)
512 DEF FNhashmap_keys(map%)
513 =FNhashmap_keys1(map%, FNempty)
515 DEF FNhashmap_keys1(map%, acc%)
516 REM PROChashmap_dump(map%)
517 IF FNis_empty(map%) THEN =acc%
518 IF FNtype_of(map%) = &0A THEN
519 =FNalloc_pair(FNalloc_string(S$(Z%(map%,1))), acc%)
521 =FNhashmap_keys1(Z%(map%,2), FNhashmap_keys1(Z%(map%,1), acc%))
523 DEF FNhashmap_vals(map%)
524 =FNhashmap_vals1(map%, FNempty)
526 DEF FNhashmap_vals1(map%, acc%)
527 REM PROChashmap_dump(map%)
528 IF FNis_empty(map%) THEN =acc%
529 IF FNtype_of(map%) = &0A THEN
530 =FNalloc_pair(Z%(map%,2), acc%)
532 =FNhashmap_vals1(Z%(map%,2), FNhashmap_vals1(Z%(map%,1), acc%))
534 DEF PROChashmap_dump(map%)
535 IF FNis_empty(map%) THEN
539 PROChashmap_dump_internal(map%, "")
543 DEF PROChashmap_dump_internal(map%, prefix$)
544 IF FNtype_of(map%) = &0A PRINT prefix$;S$(Z%(map%,1))
545 IF FNtype_of(map%) = &11 THEN
546 PRINT prefix$;"<";Z%(map%,0) >> 16;">"
547 PROChashmap_dump_internal(Z%(map%,1), prefix$ + "L ")
548 PROChashmap_dump_internal(Z%(map%,2), prefix$ + "R ")
554 REM A function is represented by two cells:
555 REM Z%(x,0) AND &80 = is_macro flag
556 REM Z%(x,1) = index in Z%() of ast
559 REM Z%(y,1) = index in Z%() of params
560 REM Z%(y,2) = index in Z%() of env
563 =FNtype_of(val%) = &15
565 DEF FNis_nonmacro_fn(val%)
566 =FNtype_of(val%) = &15 AND (Z%(val%, 0) AND &80) = &00
569 =FNtype_of(val%) = &15 AND (Z%(val%, 0) AND &80) = &80
571 DEF FNalloc_fn(ast%, params%, env%)
573 val1% = FNmalloc(&15)
575 val2% = FNmalloc(&19)
577 Z%(val2%,1) = params%
582 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
584 newval% = FNmalloc(Z%(val%,0) OR &80)
585 Z%(newval%,1) = Z%(val%,1)
586 Z%(newval%,2) = Z%(val%,2)
587 Z%(newval%,3) = Z%(val%,3)
591 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
592 =FNref_local(Z%(val%,1))
594 DEF FNfn_params(val%)
595 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
596 =FNref_local(Z%(Z%(val%,2),1))
599 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
600 =FNref_local(Z%(Z%(val%,2),2))
604 REM Z%(x,1) = index in Z% of current referent
607 =FNtype_of(val%) = &01
609 DEF FNalloc_atom(contents%)
612 Z%(val%,1) = contents%
615 DEF FNatom_deref(val%)
616 =FNref_local(Z%(val%,1))
618 DEF PROCatom_reset(val%, contents%)
619 Z%(val%,1) = contents%
622 REM ** Environments **
624 REM Z%(x,1) = index in Z% of hash-map
625 REM Z%(x,2) = index in Z% of outer environment
627 DEF FNis_environment(val%)
628 =FNtype_of(val%) = &0D
630 DEF FNalloc_environment(outer%)
633 Z%(val%,1) = FNempty_hashmap
637 DEF FNenvironment_data(val%)
638 IF NOT FNis_environment(val%) THEN ERROR &40E8091D, "Not an environment"
639 =FNref_local(Z%(val%,1))
641 DEF PROCenvironment_set_data(val%, data%)
642 IF NOT FNis_environment(val%) THEN ERROR &40E8091D, "Not an environment"
646 DEF FNenvironment_outer(val%)
647 IF NOT FNis_environment(val%) THEN ERROR &40E8091D, "Not an environment"
648 =FNref_local(Z%(val%,2))
651 REM indent-tabs-mode: nil