Pracktische inleiding
tot
de programmeertaal forth

0

Inhoud

  1. EMIT . DECIMAL
  2. De stack KEY CR
  3. + - * SWAP DUP
  4. WORDS Definities
  5. .S DROP OVER BL (
  6. CONSTANT VALUE TO +TO
  7. VARIABLE ! @ +!
  8. Overzicht
  9. Tick EXECUTE
  10. IF ELSE THEN TRUE FALSE
  11. DO LOOP I HEX
  12. BEGIN UNTIL KEY? WHILE REPEAT
  13. Signed en Unsigned
  14. U. U< CELLS
  15. HERE Name Code Body CREATE ALLOT
  16. DUMP ACCEPT CELL+
  17. Karakters
  1. Controlestructuren nesten
  2. Delen
  3. AND OR XOR
  4. .breuk
  5. Primitieven TUCK NIP SEE
  6. Algorithme voor GGD
  7. >R R> R@ Vereenvoudig VV
  8. Herdefiniëren
  9. V+ NEGATE V-
  10. V* V/ Local-Values V.
  11. Dubbelgetallen I
  12. Dubbelgetallen II
  13. M* <> ABORT"
  14. Brief van de Immediate-Words
  15. CHAR [CHAR]
  16. IMMEDIATE Besturingswoorden ?DO LEAVE
  17. SPACE SPACES
  18. J ROT
  19. PAGE Spel

1

EMIT   .   DECIMAL

Forth bestaat uit woorden.
Als je een of meer woorden achter elkaar intypt en daarna op Return drukt, zal Forth die woorden uitvoeren in de volgorde waarin je ze getypt hebt.

In het vervolg zal ik met een haakje sluiten aan het begin van een regel aangeven dat je de tekst achter het haakje moet intypen tot en met [rtn]. Met [rtn] is de Return toets of de Enter toets bedoeld.

Voorbeelden:
) 65 emit [rtn] A ok
)
) 66 emit [rtn] B ok
)
) 56 . [rtn] 56  ok
Een getal wordt ook als een woord opgevat. EMIT kan vervolgens over dat getal beschikken, beschouwt het als een ASCII code en laat het bijbehorende karakter op het scherm verschijnen.

Als het resultaat in de eerste regel een kleine letter 'e' was in plaats van een hoofdletter 'A' weet je dat forth in de hexadecimale toestand staat. Hex 65 is decimaal 101 en dat is de ASCII code voor de 'e'.
Typ in dat geval:
) decimal [rtn]
en probeer het dan nog eens.

Ook de punt is een Forth woord. Hij maakt het getal zelf op het scherm zichtbaar.
) 65 emit 66 emit [rtn] AB ok
)
) 65 66 emit emit [rtn] BA ok
)
) 65 74 emit emit emit [rtn] JA  Empty stack
)
) 1000 45 emit . [rtn] -1000  ok
Een woord dat een getal nodig heeft, pakt altijd het getal dat als laatste binnengekomen is.

Forth geeft een foutmelding als de getallen op zijn. De formulering ervan kan per Forth verschillen.

2

De stack   KEY   CR

Ingevoerde getallen worden vastgehouden met een mechanisme dat in het engels 'stack' heet (stapelen). Getallen worden op volgorde van hun binnenkomst bewaard en in omgekeerde volgorde weer afgegeven aan woorden die getallen verbruiken. Het getal dat als laatste is binnengekomen komt als eerste weer tevoorschijn.

Bij het documenteren van een Forth woord is het gebruikelijk om achter dat woord tussen haakjes aan te geven wat zijn invloed op de stack is:

EMIT ( getal -- ) Haal het getal van de stack af, beschouw het als een ASCII code en druk het bijbehorende karakter af.

. ( getal -- ) Haal het getal van de stack af en druk het af met een spatie erachter.

Nog een paar Forth woorden:

KEY ( -- getal ) Wacht totdat er een toets ingedrukt is en zet de ASCII code van die toets als getal op stack.

CR ( -- ) Ga naar een nieuwe regel. Geen verandering op de stack.

? Kun je het effekt voorspellen van:
) key . [rtn] ?

) key emit [rtn] ?

) cr key . [rtn] ?

) key cr . [rtn] ?

) key . cr [rtn] ?

) key key emit emit [rtn] ?

3

+   -   *   SWAP   DUP

+ ( x y -- z ) Haal twee getallen van de stack af, tel ze op en zet het resultaat weer op stack.
) 1 4 + [rtn]  ok
) . [rtn] 5  ok
) 2 3 + 4 + . [rtn] 9  ok
) 2 3 4 + + . [rtn] 9  ok
- ( x y -- z ) Aftrekken: z = x - y

* ( x y -- z ) Vermenigvuldigen: z = x * y
) 1 4 - . [rtn] -3  ok
) 2 3 * . [rtn] 6  ok
Je kunt de volgorde van getallen op stack veranderen:

SWAP ( x y -- y x ) Verwissel de laatste twee getallen op stack onderling.
) 1 4 swap - . [rtn] 3  ok
DUP ( x -- x x ) Maak een extra exemplaar van het laatste getal.
) key dup . emit [rtn] ?
? Als je moet uitrekenen hoeveel 500+14*(61-52) is, welke van de onderstaande uitwerkingen zijn dan onjuist? En welke vind je het overzichtelijkst?
) 500 14 61 52 - * + . [rtn] ?

) 61 52 - 14 * 500 + . [rtn] ?

) 500 14 52 61 swap - * + . [rtn] ?

) 14 61 52 - * 500 + . [rtn] ?

) 500 14 61 dup emit 52 - * + . [rtn] ?

) 500 14 61 52 - dup . * dup . + . [rtn] ?

) 61 52 - dup . 14 * dup . 500 + . [rtn] ?

4

WORDS   Definities

WORDS ( -- ) Laat de woorden zien die Forth kent. De nieuwste woorden staan voorop.

Dat laatste zinnetje wordt begrijpelijk als je weet dat je er woorden bij kunt maken:
Programmeren in Forth = Nieuwe woorden maken
Dat gaat als volgt:
) : PILS 105 * . ; [rtn] ok
) WORDS [rtn] ?
Technisch onderscheidt PILS zich niet van de bestaande Forth woorden. Het is geen tweederangs woord en het werkt ook niet trager dan de bestaande woorden.

