Basic: fix errors, reader, if form. Self-host 0-3
[jackhill/mal.git] / basic / step7_quote.in.bas
index 7bf085b..cc5abfd 100755 (executable)
@@ -14,24 +14,20 @@ 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
+SUB QUASIQUOTE
+  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
     AY=B2:GOSUB RELEASE
 
-    RETURN
+    GOTO QQ_DONE
 
   QQ_UNQUOTE:
     R=A+1:GOSUB DEREF_R
@@ -41,21 +37,24 @@ QUASIQUOTE:
       R=Z%(A,1)+1:GOSUB DEREF_R
       Z%(R,0)=Z%(R,0)+32
 
-      RETURN
+      GOTO QQ_DONE
 
   QQ_SPLICE_UNQUOTE:
     REM push A on the stack
     X=X+1:X%(X)=A
     REM rest of cases call quasiquote on ast[1..]
-    A=Z%(A,1):GOSUB QUASIQUOTE:T6=R
+    A=Z%(A,1):CALL QUASIQUOTE
+    T6=R
     REM pop A off the stack
     A=X%(X):X=X-1
 
     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
@@ -67,7 +66,7 @@ QUASIQUOTE:
       REM release inner quasiquoted since outer list takes ownership
       AY=B1:GOSUB RELEASE
       AY=B3:GOSUB RELEASE
-      RETURN
+      GOTO QQ_DONE
 
   QQ_DEFAULT:
     REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])]
@@ -75,7 +74,8 @@ QUASIQUOTE:
     REM push T6 on the stack
     X=X+1:X%(X)=T6
     REM A set above to ast[0]
-    GOSUB QUASIQUOTE:B2=R
+    CALL QUASIQUOTE
+    B2=R
     REM pop T6 off the stack
     T6=X%(X):X=X-1
 
@@ -85,13 +85,12 @@ QUASIQUOTE:
     AY=B1:GOSUB RELEASE
     AY=B2:GOSUB RELEASE
     AY=B3:GOSUB RELEASE
-    RETURN
+  QQ_DONE:
+END SUB
 
 
 REM EVAL_AST(A, E) -> R
-REM called using GOTO to avoid basic return address stack usage
-REM top of stack should have return label index
-EVAL_AST:
+SUB EVAL_AST
   REM push A and E on the stack
   X=X+2:X%(X-1)=E:X%(X)=A
 
@@ -109,7 +108,8 @@ EVAL_AST:
   GOTO EVAL_AST_RETURN
 
   EVAL_AST_SYMBOL:
-    K=A:GOSUB ENV_GET
+    K=A:GOTO ENV_GET
+    ENV_GET_RETURN:
     GOTO EVAL_AST_RETURN
 
   EVAL_AST_SEQ:
@@ -134,8 +134,11 @@ 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
+      IF (X%(X-3)=8) AND ((X%(X-2)AND1)=0) THEN GOTO EVAL_AST_DO_REF
       GOTO EVAL_AST_DO_EVAL
 
       EVAL_AST_DO_REF:
@@ -145,7 +148,7 @@ EVAL_AST:
 
       EVAL_AST_DO_EVAL:
         REM call EVAL for each entry
-        A=A+1:GOSUB EVAL
+        A=A+1:CALL EVAL
         A=A-1
         GOSUB DEREF_R: REM deref to target of evaluated entry
 
@@ -182,21 +185,21 @@ EVAL_AST:
   EVAL_AST_RETURN:
     REM pop A and E off the stack
     E=X%(X-1):A=X%(X):X=X-2
+END SUB
 
-    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:
+REM EVAL(A, E) -> R
+SUB EVAL
   LV=LV+1: REM track basic return stack level
 
   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:
 
+  IF ER<>-2 THEN GOTO EVAL_RETURN
+
   REM AZ=A:PR=1:GOSUB PR_STR
   REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]"
 
@@ -205,11 +208,7 @@ EVAL:
   GOSUB LIST_Q
   IF R THEN GOTO APPLY_LIST
   REM ELSE
-    REM push EVAL_AST return label/address
-    X=X+1:X%(X)=1
-    GOTO EVAL_AST
-    EVAL_AST_RETURN_1:
-
+    CALL EVAL_AST
     GOTO EVAL_RETURN
 
   APPLY_LIST:
@@ -248,7 +247,7 @@ EVAL:
       GOSUB EVAL_GET_A2: REM set A1 and A2
 
       X=X+1:X%(X)=A1: REM push A1
-      A=A2:GOSUB EVAL: REM eval a2
+      A=A2:CALL EVAL: REM eval a2
       A1=X%(X):X=X-1: REM pop A1
 
       IF ER<>-2 THEN GOTO EVAL_RETURN
@@ -272,9 +271,11 @@ EVAL:
 
         X=X+1:X%(X)=A1: REM push A1
         REM eval current A1 odd element
-        A=Z%(A1,1)+1:GOSUB EVAL
+        A=Z%(A1,1)+1:CALL EVAL
         A1=X%(X):X=X-1: REM pop A1
 
