Basic: Reduce GOSUB use. Partial self-host to step3
authorJoel Martin <github@martintribe.org>
Tue, 25 Oct 2016 04:29:27 +0000 (23:29 -0500)
committerJoel Martin <github@martintribe.org>
Tue, 25 Oct 2016 04:29:27 +0000 (23:29 -0500)
step4 runs out of space attempting to load the program. Step2 and
step3 run out of memory (stack exhaustion) for more complicated forms.

- Use GOTO with return label on our stack instead of GOSUB for:
    - APPLY function in types.in.bas
    - "apply", "map" and "swap!" core functions
- Implement DO TCO. Change EVAL_AST to detect if we are called from DO
  and exit one element early.
- Remove GOSUB recursion from EQUAL_Q
- Inline PAIR_Q. Reduce REPLACE stack use.
- Remove one level of GOSUB/stack by calling REP with GOTO
- Simplify mal/step2_eval.mal to remove use of (or ) macro in
  eval_ast.
- Fix ON GOTO/GOSUB line detection in basicpp

13 files changed:
basic/basicpp.py
basic/core.in.bas
basic/run
basic/step4_if_fn_do.in.bas
basic/step5_tco.in.bas
basic/step6_file.in.bas
basic/step7_quote.in.bas
basic/step8_macros.in.bas
basic/step9_try.in.bas
basic/stepA_mal.in.bas
basic/types.in.bas
basic/variables.txt
mal/step2_eval.mal

index fcfc864..c122cf9 100755 (executable)
@@ -111,8 +111,8 @@ def finalize(lines, args):
             stext = text
             text = re.sub(r"(THEN) %s\b" % a, r"THEN %s" % b, stext)
             #text = re.sub(r"(THEN)%s\b" % a, r"THEN%s" % b, stext)
-            text = re.sub(r"(ON [^:]* GOTO [^:\n]*)\b%s\b" % a, r"\g<1>%s" % b, text)
-            text = re.sub(r"(ON [^:]* GOSUB [^:\n]*)\b%s\b" % a, r"\g<2>%s" % b, text)
+            text = re.sub(r"(ON [^:\n]* GOTO [^:\n]*)\b%s\b" % a, r"\g<1>%s" % b, text)
+            text = re.sub(r"(ON [^:\n]* GOSUB [^:\n]*)\b%s\b" % a, r"\g<2>%s" % b, text)
             text = re.sub(r"(GOSUB) %s\b" % a, r"\1 %s" % b, text)
             text = re.sub(r"(GOTO) %s\b" % a, r"\1 %s" % b, text)
             #text = re.sub(r"(GOTO)%s\b" % a, r"\1%s" % b, text)
