1
1
mirror of https://github.com/kanaka/mal.git synced 2024-11-11 00:52:44 +03:00
mal/basic/step4_if_fn_do.in.bas
Joel Martin 115e430d02 Basic: QBasic fixes/enabling. Recursive includes.
Enable QBasic console mode usage and fix bugs in newline handling
differences with C64 basic (cbm). To enable console mode for QBasic
programs, have basicpp.py prefix the output with the QB64 specific
console activation variables/functions.

One change to basicpp.py to make this change more straightfowards is
recursive includes so that includes can appear in more than just the
top level step files. This allows us to conditionally include the
right readline implementation. For QBasic in the special console mode
(instead of the default full-screen UI mode) we need to use the LINE
INPUT command in order to read input.
2017-09-14 23:50:15 -05:00

372 lines
8.7 KiB
QBasic
Executable File

GOTO MAIN
REM $INCLUDE: 'mem.in.bas'
REM $INCLUDE: 'types.in.bas'
REM $INCLUDE: 'readline.in.bas'
REM $INCLUDE: 'reader.in.bas'
REM $INCLUDE: 'printer.in.bas'
REM $INCLUDE: 'env.in.bas'
REM $INCLUDE: 'core.in.bas'
REM $INCLUDE: 'debug.in.bas'
REM READ(A$) -> R
MAL_READ:
GOSUB READ_STR
RETURN
REM EVAL_AST(A, E) -> R
SUB EVAL_AST
REM push A and E on the stack
Q=E:GOSUB PUSH_Q
GOSUB PUSH_A
IF ER<>-2 THEN GOTO EVAL_AST_RETURN
GOSUB TYPE_A
IF T=5 THEN GOTO EVAL_AST_SYMBOL
IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ
REM scalar: deref to actual value and inc ref cnt
R=A
GOSUB INC_REF_R
GOTO EVAL_AST_RETURN
EVAL_AST_SYMBOL:
K=A:GOTO ENV_GET
ENV_GET_RETURN:
GOTO EVAL_AST_RETURN
EVAL_AST_SEQ:
REM setup the stack for the loop
GOSUB MAP_LOOP_START
EVAL_AST_SEQ_LOOP:
REM check if we are done evaluating the source sequence
IF Z%(A+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
REM call EVAL for each entry
GOSUB PUSH_A
IF T<>8 THEN A=Z%(A+2)
IF T=8 THEN A=Z%(A+3)
Q=T:GOSUB PUSH_Q: REM push/save type
CALL EVAL
GOSUB POP_Q:T=Q: REM pop/restore type
GOSUB POP_A
M=R
REM if error, release the unattached element
REM TODO: is R=0 correct?
IF ER<>-2 THEN AY=R:GOSUB RELEASE:R=0:GOTO EVAL_AST_SEQ_LOOP_DONE
REM for hash-maps, copy the key (inc ref since we are going to
REM release it below)
IF T=8 THEN N=M:M=Z%(A+2):Z%(M)=Z%(M)+32
REM update the return sequence structure
REM release N (and M if T=8) since seq takes full ownership
C=1:GOSUB MAP_LOOP_UPDATE
REM process the next sequence entry from source list
A=Z%(A+1)
GOTO EVAL_AST_SEQ_LOOP
EVAL_AST_SEQ_LOOP_DONE:
REM cleanup stack and get return value
GOSUB MAP_LOOP_DONE
GOTO EVAL_AST_RETURN
EVAL_AST_RETURN:
REM pop A and E off the stack
GOSUB POP_A
GOSUB POP_Q:E=Q
END SUB
REM EVAL(A, E) -> R
SUB EVAL
LV=LV+1: REM track basic return stack level
REM push A and E on the stack
Q=E:GOSUB PUSH_Q
GOSUB PUSH_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:B=1:GOSUB PR_STR
REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]"
GOSUB LIST_Q
IF R THEN GOTO APPLY_LIST
REM ELSE
CALL EVAL_AST
GOTO EVAL_RETURN
APPLY_LIST:
GOSUB EMPTY_Q
IF R THEN R=A:GOSUB INC_REF_R:GOTO EVAL_RETURN
A0=Z%(A+2)
REM get symbol in A$
IF (Z%(A0)AND 31)<>5 THEN A$=""
IF (Z%(A0)AND 31)=5 THEN A$=S$(Z%(A0+1))
IF A$="def!" THEN GOTO EVAL_DEF
IF A$="let*" THEN GOTO EVAL_LET
IF A$="do" THEN GOTO EVAL_DO
IF A$="if" THEN GOTO EVAL_IF
IF A$="fn*" THEN GOTO EVAL_FN
GOTO EVAL_INVOKE
EVAL_GET_A3:
A3=Z%(Z%(Z%(Z%(A+1)+1)+1)+2)
EVAL_GET_A2:
A2=Z%(Z%(Z%(A+1)+1)+2)
EVAL_GET_A1:
A1=Z%(Z%(A+1)+2)
RETURN
EVAL_DEF:
REM PRINT "def!"
GOSUB EVAL_GET_A2: REM set A1 and A2
Q=A1:GOSUB PUSH_Q
A=A2:CALL EVAL: REM eval a2
GOSUB POP_Q:A1=Q
IF ER<>-2 THEN GOTO EVAL_RETURN
REM set a1 in env to a2
K=A1:C=R:GOSUB ENV_SET
GOTO EVAL_RETURN
EVAL_LET:
REM PRINT "let*"
GOSUB EVAL_GET_A2: REM set A1 and A2
Q=A2:GOSUB PUSH_Q: REM push/save A2
REM create new environment with outer as current environment
C=E:GOSUB ENV_NEW
E=R
EVAL_LET_LOOP:
IF Z%(A1+1)=0 THEN GOTO EVAL_LET_LOOP_DONE
Q=A1:GOSUB PUSH_Q: REM push A1
REM eval current A1 odd element
A=Z%(Z%(A1+1)+2):CALL EVAL
GOSUB POP_Q:A1=Q: REM pop A1
IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE
REM set key/value in the environment
K=Z%(A1+2):C=R:GOSUB ENV_SET
AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership
REM skip to the next pair of A1 elements
A1=Z%(Z%(A1+1)+1)
GOTO EVAL_LET_LOOP
EVAL_LET_LOOP_DONE:
GOSUB POP_Q:A2=Q: REM pop A2
A=A2:CALL EVAL: REM eval A2 using let_env
GOTO EVAL_RETURN
EVAL_DO:
A=Z%(A+1): REM rest
CALL EVAL_AST
GOSUB PUSH_R: REM push eval'd list
A=R:GOSUB LAST: REM return the last element
GOSUB POP_Q:AY=Q: REM pop eval'd list
GOSUB RELEASE: REM release the eval'd list
GOTO EVAL_RETURN
EVAL_IF:
GOSUB EVAL_GET_A1: REM set A1
GOSUB PUSH_A: REM push/save A
A=A1:CALL EVAL
GOSUB POP_A: REM pop/restore A
IF (R=0) OR (R=2) THEN GOTO EVAL_IF_FALSE
EVAL_IF_TRUE:
AY=R:GOSUB RELEASE
GOSUB EVAL_GET_A2: REM set A1 and A2 after EVAL
A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop
EVAL_IF_FALSE:
AY=R:GOSUB RELEASE
REM if no false case (A3), return nil
GOSUB COUNT
IF R<4 THEN R=0:GOSUB INC_REF_R:GOTO EVAL_RETURN
GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL
A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop
EVAL_FN:
GOSUB EVAL_GET_A2: REM set A1 and A2
T=10:L=A2:M=A1:N=E:GOSUB ALLOC: REM mal function
GOTO EVAL_RETURN
EVAL_INVOKE:
CALL EVAL_AST
REM if error, return f/args for release by caller
IF ER<>-2 THEN GOTO EVAL_RETURN
REM push f/args for release after call
GOSUB PUSH_R
AR=Z%(R+1): REM rest
F=Z%(R+2)
REM if metadata, get the actual object
GOSUB TYPE_F
IF T=14 THEN F=Z%(F+1):GOSUB TYPE_F
ON T-8 GOTO EVAL_DO_FUNCTION,EVAL_DO_MAL_FUNCTION,EVAL_DO_MAL_FUNCTION
REM if error, pop and return f/args for release by caller
GOSUB POP_R
ER=-1:E$="apply of non-function":GOTO EVAL_RETURN
EVAL_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
GOSUB POP_Q:AY=Q
GOSUB RELEASE
GOTO EVAL_RETURN
EVAL_DO_MAL_FUNCTION:
Q=E:GOSUB PUSH_Q: REM save the current environment for release
REM create new environ using env and params stored in function
C=Z%(F+3):A=Z%(F+2):B=AR:GOSUB ENV_NEW_BINDS
REM release previous env if it is not the top one on the
REM stack (X%(X-2)) because our new env refers to it and
REM we no longer need to track it (since we are TCO recurring)
GOSUB POP_Q:AY=Q
GOSUB PEEK_Q_2
IF AY<>Q THEN GOSUB RELEASE
REM claim the AST before releasing the list containing it
A=Z%(F+1):Z%(A)=Z%(A)+32
REM add AST to pending release queue to free as soon as EVAL
REM actually returns (LV+1)
LV=LV+1:GOSUB PEND_A_LV:LV=LV-1
REM pop and release f/args
GOSUB POP_Q:AY=Q
GOSUB RELEASE
REM A set above
E=R:GOTO EVAL_TCO_RECUR: REM TCO loop
EVAL_RETURN:
REM AZ=R: B=1: GOSUB PR_STR
REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER)
REM release environment if not the top one on the stack
GOSUB PEEK_Q_1
IF E<>Q THEN AY=E:GOSUB RELEASE
LV=LV-1: REM track basic return stack level
REM release everything we couldn't release earlier
GOSUB RELEASE_PEND
REM trigger GC
#cbm T=FRE(0)
#qbasic T=0
REM pop A and E off the stack
GOSUB POP_A
GOSUB POP_Q:E=Q
END SUB
REM PRINT(A) -> R$
MAL_PRINT:
AZ=A:B=1:GOSUB PR_STR
RETURN
REM RE(A$) -> R
REM Assume D has repl_env
REM caller must release result
RE:
R1=-1
GOSUB MAL_READ
R1=R
IF ER<>-2 THEN GOTO RE_DONE
A=R:E=D:CALL EVAL
RE_DONE:
REM Release memory from MAL_READ
AY=R1:GOSUB RELEASE
RETURN: REM caller must release result of EVAL
REM REP(A$) -> R$
REM Assume D has repl_env
SUB REP
R2=-1
GOSUB RE
R2=R
IF ER<>-2 THEN GOTO REP_DONE
A=R:GOSUB MAL_PRINT
REP_DONE:
REM Release memory from MAL_READ and EVAL
AY=R2:GOSUB RELEASE
END SUB
REM MAIN program
MAIN:
GOSUB INIT_MEMORY
LV=0
REM create repl_env
C=0:GOSUB ENV_NEW:D=R
REM core.EXT: defined in Basic
E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env
ZT=ZI: REM top of memory after base repl_env
REM core.mal: defined using the language itself
A$="(def! not (fn* (a) (if a false true)))"
GOSUB RE:AY=R:GOSUB RELEASE
REPL_LOOP:
A$="user> ":GOSUB READLINE: REM call input parser
IF EZ=1 THEN GOTO QUIT
IF R$="" THEN GOTO REPL_LOOP
A$=R$:CALL REP: REM call REP
IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP
PRINT R$
GOTO REPL_LOOP
QUIT:
REM GOSUB PR_MEMORY_SUMMARY_SMALL
#cbm END
#qbasic SYSTEM
PRINT_ERROR:
PRINT "Error: "+E$
ER=-2:E$=""
RETURN