Repository navigation
Expand file tree
/
Copy pathtinybasic.sol
More file actions
2402 lines (2299 loc) · 62.1 KB
/
Copy pathtinybasic.sol
File metadata and controls
2402 lines (2299 loc) · 62.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# TinyBASIC for CPT32, implemented in sol.
#
# Structured frontend: LEXER (source text -> token array) + PARSER
# (token array -> AST nodes) + INTERPRETER (tree-walk over the AST).
# The AST layer is also the extension point for a future compiler
# backend: tb_emit_program / tb_emit_stmt / tb_emit_expr walk the same
# nodes a code generator would consume (see CODEGEN HOOK section).
#
# Interactive PCG console; keyboard input comes from the PeC event FIFO.
# Program lines are kept as plain source text (LIST / LOAD / SAVE work on
# text), and RUN first builds a line directory of AST roots, then executes
# the AST. Direct (line-number-less) input is lexed/parsed/executed the
# same way, one line at a time.
#
# BASIC truth values are 1 (true) / 0 (false). Do not confuse them with
# the sol convention (comparison true = 0, `if`/`while` run on 0).
#
# Edge semantics kept from the previous interpreter: FOR executes its
# body at least once (the exit test lives in NEXT); LOAD/SAVE default to
# 128 sectors with LBA 0; LOAD clamps to the disk capacity; LOAD or NEW
# typed inside a running program stops it (the AST under execution would
# otherwise go stale); BYE/QUIT halt the CPU.
#
# Modules: every function lives in a sol namespace named after its section
# banner, and calls that cross a namespace are written qualified so the
# module boundary is visible at the call site.
# line line text storage vars variable / array storage
# con console output kbd keyboard input
# lex lexer ast AST heap
# parse parser prog program lines -> AST directory
# eval expression evaluator exec statement executor
# emit codegen hook run REPL and execution loop
# disk DSFS load / save
# irq3 and main stay in the global namespace: solc only builds the IRQ
# dispatcher and the entry call for globally named functions.
!include "sol/cpt32/pcg.sol"
!include "sol/cpt32/pec_rng.sol"
!include "sol/cpt32/irqc.sol"
!const TB_PEC_DATA 0x80040030
!const TB_PEC_STATUS 0x80040034
!const TB_PEC_POP 0x80040038
!const TB_IDC 0x80041000
!const TB_DMA 0x80050000
!const TB_DMA_CH0 0x80050100
!const TB_DISK_DATA 0x80041024
!const TB_LINE_BASE 0x00200000
!const TB_LINE_STRIDE 128
!const TB_ARRAY_BASE 0x00180000
!const TB_VAR_BASE 0x00170000
!const TB_GOSUB_BASE 0x00190000
!const TB_FOR_BASE 0x00191000
!const TB_INPUT_BASE 0x001B0000
!const TB_WORD_BASE 0x001B0100
!const TB_TEMP_BASE 0x001B0200
# Lexer output: token array, 8 bytes per entry (type, value).
!const TB_TOK_BUF 0x001B1000
!const TB_TOK_MAX 96
# Lexer scratch for the NUL-terminated bytes of one line's strings.
!const TB_STR_BUF 0x001B2000
# Line directory: 8 bytes per entry (lineNo, astRoot).
!const TB_LINE_DIR 0x001C0000
!const TB_DIR_MAX 1024
# AST heap: 20-byte nodes (kind, value, a, b, c).
!const TB_AST_BASE 0x00500000
!const TB_AST_LIMIT 0x00540000
# Persistent string pool for AST string literals (lexer scratch is per line).
!const TB_STR_POOL 0x00540000
!const TB_STR_POOL_LIMIT 0x00580000
!const TB_DISK_BASE 0x00400000
!const TB_MAX_LINE 9999
!const TB_MAX_TEXT 120
!const TB_ARRAY_SIZE 1000
# Token types. Single-character operators keep their ASCII codes.
!const TB_TOKEN_EOF 0
!const TB_TOKEN_NUMBER 1
!const TB_TOKEN_STRING 2
!const TB_TOKEN_IDENT 3
!const TB_TOKEN_AND 256
!const TB_TOKEN_OR 257
!const TB_TOKEN_NOT 258
!const TB_TOKEN_MOD 259
!const TB_TOKEN_ABS 260
!const TB_TOKEN_ASC 261
!const TB_TOKEN_RND 262
!const TB_TOKEN_SGN 263
!const TB_TOKEN_LE 264
!const TB_TOKEN_GE 265
!const TB_TOKEN_NE 266
!const TB_TOKEN_LET 267
!const TB_TOKEN_PRINT 268
!const TB_TOKEN_INPUT 269
!const TB_TOKEN_IF 270
!const TB_TOKEN_THEN 271
!const TB_TOKEN_GOTO 272
!const TB_TOKEN_GOSUB 273
!const TB_TOKEN_RETURN 274
!const TB_TOKEN_FOR 275
!const TB_TOKEN_TO 276
!const TB_TOKEN_NEXT 277
!const TB_TOKEN_RUN 278
!const TB_TOKEN_LIST 279
!const TB_TOKEN_NEW 280
!const TB_TOKEN_CLEAR 281
!const TB_TOKEN_CLS 282
!const TB_TOKEN_END 283
!const TB_TOKEN_STOP 284
!const TB_TOKEN_REM 285
!const TB_TOKEN_HELP 286
!const TB_TOKEN_QUIT 287
!const TB_TOKEN_BYE 288
!const TB_TOKEN_LOAD 289
!const TB_TOKEN_SAVE 290
!const TB_TOKEN_TRON 291
!const TB_TOKEN_TROFF 292
# AST node kinds (expressions).
!const TB_N_NUM 1
!const TB_N_STR 2
!const TB_N_VAR 3
!const TB_N_ARR 4
!const TB_N_BINOP 5
!const TB_N_UNOP 6
!const TB_N_CALL 7
# AST node kinds (statements and print items; c links same-line siblings).
!const TB_S_PRINT 11
!const TB_PI_STR 12
!const TB_PI_EXPR 13
!const TB_PI_SEP 14
!const TB_S_LET 16
!const TB_S_ARRLET 17
!const TB_S_INPUT 18
!const TB_S_GOTO 19
!const TB_S_GOSUB 20
!const TB_S_RETURN 21
!const TB_S_IF 22
!const TB_S_FOR 23
!const TB_S_NEXT 24
!const TB_S_SYS 25
# Binary operator codes.
!const TB_OP_ADD 1
!const TB_OP_SUB 2
!const TB_OP_MUL 3
!const TB_OP_DIV 4
!const TB_OP_IDIV 5
!const TB_OP_MOD 6
!const TB_OP_POW 7
!const TB_OP_EQ 8
!const TB_OP_LT 9
!const TB_OP_GT 10
!const TB_OP_LE 11
!const TB_OP_GE 12
!const TB_OP_NE 13
!const TB_OP_AND 14
!const TB_OP_OR 15
# Unary operator codes and builtin function ids.
!const TB_U_NEG 1
!const TB_U_NOT 2
!const TB_F_ABS 1
!const TB_F_SGN 2
!const TB_F_ASC 3
!const TB_F_RND 4
# System statement codes (RUN/LIST/.../BYE).
!const TB_SYS_RUN 1
!const TB_SYS_LIST 2
!const TB_SYS_NEW 3
!const TB_SYS_CLEAR 4
!const TB_SYS_CLS 5
!const TB_SYS_HELP 6
!const TB_SYS_LOAD 7
!const TB_SYS_SAVE 8
!const TB_SYS_TRON 9
!const TB_SYS_TROFF 10
!const TB_SYS_END 11
!const TB_SYS_BYE 12
# Lexer state.
!var tb_ntok 0
!var tb_str_used 0
# Parser state (token cursor).
!var ps_pos 0
# AST allocators.
!var tb_ast_bump 0
!var tb_str_pool 0
# Program: line directory (#entries), execution cursor, control flags.
!var tb_nlines 0
!var tb_pc 0
!var tb_cur 0
!var tb_stop 0
!var tb_jump 0
!var tb_line 0
!var tb_error 0
!var tb_trace 0
!var tb_gsp 0
!var tb_fsp 0
!var tb_shift 0
!var tb_caps 0
!var tb_disk_pos 0
!var tb_disk_limit 0
# ---------------- line text storage ----------------
!namespace line -
fn tb_line_addr (line) :
TB_LINE_BASE line TB_LINE_STRIDE mul add ret
;
fn tb_copy_line (source destination) :
local i 0
local ch 0
while
source i add ldb >ch
ch 0 neq i TB_MAX_TEXT lt or
if
ch destination i add stb
i 1 add >i
0
else
1
end
end
0 destination i add stb
;
# Parse the leading line number of TB_INPUT_BASE and store/delete the
# remainder as program text. No lexer state is used here.
fn tb_store_input_line () :
local line 0
local pos 0
local ch 0
while
TB_INPUT_BASE pos add ldb >ch
ch 32 eq ch 9 eq and
if pos 1 add >pos 0 else 1 end
end
ch 48 lt ch 57 gt and if retn end
while
TB_INPUT_BASE pos add ldb >ch
ch 48 ge ch 57 le or
if
line 10 mul ch 48 sub add >line
pos 1 add >pos
0
else
1
end
end
line 1 lt line TB_MAX_LINE gt and if
"LINE RANGE" con.tb_print_string con.tb_newline
retn
end
while
TB_INPUT_BASE pos add ldb >ch
ch 32 eq ch 9 eq and
if pos 1 add >pos 0 else 1 end
end
ch 0 eq if
0 line tb_line_addr stb
else
TB_INPUT_BASE pos add line tb_line_addr tb_copy_line
end
;
fn tb_clear_program () :
local line 1
local addr 0
while
line TB_MAX_LINE le
if
line tb_line_addr >addr
addr 0 stb
line 1 add >line
0
else
1
end
end
0 >tb_nlines
;
fn tb_list_program () :
local line 1
local addr 0
while
line TB_MAX_LINE le
if
line tb_line_addr >addr
addr ldb 0 neq
if
line con.tb_print_number
32 con.tb_putc
addr con.tb_print_string
con.tb_newline
end
line 1 add >line
0
else
1
end
end
;
fn tb_help () :
"TinyBASIC: LET, PRINT, INPUT, IF/THEN, GOTO, GOSUB/RETURN," con.tb_print_string con.tb_newline
"FOR/NEXT, RUN, LIST, NEW, CLEAR, LOAD, SAVE, REM, END" con.tb_print_string con.tb_newline
"Expressions: + - * / \\ MOD ^, comparisons, AND/OR/NOT," con.tb_print_string con.tb_newline
"ABS(), ASC(), RND(), SGN(), integer variables A-Z, @(n)." con.tb_print_string con.tb_newline
"PRINT: , tab, ; no newline, #N right-justifies next number." con.tb_print_string con.tb_newline
"LOAD/SAVE use slot 0: LBA and sector count; LOAD clamps sectors to image size." con.tb_print_string con.tb_newline
;
!end
# ---------------- variables ----------------
!namespace vars -
fn tb_get_var (index) :
TB_VAR_BASE index 2 shl add ld ret
;
fn tb_set_var (index value) :
value TB_VAR_BASE index 2 shl add st
;
fn tb_array_store (index value) :
index 0 lt index TB_ARRAY_SIZE ge and if 1 >tb_error retn end
value TB_ARRAY_BASE index 2 shl add st
;
fn tb_clear_variables () :
local i 0
while
i 26 lt
if
0 TB_VAR_BASE i 2 shl add st
i 1 add >i
0
else
1
end
end
0 >i
while
i TB_ARRAY_SIZE lt
if
0 TB_ARRAY_BASE i 2 shl add st
i 1 add >i
0
else
1
end
end
;
fn tb_input_value () :
local pos 0
local sign 1
local value 0
local ch 0
TB_INPUT_BASE ldb >ch
ch 45 eq if
-1 >sign
1 >pos
else
ch 43 eq if 1 >pos end
end
while
TB_INPUT_BASE pos add ldb >ch
ch 48 ge ch 57 le or
if
value 10 mul ch 48 sub add >value
pos 1 add >pos
0
else
1
end
end
pos 0 eq if
TB_INPUT_BASE ldb >value
end
value sign mul ret
;
!end
# ---------------- console output ----------------
!namespace con -
fn tb_putc (char) :
char 13 eq if retn end
char 10 eq if pcg._pcg_ln retn end
char 8 eq if
pcg.pcg_cur_x 0 gt if
pcg.pcg_cur_x 1 sub pcg.pcg_cur_y pcg.locate
32 pcg.putcscr
pcg.pcg_cur_x 1 sub pcg.pcg_cur_y pcg.locate
end
retn
end
char pcg.putcscr
;
fn tb_print_string (ptr) :
local i 0
local ch 0
while
ptr i add ldb >ch
ch 0 neq
if
ch tb_putc
i 1 add >i
0
else
1
end
end
;
fn tb_print_number (number) :
local value 0
number >value
value 0 lt if
45 tb_putc
value neg >value
end
value 10 ge if
value 10 div tb_print_number
end
value 10 mod 48 add tb_putc
;
# Decimal digit count of a number, including a leading minus sign.
fn tb_number_width (number) :
local value 0
local digits 0
number >value
value 0 lt if
value neg >value
1 >digits
end
value 0 eq if 1 ret end
while
value 0 gt
if
digits 1 add >digits
value 10 div >value
0
else
1
end
end
digits ret
;
# Print a number right-justified in a fixed-width field.
fn tb_print_padded (number width) :
local pad 0
local digits 0
number tb_number_width >digits
width digits sub >pad
while
pad 0 gt
if
32 tb_putc
pad 1 sub >pad
0
else
1
end
end
number tb_print_number
;
fn tb_newline () :
10 tb_putc
;
fn tb_report_error () :
tb_line 0 eq if
"? ERROR" tb_print_string tb_newline
else
"? ERROR IN LINE " tb_print_string
tb_line tb_print_number
tb_newline
end
;
!end
# ---------------- keyboard input ----------------
# IRQ3 is the PeC input event FIFO. Acknowledge the latched IRQC
# request so the HALT in tb_read_line wakes on the next event. The
# REPL drains the FIFO after IRET, which drops the source level;
# clearing IRQC pending here keeps the line low so the next event
# latches a fresh rising edge at the CPU input.
fn irq3 () :
8 irqc.IRQC_PENDING sth
retn
;
!namespace kbd -
# Consume PeC keyboard events until Enter. HID shift/caps are tracked locally.
fn tb_read_line (dest capacity) :
local count 0
local event 0
local usage 0
local value 0
local ch 0
0 >tb_shift
while
TB_PEC_STATUS ld 1 and 0 gt
if
TB_PEC_DATA ld >event
event 28 shr 15 and 0 eq if
event 24 shr 15 and 0 eq if
event 16 shr 255 and >usage
event 1 and >value
usage 225 eq usage 229 eq and if
value >tb_shift
else
usage 57 eq value 1 eq or if
tb_caps 0 eq if 1 >tb_caps else 0 >tb_caps end
else
value 1 eq if
usage tb_shift tb_caps tb_key_ascii >ch
ch 13 eq if
1 TB_PEC_POP st
0 dest count add stb
con.tb_newline
count ret
end
ch 8 eq if
count 0 gt if
count 1 sub >count
0 dest count add stb
8 con.tb_putc
end
else
ch 0 neq count capacity lt or if
ch dest count add stb
count 1 add >count
ch con.tb_putc
end
end
end
end
end
end
end
1 TB_PEC_POP st
else
# No event yet: stop the CPU until the PeC input
# IRQ (IRQC source 3) wakes it. irq3 acknowledges
# IRQC; draining the FIFO above drops the source
# level so the next key latches a fresh edge.
halt
end
0
end
count ret
;
# Return ASCII for supported US keyboard HID usage IDs (0 means ignored).
fn tb_key_ascii (usage shifted caps) :
local ch 0
local upper 0
usage 4 ge usage 29 le or if
usage 97 add 4 sub >ch
shifted caps xor >upper
upper 0 neq if ch 32 sub >ch end
ch ret
end
usage 30 ge usage 39 le or if
usage 30 eq if 49 >ch end
usage 31 eq if 50 >ch end
usage 32 eq if 51 >ch end
usage 33 eq if 52 >ch end
usage 34 eq if 53 >ch end
usage 35 eq if 54 >ch end
usage 36 eq if 55 >ch end
usage 37 eq if 56 >ch end
usage 38 eq if 57 >ch end
usage 39 eq if 48 >ch end
shifted 0 neq if
ch 49 eq if 33 >ch end
ch 50 eq if 64 >ch end
ch 51 eq if 35 >ch end
ch 52 eq if 36 >ch end
ch 53 eq if 37 >ch end
ch 54 eq if 94 >ch end
ch 55 eq if 38 >ch end
ch 56 eq if 42 >ch end
ch 57 eq if 40 >ch end
ch 48 eq if 41 >ch end
end
ch ret
end
usage 40 eq if 13 ret end
usage 42 eq if 8 ret end
usage 44 eq if 32 ret end
usage 45 eq if shifted 0 eq if 45 ret else 95 ret end end
usage 46 eq if shifted 0 eq if 61 ret else 43 ret end end
usage 47 eq if shifted 0 eq if 91 ret else 123 ret end end
usage 48 eq if shifted 0 eq if 93 ret else 125 ret end end
usage 49 eq if shifted 0 eq if 92 ret else 124 ret end end
usage 51 eq if shifted 0 eq if 59 ret else 58 ret end end
usage 52 eq if shifted 0 eq if 39 ret else 34 ret end end
usage 54 eq if shifted 0 eq if 44 ret else 60 ret end end
usage 55 eq if shifted 0 eq if 46 ret else 62 ret end end
usage 56 eq if shifted 0 eq if 47 ret else 63 ret end end
usage 53 eq if shifted 0 eq if 96 ret else 126 ret end end
0 ret
;
!end
# ---------------- LEXER: text -> token array ----------------
!namespace lex -
# Tokens live in TB_TOK_BUF as (type, value) pairs; tb_ntok counts them
# including the trailing EOF token. String bytes of the current line go
# to TB_STR_BUF; the parser copies them into the persistent pool.
fn tb_lex_emit (type value) :
local addr 0
tb_ntok TB_TOK_MAX lt if
TB_TOK_BUF tb_ntok 8 mul add >addr
type addr st
value addr 4 add st
tb_ntok 1 add >tb_ntok
else
1 >tb_error
end
;
# Return 0 when the current lowercase identifier equals the supplied string.
fn tb_word_is (text) :
local i 0
local a 0
local b 0
while
TB_WORD_BASE i add ldb >a
text i add ldb >b
a b eq
if
a 0 eq
if
0 ret
end
i 1 add >i
0
else
1
end
end
1 ret
;
# Map the lowercase word in TB_WORD_BASE to a keyword token, or to an
# IDENT token whose value is the first-letter variable index.
fn tb_lex_word_token (ignored1 ignored2) :
local code 0
local index 0
3 >code
"and" tb_word_is 0 eq if 256 >code end
"or" tb_word_is 0 eq if 257 >code end
"not" tb_word_is 0 eq if 258 >code end
"mod" tb_word_is 0 eq if 259 >code end
"abs" tb_word_is 0 eq if 260 >code end
"asc" tb_word_is 0 eq if 261 >code end
"rnd" tb_word_is 0 eq if 262 >code end
"irnd" tb_word_is 0 eq if 262 >code end
"sgn" tb_word_is 0 eq if 263 >code end
"let" tb_word_is 0 eq if 267 >code end
"print" tb_word_is 0 eq if 268 >code end
"input" tb_word_is 0 eq if 269 >code end
"if" tb_word_is 0 eq if 270 >code end
"then" tb_word_is 0 eq if 271 >code end
"goto" tb_word_is 0 eq if 272 >code end
"gosub" tb_word_is 0 eq if 273 >code end
"return" tb_word_is 0 eq if 274 >code end
"for" tb_word_is 0 eq if 275 >code end
"to" tb_word_is 0 eq if 276 >code end
"next" tb_word_is 0 eq if 277 >code end
"run" tb_word_is 0 eq if 278 >code end
"list" tb_word_is 0 eq if 279 >code end
"new" tb_word_is 0 eq if 280 >code end
"clear" tb_word_is 0 eq if 281 >code end
"cls" tb_word_is 0 eq if 282 >code end
"end" tb_word_is 0 eq if 283 >code end
"stop" tb_word_is 0 eq if 284 >code end
"rem" tb_word_is 0 eq if 285 >code end
"help" tb_word_is 0 eq if 286 >code end
"quit" tb_word_is 0 eq if 287 >code end
"bye" tb_word_is 0 eq if 288 >code end
"load" tb_word_is 0 eq if 289 >code end
"save" tb_word_is 0 eq if 290 >code end
"tron" tb_word_is 0 eq if 291 >code end
"troff" tb_word_is 0 eq if 292 >code end
code 3 eq if
TB_WORD_BASE ldb 97 sub >index
code index tb_lex_emit
else
code 0 tb_lex_emit
end
;
# Lex one token at src+pos. Returns the position after the token, or -1
# when the line ends (NUL or '). Emits into TB_TOK_BUF.
fn tb_lex_step (src pos) :
local ch 0
local value 0
local digit 0
local start 0
src pos add ldb >ch
ch 32 eq ch 9 eq and if
pos 1 add ret
end
ch 0 eq ch 39 eq and if
0 0 tb_lex_emit
-1 ret
end
ch 34 eq if
TB_STR_BUF tb_str_used add >start
pos 1 add >pos
while
src pos add ldb >ch
ch 0 neq
if
ch 34 neq
if
tb_str_used 4000 lt if
ch TB_STR_BUF tb_str_used add stb
tb_str_used 1 add >tb_str_used
end
pos 1 add >pos
0
else
1
end
else
1
end
end
0 TB_STR_BUF tb_str_used add stb
tb_str_used 1 add >tb_str_used
2 start tb_lex_emit
tb_error 0 neq if -1 ret end
ch 34 eq if pos 1 add >pos end
pos ret
end
ch 48 ge ch 57 le or if
0 >value
while
src pos add ldb >ch
ch 48 ge ch 57 le or
if
ch 48 sub >digit
value 10 mul digit add >value
pos 1 add >pos
0
else
1
end
end
1 value tb_lex_emit
tb_error 0 neq if -1 ret end
pos ret
end
ch 65 ge ch 90 le or
ch 97 ge ch 122 le or and if
0 >value
while
src pos add ldb >ch
ch 65 ge ch 90 le or
ch 97 ge ch 122 le or and
ch 48 ge ch 57 le or and
if
ch 65 ge ch 90 le or if
ch 32 add >ch
end
value 120 lt if
ch TB_WORD_BASE value add stb
value 1 add >value
end
pos 1 add >pos
0
else
1
end
end
0 TB_WORD_BASE value add stb
3 0 tb_lex_word_token
tb_error 0 neq if -1 ret end
pos ret
end
pos 1 add >pos
ch 60 eq if
src pos add ldb >value
value 61 eq if
264 0 tb_lex_emit
pos 1 add ret
end
value 62 eq if
266 0 tb_lex_emit
pos 1 add ret
end
60 0 tb_lex_emit
tb_error 0 neq if -1 ret end
pos ret
end
ch 62 eq if
src pos add ldb >value
value 61 eq if
265 0 tb_lex_emit
pos 1 add ret
end
62 0 tb_lex_emit
tb_error 0 neq if -1 ret end
pos ret
end
ch 0 tb_lex_emit
tb_error 0 neq if -1 ret end
pos ret
;
fn tb_lex_line (src) :
local pos 0
0 >tb_ntok
0 >tb_str_used
0 >tb_error
while
src pos tb_lex_step >pos
pos 0 ge tb_error 0 eq or
end
;
!end
# ---------------- AST heap ----------------
!namespace ast -
# Nodes are 20 bytes: +0 kind, +4 value, +8 a, +12 b, +16 c.
fn tb_ast_alloc (kind value a b c) :
local ptr 0
tb_ast_bump 20 add TB_AST_LIMIT gt if 1 >tb_error 0 ret end
tb_ast_bump >ptr
kind ptr st
value ptr 4 add st
a ptr 8 add st
b ptr 12 add st
c ptr 16 add st
ptr 20 add >tb_ast_bump
ptr ret
;
fn tb_ast_kind (node) :
node ld ret
;
fn tb_ast_val (node) :
node 4 add ld ret
;
fn tb_ast_a (node) :
node 8 add ld ret
;
fn tb_ast_b (node) :
node 12 add ld ret
;
fn tb_ast_c (node) :
node 16 add ld ret
;
fn tb_ast_set_c (node next) :
next node 16 add st
;
# Copy a NUL-terminated string into the persistent pool.
fn tb_pool_str (src) :
local len 0
local dest 0
local i 0
while
src len add ldb 0 neq
if
len 1 add >len
0
else
1
end
end
tb_str_pool len add 1 add TB_STR_POOL_LIMIT gt if 1 >tb_error 0 ret end
tb_str_pool >dest
0 >i
while
i len le
if
src i add ldb dest i add stb
i 1 add >i
0
else
1
end
end
dest len add 1 add >tb_str_pool
dest ret
;
!end
# ---------------- PARSER: tokens -> AST ----------------
!namespace parse -
fn tb_tok_type (index) :
TB_TOK_BUF index 8 mul add ld ret
;
fn tb_tok_val (index) :
TB_TOK_BUF index 8 mul add 4 add ld ret
;
fn ps_peek () :
ps_pos tb_tok_type ret
;
fn ps_peek_val () :
ps_pos tb_tok_val ret
;
fn ps_advance () :
ps_pos 1 add >ps_pos
;
# 0 when the current token matches (and is consumed), 1 otherwise.
fn ps_match (token) :
ps_peek token eq if
ps_advance
0 ret
end
1 ret
;
fn ps_expect (token) :
token ps_match 0 eq if 0 ret end
1 >tb_error
1 ret
;
fn tb_precedence (token) :
token TB_TOKEN_OR eq if 1 ret end
token TB_TOKEN_AND eq if 2 ret end
token 61 eq if 4 ret end
token 60 eq if 4 ret end
token 62 eq if 4 ret end
token 264 eq if 4 ret end
token 265 eq if 4 ret end
token 266 eq if 4 ret end
token 43 eq if 5 ret end
token 45 eq if 5 ret end
token 42 eq if 6 ret end
token 47 eq if 6 ret end
token 92 eq if 6 ret end
token TB_TOKEN_MOD eq if 6 ret end
token 94 eq if 8 ret end
0 ret
;
# Map an operator token to a TB_OP_* code (0 = not a binary operator).
fn tb_op_code (op) :
op 43 eq if 1 ret end
op 45 eq if 2 ret end
op 42 eq if 3 ret end
op 47 eq if 4 ret end
op 92 eq if 5 ret end
op TB_TOKEN_MOD eq if 6 ret end
op 94 eq if 7 ret end
op 61 eq if 8 ret end
op 60 eq if 9 ret end
op 62 eq if 10 ret end
op 264 eq if 11 ret end
op 265 eq if 12 ret end
op 266 eq if 13 ret end
op TB_TOKEN_AND eq if 14 ret end
op TB_TOKEN_OR eq if 15 ret end
0 ret
;
fn tb_make_binop (op lhs rhs) :
local code 0
op tb_op_code >code
code 0 eq if 1 >tb_error 0 ret end
lhs 0 eq rhs 0 eq and if 1 >tb_error 0 ret end
5 code lhs rhs 0 ast.tb_ast_alloc ret
;
# Parse one builtin call; kind is one of TB_TOKEN_ABS/SGN/ASC/RND.
# The keyword token is current on entry. Flat sequential ifs with early
# returns (no else): this shape is used instead of nested if/else.
fn ps_builtin (kind) :
local arg 0
kind TB_TOKEN_ABS eq if
ps_advance
40 ps_expect drop
tb_error 0 neq if 0 ret end