index 0cff8dc..5e0bc87 100644 (file)
@@ -1,3 +1,191 @@
+REM APPLY should really be in types.in.bas but it is here because it
+REM has return labels into DO_TCO_FUNCTION so it will cause syntax
+REM errors for steps1-3 if it is in types.in.bas because there are
+REM unresolved labels.
+
+REM APPLY(F, AR) -> R
+REM   - restores E
+REM   - call using GOTO and with return label/address on the stack
+APPLY:
+  REM if metadata, get the actual object
+  IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1)
+
+  IF (Z%(F,0)AND31)=9 THEN GOTO APPLY_FUNCTION
+  IF (Z%(F,0)AND31)=10 THEN GOTO APPLY_MAL_FUNCTION
+  IF (Z%(F,0)AND31)=11 THEN GOTO APPLY_MAL_FUNCTION
+
+  APPLY_FUNCTION:
+    REM regular function
+    IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_APPLY
+    REM for recur functions (apply, map, swap!), use GOTO
+    IF Z%(F,1)>60 THEN X=X+1:X%(X)=1:GOTO DO_TCO_FUNCTION
+    DO_TCO_FUNCTION_RETURN_APPLY:
+    GOTO APPLY_DONE
+
+  APPLY_MAL_FUNCTION:
+    X=X+1:X%(X)=E: REM save the current environment
+
+    REM create new environ using env and params stored in the
+    REM function and bind the params to the apply arguments
+    O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS
+
+    A=Z%(F,1):E=R:GOSUB EVAL
+
+    AY=E:GOSUB RELEASE: REM release the new environment
+
+    E=X%(X):X=X-1: REM pop/restore the saved environment
+
+  APPLY_DONE:
+    REM pop APPLY return label/address
+    RN=X%(X):X=X-1
+    ON RN GOTO APPLY_RETURN_1,APPLY_RETURN_2,APPLY_RETURN_MAP,APPLY_RETURN_SWAP,APPLY_RETURN_MACROEXPAND
+
+
+REM DO_TCO_FUNCTION(F, AR)
+REM   - similar to DO_FUNCTION but non-GOSUB version for potentially
+REM     recursive function (apply, map, swap!)
+DO_TCO_FUNCTION:
+  FF=Z%(F,1)
+
+  REM Get argument values
+  R=AR+1:GOSUB DEREF_R:AA=R
+  R=Z%(AR,1)+1:GOSUB DEREF_R:AB=R
+
+  ON FF-60 GOTO DO_APPLY,DO_MAP,DO_SWAP_BANG
+
+  DO_APPLY:
+    F=AA
+    AR=Z%(AR,1)
+    A=AR:GOSUB COUNT:R4=R
+
+    A=Z%(AR+1,1)
+    REM no intermediate args, but not a list, so convert it first
+    IF R4<=1 AND (Z%(A,0)AND31)<>6 THEN T=6:GOSUB FORCE_SEQ_TYPE:GOTO DO_APPLY_2
+    REM no intermediate args, just call APPLY directly
+    IF R4<=1 THEN GOTO DO_APPLY_1
+
+    REM prepend intermediate args to final args element
+    A=AR:B=0:C=R4-1:GOSUB SLICE
+    REM release the terminator of new list (we skip over it)
+    AY=Z%(R6,1):GOSUB RELEASE
+    REM attach end of slice to final args element
+    Z%(R6,1)=Z%(A+1,1)
+    Z%(Z%(A+1,1),0)=Z%(Z%(A+1,1),0)+32
+
+    GOTO DO_APPLY_2
+
+    DO_APPLY_1:
+      X=X+1:X%(X)=1: REM push APPLY return label/address
+      AR=A:GOTO APPLY
+      REM APPLY return label/address popped by APPLY
+      APPLY_RETURN_1:
+
+      GOTO DO_TCO_FUNCTION_RETURN
+
+    DO_APPLY_2:
+      X=X+1:X%(X)=R: REM push/save new args for release
+
+      X=X+1:X%(X)=2: REM push APPLY return label/address
+      AR=R:GOTO APPLY
+      REM APPLY return label/address popped by APPLY
+      APPLY_RETURN_2:
+
+      AY=X%(X):X=X-1:GOSUB RELEASE: REM pop/release new args
+      GOTO DO_TCO_FUNCTION_RETURN
+
+  DO_MAP:
+    F=AA
+
+    REM first result list element
+    T=6:L=0:N=0:GOSUB ALLOC
+
+    REM push future return val, prior entry, F and AB
+    X=X+4:X%(X-3)=R:X%(X-2)=0:X%(X-1)=F:X%(X)=AB
+
+    DO_MAP_LOOP:
+      REM set previous to current if not the first element
+      IF X%(X-2)<>0 THEN Z%(X%(X-2),1)=R
+      REM update previous reference to current
+      X%(X-2)=R
+
+      IF Z%(AB,1)=0 THEN GOTO DO_MAP_DONE
+
+      REM create argument list for apply call
+      Z%(3,0)=Z%(3,0)+32
+      REM inc ref cnt of referred argument
+      T=6:L=3:N=Z%(AB+1,1):GOSUB ALLOC
+
+      REM push argument list
+      X=X+1:X%(X)=R
+
+      X=X+1:X%(X)=3: REM push APPLY return label/address
+      AR=R:GOTO APPLY
+      REM APPLY return label/address popped by APPLY
+      APPLY_RETURN_MAP:
+
+      REM pop apply args are release them
+      AY=X%(X):X=X-1:GOSUB RELEASE
+
+      REM set the result value
+      Z%(X%(X-2)+1,1)=R
+
+      REM restore F
+      F=X%(X-1)
+
+      REM update AB to next source element
+      X%(X)=Z%(X%(X),1)
+      AB=X%(X)
+
+      REM allocate next element
+      T=6:L=0:N=0:GOSUB ALLOC
+
+      GOTO DO_MAP_LOOP
+
+    DO_MAP_DONE:
+      REM get return val
+      R=X%(X-3)
+      REM pop everything off stack
+      X=X-4
+      GOTO DO_TCO_FUNCTION_RETURN
+
+
+  DO_SWAP_BANG:
+    F=AB
+
+    REM add atom to front of the args list
+    T=6:L=Z%(Z%(AR,1),1):N=Z%(AA,1):GOSUB ALLOC: REM cons
+    AR=R
+
+    REM push args for release after
+    X=X+1:X%(X)=AR
+
+    REM push atom
+    X=X+1:X%(X)=AA
+
+    X=X+1:X%(X)=4: REM push APPLY return label/address
+    GOTO APPLY
+    REM APPLY return label/address popped by APPLY
+    APPLY_RETURN_SWAP:
+
+    REM pop atom
+    AA=X%(X):X=X-1
+
+    REM pop and release args
+    AY=X%(X):X=X-1:GOSUB RELEASE
+
+    REM use reset to update the value
+    AB=R:GOSUB DO_RESET_BANG
+
+    REM but decrease ref cnt of return by 1 (not sure why)
+    AY=R:GOSUB RELEASE
+
+    GOTO DO_TCO_FUNCTION_RETURN
+
+  DO_TCO_FUNCTION_RETURN:
+    REM pop EVAL AST return label/address
+    RN=X%(X):X=X-1
+    ON RN GOTO DO_TCO_FUNCTION_RETURN_APPLY,DO_TCO_FUNCTION_RETURN_EVAL
+
 
 REM DO_FUNCTION(F, AR)
 DO_FUNCTION:
@@ -10,7 +198,7 @@ DO_FUNCTION:
 
   REM Switch on the function number
   IF FF>59 THEN ER=-1:ER$="unknown function"+STR$(FF):RETURN
-  ON FF/10+1 GOTO DO_1_9,DO_10_19,DO_20_29,DO_30_39,DO_40_49,DO_50_59
+  ON FF/10+1 GOTO DO_1_9,DO_10_19,DO_20_29,DO_30_39,DO_40_49,DO_50_56
 
   DO_1_9:
   ON FF GOTO DO_EQUAL_Q,DO_THROW,DO_NIL_Q,DO_TRUE_Q,DO_FALSE_Q,DO_STRING_Q,DO_SYMBOL,DO_SYMBOL_Q,DO_KEYWORD
@@ -21,9 +209,9 @@ DO_FUNCTION:
   DO_30_39:
   ON FF-29 GOTO DO_VECTOR_Q,DO_HASH_MAP,DO_MAP_Q,DO_ASSOC,DO_THROW,DO_GET,DO_CONTAINS,DO_KEYS,DO_VALS,DO_SEQUENTIAL_Q
   DO_40_49:
-  ON FF-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_APPLY,DO_MAP,DO_THROW
-  DO_50_59:
-  ON FF-49 GOTO DO_THROW,DO_WITH_META,DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_SWAP_BANG,DO_EVAL,DO_READ_FILE
+  ON FF-39 GOTO DO_CONS,DO_CONCAT,DO_NTH,DO_FIRST,DO_REST,DO_EMPTY_Q,DO_COUNT,DO_THROW,DO_THROW,DO_WITH_META
+  DO_50_56:
+  ON FF-49 GOTO DO_META,DO_ATOM,DO_ATOM_Q,DO_DEREF,DO_RESET_BANG,DO_EVAL,DO_READ_FILE
 
   DO_EQUAL_Q:
     A=AA:B=AB:GOSUB EQUAL_Q
@@ -148,7 +336,7 @@ DO_FUNCTION:
     T=2:L=Z%(AA,1)/Z%(AB,1):GOSUB ALLOC
     RETURN
   DO_TIME_MS:
-    R=0
+    T=2:L=INT((TI-BT)*16.667):GOSUB ALLOC
     RETURN
 
   DO_LIST:
@@ -301,81 +489,6 @@ DO_FUNCTION:
     A=AA:GOSUB COUNT
     T=2:L=R:GOSUB ALLOC
     RETURN
