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. High-order bits contain flags.
21 REM sS%() is a shadow stack, used to keep track of which mal values might
22 REM be referenced from local variables at a given depth of the BASIC call
23 REM stack. It grows upwards. sSP% points to the first unused word. sFP%
24 REM points to the start of the current shadow stack frame. The first word
25 REM of each shadow stack frame is the saved value of sFP%. The rest are
34 REM 6 list (each object is a cons cell)
35 REM 8 hash-map (each object is one entry)
41 REM Formats of individual objects are defined below.
44 REM Arbitrarily use half of BASIC's heap as the mal heap, with a bit
45 REM more for strings. Each heap entry is sixteen bytes.
46 DIM Z%((HIMEM-LOMEM)/32,3)
47 DIM S$((HIMEM-LOMEM)/64), S%((HIMEM-LOMEM)/64)
48 DIM sS%((HIMEM-LOMEM)/64)
49 Z%(1,0) = 1: REM false
50 Z%(2,0) = 1: Z%(2,1) = TRUE: REM true
51 Z%(3,0) = 6: REM empty list
52 Z%(4,0) = &86 : REM empty vector
53 Z%(5,0) = 8: REM empty hashmap
70 REM PRINT " >>> ";sFP%
73 REM FNgc_save is equivalent to PROCgc_enter except that it returns a
74 REM value that can be passed to PROCgc_restore to pop all the stack
75 REM frames back to (and including) the one pushed by FNgc_save.
81 REM PRINT ;sS%(sFP%);" <<< ";sFP%
86 DEF PROCgc_restore(oldFP%)
88 REM PRINT "!!! FP reset"
101 DEF FNgc_restore(oldFP%, val%)
102 PROCgc_restore(oldFP%)
105 DEF PROCgc_keep_only2(val1%, val2%)
108 val1% = FNref_local(val1%)
109 val2% = FNref_local(val2%)
114 REM If the heap is full, collect garbage first.
115 IF F% = 0 AND next_Z% > DIM(Z%(),1) THEN PROCgc
139 CASE FNtype_of(val%) OF
140 WHEN 4 : PROCfree_string(val%)
141 WHEN 5 : PROCfree_symbol(val%)
142 WHEN 8 : PROCfree_hashmap(val%)
158 REM PRINT "** START GC **"
161 REM PRINT "** FINISH GC **"
167 REM PRINT ">>marking...";
168 FOR sp% = sSP% - 1 TO 0 STEP -1
172 ELSE PROCgc_mark(sS%(sp%))
178 DEF PROCgc_mark(val%)
179 IF (Z%(val%,0) AND &100) = 0 THEN
182 CASE FNtype_of(val%) OF
183 WHEN 6 : PROCgc_mark_seq(val%)
184 WHEN 8 : PROCgc_mark_hashmap(val%)
185 WHEN 10 : PROCgc_mark_fn(val%)
186 WHEN 12 : PROCgc_mark_atom(val%)
187 WHEN 13 : PROCgc_mark_environment(val%)
194 REM PRINT ">>sweeping ...";
195 FOR val% = 6 TO next_Z% - 1
196 IF FNtype_of(val%) <> 15 AND (Z%(val%,0) AND &100) = 0 THEN
216 REM Z%(x,1) = TRUE or FALSE
218 DEF FNis_boolean(val%)
221 DEF FNalloc_boolean(bval%)
225 DEF FNunbox_boolean(val%)
226 IF NOT FNis_boolean(val%) THEN ERROR &40E80911, "Not a boolean"
229 DEF FNis_truish(val%)
230 IF FNis_nil(val%) THEN =FALSE
231 IF FNis_boolean(val%) THEN =FNunbox_boolean(val%)
236 REM Z%(x,1) = integer value
241 DEF FNalloc_int(ival%)
247 DEF FNunbox_int(val%)
248 IF NOT FNis_int(val%) THEN ERROR &40E80912, "Not an integer"
251 REM ** Strings and keywords **
253 REM A keyword is a string with first character CHR$(127).
255 DEF FNis_string(val%)
258 DEF FNalloc_string(sval$)
261 Z%(val%,1) = FNsalloc(sval$)
264 DEF PROCfree_string(val%)
265 PROCsfree(Z%(val%,1))
268 DEF FNunbox_string(val%)
269 IF NOT FNis_string(val%) THEN ERROR &40E80914, "Not a string"
274 REM Z%(x,1) = index in S$() of the value of the symbol
276 DEF FNis_symbol(val%)
279 DEF FNalloc_symbol(sval$)
282 Z%(val%,1) = FNsalloc(sval$)
285 DEF PROCfree_symbol(val%)
286 PROCsfree(Z%(val%,1))
289 DEF FNunbox_symbol(val%)
290 IF NOT FNis_symbol(val%) THEN ERROR &40E80915, "Not a symbol"
293 REM ** Lists and vectors **
295 REM Lists and vectors are both represented as linked lists: the only
296 REM difference is in the state of the is_vector flag in the head cell
297 REM of the list. Note that this means that the tail of a list may be
298 REM a vector, and vice versa. FNas_list and FNas_vector can be used
299 REM to convert a sequence to a particular type as necessary.
301 REM Z%(x,0) AND &80 = is_vector flag
302 REM Z%(x,1) = index in Z%() of next pair
303 REM Z%(x,2) = index in Z%() of first element
305 REM The empty list is a distinguished value, which happens to have
306 REM both elements nil.
314 DEF FNalloc_pair(car%, cdr%)
321 DEF FNalloc_vector_pair(car%, cdr%)
323 val% = FNalloc_pair(car%, cdr%)
324 Z%(val%,0) = Z%(val%,0) OR &80
328 =val% = FNempty OR val% = FNempty_vector
334 =FNtype_of(val%) = 6 AND (Z%(val%, 0) AND &80) = &00
336 DEF FNis_vector(val%)
337 =FNtype_of(val%) = 6 AND (Z%(val%, 0) AND &80) = &80
340 IF FNis_list(val%) THEN =val%
341 IF FNis_empty(val%) THEN =FNempty
342 =FNalloc_pair(FNfirst(val%), FNrest(val%))
344 DEF FNas_vector(val%)
345 IF FNis_vector(val%) = &80 THEN =val%
346 IF FNis_empty(val%) THEN =FNempty_vector
347 =FNalloc_vector_pair(FNfirst(val%), FNrest(val%))
349 DEF PROCgc_mark_seq(val%)
350 IF NOT FNis_empty(val%) THEN
351 PROCgc_mark(Z%(val%,1))
352 PROCgc_mark(Z%(val%,2))
357 IF NOT FNis_seq(val%) THEN ERROR &40E80916, "Can't get car of non-sequence"
358 IF Z%(val%,1) = 0 THEN ERROR &40E80920, "Can't get car of empty sequence"
359 =FNref_local(Z%(val%,2))
362 IF NOT FNis_seq(val%) THEN ERROR &40E80916, "Can't get cdr of non-sequence"
363 IF Z%(val%,1) = 0 THEN ERROR &40E80920, "Can't get cdr of empty sequence"
364 =FNref_local(Z%(val%,1))
366 DEF FNalloc_list2(val0%, val1%)
367 =FNalloc_pair(val0%, FNalloc_pair(val1%, FNempty))
369 DEF FNalloc_list3(val0%, val1%, val2%)
370 =FNalloc_pair(val0%, FNalloc_pair(val1%, FNalloc_pair(val2%, FNempty)))
374 WHILE NOT FNis_empty(val%)
382 IF FNis_empty(val%) THEN ERROR &40E80923, "Subscript out of range"
386 IF FNis_empty(val%) THEN ERROR &40E80923, "Subscript out of range"
389 DEF PROClist_to_array(val%, a%())
390 REM a%() must already be correctly dimensioned.
392 WHILE NOT FNis_empty(val%)
393 a%(i%) = FNref_local(FNfirst(val%))
399 DEF FNarray_to_list(a%())
403 IF DIM(a%(), 1) = 0 THEN =val%
404 FOR i% = DIM(a%(), 1) - 1 TO 0 STEP -1
405 val% = FNalloc_pair(a%(i%), val%)
409 REM ** Core functions **
411 REM Z%(x,1) = index of function in FNcore_call
413 DEF FNis_corefn(val%)
416 DEF FNalloc_corefn(fn%)
422 DEF FNunbox_corefn(val%)
423 IF NOT FNis_corefn(val%) THEN ERROR &40E80919, "Not a core function"
428 REM Z%(x,1) = index in Z%() of next element
429 REM Z%(x,2) = index in S$() of value
430 REM Z%(x,3) = index in Z%() of value
432 REM To defer implementing mal strings for a bit, hashmap keys are
433 REM currently BASIC strings rather than arbitrary values.
438 DEF FNis_empty_hashmap(val%)
439 =val% = FNempty_hashmap
441 DEF FNalloc_hashmap_entry(key$, val%, next%)
445 Z%(entry%,2) = FNsalloc(key$)
449 DEF FNis_hashmap(val%)
452 DEF PROCgc_mark_hashmap(val%)
453 PROCgc_mark(Z%(val%,1))
454 PROCgc_mark(Z%(val%,3))
457 DEF PROCfree_hashmap(val%)
458 PROCsfree(Z%(val%,2))
461 DEF FNhashmap_get(map%, key$)
462 IF NOT FNis_hashmap(map%) THEN ERROR &40E80918, "Can't get item from a non-hashmap"
463 WHILE map% <> FNempty_hashmap
464 IF S$(Z%(map%,2)) = key$ THEN =FNref_local(Z%(map%,3))
469 DEF FNhashmap_contains(map%, key$)
470 IF NOT FNis_hashmap(map%) THEN ERROR &40E80918, "Can't get item from a non-hashmap"
471 WHILE map% <> FNempty_hashmap
472 IF S$(Z%(map%,2)) = key$ THEN =TRUE
477 DEF FNhashmap_first_key(map%)
478 IF NOT FNis_hashmap(map%) THEN ERROR &40E80918, "Can't get item from a non-hashmap"
481 DEF FNhashmap_first_val(map%)
482 IF NOT FNis_hashmap(map%) THEN ERROR &40E80918, "Can't get item from a non-hashmap"
485 DEF FNhashmap_rest(map%)
486 IF NOT FNis_hashmap(map%) THEN ERROR &40E80918, "Can't get rest of a non-hashmap"
491 REM Z%(x,0) AND &80 = is_macro flag
492 REM Z%(x,1) = index in Z%() of ast
493 REM Z%(x,2) = index in Z%() of params
494 REM Z%(x,3) = index in Z%() of env
497 =FNtype_of(val%) = 10
499 DEF FNis_nonmacro_fn(val%)
500 =FNtype_of(val%) = 10 AND (Z%(val%, 0) AND &80) = &00
503 =FNtype_of(val%) = 10 AND (Z%(val%, 0) AND &80) = &80
505 DEF FNalloc_fn(ast%, params%, env%)
513 DEF PROCmake_macro(val%)
514 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
515 Z%(val%, 0) = Z%(val%, 0) OR &80
518 DEF PROCgc_mark_fn(val%)
519 PROCgc_mark(Z%(val%,1))
520 PROCgc_mark(Z%(val%,2))
521 PROCgc_mark(Z%(val%,3))
525 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
526 =FNref_local(Z%(val%,1))
528 DEF FNfn_params(val%)
529 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
530 =FNref_local(Z%(val%,2))
533 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
534 =FNref_local(Z%(val%,3))
538 REM Z%(x,1) = index in Z% of current referent
541 =FNtype_of(val%) = 12
543 DEF FNalloc_atom(contents%)
546 Z%(val%,1) = contents%
549 DEF PROCgc_mark_atom(val%)
550 PROCgc_mark(Z%(val%,1))
553 DEF FNatom_deref(val%)
554 =FNref_local(Z%(val%,1))
556 DEF PROCatom_reset(val%, contents%)
557 Z%(val%,1) = contents%
560 REM ** Environments **
562 REM Z%(x,1) = index in Z% of hash-map
563 REM Z%(x,2) = index in Z% of outer environment
565 DEF FNis_environment(val%)
566 =FNtype_of(val%) = 13
568 DEF FNalloc_environment(outer%)
571 Z%(val%,1) = FNempty_hashmap
575 DEF PROCgc_mark_environment(val%)
576 PROCgc_mark(Z%(val%,1))
577 PROCgc_mark(Z%(val%,2))
580 DEF FNenvironment_data(val%)
581 IF NOT FNis_environment(val%) THEN ERROR &40E8091D, "Not an environment"
582 =FNref_local(Z%(val%,1))
584 DEF PROCenvironment_set_data(val%, data%)
585 IF NOT FNis_environment(val%) THEN ERROR &40E8091D, "Not an environment"
589 DEF FNenvironment_outer(val%)
590 IF NOT FNis_environment(val%) THEN ERROR &40E8091D, "Not an environment"
591 =FNref_local(Z%(val%,2))
594 REM indent-tabs-mode: nil