: ccc ( -- ) Maak een definitie met de naam ccc. Met ccc bedoel ik een willekeurige naam.

; ( -- ) Beëindig een definitie.

PILS ( x -- ) Druk de rekening voor x pilsjes af.
) 1 pils [rtn] ?
) 4 pils [rtn] ?
) 10 pils [rtn] ?
) : KRAT 24 * ; [rtn] ok
) 2 krat pils [rtn] ?

5

.S   DROP   OVER   BL   (

.S ( -- ) Laat zien welke getallen er op stack staan zonder de stack te veranderen (punt S).

? Typ onderstaande regels in en stel vast wat de woorden DROP en OVER doen. Schrijf iedere keer voordat je op [rtn] drukt op een papier welk resultaat je verwacht.
) arnhem [rtn] ?
) 15 .s [rtn] ?
) dup .s [rtn] ?
) + .s [rtn] ?
) 2 .s [rtn] ?
) over .s [rtn] ?
) + .s [rtn] ?
) emit .s [rtn] ?
) drop .s [rtn] ?
) drop .s [rtn] ?
ARNHEM is geen Forth woord en veroorzaakt een foutmelding. Dat is natuurlijk flauw, maar bij een foutmelding wordt de stack altijd leeggemaakt en daar ging het mij om.

? 32 is de ASCII code voor een spatie. Kun je die spatie op het scherm ontdekken?

In de laatste regel probeert DROP een getal van stack te verwijderen. Maar de stack is leeg. Dat veroorzaakt een foutmelding. De .S die erachter staat, wordt niet meer uitgevoerd.

BL ( -- 32 ) Zet de ASCII code van de spatie op stack.
) : WIJD ( k -- ) emit bl emit ; [rtn] ok
Tekst tussen haakjes is ter informatie. Die hoef je dus niet in te typen. Je zou dat echter wel kunnen doen, want Forth slaat alles over wat tussen haakjes staat. Het haakje openen is een Forth woord en moet daarom gevolgd worden door een spatie, daarna kan de informatie komen.
) 12 ( onzin ) . .S [rtn] ?
)
) 65 wijd 66 wijd 67 wijd [rtn] ?
( ( tekst -- ) Sla de tekst t/m het eerstvolgende haakje sluiten over.

6

CONSTANT  
VALUE   TO   +TO  

) 12 constant DOZIJN [rtn] ok
) dozijn . [rtn] 12  ok
CONSTANT ccc ( x -- ) Definiëer een konstante met de waarde x en de naam ccc.
DOZIJN ( -- 12 ) Zet de waarde van de konstante op stack.
Een konstante kun je niet veranderen.

Voor variabele waarden zijn er twee methodes.

1) VALUE met de hulpwoordjes TO +TO Het adres van een VALUE wordt nergens zichtbaar.

De forthstandaard schrijft merkwaardigerwijs voor dat je bij de definitie van een value een initialisatiewaarde mee moet geven: 0 value ccc
noForth wijkt hiervan af want in een forth die met ROM en RAM werkt is het zinloos om tijdens het compileren waarden in RAM te initialiseren. Lees hierover in ...

7

VARIABLE   @   !   +!

2) VARIABLE met de hulpwoordjes @ ! +! Bij een VARIABLE gaat het lezen en schrijven expliciet via het adres op stack. Let er daarom op, dat het nieuwste getal op de stack ALTIJD een adres is als je de woorden ! @ en +! gebruikt. Vooral ! en +! zijn gevaarlijke woorden. Als je er fouten mee maakt, overschrijf je ongewild ergens iets in het geheugen en dat kan "fatale" gevolgen hebben. Dus:
Gebruik ! @ en +! alleen direkt achter een adres.

Het gebruik van Values en Variables met elkaar vergeleken:

Values
: APPELS ( n -- ) 80 * +to bedrag ;
: PEREN  ( n -- ) 70 * +to bedrag ;
: TOTAAL ( -- ) bedrag . 0 to bedrag ;

totaal [rtn] ?

5 appels 3 peren totaal [rtn] ?

: EIEREN ( n -- ) 60 * +to bedrag ;
2 peren 2 eieren totaal [rtn] ?

dozijn eieren 10 appels 10 peren
totaal [rtn] ?

Variables
: APPELS ( n -- ) 80 * bedrag +! ;
: PEREN  ( n -- ) 70 * bedrag +! ;
: TOTAAL ( -- ) bedrag @ . 0 bedrag ! ;

totaal [rtn] ?

5 appels 3 peren totaal [rtn] ?

: EIEREN 60 * bedrag +! ;
2 peren 2 eieren totaal [rtn] ?

dozijn eieren 10 appels 10 peren
totaal [rtn] ?

8

Overzicht

? Opgave.
Schrijf, uit het hoofd, tussen haakjes achter de tot nu toe geleerde Forth woorden wat hun effekt op de stack is. ( ... -- ... )
Vóór het dubbele minteken: de getallen die het woord verbruikt.
Achter het dubbele minteken: de getallen die het woord produceert.
Geef met ccc aan dat het woord een naam nodig heeft.
EMIT
.
DECIMAL
KEY
CR
+
-
*
SWAP
DUP
WORDS
:
;
.S
DROP
OVER
BL
(
CONSTANT
VALUE
TO
+TO
VARIABLE
@
!
+!
In sommige Forth systemen laat de dubbele punt tijdens het compileren een waarde achter op stack die er door de puntkomma weer afgehaald wordt. Deze waarde wordt in het systematische deel achter deze kursus met sys? aangeduid.

9

Tick   EXECUTE

Een snelle en veilige manier om te weten te komen of Forth een bepaald woord kent is met het enkele aanhalingsteken (engels: Tick).
' ccc ( -- s ) Zet de sleutel van het woord ccc op stack. Als het woord niet gevonden wordt, komt er een foutmelding.
Elk woord heeft, behalve een naam, nog een uniek herkenningsgetal: de sleutel (engels: Token).
) ' drop . [rtn] ?
) ' words . [rtn] ?
) ' arnhem . [rtn] ?
) ' 0 . [rtn] ?
) ' 75 . [rtn] ?
De waarde van een sleutel is systeemafhankelijk. Getallen hebben over het algemeen geen naam en geen sleutel.

