3 REM $INCLUDE: 'mem.in.bas'
4 REM $INCLUDE: 'types.in.bas'
5 REM $INCLUDE: 'readline.in.bas'
6 REM $INCLUDE: 'reader.in.bas'
7 REM $INCLUDE: 'printer.in.bas'
8 REM $INCLUDE: 'env.in.bas'
9 REM $INCLUDE: 'core.in.bas'
11 REM $INCLUDE: 'debug.in.bas'
13 REM READ is inlined in RE
15 REM QUASIQUOTE(A) -> R
19 IF T
<6 OR T
>7 THEN GOTO QQ_QUOTE
20 IF (Z
%(A
+1)=0) THEN GOTO QQ_QUOTE
25 B
$="quote":T=5:GOSUB STRING
33 IF (Z
%(R
)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE
34 IF S
$(Z%(R+1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE
43 REM rest of cases call quasiquote on ast[1..]
44 A
=Z
%(A
+1):CALL QUASIQUOTE
48 REM set A to ast[0] for last two cases
53 IF T
<6 OR T
>7 THEN GOTO QQ_DEFAULT
54 IF (Z
%(A
+1)=0) THEN GOTO QQ_DEFAULT
57 IF (Z
%(B
)AND 31)<>5 THEN GOTO QQ_DEFAULT
58 IF S
$(Z%(B+1))<>"splice-unquote" THEN QQ_DEFAULT
59 REM ['concat, ast[0][1], quasiquote(ast[1..])]
62 B
$="concat":T=5:GOSUB STRING:C
=R
64 REM release inner quasiquoted since outer list takes ownership
70 REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])]
73 REM A set above to ast[0]
78 B
$="cons":T=5:GOSUB STRING:C
=R
80 REM release inner quasiquoted since outer list takes ownership
87 REM MACROEXPAND(A, E) -> A:
94 IF T
<>6 THEN GOTO MACROEXPAND_DONE
96 IF Z
%(A
+1)=0 THEN GOTO MACROEXPAND_DONE
98 REM symbol? in first position
99 IF (Z
%(B
)AND 31)<>5 THEN GOTO MACROEXPAND_DONE
100 REM defined in environment?
102 IF R
=-1 THEN GOTO MACROEXPAND_DONE
105 IF (Z
%(B
)AND 31)<>11 THEN GOTO MACROEXPAND_DONE
107 F
=B
:AR
=Z
%(A
+1):CALL APPLY
111 REM if previous A was not the first A into macroexpand (i.e. an
112 REM intermediate form) then free it
113 IF A
<>AY
THEN GOSUB PEND_A_LV
115 IF ER
<>-2 THEN GOTO MACROEXPAND_DONE
116 GOTO MACROEXPAND_LOOP
119 GOSUB POP_Q
: REM pop original A
122 REM EVAL_AST(A, E) -> R
124 REM push A and E on the stack
128 IF ER
<>-2 THEN GOTO EVAL_AST_RETURN
131 IF T
=5 THEN GOTO EVAL_AST_SYMBOL
132 IF T
>5 AND T
<9 THEN GOTO EVAL_AST_SEQ
134 REM scalar: deref to actual value and inc ref cnt
145 REM setup the stack for the loop
149 REM check if we are done evaluating the source sequence
150 IF Z
%(A
+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
152 REM if we are returning to DO, then skip last element
153 REM The EVAL_DO call to EVAL_AST must be call #2 for EVAL_AST to
154 REM return early and for TCO to work
156 IF Q
=2 AND Z
%(Z
%(A
+1)+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
158 REM call EVAL for each entry
160 IF T
<>8 THEN A
=Z
%(A
+2)
161 IF T
=8 THEN A
=Z
%(A
+3)
162 Q
=T
:GOSUB PUSH_Q
: REM push/save type
164 GOSUB POP_Q
:T
=Q
: REM pop/restore type
168 REM if error, release the unattached element
169 REM TODO: is R=0 correct?
170 IF ER
<>-2 THEN AY
=R
:GOSUB RELEASE
:R
=0:GOTO EVAL_AST_SEQ_LOOP_DONE
172 REM for hash-maps, copy the key (inc ref since we are going to
173 REM release it below)
174 IF T
=8 THEN N
=M
:M
=Z
%(A
+2):Z
%(M
)=Z
%(M
)+32
177 REM update the return sequence structure
178 REM release N (and M if T=8) since seq takes full ownership
179 C
=1:GOSUB MAP_LOOP_UPDATE
181 REM process the next sequence entry from source list
184 GOTO EVAL_AST_SEQ_LOOP
185 EVAL_AST_SEQ_LOOP_DONE:
186 REM cleanup stack and get return value
191 REM pop A and E off the stack
198 LV
=LV
+1: REM track basic return stack level
200 REM push A and E on the stack
204 REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0))
208 IF ER
<>-2 THEN GOTO EVAL_RETURN
210 REM AZ=A:B=1:GOSUB PR_STR
211 REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]"
214 IF R
THEN GOTO APPLY_LIST
224 IF R
<>1 THEN GOTO EVAL_NOT_LIST
227 IF R
THEN R
=A
:GOSUB INC_REF_R
:GOTO EVAL_RETURN
232 IF (Z
%(A0
)AND 31)<>5 THEN A
$=""
233 IF (Z
%(A0
)AND 31)=5 THEN A
$=S$(Z%(A0+1))
235 IF A
$="def!" THEN GOTO EVAL_DEF
236 IF A
$="let*" THEN GOTO EVAL_LET
237 IF A
$="quote" THEN GOTO EVAL_QUOTE
238 IF A
$="quasiquote" THEN GOTO EVAL_QUASIQUOTE
239 IF A
$="defmacro!" THEN GOTO EVAL_DEFMACRO
240 IF A
$="macroexpand" THEN GOTO EVAL_MACROEXPAND
241 IF A
$="try*" THEN GOTO EVAL_TRY
242 IF A
$="do" THEN GOTO EVAL_DO
243 IF A
$="if" THEN GOTO EVAL_IF
244 IF A
$="fn*" THEN GOTO EVAL_FN
248 A3
=Z
%(Z
%(Z
%(Z
%(A
+1)+1)+1)+2)
250 A2
=Z
%(Z
%(Z
%(A
+1)+1)+2)
257 GOSUB EVAL_GET_A2
: REM set A1 and A2
260 A
=A2
:CALL EVAL
: REM eval a2
263 IF ER
<>-2 THEN GOTO EVAL_RETURN
265 REM set a1 in env to a2
266 K
=A1
:C
=R
:GOSUB ENV_SET
271 GOSUB EVAL_GET_A2
: REM set A1 and A2
273 Q
=A2
:GOSUB PUSH_Q
: REM push/save A2
274 Q
=E
:GOSUB PUSH_Q
: REM push env for for later release
276 REM create new environment with outer as current environment
280 IF Z
%(A1
+1)=0 THEN GOTO EVAL_LET_LOOP_DONE
282 Q
=A1
:GOSUB PUSH_Q
: REM push A1
283 REM eval current A1 odd element
284 A
=Z
%(Z
%(A1
+1)+2):CALL EVAL
285 GOSUB POP_Q
:A1
=Q
: REM pop A1
287 IF ER
<>-2 THEN GOTO EVAL_LET_LOOP_DONE
289 REM set key/value in the environment
290 K
=Z
%(A1
+2):C
=R
:GOSUB ENV_SET
291 AY
=R
:GOSUB RELEASE
: REM release our use, ENV_SET took ownership
293 REM skip to the next pair of A1 elements
298 GOSUB POP_Q
:AY
=Q
: REM pop previous env
300 REM release previous environment if not the current EVAL env
302 IF AY
<>Q
THEN GOSUB RELEASE
304 GOSUB POP_Q
:A2
=Q
: REM pop A2
305 A
=A2
:GOTO EVAL_TCO_RECUR
: REM TCO loop
309 GOSUB PUSH_A
: REM push/save A
311 REM this must be EVAL_AST call #2 for EVAL_AST to return early
312 REM and for TCO to work
316 AY
=R
: REM get eval'd list for release
318 GOSUB POP_A
: REM pop/restore original A for LAST
319 GOSUB LAST
: REM get last element for return
320 A
=R
: REM new recur AST
323 GOSUB RELEASE
: REM release eval'd list
324 AY
=A
:GOSUB RELEASE
: REM release LAST value (not sure why)
326 GOTO EVAL_TCO_RECUR
: REM TCO loop
337 REM add quasiquote result to pending release queue to free when
338 REM next lower EVAL level returns (LV)
341 GOTO EVAL_TCO_RECUR
: REM TCO loop
344 REM PRINT "defmacro!"
345 GOSUB EVAL_GET_A2
: REM set A1 and A2
347 Q
=A1
:GOSUB PUSH_Q
: REM push A1
348 A
=A2
:CALL EVAL
: REM eval A2
349 GOSUB POP_Q
:A1
=Q
: REM pop A1
351 REM change function to macro
354 REM set A1 in env to A2
355 K
=A1
:C
=R
:GOSUB ENV_SET
359 REM PRINT "macroexpand"
364 REM since we are returning it unevaluated, inc the ref cnt
370 GOSUB EVAL_GET_A1
: REM set A1
372 GOSUB PUSH_A
: REM push/save A
373 A
=A1
:CALL EVAL
: REM eval A1
374 GOSUB POP_A
: REM pop/restore A
376 GOSUB EVAL_GET_A2
: REM set A1 and A2
378 REM if there is no error or catch block then return
379 IF ER
=-2 OR A2
=0 THEN GOTO EVAL_RETURN
381 REM create environment for the catch block eval
382 C
=E
:GOSUB ENV_NEW
:E
=R
384 A
=A2
:GOSUB EVAL_GET_A2
: REM set A1 and A2 from catch block
386 REM create object for ER=-1 type raw string errors
387 IF ER
=-1 THEN B
$=E$:T=4:GOSUB STRING:ER
=R
:GOSUB INC_REF_R
389 REM bind the catch symbol to the error object
390 K
=A1
:C
=ER
:GOSUB ENV_SET
391 AY
=R
:GOSUB RELEASE
: REM release our use, env took ownership
393 REM unset error for catch eval
401 GOSUB EVAL_GET_A1
: REM set A1
402 GOSUB PUSH_A
: REM push/save A
404 GOSUB POP_A
: REM pop/restore A
405 IF (R
=0) OR (R
=2) THEN GOTO EVAL_IF_FALSE
409 GOSUB EVAL_GET_A2
: REM set A1 and A2 after EVAL
410 A
=A2
:GOTO EVAL_TCO_RECUR
: REM TCO loop
413 REM if no false case (A3), return nil
415 IF R
<4 THEN R
=0:GOSUB INC_REF_R
:GOTO EVAL_RETURN
416 GOSUB EVAL_GET_A3
: REM set A1 - A3 after EVAL
417 A
=A3
:GOTO EVAL_TCO_RECUR
: REM TCO loop
420 GOSUB EVAL_GET_A2
: REM set A1 and A2
421 T
=10:L
=A2
:M
=A1
:N
=E
:GOSUB ALLOC
: REM mal function
427 REM if error, return f/args for release by caller
428 IF ER
<>-2 THEN GOTO EVAL_RETURN
430 REM push f/args for release after call
436 REM if metadata, get the actual object
438 IF T
=14 THEN F
=Z
%(F
+1):GOSUB TYPE_F
440 ON T
-8 GOTO EVAL_DO_FUNCTION
,EVAL_DO_MAL_FUNCTION
,EVAL_DO_MAL_FUNCTION
442 REM if error, pop and return f/args for release by caller
444 ER
=-1:E
$="apply of non
-function":GOTO EVAL_RETURN
448 IF Z%(F+1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP
449 REM for recur functions (apply, map, swap!), use GOTO
450 IF Z%(F+1)>60 THEN CALL DO_TCO_FUNCTION
451 EVAL_DO_FUNCTION_SKIP:
453 REM pop and release f/args
458 EVAL_DO_MAL_FUNCTION:
459 Q=E:GOSUB PUSH_Q: REM save the current environment for release
461 REM create new environ using env and params stored in function
462 C=Z%(F+3):A=Z%(F+2):B=AR:GOSUB ENV_NEW_BINDS
464 REM release previous env if it is not the top one on the
465 REM stack (X%(X-2)) because our new env refers to it and
466 REM we no longer need to track it (since we are TCO recurring)
469 IF AY<>Q THEN GOSUB RELEASE
471 REM claim the AST before releasing the list containing it
472 A=Z%(F+1):Z%(A)=Z%(A)+32
473 REM add AST to pending release queue to free as soon as EVAL
474 REM actually returns (LV+1)
475 LV=LV+1:GOSUB PEND_A_LV:LV=LV-1
477 REM pop and release f/args
482 E=R:GOTO EVAL_TCO_RECUR: REM TCO loop
485 REM AZ=R: B=1: GOSUB PR_STR
486 REM PRINT "EVAL_RETURN R
: ["+R$+"] ("+STR
$(R)+"), LV
:"+STR$(LV)+",ER:"+STR$(ER)
488 REM release environment if not the top one on the stack
490 IF E<>Q THEN AY=E:GOSUB RELEASE
492 LV=LV-1: REM track basic return stack level
494 REM release everything we couldn't release earlier
501 REM pop A and E off the stack
507 REM PRINT is inlined in REP
511 REM Assume D has repl_env
512 REM caller must release result
515 GOSUB READ_STR: REM inlined MAL_READ
517 IF ER<>-2 THEN GOTO RE_DONE
522 REM Release memory from MAL_READ
524 RETURN: REM caller must release result of EVAL
527 REM Assume D has repl_env
533 IF ER<>-2 THEN GOTO REP_DONE
535 AZ=R:B=1:GOSUB PR_STR: REM MAL_PRINT
538 REM Release memory from MAL_READ and EVAL
549 C=0:GOSUB ENV_NEW:D=R
551 REM core.EXT: defined in Basic
552 E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env
554 ZT=ZI: REM top of memory after base repl_env
556 REM core.mal: defined using the language itself
557 #cbm A$="(def! *host-language* "+CHR
$(34)+"C64 BASIC
"+CHR$(34)+")"
558 #qbasic A$="(def! *host-language* "+CHR
$(34)+"QBasic"+CHR$(34)+")"
559 GOSUB RE
:AY
=R
:GOSUB RELEASE
561 A
$="(def! not (fn
* (a
) (if a
false true)))"
562 GOSUB RE:AY=R:GOSUB RELEASE
564 A$="(def! load-file (fn* (f) (do (eval (read-file f)) nil)))"
565 GOSUB RE
:AY
=R
:GOSUB RELEASE
567 A
$="(defmacro! cond (fn
* (& xs
) (if (> (count xs
) 0) (list
'if (first xs)"
568 A
$=A$+" (if (> (count xs
) 1) (nth xs
1) (throw
"+CHR$(34)+"odd number of"
569 A
$=A$+" forms
to cond
"+CHR$(34)+")) (cons 'cond (rest (rest xs)))))))"
570 GOSUB RE
:AY
=R
:GOSUB RELEASE
572 REM load the args file
573 A
$="(load-file "+CHR$(34)+".args.mal"+CHR$(34)+")"
574 GOSUB RE:AY=R:GOSUB RELEASE
576 IF ER>-2 THEN GOSUB PRINT_ERROR:END
578 REM set the argument list
579 A$="(def! *ARGV* (rest -*ARGS*-))"
580 GOSUB RE
:AY
=R
:GOSUB RELEASE
582 REM get the first argument
583 A
$="(first -*ARGS
*-)"
586 REM no arguments, start REPL loop
587 IF R<16 THEN GOTO REPL
589 REM if there is an argument, then run it as a program
592 REM free up first arg because we get it again
594 REM run a single mal program and exit
595 A$="(load-file (first -*ARGS*-))"
597 IF ER
<>-2 THEN GOSUB PRINT_ERROR
601 REM print the REPL startup header
602 REM save memory by printing this directly
603 #cbm PRINT
"Mal [C64 BASIC]"
604 #qbasic PRINT
"Mal [QBasic]"
607 A
$="user> ":GOSUB READLINE: REM call input parser
608 IF EZ=1 THEN GOTO QUIT
609 IF R$="" THEN GOTO REPL_LOOP
611 A$=R$:CALL REP: REM call REP
613 IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP
618 REM GOSUB PR_MEMORY_SUMMARY_SMALL
619 REM GOSUB PR_MEMORY_MAP
620 REM P1=0:P2=ZI:GOSUB PR_MEMORY
621 REM P1=D:GOSUB PR_OBJECT
622 REM P1=ZK:GOSUB PR_OBJECT
627 REM if the error is an object, then print and free it
628 IF ER>=0 THEN AZ=ER:B=0:GOSUB PR_STR:E$=R$:AY=ER:GOSUB RELEASE