Merge pull request #383 from asarhaddon/ada2tco-do
[jackhill/mal.git] / basic / step8_macros.in.bas
1 GOTO MAIN
2
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'
10
11 REM $INCLUDE: 'debug.in.bas'
12
13 REM READ(A$) -> R
14 MAL_READ:
15 GOSUB READ_STR
16 RETURN
17
18 REM QUASIQUOTE(A) -> R
19 SUB QUASIQUOTE
20 REM pair?
21 GOSUB TYPE_A
22 IF T<6 OR T>7 THEN GOTO QQ_QUOTE
23 IF (Z%(A+1)=0) THEN GOTO QQ_QUOTE
24 GOTO QQ_UNQUOTE
25
26 QQ_QUOTE:
27 REM ['quote, ast]
28 B$="quote":T=5:GOSUB STRING
29 B=R:A=A:GOSUB LIST2
30 AY=B:GOSUB RELEASE
31
32 GOTO QQ_DONE
33
34 QQ_UNQUOTE:
35 R=Z%(A+2)
36 IF (Z%(R)AND 31)<>5 THEN GOTO QQ_SPLICE_UNQUOTE
37 IF S$(Z%(R+1))<>"unquote" THEN GOTO QQ_SPLICE_UNQUOTE
38 REM [ast[1]]
39 R=Z%(Z%(A+1)+2)
40 GOSUB INC_REF_R
41
42 GOTO QQ_DONE
43
44 QQ_SPLICE_UNQUOTE:
45 GOSUB PUSH_A
46 REM rest of cases call quasiquote on ast[1..]
47 A=Z%(A+1):CALL QUASIQUOTE
48 W=R
49 GOSUB POP_A
50
51 REM set A to ast[0] for last two cases
52 A=Z%(A+2)
53
54 REM pair?
55 GOSUB TYPE_A
56 IF T<6 OR T>7 THEN GOTO QQ_DEFAULT
57 IF (Z%(A+1)=0) THEN GOTO QQ_DEFAULT
58
59 B=Z%(A+2)
60 IF (Z%(B)AND 31)<>5 THEN GOTO QQ_DEFAULT
61 IF S$(Z%(B+1))<>"splice-unquote" THEN QQ_DEFAULT
62 REM ['concat, ast[0][1], quasiquote(ast[1..])]
63
64 B=Z%(Z%(A+1)+2)
65 B$="concat":T=5:GOSUB STRING:C=R
66 A=W:GOSUB LIST3
67 REM release inner quasiquoted since outer list takes ownership
68 AY=A:GOSUB RELEASE
69 AY=C:GOSUB RELEASE
70 GOTO QQ_DONE
71
72 QQ_DEFAULT:
73 REM ['cons, quasiquote(ast[0]), quasiquote(ast[1..])]
74
75 Q=W:GOSUB PUSH_Q
76 REM A set above to ast[0]
77 CALL QUASIQUOTE
78 B=R
79 GOSUB POP_Q:W=Q
80
81 B$="cons":T=5:GOSUB STRING:C=R
82 A=W:GOSUB LIST3
83 REM release inner quasiquoted since outer list takes ownership
84 AY=A:GOSUB RELEASE
85 AY=B:GOSUB RELEASE
86 AY=C:GOSUB RELEASE
87 QQ_DONE:
88 END SUB
89
90 REM MACROEXPAND(A, E) -> A:
91 SUB MACROEXPAND
92 GOSUB PUSH_A
93
94 MACROEXPAND_LOOP:
95 REM list?
96 GOSUB TYPE_A
97 IF T<>6 THEN GOTO MACROEXPAND_DONE
98 REM non-empty?
99 IF Z%(A+1)=0 THEN GOTO MACROEXPAND_DONE
100 B=Z%(A+2)
101 REM symbol? in first position
102 IF (Z%(B)AND 31)<>5 THEN GOTO MACROEXPAND_DONE
103 REM defined in environment?
104 K=B:CALL ENV_FIND
105 IF R=-1 THEN GOTO MACROEXPAND_DONE
106 B=R4
107 REM macro?
108 IF (Z%(B)AND 31)<>11 THEN GOTO MACROEXPAND_DONE
109
110 F=B:AR=Z%(A+1):CALL APPLY
111 A=R
112
113 GOSUB PEEK_Q:AY=Q
114 REM if previous A was not the first A into macroexpand (i.e. an
115 REM intermediate form) then free it
116 IF A<>AY THEN GOSUB PEND_A_LV
117
118 IF ER<>-2 THEN GOTO MACROEXPAND_DONE
119 GOTO MACROEXPAND_LOOP
120
121 MACROEXPAND_DONE:
122 GOSUB POP_Q: REM pop original A
123 END SUB
124
125 REM EVAL_AST(A, E) -> R
126 SUB EVAL_AST
127 REM push A and E on the stack
128 Q=E:GOSUB PUSH_Q
129 GOSUB PUSH_A
130
131 IF ER<>-2 THEN GOTO EVAL_AST_RETURN
132
133 GOSUB TYPE_A
134 IF T=5 THEN GOTO EVAL_AST_SYMBOL
135 IF T>=6 AND T<=8 THEN GOTO EVAL_AST_SEQ
136
137 REM scalar: deref to actual value and inc ref cnt
138 R=A
139 GOSUB INC_REF_R
140 GOTO EVAL_AST_RETURN
141
142 EVAL_AST_SYMBOL:
143 K=A:GOTO ENV_GET
144 ENV_GET_RETURN:
145 GOTO EVAL_AST_RETURN
146
147 EVAL_AST_SEQ:
148 REM setup the stack for the loop
149 GOSUB MAP_LOOP_START
150
151 EVAL_AST_SEQ_LOOP:
152 REM check if we are done evaluating the source sequence
153 IF Z%(A+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
154
155 REM if we are returning to DO, then skip last element
156 REM The EVAL_DO call to EVAL_AST must be call #2 for EVAL_AST to
157 REM return early and for TCO to work
158 Q=5:GOSUB PEEK_Q_Q
159 IF Q=2 AND Z%(Z%(A+1)+1)=0 THEN GOTO EVAL_AST_SEQ_LOOP_DONE
160
161 REM call EVAL for each entry
162 GOSUB PUSH_A
163 IF T<>8 THEN A=Z%(A+2)
164 IF T=8 THEN A=Z%(A+3)
165 Q=T:GOSUB PUSH_Q: REM push/save type
166 CALL EVAL
167 GOSUB POP_Q:T=Q: REM pop/restore type
168 GOSUB POP_A
169 M=R
170
171 REM if error, release the unattached element
172 REM TODO: is R=0 correct?
173 IF ER<>-2 THEN AY=R:GOSUB RELEASE:R=0:GOTO EVAL_AST_SEQ_LOOP_DONE
174
175 REM for hash-maps, copy the key (inc ref since we are going to
176 REM release it below)
177 IF T=8 THEN N=M:M=Z%(A+2):Z%(M)=Z%(M)+32
178
179
180 REM update the return sequence structure
181 REM release N (and M if T=8) since seq takes full ownership
182 C=1:GOSUB MAP_LOOP_UPDATE
183
184 REM process the next sequence entry from source list
185 A=Z%(A+1)
186
187 GOTO EVAL_AST_SEQ_LOOP
188 EVAL_AST_SEQ_LOOP_DONE:
189 REM cleanup stack and get return value
190 GOSUB MAP_LOOP_DONE
191 GOTO EVAL_AST_RETURN
192
193 EVAL_AST_RETURN:
194 REM pop A and E off the stack
195 GOSUB POP_A
196 GOSUB POP_Q:E=Q
197 END SUB
198
199 REM EVAL(A, E) -> R
200 SUB EVAL
201 LV=LV+1: REM track basic return stack level
202
203 REM push A and E on the stack
204 Q=E:GOSUB PUSH_Q
205 GOSUB PUSH_A
206
207 REM PRINT "EVAL A:"+STR$(A)+",X:"+STR$(X)+",LV:"+STR$(LV)+",FRE:"+STR$(FRE(0))
208
209 EVAL_TCO_RECUR:
210
211 IF ER<>-2 THEN GOTO EVAL_RETURN
212
213 REM AZ=A:B=1:GOSUB PR_STR
214 REM PRINT "EVAL: "+R$+" [A:"+STR$(A)+", LV:"+STR$(LV)+"]"
215
216 GOSUB LIST_Q
217 IF R THEN GOTO APPLY_LIST
218 EVAL_NOT_LIST:
219 REM ELSE
220 CALL EVAL_AST
221 GOTO EVAL_RETURN
222
223 APPLY_LIST:
224 CALL MACROEXPAND
225
226 GOSUB LIST_Q
227 IF R<>1 THEN GOTO EVAL_NOT_LIST
228
229 GOSUB EMPTY_Q
230 IF R THEN R=A:GOSUB INC_REF_R:GOTO EVAL_RETURN
231
232 A0=Z%(A+2)
233
234 REM get symbol in A$
235 IF (Z%(A0)AND 31)<>5 THEN A$=""
236 IF (Z%(A0)AND 31)=5 THEN A$=S$(Z%(A0+1))
237
238 IF A$="def!" THEN GOTO EVAL_DEF
239 IF A$="let*" THEN GOTO EVAL_LET
240 IF A$="quote" THEN GOTO EVAL_QUOTE
241 IF A$="quasiquote" THEN GOTO EVAL_QUASIQUOTE
242 IF A$="defmacro!" THEN GOTO EVAL_DEFMACRO
243 IF A$="macroexpand" THEN GOTO EVAL_MACROEXPAND
244 IF A$="do" THEN GOTO EVAL_DO
245 IF A$="if" THEN GOTO EVAL_IF
246 IF A$="fn*" THEN GOTO EVAL_FN
247 GOTO EVAL_INVOKE
248
249 EVAL_GET_A3:
250 A3=Z%(Z%(Z%(Z%(A+1)+1)+1)+2)
251 EVAL_GET_A2:
252 A2=Z%(Z%(Z%(A+1)+1)+2)
253 EVAL_GET_A1:
254 A1=Z%(Z%(A+1)+2)
255 RETURN
256
257 EVAL_DEF:
258 REM PRINT "def!"
259 GOSUB EVAL_GET_A2: REM set A1 and A2
260
261 Q=A1:GOSUB PUSH_Q
262 A=A2:CALL EVAL: REM eval a2
263 GOSUB POP_Q:A1=Q
264
265 IF ER<>-2 THEN GOTO EVAL_RETURN
266
267 REM set a1 in env to a2
268 K=A1:C=R:GOSUB ENV_SET
269 GOTO EVAL_RETURN
270
271 EVAL_LET:
272 REM PRINT "let*"
273 GOSUB EVAL_GET_A2: REM set A1 and A2
274
275 Q=A2:GOSUB PUSH_Q: REM push/save A2
276 Q=E:GOSUB PUSH_Q: REM push env for for later release
277
278 REM create new environment with outer as current environment
279 C=E:GOSUB ENV_NEW
280 E=R
281 EVAL_LET_LOOP:
282 IF Z%(A1+1)=0 THEN GOTO EVAL_LET_LOOP_DONE
283
284 Q=A1:GOSUB PUSH_Q: REM push A1
285 REM eval current A1 odd element
286 A=Z%(Z%(A1+1)+2):CALL EVAL
287 GOSUB POP_Q:A1=Q: REM pop A1
288
289 IF ER<>-2 THEN GOTO EVAL_LET_LOOP_DONE
290
291 REM set key/value in the environment
292 K=Z%(A1+2):C=R:GOSUB ENV_SET
293 AY=R:GOSUB RELEASE: REM release our use, ENV_SET took ownership
294
295 REM skip to the next pair of A1 elements
296 A1=Z%(Z%(A1+1)+1)
297 GOTO EVAL_LET_LOOP
298
299 EVAL_LET_LOOP_DONE:
300 GOSUB POP_Q:AY=Q: REM pop previous env
301
302 REM release previous environment if not the current EVAL env
303 GOSUB PEEK_Q_2
304 IF AY<>Q THEN GOSUB RELEASE
305
306 GOSUB POP_Q:A2=Q: REM pop A2
307 A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop
308
309 EVAL_DO:
310 A=Z%(A+1): REM rest
311 GOSUB PUSH_A: REM push/save A
312
313 REM this must be EVAL_AST call #2 for EVAL_AST to return early
314 REM and for TCO to work
315 CALL EVAL_AST
316
317 REM cleanup
318 AY=R: REM get eval'd list for release
319
320 GOSUB POP_A: REM pop/restore original A for LAST
321 GOSUB LAST: REM get last element for return
322 A=R: REM new recur AST
323
324 REM cleanup
325 GOSUB RELEASE: REM release eval'd list
326 AY=A:GOSUB RELEASE: REM release LAST value (not sure why)
327
328 GOTO EVAL_TCO_RECUR: REM TCO loop
329
330 EVAL_QUOTE:
331 R=Z%(Z%(A+1)+2)
332 GOSUB INC_REF_R
333 GOTO EVAL_RETURN
334
335 EVAL_QUASIQUOTE:
336 R=Z%(Z%(A+1)+2)
337 A=R:CALL QUASIQUOTE
338 A=R
339 REM add quasiquote result to pending release queue to free when
340 REM next lower EVAL level returns (LV)
341 GOSUB PEND_A_LV
342
343 GOTO EVAL_TCO_RECUR: REM TCO loop
344
345 EVAL_DEFMACRO:
346 REM PRINT "defmacro!"
347 GOSUB EVAL_GET_A2: REM set A1 and A2
348
349 Q=A1:GOSUB PUSH_Q: REM push A1
350 A=A2:CALL EVAL: REM eval A2
351 GOSUB POP_Q:A1=Q: REM pop A1
352
353 REM change function to macro
354 Z%(R)=Z%(R)+1
355
356 REM set A1 in env to A2
357 K=A1:C=R:GOSUB ENV_SET
358 GOTO EVAL_RETURN
359
360 EVAL_MACROEXPAND:
361 REM PRINT "macroexpand"
362 R=Z%(Z%(A+1)+2)
363 A=R:CALL MACROEXPAND
364 R=A
365
366 REM since we are returning it unevaluated, inc the ref cnt
367 GOSUB INC_REF_R
368 GOTO EVAL_RETURN
369
370 EVAL_IF:
371 GOSUB EVAL_GET_A1: REM set A1
372 GOSUB PUSH_A: REM push/save A
373 A=A1:CALL EVAL
374 GOSUB POP_A: REM pop/restore A
375 IF (R=0) OR (R=2) THEN GOTO EVAL_IF_FALSE
376
377 EVAL_IF_TRUE:
378 AY=R:GOSUB RELEASE
379 GOSUB EVAL_GET_A2: REM set A1 and A2 after EVAL
380 A=A2:GOTO EVAL_TCO_RECUR: REM TCO loop
381 EVAL_IF_FALSE:
382 AY=R:GOSUB RELEASE
383 REM if no false case (A3), return nil
384 GOSUB COUNT
385 IF R<4 THEN R=0:GOSUB INC_REF_R:GOTO EVAL_RETURN
386 GOSUB EVAL_GET_A3: REM set A1 - A3 after EVAL
387 A=A3:GOTO EVAL_TCO_RECUR: REM TCO loop
388
389 EVAL_FN:
390 GOSUB EVAL_GET_A2: REM set A1 and A2
391 T=10:L=A2:M=A1:N=E:GOSUB ALLOC: REM mal function
392 GOTO EVAL_RETURN
393
394 EVAL_INVOKE:
395 CALL EVAL_AST
396
397 REM if error, return f/args for release by caller
398 IF ER<>-2 THEN GOTO EVAL_RETURN
399
400 REM push f/args for release after call
401 GOSUB PUSH_R
402
403 AR=Z%(R+1): REM rest
404 F=Z%(R+2)
405
406 REM if metadata, get the actual object
407 GOSUB TYPE_F
408 IF T=14 THEN F=Z%(F+1):GOSUB TYPE_F
409
410 ON T-8 GOTO EVAL_DO_FUNCTION,EVAL_DO_MAL_FUNCTION,EVAL_DO_MAL_FUNCTION
411
412 REM if error, pop and return f/args for release by caller
413 GOSUB POP_R
414 ER=-1:E$="apply of non-function":GOTO EVAL_RETURN
415
416 EVAL_DO_FUNCTION:
417 REM regular function
418 IF Z%(F+1)<60 THEN GOSUB DO_FUNCTION:GOTO EVAL_DO_FUNCTION_SKIP
419 REM for recur functions (apply, map, swap!), use GOTO
420 IF Z%(F+1)>60 THEN CALL DO_TCO_FUNCTION
421 EVAL_DO_FUNCTION_SKIP:
422
423 REM pop and release f/args
424 GOSUB POP_Q:AY=Q
425 GOSUB RELEASE
426 GOTO EVAL_RETURN
427
428 EVAL_DO_MAL_FUNCTION:
429 Q=E:GOSUB PUSH_Q: REM save the current environment for release
430
431 REM create new environ using env and params stored in function
432 C=Z%(F+3):A=Z%(F+2):B=AR:GOSUB ENV_NEW_BINDS
433
434 REM release previous env if it is not the top one on the
435 REM stack (X%(X-2)) because our new env refers to it and
436 REM we no longer need to track it (since we are TCO recurring)
437 GOSUB POP_Q:AY=Q
438 GOSUB PEEK_Q_2
439 IF AY<>Q THEN GOSUB RELEASE
440
441 REM claim the AST before releasing the list containing it
442 A=Z%(F+1):Z%(A)=Z%(A)+32
443 REM add AST to pending release queue to free as soon as EVAL
444 REM actually returns (LV+1)
445 LV=LV+1:GOSUB PEND_A_LV:LV=LV-1
446
447 REM pop and release f/args
448 GOSUB POP_Q:AY=Q
449 GOSUB RELEASE
450
451 REM A set above
452 E=R:GOTO EVAL_TCO_RECUR: REM TCO loop
453
454 EVAL_RETURN:
455 REM AZ=R: B=1: GOSUB PR_STR
456 REM PRINT "EVAL_RETURN R: ["+R$+"] ("+STR$(R)+"), LV:"+STR$(LV)+",ER:"+STR$(ER)
457
458 REM release environment if not the top one on the stack
459 GOSUB PEEK_Q_1
460 IF E<>Q THEN AY=E:GOSUB RELEASE
461
462 LV=LV-1: REM track basic return stack level
463
464 REM release everything we couldn't release earlier
465 GOSUB RELEASE_PEND
466
467 REM trigger GC
468 #cbm T=FRE(0)
469 #qbasic T=0
470
471 REM pop A and E off the stack
472 GOSUB POP_A
473 GOSUB POP_Q:E=Q
474
475 END SUB
476
477 REM PRINT(A) -> R$
478 MAL_PRINT:
479 AZ=A:B=1:GOSUB PR_STR
480 RETURN
481
482 REM RE(A$) -> R
483 REM Assume D has repl_env
484 REM caller must release result
485 RE:
486 R1=-1
487 GOSUB MAL_READ
488 R1=R
489 IF ER<>-2 THEN GOTO RE_DONE
490
491 A=R:E=D:CALL EVAL
492
493 RE_DONE:
494 REM Release memory from MAL_READ
495 AY=R1:GOSUB RELEASE
496 RETURN: REM caller must release result of EVAL
497
498 REM REP(A$) -> R$
499 REM Assume D has repl_env
500 SUB REP
501 R2=-1
502
503 GOSUB RE
504 R2=R
505 IF ER<>-2 THEN GOTO REP_DONE
506
507 A=R:GOSUB MAL_PRINT
508
509 REP_DONE:
510 REM Release memory from MAL_READ and EVAL
511 AY=R2:GOSUB RELEASE
512 END SUB
513
514 REM MAIN program
515 MAIN:
516 GOSUB INIT_MEMORY
517
518 LV=0
519
520 REM create repl_env
521 C=0:GOSUB ENV_NEW:D=R
522
523 REM core.EXT: defined in Basic
524 E=D:GOSUB INIT_CORE_NS: REM set core functions in repl_env
525
526 ZT=ZI: REM top of memory after base repl_env
527
528 REM core.mal: defined using the language itself
529 A$="(def! not (fn* (a) (if a false true)))"
530 GOSUB RE:AY=R:GOSUB RELEASE
531
532 A$="(def! load-file (fn* (f) (eval (read-file f))))"
533 GOSUB RE:AY=R:GOSUB RELEASE
534
535 A$="(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs)"
536 A$=A$+" (if (> (count xs) 1) (nth xs 1) (throw "+CHR$(34)+"odd number of"
537 A$=A$+" forms to cond"+CHR$(34)+")) (cons 'cond (rest (rest xs)))))))"
538 GOSUB RE:AY=R:GOSUB RELEASE
539
540 A$="(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs)"
541 A$=A$+" `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"
542 GOSUB RE:AY=R:GOSUB RELEASE
543
544 REM load the args file
545 A$="(def! -*ARGS*- (load-file "+CHR$(34)+".args.mal"+CHR$(34)+"))"
546 GOSUB RE:AY=R:GOSUB RELEASE
547
548 IF ER>-2 THEN GOSUB PRINT_ERROR:END
549
550 REM set the argument list
551 A$="(def! *ARGV* (rest -*ARGS*-))"
552 GOSUB RE:AY=R:GOSUB RELEASE
553
554 REM get the first argument
555 A$="(first -*ARGS*-)"
556 GOSUB RE
557
558 REM no arguments, start REPL loop
559 IF R<16 THEN GOTO REPL_LOOP
560
561 REM if there is an argument, then run it as a program
562
563 RUN_PROG:
564 REM free up first arg because we get it again
565 AY=R:GOSUB RELEASE
566 REM run a single mal program and exit
567 A$="(load-file (first -*ARGS*-))"
568 GOSUB RE
569 IF ER<>-2 THEN GOSUB PRINT_ERROR
570 GOTO QUIT
571
572 REPL_LOOP:
573 A$="user> ":GOSUB READLINE: REM call input parser
574 IF EZ=1 THEN GOTO QUIT
575 IF R$="" THEN GOTO REPL_LOOP
576
577 A$=R$:CALL REP: REM call REP
578
579 IF ER<>-2 THEN GOSUB PRINT_ERROR:GOTO REPL_LOOP
580 PRINT R$
581 GOTO REPL_LOOP
582
583 QUIT:
584 REM GOSUB PR_MEMORY_SUMMARY_SMALL
585 #cbm END
586 #qbasic SYSTEM
587
588 PRINT_ERROR:
589 PRINT "Error: "+E$
590 ER=-2:E$=""
591 RETURN
592