-  DO_APPLY:
-    F=AA
-    AR=Z%(AR,1)
-    A=AR:GOSUB COUNT:R4=R
-
-    A=Z%(AR+1,1)
-    REM no intermediate args, but not a list, so convert it first
-    IF R4<=1 AND (Z%(A,0)AND31)<>6 THEN T=6:GOSUB FORCE_SEQ_TYPE:GOTO DO_APPLY_2
-    REM no intermediate args, just call APPLY directly
-    IF R4<=1 THEN AR=A:GOSUB APPLY:RETURN
-
-    REM prepend intermediate args to final args element
-    A=AR:B=0:C=R4-1:GOSUB SLICE
-    REM release the terminator of new list (we skip over it)
-    AY=Z%(R6,1):GOSUB RELEASE
-    REM attach end of slice to final args element
-    Z%(R6,1)=Z%(A+1,1)
-    Z%(Z%(A+1,1),0)=Z%(Z%(A+1,1),0)+32
-
-    DO_APPLY_2:
-      X=X+1:X%(X)=R: REM push/save new args for release
-      AR=R:GOSUB APPLY
-      AY=X%(X):X=X-1:GOSUB RELEASE: REM pop/release new args
-      RETURN
-  DO_MAP:
-    F=AA
-
-    REM first result list element
-    T=6:L=0:N=0:GOSUB ALLOC
-
-    REM push future return val, prior entry, F and AB
-    X=X+4:X%(X-3)=R:X%(X-2)=0:X%(X-1)=F:X%(X)=AB
-
-    DO_MAP_LOOP:
-      REM set previous to current if not the first element
-      IF X%(X-2)<>0 THEN Z%(X%(X-2),1)=R
-      REM update previous reference to current
-      X%(X-2)=R
-
-      IF Z%(AB,1)=0 THEN GOTO DO_MAP_DONE
-
-      REM create argument list for apply call
-      Z%(3,0)=Z%(3,0)+32
-      REM inc ref cnt of referred argument
-      T=6:L=3:N=Z%(AB+1,1):GOSUB ALLOC
-
-      REM push argument list
-      X=X+1:X%(X)=R
-
-      AR=R:GOSUB APPLY
-
-      REM pop apply args are release them
-      AY=X%(X):X=X-1:GOSUB RELEASE
-
-      REM set the result value
-      Z%(X%(X-2)+1,1)=R
-
-      REM restore F
-      F=X%(X-1)
-
-      REM update AB to next source element
-      X%(X)=Z%(X%(X),1)
-      AB=X%(X)
-
-      REM allocate next element
-      T=6:L=0:N=0:GOSUB ALLOC
-
-      GOTO DO_MAP_LOOP
-
-    DO_MAP_DONE:
-      REM get return val
-      R=X%(X-3)
-      REM pop everything off stack
-      X=X-4
-      RETURN
 
   DO_WITH_META:
     T=Z%(AA,0)AND31
@@ -408,41 +521,13 @@ DO_FUNCTION:
     REM update value
     Z%(AA,1)=R
     RETURN
-  DO_SWAP_BANG:
-    F=AB
-
-    REM add atom to front of the args list
-    T=6:L=Z%(Z%(AR,1),1):N=Z%(AA,1):GOSUB ALLOC: REM cons
-    AR=R
-
-    REM push args for release after
-    X=X+1:X%(X)=AR
-
-    REM push atom
-    X=X+1:X%(X)=AA
-
-    GOSUB APPLY
-
-    REM pop atom
-    AA=X%(X):X=X-1
-
-    REM pop and release args
-    AY=X%(X):X=X-1:GOSUB RELEASE
-
-    REM use reset to update the value
-    AB=R:GOSUB DO_RESET_BANG
-
-    REM but decrease ref cnt of return by 1 (not sure why)
-    AY=R:GOSUB RELEASE
-
-    RETURN
 
-  DO_PR_MEMORY:
-    P1=ZT:P2=-1:GOSUB PR_MEMORY
-    RETURN
-  DO_PR_MEMORY_SUMMARY:
-    GOSUB PR_MEMORY_SUMMARY
-    RETURN
+  REM DO_PR_MEMORY:
+  REM   P1=ZT:P2=-1:GOSUB PR_MEMORY
+  REM   RETURN
+  REM DO_PR_MEMORY_SUMMARY:
+  REM   GOSUB PR_MEMORY_SUMMARY
+  REM   RETURN
 
   DO_EVAL:
     A=AA:E=D:GOSUB EVAL
@@ -513,19 +598,23 @@ INIT_CORE_NS:
   K$="rest":A=44:GOSUB INIT_CORE_SET_FUNCTION
   K$="empty?":A=45:GOSUB INIT_CORE_SET_FUNCTION
   K$="count":A=46:GOSUB INIT_CORE_SET_FUNCTION
-  K$="apply":A=47:GOSUB INIT_CORE_SET_FUNCTION
-  K$="map":A=48:GOSUB INIT_CORE_SET_FUNCTION
 
-  K$="with-meta":A=51:GOSUB INIT_CORE_SET_FUNCTION
-  K$="meta":A=52:GOSUB INIT_CORE_SET_FUNCTION
-  K$="atom":A=53:GOSUB INIT_CORE_SET_FUNCTION
-  K$="atom?":A=54:GOSUB INIT_CORE_SET_FUNCTION
-  K$="deref":A=55:GOSUB INIT_CORE_SET_FUNCTION
-  K$="reset!":A=56:GOSUB INIT_CORE_SET_FUNCTION
-  K$="swap!":A=57:GOSUB INIT_CORE_SET_FUNCTION
+  REM K$="conj":A=47:GOSUB INIT_CORE_SET_FUNCTION
+  REM K$="seq":A=48:GOSUB INIT_CORE_SET_FUNCTION
+
+  K$="with-meta":A=49:GOSUB INIT_CORE_SET_FUNCTION
+  K$="meta":A=50:GOSUB INIT_CORE_SET_FUNCTION
+  K$="atom":A=51:GOSUB INIT_CORE_SET_FUNCTION
+  K$="atom?":A=52:GOSUB INIT_CORE_SET_FUNCTION
+  K$="deref":A=53:GOSUB INIT_CORE_SET_FUNCTION
+  K$="reset!":A=54:GOSUB INIT_CORE_SET_FUNCTION
 
-  K$="eval":A=58:GOSUB INIT_CORE_SET_FUNCTION
+  K$="eval":A=55:GOSUB INIT_CORE_SET_FUNCTION
+  K$="read-file":A=56:GOSUB INIT_CORE_SET_FUNCTION
 
