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