EXECUTE ( s -- ) Voer het woord dat de sleutel s heeft uit.
) 3 4 .S [rtn] ?
) ' swap execute [rtn] ok
) .s [rtn] ?
Het is aan de programmeur om ervoor te zorgen dat de sleutel voor EXECUTE een geldige sleutel is, dwz. verkregen is met het woordje ' (Tick).

Bij het lezen en uitvoeren van een tekst die je ingetypt hebt, kijkt Forth woord voor woord of hij het kan vinden in zijn woordenboek.
Gevonden: Forth voert het woord uit.
Niet gevonden: Forth gaat er van uit dat het een getal zal zijn, dat hij op stack moet zetten. Pas als dit ook niet lukt, komt er een foutmelding.

Ter overdenking:
) : ZEVEN 7 ;
) zeven 2 * . [rtn] ?

) : 7 7 ;
) 7 7 * . [rtn] ?

) zeven constant SEVEN
) seven . [rtn] ?

) ' 7 execute . [rtn] ?

) : 8 100 ;
) 8 1 + . [rtn] ?

10

IF   ELSE   THEN   TRUE   FALSE


Als je zelf gaat programmeren typ je je programma's natuurlijk niet rechtstreeks in forth maar in een editor. Je kunt je het dom overtypen van code besparen door de code in het blauwe blok te selecteren en te kopiëren en vervolgens in de forth(terminal) te plakken. Als dat vanuit deze pdf niet lukt probeer het dan vanuit de file (forthlessen-prgs.f).
: NUL? ( x -- )
  ." ("
  0 =
  if ." ja"
  else ." nee"
  then ." ) "   ;
Probeer NUL? uit op verschillende getallen.
Nieuwe woorden:
." ccc" ( -- ) Druk de tekst ccc af. Omdat ." (punt aanhaling) een woord is, moet hij door een spatie gescheiden worden van de af te drukken tekst. Het tweede aanhalingsteken geeft het teksteinde aan en maakt geen deel uit van de tekst. Er mogen nu wel spaties maar uiteraard geen aanhalingstekens in ccc voorkomen.

= ( x y -- vlag ) Test of x gelijk is aan y en laat een vlag achter op de stack. De True vlag is gelijk aan -1 en de False vlag is gelijk aan 0.

TRUE ( -- x ) x=-1 d.w.z. alle bits van x zijn gezet.
FALSE ( -- x ) x=0 d.w.z. alle bits van x zijn nul.

IF ( vlag -- ) Haal een vlag van stack en reageer aldus:
[true?] IF [zo ja, doe dan dit]
ELSE [zo nee, doe dan dit]
THEN [enz.]

IF beschouwt ieder getal dat niet nul is als een True vlag.

De If-Else-Then-konstruktie kan ook zonder ELSE voorkomen.
: TEST ( x -- )
  dup . ." is "
  dup 0 < if ." kleiner dan " then
  dup 0 > if ." groter dan " then
  drop ." nul " ;
Probeer TEST uit op verschillende getallen.
Nieuwe woorden:
< ( x y -- vlag ) Test of x<y.
> ( x y -- vlag ) Test of x>y.

? Maak een woord CIJFER? ( -- ) dat vaststelt of een ingetypte toets een cijfer is. Op het scherm moet de mededeling 'cijfer' of 'geen cijfer' verschijnen. Gebruik het woord KEY hierbij.

11

DO   LOOP   I   HEX

Voorbeeld:
) : TEL ( -- )
)   20 0 do i . loop ;
DO ( grenswaarde beginwaarde -- ) Start een lus. De teller begint met beginwaarde. Grens- en beginwaarde (let op hun volgorde) worden van stack gehaald en intern opgeborgen.
LOOP ( -- ) Verhoog de teller met 1 en spring terug naar het eerste woord dat achter code> DO staat, maar ga verder als de teller gelijk is geworden aan de grenswaarde.
I ( -- t ) Zet de waarde van de teller van de Do-Loop op stack. I is een afkorting van Index.

Het invoeren en het afdrukken van getallen ging tot nu toe in het tientallig (decimale) stelsel. Forth gebruikt daarbij de variabele BASE als grondtal. Door de waarde van BASE te veranderen kun je in andere talstelsels werken. HEX BINARY en DECIMAL zijn Forth woorden die omschakelen naar een bepaald talstelsel. Als BINARY nog niet bestaat:
: BINARY ( -- ) 2 BASE ! ;
) decimal tel [rtn] ?
) base @ . [rtn] ?
) hex tel [rtn] ?
) base @ . [rtn] ? (strikvraag)
) binary tel [rtn] ?
) 10 decimal . [rtn] ?
) 10 hex . [rtn] ?
) 10 1 - . [rtn] ?
) 10 decimal . [rtn] ?
? Leg uit wat het woord LIJST doet:
decimal
: LIJST ( -- )
  17 0 do cr
  i hex .   i decimal .   i binary .
  loop decimal ;
  
) lijst [rtn] ?

12

BEGIN   UNTIL   KEY?   WHILE   REPEAT

Voorbeeld:
: TEL-DOOR ( x -- y )
  begin dup .
  1 +
  key? until
  key drop ;
Nieuwe woorden:
BEGIN ( -- ) Start een lus.
UNTIL ( vlag -- ) Ga verder als de vlag True is, maar spring terug naar het eerste woord achter BEGIN als de vlag False is.
KEY? ( -- vlag ) Test of er een toets ingedrukt is.
Als het antwoord True is, kan met KEY de ASCII code van die toets op stack gezet worden.
) 0 tel-door . [rtn] ?
) 0 tel-door tel-door . [rtn] ?
voorbeeld:
value TEKEN
#43 to teken
teken emit [rtn] ?
: TEKENS ( n -- )
  begin
  dup 0 > while
  1 -
  teken emit
  repeat drop ;
) 5 tekens [rtn] ?
Nieuw:
WHILE ( vlag -- ) Ga verder als de vlag True is, maar spring uit de lus, over REPEAT heen, als de vlag False is.
? Wat moet je doen om met TEKENS zes minnetjes te laten verschijnen?
? Kun je het woord TEKENS maken met behulp van Do-Loop in plaats van met Begin-While-Repeat ?
? Wat doet het woord TEKST ?
) : TEKST ( -- )
)   key cr
)   begin key
)   dup emit
)   over = until
)   drop ; ( Ik hoop dat je hier uitkomt. )