-  K$="read-file":A=59:GOSUB INIT_CORE_SET_FUNCTION
+  REM these are in DO_TCO_FUNCTION
+  K$="apply":A=61:GOSUB INIT_CORE_SET_FUNCTION
+  K$="map":A=62:GOSUB INIT_CORE_SET_FUNCTION
+  K$="swap!":A=63:GOSUB INIT_CORE_SET_FUNCTION
 
   RETURN
index 6c2e48a..7fe8318 100755 (executable)
--- a/basic/run
+++ b/basic/run
@@ -1,3 +1,4 @@
 #!/bin/bash
-(echo "(list $(for a in "${@}"; do echo -n "\"${a}\""; done))") > $(dirname $0)/.args.mal
-exec cbmbasic $(dirname $0)/${STEP:-stepA_mal}.bas "${@}"
+cd $(dirname $0)
+(echo "(list $(for a in "${@}"; do echo -n "\"${a}\""; done))") > .args.mal
+exec cbmbasic ${STEP:-stepA_mal}.bas "${@}"
index ed61505..1b10b86 100755 (executable)
@@ -112,7 +112,6 @@ EVAL_AST:
     REM pop EVAL AST return label/address
     RN=X%(X):X=X-1
     ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3
-    RETURN
 
 REM EVAL(A, E)) -> R
 EVAL:
@@ -121,6 +120,8 @@ EVAL:
   REM push A and E on the stack
   X=X+2:X%(X-1)=E:X%(X)=A
 
+  REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0))
+
   EVAL_TCO_RECUR:
 
   REM AZ=A:PR=1:GOSUB PR_STR
@@ -215,6 +216,7 @@ EVAL:
       REM push EVAL_AST return label/address
       X=X+1:X%(X)=2
       GOTO EVAL_AST
+      REM return label/address already popped by EVAL_AST
       EVAL_AST_RETURN_2:
 
       X=X+1:X%(X)=R: REM push eval'd list
@@ -276,7 +278,11 @@ EVAL:
       ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN
 
       EVAL_DO_FUNCTION:
-        GOSUB DO_FUNCTION
+        REM regular function
+        IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL
+        REM for recur functions (apply, map, swap!), use GOTO
+        IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION
+        DO_TCO_FUNCTION_RETURN_EVAL:
 
         REM pop and release f/args
         AY=X%(X):X=X-1:GOSUB RELEASE
index cc96817..731e18c 100755 (executable)
@@ -60,6 +60,9 @@ EVAL_AST:
       REM check if we are done evaluating the source sequence
       IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
 
+      REM if we are returning to DO, then skip last element
+      IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
+
       REM if hashmap, skip eval of even entries (keys)
       IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF
       GOTO EVAL_AST_DO_EVAL
@@ -112,7 +115,6 @@ EVAL_AST:
     REM pop EVAL AST return label/address
     RN=X%(X):X=X-1
     ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3
-    RETURN
 
 REM EVAL(A, E)) -> R
 EVAL:
@@ -121,6 +123,8 @@ EVAL:
   REM push A and E on the stack
   X=X+2:X%(X-1)=E:X%(X)=A
 
+  REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0))
+
   EVAL_TCO_RECUR:
 
   REM AZ=A:PR=1:GOSUB PR_STR
@@ -218,19 +222,26 @@ EVAL:
 
     EVAL_DO:
       A=Z%(A,1): REM rest
-
-      REM TODO: TCO
+      X=X+1:X%(X)=A: REM push/save A
 
       REM push EVAL_AST return label/address
       X=X+1:X%(X)=2
       GOTO EVAL_AST
+      REM return label/address already popped by EVAL_AST
       EVAL_AST_RETURN_2:
 
-      X=X+1:X%(X)=R: REM push eval'd list
-      A=R:GOSUB LAST: REM return the last element
-      AY=X%(X):X=X-1: REM pop eval'd list
-      GOSUB RELEASE: REM release the eval'd list
-      GOTO EVAL_RETURN
+      REM cleanup
+      AY=R: REM get eval'd list for release
+
+      A=X%(X):X=X-1: REM pop/restore original A for LAST
+      GOSUB LAST: REM get last element for return
+      A=R: REM new recur AST
+
+      REM cleanup
+      GOSUB RELEASE: REM release eval'd list
+      AY=A:GOSUB RELEASE: REM release LAST value (not sure why)
+
+      GOTO EVAL_TCO_RECUR: REM TCO loop
 
     EVAL_IF:
       GOSUB EVAL_GET_A1: REM set A1
@@ -285,7 +296,11 @@ EVAL:
       ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN
 
       EVAL_DO_FUNCTION:
-        GOSUB DO_FUNCTION
+        REM regular function
+        IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL
+        REM for recur functions (apply, map, swap!), use GOTO
+        IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION
+        DO_TCO_FUNCTION_RETURN_EVAL:
 
         REM pop and release f/args
         AY=X%(X):X=X-1:GOSUB RELEASE
@@ -375,7 +390,7 @@ REP:
     IF R2<>0 THEN AY=R2:GOSUB RELEASE
     IF R1<>0 THEN AY=R1:GOSUB RELEASE
     R$=RT$
-    RETURN
+    GOTO REP_RETURN
 
 REM MAIN program
 MAIN:
@@ -399,7 +414,8 @@ MAIN:
     A$="user> ":GOSUB READLINE: REM call input parser
     IF EOF=1 THEN GOTO QUIT
 
-    A$=R$:GOSUB REP: REM call REP
+    A$=R$:GOTO REP: REM call REP
+    REP_RETURN:
 
     IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP
     PRINT R$
index d06e168..6b8ee7e 100755 (executable)
@@ -60,6 +60,9 @@ EVAL_AST:
       REM check if we are done evaluating the source sequence
       IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
 
+      REM if we are returning to DO, then skip last element
+      IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
+
       REM if hashmap, skip eval of even entries (keys)
       IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF
       GOTO EVAL_AST_DO_EVAL
@@ -112,7 +115,6 @@ EVAL_AST:
     REM pop EVAL AST return label/address
     RN=X%(X):X=X-1
     ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3
-    RETURN
 
 REM EVAL(A, E)) -> R
 EVAL:
@@ -121,6 +123,8 @@ EVAL:
   REM push A and E on the stack
   X=X+2:X%(X-1)=E:X%(X)=A
 
