forked from dan4thewin/FreeForth2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ff.help
2197 lines (1973 loc) · 114 KB
/
ff.help
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
\ ff.help
\ This help file begins with the help source code, followed by the help
\ contents, composed of help records, each starting with a line beginning
\ with a keyword (help entry), and ending with an empty line, with maybe
\ other lines in between, each beginning with at least one space.
\ -- @ # ; we're called from help` (defined in ff.ff)
H@ @ execute \ forget marker defined by needed called by help`
swap dup>r swap dup>r BEGIN 2drop lnparse \ -- a n | == @ # ; n=0:EOF
0= IF dropr> swap dropr> swap ;THEN \ -- @ # ; not found
over 2r $- 0= drop TILL over r + c@ $BF& $20- 0= drop UNTIL 2rdrop
\ keyword+space or keyword+backquote match at beginning of line:
\ display upto empty line (Linux:LF Windows:CR,LF):
START lnparse ENTER 2dup type cr + >in@ over- 2* + 1- c@ 10- 0= drop UNTIL
0 EOF` \ -- 0 ; found, skip to end of file
;
help` ( <name> -- ) displays name's related help (try: help bye)
Items with no stack diagram are concepts, or hidden non-compilable words.
Beginner: start with "help DATAstack" to understand "stack diagrams".
Forth geek: look at "conditionals" and "flow-control" which are unusual,
and at "anonymous" and "backquote" which are specific to FreeForth.
Curious: start with the comments at beginning of file "ff.asm".
see also: man win32.hlp bye ff.help ff.ff
------------------
DISTRIBUTION FILES
hanoi is an example source file animating the Hanoi Tours problem/game
hello is an example auto-executable source file explaining how to make
auto-executable "FreeForth-script" files for Linux, such as hanoi and bed
bed.ff is the auto-executable source code of a minimalist Blocks-EDitor
inspired from RetroForth
see also: ff.asm ff.boot ff.ff ff.help hello hanoi
ff.help is the FreeForth help file, expected by "help`" (defined in ff.ff)
to be found in FreeForth root directory (where ff.ff is found at boot time).
Users may create their own help files following the same simple text format,
and add them into "help`" definition, as explained in commented sample line.
see also: ff.asm ff.boot ff.ff bed.ff hello
ff.asm is the assembler source file defining FreeForth kernel;
read its contents for implementation and compilation notes.
see also: fflin.asm ffwin.asm
ff.boot is the FreeForth kernel extensions inlined source file
assembled by fasm in the ff executable and compiled at boot time.
see also: fflin.boot ffwin.boot
ff.ff is the FreeForth kernel extensions separate source file
automatically compiled at boot time after ff.boot if it is found.
The environment variable FFPATH specifies a colon-separated list of
directories that are searched, followed by "/usr/local/share/ff" and ".".
full.ff has additional FreeForth kernel extensions for special uses.
Load it as desired (i.e. needs full.ff).
Edit full.ff to enable([1]) or disable([0]) its optional sections,
or to add your own startup code, such as: needs mystartup.ff
see also: ff.asm ff.boot ff.help boot bed.ff hello needs
fflin.asm is the Linux-port main assembler source file
fflinio.asm is the assembler source file for Linux-specific I/O
fflin.boot is the FreeForth source file for Linux-specific boot
see also: fflin.asm fflinio.asm boot ffwin.asm
ffwin.asm is the Windows-port main assembler source file
ffwinio.asm is the assembler source file for Windows-specific I/O
ffwin.boot is the FreeForth source file for Windows-specific boot
see also: ffwin.asm ffwinio.asm boot fflin.asm
fasm is the free-software assembler "Flat-Assembler" used by FreeForth to
generate both Linux and Windows executables, thanks Tomasz Grysztar!
see also: http://flatassembler.net
--------------------
DATA AND CALL STACKS
DATAstack is a memory space analog to the CALLstack, but separate
(most other languages use a single stack to store "stack frames"
containing subroutine arguments, return address to caller, saved
processor registers, and local variables).
The DATAstack is a 32 bits wide Last-In-First-Out stack, where the "push"
operation pre-decrements by 4 the stack-pointer before writing to memory,
and the "pop" operation post-increments by 4 the stack-pointer after
reading from memory.
The DATAstack-pointer is stored in the i386 register "eax" or "esp",
depending on the state of the compiler CALLbit (SC bit0, see "SC").
Almost all Forth words (usually called "subroutines" in other languages)
pop their input argument(s) from the DATAstack, and push their result(s)
back onto it (and maybe do some other useful side effect).
This is documented for each word with a "stack diagram" composed of two
parts separated by "--": the left part shows, and names, the number of
DATAstack cells used as inputs, the right part the cells used for outputs;
in both parts, the rightmost item represents the DATAstack top cell.
Some conventions are often used to indicate DATAstack items types:
. c represents a byte value ( 8 bits stored in a 32 bits DATAstack cell)
. w represents a word value (16 bits stored in a 32 bits DATAstack cell)
. n represents a long value (32 bits stored in a 32 bits DATAstack cell)
. u represents an unsigned long value
. @ represents a memory address
. # represents a string size
. ? represents a boolean, FALSE if null, TRUE otherwise
see also: CALLstack TOS NOS SC
TOS is an abbrev for Top Of Stack: it refers to the DATAstack top cell
In the DATAstack diagrams, this is always the rightmost cell
TOS is cached into a 386 register, either "ebx" or "edx" depending on
the state of the compiler SWAPbit (SC bit1, see "SC")
NOS is an abbrev for Next Of Stack: it refers to the DATAstack 2nd cell,
just under TOS, also just left of TOS in the DATAstack diagrams
NOS is cached into a 386 register, either "edx" or "ebx" depending on
the state of the compiler SWAPbit (SC bit1, see "SC")
see also: TOS DATAstack SC
CALLstack is a memory space allocated by the operating system mainly to save
return addresses from subroutine calls; it is also used to store loop
counters (see "for" and "next"), and may be used for temporary storage
(see the inter-stack transfer words ">r", "r>", etc.), in which case this
is documented for each such word by a second "stack diagram" (see DATAstack)
composed of two parts separated by "==" (instead of "--" for the DATAstack)
with the left part showing the number of CALLstack cells used as inputs,
and the right part the cells used for outputs; as for the DATAstack, in
both parts the rightmost item represents the CALLstack top cell.
The CALLstack-pointer is stored in the i386 register "esp" or "eax",
depending on the state of the compiler CALLbit (SC bit0, see "SC").
see also: DATAstack TOS NOS SC
---------------------
CODE GENERATION BASIS
rst ( -- ) tests and resets SC, restoring default registers allocation:
if SC.bit1(SWAPbit)=1: clear SWAPbit and generates "xchg ebx,edx" (87DA)
if SC.bit0(CALLbit)=1: clear CALLbit and generates "xchg eax,esp" (94)
typically, "rst" must be used before generating any "call" or "ret"
>SC ( n -- ) sets SC as specified by n, maybe changing registers allocation:
if SC.bit1(SWAPbit) changes: generates "xchg ebx,edx" (87DA)
if SC.bit0(CALLbit) changes: generates "xchg eax,esp" (94)
typically, ">SC" is used before a control-thread join (such as "THEN")
to restore the state "SC" had before a control-thread split (such as "IF")
SC ( -- @ ) compiler state variable: stores current register-allocations
bit1(SWAPbit):NOS,TOS bit0(CALLbit):callSP,dataSP
0: -- edx ebx 0: esp eax
1: -- ebx edx 1: eax esp
see also: rst >SC >C0 >C1 >S0 >S1 swap ff.asm
>S1 ( -- ) tests and sets SC SWAPbit, such that NOS=ebx and TOS=edx
if the SWAPbit was reset, generates "xchg ebx,edx" (87DA)
>S0 ( -- ) tests and resets SC SWAPbit, such that NOS=edx and TOS=ebx
if the SWAPbit was set, generates "xchg ebx,edx" (87DA)
>C1 ( -- ) tests and sets SC CALLbit, such that callSP=eax and dataSP=esp
if the CALLbit was reset, generates "xchg eax,esp" (94)
>C0 ( -- ) tests and resets SC CALLbit, such that callSP=esp and dataSP=eax
if the CALLbit was set, generates "xchg eax,esp" (94)
see also: >S1 >S0 >C1 >C0 SC >SC swap ff.asm
c04 ( -- ) exchanges i386 registers eax and esp in last compiled word:
increments by 2 the compilation pointer (ebp), and toggles bit2
of byte at [ebp-1] if the CALLbit is set
s09 ( -- ) exchanges srce and dest regs ebx/edx in last compiled mod/rm byte:
increments by 2 the compilation pointer (ebp),
and toggles bit3 and bit0 at [ebp-1] if the SWAPbit was set
s08 ( -- ) exchanges i386 srce regs ebx/edx in last compiled mod/rm byte:
increments by 2 the compilation pointer (ebp),
and toggles bit3 at [ebp-1] if the SWAPbit was set
s01 ( -- ) same as s1 except increments by 2 the compilation pointer
s1 ( -- ) exchanges i386 dest regs ebx/edx in last compiled mod/rm byte:
increments by 1 the compilation pointer (ebp),
and toggles bit0 at [ebp-1] if the SWAPbit was set
see also: c04 s01 s08 s09 SC ff.asm
,3` ( -- ) compiles code to increment by 3 the compilation pointer
: ,3` $036D8D, ,^M~m^C" ; \ lea ebp,[ebp+3] ^M~=$8D m=$6D ^C=$03
,4` ( -- ) compiles code to increment by 4 the compilation pointer
: ,4` $046D8D, ,3 ; \ lea ebp,[ebp+4]
,2` ( -- ) compiles code to increment by 2 the compilation pointer
: ,2` $026D8D, ,3 ; \ lea ebp,[ebp+2]
,1` ( -- ) compiles code to increment by 1 the compilation pointer
: ,1` $45, ,E" ; \ inc ebp 'E'=$45
Note: the i386 ebp register holds the compilation pointer
see also: ,3 ,4 ,2 ,1 here allot , c, w, ff.boot
here` ( -- @ ) pushes onto DATAstack the current compilation pointer
(kept in i386 register ebp); this is the address where the compiler
appends generated i386 native binary code
: here` over` $EB89, s01 ; \ mov ebx,ebp
allot` ( n -- ) adds n to the compilation pointer (kept in i386 register ebp)
Typically, "allot" is used to allocate space for data after "create"
: allot` $DD01, s08 drop` ; \ add ebp,ebx
see also: here allot , c, w, ,1 ,2 ,4 ,3 ff.boot
align` ( -- ) aligns the compilation pointer on an address multiple of 4
: align` $90909090, here negate 3& allot ; \ nop nop nop nop
,` ( n -- ) appends one long (4 bytes) n to the code&data space
: ,` $FF005D89, s08 ,1 ,4` drop` ; \ mov[ebp],ebx lea ebp,[ebp+4]
w,` ( w -- ) appends one word (2 bytes) w to the code&data space
: w,` $005D8966, ,1 s08 ,1 ,2` drop` ; \ mov[ebp],bx lea ebp,[ebp+2]
c,` ( c -- ) appends one byte c to the code&data space (pronounce "c-comma")
: c,` $45005D88, s08 ,2 drop` ; \ mov[ebp],bl inc ebp
see also: here allot align , c, w, ,1 ,2 ,4 ,3 ff.boot
---------------
STACKS HANDLING
swap` ( x y -- y x ) exchanges the top two DATAstack cells TOS and NOS
"swap" may be used almost for free, because instead of generating code,
this macro only exchanges the names of the two i386 registers allocated
to cache the top two DATAstack cells (namely ebx and edx); sometimes,
such as before compiling a "call" or "ret" instruction, the compiler must
restore the default registers allocation, and generate an "xchg ebx,edx"
instruction (only if the registers names are not already the default).
: swap` [ $8035 w, SC , 2 c, ] ; \ xorb[SC],2
see also: >S1 dup rot ff.asm
2dup` ( x y -- x y x y ) double-duplicate DATAstack
: 2dup` over` over` ;
dup` ( x -- x x ) duplicates TOS contents on top of DATAstack
: dup` under` nipdup` ;
nipdup` ( x y -- y y ) overwrites NOS with TOS
: nipdup` $DA89, s09 ; \ mov edx,ebx
tuck` ( x y -- y x y ) duplicates TOS contents under NOS
: tuck` swap` over` ;
over` ( x y -- x y x ) duplicates NOS contents over TOS
: over` under` swap` ;
under` ( x y -- x x y ) duplicates NOS contents under TOS
: under` >C1 $52, s1 ; \ push edx
see also: dup over tuck under ff.asm ff.boot
pick` ( xn..x0 n -- xn..x0 xn ) compiles inline code to replace TOS=n with
the n-th DATAstack item under TOS, where "0 pick" is equivalent to "dup"
Note: n is expected to be a (small) constant compiled just before "pick"
: pick` \ must be preceded by "52(push edx)6Axx(push byte)5A(pop edx)"
here 4- @ $FE00FFFE& $5A006A52- IF !"is_not_preceded_by_a_constant" ;THEN
drop -3 allot here 1+ c@ 1- 0= IF drop ;THEN \ "52(push edx)"=over` C=1
0< IF drop swap` nipdup` ;THEN \ i.e. dup`
$5C8B, s08 10 << $24+ w, ; \ 8B5C24xx(mov ebx,[esp+4n])
see also: dup over ff.ff
2drop` ( x y -- ) double-pop DATAstack
: 2drop` drop` drop` ;
drop` ( n -- ) pops TOS from DATAstack; TOS previous contents is lost
: drop` swap` nip` ;
nip` ( x y -- y ) pops NOS from DATAstack; NOS previous contents is lost
: nip` >C1 $5A, s1 ; \ pop edx
see also: nip drop 2drop ff.asm ff.boot
rot` ( x y z -- y z x ) rotates the top three DATAstack cells, 3rd to top
: rot` >rswapr>` swap` ;
-rot` ( x y z -- z x y ) rotates the top three DATAstack cells, top to 3rd
: -rot` swap` >rswapr>` ;
>rswapr>` ( x y z -- y x z ) exchanges the 2nd and 3rd DATAstack cells
: >rswapr>` $201487, s08 -1 allot c04 ; \ xchg edx,[eax/esp]
see also: rot -rot swap 2drop 2dup ff.boot
depth ( -- n ) returns the net number of cells pushed onto (positive)
or popped from (negative) the DATAstack since startup; ".s" displays it
see also: .s .h ff.asm
>r` ( x -- | == x ) pops x from DATAstack and pushes it onto CALLstack
: >r` dup>r` drop` ;
2>r` ( x y -- | == x y ) double-pop DATAstack to double-push CALLstack
: 2>r` swap` dup>r` swap` dup>r` 2drop` ;
dup>r` ( x -- x | == x ) pushes TOS contents onto CALLstack
: dup>r` >C0 $53, s1 ; \ push ebx
r>` ( -- x | x == ) pops x from CALLstack and pushes it onto DATAstack
: r>` over` dropr>` ;
2r>` ( -- x y | x y == ) double-pop CALLstack to double-push DATAstack
: 2r>` 2dup` dropr>` swap` dropr>` swap` ;
dropr>` ( x -- y | y == ) pops y from CALLstack and overwrites TOS with it
: dropr>` >C0 $5B, s1 ; \ pop ebx
>>r` ( xn..x1 n -- | == x1..xn ) repeatedly pop DATAstack, push to CALLstack
>>rr` ( xn..x1 n -- | == xn..x1 ) repeatedly pop DATAstack, push to CALLstack
see also: >r 2>r dup>r r> 2r> dropr> r 2r rdrop 2rdrop
>>r >>rr +r -r xxr r0 r1 r2 r0! r1! r2! ff.boot
r` ( -- x | x == x ) pushes the CALLstack top cell onto the DATAstack
: r` over` $188B, s08 ; \ mov ebx,[eax]
2r` ( -- x y | x y == x y ) pushes the top two CALLstack cells onto DATAstack
Note: the order of x and y are the same on both stacks
: 2r` over` $04588B, s08 ,1 r` ; \ mov ebx,[eax+4]
rdrop` ( -- | x == ) pops the CALLstack top cell, which is lost
: rdrop` $04C483, c04 ,1 ; \ add esp/eax,4
2rdrop` ( -- | x y == ) pops the CALLstack top two cells, which are lost
: 2rdrop` $08C483, c04 ,1 ; \ add esp/eax,8
+r` ( n -- | xn..x1 == ) pops the CALLstack top n cells, which are lost
-r` ( n -- | == ?n..?1 ) reserves n uninitialized cells on CALLstack
xxr` ( n -- | xn..x1 == ) alias for +r`
r0` ( -- x | x == x ) pushes the CALLstack top cell onto the DATAstack
r1` ( -- y | y x == y x ) pushes the CALLstack 2nd cell onto the DATAstack
r2` ( -- z | z y x == z y x ) pushes the CALLstack 3rd cell onto the DATAstack
r3` ... 4th cell
r4` ... 5th cell
r5` ... 6th cell
r0!` ( x -- | a == x ) pops the DATAstack and stores in CALLstack top cell
r1!` ( x -- | b a == x a ) pops the DATAstack and stores in CALLstack 2nd cell
r2!` ( x -- | c b a == x b a ) pops DATAstack and stores in CALLstack 3rd cell
r3!` ... 4th cell
r4!` ... 5th cell
r5!` ... 6th cell
r!` alias for r0!`
see also: r 2r rdrop 2rdrop >r 2>r dup>r r> 2>r dropr>
>>r >>rr +r -r xxr r0 r1 r2 r0! r1! r2! ff.boot
rp@` ( -- @ ) push on DATAstack the current CALLstack pointer
: rp@` over` $C389, s01 ; \ mov ebx,eax
sp@` ( -- @ ) push on DATAstack the current DATAstack pointer
: sp@` over` $E389, s01 ; \ mov ebx,esp
see also: rp@ pick ff.ff
------------------
INTEGER ARITHMETIC
over&` ( x y -- x y&x) over-and : over&` $D321, s09 ; \ and ebx,edx
(0&0=0 0&1=0 1&0=0 1&1=1, 0 is dominant, 1 recessive)
over|` ( x y -- x y|x) inclusive-or : over|` $D309, s09 ; \ or ebx,edx
(0|0=0 0|1=1 1|0=1 1|1=1, 1 is dominant, 0 recessive)
over^` ( x y -- x y^x) exclusive-or : over^` $D331, s09 ; \ xor ebx,edx
(0^0=0 0^1=1 1^0=1 1^1=0, 0 is neutral, 1 toggles)
2dup+` ( x y -- x y x+y ) push-add : 2dup+` over` over+` ; \ see also bounds
over+` ( x y -- x y+x) over-add : over+` $D301, s09 ; \ add ebx,edx
over-` ( x y -- x y-x) over-subtract : over-` $D329, s09 ; \ sub ebx,edx
over*` ( x y -- x x*y) over-multiply : over*` $DAAF0F, ,1 s09 ; \ imul ebx,edx
These are unusual but efficient basic building primitives
see also: over& over| over^ over+ over- over* ~ & | ^ + - * / ff.boot
&` ( x y -- x&y ) pops-and : &` over&` nip` ;
|` ( x y -- x|y ) pops-incl-or : |` over|` nip` ;
^` ( x y -- x^y ) pops-excl-or : ^` over^` nip` ;
Note: | ^ & are bitwise operators; resp. usual Forth names: or xor and
+` ( x y -- x+y ) pops-add : +` over+` nip` ;
-` ( x y -- x-y ) pops-subtract : -` swap` over-` nip` ;
*` ( x y -- x*y ) pops-multiply : *` over*` nip` ;
/` ( x y -- x/y ) pops-divide : /` /%` nip` ;
%` ( x y -- x%y ) pops-modulo : %` /%` drop` ;
Note: % sign = x sign (usual Forth name: mod)
Note: every literal or constant may be suffixed by | ^ & + - * / %
see also: & | ^ + - * / % ~ negate /% /mod literalcompiler ff.boot
~` ( u -- ~u ) complements TOS bitwise : ~` $D3F7, s01 ; \ not ebx
Note: usual Forth name is "not", or "invert" in ANS-Forth
WARNING: this instruction doesn't change the i386 condition flags!!!
negate` ( n -- -n ) negates TOS : negate` $DBF7, s01 ; \ neg ebx
bswap` ( n3210 -- n0123 ) byteswaps TOS : bswap` $CB0F, s01 ; \ bswap ebx
Note: use bswap to reverse TOS bytes order (i.e. reverse its "endianness")
flip` ( n3210 -- n3201 ) flips TOS LSWord : flip` $FB86, s09 ; \ xchg bh,bl
Note: flip is a 16-bits version of bswap
see also: ~ negate bswap & | ^ + - ff.boot
invert see ~
not see ~
and see &
or see |
xor see ^
mod see %
These usual Forth names are renamed in FreeForth with their single-character
C aliases; if you aren't happy with these, simply create aliases, such as:
&` ' alias and` |` ' alias or` ^` alias xor` %` ' alias mod`
see also: invert ~ & | ^ % alias
1-` ( n -- n-1 ) : 1-` $4B, s1 ; \ dec ebx
1+` ( n -- n+1 ) : 1+` $43, s1 ; \ inc ebx
2+` ( n -- n+2 ) : 2+` 1+` 1+` ;
4+` ( n -- n+4 ) : 4+` $04C383, s01 ,1 ; \ add ebx,4
2*` ( n -- n*2 ) : 2*` $E3D1, s01 ; \ shl ebx,1
2/` ( n -- n/2 ) : 2/` $FBD1, s01 ; \ sar ebx,1
4*` ( n -- n*4 ) : 4*` $02E3C1, s01 ,1 ; \ shl ebx,2
4/` ( n -- n/4 ) : 4/` $02FBC1, s01 ,1 ; \ sar ebx,2
8*` ( n -- n*8 ) : 8*` $03E3C1, s01 ,1 ; \ shl ebx,3
8/` ( n -- n/8 ) : 8/` $03FBC1, s01 ,1 ; \ sar ebx,3
<<` ( n u -- n<<u ) : <<` $E2D3D989, s08 s01 drop` ; \ mov ecx,ebx shl edx,cl
>>` ( U u -- U>>u ) : >>` $EAD3D989, s08 s01 drop` ; \ mov ecx,ebx shr edx,cl
Warning: left shifts (2* 4* 8* <<) don't change the i386 flags, except carry.
Warning: arithmetic right shifts (2/ 4/ 8/) truncate fractional bits, i.e.
round towards -infinity, whereas division by powers of 2 round towards zero:
the results are identical for n>=0, but differ for n<0! To divide negative
numbers by 2 4 8 with rounding towards zero, use 2 / or 4 / or 8 / (or better
02/ or 04/ or 08/ that literalcompiler generates in 10 bytes instead of 16).
Note: >> is unsigned (the MSBit is not propagated to the right)
see also: 1- 1+ 2+ 4+ 2* 2/ 4* 4/ 8* 8/ << >> + - * / % m/mod ff.boot
m/mod` ( xl xh y -- x%y x/y) divides the signed 64 bits dividend (xh<<32)+xl
by signed divisor in TOS, TOS gets signed quotient, and NOS signed remainder
with same sign as dividend (i.e. integer division rounding towards zero)
\ 870424(xchg eax,[esp])F7FB(idiv ebx)89C3(mov ebx,eax)58(pop eax)
: m/mod >S0 >C1 $F7240487, ,4 $58C389FB, ,4 ;
/%` ( x y -- x%y x/y ) divides signed dividend in NOS by signed divisor in TOS,
TOS gets signed quotient, and NOS signed remainder with same sign as dividend
(Note: usual Forth name: /mod)
\ 50(push eax)89D0(mov eax,edx)99(cdq)F7FB(idiv ebx)89C3(mov ebx,eax)pop eax
: /%` >S0 $99D08950, ,4 $C389FBF7, ,4 $58, ,1 ;
see also: m/mod /% / % ff.boot
min` ( n2 n1 -- n ) returns the minimum of two signed numbers
: min` <` IF` swap` THEN` nip` ;
max` ( n2 n1 -- n ) returns the maximum of two signed numbers
: max` >` IF` swap` THEN` nip` ;
within ( n x y -- ; nz? ) if x=<n<y or(y=<x and(n<y or x=<n)), returns nzTRUE
(i.e. i386 Z flag reset), otherwise returns zFALSE (i.e. Z flag set).
This works for both unsigned and signed integers (but not a mixture).
Another way to think about "within" is to consider the integers set as
a circle wrapping around from MAXINT (-1+2^32 unsigned, -1+2^31 signed)
to MININT (0 unsigned, -2^31 signed); now consider the increasing range
from x upto and excluding y (giving an empty range if x=y):
if n is in this range, "within" returns nzTRUE, otherwise zFALSE.
: within over- -rot - u> 2drop nzTRUE ? zFALSE ;
see also: min max BOOL conditionals ff.ff
bounds` ( @ # -- @+# @ ) converts start address "@" under count "#"
into limit address "@+#" under start address
: bounds` over+` swap` ;
see also: 2dup+ ff.boot
-------------
MEMORY ACCESS
@` ( @ -- n ) "fetch" 32 bits from @ (LSByte at @, MSByte at @+3)
: @` $1B8B, s09 ; \ mov ebx,[ebx]
c@` ( @ -- c ) fetch 8-bits byte from @, zero-extended into TOS 32 bits
: c@` $1BB60F, ,1 s09 ; \ movzx ebx,byte[ebx]
w@` ( @ -- w ) fetch 16-bits word from @, zero-extended into TOS 32 bits
: w@` $1BB70F, ,1 s09 ; \ movzx ebx,word[ebx]
2@` ( @ -- lo hi ) fetch 64-bits from @ (hi 32-bits at @, lo 32-bits at @+4)
: 2@` @+` swap` @` swap` ;
see also: @ dup@ @+ ! ff.boot
dup@` ( @ -- @ n ) fetch 32 bits from @ (LSByte at @, MSByte at @+3)
: dup@` over` $1A8B, s09 ; \ mov ebx,[edx]
dupc@` ( @ -- @ c ) fetch 8-bits byte from @, zero-extended into TOS 32 bits
: dupc@` over` $1AB60F, ,1 s09 ; \ movzx ebx,byte[edx]
dupw@` ( @ -- @ w ) fetch 16-bits word from @, zero-extended into TOS 32 bits
: dupw@` over` $1AB70F, ,1 s09 ; \ movzx ebx,word[edx]
see also: dup@ @ @+ ! ff.boot
@+` ( @ -- @+4 n ) fetch next 32 bits long
: @+` dup@` swap` 4+` swap` ;
c@+` ( @ -- @+1 n ) fetch next 8 bits byte, zero-extended into TOS
: c@+` dupc@` swap` 1+` swap` ;
w@+` ( @ -- @+2 w ) fetch next 16 bits word, zero-extended into TOS
: w@+` dupw@` swap` 2+` swap` ;
see also: @+ @ dup@ ! ff.boot
!` ( n @ -- ) stores 32-bits "n" at memory address "@"
: !` tuck!` drop` ;
c!` ( c @ -- ) stores 8-bits "c" at memory address "@"
: c!` tuckc!` drop` ;
w!` ( w @ -- ) stores 16-bits "w" at memory address "@"
: w!` tuckw!` drop` ;
2!` ( lo hi @ -- ) stores 64-bits at memory address "@" (hi at @, lo at @+4)
: 2!` tuck!` 4+` !` ;
+!` ( n @ -- ) adds "n" into memory address "@"
: +!` tuck+!` drop` ;
-!` ( n @ -- ) subtracts "n" from memory address "@"
: -!` tuck-!` drop` ;
see also: ! over! tuck! 2dup! on @ ff.boot
over!` ( @ n -- @ ) stores "n" at memory address "@" and keeps address
: over!` swap` tuck!` ;
overc!` ( @ c -- @ ) stores "c" at memory address "@" and keeps address
: overc!` swap` tuckc!` ;
overw!` ( @ w -- @ ) stores "w" at memory address "@" and keeps address
: overw!` swap` tuckw!` ;
over+!` ( @ n -- @ ) adds "n" into memory address "@" and keeps address
: over+!` swap` tuck+!` ;
over-!` ( @ n -- @ ) subtracts "n" from memory address "@" and keeps address
: over-!` swap` tuck-!` ;
see also: over! ! tuck! 2dup! on @ ff.boot
tuck!` ( n @ -- @ ) stores "n" at memory address "@" and keeps address
: tuck!` 2dup!` nip` ;
tuckc!` ( c @ -- @ ) stores "c" at memory address "@" and keeps address
: tuckc!` 2dupc!` nip` ;
tuckw!` ( w @ -- @ ) stores "w" at memory address "@" and keeps address
: tuckw!` 2dupw!` nip` ;
tuck+!` ( n @ -- @ ) adds "n" into memory address "@" and keeps address
: tuck+!` 2dup+!` nip` ;
tuck-!` ( n @ -- @ ) subtracts "n" from memory address "@" and keeps address
: tuck-!` 2dup-!` nip` ;
see also: tuck! ! over! 2dup! on @ ff.boot
2dup!` ( n @ -- n @ ) writes NOS 32 bits contents into memory at address in TOS
: 2dup!` $1389, s09 ; \ mov [ebx],ebx
2dupc!` ( c @ -- c @ ) writes NOS 8 bits contents into memory at address in TOS
: 2dupc!` $1388, s09 ; \ mov [ebx],dl
2dupw!` ( w @ -- w @ ) writes NOS 16bits contents into memory at address in TOS
: 2dupw!` $66 c, $1389, s09 ; \ mov [ebx],dx
2dup+!` ( n @ -- n @ ) adds "n" into memory address "@" and keeps both
: 2dup+!` $1301, s09 ; \ add [ebx],edx
2dup-!` ( n @ -- n @ ) subtracts "n" from memory address "@" and keeps both
: 2dup-!` $1329, s09 ; \ sub [ebx],edx
Note: the contents of NOS and TOS remain unchanged
see also: 2dup! ! over! tuck! on @ ff.boot
on` ( @ -- ) sets all bits contained at address @
: on` -1 lit` swap` !` ;
off` ( @ -- ) clears all bits contained at address @
: off` 0 lit` swap` !` ;
see also: on ! @ ff.boot
--------------------
MEMORY BLOCKS ACCESS
erase ( @ # -- ) clears memory : erase 0 fill ;
fill ( @ # c -- ) fills memory from address "@" with "#" bytes of value "c"
move ( @src @dst # -- ) reads # bytes from @src and writes them to @dst,
by increasing (resp. decreasing) addresses if @src>=@dst (resp. @src<@dst)
such that the move is always safe even if the two address ranges overlap.
see also: erase fill cmove $- ff.asm
cmove` ( @src @dst # -- ) compiles inline code to read # bytes from @src
and write them to @dst, by increasing addresses one byte at a time.
Note: if @dst is between @src and @src+#, the contents from @src to @dst
will be replicated, which may not be the desired behaviour...
Note: the memory side-effect is the same as "place" but not the stack effect
: cmove` swap` place` drop` ;
see also: place erase fill move $- ff.boot
place` ( @src # @dst -- @dst ) compiles inline code to read # bytes from
@src and write them to @dst, by increasing addresses one byte at a time.
Note: if @dst is between @src and @src+#, the contents from @src to @dst
will be replicated, which may not be the desired behaviour...
Note: the memory side-effect is the same as "move" but not the stack effect
\ 89DF(mov edi,ebx)89D1(mov ecx,edx) 5E(pop esi)F3A4(rep movsb)5A(pop edx)
: place` $D189DF89, s08 s08 >C1 $5AA4F35E, ,3 s1 ;
see also: cmove move fill erase $- ff.boot
$- ( @1 @2 # -- n ) compares the string starting at address "@1" with
the string starting at address "@2", both "#" bytes long: compares
byte per byte by increasing addresses while the bytes are equal, or
until "#" bytes are compared; returns the difference between the
last two compared bytes (i.e. null means that the two string are equal)
see also: move place fill search ff.asm
search ( @ # @' #' -- @r #r ; z:match ) in the string starting at address "@"
for "#" bytes, search the first occurrence of string starting at address "@'"
for "#'" bytes; if a matching substring is found, returns its base address
and the remaining searched-string count of bytes and the i386 Z flag set;
otherwise returns the original searched string address and size and the
i386 Z flag reset (as would do nzFALSE).
see also: $- ff.asm
-------------
COMPILER CORE
lit` ( n -- ; -- n ) compiles a literal, i.e. compiles code with n as
immediate data, which the code will push at runtime onto the DATAstack.
see also: ` create variable constant literalcompiler compiler ff.asm
'` ( -- ) converts a compiled call into a literal:
if a call was compiled just before, "'" converts it into a literal,
which at runtime will push the call target address onto the DATAstack
(typically for use with "alias" or "!^" or "execute" or "call,"); otherwise
throws an exception with the error message "is not preceded by a call".
Note: this is a postfix replacement for the usual Forth prefix "[']"
: '` -call lit` ;
see also: :^ @^ !^ ^^ alias execute ff.boot
-call ( -- @ ) if a call was compiled just before, returns its target address,
otherwise throws an exception with error-message "is not preceded by a call"
see also: ? ' ^@ ^! ^^ ff.boot
call, ( @ -- ) compiles a "call" with the address in TOS as target address
: call, rst $E8 c, here 4+ - , here callmark! ;
callmark ( -- @ ) compiler state variable: stores the value that the
compilation pointer had just after compiling the last call, or null
to prevent any transformation on the last compiled call (such as after
a "THEN")
see also: call, ;; compiler THEN ff.asm
;;` ( -- ) compiles a "ret" instruction, unless the last compiled instruction
was a "call", which is instead changed to an unconditional jump (short
or long depending on the call offset): this is called "tail recursion"
optimization, which often simplifies control structures; this may be
switched off/on permanently by storing a null/non-null value into "tailrec",
or may be inhibited just once by storing a null value into "callmark"
just before ";;" or ";" or ";THEN".
tailrec ( -- @ ) compiler state variable: when null (resp. non-null, initial)
inhibits (resp. allows) tail-recursion optimizations.
see also: ;; ; ;THEN callmark call, tailrec ff.asm
anonymous is the concept that makes FreeForth an original "STATE-free"
implementation of the Forth language, i.e. with no interpret/compile
STATE variable, and therefore no "STATE-smart" compiler complexity.
Instead, FreeForth is only compiling, and keeps in its symbol table
a "header" for each "named" definition (i.e. created with ":" or any
other "defining word" which creates a header in the compiler symbol table),
but when it finishes compiling an "anonymous" definition (with the ";" macro),
as it has no header to refer to it later, FreeForth immediately executes it,
and recycles the anonymous definition code memory (in fact the memory is
recycled before, to allow the anonymous definition execution to compile
code there ... hopefully without overwriting the executing code ;-)
see also: : ; anon anon: ff.asm
anon:` ( -- ) resets the compiler state as it must be before starting a new
anonymous definition
: anon:` here anon! 0 SC c! 0 callmark! ; \ prevent tail-recursion
anon ( -- @ ) compiler state variable: stores the entry point of the
current anonymous definition, or null if compiling a named definition.
see also: anon: : ; anonymous ff.asm
;` ( -- ) this is the kernel of FreeForth interactivity!
If the compilation pointer didn't progress since last execution of ";"
simply returns; otherwise calls ";;" to close the current definition,
then if "anon" is non-null, indicating that we just finished to compile
an anonymous definition, then automatically resets the compilation pointer
to the address in "anon" and executes a call at this address: this is
where compiled code gets executed, i.e. where the user gets interactivity;
then, or if "anon" was null, resets "anon" to the address in the compilation
pointer, and finally returns.
see also: : ;; ; anon anon: anonymous ff.asm
[` ( -- anon SC ) suspends the compilation of the current definition
and starts the compilation of a new anonymous definition
: [` anon@ SC c@ 0 SC c! here anon! ;
]` ( anon SC -- ) completes and executes the current anonymous definition
started by "[" and resumes the compilation of the definition suspended by "["
: ]` 2>r ;` 2r> SC c! anon! ;
see also: [ ] ; ff.boot
H ( -- @ ) compiler state variable: symbol table allocation pointer,
stores the head address of the compiler's separate symbol table (headers),
which grows backwards (from "tib"), sharing with the forwards-growing
compilation pointer the "heap" space, which is one of the only two
FreeForth allocatable memory spaces, as shown in this schematic memory map
(underlined with corresponding pointers):
[binary code and data> heap <headers][source code> blocks][ ] < stacks ]
: ebp^ ^H tib: tin^> tp^ eob: ; eax^ esp^
Growing backwards, H also points on the base of the last defined header
(in usual Forth where headers grow forward, this is another variable LAST).
see also: header find words ff.asm
header ( @ # xt ct -- ) creates a header with string starting at "@" for "#"
characters, with type "ct", and with embedded data "xt".
Each header is composed of:
. +0: 4 bytes: header embedded data: pointer on code or data, or constant
. +4: 1 byte: header type: 0 = ptr on code, 1 = ptr on data or constant
. +5: 1 byte: header name string size (null-terminator excluded)
. +6: size bytes: header name string, null-terminated
source code in ff.asm, functionally equivalent to:
: header 2>r tuck 0 H@ 1- tuckc! over- place \ -- # H@-#-1 | == xt ct
1- tuckc! r> swap 1- tuckc! r> swap 4- tuck! H! ;
see also: H : :^ alias create variable constant mark words compiler classes
find ( @ # -- ptr 0 | @ # ) looks in the symbol table for a header
matching the string "#" bytes long stored at "@";
if a match is found, the header base address is stored in "which",
and the header pointer field is returned in NOS and a null in TOS;
otherwise, NOS and TOS are returned unchanged (and TOS is non-null)
which ( -- @ ) compiler state variable: stores the base address of the
last header found by "find"
see also: find H header words ff.asm
>in ( -- @ ) compiler state variable: stores the address of the next
source character to be parsed
tp ( -- @ ) compiler state variable: stores the address _after_ the last
source character to be parsed
tib ( -- @ ) constant, returns the "terminal-input-buffer" base address;
this is the base of the input sources "stack" used by "needs"
eob ( -- @ ) constant, returns the "end-of-blocks" limit address;
this is the maximum address used for reading input sources, or
for accessing memory in blocks (see source file "bed.ff")
\` ( <line> -- ) ignore comment to end of line
: \` 2 >in -! lnparse 2drop ; \ 2>in-! for \-at-end-of-line
(` ( <line> -- ) ignore comment upto closing closing-paren
: (` ') parse 2drop ;
EOF` ( -- ) ignore comment upto end-of-file
: EOF` tp@ >in! ;
see also: >in tp tib eob \ ( EOF parse wsparse lnparse needs ff.asm ff.boot
parse ( c -- @ # ) parses the next word from the input source,
using as separator the character c: starting from the address stored
in the compiler variable ">in", skips all separators, stores the address
of the first non-separator (or the address stored in "tp" if reached)
into NOS, then skips all non-separators and stores the address after the
first separator (or the address stored in "tp" if reached) into ">in",
and stores the number of non-separators into TOS.
Note: at the end of source, TOS is therefore returned null
see also: wsparse lnparse >in tp ff.asm
wsparse ( -- @ # ) parses the next word from the input source, using as
separator whitespace, i.e. ASCII codes among NUL HT LF VT FF CR SPACE
Note: this greatly simplifies parsing of files and of the command line
Note: this automatically skips empty lines
see also: parse lnparse ff.asm
lnparse ( -- @ # ) skip empty lines if any, then parse to end-of-line;
if at end-of-file, "#" is returned null.
Lin: lnparse 10 parse ;
Win: lnparse 10 parse \ must handle CR+LF:
2dup+ 1- c@ 13- 0= drop IF 1- THEN IF ;THEN 2drop lnparse ;
Note: this automatically skips empty lines
see also: parse wsparse fflinio.asm/ffwinio.asm
` i.e. the backquote character (ASCII 96)
backquote "`" is used a lot in FreeForth.
As a final character of a word name, backquote identifies this word
as a FreeForth macro: when used without its final backquote, the word
is "immediate"-ly executed at compile time; but when used with its
final backquote, it is normally compiled into a call: this makes macro
definitions very easy, in particular when in terms of other macros,
which happens a lot in FreeForth compiler (other Forth systems require
the heavy use of "[POSTPONE]" to do the same thing).
As an initial character of a word name, backquote identifies this word
as hidable: when "hid'm" is later executed, it removes from the compiler
symbol table all the headers with a backquote initial; this is much
easier to use than vocabularies, or even RetroForth's "loc:".
see also: compiler ' hid'm
compiler ( -- ) this is the (vectorized) compiler loop:
. calls "wsparse" to get the next word from the input source;
. if its string size is null, then returns because source end was reached;
. otherwise appends a backquote "`" and looks a first time in the compiler
symbol table for a matching header;
. if found with the appended backquote, executes its "immediate behavior"
(also referred as "macro behavior") which depends on the header type:
+ if "code" type (0), executes a call to the header-embedded address
+ if "data" type (1), pushes the header-embedded address/data on DATAstack
+ header types 2 to 7 are user-definable (see "classes"), their immediate
behavior is expected to find the header-embedded data on DATAstack top
then the compiler loop loops again;
. otherwise removes the appended backquote and looks a second time in the
compiler symbol table for a match;
. if found without the appended backquote, executes its "postpone behavior"
which depends on the header type:
+ if "code" type (0), compiles a call to the header-embedded address,
+ if "data" type (1), compiles the header-embedded data as a literal
(i.e. which at runtime will push the address onto the DATAstack)
+ header types 2 to 7 are user-definable (see "classes"), their postpone
behavior is expected to find the header-embedded data on DATAstack top
then the compiler loop loops again;
. otherwise (if not found twice), calls the literalcompiler and loops again.
see also: eval wsparse find literalcompiler classes ff.asm
literalcompiler is invoked by the compiler when it can't find in its
symbol table the word that it just parsed; literalcompiler is driven
by the last character of the word string:
" final invokes the literal-string compiler, which is driven by the initial:
see stringcompiler
+-*/%&|^ finals call "find" to look in the compiler symbol table for a
header matching the string (without final), and if found with a "data"
type, get the number in its pointer field, otherwise call "number" to
convert the string (without final) into a number; then these finals
compile their corresponding binary-op instruction (+=add -=sub *=mul /=div
%=mod &=and |=or ^=xor) with the above number as immediate argument, and
TOS as destination; simple and efficient "manual" optimization :-)
, final calls "find" or "number" as above, and compiles runtime code which
will store the obtained number at the location pointed at runtime by the
compilation pointer (i386 register ebp), which will have to be incremented
separately; useful for macros such as "s1" which compile inline code
@ final calls "find" or "number" as above, and compiles runtime code which
will read memory cell at that address and push its contents onto DATAstack
! final calls "find" or "number" as above, and compiles runtime code which
will store TOS at the address and pop DATAstack
_ final calls "find" or "number" as above, and compiles runtime code which
will replace TOS with the obtained number; this saves a "drop" (and push)
Otherwise, literalcompiler calls "number" to convert the string
(with its final) into a number, and compiles a runtime (see "lit")
which will push that number onto the DATAstack.
For every call to "number", if the conversion fails, literalcompiler
falls back by calling the vector "notfound".
see also: compiler wsparse find notfound type here ff.asm
stringcompiler is invoked by the compiler when a literal is terminated by a "
," i.e. string with final '"' and initial ',"': ,"XYZ"
compiles the string with no count and no preceding call
(this is useful to compile inline any data and/or code)
!" i.e. string with final '"' and initial '!"': !"XYZ"
compiles a counted string preceded by a call to a hidden runtime
which will call "throw" with the counted-string address in TOS;
if not catched, the exception will display: <-error: XYZ
"" i.e. string with final '"' and initial '"': "XYZ"
compiles a counted string preceded by a call to a hidden runtime
which will push onto the DATAstack the string address and count (-- @ #)
and will continue execution after the compiled string
." i.e. string with final '"' and initial '."': ."XYZ"
compiles a counted string preceded by a call to a hidden runtime
which will push onto the DATAstack the string address and count,
will call "type", and continue execution after the compiled string
Note: some characters, between the initial and final, are interpreted
by the literal-string compiler:
_ is substituted by a space
^ toggles bit6 of following character (^@=0 ^A=1 ^B=2 ... ^?=127)
~ toggles bit7 of last compiled byte (@~=$C0 A~=$C1 and ^@~=$80)
\ compiles the next character literally (\_=_ \^=^ \~=~ \\=\)
Note: double quotes must be balanced. To embed a quote, use ^b.
see also: literalcompiler stringcodes ff.asm
stringcodes in quoted source strings (compiled by stringcompiler)
0 1 2 3 4 5 6 7 8 9 A B C D E F
00: ^@ ^A ^B ^C ^D ^E ^F ^G ^H ^I ^J ^K ^L ^M ^N ^O
10: ^P ^Q ^R ^S ^T ^U ^V ^W ^X ^Y ^Z ^[ ^\ ^] ^^ ^_ prefix ^ toggles bit6
20: _ ! ^b # $ % & ' ( ) * + , - . /
30: 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
40: @ A B C D E F G H I J K L M N O
50: P Q R S T U V W X Y Z [ \\ ] \^ \_ prefix special with \
60: ` a b c d e f g h i j k l m n o
70: p q r s t u v w x y z { | } \~ ^? suffix ~ toggles bit7
80: ^@~ etc. upto ^O~
90: ^P~ etc. upto ^_~
A0: _~ etc. upto /~
B0: 0~ etc. upto ?~
C0: @~ etc. upto O~
D0: P~ etc. upto \_~
E0: `~ etc. upto o~
F0: p~ etc. upto ^?~
see also: stringcompiler ff.asm
number ( @ # -- n 0 | @ # ) converts a literal string into a number;
if the conversion succeeds, the input string address in NOS and count in
TOS are replaced with the converted number in NOS and a null in TOS;
otherwise NOS and TOS are returned unchanged (and TOS is non-null).
The conversion is context-free (unlike usual Forth systems, which use
an implicit conversion "base" variable), it starts in decimal and is
driven by a table indexed by the string characters, which are interpreted
as follows (look at FreeForth sources for examples):
- as string initial changes the number sign to negative
$ changes the conversion current base to 16 (hexadecimal)
& changes the conversion current base to 8 (octal)
% changes the conversion current base to 2 (binary)
# changes the conversion current base to the number converted so far
0..9 count for 0..9 digits, or fail if >= current base
A..Z or a..z count for 10..35 digits, or fail if >= current base
-_: allow gregorian date and/or sexagesimal time conversions (see .dt)
',./ are simply ignored |
other characters fail | (this is easy to change in ff.asm)
examples: $100 = &400 = %1'0000'0000 = 256 -3#21$08 = -$708 = -1800
1956-7-23 = 714'558 2006-7-2_19:33:20 = 200'000'000
see also: literalcompiler .dt ff.asm
notfound ( @ # -- ) user redirectable vector, by default throws the
exception with error-message "???"; this is the place where user code
may take a last chance to compile a string, parsed but not understood
by the compiler.
see also: literalcompiler :^ throw ff.asm
classes ( -- @ ) base address of the compiler's classes table, indexed by
the header types; each table entry contains two executable addresses:
. the code entry of the "immediate behavior", executed when a word is
found in the compiler's symbols table with an appended backquote
. the code entry of the "postpone behavior", executed when a word is
found in the compiler's symbols table without appended backquote
Header types 0 and 1 are predefined (type 2 is reserved for floats):
. type 0 "code" is used by ":" for subroutines, which code entry address is
either immediately executed, or postponed by compiling a call instruction
. type 1 "data" is used by "create" for variables and constants, which
address/data is either immediately pushed on DATAstack, or postponed by
compiling a literal which will push the address/data on DATAstack
. type 2 "float" is used by "`f:" for FPU macros (see ff.ff)
Header types 3 to 7 are user definable in the following way:
. define an immediate behavior for type X (let's call it here "immediateX")
. define a postpone behavior for type X (let's call it here "postponeX")
. execute the following to setup the compiler's classes table for type X:
postponeX ' immediateX ' classes &X0+ 2! ;
then you can define a "defining word" calling "header" to create the symbol
table entry with type X (see for example "`f:`" in ff.ff).
see also: compiler ff.asm
--------------
DEFINING WORDS
:` ( <name> -- ) creates a new subroutine entry point named "name":
if an anonymous definition is pending then calls ";" to execute it;
otherwise it's another entry point in an already open named definition,
then calls "rst" to reset default registers allocation on entry, then
finally points the created header on the address in the compilation pointer.
Typically, use ":" to create a new entry point in code; as headers are
separate, there may be several entry points sharing the same exit point(s);
some entry points may also be used as target for "tail recursion"
source code in ff.asm, functionally equivalent to:
: :` anon@ 0- drop IF ;` 0 anon! THEN rst wsparse here 0 header ;
see also: :` ;; ; anon anonymous ff.asm
alias` ( @ <name> -- ) creates a synonym for a header of "code" type:
calls ":" and changes the header pointer to "@".
Typically, use "'" to obtain the address "@" before using "alias"
: alias` :` H@ ! anon:` ;
see also: : :^ alias mark ff.boot
create` ( <name> -- ) creates a header with default "data" type:
calls ":", changes the header type to data, and resets "anon" to the
address in the compilation pointer to open a new anonymous definition.
To allocate (and initialize) space for some data, use "allot" (or ",").
A word created with "create" will compile a runtime with the data
address as immediate data, that it will push onto the DATAstack
: create` :` 1 H@ 4+ c! anon:` ;
see also: : create variable constant anon anonymous ff.boot
variable` ( <name> -- ; -- @ ) creates a variable initially null:
calls "create", allocates 4 bytes and clears them, and resets "anon" to
the address in the compilation pointer to open a new anonymous definition.
A word created with "variable" will compile a runtime with the data
address as immediate data, that it will push onto the DATAstack
: variable` create` 0 , anon:` ;
see also: create variable constant anonymous ff.boot
constant` ( n <name> -- ; -- n ) creates a constant with value n:
calls "create" and replaces the header address with the constant value.
A word created with "constant" will compile a runtime with the constant
value as immediate data, that it will push onto the DATAstack
: constant` create` H@ ! anon:` ;
example: tib $20000+ constant `sob \ start of blocks (see bed.ff)
equ` ( n <name> -- ; -- n ) a shorter, assembly friendly, alias for constant
constant` ' alias equ` \ defined in ff.ff
see also: create constant ff.boot
:^` ( <name> -- ) creates a user redirectable vector:
calls ":" and compiles default redirection code pointing just after itself
(push $+6 ret), as also does "^^"
: :^` :` $68 c, here 5+ , $C3 c, ; \ push long ret
see also: : :^ @^ ' !^ ^^ n^ ff.boot
^^` ( -- ) resets vector: if a call was compiled just before, converts it
to runtime code which will reset the vector target address to its default,
such that the vector will execute the code compiled just after its
redirection code; otherwise throws an exception with error message
"is not preceded by a call".
: ^^` -call $05C7, 2 allot dup 1+ , 6+ , ; \ mov[],long
see also: :^ @^ !^ ^^ n^ ff.boot
!^` ( @ -- ) sets vector contents: if a call was compiled just before,
converts it to runtime code which will set the vector target address
from TOS; otherwise throws an exception with error message
"is not preceded by a call".
: !^` -call $1D89, s08 1+ , drop` ; \ mov [],ebx
see also: :^ @^ !^ ^^ n^ ff.boot
@^` ( -- @ ) gets vector contents: if a call was compiled just before,
converts it to runtime code which will read the vector target address
and will push it onto the DATAstack; otherwise throws an exception
with error message "is not preceded by a call".
: @^` -call over` $1D8B, s08 1+ , ; \ mov ebx,[]
see also: :^ @^ !^ ^^ n^ ff.boot
n^` ( -- ) disables vector contents: if a call was compiled just before,
converts it to runtime code which will replace the initial call
in the vector with a nop, so the vector simply returns when called;
otherwise throws an exception with error message
"is not preceded by a call".
: n^` -call $05C7, ,2 , $441F0F , ; \ mov [],nop
see also: :^ @^ !^ ^^ n^ ff.boot
------------
FLOW-CONTROL
execute ( @ -- ) pops TOS and executes a call at the address it contained
: execute >r ;
reverse` ( -- ) compiles inline code to exchange the return address of the
subroutine executing "reverse" with the address of the code compiled after
"reverse", such that this code is scheduled to be executed when the caller
(of the subroutine which executed "reverse") executes a "ret".
This seems tricky, but allows powerful programming techniques.
: reverse` $D1FF59, ,3 ; \ pop ecx call ecx
see also: execute reverse catch throw ff.boot
catch ( @ -- err ) pushes an exception-frame on top of the CALLstack,
then pops TOS and executes a call at the address it contained;
if the call returns, i.e. if no "throw" is executed before, then
pushes a "0" on the DATAstack; otherwise see "throw"
: catch push eax push edx push[xfp] mov[xfp],esp \ push exception-frame
mov ecx,ebx mov ebx,edx xchg eax,esp pop edx xchg eax,esp
call ecx pop[xfp] add esp,8 \ pop exception-frame, push 0:
xchg eax,esp push edx xchg eax,esp mov edx,ebx xor ebx,ebx ret
throw ( err -- ) raises an exception identified by err:
restores the two stack pointers and NOS values saved in the last
exception-frame pushed by "catch", keeps "err" in TOS as apparent
result of "catch", and resumes execution after the "catch" which
pushed the last exception-frame.
: throw mov esp,[xfp] pop[xfp] pop edx pop eax ret
Note: the top-level main-loop "catch" expects "err" to be the address of a
counted-string error-message: see "stringcompiler" (final '"' initial '!')
for howto compile such error messages (example: !"Error_message")
see also: catch throw execute CALLstack ff.asm
flow-control The most basic level of flow-control is supported by 3 macros:
":" which defines code entry points (which assemblers call "labels")
which are compiled as "call" instructions when referenced
";" which compiles a "ret" instruction, unless the instruction compiled
just before was a "call", which it converts into an unconditional
"jmp" instruction (this is also called "tail-recursion")
"?" which expects the instruction compiled just before it to be a "call",
that it converts into a conditional jump instruction, which condition
is either specified before (see "conditionals"), or by default "jump
if non-zero" (i.e. if i386 zero-flag is clear)
This basic flow-control support requires source code to be ordered such
that every call or jump instruction refers to a previously defined entry
point, which is almost always possible (except for co-recursive programs
or for state automata, where the reference-loops may be broken by using
redirectable vectors: see ":^"), sometimes at the cost of some extra
calls or jumps.
Higher levels of flow-control, supporting forward references and exception
handling with nestable control structures, are supported by sets of macros:
"IF" "ELSE" "SKIP" "THEN" ";THEN" support independent forward references
"BEGIN" "START" "TIMES" "RTIMES" open a looping control block
"WHILE" "ENTER" "TILL" "BREAK" "AGAIN" control flow inside a control block,
may be used inside "IF ... ELSE ... THEN" (nested) control structure(s)
"REPEAT" "UNTIL" "END" close a looping control block
"catch" "throw" handle exceptions through several call-levels
See each individual macro for detailed help.
see also: conditionals +longconds
conditionals Usual Forth systems compute boolean values as integers
on the DATAstack, and pop them when testing them for branching
(with "IF" "WHILE" "UNTIL").
FreeForth instead relies on the processor's status-register condition-flags
and lets the programmer write instructions to:
1) modify these flags (all arithmetical and logical instructions do)