13

Signed   Unsigned

Voor een mens is een getal een rijtje cijfers. Als Forth zo'n getal binnenkrijgt, vertaalt hij dat rijtje cijfers naar een bitpatroon en zet dat op stack. Bij die vertaling speelt BASE een rol.
Als Forth een bitpatroon als getal moet afdrukken (met . bijvoorbeeld), moet de weg terug afgelegd worden.
Berekeningen voert Forth uit met getallen die al vertaald zijn naar bitpatronen. BASE heeft daar geen invloed meer op.

Hoe is nu het verband tussen 'menselijke' getallen en bitpatronen?
Voor het gemak ga ik even uit van een heel klein Forthje waarin de patronen uit slechts vier bits bestaan (in werkelijkheid meestal 16 of 32 bits). Hier volgen alle mogelijke 4-bits patronen:
0000 0001 0010 0011 0100 0101 0110 0111
1000 1001 1010 1011 1100 1101 1110 1111
De patronen die met een 0 beginnen zijn zwart, de andere, die met een 1 voorop, maak even ik blauw.

Forth kent twee methodes om de patronen naar getallen te vertalen.
[wordt vervolgd]

14

U.   U<   CELLS

Gewoonlijk werkt men in Forth met Signed getallen. Voor tekenloze getallen bestaan soms aparte woorden.
Naast . (punt) bestaat U. (u punt).
Het woord . (punt) vertaalt naar Signed getallen.
Het woord U. (u punt) vertaalt naar Unsigned getallen.
) -1 u. [rtn] ?
Naast < (kleiner dan) bestaat U< (u kleiner dan).
) -5 5 < . [rtn] ?
) -5 5 u< . [rtn] ?
Voor optellen en aftrekken hoeft er geen onderscheid gemaakt te worden tussen Signed en Unsigned. Dat is mogelijk doordat Forth daarbij geen kontrole uitvoert op het te groot of te klein worden van de uitkomst. Overflow en underflow worden niet gemeld, de programmeur moet dat zelf opvangen. Het gaat als bij een kilometerteller in de auto. Als het hoogste getal verschijnt, allemaal negens, en je rijdt toch verder, dan komt er geen melding op het dashboard in de trant van END-OF-AUTO-ERROR.
Voor beide getalsoorten geldt: Het grootst mogelijke getal plus 1 geeft het kleinst mogelijke getal als uitkomst.
) binary false . [rtn] ?
) false u. [rtn] ?
) true . [rtn] ?
) true u. [rtn] ?
) decimal true u. [rtn] ?
) 3 5 - u. [rtn] ?
? Hoeveel bits gaan er per getal op de stack?

Een cel is de hoeveelheid geheugen die een getal als bitpatroon in beslag neemt.

CELLS ( x -- y ) y is het aantal bytes dat nodig is voor x cellen.
) 10 cells . [rtn] ?
? Hoeveel bytes gaan er per getal op de stack?
? Lees de beschijving van het woord LOOP in hoofdstuk 11 nog eens nauwkeurig en bedenk hoevaak er 'ha ' verschijnt als je het woord HOEVAAK? uit zou voeren.
) : HOEVAAK? 0 0 do ." Ha " loop ; [rtn]
 ( Bezint eer gij begint )

15

HERE   Name   Code   Body   CREATE   ALLOT

Forth is een woordenboek waar nieuwe woorden aan toegevoegd kunnen worden. Dat vraagt natuurlijk geheugenruimte. Met het woord HERE kun je zien hoever het woordenboek doorloopt, en waar het geheugengebied dat nog vrij is, begint.

HERE ( - a ) a is het adres waar nieuwe woorden terecht komen. Bij het definiëren van een woord verandert dat adres.
) here . [rtn] ?
) : punt . ; [rtn] ok
) here . [rtn] ?
) here punt [rtn] ?
) forget punt [rtn] ok
) here . [rtn] ?
Een Forth woord bestaat in principe uit drie elementen:
  1. de Name, waardoor het woord terug te vinden is,
  2. de Code, een stukje programma dat het eigenlijke werk doet,
  3. de Body, waar de gegevens, die Code nodig heeft, instaan.
CREATE ccc ( -- ) Maak een woord ccc waarvan de Body nog leeg is.
Een woord dat met CREATE gemaakt is, doet niets anders dan het adres van zijn Body op stack zetten.
) here . [rtn] ?
) create DAAR [rtn]
) here . [rtn] ?
DAAR ( -- Body ) Zet het adres van de Body van DAAR op stack.
) daar . [rtn] ?
Dat is dus HERE
) 10 cells allot [rtn] ok
) here . [rtn] ?
ALLOT ( n -- ) Reserveer n bytes bij HERE

Je beschikt nu over een een gebied van 10 cellen vanaf een adres dat DAAR heet. Je kunt ermee doen wat je wilt, Forth zal er uit zichzelf niet meer aankomen.

[wordt vervolgd]

16

DUMP   ACCEPT   CELL+

DAAR levert een adres. Dan kun je er dus ook @ en ! op loslaten.
) daar @ . [rtn] ?
) 1992 daar ! [rtn] ok
) 1 daar +! [rtn] ok
) daar @ . [rtn] ?
) daar @ daar 8 + ! [rtn] ok
) 7 daar +! [rtn] ok
) daar @ . [rtn] ?
) daar 8 + @ . [rtn] ?
Je moet er zelf voor zorgen dat je niet buiten het gereserveerde gebied komt. Iets als
7 daar 100 - !
zal waarschijnlijk een verrassend, maar zeker een ongewenst effekt hebben.

DUMP ( a n -- ) Laat vanaf adres a de inhoud van een gebied van n bytes zien.
) daar 40 dump [rtn] ?
ACCEPT ( a n -- m ) Ontvang maximaal n karakters via het toetsenbord en noteer die bij adres a.
m is het het aantal karakters dat werkelijk ingevoerd is. De Return toets sluit de invoer af en telt zelf niet mee.
) daar 10 accept [rtn] ...... ok
) . [rtn] ?
) daar 40 dump [rtn] ?
CELL+ ( a1 -- a2 ) a2 is het adres dat een cel verder ligt dan a1.
) daar cell+ 10 accept daar ! [rtn] ...... ok
) daar 40 dump [rtn] ?
[wordt vervolgd]