+  REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0))
+
   EVAL_TCO_RECUR:
 
   REM AZ=A:PR=1:GOSUB PR_STR
@@ -218,19 +222,26 @@ EVAL:
 
     EVAL_DO:
       A=Z%(A,1): REM rest
-
-      REM TODO: TCO
+      X=X+1:X%(X)=A: REM push/save A
 
       REM push EVAL_AST return label/address
       X=X+1:X%(X)=2
       GOTO EVAL_AST
+      REM return label/address already popped by EVAL_AST
       EVAL_AST_RETURN_2:
 
-      X=X+1:X%(X)=R: REM push eval'd list
-      A=R:GOSUB LAST: REM return the last element
-      AY=X%(X):X=X-1: REM pop eval'd list
-      GOSUB RELEASE: REM release the eval'd list
-      GOTO EVAL_RETURN
+      REM cleanup
+      AY=R: REM get eval'd list for release
+
+      A=X%(X):X=X-1: REM pop/restore original A for LAST
+      GOSUB LAST: REM get last element for return
+      A=R: REM new recur AST
+
+      REM cleanup
+      GOSUB RELEASE: REM release eval'd list
+      AY=A:GOSUB RELEASE: REM release LAST value (not sure why)
+
+      GOTO EVAL_TCO_RECUR: REM TCO loop
 
     EVAL_IF:
       GOSUB EVAL_GET_A1: REM set A1
@@ -285,7 +296,11 @@ EVAL:
       ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN
 
       EVAL_DO_FUNCTION:
-        GOSUB DO_FUNCTION
+        REM regular function
+        IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL
+        REM for recur functions (apply, map, swap!), use GOTO
+        IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION
+        DO_TCO_FUNCTION_RETURN_EVAL:
 
         REM pop and release f/args
         AY=X%(X):X=X-1:GOSUB RELEASE
@@ -375,7 +390,7 @@ REP:
     IF R2<>0 THEN AY=R2:GOSUB RELEASE
     IF R1<>0 THEN AY=R1:GOSUB RELEASE
     R$=RT$
-    RETURN
+    GOTO REP_RETURN
 
 REM MAIN program
 MAIN:
@@ -420,13 +435,14 @@ MAIN:
     A$="(load-file (first -*ARGS*-))"
     GOSUB RE
     IF ER<>-2 THEN GOSUB PRINT_ERROR
-    END
+    GOTO QUIT
 
   REPL_LOOP:
     A$="user> ":GOSUB READLINE: REM call input parser
     IF EOF=1 THEN GOTO QUIT
 
-    A$=R$:GOSUB REP: REM call REP
+    A$=R$:GOTO REP: REM call REP
+    REP_RETURN:
 
     IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP
     PRINT R$
index 80aab03..f72c2b7 100755 (executable)
@@ -134,6 +134,9 @@ EVAL_AST:
       REM check if we are done evaluating the source sequence
       IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
 
+      REM if we are returning to DO, then skip last element
+      IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
+
       REM if hashmap, skip eval of even entries (keys)
       IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF
       GOTO EVAL_AST_DO_EVAL
@@ -186,7 +189,6 @@ EVAL_AST:
     REM pop EVAL AST return label/address
     RN=X%(X):X=X-1
     ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3
-    RETURN
 
 REM EVAL(A, E)) -> R
 EVAL:
@@ -195,6 +197,8 @@ EVAL:
   REM push A and E on the stack
   X=X+2:X%(X-1)=E:X%(X)=A
 
+  REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0))
+
   EVAL_TCO_RECUR:
 
   REM AZ=A:PR=1:GOSUB PR_STR
@@ -294,19 +298,26 @@ EVAL:
 
     EVAL_DO:
       A=Z%(A,1): REM rest
-
-      REM TODO: TCO
+      X=X+1:X%(X)=A: REM push/save A
 
       REM push EVAL_AST return label/address
       X=X+1:X%(X)=2
       GOTO EVAL_AST
+      REM return label/address already popped by EVAL_AST
       EVAL_AST_RETURN_2:
 
-      X=X+1:X%(X)=R: REM push eval'd list
-      A=R:GOSUB LAST: REM return the last element
-      AY=X%(X):X=X-1: REM pop eval'd list
-      GOSUB RELEASE: REM release the eval'd list
-      GOTO EVAL_RETURN
+      REM cleanup
+      AY=R: REM get eval'd list for release
+
+      A=X%(X):X=X-1: REM pop/restore original A for LAST
+      GOSUB LAST: REM get last element for return
+      A=R: REM new recur AST
+
+      REM cleanup
+      GOSUB RELEASE: REM release eval'd list
+      AY=A:GOSUB RELEASE: REM release LAST value (not sure why)
+
+      GOTO EVAL_TCO_RECUR: REM TCO loop
 
     EVAL_QUOTE:
       R=Z%(A,1)+1:GOSUB DEREF_R
@@ -375,7 +386,11 @@ EVAL:
       ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN
 
       EVAL_DO_FUNCTION:
-        GOSUB DO_FUNCTION
+        REM regular function
+        IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL
+        REM for recur functions (apply, map, swap!), use GOTO
+        IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION
+        DO_TCO_FUNCTION_RETURN_EVAL:
 
         REM pop and release f/args
         AY=X%(X):X=X-1:GOSUB RELEASE
@@ -465,7 +480,7 @@ REP:
     IF R2<>0 THEN AY=R2:GOSUB RELEASE
     IF R1<>0 THEN AY=R1:GOSUB RELEASE
     R$=RT$
-    RETURN
+    GOTO REP_RETURN
 
 REM MAIN program
 MAIN:
@@ -510,13 +525,14 @@ MAIN:
     A$="(load-file (first -*ARGS*-))"
     GOSUB RE
     IF ER<>-2 THEN GOSUB PRINT_ERROR
-    END
+    GOTO QUIT
 
   REPL_LOOP:
     A$="user> ":GOSUB READLINE: REM call input parser
     IF EOF=1 THEN GOTO QUIT
 
-    A$=R$:GOSUB REP: REM call REP
+    A$=R$:GOTO REP: REM call REP
+    REP_RETURN:
 
     IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP
     PRINT R$
index 0b52a43..b33214d 100755 (executable)
@@ -110,7 +110,11 @@ MACROEXPAND:
     IF (Z%(B,0)AND31)<>11 THEN GOTO MACROEXPAND_DONE
   
     REM apply
