HCC!Forth
27 nov 2002


Programmeren in Forth

Albert Nijhof
1. Rekenen in Forth
2. Programmeren in Forth
3. Definiërende woorden
4. Compilerende woorden

Rood en wit

Antonio heeft een winkeltje. Hij verkoopt wijn. Er komt iemand binnen die 2 flessen wil.
Antonio typt:
 nul
 1 rood
 1 wit
 totaal
Forth reageert:
 0  ok
 990  ok
 890  ok
 1880  ok
Hij gebruikt daarvoor het volgende Forth programma:

 990 value ROOD-PRIJS         \ Prijs per fles rode wijn
 890 value WIT-PRIJS          \ Prijs per fles witte wijn

: ERBIJ    * dup . + ;      \ Bedrag afdrukken en bijtellen

: ROOD rood-prijs erbij ;
: WIT  wit-prijs erbij ;

: NUL      0 dup . ;        \ Begin met subtotaal nul.
: TOTAAL   . ;              \ Einde van de bestelling
Na een \ is de rest van de regel commentaar.
Namen van nieuwe definities zijn voor de duidelijkheid vet gedrukt.

Woorden definiëren

Een commando heet in Forth een «woord».

Programmeren in Forth betekent NIEUWE WOORDEN DEFINIËREN.

Woorden definiëer je met «definiërende woorden». In bovenstaand programma komen twee van die definiërende woorden voor:

value

 990 value ROOD-PRIJS 
ROOD-PRIJS is een soort variabele. Als je het later uitvoert (intypt en dan op [enter] drukt), zet het 990 op stack.

:

 : ERBIJ    * dup . + ; 
ERBIJ is een soort macro. Als je het later intypt wordt de reeks commando's
 * dup . + 
uitgevoerd. Punt-komma is de afsluiter.

Omdat je in een Forth-programma steeds nieuwe woorden maakt kan het er heel nederlands uit gaan zien, want niemand verplicht je om voor de namen engelse woorden te kiezen.
Forth kent meer definiërende woorden dan deze twee en je kunt er natuurlijk ook zelf definiërende woorden bijmaken.

ERBIJ van dichtbij

Het woord ERBIJ is het interessantste nieuwe woord van het bovenstaande programma. Het verwacht 3 getallen op de stack:
  • subtotaal
  • aantal flessen
  • prijs per stuk