17

Karakters

ASCII codes voor karakters staan in het geheugen dichter opeengepakt dan bitpatronen voor getallen. In een cel gaan meestal 2 of 4 karakters.

CHAR+ ( a1 -- a2 ) Adres a2 ligt een karakter verder dan a1.
CHARS ( x -- y ) Voor x karakters zijn y bytes nodig.
C@ ( a -- k ) Lees karakter k uit adres a.
C! ( k a -- ) Zet karakter k in adres a.
) daar char+ 10 accept daar c! [rtn] ok
) daar 11 chars dump [rtn] ?
) daar c@ . [rtn] ?
In bovenstaand voorbeeld zet C! een bitpatroon in DAAR dat aangeeft hoeveel karakters er ingevoerd zijn. De programmeur moet zelf bijhouden dat het hier om een (klein) getal gaat en niet om een karakter. C! en C@ onderscheiden zich van ! en @ door de lengte van de bitpatronen die ze verplaatsen. Met de inhoud of de betekenis ervan houden ze zich niet bezig.
Met C! komt niet het volledige bitpatroon dat op stack staat in RAM terecht; het voorste stuk wordt domweg genegeerd. Bij het teruglezen met C@ komen daar weer nullen voor in de plaats.
) -1 daar 8 + c! [rtn] ok
) daar 8 + c@ [rtn] ok
) dup hex . [rtn] ?
) dup binary . [rtn] ?
) decimal . [rtn] ?
? Hoeveel bits leest C@ ?
) daar char+ daar c@ type [rtn] ?
TYPE ( a len -- ) Druk de tekst af die op adres a begint en uit len karakters bestaat.

Korte teksten zet men vaak in het geheugen als een getelde sliert (Counted String). Op de plaats van het eerste karakter staat om hoeveel karakters het gaat, daarachter komt de tekst. Daardoor is het niet nodig om aan het einde een speciaal teken neer te zetten.
) daar count type [rtn] ?
COUNT ( a1 -- a2 k ) Zet de inhoud van de karaktercel bij adres a1 op stack. Het adres a2 is een karakter verder dan a1. Je kunt met COUNT door een tekst heenlopen:
) daar count . [rtn] ?
) count emit [rtn] ?
) count emit [rtn] ?
) count emit [rtn] ? ( enzovoort )
) drop [rtn] ok

18

Controlestructuren nesten

Je zou COUNT aldus kunnen definiëren:
 : COUNT      ( a1 -- a2 k )
   dup        ( a1 a1 )
   char+ swap ( a2 a1 )
   c@         ( a2 k )
 ;
Tussen haakjes vermeld ik steeds de situatie op de stack.

De meeste Forth woorden kun je definiëren met behulp van andere Forth woorden. Nog een voorbeeld ter bestudering:
: TYPE ( a len -- )
  dup 0 >      ( a len groter-dan-nul? )
  if 0         ( a len 0 )
     do        ( a )
        count  ( a+ k )
        emit   ( a+ )
     loop      \ ga terug naar COUNT
  else  drop   \ drop len
  then  drop   \ drop adres
;
\ ( -- ) Negeer de rest van de regel. Dit geldt voor Forth, niet voor de lezer!

In het laatste voorbeeld zie je dat If-Then en Do-Loop samen kunnen gaan. Ze moeten dan wel zoals dat heet "genest" zijn. Je kunt middenin een If-Then struktuur aan een Do-Loop beginnen, maar die Do-Loop moet volledig afgehandeld zijn voordat je de If-Then afmaakt.
De volgorde .. IF .. DO .. THEN .. LOOP .. is dus niet mogelijk.
Algemeen geldt voor If-Else-Then, Begin-Until, Begin-While-Repeat en Do-Loop dat je ze in een definitie onbeperkt kunt nesten: Je mag op elk moment aan zo'n struktuur beginnen, maar je mag alleen verder gaan aan de onafgewerkte struktuur die als laatste begon.

? Zoek de fouten:
  1. .. IF .. IF .. THEN .. THEN ..
  2. .. IF .. THEN .. IF .. THEN ..
  3. .. BEGIN .. IF .. UNTIL .. THEN ..
  4. .. DO .. IF .. BEGIN .. UNTIL .. THEN .. LOOP ..
  5. .. IF .. IF .. THEN .. ELSE .. IF .. ELSE .. THEN .. THEN ..

19

Delen

) 29 10 /mod .s [rtn] ?
) . . [rtn] ?
/MOD ( x y -- r   ) Deel x door y.
  (een geheel getal!) is het resultaat, en r is de rest. Uitspraak: "slash-mod".
) 29 10 / . [rtn] ?
) 29 10 mod . [rtn] ?
/ ( x y --   ) Deel x door y.
  is het  uotient, een geheel getal!
MOD ( x y -- r ) Deel x door y.
r is de rest. MOD is een afkorting van de wiskunde term Modulo.

Bij deze vorm van delen zijn alleen gehele getallen betrokken.

- Je weet de prijzen van verschillende artikelen zonder BTW en je wilt de prijzen met BTW (17%) berekenen:
) : MET1 ( prijs-zonder-BTW -- )   100 / 117 * . ;
) : MET2 ( prijs-zonder-BTW -- )   117 * 100 / . ;
? Welke methode (proberen!) voldoet het beste, MET1 of MET2 ? Kun je de verschillen verklaren?

Beter is:
) : MET3 ( netto-prijs -- )   117 100 */ . ;
*/ ( x y z -- x*y/z ) Het voordeel van */ boven een losse * en / is, dat bij */ het tussenresultaat x*y een getal (een bitpatroon) mag worden dat te groot (te lang) is voor de stack. Uitspraak: "star-slash".

Nog beter is:
value PERCENTAGE   17 to percentage !
: MET ( netto-prijs -- eindbedrag)
  100 percentage +   100 */ ;
: .MET met . ;
MET is niet meer afhankelijk van de hoogte van de BTW. Je kunt er zelfs kortingen mee berekenen:
) -15 to percentage
) 1745 .met [rtn] ?
) 495 .met [rtn] ?
) 1245 met .met [rtn] ?  ( dubbele korting )

20

AND   OR   XOR