-    F=B:AR=Z%(A,1):GOSUB APPLY
+    X=X+1:X%(X)=5: REM push APPLY return label/address
+    F=B:AR=Z%(A,1):GOTO APPLY
+    REM APPLY return label/address popped by APPLY
+    APPLY_RETURN_MACROEXPAND:
+
     A=R
 
     AY=X%(X)
@@ -171,6 +175,9 @@ EVAL_AST:
       REM check if we are done evaluating the source sequence
       IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
 
+      REM if we are returning to DO, then skip last element
+      IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
+
       REM if hashmap, skip eval of even entries (keys)
       IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF
       GOTO EVAL_AST_DO_EVAL
@@ -223,7 +230,6 @@ EVAL_AST:
     REM pop EVAL AST return label/address
     RN=X%(X):X=X-1
     ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3
-    RETURN
 
 REM EVAL(A, E)) -> R
 EVAL:
@@ -232,6 +238,8 @@ EVAL:
   REM push A and E on the stack
   X=X+2:X%(X-1)=E:X%(X)=A
 
+  REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0))
+
   EVAL_TCO_RECUR:
 
   REM AZ=A:PR=1:GOSUB PR_STR
@@ -339,19 +347,26 @@ EVAL:
 
     EVAL_DO:
       A=Z%(A,1): REM rest
-
-      REM TODO: TCO
+      X=X+1:X%(X)=A: REM push/save A
 
       REM push EVAL_AST return label/address
       X=X+1:X%(X)=2
       GOTO EVAL_AST
+      REM return label/address already popped by EVAL_AST
       EVAL_AST_RETURN_2:
 
-      X=X+1:X%(X)=R: REM push eval'd list
-      A=R:GOSUB LAST: REM return the last element
-      AY=X%(X):X=X-1: REM pop eval'd list
-      GOSUB RELEASE: REM release the eval'd list
-      GOTO EVAL_RETURN
+      REM cleanup
+      AY=R: REM get eval'd list for release
+
+      A=X%(X):X=X-1: REM pop/restore original A for LAST
+      GOSUB LAST: REM get last element for return
+      A=R: REM new recur AST
+
+      REM cleanup
+      GOSUB RELEASE: REM release eval'd list
+      AY=A:GOSUB RELEASE: REM release LAST value (not sure why)
+
+      GOTO EVAL_TCO_RECUR: REM TCO loop
 
     EVAL_QUOTE:
       R=Z%(A,1)+1:GOSUB DEREF_R
@@ -444,7 +459,11 @@ EVAL:
       ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN
 
       EVAL_DO_FUNCTION:
-        GOSUB DO_FUNCTION
+        REM regular function
+        IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL
+        REM for recur functions (apply, map, swap!), use GOTO
+        IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION
+        DO_TCO_FUNCTION_RETURN_EVAL:
 
         REM pop and release f/args
         AY=X%(X):X=X-1:GOSUB RELEASE
@@ -534,7 +553,7 @@ REP:
     IF R2<>0 THEN AY=R2:GOSUB RELEASE
     IF R1<>0 THEN AY=R1:GOSUB RELEASE
     R$=RT$
-    RETURN
+    GOTO REP_RETURN
 
 REM MAIN program
 MAIN:
@@ -588,13 +607,14 @@ MAIN:
     A$="(load-file (first -*ARGS*-))"
     GOSUB RE
     IF ER<>-2 THEN GOSUB PRINT_ERROR
-    END
+    GOTO QUIT
 
   REPL_LOOP:
     A$="user> ":GOSUB READLINE: REM call input parser
     IF EOF=1 THEN GOTO QUIT
 
-    A$=R$:GOSUB REP: REM call REP
+    A$=R$:GOTO REP: REM call REP
+    REP_RETURN:
 
     IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP
     PRINT R$
index 024ebf0..4f7d443 100755 (executable)
@@ -110,7 +110,11 @@ MACROEXPAND:
     IF (Z%(B,0)AND31)<>11 THEN GOTO MACROEXPAND_DONE
   
     REM apply
-    F=B:AR=Z%(A,1):GOSUB APPLY
+    X=X+1:X%(X)=5: REM push APPLY return label/address
+    F=B:AR=Z%(A,1):GOTO APPLY
+    REM APPLY return label/address popped by APPLY
+    APPLY_RETURN_MACROEXPAND:
+
     A=R
 
     AY=X%(X)
@@ -171,6 +175,9 @@ EVAL_AST:
       REM check if we are done evaluating the source sequence
       IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
 
+      REM if we are returning to DO, then skip last element
+      IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
+
       REM if hashmap, skip eval of even entries (keys)
       IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF
       GOTO EVAL_AST_DO_EVAL
@@ -223,7 +230,6 @@ EVAL_AST:
     REM pop EVAL AST return label/address
     RN=X%(X):X=X-1
     ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3
-    RETURN
 
 REM EVAL(A, E)) -> R
 EVAL:
@@ -232,6 +238,8 @@ EVAL:
   REM push A and E on the stack
   X=X+2:X%(X-1)=E:X%(X)=A
 
+  REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0))
+
   EVAL_TCO_RECUR:
 
   REM AZ=A:PR=1:GOSUB PR_STR
@@ -340,19 +348,26 @@ EVAL:
 
     EVAL_DO:
       A=Z%(A,1): REM rest
-
-      REM TODO: TCO
+      X=X+1:X%(X)=A: REM push/save A
 
       REM push EVAL_AST return label/address
       X=X+1:X%(X)=2
       GOTO EVAL_AST
+      REM return label/address already popped by EVAL_AST
       EVAL_AST_RETURN_2:
 
-      X=X+1:X%(X)=R: REM push eval'd list
-      A=R:GOSUB LAST: REM return the last element
-      AY=X%(X):X=X-1: REM pop eval'd list
-      GOSUB RELEASE: REM release the eval'd list
-      GOTO EVAL_RETURN
+      REM cleanup
+      AY=R: REM get eval'd list for release
+
+      A=X%(X):X=X-1: REM pop/restore original A for LAST
+      GOSUB LAST: REM get last element for return
+      A=R: REM new recur AST
+
+      REM cleanup
+      GOSUB RELEASE: REM release eval'd list
+      AY=A:GOSUB RELEASE: REM release LAST value (not sure why)
+
+      GOTO EVAL_TCO_RECUR: REM TCO loop
 
     EVAL_QUOTE:
       R=Z%(A,1)+1:GOSUB DEREF_R
