2
2
\ A sometimes minimal FORTH compiler and tutorial for Linux / i386 systems. -*- asm -*-
3
3
\ By Richard W.M. Jones <
[email protected] > http://annexia.org/forth
4
4
\ This is PUBLIC DOMAIN (see public domain release statement below).
5
- \ $Id: jonesforth.f,v 1.13 2007-10-07 11:07:15 rich Exp $
5
+ \ $Id: jonesforth.f,v 1.14 2007-10-10 13:01:05 rich Exp $
6
6
\
7
7
\ The first part of this tutorial is in jonesforth.S. Get if from http://annexia.org/forth
8
8
\
24
24
\ Secondly make sure TABS are set to 8 characters. The following should be a vertical
25
25
\ line. If not, sort out your tabs.
26
26
\
27
- \ |
28
- \ |
29
- \ |
27
+ \ |
28
+ \ |
29
+ \ |
30
30
\
31
31
\ Thirdly I assume that your screen is at least 50 characters high.
32
32
\
65
65
: 2DUP OVER OVER ;
66
66
: 2DROP DROP DROP ;
67
67
68
- \ More standard FORTH words.
69
- : 2* 2 * ;
70
- : 2/ 2 / ;
71
-
72
68
\ NEGATE leaves the negative of a number on the stack.
73
69
: NEGATE 0 SWAP - ;
74
70
@@ -658,8 +654,9 @@ \ FORTH allows ( ... ) as comments within function definitions. This works by h
658
654
want a variable which is read often, and written infrequently.
659
655
660
656
20 VALUE VAL creates VAL with initial value 20
661
- VAL pushes the value directly on the stack
657
+ VAL pushes the value (20) directly on the stack
662
658
30 TO VAL updates VAL, setting it to 30
659
+ VAL pushes the value (30) directly on the stack
663
660
664
661
Notice that 'VAL' on its own doesn't return the address of the value, but the value itself,
665
662
making values simpler and more obvious to use than variables (no indirection through '@').
@@ -833,10 +830,10 @@ \ FORTH allows ( ... ) as comments within function definitions. This works by h
833
830
)
834
831
: DUMP ( addr len -- )
835
832
BASE @ ROT ( save the current BASE at the bottom of the stack )
836
- HEX ( and switch the hexadecimal mode )
833
+ HEX ( and switch to hexadecimal mode )
837
834
838
835
BEGIN
839
- DUP 0> ( while len > 0 )
836
+ ? DUP ( while len > 0 )
840
837
WHILE
841
838
OVER 8 U.R ( print the address )
842
839
SPACE
@@ -845,19 +842,19 @@ \ FORTH allows ( ... ) as comments within function definitions. This works by h
845
842
2DUP ( addr len addr len )
846
843
1- 15 AND 1+ ( addr len addr linelen )
847
844
BEGIN
848
- DUP 0> ( while linelen > 0 )
845
+ ? DUP ( while linelen > 0 )
849
846
WHILE
850
847
SWAP ( addr len linelen addr )
851
848
DUP C@ ( addr len linelen addr byte )
852
849
2 .R SPACE ( print the byte )
853
850
1+ SWAP 1- ( addr len linelen addr -- addr len addr+1 linelen-1 )
854
851
REPEAT
855
- 2DROP ( addr len )
852
+ DROP ( addr len )
856
853
857
854
( print the ASCII equivalents )
858
855
2DUP 1- 15 AND 1+ ( addr len addr linelen )
859
856
BEGIN
860
- DUP 0> ( while linelen > 0)
857
+ ? DUP ( while linelen > 0)
861
858
WHILE
862
859
SWAP ( addr len linelen addr )
863
860
DUP C@ ( addr len linelen addr byte )
@@ -868,7 +865,7 @@ \ FORTH allows ( ... ) as comments within function definitions. This works by h
868
865
THEN
869
866
1+ SWAP 1- ( addr len linelen addr -- addr len addr+1 linelen-1 )
870
867
REPEAT
871
- 2DROP ( addr len )
868
+ DROP ( addr len )
872
869
CR
873
870
874
871
DUP 1- 15 AND 1+ ( addr len linelen )
@@ -880,7 +877,7 @@ \ FORTH allows ( ... ) as comments within function definitions. This works by h
880
877
SWAP ( addr-linelen len-linelen )
881
878
REPEAT
882
879
883
- 2DROP ( restore stack )
880
+ DROP ( restore stack )
884
881
BASE ! ( restore saved BASE )
885
882
;
886
883
@@ -891,13 +888,13 @@ \ FORTH allows ( ... ) as comments within function definitions. This works by h
891
888
agreed syntax for this, so I've gone for the syntax mandated by the ISO standard
892
889
FORTH (ANS-FORTH).
893
890
894
- ( some value on the stack )
895
- CASE
896
- test1 OF ... ENDOF
897
- test2 OF ... ENDOF
898
- testn OF ... ENDOF
899
- ... ( default case )
900
- ENDCASE
891
+ ( some value on the stack )
892
+ CASE
893
+ test1 OF ... ENDOF
894
+ test2 OF ... ENDOF
895
+ testn OF ... ENDOF
896
+ ... ( default case )
897
+ ENDCASE
901
898
902
899
The CASE statement tests the value on the stack by comparing it for equality with
903
900
test1, test2, ..., testn and executes the matching piece of code within OF ... ENDOF.
@@ -912,14 +909,14 @@ \ FORTH allows ( ... ) as comments within function definitions. This works by h
912
909
An example (assuming that 'q', etc. are words which push the ASCII value of the letter
913
910
on the stack):
914
911
915
- 0 VALUE QUIT
916
- 0 VALUE SLEEP
917
- KEY CASE
918
- 'q' OF 1 TO QUIT ENDOF
919
- 's' OF 1 TO SLEEP ENDOF
920
- ( default case: )
921
- ." Sorry, I didn't understand key <" DUP EMIT ." >, try again." CR
922
- ENDCASE
912
+ 0 VALUE QUIT
913
+ 0 VALUE SLEEP
914
+ KEY CASE
915
+ 'q' OF 1 TO QUIT ENDOF
916
+ 's' OF 1 TO SLEEP ENDOF
917
+ ( default case: )
918
+ ." Sorry, I didn't understand key <" DUP EMIT ." >, try again." CR
919
+ ENDCASE
923
920
924
921
(In some versions of FORTH, more advanced tests are supported, such as ranges, etc.
925
922
Other versions of FORTH need you to write OTHERWISE to indicate the default case.
@@ -1630,6 +1627,67 @@ EXCEPTION-MARKER, namely a function that just drops the stack frame and itself
1630
1627
. CR
1631
1628
;
1632
1629
1630
+ (
1631
+ ASSEMBLER CODE ----------------------------------------------------------------------
1632
+
1633
+ This is just the outline of a simple assembler, allowing you to write FORTH primitives
1634
+ in assembly language.
1635
+
1636
+ Assembly primitives begin ': NAME' in the normal way, but are ended with ;CODE. ;CODE
1637
+ updates the header so that the codeword isn't DOCOL, but points instead to the assembled
1638
+ code (in the DFA part of the word).
1639
+
1640
+ We provide a convenience macro NEXT (you guessed the rest).
1641
+
1642
+ The rest consists of some immediate words which expand into machine code appended to the
1643
+ definition of the word. Only a very tiny part of the i386 assembly space is covered, just
1644
+ enough to write a few assembler primitives below.
1645
+ )
1646
+
1647
+ : ;CODE IMMEDIATE
1648
+ ALIGN ( machine code is assembled in bytes so isn't necessarily aligned at the end )
1649
+ LATEST @ DUP
1650
+ HIDDEN ( unhide the word )
1651
+ DUP >DFA SWAP >CFA ! ( change the codeword to point to the data area )
1652
+ [COMPILE] [ ( go back to immediate mode )
1653
+ ;
1654
+
1655
+ HEX
1656
+
1657
+ ( Equivalent to the NEXT macro )
1658
+ : NEXT IMMEDIATE AD C, FF C, 20 C, ;
1659
+
1660
+ ( The i386 registers )
1661
+ : EAX IMMEDIATE 0 ;
1662
+ : ECX IMMEDIATE 1 ;
1663
+ : EDX IMMEDIATE 2 ;
1664
+ : EBX IMMEDIATE 3 ;
1665
+ : ESP IMMEDIATE 4 ;
1666
+ : EBP IMMEDIATE 5 ;
1667
+ : ESI IMMEDIATE 6 ;
1668
+ : EDI IMMEDIATE 7 ;
1669
+
1670
+ ( i386 stack instructions )
1671
+ : PUSH IMMEDIATE 50 + C, ;
1672
+ : POP IMMEDIATE 58 + C, ;
1673
+
1674
+ ( RDTSC instruction )
1675
+ : RDTSC IMMEDIATE 0F C, 31 C, ;
1676
+
1677
+ DECIMAL
1678
+
1679
+ (
1680
+ RDTSC is an assembler primitive which reads the Pentium timestamp counter (a very fine-
1681
+ grained counter which counts processor clock cycles). Because the TSC is 64 bits wide
1682
+ we have to push it onto the stack in two slots.
1683
+ )
1684
+ : RDTSC ( -- lsb msb )
1685
+ RDTSC ( writes the result in %edx:%eax )
1686
+ EAX PUSH ( push lsb )
1687
+ EDX PUSH ( push msb )
1688
+ NEXT
1689
+ ;CODE
1690
+
1633
1691
(
1634
1692
NOTES ----------------------------------------------------------------------
1635
1693
0 commit comments