*/MOD ( x y z -- rest x*y/z )
) 4 5 6 */mod . . [rtn] ?
Overzicht van de delingen tot nu toe:  */MOD  */  /MOD  / en MOD

In het volgende hoofdstuk wil ik een wat ingewikkelder woord gaan maken dat de verhouding tussen twee getallen afdrukt als een decimale breuk. Ter voorbereiding eerst nog een paar nieuwe woorden.

AND ( x y -- z ) x en y worden bit voor bit ge-AND. Een z-bit is 1 als het x-bit en het y-bit beide 1 zijn. Zo ook:
OR ( x y -- z ) Een z-bit is 1 als het x-bit en het y-bit niet beide 0 zijn.
XOR ( x y -- z ) Een z-bit is 1 als het x-bit en het y-bit verschillend zijn.
) : NEGATIEF? ( x y -- vlag )
)   xor 0 < ;
? Wat is het verband tussen de vlag die NEGATIEF? levert en het teken van x en y ? 2DROP ( x y -- ) Verwijder 2 getallen van de stack.
2DUP ( x y -- x y x y )
Je kunt 2DROP als volgt definiëren:
: 2DROP DROP DROP ;
? Hoe zal de definitie van 2DUP eruit zien?

) : STIP ." ." ;
? Wat doet STIP?

.R ( x n -- ) Druk het getal x af, rechtsgericht in een veld van n posities, zonder afsluitende spatie. Als het getal daar niet in past, wordt het toch volledig afgedrukt. (punt r)
) cr 1 4 .r [rtn] ?
) cr 20 4 .r [rtn] ?
) cr 300 4 .r [rtn] ?
) cr 2000 4 .r [rtn] ?
) cr 10000 4 .r [rtn] ?
) cr 7 0 .r 8 0 .r [rtn] ?
ABS ( x -- y ) y is de absolute waarde van x.
[wordt vervolgd]

22

Primitieven   TUCK   NIP   SEE

Sommige woordparen komen zo vaak voor dat er aparte woorden voor zijn.
: 1+ 1 + ;    : 1- 1 - ;     : TUCK swap over ;
: 2* 2 * ;    : 2/ 2 / ;     : NIP swap drop ;
: 0> 0 > ;    : 0< 0 < ;
: 0= 0 = ;    : 0<> 0 <> ;
? Geef het stackeffect van deze woorden.

In de praktijk zijn deze woorden vaak niet met de dubbele punt gemaakt, maar rechtstreeks in machinetaal geschreven. Dergelijke woorden noemt men primitieven (Lo-level woorden). Ze zijn afhankelijk van de microprocessor. Vooral als woorden een elementaire taak verrichten en formuleerbaar zijn met slechts enkele assemblerkommando's, zullen het primitieven zijn. Voorbeelden: + - SWAP DUP DROP OVER ! @ +! = TRUE FALSE <> U< CELL+ C! C@ enz. Ook COUNT waar ik een paar hoofdstukken terug een Hi-level definitie van heb gegeven, zal meestal een primitieve zijn.

Dubbele punt woorden (Hi-level woorden) zijn gemaakt met reeds bestaande Forth woorden en onafhankelijk van de microprocessor analyseerbaar. In sommige Forth's vind je daarvoor het woord SEE.
SEE ccc ( -- ) SEE gevolgd door de naam van een Hi-level woord geeft inzage in de bouw van dat woord.

23

Algoritme voor GGD

De grootste gemene deler van twee getallen x en y is het grootste getal (GGD) waarvoor de delingen x/GGD en y/GGD opgaan, d.w.z. nul als rest hebben. Als je de breuk x/y wilt vereenvoudigen, moet je teller en noemer door hun GGD delen.

Algorithme (recept) om de GGD van twee getallen te vinden.
  1. Noem het grootste getal g en het kleinste getal k.
  2. Als g=0 dan GGD=1.
  3. Als k=0 dan GGD=g.
  4. Vervang g door de rest die onstaat als je g door k deelt.
  5. Ga naar 1.
: GGD ( x y -- ggd )
  2dup or 0= if drop 1 then
  abs
  swap abs
  2dup < if swap then
  begin dup
  while
     tuck
     mod
  repeat
  drop ;
? Beantwoord de volgende vier vragen door met de hand, d.w.z. met potlood en papier en zonder computer, het programma woord voor woord uit te voeren.
  1. Wat is het resultaat als x=0 en y=0 ?
  2. Wat is het resultaat als x=0 en y=5 ?
  3. Wat is het resultaat als x=-4 en y=0 ?
  4. Wat is het resultaat als x=3 en y=-6 ?
? Noteer achter iedere regel de situatie op de stack.

? Geef nauwkeurig aan welke delen van het programma korresponderen met de vijf regels van de algorithme.

Dringend advies: Zet bij het bestuderen van een definitie altijd achter elke regel wat er op de stack staat.

24

>R   R>   R@

Behalve de gewone stack heeft Forth nog de Return stack.
Als het ingewikkeld dreigt te worden op de gewone stack is het handig om even 1 of 2 getallen op de Return stack te zetten.
>R ( x -- ) ( R: -- x ) Zet x op de Return stack.
R> ( -- x ) ( R: x -- ) Haal x weer van de Return stack af.
R@ ( -- x ) ( R: x -- x ) Lees de x die op de Return stack staat.

Voorbeeld, eerst weer zonder kommentaar, daarna met.
\ Vereenvoudig de breuk x1/y1 tot x2/y2.
: VEREENVOUDIG ( x1 y1 -- x2 y2 )
  2dup ggd
  tuck
  /
  >r
  /
  r>
;
? Zet, voordat je verder leest, de situatie op stack achter iedere regel.
\ Vereenvoudig de breuk x1/y1 tot x2/y2.
: VEREENVOUDIG ( x1 y1 -- x2 y2 )
  2dup ggd     ( x1 y1 ggd )
  tuck         ( x1 ggd y1 ggd )
  /            ( x1 ggd y2 )
  >r           ( x1 ggd )   ( r: y2 )
  /            ( x2 )       ( r: y2 )
  r>           ( x2 y2 )