@@ -476,7 +491,11 @@ EVAL:
       ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN
 
       EVAL_DO_FUNCTION:
-        GOSUB DO_FUNCTION
+        REM regular function
+        IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL
+        REM for recur functions (apply, map, swap!), use GOTO
+        IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION
+        DO_TCO_FUNCTION_RETURN_EVAL:
 
         REM pop and release f/args
         AY=X%(X):X=X-1:GOSUB RELEASE
@@ -566,7 +585,7 @@ REP:
     IF R2<>0 THEN AY=R2:GOSUB RELEASE
     IF R1<>0 THEN AY=R1:GOSUB RELEASE
     R$=RT$
-    RETURN
+    GOTO REP_RETURN
 
 REM MAIN program
 MAIN:
@@ -620,13 +639,14 @@ MAIN:
     A$="(load-file (first -*ARGS*-))"
     GOSUB RE
     IF ER<>-2 THEN GOSUB PRINT_ERROR
-    END
+    GOTO QUIT
 
   REPL_LOOP:
     A$="user> ":GOSUB READLINE: REM call input parser
     IF EOF=1 THEN GOTO QUIT
 
-    A$=R$:GOSUB REP: REM call REP
+    A$=R$:GOTO REP: REM call REP
+    REP_RETURN:
 
     IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP
     PRINT R$
index b5b5b57..18ffbab 100755 (executable)
@@ -16,18 +16,14 @@ MAL_READ:
   GOSUB READ_STR
   RETURN
 
-REM PAIR_Q(B) -> R
-PAIR_Q:
-  R=0
-  IF (Z%(B,0)AND31)<>6 AND (Z%(B,0)AND31)<>7 THEN RETURN
-  IF (Z%(B,1)=0) THEN RETURN
-  R=1
-  RETURN
-
 REM QUASIQUOTE(A) -> R
 QUASIQUOTE:
-  B=A:GOSUB PAIR_Q
-  IF R=1 THEN GOTO QQ_UNQUOTE
+  REM pair?
+  IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_QUOTE
+  IF (Z%(A,1)=0) THEN GOTO QQ_QUOTE
+  GOTO QQ_UNQUOTE
+
+  QQ_QUOTE:
     REM ['quote, ast]
     AS$="quote":T=5:GOSUB STRING
     B2=R:B1=A:GOSUB LIST2
@@ -56,8 +52,10 @@ QUASIQUOTE:
     REM set A to ast[0] for last two cases
     A=A+1:GOSUB DEREF_A
 
-    B=A:GOSUB PAIR_Q
-    IF R=0 THEN GOTO QQ_DEFAULT
+    REM pair?
+    IF (Z%(A,0)AND31)<6 OR (Z%(A,0)AND31)>7 THEN GOTO QQ_DEFAULT
+    IF (Z%(A,1)=0) THEN GOTO QQ_DEFAULT
+
     B=A+1:GOSUB DEREF_B
     IF (Z%(B,0)AND31)<>5 THEN GOTO QQ_DEFAULT
     IF S$(Z%(B,1))<>"splice-unquote" THEN QQ_DEFAULT
@@ -110,7 +108,11 @@ MACROEXPAND:
     IF (Z%(B,0)AND31)<>11 THEN GOTO MACROEXPAND_DONE
   
     REM apply
-    F=B:AR=Z%(A,1):GOSUB APPLY
+    X=X+1:X%(X)=5: REM push APPLY return label/address
+    F=B:AR=Z%(A,1):GOTO APPLY
+    REM APPLY return label/address popped by APPLY
+    APPLY_RETURN_MACROEXPAND:
+
     A=R
 
     AY=X%(X)
@@ -171,6 +173,9 @@ EVAL_AST:
       REM check if we are done evaluating the source sequence
       IF Z%(A,1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
 
+      REM if we are returning to DO, then skip last element
+      IF X%(X-6)=2 AND Z%(Z%(A,1),1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
+
       REM if hashmap, skip eval of even entries (keys)
       IF (X%(X-3)=8) AND ((X%(X-2) AND 1)=0) THEN GOTO EVAL_AST_DO_REF
       GOTO EVAL_AST_DO_EVAL
@@ -223,7 +228,6 @@ EVAL_AST:
     REM pop EVAL AST return label/address
     RN=X%(X):X=X-1
     ON RN GOTO EVAL_AST_RETURN_1,EVAL_AST_RETURN_2,EVAL_AST_RETURN_3
-    RETURN
 
 REM EVAL(A, E)) -> R
 EVAL:
@@ -232,6 +236,8 @@ EVAL:
   REM push A and E on the stack
   X=X+2:X%(X-1)=E:X%(X)=A
 
+  REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0))
+
   EVAL_TCO_RECUR:
 
   REM AZ=A:PR=1:GOSUB PR_STR
@@ -340,19 +346,26 @@ EVAL:
 
     EVAL_DO:
       A=Z%(A,1): REM rest
-
-      REM TODO: TCO
+      X=X+1:X%(X)=A: REM push/save A
 
       REM push EVAL_AST return label/address
       X=X+1:X%(X)=2
       GOTO EVAL_AST
+      REM return label/address already popped by EVAL_AST
       EVAL_AST_RETURN_2:
 
-      X=X+1:X%(X)=R: REM push eval'd list
-      A=R:GOSUB LAST: REM return the last element
-      AY=X%(X):X=X-1: REM pop eval'd list
-      GOSUB RELEASE: REM release the eval'd list
-      GOTO EVAL_RETURN
+      REM cleanup
+      AY=R: REM get eval'd list for release
+
+      A=X%(X):X=X-1: REM pop/restore original A for LAST
+      GOSUB LAST: REM get last element for return
+      A=R: REM new recur AST
+
+      REM cleanup
+      GOSUB RELEASE: REM release eval'd list
+      AY=A:GOSUB RELEASE: REM release LAST value (not sure why)
+
+      GOTO EVAL_TCO_RECUR: REM TCO loop
 
     EVAL_QUOTE:
       R=Z%(A,1)+1:GOSUB DEREF_R
@@ -476,7 +489,11 @@ EVAL:
       ER=-1:ER$="apply of non-function":GOTO EVAL_RETURN
 
       EVAL_DO_FUNCTION:
