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%(5,0) = 8: REM empty hashmap
70 REM PRINT " >>> ";sFP%
77 REM PRINT ;sS%(sFP%);" <<< ";sFP%
82 DEF PROCgc_restore(oldFP%)
95 val% = FNref_local(val%)
98 DEF PROCgc_keep_only2(val1%, val2%)
101 val1% = FNref_local(val1%)
102 val2% = FNref_local(val2%)
107 REM If the heap is full, collect garbage first.
108 IF F% = 0 AND next_Z% > DIM(Z%(),1) THEN PROCgc
116 Z%(val%,0) = type% OR GCtoggle%
132 CASE FNtype_of(val%) OF
133 WHEN 4 : PROCfree_string(val%)
134 WHEN 5 : PROCfree_symbol(val%)
135 WHEN 8 : PROCfree_hashmap(val%)
151 REM PRINT "** START GC **"
152 GCtoggle% = GCtoggle% EOR &100
155 REM PRINT "** FINISH GC **"
161 REM PRINT ">>marking...";
162 FOR sp% = sSP% - 1 TO 0 STEP -1
166 ELSE PROCgc_mark(sS%(sp%))
172 DEF PROCgc_mark(val%)
173 IF (Z%(val%,0) AND &100) <> GCtoggle% THEN
175 Z%(val%,0) = Z%(val%,0) EOR &100
176 CASE FNtype_of(val%) OF
177 WHEN 6 : PROCgc_mark_list(val%)
178 WHEN 8 : PROCgc_mark_hashmap(val%)
179 WHEN 10 : PROCgc_mark_fn(val%)
180 WHEN 12 : PROCgc_mark_atom(val%)
181 WHEN 13 : PROCgc_mark_environment(val%)
188 REM PRINT ">>sweeping ...";
189 FOR val% = 6 TO next_Z% - 1
190 IF FNtype_of(val%) <> 15 AND (Z%(val%,0) AND &100) <> GCtoggle% THEN
208 REM Z%(x,1) = TRUE or FALSE
210 DEF FNis_boolean(val%)
213 DEF FNalloc_boolean(bval%)
217 DEF FNunbox_boolean(val%)
218 IF NOT FNis_boolean(val%) THEN ERROR &40E80911, "Not a boolean"
221 DEF FNis_truish(val%)
222 IF FNis_nil(val%) THEN =FALSE
223 IF FNis_boolean(val%) THEN =FNunbox_boolean(val%)
228 REM Z%(x,1) = integer value
233 DEF FNalloc_int(ival%)
239 DEF FNunbox_int(val%)
240 IF NOT FNis_int(val%) THEN ERROR &40E80912, "Not an integer"
245 DEF FNis_string(val%)
248 DEF FNalloc_string(sval$)
251 Z%(val%,1) = FNsalloc(sval$)
254 DEF PROCfree_string(val%)
255 PROCsfree(Z%(val%,1))
258 DEF FNunbox_string(val%)
259 IF NOT FNis_string(val%) THEN ERROR &40E80914, "Not a string"
264 REM Z%(x,1) = index in S$() of the value of the symbol
266 DEF FNis_symbol(val%)
269 DEF FNalloc_symbol(sval$)
272 Z%(val%,1) = FNsalloc(sval$)
275 DEF PROCfree_symbol(val%)
276 PROCsfree(Z%(val%,1))
279 DEF FNunbox_symbol(val%)
280 IF NOT FNis_symbol(val%) THEN ERROR &40E80915, "Not a symbol"
285 REM Z%(x,1) = index in Z%() of next pair
286 REM Z%(x,2) = index in Z%() of first element
288 REM The empty list is a distinguished value, which happens to have
289 REM both elements nil.
294 DEF FNalloc_pair(car%, cdr%)
307 DEF PROCgc_mark_list(val%)
308 IF NOT FNis_empty(val%) THEN
309 PROCgc_mark(Z%(val%,1))
310 PROCgc_mark(Z%(val%,2))
315 IF NOT FNis_list(val%) THEN ERROR &40E80916, "Can't get car of non-list"
316 IF Z%(val%,1) = 0 THEN ERROR &40E80920, "Can't get car of empty list"
317 =FNref_local(Z%(val%,2))
320 IF NOT FNis_list(val%) THEN ERROR &40E80916, "Can't get cdr of non-list"
321 IF Z%(val%,1) = 0 THEN ERROR &40E80920, "Can't get cdr of empty list"
322 =FNref_local(Z%(val%,1))
324 DEF FNalloc_list2(val0%, val1%)
325 =FNalloc_pair(val0%, FNalloc_pair(val1%, FNempty))
327 DEF FNalloc_list3(val0%, val1%, val2%)
328 =FNalloc_pair(val0%, FNalloc_pair(val1%, FNalloc_pair(val2%, FNempty)))
332 WHILE NOT FNis_empty(val%)
333 val% = FNlist_cdr(val%)
338 DEF FNlist_nth(val%, n%)
340 IF FNis_empty(val%) THEN ERROR &40E80923, "Subscript out of range"
341 val% = FNlist_cdr(val%)
344 IF FNis_empty(val%) THEN ERROR &40E80923, "Subscript out of range"
347 DEF PROClist_to_array(val%, a%())
348 REM a%() must already be correctly dimensioned.
350 WHILE NOT FNis_empty(val%)
351 a%(i%) = FNref_local(FNlist_car(val%))
352 val% = FNlist_cdr(val%)
357 DEF FNarray_to_list(a%())
361 IF DIM(a%(), 1) = 0 THEN =val%
362 FOR i% = DIM(a%(), 1) - 1 TO 0 STEP -1
363 val% = FNalloc_pair(a%(i%), val%)
367 REM ** Core functions **
369 REM Z%(x,1) = index of function in FNcore_call
371 DEF FNis_corefn(val%)
374 DEF FNalloc_corefn(fn%)
380 DEF FNunbox_corefn(val%)
381 IF NOT FNis_corefn(val%) THEN ERROR &40E80919, "Not a core function"
386 REM Z%(x,1) = index in Z%() of next element
387 REM Z%(x,2) = index in S$() of value
388 REM Z%(x,3) = index in Z%() of value
390 REM To defer implementing mal strings for a bit, hashmap keys are
391 REM currently BASIC strings rather than arbitrary values.
396 DEF FNalloc_hashmap_entry(key$, val%, next%)
400 Z%(entry%,2) = FNsalloc(key$)
404 DEF FNis_hashmap(val%)
407 DEF PROCgc_mark_hashmap(val%)
408 PROCgc_mark(Z%(val%,1))
409 PROCgc_mark(Z%(val%,3))
412 DEF PROCfree_hashmap(val%)
413 PROCsfree(Z%(val%,2))
416 DEF FNhashmap_get(map%, key$)
417 IF NOT FNis_hashmap(map%) THEN ERROR &40E80918, "Can't get item from a non-hashmap"
418 IF map% = FNempty_hashmap THEN =FNnil
419 IF S$(Z%(map%,2)) = key$ THEN =FNref_local(Z%(map%,3))
420 =FNhashmap_get(Z%(map%,1), key$)
422 DEF FNhashmap_contains(map%, key$)
423 IF NOT FNis_hashmap(map%) THEN ERROR &40E80918, "Can't get item from a non-hashmap"
424 IF map% = FNempty_hashmap THEN =FALSE
425 IF S$(Z%(map%,2)) = key$ THEN =TRUE
426 =FNhashmap_get(Z%(map%,1), key$)
430 REM Z%(x,0) AND &80 = is_macro flag
431 REM Z%(x,1) = index in Z%() of ast
432 REM Z%(x,2) = index in Z%() of params
433 REM Z%(x,3) = index in Z%() of env
436 =FNtype_of(val%) = 10
438 DEF FNis_nonmacro_fn(val%)
439 =FNtype_of(val%) = 10 AND (Z%(val%, 0) AND &80) = &00
442 =FNtype_of(val%) = 10 AND (Z%(val%, 0) AND &80) = &80
444 DEF FNalloc_fn(ast%, params%, env%)
452 DEF PROCmake_macro(val%)
453 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
454 Z%(val%, 0) = Z%(val%, 0) OR &80
457 DEF PROCgc_mark_fn(val%)
458 PROCgc_mark(Z%(val%,1))
459 PROCgc_mark(Z%(val%,2))
460 PROCgc_mark(Z%(val%,3))
464 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
465 =FNref_local(Z%(val%,1))
467 DEF FNfn_params(val%)
468 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
469 =FNref_local(Z%(val%,2))
472 IF NOT FNis_fn(val%) THEN ERROR &40E8091A, "Not a function"
473 =FNref_local(Z%(val%,3))
477 REM Z%(x,1) = index in Z% of current referent
480 =FNtype_of(val%) = 12
482 DEF FNalloc_atom(contents%)
485 Z%(val%,1) = contents%
488 DEF PROCgc_mark_atom(val%)
489 PROCgc_mark(Z%(val%,1))
492 DEF FNatom_deref(val%)
493 =FNref_local(Z%(val%,1))
495 DEF PROCatom_reset(val%, contents%)
496 Z%(val%,1) = contents%
499 REM ** Environments **
501 REM Z%(x,1) = index in Z% of hash-map
502 REM Z%(x,2) = index in Z% of outer environment
504 DEF FNis_environment(val%)
505 =FNtype_of(val%) = 13
507 DEF FNalloc_environment(outer%)
510 Z%(val%,1) = FNempty_hashmap
514 DEF PROCgc_mark_environment(val%)
515 PROCgc_mark(Z%(val%,1))
516 PROCgc_mark(Z%(val%,2))
519 DEF FNenvironment_data(val%)
520 IF NOT FNis_environment(val%) THEN ERROR &40E8091D, "Not an environment"
521 =FNref_local(Z%(val%,1))
523 DEF PROCenvironment_set_data(val%, data%)
524 IF NOT FNis_environment(val%) THEN ERROR &40E8091D, "Not an environment"
528 DEF FNenvironment_outer(val%)
529 IF NOT FNis_environment(val%) THEN ERROR &40E8091D, "Not an environment"
530 =FNref_local(Z%(val%,2))
533 REM indent-tabs-mode: nil