;
Omdat ik VEREENVOUDIG zo'n lang woord vind, geef ik hem een kortere naam.
) : VV vereenvoudig ;
) 1234 2468 vv . . [rtn] ?
De woorden : en ; maken gebruik van de Return stack. DO I en LOOP meestal ook. Daarom gelden de volgende beperkingen:
  1. >R en R> moeten genest zijn t.o.v. : ; en DO LOOP.
  2. >R en R> mogen alleen binnen een definitie voorkomen.
  3. In een Do-Loop geeft I tussen >R en R> een onjuiste waarde.

25

Herdefiniëren

) : PLUS + ;
) : + ( x y -- )
)   2drop cr ."  vandaag wordt er niet opgeteld " ;
Tijdens het definiëren van + zal Forth meedelen dat er al een definitie met de naam + bestaat. Dat is geen foutmelding. Het is slechts een mededeling voor de programmeur. Het woord wordt wel gebouwd.
Aangezien nieuwe woorden achteraan de woordenlijst worden toegevoegd en Forth altijd achteraan begint te zoeken, kun je vanaf nu alleen nog de nieuwe betekenis van + aanroepen.
) 4 5 + . [rtn] ?
De oude betekenis bestaat nog wel en definities waar de oude + in voorkomt blijven op de oude manier werken.
) 4 5 plus . [rtn] ?
Na
) FORGET + [rtn] ok
zal de oude + weer aanspreekbaar zijn, want FORGET heeft alleen de nieuwe + gevonden.
) 4 5 + . [rtn] ?
? Wat zal het gevolg zijn van de volgende definitie?
: FORGET ."  Nooit! " ;
In de programmeertaal Forth heb je grote vrijheden. Het devies is: alles moet mogelijk zijn. De prijs daarvan is, dat je niet gewaarschuwd kunt worden als je iets onhandigs doet. De programmeur draagt de verantwoordelijkheid voor wat hij doet en moet daarom ook begrijpen wat hij doet.
De een vindt het vervelend dat je steeds moet beseffen wat je aan het doen bent, en wenst door een PIEP - Syntax Error of wat voor Error dan ook, of op een struktureler manier, tegen zichzelf beschermd te worden. De ander bejubelt het feit dat Forth de volledige toegang verschaft tot de computer, "je kunt overal aankomen".

28

Dubbelgetallen I

Het is in Forth mogelijk om met getallen te werken die meer ruimte innemen dan een cel. Zulke getallen, Double Numbers, hebben slechts een waarde, maar nemen twee plaatsen op de stack in beslag.

S>D ( x -- xlo xhi ) Breid een Signed Single Number uit tot een Signed Double Number met dezelfde waarde. (xlo=x)
D. ( xlo xhi -- ) Druk de laatste twee getallen op de stack samen af als een dubbelgetal.
) 3 S>D .S [rtn] ?
) D. [rtn] ?
) -3 S>D .S D. [rtn] ?
Als je een punt gebruikt bij de invoer van een getal maakt Forth er een dubbelgetal van.
) 30. .S [rtn] ?
) D. [rtn] ?
) -30. .S D. [rtn] ?
) 3.0 .S D. [rtn] ?
) .30 .S D. [rtn] ?
De plaats van de punt in het getal is niet van belang.

D>S ( xlo xhi -- x ) Kort een dubbelgetal in tot een gewoon getal. Omdat er geen foutmelding komt als de waarde niet in een cel past, kan de waarde hierdoor veranderen.
) 3. D>S .S . [rtn] ?
Meer woorden voor dubbelgetallen.

2DROP ( xlo xhi -- )
2DUP ( ? )
2OVER ( ? )
2SWAP ( ? )
2ROT ( ? )
D+ ( xlo xhi ylo yhi -- zlo zhi ) z is de som van x en y.
D- ( ? )
D2* ( xlo xhi -- ylo yhi )
D2/ ( xlo xhi -- ylo yhi )
D= ( ? )
D< ( ? )
DNEGATE ( ? )

? Zoek aan de hand van zelf te maken voorbeelden nauwkeurig uit wat bovenstaande woorden doen.

29

Dubbelgetallen II

Een mogelijke definitie van S>D is
: S>D DUP 0< ;
Bij een negatief getal wordt er True voorgezet, anders False.
) 3 S>D .S [rtn] ?
) DNEGATE .S D. [rtn] ?
) -3 S>D .S [rtn] ?
) DNEGATE .S D. [rtn] ?
Definitie van D>S
: D>S DROP ;
De voorste cel wordt domweg weggegooid.

