-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkernel.4th
1636 lines (1321 loc) · 45.1 KB
/
kernel.4th
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
\ This is the file kernel.4, included by the cross compiler.
\ Copyright 1994 L.C. Benschop Eindhoven, The Netherlands.
\ The program is released under the GNU General Public License version 2.
\ There is NO WARRANTY.
\ It is excessively commented as it must serve as an introduction to the
\ construction of Forth compilers.
\ Lines starting with \G are comments that are included in the glossary.
\ All of the file after the next line is cross compiled.
\ Change 2025-01-11: FIxed DEPTH.
CROSS-COMPILE
\ PART 0: BOOT VECTORS.
\ The computer starts execution at address 0.
COLD \ call to cold start location.
WARM \ call to warm start location.
DIV-EX \ address of division exception routine.
BREAK-EX \ address of break handling routine.
TIMER-EX \ address of timer interrupt routine.
\ PART 1: SINGLE OPCODE WORDS
\ Their execution parts contain the single opcode plus the return bit. The
\ compiler compiles the single opcode in-line.
0
OPCODE NOOP ( --- )
\G Do nothing
1
OPCODE SWAP ( x1 x2 --- x2 x1 )
\G Swap the two top items on the stack.
2
OPCODE ROT ( x1 x2 x3 --- x2 x3 x1 )
\G Rotate the three top items on the stack.
3
OPCODE 0= ( x --- f)
\G f is true if and only if x is 0.
4
OPCODE NEGATE ( n1 --- -n1)
\G Negate top number on the stack.
5
OPCODE UM* ( u1 u2 --- ud )
\G Multiply two unsigned numbers, giving double result.
6
OPCODE C@ ( c-addr --- c)
\G Fetch character c at c-addr.
7
OPCODE @ ( a-addr --- x)
\G Fetch cell x at a-addr.
8
OPCODE + ( w1 w2 --- w3)
\G Add the top two numbers on the stack.
9
OPCODE AND ( x1 x2 --- x3)
\G Bitwise and of the top two cells on the stack.
10
OPCODE OR ( x1 x2 --- x3)
\G Bitwise or of the top two cells on the stack.
11
OPCODE XOR ( x1 x2 --- x3)
\G Bitwise exclusive or of the top two cells on the stack.
12
OPCODE U< ( u1 u2 ---- f)
\G f is true if and only if unsigned number u1 is less than u2.
13
OPCODE < ( n1 n2 --- f)
\G f is true if and only if signed number n1 is less than n2.
14
OPCODE LSHIFT ( x1 u --- x2)
\G Shift x1 left by u bits, zeros are added to the right.
15
OPCODE RSHIFT ( x1 u --- x2)
\G Shift x1 right by u bits, zeros are added to the left.
16
OPCODE UM/MOD ( ud u1 --- urem uquot)
\G Divide the unsigned double number ud by u1, giving unsigned quotient
\G and remainder.
17
OPCODE +CY ( n1 n2 cy1 --- n3 cy2)
\G Add n1 and n2 and the carry flag cy1 (must be 0 or 1) giving sum n3
\G and carry flag cy2. (n3 cy2 can be seen as a double number)
18
OPCODE SCAN1 ( x d --- u)
\G Scan x for the first 1 bit. u is the position of that bit (counted
\G from the scan direction) and 32 if x=0. d is the scan direction,
\G 0 is left-to-right, 1 is right-to-left.
19
OPCODE SPECIAL ( x ---)
\G Any of a large number of special instructions, indicated by x.
20
OPCODE DROP ( x ---)
\G Discard the top item on the stack.
21
OPCODE >R ( x ---)
\G Push x on the return stack.
22
OPCODE C!A ( c c-addr --- c-addr)
\G Store character c at c-addr, keep address.
23
OPCODE !A ( x a-addr --- a-addr)
\G Store cell x at a-addr, keep address.
24
OPCODE DUP ( x --- x x )
\G Duplicate the top cell on the stack.
25
OPCODE OVER ( x1 x2 --- x1 x2 x1)
\G Copy the second cell of the stack.
26
OPCODE R@ ( --- x)
\G x is a copy of the top of the return stack.
27
OPCODE R> ( --- x)
\G Pop the top of the return stack and place it on the stack.
28
OPCODE 0 ( --- 0)
\G The constant number 0.
29
OPCODE 1 ( --- 1)
\G The constant number 1.
30
OPCODE 4 ( --- 4)
\G The constant number 4.
31
OPCODE LIT ( --- lit)
\G Push literal on the stack (literal number is in-line).
\ PART 2: RUNTIME PARTS THE VARIOUS DEFINITION CLASSES.
\ Only VARIABLES (or CREATE) need a runtime part in this system.
\ As this is a native code compiler, colon definitions have no runtime
\ part and for CONSTANT it is compiled inline. For variables, a call
\ to DOVAR is compiled. DOVAR pushes the return address (the address
\ where the data of the variable is stored) on the stack.
: DOVAR ( --- a-addr)
\G Runtime part of variables.
R> ;
\ PART 3: SIMPLE DEFINITIONS
\ This is a large class of words, which would be written in machine code
\ on most non-native code systems. Many contain just a few words, so they
\ are implemented as macros.
\ This category contains simple arithmetic and compare words, the runtime
\ parts of DO LOOP and string related words etc, many on which are
\ dependent on each other, so they are in a less than logical order to
\ avoid large numbers of forward references.
M: - ( w1 w2 --- w3 )
\G Subtract the top two numbers on the stack (w2 from w1).
NEGATE + ;
M: = ( x1 x2 --- f)
\G f is true if and only if x1 is equal to x2.
- 0= ;
M: <> ( x1 x2 --- f)
\G f is true if and only if x1 is not equal to x2.
= 0= ;
M: 0< ( n --- f)
\G f is true if and only if n is less than 0.
0 < ;
M: > ( n1 n2 --- f)
\G f is true if and only if the signed number n1 is less than n2.
SWAP < ;
M: 0> ( n --- f)
\G f is true if and only if n is greater than 0.
0 > ;
M: U> ( u1 u2 --- f)
\G f is true if and only if the unsigned number u1 is greater than u2.
SWAP U< ;
M: EMIT ( c ---)
\G Output the character c to the terminal.
1 1 + 32 SPECIAL ;
M: KEY ( --- c)
\G Input the character c from the terminal.
1 32 SPECIAL ;
M: BYE ( ---)
\G Terminate the execution of SOD-32 Forth, return to OS.
0 32 SPECIAL ;
: CR ( --- )
\G Output a newline to the terminal.
13 EMIT 10 EMIT ;
VARIABLE S0 ( --- a-addr)
\G Variable that holds the bottom address of the stack.
VARIABLE R0 ( --- a-addr)
\G Variable that holds the bottom address of the return stack.
M: SP@ ( --- a-addr)
\G Return the address of the stack pointer (before SP@ was executed).
0 SPECIAL ;
M: SP! ( a-addr ---)
\G Set the stack pointer to a-addr.
1 SPECIAL ;
M: RP@ ( --- a-addr)
\G Return the address of the return stack pointer.
02 SPECIAL ;
M: RP! ( a-addr ---)
\G Set the return stack pointer to a-addr.
03 SPECIAL ;
M: 2* ( w1 --- w2)
\G Multiply w1 by 2.
1 LSHIFT ;
: 2/ ( n1 --- n2)
\G Divide signed number n1 by 2.
DUP $80000000 AND SWAP 1 RSHIFT OR ;
: DEPTH ( --- n )
\G n is the number of cells on the stack (before DEPTH was executed).
SP@ S0 @ SWAP - 2 RSHIFT ;
\ The DO LOOP related words use the return stack. The top of the
\ return stack is the loop counter (I) and the next cell is the limit.
\ (LOOP) and (+LOOP) are followed by an inline loop start address.
\ (?DO) and (LEAVE) are followed by an inline leave address.
\ The inline parameters are accessed through the return stack.
\ They can 'jump' by returning to a different address.
\ These words are called 'subroutines', not macros.
\ To access the loop parameters on the return stack, the DO LOOP words must
\ first pop their OWN return address!
: (DO) ( n1 n2 ---)
\G Runtime part of DO.
R> ROT ROT SWAP >R >R >R ;
: (?DO) ( n1 n2 ---)
\G Runtime part of ?DO
OVER OVER - IF R> ROT ROT SWAP >R >R 4 + >R
ELSE DROP DROP R> @ >R \ Jump to leave address if equal
THEN ;
M: I ( --- n )
\G Return the counter (index) of the innermost DO LOOP
R@ ;
: J ( --- n)
\G Return the counter (index) of the next loop outer to the innermost DO LOOP
RP@ 12 + @ ;
: (LEAVE) ( --- )
\G Runtime part of LEAVE
R> @ R> DROP R> DROP >R ; \ Remove loop parameters and replace top of ret
\ stack by leave address.
M: UNLOOP ( --- )
\G Remove one set of loop parameters from the return stack.
R> DROP R> DROP ;
: (LOOP) ( ---)
\G Runtime part of LOOP
R> R> 1 + DUP R@ = \ Add 1 to count and compare to limit.
IF
R> DROP DROP 4 + >R \ Discard parameters and skip leave address.
ELSE
>R @ >R \ Repush counter and jump to loop start address.
THEN ;
: (+LOOP) ( n ---)
\G Runtime part of +LOOP
\ Very similar to (LOOP), but the compare condition is different.
\ exit if ( oldcount - lim < 0) xor ( newcount - lim < 0).
R> SWAP R> DUP R@ - ROT ROT + DUP R@ - ROT XOR 0 <
IF R> DROP DROP 4 + >R
ELSE >R @ >R THEN ;
M: -1 ( --- -1)
\G The constant number -1.
1 NEGATE ;
M: COUNT ( c-addr1 --- c-addr2 c)
\G c-addr2 is the next address after c-addr1 and c is the character
\G stored at c-addr1.
\G This word is intended to be used with 'counted strings' where the
\G first character indicates the length of the string.
DUP 1 + SWAP C@ ;
: TYPE ( c-addr1 u --- )
\G Output the string starting at c-addr and length u to the terminal.
DUP IF 0 DO DUP I + C@ EMIT LOOP DROP ELSE DROP DROP THEN ;
M: ALIGNED ( c-addr --- a-addr )
\G a-addr is the first aligned address after c-addr.
4 1 NEGATE + + 4 NEGATE AND ;
: (.") ( --- )
\G Runtime part of ."
\ This expects an in-line counted string.
R> COUNT OVER OVER TYPE + ALIGNED >R ;
: (S") ( --- c-addr u )
\G Runtime part of S"
\ It returns address and length of an in-line counted string.
R> COUNT OVER OVER + ALIGNED >R ;
00
CONSTANT FALSE ( --- 0)
\G Constant 0, indicates FALSE
-01
CONSTANT TRUE ( --- -1)
\G Constant -1, indicates TRUE
32
CONSTANT BL ( --- 32 )
\G Constant 32, the blank character
M: PICK ( u --- x)
\G place a copy of stack cell number u on the stack. 0 PICK is DUP, 1 PICK
\G is OVER etc.
1 + 1 1 + LSHIFT SP@ + @ ;
M: C! ( c c-addr --- )
\G Store character c at c-addr
C!A DROP ;
M: ! ( x a-addr --- )
\G Store cell x at a-addr
!A DROP ;
M: OFF ( a-addr ---)
\G Store FALSE at a-addr.
0 SWAP ! ;
M: ON ( a-addr ---)
\G Store TRUE at a-addr.
-1 SWAP ! ;
M: 1+ ( w1 --- w2 )
\G Add 1 to the top of the stack.
1 + ;
M: 1- ( w1 --- w2)
\G Subtract 1 from the top of the stack.
1 - ;
M: INVERT ( x1 --- x2)
\G Invert all the bits of x1 (one's complement)
-1 XOR ;
\ The next few words manipulate addresses in a system-independent way.
\ Use CHAR+ instead of 1+ and it will be portable to systems where you
\ have to add something different from 1.
M: CHAR+ ( c-addr1 --- c-addr2)
\G c-addr2 is the next character address after c-addr1.
1 + ;
M: CHARS ( n1 --- n2)
\G n2 is the number of address units occupied by n1 characters.
; \ A no-op.
M: CHAR- ( c-addr1 --- c-addr2)
\G c-addr2 is the previous character address before c-addr1.
1 - ;
M: CELL+ ( a-addr1 --- a-addr2)
\G a-addr2 is the address of the next cell after a-addr2.
4 + ;
M: CELLS ( n2 --- n1)
\G n2 is the number of address units occupied by n1 cells.
1 1 + LSHIFT ;
M: CELL- ( a-addr1 --- a-addr2)
\G a-addr2 is the address of the previous cell before a-addr1.
4 - ;
M: +! ( w a-addr ---)
\G Add w to the contents of the cell at a-addr.
DUP @ ROT + SWAP !A DROP ;
\ Double numbers occupy two cells in memory and on the stack.
\ The most significant half on the number is in the first memory
\ cell or in the top cell on the stack (which is also the first address).
M: 2@ ( a-addr --- d )
\G Fetch double number d at a-addr.
DUP 4 + @ SWAP @ ;
M: D+ ( d1 d2 --- d3)
\G Add the double numbers d1 and d2.
>R ROT 0 +CY ROT + R> + ;
M: 2! ( d a-addr --- )
\G Store the double number d at a-addr.
!A 4 + !A DROP ;
M: 2DUP ( d --- d d)
\G Duplicate the top double number on the stack.
OVER OVER ;
M: 2DROP ( d --- )
\G Discard the top double number on the stack.
DROP DROP ;
: ?DUP ( n --- 0 | n n)
\G Duplicate the top cell on the stack, but only if it is nonzero.
DUP IF DUP THEN ;
: MIN ( n1 n2 --- n3)
\G n3 is the minimum of n1 and n2.
OVER OVER > IF SWAP THEN DROP ;
: MAX ( n1 n2 --- n3)
\G n3 is the maximum of n1 and n2.
OVER OVER < IF SWAP THEN DROP ;
M: DNEGATE ( d1 --- d2)
\G Negate the top double number on the stack.
>R NEGATE R> NEGATE OVER 0= 0= + ;
: ABS ( n --- u)
\G u is the absolute value of n.
DUP 0< IF NEGATE THEN ;
: DABS ( d --- ud)
\G ud is the absolute value of d.
DUP 0< IF DNEGATE THEN ;
: SM/REM ( d n1 --- nrem nquot )
\G Divide signed double number d by single number n1, giving quotient and
\G remainder. Round towards zero, remainder has same sign as dividend.
2DUP XOR >R OVER >R \ Push signs of quot and rem.
ABS >R DABS R>
UM/MOD
SWAP R> 0< IF NEGATE THEN SWAP
R> 0< IF NEGATE THEN ;
: FM/MOD ( d n1 --- nrem nquot )
\G Divide signed double number d by single number n1, giving quotient and
\G remainder. Round always down (floored division),
\G remainder has same sign as divisor.
DUP >R OVER OVER XOR >R
SM/REM
OVER R> 0< AND IF SWAP R@ + SWAP 1 - THEN R> DROP ;
: M* ( n1 n2 --- d )
\G Multiply the signed numbers n1 and n2, giving the signed double number d.
2DUP XOR >R ABS SWAP ABS UM* R> 0< IF DNEGATE THEN ;
M: * ( w1 w2 --- w3)
\G Multiply single numbers, signed or unsigned give the same result.
UM* DROP ;
: */MOD ( n1 n2 n3 --- nrem nquot)
\G Multiply signed numbers n1 by n2 and divide by n3, giving quotient and
\G remainder. Intermediate result is double.
>R M* R> FM/MOD ;
: */ ( n1 n2 n3 --- n4 )
\G Multiply signed numbers n1 by n2 and divide by n3, giving quotient n4.
\G Intermediate result is double.
*/MOD SWAP DROP ;
M: S>D ( n --- d)
\G Convert single number to double number.
DUP 0< ;
: /MOD ( n1 n2 --- nrem nquot)
\G Divide signed number n1 by n2, giving quotient and remainder.
SWAP S>D ROT FM/MOD ;
: / ( n1 n2 --- n3)
\G n3 is n1 divided by n2.
/MOD SWAP DROP ;
: MOD ( n1 n2 --- n3)
\G n3 is the remainder of n1 and n2.
/MOD DROP ;
: EXIT ( ---)
\G Exit the definition that calls EXIT.
R> DROP ;
: EXECUTE ( xt ---)
\G Execute the word with execution token xt.
\ Return from EXECUTE goes to xt pushed on the ret stack by >R, return from
\ the word x1 returns to definition that calls EXECUTE
>R ;
: ?THROW ( f n --- )
\G Perform n THROW if f is nonzero.
SWAP IF THROW ELSE DROP THEN ;
\ PART 4: NUMERIC OUTPUT WORDS.
VARIABLE BASE ( --- a-addr)
\G Variable that contains the numerical conversion base.
VARIABLE DP ( --- a-addr)
\G Variable that contains the dictionary pointer. New space is allocated
\G from the address in DP
VARIABLE HLD ( --- a-addr)
\G Variable that holds the address of the numerical output conversion
\G character.
VARIABLE DPL ( --- a-addr)
\G Variable that holds the decimal point location for numerical conversion.
: DECIMAL ( --- )
\G Set numerical conversion to decimal.
10 BASE ! ;
: HEX ( --- )
\G Set numerical conversion to hexadecimal.
16 BASE ! ;
: SPACE ( ---)
\G Output a space to the terminal.
32 EMIT ;
: SPACES ( u --- )
\G Output u spaces to the terminal.
?DUP IF 0 DO SPACE LOOP THEN ;
: HERE ( --- c-addr )
\G The address of the dictionary pointer. New space is allocated here.
DP @ ;
: PAD ( --- c-addr )
\G The address of a scratch pad area. Right below this address there is
\G the numerical conversion buffer.
DP @ 84 + ;
: MU/MOD ( ud u --- urem udquot )
\G Divide unsigned double number ud by u and return a double quotient and
\G a single remainder.
>R 0 R@ UM/MOD R> SWAP >R UM/MOD R> ;
\ The numerical conversion buffer starts right below PAD and grows down.
\ Characters are added to it from right to left, as as the div/mod algorithm
\ to convert numbers to an arbitrary base produces the digits from right to
\ left.
: HOLD ( c ---)
\G Insert character c into the numerical conversion buffer.
1 NEGATE HLD +! HLD @ C! ;
: # ( ud1 --- ud2)
\G Extract the rightmost digit of ud1 and put it into the numerical
\G conversion buffer.
BASE @ MU/MOD ROT DUP 9 > IF 7 + THEN 48 + HOLD ;
: #S ( ud --- 0 0 )
\G Convert ud by repeated use of # until ud is zero.
BEGIN # OVER OVER OR 0= UNTIL ;
: SIGN ( n ---)
\G Insert a - sign in the numerical conversion buffer if n is negative.
0< IF 45 HOLD THEN ;
: <# ( --- )
\G Reset the numerical conversion buffer.
PAD HLD ! ;
: #> ( ud --- addr u )
\G Discard ud and give the address and length of the numerical conversion
\G buffer.
DROP DROP HLD @ PAD OVER - ;
: D. ( d --- )
\G Type the double number d to the terminal.
SWAP OVER DABS <# #S ROT SIGN #> TYPE SPACE ;
: U. ( u ---)
\G Type the unsigned number u to the terminal.
0 D. ;
: . ( n ---)
\G Type the signed number n to the terminal.
S>D D. ;
\ PART 5: MEMORY BLOCK MOVE AND RELATED WORDS.
: CMOVE ( c-addr1 c-addr2 u --- )
\G Copy u bytes starting at c-addr1 to c-addr2, proceeding in ascending
\G order.
DUP IF >R
BEGIN
OVER C@ SWAP C!A 1 + SWAP 1 + SWAP
R> 1 - DUP >R 0=
UNTIL
R>
THEN
DROP DROP DROP
;
: CMOVE> ( c-addr1 c-addr2 u --- )
\G Copy a block of u bytes starting at c-addr1 to c-addr2, proceeding in
\G descending order.
DUP IF >R R@ + 1 - SWAP R@ + 1 - SWAP
BEGIN
OVER C@ SWAP C!A 1 - SWAP 1 - SWAP
R> 1 - DUP >R 0=
UNTIL
R>
THEN
DROP DROP DROP
;
\ It's here because it needs CMOVE>
: ROLL ( u ---)
\G Move stack cell number u to the top. 1 ROLL is SWAP, 2 ROLL is ROT etc.
1 + 1 1 + LSHIFT DUP SP@ + 4 + @ SWAP
SP@ 4 + DUP 4 + ROT CMOVE> DROP ;
: MOVE ( c-addr1 c-addr2 u --- )
\G Copy a block of u bytes starting at c-addr1 to c-addr2. Order is such
\G that partially overlapping blocks are copied intact.
>R OVER OVER U< IF R> CMOVE> ELSE R> CMOVE THEN ;
: FILL ( c-addr u c ---)
\G Fill a block of u bytes starting at c-addr with character c.
OVER IF >R
BEGIN
R@ ROT C!A 1 + SWAP
1 - DUP 0=
UNTIL
R>
THEN
DROP DROP DROP
;
: 2OVER ( d1 d2 --- d1 d2 d1)
\G Take a copy of the second double number of the stack and push it on the
\G stack.
03 PICK 03 PICK ;
: 2SWAP ( d1 d2 --- d2 d1)
\G Swap the top two double numbers on the stack.
03 ROLL 03 ROLL ;
\ PART 6: FILE ACCESS WORDS.
00
CONSTANT R/O ( --- mode)
\G Read only file access mode.
02
CONSTANT W/O ( --- mode)
\G Write only file access mode.
04
CONSTANT R/W ( --- mode)
\G Read write file access mode.
: BIN ( mode1 --- mode2)
\G Modify the R/O W/O or R/W mode so that it applies to binary files.
1 + ;
M: OSCALL ( n ---)
\G Call the operating system service number n.
32 SPECIAL ;
\ All open files are known by a file-id, which is a number between 1 and 20.
\ All file operations return an io result (ior), which is zero if the
\ operation was successful and nonzero in the case of an error.
\ File ID's must be nonzero, therefore 1 higher than those used
\ by the OS.
: OPEN-FILE ( c-addr u mode --- fid ior)
\G Open the file with the name starting at c-addr and with length u.
\G File must already exist unless open mode is write only.
\G Return the file-ID and the IO result. (ior=0 if success)
7 OSCALL SWAP 1+ SWAP ;
: CREATE-FILE ( c-addr u mode --- fid ior)
\G Create a new file with the name starting at c-addr with length u.
\G Return the file-ID and the IO result. (ior=0 if success)
1 AND 02 + OPEN-FILE ;
: CLOSE-FILE ( fid --- ior)
\G Close the open file described by fid.
1- 8 OSCALL ;
: WRITE-LINE ( c-addr u fid --- ior)
\G Write the string at addr c-addr with length u to the file described by
\G fid. Append the end of line character to it.
1- 9 OSCALL ;
: READ-LINE ( c-addr u1 fid --- u2 flag ior)
\G Read a line from the file described by fid to a buffer at c-addr that
\G is u1+2 characters long. The line is at most u1 characters long.
\G flag is 0 at the end of file (no line could be read) TRUE otherwise.
\G (ior is 0 in this case.)
\G n2 is the length of the line read,
1- 10 OSCALL ;
: WRITE-FILE ( c-addr u fid --- ior)
\G Write a block of u bytes starting at c-addr to the file described by
\G fid. (file must be opened in BIN mode).
1- 11 OSCALL ;
: READ-FILE ( c-addr u1 fid --- u2 ior)
\G Read a block of u1 bytes starting at c-addr from the file described by
\G fid. (file must be opened in BIN mode). u2 is the number of bytes
\G actually read. This is less than u1 at the end of the file.
1- 12 OSCALL ;
: DELETE-FILE ( c-addr u --- ior)
\G Delete the file with a name starting at c-addr with length u.
13 OSCALL ;
: REPOSITION-FILE ( ud fid --- ior)
\G Set the file position of the open file described by fid to ud.
1- 14 OSCALL ;
: FILE-POSITION ( fid --- ud ior)
\G ud is the file position of the open file described by fid.
1- 15 OSCALL ;
: SYSTEM ( c-addr u --- ior)
\G Execute a the string at c-addr with length u as a system command.
16 OSCALL ;
: FILE-SIZE ( fid --- ud ior)
\G ud is the file size of the file described by fid.
1- 17 OSCALL ;
\ PART 7: SOURCE INPUT WORDS.
VARIABLE TERMMODE
: SETRAW ( --- )
\G Make the input raw mode.
1 4 32 SPECIAL 1 TERMMODE ! ;
: NONRAW ( --- )
\G Make the input nonraw.
0 4 32 SPECIAL 0 TERMMODE ! ;
: ACCEPT ( c-addr n1 --- n2 )
\G Read a line from the terminal to a buffer starting at c-addr with
\G length n1. n2 is the number of characters read,
\ key is not echoed because it is 'cooked' mode. Backspace processing is
\ already in place for easy adaptation to 'raw' input.
>R 0
BEGIN
KEY DUP 8 = OVER 127 = OR
IF \ Backspace/delete
DROP DUP IF 1- TERMMODE @ IF 8 EMIT THEN THEN
ELSE
DUP 10 = OVER 13 = OR
IF \ CR/LF
DROP SWAP DROP R> DROP SPACE EXIT
ELSE
TERMMODE @ IF DUP EMIT THEN
OVER R@ - IF
>R OVER OVER + R> SWAP C! 1+
ELSE
DROP
THEN
THEN
THEN
0 UNTIL
;
VARIABLE TIB ( --- addr)
\G is the standard terminal input buffer.
80 CHARS-T ALLOT-T
VARIABLE SPAN ( --- addr)
\G This variable holds the number of characters read by EXPECT.
VARIABLE #TIB ( --- addr)
\G This variable holds the number of characters in the terminal input buffer.
VARIABLE >IN ( --- addr)
\G This variable holds an index in the current input source where the next word
\G will be parsed.
VARIABLE SID ( --- addr)
\G This variable holds the source i.d. returned by SOURCE-ID.
VARIABLE SRC ( --- addr)
\G This variable holds the address of the current input source.
VARIABLE #SRC ( --- addr)
\G This variable holds the length of the current input source.
VARIABLE LOADLINE ( --- addr)
\G This variable holds the line number in the file being included.
: EXPECT ( c-addr u --- )
\G Read a line from the terminal to a buffer at c-addr with length u.
\G Store the length of the line in SPAN.
ACCEPT SPAN ! ;
: QUERY ( --- )
\G Read a line from the terminal into the terminal input buffer.
TIB 80 ACCEPT #TIB ! ;
: SOURCE ( --- addr len)
\G Return the address and length of the current input source.
SRC @ #SRC @ ;
: SOURCE-ID ( --- sid)
\G Return the i.d. of the current source i.d., 0 for terminal, -1
\G for EVALUATE and positive number for INCLUDE file.
SID @ ;
: REFILL ( --- f)
\G Refill the current input source when it is exhausted. f is
\G true if it was successfully refilled.
SOURCE-ID -1 = IF
0 \ Not refillable for EVALUATE
ELSE
SOURCE-ID IF
SRC @ 256 SOURCE-ID READ-LINE -37 ?THROW
SWAP #SRC ! 0 >IN !
#SRC @ IF SOURCE OVER + SWAP DO I C@ 9 = IF 32 I C! THEN LOOP THEN
1 LOADLINE +!
\ Change tabs to space.
\ flag from READ-LINE is returned (no success at EOF)
ELSE
QUERY #TIB @ #SRC ! 0 >IN ! -1 \ Always successful from terminal.
THEN
THEN
;
: SCAN ( c-addr1 u1 c --- c-addr2 u2 )
\G Find the first occurrence of character c in the string c-addr1 u1
\G c-addr2 u2 is the remaining part of the string starting with that char.
\G It is a zero-length string if c was not found.
BEGIN
OVER
WHILE
ROT DUP C@ >R OVER R> =
IF
ROT ROT DROP EXIT
THEN
1+ ROT ROT SWAP 1- SWAP
REPEAT DROP
;
: SKIP ( c-addr1 u1 c --- c-addr2 u2 )
\G Find the first character not equal to c in the string c-addr1 u1
\G c-addr2 u2 is the remaining part of the string starting with the
\G nonmatching char. It is a zero-length string if no other chars found.
BEGIN
OVER
WHILE
ROT DUP C@ >R OVER R> -
IF
ROT ROT DROP EXIT
THEN
1+ ROT ROT SWAP 1- SWAP
REPEAT DROP
;
: PARSE ( c --- addr len )
\G Find a character sequence in the current source that is delimited by
\G character c. Adjust >IN to 1 past the end delimiter character.
>R SOURCE >IN @ - SWAP >IN @ + R> OVER >R >R SWAP
R@ SKIP OVER R> SWAP >R SCAN IF 1 >IN +! THEN
DUP R@ - R> SWAP
ROT R> - >IN +! ;
: PLACE ( addr len c-addr --- )
\G Place the string starting at addr with length len at c-addr as
\G a counted string.
OVER OVER C!
1+ SWAP CMOVE ;
: WORD ( c --- addr )
\G Parse a character sequence delimited by character c and return the
\G address of a counted string that is a copy of it. The counted
\G string is actually placed at HERE. The character after the counted
\G string is set to a space.
PARSE HERE PLACE HERE BL HERE COUNT + C! ;
VARIABLE CAPS ( --- a-addr)
\G This variable contains a nonzero number if input is case insensitive.
: UPPERCASE? ( --- )
\G Convert the parsed word to uppercase is CAPS is true.
CAPS @ HERE C@ AND IF
HERE COUNT 0 DO
DUP I + C@ DUP 96 > SWAP 123 < AND IF DUP I + DUP C@ 32 - SWAP C! THEN
LOOP DROP
THEN
;
\ PART 8: INTERPRETER HELPER WORDS
\ First we need FIND and related words.
\ Each word list consists of a number of linked list of definitions (number
\ is a power of 2). Hashing
\ is used to speed up dictionary search. All names in the dictionary
\ are at aligned addresses and FIND is optimized to compare one 4-byte
\ cell at a time.
\ Dictionary definitions are built as follows:
\
\ LINK field: 1 cell, aligned, contains name field of previous word in thread.
\ NAME field: counted string of at most 31 characters.
\ bits 5-7 of length byte have special meaning.
\ 7 is always set to mark start of name ( for >NAME)
\ 6 is set if the word is immediate.
\ 5 is set if the word is a macro.
\ CODE field: first aligned address after name, is execution token for word.
\ here the executable code for the word starts. (is 1 cell for
\ variables etc.)
\ PARAMETER field: (body) Contains the data of constants and variables etc.
VARIABLE NAMEBUF ( --- a-addr)
\G An aligned buffer that holds a copy of the name that is searched.
28 ALLOT-T
VARIABLE FORTH-WORDLIST ( --- addr)
32 CELLS-T ALLOT-T
\G This array holds pointers to the last definition of each thread in the Forth
\G word list.
VARIABLE LAST ( --- addr)
\G This variable holds a pointer to the last definition created.
VARIABLE CONTEXT 28 ALLOT-T ( --- a-addr)
\G This variable holds the addresses of up to 8 word lists that are
\G in the search order.
VARIABLE #ORDER ( --- addr)
\G This variable holds the number of word list that are in the search order.
VARIABLE CURRENT ( --- addr)
\G This variable holds the address of the word list to which new definitions
\G are added.
: HASH ( c-addr u #threads --- n)
\G Compute the hash function for the name c-addr u with the indicated number
\G of threads.
>R OVER C@ 1 LSHIFT OVER 1 > IF ROT CHAR+ C@ 2 LSHIFT XOR ELSE ROT DROP
THEN XOR
R> 1- AND
;
: NAME>BUF ( c-addr u ---)