-        GOSUB DO_FUNCTION
+        REM regular function
+        IF Z%(F,1)<60 THEN GOSUB DO_FUNCTION:GOTO DO_TCO_FUNCTION_RETURN_EVAL
+        REM for recur functions (apply, map, swap!), use GOTO
+        IF Z%(F,1)>60 THEN X=X+1:X%(X)=2:GOTO DO_TCO_FUNCTION
+        DO_TCO_FUNCTION_RETURN_EVAL:
 
         REM pop and release f/args
         AY=X%(X):X=X-1:GOSUB RELEASE
@@ -566,7 +583,7 @@ REP:
     IF R2<>0 THEN AY=R2:GOSUB RELEASE
     IF R1<>0 THEN AY=R1:GOSUB RELEASE
     R$=RT$
-    RETURN
+    GOTO REP_RETURN
 
 REM MAIN program
 MAIN:
@@ -623,7 +640,7 @@ MAIN:
     A$="(load-file (first -*ARGS*-))"
     GOSUB RE
     IF ER<>-2 THEN GOSUB PRINT_ERROR
-    END
+    GOTO QUIT
 
   REPL:
     REM print the REPL startup header
@@ -635,7 +652,8 @@ MAIN:
     A$="user> ":GOSUB READLINE: REM call input parser
     IF EOF=1 THEN GOTO QUIT
 
-    A$=R$:GOSUB REP: REM call REP
+    A$=R$:GOTO REP: REM call REP
+    REP_RETURN:
 
     IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP
     PRINT R$
index bc3483c..4279576 100644 (file)
@@ -64,8 +64,8 @@ INIT_MEMORY:
   REM pending release stack
   Y=-1:DIM Y%(Z4,1): REM stack of Z% indexes
 
-  REM PRINT "Lisp data memory: "+STR$(T-FRE(0))
-  REM PRINT "Interpreter working memory: "+STR$(FRE(0))
+  BT=TI
+
   RETURN
 
 
@@ -262,37 +262,52 @@ 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
 
-  R=0
+  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 NOT (U1=U2 OR ((U1=6 OR U1=7) AND (U2=6 OR U2=7))) THEN RETURN
-  IF U1=6 THEN GOTO EQUAL_Q_SEQ
-  IF U1=7 THEN GOTO EQUAL_Q_SEQ
-  IF U1=8 THEN GOTO EQUAL_Q_HM
+  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 Z%(A,1)=Z%(B,1) THEN R=1
-  RETURN
+  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 R=1:RETURN
-    IF (Z%(A,1)=0) OR (Z%(B,1)=0) THEN R=0:RETURN
+    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 push A and B
-    X=X+2:X%(X-1)=A:X%(X)=B
     REM compare the elements
-    A=Z%(A+1,1):B=Z%(B+1,1):GOSUB EQUAL_Q
-    REM pop A and B
-    A=X%(X-1):B=X%(X):X=X-2
-    IF R=0 THEN RETURN
+    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=Z%(A,1):B=Z%(B,1):GOTO EQUAL_Q_SEQ
+    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
-    RETURN
+    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
 
@@ -302,9 +317,12 @@ STRING_:
   IF S=0 THEN GOTO STRING_NOT_FOUND
 
   REM search for matching string in S$
-  FOR I=0 TO S-1
+  I=0
+  STRING_LOOP:
+    IF I>S-1 THEN GOTO STRING_NOT_FOUND
     IF AS$=S$(I) THEN R=I:RETURN
-    NEXT I
+    I=I+1
+    GOTO STRING_LOOP
 
   STRING_NOT_FOUND:
     S$(S)=AS$
@@ -502,34 +520,3 @@ REM MAL_FUNCTION(A, P, E) -> R
 MAL_FUNCTION:
   T=10:L=A:M=P:N=E:GOSUB ALLOC
   RETURN
-
-REM APPLY(F, AR) -> R
-REM   restores E
-APPLY:
-  REM if metadata, get the actual object
-  IF (Z%(F,0)AND31)>=16 THEN F=Z%(F,1)
-
-  IF (Z%(F,0)AND31)=9 THEN GOTO DO_APPLY_FUNCTION
-  IF (Z%(F,0)AND31)=10 THEN GOTO DO_APPLY_MAL_FUNCTION
-  IF (Z%(F,0)AND31)=11 THEN GOTO DO_APPLY_MAL_FUNCTION
-
-  DO_APPLY_FUNCTION:
-    GOSUB DO_FUNCTION
-
-    RETURN
-
-  DO_APPLY_MAL_FUNCTION:
-    X=X+1:X%(X)=E: REM save the current environment
-
-    REM create new environ using env and params stored in the
-    REM function and bind the params to the apply arguments
-    O=Z%(F+1,1):BI=Z%(F+1,0):EX=AR:GOSUB ENV_NEW_BINDS
-
-    A=Z%(F,1):E=R:GOSUB EVAL
-
-    AY=E:GOSUB RELEASE: REM release the new environment
-
-    E=X%(X):X=X-1: REM pop/restore the saved environment
-
-    RETURN
-
index c64e68b..aebe3e4 100644 (file)
@@ -56,11 +56,15 @@ P2  : PR_MEMORY, CHECK_FREE_LIST end
 SZ  : size argument to ALLOC
 
 Reused/temporaries:
-
 A0  : EVAL ast elements
 A1  : EVAL ast elements
 A2  : EVAL ast elements
 A3  : EVAL ast elements
+
+ED  : EQUAL_Q recursion depth counter
+RD  : PR_OBJECT recursion depth
+SD  : READ_STR sequence read recursion depth
+
 CH$ : READ_TOKEN, SKIP_SPACES, SKIP_TO_EOL current character
 I   : STRING, REPLACE, SLICE, PR_MEMORY, PR_OBJECT
 J   : REPLACE
index 173b6b9..499ff94 100644 (file)
@@ -7,8 +7,8 @@
 (def! eval-ast (fn* [ast env] (do
   ;;(do (prn "eval-ast" ast "/" (keys env)) )
   (cond
-    (symbol? ast) (or (get env (str ast))
-                      (throw (str ast " not found")))
+    (symbol? ast) (let* [res (get env (str ast))]
+                    (if res res (throw (str ast " not found"))))
 
     (list? ast)   (map (fn* [exp] (EVAL exp env)) ast)