Voorbeeld.
Een woord om te onderzoeken of de waarde van een dubbelgetal in een cel past.
: ENKEL? ( xlo xhi -- xlo xhi vlag )
  over s>d   ( xlo xhi  xlo xhi' )
  2over      ( xlo xhi  xlo xhi' xlo xhi )
  d= ;       ( xlo xhi vlag )
Dat kan korter.
: ENKEL? ( xlo xhi -- xlo xhi vlag )
  over 0<     ( xlo xhi xhi' )
  over = ;
? Wat is de betekenis van de vlag die ENKEL? levert?

Voor puzzelaars:
? Van een Unsigned Number maak je een dubbelgetal door er nul voor te zetten.
Leg uit, waarom MAX-N het grootste (positieve) Signed enkelgetal geeft.
) : MAX-N -1 0 D2/ DROP ;
? Leg uit, waarom MIN-N het kleinste (negatieve) Signed enkelgetal geeft.
) : MIN-N 0 1 D2/ DROP ;
? Maak zo ook de woorden MAX-DN en MIN-DN voor dubbelgetallen.
Hint: hoe zien deze getallen er hexadecimaal uit?

31

Ingezonden brief van de immediate woorden

Geachte heer Nijhof,

Tijdens een vergadering van de VFW, de vakbond van Forth woorden, kwam uw kursus ter sprake. In positieve zin overigens. Maar, laat ik meteen ter zake komen.

In hoofdstuk 4, bij de bespreking van het woord PILS, schrijft u dat bij het maken van definities de woorden in een lijst achter de nieuwe naam komen te staan. Dat is een wel erg simpele voorstelling van zaken, die sommigen van ons tekort doet. Het is inderdaad waar, dat de meeste Forth woorden zich zo passief opstellen, dat ze zich tijdens het Definiëren zonder meer in een tabel laten opnemen.
Echter, de leden van mijn afdeling, de Immediate words, denken er anders over. Wij laten ons niet zo maar in een tabel zetten. Sterker nog, sommigen van ons, waaronder ikzelf (mijn naam is Dot Quote), zijn van de Compile groep; wij zelf houden ons bezig met het samenstellen van die lijst. Persoonlijk heb ik bijvoorbeeld de taak om, als ik opgeroepen wordt, een typiste in de tabel te zetten, daarna de tekst die binnenkomt te doorzoeken tot ik een aanhalingsteken vind en vervolgens dat stuk tekst achter de typiste te plaatsen. Wij noemen dat Compileren.

(Mag ik tussendoor even opmerken dat programmeurs erg slordig kunnen zijn. Het komt nl. voor dat er geen aanhalingsteken te bekennen is. Daar zeur ik dan niet over. Ik neem gewoon de gehele resterende tekst op. Men zij gewaarschuwd. Misschien kunt u daar in uw kursus iets over zeggen.)

Het is de bedoeling dat bij het uitvoeren van het nieuwe woord de typiste de tekst typt. Ik heb dus mijn ondergeschikten. Mijn werk is wel iets ingewikkelder dan u suggereert!
In mijn vorige werkkring had ik bovendien tot taak om, als ik buiten een definitie om opgeroepen werd, de tekst direkt zelf uit te typen. De Klantenservice had daar een handje van en men had er helaas ook altijd haast. Maar daar waren destijds meer dingen niet goed geregeld.
Gelukkig hoef ik dat nu niet meer. Mijn kollega  .(  (Dot Paren) heeft die taak van mij overgenomen, met dit verschil dat zij naar een haakje sluiten zoekt in plaats van naar een aanhalingsteken. Ik gun ieder zijn eigenaardigheden. Zij is wel een Immediate word, in dienst van de Klantenservice en een snelle typiste, maar van Compileren heeft ze geen verstand. En ik ben nu Only Compiling.

Mocht u meer willen weten over de verschillende Immediate woorden, dan raad ik u aan om ze persoonlijk te raadplegen. Zij zijn gaarne bereid om u inzicht te verschaffen in hun werkzaamheden.

Hoogachtend, namens de Immediate words,
     ."  (Dot Quote)

32

CHAR   [CHAR]

) : DOT-QUOTE ." waterval " ; [rtn] ?
) SEE DOT-QUOTE [rtn] ?
) DOT-QUOTE [rtn] ?
)
) : DOT-PAREN .( Tot Ziens! ) ; [rtn] ?
) SEE DOT-PAREN [rtn] ?
) DOT-PAREN [rtn] ?
De programmatekst die Forth te verwerken krijgt, heet de invoerstroom.
." en .( lezen zelf tekst van de invoerstroom. Omdat ze Immediate zijn, doen ze dat ook tijdens het definiëren.

CHAR ( ccc -- k ) Lees het volgende woord van de invoerstroom en zet de ASCII code van de eerste letter op stack.
) CHAR A . [rtn] ?
) CHAR a . [rtn] ?
) CHAR . . [rtn] ?
Omdat CHAR niet Immediate is, doet hij tijdens het definiëren nog niets. Pas als het nieuwe woord uitgevoerd wordt, leest CHAR de invoerstroom.
) : LETTER ( ccc -- )
)   ." heeft ASCII code "
)   CHAR . ;
)   LETTER A [rtn] ?
)   LETTER a [rtn] ?
CHAR heeft een variant die [CHAR] heet.

[CHAR] ( ccc -- ) Zet tijdens het compileren de ASCII code van de eerste letter van ccc als een getal in de definitie. Omdat [CHAR] Immediate is, komt hij tijdens het compileren in aktie. Buiten een definitie heeft hij geen betekenis.
) : A-KODE ( -- k )
)   65 ;
) : KODE-A ( -- k )
)   [CHAR] A ;
) SEE A-KODE [rtn] ?
) SEE KODE-A [rtn] ?
Voor lezers die geen SEE in hun Forth hebben: A-KODE en KODE-A leveren niet alleen hetzelfde resultaat, ze zijn ook hetzelfde gebouwd.
[CHAR] A
zet tijdens het definiëren het getal 65 in de definitie.

33

IMMEDIATE   Besturingswoorden   ?DO   LEAVE

Je kunt zelf natuurlijk ook Immediate woorden maken.
IMMEDIATE ( -- ) Maak het laatst gedefinieerde woord Immediate.
) : [.S] ( -- )
)   .S ; IMMEDIATE
.S laat de getallen op stack zien. [.S] doet hetzelfde, maar dan tijdens het compileren, en laat geen sporen na in de definitie.
) 5 5555 [rtn] ok
) : TEST1 .S ; [rtn] ?
) : TEST2 [.S] ; [rtn] ?
) TEST1 [rtn] ?
) TEST2 [rtn] ?
) .S [rtn] ?
) [.S] [rtn] ?
) SEE TEST1 [rtn] ?
) SEE TEST2 [rtn] ?
? Kun je aannemelijk maken waarom ; een Immediate woord zal zijn?

Samenvattend:
  1. Immediate woorden zijn woorden die zich niet laten compileren. Zij worden tijdens het definiëren uitgevoerd.
  2. Compiler woorden zijn woorden die tijdens het definiëren (compileren) invloed op het nieuw te maken woord hebben. Om dat te kunnen moeten ze wel Immediate zijn. Buiten een definitie zijn ze zinloos.
." is een Compiler woord, en dus ook Immediate. Only Compiling.
.( is Immediate, maar geen Compiler woord en heeft geen effekt op definities.

IF ELSE THEN BEGIN UNTIL WHILE REPEAT DO LOOP enz. noemt men besturingswoorden. Zij organiseren tijdens het compileren de vertakkingen. Besturingswoorden zijn dus Compiler woorden, onbruikbaar buiten definities.

Nieuwe besturingswoorden.
?DO ( n2 n1 -- ) Bijna hetzelfde als DO.
Het verschil is dat ?DO naar het eerste woord achter LOOP springt als n2 gelijk is aan n1.
Deze uitleg van ?DO is heel slordig. Juister is:
?DO ( -- ) (compilerend:) Installeer ?LUSJESMAN in de definitie.
?LUSJESMAN ( n2 n1 -- ) (uitvoerend:) Begin aan een lus, tenzij n1=n2.

LEAVE ( -- ) Ga naar het eerste woord achter LOOP (alleen tussen DO en LOOP te gebruiken).