Basic: fix errors, reader, if form. Self-host 0-3
[jackhill/mal.git] / basic / types.in.bas
dissimilarity index 94%
index 302f0bf..0ce329d 100644 (file)
-REM               TYPE% -> VALUE%
-REM nil           0     -> (unused)
-REM false         1     -> (unused)
-REM true          2     -> (unused)
-REM integer       3     -> int value
-REM float         4     -> ???
-REM string        5     -> ZS$ index
-REM keyword       6     -> ZS$ index
-REM symbol        7     -> ZS$ index
-REM list next     8     -> ZT% index / or 0
-REM                        followed by value unless empty
-REM vector next   9     -> ZT% index / or 0
-REM                        followed by value unless empty
-REM hashmap       12    -> ???
-REM mal function  13    -> ???
-REM atom          14    -> TYPE% index
-
-INIT_MEMORY:
-  REM global error state
-  ER%=0
-  ER$=""
-
-  REM boxes memory elements
-  SZ%=4096
-  DIM ZT%(SZ%): REM TYPE ARRAY
-  DIM ZV%(SZ%): REM VALUE ARRAY
-
-  REM Predefine nil, false, true
-  ZT%(0) = 0
-  ZT%(1) = 1
-  ZT%(2) = 2
-  ZI%=3
-
-  REM string memory
-  ZJ%=0
-  DIM ZS$(1024)
-
-  REM logic stack
-  PT%=-1: REM index of top of PS% stack
-  DIM PS%(128): REM stack of ZT% indexes
-
-  REM environment
-  REM DIM EKEYS$(1024)
-  REM DIM EVALS%(1024)
-  RETURN
+REM                  Z 0   ->  1
+REM nil                0   ->  (unused)
+REM boolean            1   ->  0: false, 1: true
+REM integer            2   ->  int value
+REM float              3   ->  ???
+REM string/kw          4   ->  S$ index
+REM symbol             5   ->  S$ index
+REM list next/val      6   ->  next Z% index (0 for last)
+REM                    14      value (unless empty)
+REM vector next/val    7   ->  next Z% index (0 for last)
+REM                    14      value (unless empty)
+REM hashmap next/val   8   ->  next Z% index (0 for last)
+REM                    14      key/value (alternating)
+REM function           9   ->  function index
+REM mal function       10  ->  body AST Z% index
+REM                    param   env Z% index
+REM macro (same as 10) 11  ->  body AST Z% index
+REM                    param   env Z% index
+REM atom               12  ->  Z% index
+REM environment        13  ->  data/hashmap Z% index
+REM                    14      outer Z% index (-1 for none)
+REM reference/ptr      14  ->  Z% index / or 0
+REM next free ptr      15  ->  Z% index / or 0
+REM metadata        16-31  ->  Z% index of object with this metadata
+REM                    14  ->  Z% index of metdata object
+
+INIT_MEMORY:
+  T=FRE(0)
+
+  Z1=2048+1024: REM Z% (boxed memory) size (4 bytes each)
+  Z2=256: REM S$ (string memory) size (3 bytes each)
+  Z3=256: REM X% (call stack) size (2 bytes each)
+  Z4=64: REM Y% (release stack) size (4 bytes each)
+
+  REM global error state
+  REM  -2 : no error
+  REM  -1 : string error in ER$
+  REM >=0 : pointer to error object
+  ER=-2
+  ER$=""
+
+  REM boxed element memory
+  DIM Z%(Z1,1): REM TYPE ARRAY
+
+  REM Predefine nil, false, true, and an empty list
+  Z%(0,0)=0:Z%(0,1)=0
+  Z%(1,0)=1:Z%(1,1)=0
+  Z%(2,0)=1:Z%(2,1)=1
+  Z%(3,0)=6+32:Z%(3,1)=0
+  Z%(4,0)=0:Z%(4,1)=0
+  Z%(5,0)=7+32:Z%(5,1)=0
+  Z%(6,0)=0:Z%(6,1)=0
+  Z%(7,0)=8+32:Z%(7,1)=0
+  Z%(8,0)=0:Z%(8,1)=0
+
+  REM start of unused memory
+  ZI=9
+
+  REM start of free list
+  ZK=9
+
+  REM string memory storage
+  S=0:DIM S$(Z2)
+
+  REM call/logic stack
+  X=-1:DIM X%(Z3): REM stack of Z% indexes
+
+  REM pending release stack
+  Y=-1:DIM Y%(Z4,1): REM stack of Z% indexes
+
+  BT=TI
+
+  RETURN
+
+
+REM memory functions
+
+REM ALLOC(T,L) -> R
+REM ALLOC(T,L,N) -> R
+REM ALLOC(T,L,M,N) -> R
+REM L is default for Z%(R,1)
+REM M is default for Z%(R+1,0), if relevant for T
+REM N is default for Z%(R+1,1), if relevant for T
+ALLOC:
+  SZ=2
+  IF T<6 OR T=9 OR T=12 OR T=14 THEN SZ=1
+  REM PRINT "ALLOC T: "+STR$(T)+", SZ: "+STR$(SZ)+", ZK: "+STR$(ZK)
+  U3=ZK
+  U4=ZK
+  ALLOC_LOOP:
+    IF U4=ZI THEN GOTO ALLOC_UNUSED
+    REM TODO sanity check that type is 15
+    IF ((Z%(U4,0)AND-32)/32)=SZ THEN GOTO ALLOC_MIDDLE
+    REM PRINT "ALLOC search: U3: "+STR$(U3)+", U4: "+STR$(U4)
+    U3=U4: REM previous set to current
+    U4=Z%(U4,1): REM current set to next
+    GOTO ALLOC_LOOP
+  ALLOC_MIDDLE:
+    REM PRINT "ALLOC_MIDDLE: U3: "+STR$(U3)+", U4: "+STR$(U4)
+    R=U4
+    REM set free pointer (ZK) to next free
+    IF U4=ZK THEN ZK=Z%(U4,1)
+    REM set previous free to next free
+    IF U4<>ZK THEN Z%(U3,1)=Z%(U4,1)
+    GOTO ALLOC_DONE
+  ALLOC_UNUSED:
+    REM PRINT "ALLOC_UNUSED ZI: "+STR$(ZI)+", U3: "+STR$(U3)+", U4: "+STR$(U4)
+    R=U4
+    ZI=ZI+SZ
+    IF U3=U4 THEN ZK=ZI
+    REM set previous free to new memory top
+    IF U3<>U4 THEN Z%(U3,1)=ZI
+    GOTO ALLOC_DONE
+  ALLOC_DONE:
+    Z%(R,0)=T+32
+    REM set Z%(R,1) to default L
+    IF T>=6 AND T<>9 AND L>0 THEN Z%(L,0)=Z%(L,0)+32
+    Z%(R,1)=L
+
+    IF SZ=1 THEN RETURN
+    Z%(R+1,0)=14: REM default for 6-8, and 13, and >=16 (metadata)
+
+    REM function/macro sets Z%(R+1,0) to default M
+    IF T=10 OR T=11 THEN Z%(M,0)=Z%(M,0)+32:Z%(R+1,0)=M
+
+    REM seq, function/macro, environment sets Z%(R+1,1) to default N
+    IF N>0 THEN Z%(N,0)=Z%(N,0)+32
+    Z%(R+1,1)=N
+    RETURN
+
+REM FREE(AY, SZ) -> nil
+FREE:
+  REM assumes reference count cleanup already (see RELEASE)
+  Z%(AY,0)=(SZ*32)+15: REM set type(15) and size
+  Z%(AY,1)=ZK
+  ZK=AY
+  IF SZ>=2 THEN Z%(AY+1,0)=0:Z%(AY+1,1)=0
+  IF SZ>=3 THEN Z%(AY+2,0)=0:Z%(AY+2,1)=0
+  RETURN
+
+
+REM RELEASE(AY) -> nil
+REM R should not be affected by this call
+RELEASE:
+  RC=0
+
+  GOTO RELEASE_ONE
+
+  RELEASE_TOP:
+
+  IF RC=0 THEN RETURN
+
+  REM pop next object to release, decrease remaining count
+  AY=X%(X):X=X-1
+  RC=RC-1
+
+  RELEASE_ONE:
+
+  REM nil, false, true
+  IF AY<3 THEN GOTO RELEASE_TOP
+
+  U6=Z%(AY,0)AND31: REM type
+
+  REM AZ=AY: PR=1: GOSUB PR_STR
+  REM PRINT "RELEASE AY:"+STR$(AY)+"["+R$+"] (byte0:"+STR$(Z%(AY,0))+")"
+
+  REM sanity check not already freed
+  IF (U6)=15 THEN ER=-1:ER$="Free of free memory: "+STR$(AY):RETURN
+  IF U6=14 THEN GOTO RELEASE_REFERENCE
+  IF Z%(AY,0)<15 THEN ER=-1:ER$="Free of freed object: "+STR$(AY):RETURN
+
+  REM decrease reference count by one
+  Z%(AY,0)=Z%(AY,0)-32
+
+  REM our reference count is not 0, so don't release
+  IF Z%(AY,0)>=32 GOTO RELEASE_TOP
+
+  REM switch on type
+  IF (U6<=5) OR (U6=9) THEN GOTO RELEASE_SIMPLE
+  IF (U6>=6) AND (U6<=8) THEN GOTO RELEASE_SEQ
+  IF U6=10 OR U6=11 THEN GOTO RELEASE_MAL_FUNCTION
+  IF U6>=16 THEN GOTO RELEASE_METADATA
+  IF U6=12 THEN GOTO RELEASE_ATOM
+  IF U6=13 THEN GOTO RELEASE_ENV
+  IF U6=15 THEN ER=-1:ER$="RELEASE of already freed: "+STR$(AY):RETURN
+  ER=-1:ER$="RELEASE not defined for type "+STR$(U6):RETURN
+
+  RELEASE_SIMPLE:
+    REM simple type (no recursing), just call FREE on it
+    SZ=1:GOSUB FREE
+    GOTO RELEASE_TOP
+  RELEASE_SIMPLE_2:
+    REM free the current element and continue
+    SZ=2:GOSUB FREE
+    GOTO RELEASE_TOP
+  RELEASE_SEQ:
+    IF Z%(AY,1)=0 THEN GOTO RELEASE_SIMPLE_2
+    IF Z%(AY+1,0)<>14 THEN ER=-1:ER$="invalid list value"+STR$(AY+1):RETURN
+    REM add value and next element to stack
+    RC=RC+2:X=X+2
+    X%(X-1)=Z%(AY+1,1):X%(X)=Z%(AY,1)
+    GOTO RELEASE_SIMPLE_2
+  RELEASE_ATOM:
+    REM add contained/referred value
+    RC=RC+1:X=X+1:X%(X)=Z%(AY,1)
+    REM free the atom itself
+    GOTO RELEASE_SIMPLE
+  RELEASE_MAL_FUNCTION:
+    REM add ast, params and environment to stack
+    RC=RC+3:X=X+3
+    X%(X-2)=Z%(AY,1):X%(X-1)=Z%(AY+1,0):X%(X)=Z%(AY+1,1)
+    REM free the current 2 element mal_function and continue
+    SZ=2:GOSUB FREE
+    GOTO RELEASE_TOP
+  RELEASE_METADATA:
+    REM add object and metadata object
+    RC=RC+2:X=X+2
+    X%(X-1)=Z%(AY,1):X%(X)=Z%(AY+1,1)
+    SZ=2:GOSUB FREE
+    GOTO RELEASE_TOP
+  RELEASE_ENV:
+    REM add the hashmap data to the stack
+    RC=RC+1:X=X+1:X%(X)=Z%(AY,1)
+    REM if no outer set
+    IF Z%(AY+1,1)=-1 THEN GOTO RELEASE_ENV_FREE
+    REM add outer environment to the stack
+    RC=RC+1:X=X+1:X%(X)=Z%(AY+1,1)
+    RELEASE_ENV_FREE:
+      REM free the current 2 element environment and continue
+      SZ=2:GOSUB FREE
+      GOTO RELEASE_TOP
+  RELEASE_REFERENCE:
+    IF Z%(AY,1)=0 THEN GOTO RELEASE_SIMPLE
+    REM add the referred element to the stack
+    RC=RC+1:X=X+1:X%(X)=Z%(AY,1)
+    REM free the current element and continue
+    SZ=1:GOSUB FREE
+    GOTO RELEASE_TOP
+
+REM RELEASE_PEND(LV) -> nil
+RELEASE_PEND:
+  IF Y<0 THEN RETURN
+  IF Y%(Y,1)<=LV THEN RETURN
+  REM PRINT "RELEASE_PEND releasing:"+STR$(Y%(Y,0))
+  AY=Y%(Y,0):GOSUB RELEASE
+  Y=Y-1
+  GOTO RELEASE_PEND
+
+REM DEREF_R(R) -> R
+DEREF_R:
+  IF (Z%(R,0)AND31)=14 THEN R=Z%(R,1):GOTO DEREF_R
+  RETURN
+
+REM DEREF_A(A) -> A
+DEREF_A:
+  IF (Z%(A,0)AND31)=14 THEN A=Z%(A,1):GOTO DEREF_A
+  RETURN
+
+REM DEREF_B(B) -> B
+DEREF_B:
+  IF (Z%(B,0)AND31)=14 THEN B=Z%(B,1):GOTO DEREF_B
+  RETURN
+
+
+REM general functions
+
+REM EQUAL_Q(A, B) -> R
+EQUAL_Q:
+  ED=0: REM recursion depth
+  R=-1: REM return value
+
+  EQUAL_Q_RECUR:
+
+  GOSUB DEREF_A
+  GOSUB DEREF_B
+
+  REM push A and B
+  X=X+2:X%(X-1)=A:X%(X)=B
+  ED=ED+1
+
+  U1=Z%(A,0)AND31
+  U2=Z%(B,0)AND31
+  IF U1>5 AND U1<8 AND U2>5 AND U2<8 THEN GOTO EQUAL_Q_SEQ
+  IF U1=8 AND U2=8 THEN GOTO EQUAL_Q_HM
+
+  IF U1<>U2 OR Z%(A,1)<>Z%(B,1) THEN R=0
+  GOTO EQUAL_Q_DONE
+
+  EQUAL_Q_SEQ:
+    IF (Z%(A,1)=0) AND (Z%(B,1)=0) THEN GOTO EQUAL_Q_DONE
+    IF (Z%(A,1)=0) OR (Z%(B,1)=0) THEN R=0:GOTO EQUAL_Q_DONE
+
+    REM compare the elements
+    A=Z%(A+1,1):B=Z%(B+1,1)
+    GOTO EQUAL_Q_RECUR
+
+  EQUAL_Q_SEQ_CONTINUE:
+    REM next elements of the sequences
+    A=X%(X-1):B=X%(X)
+    A=Z%(A,1):B=Z%(B,1)
+    X%(X-1)=A:X%(X)=B
+    GOTO EQUAL_Q_SEQ
+
+  EQUAL_Q_HM:
+    R=0
+    GOTO EQUAL_Q_DONE
+
+  EQUAL_Q_DONE:
+    X=X-2: REM pop current A and B
+    ED=ED-1
+    IF R>-1 AND ED>0 THEN GOTO EQUAL_Q_DONE: REM unwind
+    IF ED=0 AND R=-1 THEN R=1
+    IF ED=0 THEN RETURN
+    GOTO EQUAL_Q_SEQ_CONTINUE
+
+REM string functions
+
+REM STRING_(AS$) -> R
+REM intern string (returns string index, not Z% index)
+STRING_:
+  IF S=0 THEN GOTO STRING_NOT_FOUND
+
+  REM search for matching string in S$
+  I=0
+  STRING_LOOP:
+    IF I>S-1 THEN GOTO STRING_NOT_FOUND
+    IF AS$=S$(I) THEN R=I:RETURN
+    I=I+1
+    GOTO STRING_LOOP
+
+  STRING_NOT_FOUND:
+    S$(S)=AS$
+    R=S
+    S=S+1
+    RETURN
+
+REM STRING(AS$, T) -> R
+REM intern string and allocate reference (return Z% index)
+STRING:
+  GOSUB STRING_
+  L=R:GOSUB ALLOC
+  RETURN
+
+REM REPLACE(R$, S1$, S2$) -> R$
+REPLACE:
+  T3$=R$
+  R$=""
+  I=1
+  J=LEN(T3$)
+  REPLACE_LOOP:
+    IF I>J THEN RETURN
+    CH$=MID$(T3$,I,LEN(S1$))
+    IF CH$=S1$ THEN R$=R$+S2$:I=I+LEN(S1$)
+    IF CH$<>S1$ THEN R$=R$+MID$(T3$,I,1):I=I+1
+    GOTO REPLACE_LOOP
+
+
+REM sequence functions
+
+REM FORCE_SEQ_TYPE(A,T) -> R
+FORCE_SEQ_TYPE:
+  REM if it's already the right type, inc ref cnt and return it
+  IF (Z%(A,0)AND31)=T THEN R=A:Z%(R,0)=Z%(R,0)+32:RETURN
+  REM otherwise, copy first element to turn it into correct type
+  B=A+1:GOSUB DEREF_B: REM value to copy
+  L=Z%(A,1):N=B:GOSUB ALLOC: REM T already set
+  IF Z%(A,1)=0 THEN RETURN
+  RETURN
+
+
+REM LIST_Q(A) -> R
+LIST_Q:
+  R=0
+  IF (Z%(A,0)AND31)=6 THEN R=1
+  RETURN
+
+REM EMPTY_Q(A) -> R
+EMPTY_Q:
+  R=0
+  IF Z%(A,1)=0 THEN R=1
+  RETURN
+
+REM COUNT(B) -> R
+REM - returns length of list, not a Z% index
+REM - modifies B
+COUNT:
+  R=-1
+  DO_COUNT_LOOP:
+    R=R+1
+    IF Z%(B,1)<>0 THEN B=Z%(B,1):GOTO DO_COUNT_LOOP
+  RETURN
+
+REM LAST(A) -> R
+LAST:
+  REM TODO check that actually a list/vector
+  IF Z%(A,1)=0 THEN R=0:RETURN: REM empty seq, return nil
+  T6=0
+  LAST_LOOP:
+    IF Z%(A,1)=0 THEN GOTO LAST_DONE: REM end, return previous value
+    T6=A: REM current becomes previous entry
+    A=Z%(A,1): REM next entry
+    GOTO LAST_LOOP
+  LAST_DONE:
+    R=T6+1:GOSUB DEREF_R
+    Z%(R,0)=Z%(R,0)+32
+    RETURN
+
+REM SLICE(A,B,C) -> R
+REM make copy of sequence A from index B to C
+REM returns R6 as reference to last element of slice
+REM returns A as next element following slice (of original)
+SLICE:
+  I=0
+  R5=-1: REM temporary for return as R
+  R6=0: REM previous list element
+  SLICE_LOOP:
+    REM always allocate at least one list element
+    T=6:L=0:N=0:GOSUB ALLOC
+    IF R5=-1 THEN R5=R
+    IF R5<>-1 THEN Z%(R6,1)=R
+    REM advance A to position B
+    SLICE_FIND_B:
+      IF I<B AND Z%(A,1)<>0 THEN A=Z%(A,1):I=I+1:GOTO SLICE_FIND_B
+    REM if current position is C, then return
+    IF C<>-1 AND I>=C THEN R=R5:RETURN
+    REM if we reached end of A, then return
+    IF Z%(A,1)=0 THEN R=R5:RETURN
+    R6=R: REM save previous list element
+    REM copy value and inc ref cnt
+    Z%(R6+1,1)=Z%(A+1,1)
+    R=A+1:GOSUB DEREF_R:Z%(R,0)=Z%(R,0)+32
+    REM advance to next element of A
+    A=Z%(A,1)
+    I=I+1
+    GOTO SLICE_LOOP
+
+REM LIST2(B2,B1) -> R
+LIST2:
+  REM last element is 3 (empty list), second element is B1
+  T=6:L=3:N=B1:GOSUB ALLOC
+
+  REM first element is B2
+  T=6:L=R:N=B2:GOSUB ALLOC
+  AY=L:GOSUB RELEASE: REM new list takes ownership of previous
+
+  RETURN
+
+REM LIST3(B3,B2,B1) -> R
+LIST3:
+  GOSUB LIST2
+
+  REM first element is B3
+  T=6:L=R:N=B3:GOSUB ALLOC
+  AY=L:GOSUB RELEASE: REM new list takes ownership of previous
+
+  RETURN
+
+
+REM hashmap functions
+
+REM HASHMAP() -> R
+HASHMAP:
+  REM just point to static empty hash-map
+  R=7
+  Z%(R,0)=Z%(R,0)+32
+  RETURN
+
+REM ASSOC1(H, K, V) -> R
+ASSOC1:
+  REM deref K and V
+  R=V:GOSUB DEREF_R:V=R
+  R=K:GOSUB DEREF_R:K=R
+
+  REM value ptr
+  T=8:L=H:N=V:GOSUB ALLOC
+  AY=L:GOSUB RELEASE: REM we took ownership of previous hashmap
+  REM key ptr
+  T=8:L=R:N=K:GOSUB ALLOC
+  AY=L:GOSUB RELEASE: REM we took ownership of previous hashmap
+  RETURN
+
+REM ASSOC1(H, K$, V) -> R
+ASSOC1_S:
+  S$(S)=K$
+  REM add the key string
+  T=4:L=S:GOSUB ALLOC
+  S=S+1
+  K=R:GOSUB ASSOC1
+  AY=K:GOSUB RELEASE: REM map took ownership of key
+  RETURN
+
+REM HASHMAP_GET(H, K) -> R
+HASHMAP_GET:
+  H2=H
+  T1$=S$(Z%(K,1)): REM search key string
+  T3=0: REM whether found or not (for HASHMAP_CONTAINS)
+  R=0
+  HASHMAP_GET_LOOP:
+    REM no matching key found
+    IF Z%(H2,1)=0 THEN R=0:RETURN
+    REM follow value ptrs
+    T2=H2+1
+    HASHMAP_GET_DEREF:
+      IF Z%(T2,0)=14 THEN T2=Z%(T2,1):GOTO HASHMAP_GET_DEREF
+    REM get key string
+    T2$=S$(Z%(T2,1))
+    REM if they are equal, we found it
+    IF T1$=T2$ THEN T3=1:R=Z%(H2,1)+1:RETURN
+    REM skip to next key
+    H2=Z%(Z%(H2,1),1)
+    GOTO HASHMAP_GET_LOOP
+
+REM HASHMAP_CONTAINS(H, K) -> R
+HASHMAP_CONTAINS:
+  GOSUB HASHMAP_GET
+  R=T3
+  RETURN
+
+
+REM function functions
+
+REM NATIVE_FUNCTION(A) -> R
+NATIVE_FUNCTION:
+  T=9:L=A:GOSUB ALLOC
+  RETURN
+
+REM MAL_FUNCTION(A, P, E) -> R
+MAL_FUNCTION:
+  T=10:L=A:M=P:N=E:GOSUB ALLOC
+  RETURN