ERBIJ vermenigvuldigt de stuksprijs met het aantal-flessen (*), zet dat bedrag tweemaal op de stack (DUP), drukt het ene af (.) en telt het andere bij het subtotaal op (+).
Alleen dat subtotaal blijft achter op de stack. In Forth geef je dat aan met een stackdiagram:
 ERBIJ ( subtotaal1 #flessen stuksprijs -- subtotaal2 )
Het valt op dat er in ERBIJ alleen bewerkingen staan. Je ziet geen verwijzingen naar de te bewerken data, want de gegevens worden naamloos op de stack verwacht. ERBIJ komt voor in ROOD en WIT en het is nauwelijks zinvol om het interactief (bij de klant in de winkel) te gebruiken.
Natuurlijk is het wel interactief te gebruiken, bijvoorbeeld om het te testen. Zorg er dan voor dat er geschikte data op de stack klaarstaat:
1000 25 105 erbij [enter] 2625
. [enter] 3625
D.w.z., 25 dingen van 105 kosten 2625, en het subtotaal dat 1000 was, is 3625 geworden.

Wat gebeurt er nu eigenlijk?

In onderstaand schema kun je de nesting van Forth-woorden en stack-data van de bestelling van 1 ROOD en 1 WIT tot in details volgen.

Stack vóór Actie Stack na

nul( : nul 0 dup . ; )
  0 0 (subtotaal = 0)
0 dup 0 0
0 0 . 0 (0 werd afgedrukt)

0 1 0 1
rood( : rood rood-prijs erbij ; )
0 1 rood-prijs 0 1 990
erbij : erbij * dup . + ;
0 1 990* 0 990
0 990 dup 0 990 990
0 990 990. 0 990 (990 werd afgedrukt)
0 990 + 990 (subtotaal)

990 1 990 1
wit( : wit wit-prijs erbij ; )
990 1 wit-prijs 990 1 890
erbij ( : erbij * dup . + ; )
990 1 890* 990 890
990 890dup 990 890 890
990 890 890. 990 890 (890 werd afgedrukt)
990 890+ 1880 (subtotaal)

totaal( : totaal . ; )
1980 .   (1980 werd afgedrukt)

Olie, azijn en dozen

Antonio verkoopt ook olijfolie en azijn. Bovendien zijn alle artikelen per doos van 12 stuks verkrijgbaar. Op dozen geeft Antonio korting: 12 halen, 11 betalen.

 490 value OLIE-PRIJS   \ Prijs per fles olijfolie
  90 value AZIJN-PRIJS  \ Prijs per fles azijn

: OLIE   olie-prijs erbij ;
: AZIJN  azijn-prijs erbij ;

: DOZIJN  11 * ;  \ 12 halen 11 betalen

Een klant wil 4 flessen olijfolie, 3 dozen met 12 flessen azijn en twee flessen rode wijn:

 nul
 4 olie
 3 dozijn azijn
 2 rood
 totaal
 0  ok
 1960  ok
 2970  ok
 1980  ok
 6910  ok

Dit vindt Antonio nog te veel typewerk. Hij bedenkt afkortingen:

: RW   rood ;
: WW   wit ;
: OL   olie ;
: AZ   azijn ;

: DOZ  dozijn ;
: N    nul ;
: TT   totaal ;

Nu ziet dezelfde bestelling er zo uit:

Antonio:
 n
 4 ol
 3 doz az
 2 rw
 tt
Forth:
 0  ok
 1960  ok
 2970  ok
 1980  ok
 6910  ok

Kort, korter, kortst

Het hele programma samengevat, met stackdiagrammen:
Forth negeert ( stack-diagram ) en \ commentaar.

 990 value RW-PRIJS ( -- x ) \ Prijs per fles rode wijn
 890 value WW-PRIJS          \ Prijs per fles witte wijn
 490 value OL-PRIJS          \ Prijs per fles olijfolie
  90 value AZ-PRIJS          \ Prijs per fles azijn
: ERBIJ ( st1 x y -- st2 ) * dup . + ;

: ROOD ( st1 x -- st2 ) rw-prijs erbij ;
: WIT                   ww-prijs erbij ;
: OLIE                  ol-prijs erbij ;
: AZIJN                 az-prijs erbij ;
: NUL    ( -- 0 )     0 dup . ; \ Begin met subtotaal nul.
: TOTAAL ( st -- )    . ;       \ Einde van de bestelling
: DOZIJN ( x1 -- x2 ) 11 * ;    \ 12 halen, 11 betalen

: RW ( st1 x -- st2 ) rood ;
: WW                  wit
: OL                  olie ;
: AZ                  azijn ;
: N   ( -- 0 )     nul ;
: TT  ( st -- )    totaal ;
: DOZ ( x1 -- x2 ) dozijn ;

Als je direct de korte woorden definiëert en de lange achterwege laat, blijft er dit over:

 990 value RW-PRIJS ( -- x ) \ Rode wijn
 890 value WW-PRIJS          \ Witte wijn
 490 value OL-PRIJS          \ Olijfolie
  90 value AZ-PRIJS          \ Azijn
: ERBIJ ( st1 x y -- st2 ) * dup . + ;

: RW ( st1 x -- st2 ) rw-prijs erbij ;
: WW                  ww-prijs erbij ;
: OL                  ol-prijs erbij ;
: AZ                  az-prijs erbij ;
: N   ( -- 0 )     0 dup . ;
: TT  ( st -- )    . ;
: DOZ ( x1 -- x2 ) 11 * ; \ 12 halen, 11 betalen

En in de beruchte onleesbare stijl, onmenselijk kort, maar probleemloos voor Forth ...

990 value RW-PRIJS 890 value WW-PRIJS 490 value OL-PRIJS 90 value AZ-PRIJS : ERBIJ * dup . + ; : RW rw-prijs erbij ; : WW ww-prijs erbij ; : OL ol-prijs erbij ; : AZ az-prijs erbij ; : N 0 dup . ; : TT . ; : DOZ 11 * ;

... liever niet doen dus.

Re-entrant

Er is een klant in de winkel. Zij neemt 1 fles olijfolie en aarzelt of ze nog meer aan zal schaffen. Dan komt er iemand binnen die kennelijk haast heeft. Hij wil 2 flessen olijfolie en een fles azijn, rekent 1070 af en verdwijnt weer. De andere klant heeft ondertussen haar besluit genomen en wenst nog 1 fles rode wijn. Zij betaalt 1480.

 n
 1 ol

n 2 ol 1 az tt
1 rw tt
 0  ok       (klant 1)
 490 ok

0 ok (klant 2) 980 ok 90 ok 1070 ok (totaal 2)
990 ok 1480 ok (totaal 1)

Idiot proof

Als je niet wilt dat de gebruiker direct in de Forth-omgeving werkt (met alle vrijheden en dus risico's van dien) moet je het accepteren en interpreteren van toetsaanslagen expliciet in het programma regelen. Je kunt dan precies vastleggen wat de gebruiker moet en mag doen.

Het programma start met

 KASSA [enter] 
De gebruiker blijft deze cyclus van mogelijkheden doorlopen totdat hij een keer op [escape] drukt. Regels tussen haakjes zijn optioneel:
  • ( [escape] Verlaat het programma. )
  • ( T Druk totaal af en sta klaar voor nieuwe klant. )
  • «aantal» Een getal van 1..99. Andere tekens worden genegeerd.
  • ( D Het gaat om dozen i.p.v. flessen. )
  • R,W,O of A Productkeuze. Andere tekens annuleren de post.

 create PRODUCTENLIJST ( -- adr )
 char R , 990 , char W , 890 , char O , 490 , char A ,  90 ,
 char ? ,   0 ,
 4 constant #PRODUCTEN ( -- x )

: ERBIJ   ( st1 x y -- st2 ) * dup . + ;
: PRODUCT ( st x ch -- st )  productenlijst #producten 0
   do 2dup @ = if leave then cell+ cell+ loop
   nip @+ emit space @ erbij ;

: TOTAAL  ( st 0 ch -- )        emit space drop . ;
: 1..9?   ( ch -- ch vlag )     dup [char] 1 - 9 u< ;
: 0..9?   ( ch -- ch vlag )     dup [char] 0 - 10 u< ;
: CIJFER  ( st x1 ch -- st x2 ) dup emit [char] 0 - swap 10 * + ;
: DOOS    ( st x1 ch -- st x2 ) emit 11 * ; 
: ?ESCAPE ( st 0 ch -- .. )     27 over = if abort then ;

: N ( -- ) 0    \ subtotaal
  BEGIN cr 0    \ x
     BEGIN key ?escape                      \ uitgang via [escape]
        [char] T over = if totaal exit then \ T
        1..9? and ?dup UNTIL cijfer key     \ 1..9
     0..9? if cijfer key then               \ 0..9
     [char] D over = if doos key then       \ D
     space product                          \ R,W,O,A of ..
  AGAIN ;
: KASSA ( -- ) begin n again ;

Een paar verduidelijkingen:

  • KEY ( -- ASCII ) Wacht op een toetsindruk.
  • EMIT ( ASCII -- ) Stuur een teken naar het scherm.
  • = ( x y -- vlag ) Kijk of x=y en antwoord met TRUE of FALSE op stack.
  • Let op de controlestructuren in Forth: IF en UNTIL verwachten een vlag op stack. Daar reageren ze op!
    • regent-het? IF paraplu-opzetten THEN doorlopen
      (Ik loop niet door omdat ik mijn paraplu opzet, ik zet mijn paraplu op omdat het regent)
    • BEGIN geld-verdienen, geld-genoeg? UNTIL vakantie!
    • BEGIN draaien AGAIN (eindeloos)
  • De productenlijst is gemakkelijk uit te breiden.
  • Als je ?escape uit het programma schrapt, kan de gebruiker niet eens terugkomen in Forth.

1. Rekenen in Forth
2. Programmeren in Forth
3. Definiërende woorden
4. Compilerende woorden


© 2007 HCC!Forth