+        IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE
+
         REM set environment: even A1 key to odd A1 eval'd above
         K=A1+1:V=R:GOSUB ENV_SET
         AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership
@@ -294,19 +295,22 @@ EVAL:
 
     EVAL_DO:
       A=Z%(A,1): REM rest
+      X=X+1:X%(X)=A: REM push/save A
 
-      REM TODO: TCO
+      CALL EVAL_AST
 
-      REM push EVAL_AST return label/address
-      X=X+1:X%(X)=2
-      GOTO EVAL_AST
-      EVAL_AST_RETURN_2:
+      REM cleanup
+      AY=R: REM get eval'd list for release
 
-      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
+      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
@@ -315,7 +319,7 @@ EVAL:
 
     EVAL_QUASIQUOTE:
       R=Z%(A,1)+1:GOSUB DEREF_R
-      A=R:GOSUB QUASIQUOTE
+      A=R:CALL QUASIQUOTE
       REM add quasiquote result to pending release queue to free when
       REM next lower EVAL level returns (LV)
       Y=Y+1:Y%(Y,0)=R:Y%(Y,1)=LV
@@ -326,7 +330,7 @@ EVAL:
       GOSUB EVAL_GET_A1: REM set A1
       REM push A
       X=X+1:X%(X)=A
-      A=A1:GOSUB EVAL
+      A=A1:CALL EVAL
       REM pop A
       A=X%(X):X=X-1
       IF (R=0) OR (R=1) THEN GOTO EVAL_IF_FALSE
@@ -338,7 +342,8 @@ EVAL:
       EVAL_IF_FALSE:
         AY=R:GOSUB RELEASE
         REM if no false case (A3), return nil
-        IF Z%(Z%(Z%(A,1),1),1)=0 THEN R=0:GOTO EVAL_RETURN
+        B=A:GOSUB COUNT
+        IF R<4 THEN R=0:GOTO EVAL_RETURN
         GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL
         A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop
 
@@ -348,10 +353,7 @@ EVAL:
       GOTO EVAL_RETURN
 
     EVAL_INVOKE:
-      REM push EVAL_AST return label/address
-      X=X+1:X%(X)=3
-      GOTO EVAL_AST
-      EVAL_AST_RETURN_3:
+      CALL EVAL_AST
 
       REM if error, return f/args for release by caller
       IF ER<>-2 THEN GOTO EVAL_RETURN
@@ -375,7 +377,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 EVAL_DO_FUNCTION_SKIP
+        REM for recur functions (apply, map, swap!), use GOTO
+        IF Z%(F,1)>60 THEN CALL DO_TCO_FUNCTION
+        EVAL_DO_FUNCTION_SKIP:
 
         REM pop and release f/args
         AY=X%(X):X=X-1:GOSUB RELEASE
@@ -422,7 +428,7 @@ EVAL:
     REM pop A and E off the stack
     E=X%(X-1):A=X%(X):X=X-2
 
-    RETURN
+END SUB
 
 REM PRINT(A) -> R$
 MAL_PRINT:
@@ -436,24 +442,24 @@ RE:
   R1=0
   GOSUB MAL_READ
   R1=R
-  IF ER<>-2 THEN GOTO REP_DONE
+  IF ER<>-2 THEN GOTO RE_DONE
 
-  A=R:E=D:GOSUB EVAL
+  A=R:E=D:CALL EVAL
 
-  REP_DONE:
+  RE_DONE:
     REM Release memory from MAL_READ
     IF R1<>0 THEN AY=R1:GOSUB RELEASE
     RETURN: REM caller must release result of EVAL
 
 REM REP(A$) -> R$
 REM Assume D has repl_env
-REP:
+SUB REP
   R1=0:R2=0
   GOSUB MAL_READ
   R1=R
   IF ER<>-2 THEN GOTO REP_DONE
 
-  A=R:E=D:GOSUB EVAL
+  A=R:E=D:CALL EVAL
   R2=R
   IF ER<>-2 THEN GOTO REP_DONE
 
@@ -465,7 +471,7 @@ REP:
     IF R2<>0 THEN AY=R2:GOSUB RELEASE
     IF R1<>0 THEN AY=R1:GOSUB RELEASE
     R$=RT$
-    RETURN
+END SUB
 
 REM MAIN program
 MAIN:
@@ -485,8 +491,7 @@ MAIN:
   A$="(def! not (fn* (a) (if a false true)))"
   GOSUB RE:AY=R:GOSUB RELEASE
 
-  A$="(def! load-file (fn* (f) (eval (read-string (str "
-  A$=A$+CHR$(34)+"(do "+CHR$(34)+" (slurp f) "+CHR$(34)+")"+CHR$(34)+")))))"
+  A$="(def! load-file (fn* (f) (eval (read-file f))))"
   GOSUB RE:AY=R:GOSUB RELEASE
 
   REM load the args file
@@ -511,13 +516,13 @@ 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$:CALL REP: REM call REP
 
     IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP
     PRINT R$