<!doctype linuxdoc system> 
<linuxdoc>
<!-- $Id: Lex-YACC-HOWTO-NL.sgml,v 1.1 2003/07/07 18:37:39 bokkie Exp $
 -->

<article>

<!-- Title information -->
<titlepag>
<title>Lex en YACC inleiding/HOWTO</title>
<author><name>PowerDNS BV (bert hubert &lt;bert at powerdns.com&gt;),&nl;
vertaald door Paul Boekholt (p.boekholt at hetnet.nl)&nl;</name></author>
<date>v0.8 $Date: 2003/07/07 18:37:39 $</date>
<abstract>
Dit document dient om je op weg te helpen met het gebruik van Lex en YACC
</abstract>
</titlepag>
<!-- Table of contents -->
<toc>

<!-- Begin the document -->

<sect><heading>Inleiding</heading>
<p>
Welkom beste lezer.
</p>
<p>
Als je enige tijd in een Unix omgeving hebt geprogrammeerd, ben je
vast de mystieke programma's Lex &amp; YACC, of Flex &amp; Bison,
zoals ze wereldwijd genoemd worden door GNU/Linux gebruikers,
tegengekomen, Flex zijnde een implementatie van Lex door Vern Paxon en
Bison zijnde de GNU versie van YACC. We zullen deze programma's verder
Lex en YACC noemen - de nieuwere versies zijn opwaarts compatible, dus
je kunt Flex en Bison gebruiken bij het proberen van onze programma's.
</p>

<p>
Deze programma's zijn waanzinnig nuttig, maar net als bij je C
compiler legt de manpage de taal die ze verstaan niet uit, noch hoe ze
te gebruiken. YACC is echt fantastisch wanneer gebruikt met Lex,
echter, de Bison manpage beschrijft niet hoe je door Lex gegenereerde
code integreert met je Bison programma.
</p>

<sect1><heading>Wat dit document NIET is</heading>
<p>
Er zijn geweldige boeken over Lex &amp; YACC. Lees in ieder geval deze
boeken als je meer wilt weten. Ze geven veel meer informatie dan wij
ooit kunnen. Zie de sectie "Lees Verder" aan het eind. Dit document is
bedoeld om je op weg te helpen bij het gebruik van Lex &amp; YACC, om
je in staat te stellen je eerste programma's te maken.
</p>
<p>
De documentatie die bij Flex en Bison hoort is ook uitstekend, maar
geen leerboek. Maar ze vormen een aardige aanvulling op mijn HOWTO. Zie
de verwijzingen aan het einde.
</p>
<p>
Ik ben zeker geen YACC/Lex expert. Toen ik begon dit document te
schrijven, had ik precies twee dagen ervaring. Mijn enige doel is die
twee dagen makkelijker voor je te maken.
</p>
<p>
Verwacht niet de juiste YACC en Lex stijl in deze HOWTO aan te
treffen. Voorbeelden zijn uiterst eenvoudig gehouden en er zijn
misschien betere manieren om ze te schrijven. Als je weet hoe, laat
het me dan weten.
</p>
</sect1>

<sect1><heading>Spul downloaden</heading>
<p>
Merk op dat je al de gegeven voorbeelden kunt downloaden in
machine-leesbare vorm. Zie de <url name="homepage"
url="http://ds9a.nl/lex-yacc">voor details.
</p>
</sect1>

<sect1><heading>Licentie</heading>
<p>
Copyright (c) 2001 by Bert Hubert. Dit materiaal mag alleen verspreid
worden onder de voorwaarden van de Open Publication License, vX.Y of
later (de laatste versie is te vinden op 
http://www.opencontent.org/openpub/).
</p>
</sect1>
</sect>

<sect><heading>Wat Lex &amp; YACC voor je kunnen doen</heading>
<p>
Bij juist gebruik maken deze programma's het mogelijk met gemak
complexe talen te scannen. Dit is een groot voordeel als je een
configuratiebestand wilt lezen, of een compiler wilt schrijven voor
welke taal dan ook die je (of iemand anders) misschien hebt
uitgevonden.

Met een beetje hulp, die dit document hopelijk biedt, zul je nooit
meer met de hand een parser hoeven te schrijven - Lex &amp; YACC zijn
de gereedschappen om dit te doen.
</p>

<sect1><heading>Wat ieder programma op zichzelf doet</heading>
<p>
Hoewel deze programma's uitblinken wanneer ze samen gebruikt worden,
hebben ze ieder een eigen doel. Het volgende hoofdstuk legt uit wat
ieder onderdeel doet.
</p>
</sect1>
</sect>

<sect><heading>Lex</heading>
<p>
Het programma Lex genereert een zogenaamde `Lexer'. Dit is een functie
die een stroom karakters als input neemt, en steeds als het een groep
karakters ziet die met een sleutelwaarde overeenkomen, een bepaalde
actie onderneemt. Een heel eenvoudig voorbeeld:

<tscreen><verb>
%{
#include <stdio.h>
%}

%%
stop	printf("Stop commando ontvangen\n");
start	printf("Start commando ontvangen\n");
%%

</verb></tscreen>

De eerste sectie, tussen de %{ en %} wordt direct overgenomen in het
gegenereerde programma. Dit is nodig omdat we later printf gebruiken,
wat gedefinieerd is in stdio.h.

Secties worden gescheiden door `%%', dus de eerste regel van de tweede
sectie start met de `stop' sleutel. Iedere keer dat de `stop' sleutel
tegengekomen wordt in de input, wordt de rest van de regel (een
print() call) uitgevoerd.

Behalve `stop' hebben we ook een `start' gedefinieerd, die verder
grotendeels hetzelfde doet.

We be&euml;indigen de code weer met `%%'.

Doe dit om Voorbeeld 1 te compileren:
<tscreen><verb>
lex example1.l
cc lex.yy.c -o example1 -ll

</verb></tscreen>

<tscreen>
OPMERKING:Als je flex gebruikt ipv lex, moet je misschien `-ll'
vervangen door `-lfl' in de compilatiescripts. Redhat 6.x en SuSE
vereisen dit, zelfs als je `flex' aanroept als `lex'!
</tscreen>

Dit genereert het programma `example1'. Als je het draait, wacht het
tot je iets intikt. Als je iets typt dat niet overeenkomt met een
gedefinieerde sleutel (`stop' en `start') wordt het weer geoutput. Als
je `stop' intikt geeft het `stop commando ontvangen';

Sluit af met een EOF (^D).

Je vraagt je misschien af hoe het programma draait, daar we geen
main() gedefinieerd hebben. Die functie is voor je gedefinieerd in
libl (liblex) die we ingecompileerd hebben met het -ll commando.
</p>

<sect1><heading>Reguliere expressies in matches</heading>
<p>
Dit voorbeeld was op zichzelf niet erg nuttig, en dat is het volgende
ook niet. Het laat echter wel zien hoe je reguliere expressies
gebruikt in Lex, wat later superhandig zal zijn.

Voorbeeld 2:
<tscreen><verb>
%{
#include <stdio.h>
%}

%%
[0123456789]+		printf("NUMMER\n");
[a-zA-Z][a-zA-Z0-9]*	printf("WOORD\n");
%%

</verb></tscreen>

Dit Lex bestand beschrijft twee soorten matches (tokens): WOORDen en
NUMMERs. Reguliere expressies kunnen behoorlijk intimiderend zijn maar
met een beetje werk zijn ze makkelijk te begrijpen. Laten we de NUMMER
match bekijken:

[0123456789]+

Dit betekent: een reeks van een of meer karakters uit de groep
0123456789. We hadden het ook kunnen afkorten tot:

[0-9]+

De WOORD match is iets ingewikkelder:

[a-zA-Z][a-zA-Z0-9]*

Het eerste deel matcht 1 en slechts 1 karakter tussen `a' en `z', of
tussen `A' en `Z'. Met andere woorden, een letter. Deze beginletter
moet gevolgd worden door nul of meer karakters die ofwel een letter of
een cijfer zijn. Waarom hier een asterisk gebruiken? De `+' betekent 1
of meer matches, maar een WOORD kan heel goed uit slechts 1 karakter
bestaan, dat we al gematcht hebben. Dus het tweede deel heeft
misschien nul matches, dus schrijven we een `*'.

Op deze manier hebben we het gedrag van veel programmeertalen
ge&iuml;miteerd die vereisen dat een variabelenaam met een letter
*moet* beginnen maar daarna cijfers mag bevatten. Met andere woorden,
`temperature1' is een geldige naam, maar `1temperature' niet.

Probeer voorbeeld 2 te compileren, net zoals voorbeeld 1, en voer wat
tekst in. Een voorbeeldsessie:

<tscreen><verb>
<tscreen><verb>
$ ./example2
foo
WOORD

bar
WOORD

123
NUMMER

bar123
WOORD

123bar
NUMMER
WOORD
</verb></tscreen>

Je vraagt je misschien ook af waar al die witregels in de uitvoer
vandaan komen. De reden is eenvoudig: het was in de invoer, en het
wordt nergens gematcht, dus wordt het weer uitgevoerd.

De Flex manpage beschrijft de reguliere expressies in detail. Velen
vinden de perl reguliere expressies manpage (perlre) ook nuttig, al
kan Flex niet alles dat perl kan.

Zorg dat je geen matches met een lengte van nul zoals `[0-9]*' maakt -
je lexer kan in de war raken en lege strings herhaaldelijk matchen.
</p>
</sect1>

<sect1><heading>Een ingewikkelder voorbeeld voor een C-achtige syntax</heading>
<p>
Laten we zeggen dat we een bestand willen parsen dat er zo uitziet:
<tscreen><verb>
logging {
	category lame-servers { null; };
	category cname { null; };
};

zone "." {
        type hint;
        file "/etc/bind/db.root";
};
</verb></tscreen>

We herkennen duidelijk een aantal categorie&euml;n (tokens) in dit
bestand:
<itemize>
<item> WOORDen, zoals `zone' en `type'</item>
<item> BESTANDSNAAMen, zoals `etc/bind/db.root'</item>
<item> QUOTEs, zoals om de bestandsnaam</item>
<item> OBRACEs, {</item>
<item> EBRACEs, }</item>
<item> PUNTKOMMAs, ;</item>
</itemize>

Het overeenkomstige Lex bestand is Voorbeeld 3:
<tscreen><verb>
%{
#include <stdio.h>
%}

%%
[a-zA-Z][a-zA-Z0-9]*    printf("WOORD ");
[a-zA-Z0-9\/.-]+        printf("BESTANDSNAAM ");
\"                      printf("QUOTE ");
\{                      printf("OBRACE ");
\}                      printf("EBRACE ");
;                       printf("PUNTKOMMA ");
\n                      printf("\n");
[ \t]+                  /* negeer whitespace */;
%%


</verb></tscreen>

Als we ons bestand aan het programma toevoeren dat door dit Lex bestand
gegenereerd is (met gebruik van example3.compile) krijgen we:

<tscreen><verb>
WOORD OBRACE 
WOORD BESTANDSNAAM OBRACE WOORD PUNTKOMMA EBRACE PUNTKOMMA 
WOORD WOORD OBRACE WOORD PUNTKOMMA EBRACE PUNTKOMMA 
EBRACE PUNTKOMMA 

WOORD QUOTE BESTANDSNAAM QUOTE OBRACE 
WOORD WOORD PUNTKOMMA 
WOORD QUOTE BESTANDSNAAM QUOTE PUNTKOMMA 
EBRACE PUNTKOMMA 
</verb></tscreen>

Als we dit vergelijken met het bovengenoemde configuratiebestand, wordt
duidelijk dat we het netjes `getokeniseerd' hebben. Ieder deel van het
configuratiebestand is gematcht, en omgezet naar een token.
</p>
</sect1>

<sect1><heading>Wat we geleerd hebben</heading>
<p>
We hebben geleerd dat Lex in staat is om willekeurige invoer te lezen, en te
bepalen wat ieder onderdeel van de invoer is. Dit wordt `tokenizering'
genoemd.
</p>
</sect1>
</sect>

<sect><heading>YACC</heading>
<p>
YACC kan invoerstromen parsen die bestaan uit tokens met bepaalde waarden.
Dit geeft precies weer hoe YACC zich verhoudt tot LEX, YACC weet niet wat
`invoerstromen' zijn, het heeft voorbewerkte tokens nodig. Je kunt je eigen
tokenizeerder schrijven, maar we laten dit aan Lex over.

Een opmerking over grammatica's en parsers. Toen YACC het levenslicht zag,
werd het gebruikt om invoerbestanden voor compilers te parsen: programma's.
Programma's in een computertaal zijn in het algemeen *niet* dubbelzinnig -
ze hebben maar een betekenis. YACC kan dus niet met dubbelzinnigheid omgaan
en zal klagen over shift/reduce en reduce/reduce conflicten. Meer over
dubbelzinnigheid en YACC "problemen" in het hoofdstuk `Conflicten'.
</p>

<sect1><heading>Een eenvoudige thermostaat beheerser</heading>
<p>
Laten we zeggen dat we een thermostaat willen beheersen met een eenvoudige
taal. Een sessie met de thermostaat kan er als volgt uit zien:
<tscreen><verb>
warmte aan
	Verwarming aan!
warmte uit
	Verwarming uit!
</verb></tscreen>

De tokens die we moeten herkennen zijn: warmte, aan/uit (TOESTAND), doel,
temperatuur, NUMMER

De Lex tokenizeerder (Voorbeeld 4) is:
<tscreen><verb>
%{
#include <stdio.h>
#include "y.tab.h"
%}
%%
[0-9]+                  return NUMMER;
warmte                  return TOKWARMTE;
aan|uit                 return TOESTAND;
doel                    return TOKDOEL;
temperatuur             return TOKTEMPERATUUR;
\n                      /* negeer einde regel */;
[ \t]+                  /* negeer whitespace */;
%%

</verb></tscreen>

We merken twee belangrijke veranderingen op. Ten eerste includen we het
bestand `y.tab.h' en ten tweede printen we niet meer, we returnen namen van
tokens. Deze verandering is omdat we het nu allemaal aan YACC toevoeren, die
is niet ge&iuml;nteresseerd in wat we op het scherm uitvoeren. Y.tab.h heeft
definities voor deze tokens.

Maar waar komt y.tab.h vandaan? Het wordt gegenereerd door YACC vanuit het
Grammatica Bestand dat we gaan cre&euml;ren. Onze taal is eenvoudig dus de
grammatica ook:

<tscreen><verb>
commands: /* leeg */
        | commands command
        ;

command:
        heat_switch
        |
        target_set
        ;

heat_switch:
        TOKWARMTE TOESTAND
        {
         	printf("\tWarmte aan- of uitgezet\n");
        }
        ;

target_set:
        TOKDOEL TOKTEMPERATUUR NUMMER
        {
         	printf("\tTemperatuur ingesteld\n");
        }
        ;
</verb></tscreen>

Het eerste gedeelte is wat ik de `wortel' zal noemen. Het vertelt ons dat we
`commands' hebben, en dat deze commands bestaan uit individuele `command'
delen. Je ziet dat deze regel erg recursief is, want hij bevat weer het
woord `commands'. Dit betekent dat het programma nu een reeks commands een
voor een kan reduceren. Zie het hoofdstuk `Hoe werken Lex en YACC intern'
voor belangrijke details over recursie.

De tweede regel definieert wat een command is. We ondersteunen maar twee
soorten commands, de `heat_switch' en de `target_set'. Dat is de betekenis
van het |-symbool - `een command bestaat uit ofwel een heat_switch of een
target_set'.

Een heat_switch bestaat uit het HEAT token, wat simpelweg het woord `warmte'
is gevolgd door een toestand (die we in het Lex bestand gedefinieerd hebben
als `aan' of `uit').

Iets ingewikkelder is de target_set, die bestaat uit het TARGET token (het
woord `doel'), het TEMPERATURE token (het woord `temperatuur') en een getal.
</p>

<sect2><heading>Een volledig YACC bestand</heading>
<p>
De vorige paragraaf toonde alleen het grammatica gedeelte van het YACC
bestand, maar er is meer. Dit is de header die we hebben weggelaten:

<tscreen><verb>
%{
#include <stdio.h>
#include <string.h>
 
void yyerror(const char *str)
{
 	fprintf(stderr,"fout: %s\n",str);
}
 
int yywrap()
{
 	return 1;
} 
  
main()
{
  	yyparse();
} 

%}

%token NUMMER TOKWARMTE TOESTAND TOKDOEL TOKTEMPERATUUR
</verb></tscreen>
(Noot van de vertaler: tussen header en grammatica moet nog een %% staan.)
De functie yyerror() wordt door YACC aangeroepen als hij een fout tegenkomt.
We voeren gewoon de doorgestuurde boodschap uit, maar er zijn slimmere
dingen te doen. Zie de paragraaf `Verder lezen' aan het einde.

De functie yywrap() kan gebruikt worden om verder te lezen uit een ander
bestand. Het wordt bij EOF aangeroepen en dan kun je een ander bestand
openen, en 0 returnen. Of je kunt 1 returnen, om aan te geven dat dit echt
het einde is. Zie verder het hoofdstuk `Hoe Lex en YACC intern werken'.

Dan is er nog de functie main(), die niets doet behalve alles in gang zetten.

De laatste regel definieert eenvoudig de tokens die we gaan gebruiken. Die
worden uitgevoerd met y.tab.h als YACC is aangeroepen met de `-d' optie.
</p>
</sect2>

<sect2><heading>De thermostaat beheerser compileren &amp; draaien</heading>
<p>
<tscreen><verb>
lex example4.l
yacc -d example4.y
cc lex.yy.c y.tab.c -o example4 

</verb></tscreen>
Sommige dingen zijn veranderd. We roepen nu ook YACC aan om onze grammatica
te compileren, wat y.tab.c en y.tab.h genereert. Dan roepen we als
gewoonlijk Lex aan. Bij het compileren laten we de -ll vlag weg; we hebben
nu onze eigen main() en hebben die van libl niet nodig.

<tscreen>
OPMERKING: als je een foutmelding krijgt dat je compiler `yylval' niet kan
vinden, voeg dan onder #include &lt;y.tab.h&gt; dit toe:
<verb>
extern YYSSTYOE yylval;
</verb>
Dit wordt uitgelegd in de paragraaf `Hoe Lex en YACC intern werken'.
</tscreen>

Een voorbeeldsessie:
<tscreen><verb>
$ ./example4 
warmte aan
	Warmte aan- of uitgezet
warmte uit
	Warmte aan- of uitgezet
doel temperatuur 10
	Temperatuur ingesteld
doel vochtigheid 20
fout: parse error
$
</verb></tscreen>

Dit is niet helemaal wat we wilden bereiken, maar om de leercurve behapbaar
te houden kunnen we niet alles tegelijk presenteren.
</p>
</sect2>
</sect1>

<sect1><heading>De thermostaat uitbreiden om parameters te verwerken</heading>
<p>
Zoals we hebben gezien kunnen we de thermostaat commands nu correct parsen,
en zelfs fouten correct opmerken. Maar als je misschien hebt geraden door de
dubbelzinnige bewoordingen, heeft het programma geen idee wat het zou moeten
doen, de waarden die je invoert worden er niet naar doorgestuurd.

Laten we beginnen met het vermogen om de nieuwe doeltemperatuur te lezen.
Hiervoor moeten we de NUMMER match in de Lexer leren zichzelf in een integer
waarde om te zetten, die dan ingelezen kan worden in YACC.

Als Lex een doel matcht, stopt het de tekst van de match in de string
`yytext'. YACC op zijn beurt verwacht een waarde in `yylval'. In voorbeeld 5
zien we de voor de hand liggende oplossing:
<tscreen><verb>
%{
#include <stdio.h>
#include "y.tab.h"
%}
%%
[0-9]+                  yylval=atoi(yytext); return NUMMER;
warmte                  return TOKWARMTE;
aan|uit                 yylval=!strcmp(yytext,"aan"); return TOESTAND;
doel                    return TOKDOEL;
temperatuur             return TOKTEMPERATUUR;
\n                      /* negeer einde regel */;
[ \t]+                  /* negeer whitespace */;
%%

</verb></tscreen>

Zoals je ziet draaien we atoi() op yytext, en stoppen we de uitkomst in
yylval, waar YACC het kan vinden. Net zoiets voor de TOESTAND match, waarbij 
we yylval op 1 zetten als deze `aan' is. Merk op dat een aparte `aan' en
`uit' match in Lex een sneller programma zou genereren, maar ik wilde voor
de verandering een ingewikkelder regel en actie laten zien.

Nu moeten we YACC leren hoe hiermee om te gaan. Wat in Lex `yylval' genoemd
wordt, heeft in YACC een andere naam. Laten we de regel die het nieuwe
temperatuurdoel instelt bekijken:

<tscreen><verb>
target_set: 
	TOKDOEL TOKTEMPERATUUR NUMMER
	{
         	printf("\tTemperatuur ingesteld op %d\n",$3);
	}
        ;
</verb></tscreen>

Om toegang te krijgen tot het derde gedeelte van de regel (NUMMER), moeten
we $3 gebruiken. Steeds als yylex() returnt, wordt de waarde van yylval aan
de terminal (vertaler: token) toegekend, en kan benaderd worden met de
$-constructie.

Om hier verder op in te gaan, beschouw de nieuwe `heat_switch' regel:
<tscreen><verb>
heat_switch:
        TOKWARMTE TOESTAND
        {
         	if($2)
         	        printf("\tWarmte aan\n");
	 	else
                        printf("\tWarmte uit\n");
        }
        ;
</verb></tscreen>

Als je nu example5 draait, voert het netjes uit wat je hebt ingevoerd.
</p>
</sect1>

<sect1><heading>Een configuratiebestand parsen</heading>
<p>
Laten we een deel van het eerder genoemde configuratiebestand herhalen:
<tscreen><verb>
zone "." {
        type hint;
        file "/etc/bind/db.root";
};
</verb></tscreen>

We hebben al een Lexer voor dit bestand. Nu hoeven we alleen nog maar een
YACC grammatica te schrijven, en de Lexer aanpassen zodat het waarden
teruggeeft in een vorm die YACC kan snappen.

In de lexer van Voorbeeld 6 zien we:
<tscreen><verb>
%{
#include <stdio.h>
#include "y.tab.h"
%}

%%

zone			return ZONETOK;
file			return FILETOK;
[a-zA-Z][a-zA-Z0-9]*    yylval=strdup(yytext); return WOORD;
[a-zA-Z0-9\/.-]+        yylval=strdup(yytext); return BESTANDSNAAM;
\"                      return QUOTE;
\{                      return OBRACE;
\}                      return EBRACE;
;                       return PUNTKOMMA;
\n                      /* negeer einde regel*/;
[ \t]+                  /* negeer whitespace */;
%%

</verb></tscreen>

Als je goed kijkt, zie je dat yylval veranderd is! We verwachten niet meer
dat het een integer is, maar nemen aan dat het een char * is. Voor de
eenvoud roepen we strdup aan en verspillen een hoop geheugen. Merk op dat
dit geen probleem is in veel gevallen als je een bestand maar een keer hoeft
te parsen, en dan exit.

We willen strings opslaan omdat we nu voornamelijk met namen te maken
hebben: bestandsnamen en zonenamen. In een later hoofdstuk zullen we
uitleggen hoe je met verschillende soorten data omgaat.

Om YACC over het nieuwe type van yylval te vertellen, voegen we dit toe aan
de header van onze YACC grammatica:
<verb>#define YYSTYPE char *</verb>

De grammatica zelf is weer ingewikkelder. We hakken het in stukjes om het
verteerbaarder te maken.

<tscreen><verb>
commands:
	|	 
	commands command PUNTKOMMA
	;


command:
	zone_set 
	;

zone_set:
	ZONETOK quotedname zonecontent
	{
		printf("Complete zone for '%s' gevonden\n",$2);
	}
	;

</verb></tscreen>

Dit is de intro, inclusief voornoemde recursieve `wortel' Merk op dat we
specificeren dat commands worden getermineerd (en gescheiden) door ;'s. We
defini&euml;ren &eacute;&eacute;n soort command, de `zone_set'. Deze bestaat
uit het ZONE token (het woord `zone'), gevolgd door een quotedname en de
`zonecontent'. De zonecontent begint eenvoudig:
<tscreen><verb>
zonecontent:
	OBRACE zonestatements EBRACE 

</verb></tscreen>

Hij moet beginnen met een OBRACE, een {. Dan komen de zonestatements,
gevolgd door een EBRACE, een }.

<tscreen><verb>
quotedname:
        QUOTE BESTANDSNAAM QUOTE
        {
         	$$=$2;
        }

</verb></tscreen>

Deze sectie definieert een `quotedname': een BESTANDSNAAM tussen QUOTES. Dan
iets bijzonders: de waarde van een quotedname token is de waarde van de
BESTANDSNAAM. Dit betekent dat de quotedname als waarde de bestandsnaam
zonder quotes heeft.

Dat is wat het magische `$$=$2' doet. Het zegt: mijn waarde is de waarde van
mijn tweede deel. Als nu naar de quotedname verwezen wordt in andere regels,
en je benadert zijn waarde met de $-constructie, zie je de waarde die we
hier ingesteld hebben met $$=$2.

<tscreen>
OPMERKING: deze grammatica verslikt zich in bestandsnamen zonder een `.' of
een `/' erin.
</tscreen>

<tscreen><verb>
zonestatements:
	|
	zonestatements zonestatement PUNTKOMMA
	;

zonestatement:
	statements
	|
	FILETOK quotedname 
	{
		printf("Een zonefile naam '%s' tegengekomen\n", $2);
	}
	;

</verb></tscreen>

Dit is een algemeen statement die alle soorten statements binnen het `zone'
blok afvangt. We zien opnieuw de recursiviteit.

<tscreen><verb>
block: 
	OBRACE zonestatements EBRACE PUNTKOMMA
	;

statements:
	| statements statement
	;

statement: WOORD | block | quotedname
</verb></tscreen>

Dit definieert een blok, en `statements' die er in gevonden kunnen worden.

Bij uitvoering ziet de uitvoer er zo uit:

<tscreen><verb>
$ ./example6
zone "." {
        type hint;
        file "/etc/bind/db.root";
        type hint;
};
Een zonefile naam '/etc/bind/db.root' tegengekomen
Complete zone for '.' gevonden
</verb></tscreen>
</p>
</sect1>
</sect>

<sect><heading>Een Parser in C++ maken</heading>
<p>
Hoewel Lex en YACC ouder zijn dan C++, is het mogelijk een C++ parser te
maken. Flex heeft een optie om een C++ lexer te genereren, maar die zullen
we niet gebruiken, want YACC kan er direct mee omgaan.

Ik geef er de voorkeur aan Lex een gewoon C bestand te laten genereren, en
YACC C++ code te laten genereren. Als je dan je toepassing linkt, kom je
misschien problemen tegen omdat de C++ code standaard geen C functies kan
vinden, tenzij je het verteld hebt dat de functies extern "C" zijn.

Maak hiervoor een C header in YACC:

<tscreen><verb>
extern "C"
{
 	int yyparse(void);
 	int yylex(void);  
 	int yywrap()
 	{
 	 	return 1;
        }

}
</verb></tscreen>

Als je yydebug wilt declareren of veranderen, moet dat nu zo:

<tscreen><verb>
extern int yydebug;

main()
{
	yydebug=1;
	yyparse();
}
</verb></tscreen>

Dit is vanwege de Een Definitie Regel van C++, die geen meervoudige
definities van yydebug toestaat.

Je zult misschien ook de #define van YYSTYPE in je Lex bestand moeten
herhalen, vanwege C++ z'n strengere type checking.

Compileren gaat ongeveer zo:
<tscreen><verb>
lex bindconfig2.l
yacc --verbose --debug -d bindconfig2.y -o bindconfig2.cc
cc -c lex.yy.c -o lex.yy.o
c++ lex.yy.o bindconfig2.cc -o bindconfig2 
</verb></tscreen>

Vanwege het -o statement heet y.tab.h nu bindconfig2.cc.h, dus hou daar
rekening mee.

Samengevat: doe geen moeite om een Lexer in C++ te compileren, hou het in C.
Maak je parser in C++ en leg je compiler uit dat sommige functies C functies
zijn met extern "C" statements.
</p>
</sect>

<sect><heading>Hoe werken Lex en YACC intern</heading>
<p>
In het YACC bestand schrijf je je eigen main() functie, die op een gegeven
moment yyparse() aanroept. De functie yyparse() is voor je aangemaakt door
YACC, en komt in y.tab.c terecht.

yyparse() leest een stroom token/waarde paren van yylex(), welke beschikbaar
gesteld moet worden. Je kunt deze functie zelf schrijven, of Lex dit laten
doen. In onze voorbeelden hebben we dit aan Lex overgelaten.

De yylex() zoals geschreven door Lex leest karakters van een FILE *
bestandspointer, yyin genaamd. Als je yyin niet instelt, is het de standard
input. Hij voert uit naar yyout, als niet ingesteld is dat stdout. Je kunt
yyin ook veranderen in de yywrap() functie die bij einde van een bestand
wordt aangeroepen. Daarmee kun je een ander bestand openen, en verder parsen.

Als dat het geval is, moet hij 0 returnen. Als je na dit bestand wilt
stoppen met parsen, moet hij 1 returnen.

Iedere aanroep van yylex() returnt een integer waarde die een token type
voorstelt. Dit vertelt YACC wat voor token hij gelezen heeft. Optioneel mag
het token een waarde hebben, die in de variabele yylval geplaatst moet worden.

Standaard is yylval van het type int, maar je kunt dit in het YACC bestand
overriden door YYSTYPE te her#definen.

De Lexer moet toegang hebben tot yylval. Hiervoor moet deze gedeclareerd
worden in de scope van de lexer als een externe variabele. De originele YACC
doet dit niet voor je, dus moet je het volgende aan je lexer toevoegen, net
onder de #include &lt;y.tab.h&gt;:

<verb>
extern YYSTYPE yylval;
</verb>

Bison, welke de meeste mensen tegenwoordig gebruiken, doet dit automatisch
voor je.
</p>

<sect1><heading>Tokenwaarden</heading>
<p>
Zoals opgemerkt moet yylex() returnen wat voor soort token het is
tegengekomen, en zijn waarde in yylval stoppen. Als deze tokens gedefinieerd
zijn met het %token commando, worden er numerieke id's aan toegekend,
beginnend bij 256.

Hierdoor is het mogelijk alle ascii karakters als token te gebruiken. Laten
we zeggen dat je een rekenmachine aan het schrijven bent, tot nu toe zouden
we de lexer als volgt hebben geschreven:

<tscreen><verb>
[0-9]+          yylval=atoi(yytext); return NUMMER;
[ \n]+          /* eet whitespace */;
-               return MINUS;
\*              return MULT; 
\+              return PLUS;
...
</verb></tscreen>

Onze YACC grammatica zou dan het volgende bevatten:

<tscreen><verb>
	exp: 	NUMMER 
		|
		exp PLUS exp
		|
		exp MINUS exp
		|
		exp MULT exp
</verb></tscreen>

Dit is onnodig ingewikkeld. Door karakters te gebruiken als afkortingen voor
numerieke token id's kunnen we onze lexer herschrijven:

<verb>
[0-9]+          yylval=atoi(yytext); return NUMMER;
[ \n]+          /* eet whitespace */;
.               return (int) yytext[0];
</verb>

De laatste punt matcht alle verder niet gematchte karakters.

De YACC grammatica wordt dan:
<tscreen><verb>
	exp: 	NUMMER 
		|
		exp '+' exp
		|
		exp '-' exp
		|
		exp '*' exp
</verb></tscreen>

Dit is veel korter en ook duidelijker. Je hoeft de ascii tokens nu niet met
%token in de header te declareren, ze werken zo uit de doos.

Een ander goed ding van deze constructie is dat Lex nu alles zal matchen dat
we het geven - wat het standaard gedrag om niet gematchte invoer naar stdout
te echo'en vermijdt. Als een gebruiker van deze rekenmachine een ^ gebruikt,
geeft het nu een parse error, in plaats van op stdout ge-echoot te worden.
</p>
</sect1>

<sect1><heading>Recursie: `rechts is fout'</heading>
<p>
Recursie is een vitaal aspect van YACC. Zonder recursie kun je niet
specificeren dat een bestand bestaat uit een reeks onafhankelijke commando's
of statements. Van zichzelf is YACC alleen ge&iuml;nteresseerd in de eerste
regel, of de regel die je aanwijst als de startregel, met het `%start'
symbool.

Recursie in YACC komt in twee smaken: rechts en links. Linker recursie, die
je meestal moet gebruiken, ziet er zo uit:
<verb>
commands: /* leeg */
	|
	commands command
</verb>
Dit betekent: een commando is ofwel leeg, of het bestaat uit een of meer
commando's, gevolgd door een commando. De manier waarop YACC werkt betekent
dat je nu gemakkelijk individuele commando groepen kunt afhakken (aan de
voorkant) en reduceren.

Vergelijk dit met rechter recursie, die er verwarrend genoeg voor velen
beter uitziet:
<verb>
commands: /* leeg */
	|
	command commands
</verb>
Maar dit is kostbaar. Gebruikt als de %start regel, vereist het dat YACC
alle commando's in je bestand op de stack houdt, wat een hoop geheugen kan
gebruiken. Dus gebruik in ieder geval linker recursie voor het parsen van
lange statements. Soms is het moeilijk rechter recursie te vermijden maar
als je statements niet te lang zijn, hoef je je niet in bochten te wringen
om linker recursie te gebruiken.

Als iets je commando's be&euml;indigt (en dus scheidt), lijkt rechter
recursie erg natuurlijk, maar is het nog steeds kostbaar:
<verb>
commands: /* leeg */
	|
	command PUNTKOMMA commands
</verb>

De juiste manier om dit te coderen is met linker recursie (ik heb het ook
niet uitgevonden):
<verb>
commands: /* leeg */
	|
	commands command PUNTKOMMA
</verb>

Eerdere versies van deze HOWTO gebruikten abusievelijk rechter recursie. Met
dank aan Markus Triska.
</p>
</sect1>

<sect1><heading>yylval voor gevorderden: %union</heading>
<p>
Momenteel moeten we *het* type van yylval defini&euml;ren. Dit is echter
niet altijd toepasselijk. Er zijn tijden dat we meerdere datatypen moeten
kunnen verwerken. Terugkerend naar onze hypothetische thermostaat, willen we
misschien een verwarming die we willen beheersen uitkiezen, als volgt:

<tscreen><verb>
Verwarming hoofdgebouw
	`hoofdgebouw' verwarming geselecteerd
doel temperatuur 23
	`hoofdgebouw' verwarming doel temperatuur nu 23
</verb></tscreen>

Hiervoor moet yylval een union wezen, die zowel strings als integers kan
bevatten - maar niet tegelijk.

Bedenk dat we YACC eerder verteld hebben welk type yylval was door YYSTYPE
te defini&euml;ren. We zouden YYSTYPE op deze manier als union kunnen
defini&euml;ren, maar YACC heeft hier een gemakkelijker methode voor: het
%union statement.

Gebaseerd op Voorbeeld 4, schrijven we nu de YACC grammatica van Voorbeeld
7. Eerst de intro:
<tscreen><verb>%token TOKVERWARMING TOKWARMTE TOKDOEL TOKTEMPERATUUR

%union 
{
        int number;
        char *string;
}

%token <number> TOESTAND
%token <number> NUMMER
%token <string> WOORD
</verb></tscreen>

We defini&euml;ren onze union, die alleen een nummer en een string bevat.
Dan leggen we YACC met een uitgebreide %token syntax uit welk deel van de
union ieder token moet benaderen.

In dit geval laten we het TOESTAND token een integer gebruiken, net als zonet.
Hetzelfde geldt voor het NUMMER token, dat we gebruiken om temperaturen te
lezen.

Nieuw is het WOORD token, dat gedeclareerd is om een string te gebruiken.

Het lexerbestand verandert ook een beetje:
<tscreen><verb>
%{
#include <stdio.h>
#include <string.h>
#include "y.tab.h"
%}
%%
[0-9]+                  yylval.number=atoi(yytext); return NUMMER;
verwarming		return TOKVERWARMING;
warmte                  return TOKWARMTE;
aan|uit                 yylval.number=!strcmp(yytext,"aan"); return TOESTAND;
doel                    return TOKDOEL;
temperatuur             return TOKTEMPERATUUR;
[a-z0-9]+		yylval.string=strdup(yytext);return WOORD;
\n                      /* negeer einde regel */;
[ \t]+                  /* negeer whitespace  */;
%%

</verb></tscreen>

Zoals je ziet benaderen we de yylval niet meer direct, we voegen een suffix
toe om aan te geven welk deel we willen benaderen. In de YACC grammatica
hoeft dat echter niet, want YACC doet het voor ons:

<tscreen><verb>
heater_select:
        TOKVERWARMING WOORD
	{
	 	printf("\tVerwarming '%s' geselecteerd\n",$2);
        	heater=$2;
	}
        ;
</verb></tscreen>

Vanwege de %token declaratie hierboven kiest YACC automatisch het `string'
lid uit onze union. Merk ook op dat we een kopie van $2 opslaan, die later
gebruikt wordt om de gebruiker te vertellen naar welke verwarming hij
commando's stuurt:

<tscreen><verb>
target_set:
        TOKDOEL TOKTEMPERATUUR NUMMER
        {
	 	printf("\tTemperatuur van verwarming '%s' ingesteld op %d\n",heater,$3);
  	}
	;
</verb></tscreen>
Lees voor meer details example7.y.
</p>
</sect1>
</sect>

<sect><heading>Debuggen</heading>
<p>
Het is belangrijk om debuggingsfaciliteiten te hebben, vooral als je aan het
leren bent. Gelukkig kan YACC een hoop feedback geven. Deze feedback komt
tegen de prijs van enige overhead, dus je moet enige switches geven om het
aan te zetten.

Voeg als je je grammatica compileert, --debug en --verbose toe aan de YACC
opdrachtregel. Voeg dit toe aan de C heading van je grammatica:

	int yydebug = 1;
	
Dit zal het bestand `y.output' genereren die de gemaakte toestandsmachine
uitlegt.

Als je nu de gegenereerde binary draait, zal het een *hoop* uitvoer geven
over wat er gebeurt, inclusief in welke toestand de toestandsmachine op dit
moment is, en welke tokens er worden gelezen.

Peter Jinks schreef een pagina op
<URL
URL="http://www.cs.man.ac.uk/~pjj/cs2121/debug.html" name="debugging"> die
vaak voorkomende fouten en hun oplossingen bevat.
</p>

<sect1><heading>De toestandsmachine</heading>
<p>
Intern draait je YACC parser een zogenaamde `toestandsmachine'. Zoals de
naam zegt, is dit een machine die in verschillende toestanden kan verkeren.
Dan zijn er regels die overgangen van de ene toestand naar de andere
bepalen. Alles begint met de zogenaamde `root' regel die ik eerder vermeld
heb.

Citaat uit de uitvoer van y.output van Voorbeeld 7:
<tscreen><verb>
state 0

    ZONETOK     , and go to state 1

    $default    reduce using rule 1 (commands)

    commands    go to state 29
    command     go to state 2
    zone_set    go to state 3
</verb></tscreen>

Standaard reduceert deze toestand met de `commands' regel. Dit is de
voornoemde recursieve regel die `commands' definieert als zijnde opgebouwd
uit individuele command statements, gevolgd door een puntkomma, gevolgd door
mogelijk meer commands.

Deze toestand reduceert tot het iets tegenkomt dat het begrijpt, in dit
geval een ZONETOK, d.i. het woord `zone'. Dan gaat het naar toestand 1, die
het zone command verder afhandelt:

<tscreen><verb>
state 1

    zone_set  ->  ZONETOK . quotedname zonecontent   (rule 4)

    QUOTE       , and go to state 4

    quotedname  go to state 5
</verb></tscreen>

De eerste regel heeft een `.' om aan te geven waar we zijn: we hebben net
een ZONETOK gezien en zoeken nu naar een `quotedname'. Blijkbaar begint een
quotedname met een QUOTE, die ons naar toestand 4 stuurt.

Compileer om dit verder te volgen Voorbeeld 7 met de vlaggen uit de
paragraaf Debuggen.
</p>
</sect1>

<sect1><heading>Conflicten: `shift/reduce', `reduce/reduce'</heading>
<p>
Steeds als YACC je waarschuwt over conflicten, kun je problemen
verwachten.Het oplossen van deze conflicten schijnt iets van een kunstvorm
te zijn die je een hoop over je taal kan leren. Misschien meer dan je wilde
weten.

De problemen draaien om de vraag hoe een reeks tokens te interpreteren.
Laten we aannemen dat we een taal hebben die deze commando's moet accepteren:

<tscreen><verb>
	verwijder verwarming all
	verwijder verwarming number1
</verb></tscreen>

Hiertoe defini&euml;ren we deze grammatica:

<tscreen><verb>
	delete_heaters:
		TOKDELETE TOKVERWARMING mode
		{
			deleteheaters($3);
		}
	
	mode:	WOORD

	delete_a_heater:
		TOKDELETE TOKVERWARMING WOORD
		{
			delete($3);
		}
</verb></tscreen>

Je ruikt misschien al moeilijkheden. De toestandsmachine begint met het
woord `verwijder' te lezen, en moet dan op basis van het volgende token
beslissen waar naartoe te gaan. Dit volgende token kan een mode zijn, die
aangeeft hoe de verwarmingen te verwijderen, of de naam van een verwarming 
die te verwijderen is.

Het probleem is dat het volgende token in beide gevallen een WOORD is. YACC
weet dus niet wat het moet doen. Dit leidt tot een `reduce/reduce'
waarschuwing, en een waarschuwing dat de `delete_a_heater' node nooit
bereikt zal worden.

In dit geval is het conflict gemakkelijk opgelost (door het eerste commando
te hernoemen tot `verwijder verwarmingen all' of door `all' een apart token te
maken), maar soms is het moeilijker. Het y.output bestand dat gegenereerd
wordt als je YACC de --verbose vlag geeft kan enorm helpen.
</p>
</sect1>
</sect>

<sect><heading>Verder lezen</heading>
<p>
GNU YACC (Bison) komt met een heel aardig info-bestand (.info) dat de YACC
syntax heel goed documenteert. Het vermeldt Lex maar &eacute;&eacute;n keer,
maar verder is het heel goed. Je kunt .info-bestanden lezen met Emacs of met
het heel aardige hulpmiddel `pinfo'. Het is ook beschikbaar op de GNU site:
<URL name="BISON Manual" URL="http://www.gnu.org/manual/bison/">

Flex komt met een goede manpage die erg nuttig is als je al een ruw begrip
hebt van wat Flex doet. Het
<URL name="Flex Manual" URL="http://www.gnu.org/manual/flex/"> is ook online.

Na deze inleiding tot Lex en YACC kom je er misschien achter dat je meer
informatie nodig hebt. Ik heb deze boeken nog niet gelezen, maar ze klinken
goed:
<descrip>
<tag>Bison-The Yacc-Compatible Parser Generator</tag>
<p>van Charles Donnelly and Richard Stallman. Een <URL name="Amazon"
url="http://www.amazon.com/exec/obidos/ASIN/0595100325/qid=989165194/sr=1-2/ref=sc_b_3/002-7737249-1404015">
gebruiker vond het nuttig.</p>
</descrip>

<descrip>
<tag>Lex & YACC</tag>
<p>Van John R. Levine, Tony Mason and Doug Brown. Wordt beschouwd als het
standaardwerk over dit onderwerp, maar is een beetje gedateerd. Besprekingen
op <URL name="Amazon"
URL="http://www.amazon.com/exec/obidos/ASIN/1565920007/ref=sim_books/002-7737249-1404015"></p>
</descrip>

<descrip>
<tag>Compilers : Principles, Techniques, and Tools</tag>
<p>Van Alfred V. Aho, Ravi Sethi, Jeffrey D. Ullman. Het 'Dragon Book'. Uit
1985 en ze blijven het maar herdrukken. Beschouwd als het standaardwerk over 
compiler constructie.
<URL Name="Amazon"
URL="http://www.amazon.com/exec/obidos/ASIN/0201100886/ref=sim_books/002-7737249-1404015"></p>
</descrip>
</p>
<p>
Thomas Niemann schreef een document over het schrijven van compilers en
calculators met Lex &amp; YACC. Je vindt het <URL
URL="http://epaperpress.com/y_man.html" name="hier">

De gemodereerde usenet nieuwsgroep comp.compilers kan ook helpen maar hou in
gedachten dat de mensen daar geen toegewijde parser helpdesk zijn! Lees voor
je post hun interessante <URL name="pagina"
URL="http://compilers.iecc.com/"> en vooral de <URL name="FAQ"
URL="http://compilers.iecc.com/faq.txt">
Lex - A Lexical Analyzer Generator van M. E. Lesk and E. Schmidt is een van
de originele naslagwerken. Je vindt het 
<url NAME="hier" url="http://www.cs.utexas.edu/users/novak/lexpaper.htm">

YACC: Yet Another Compiler-Compiler door Stephen C. Johnson is een van de
originele naslagwerken. Je vindt het <url NAME="hier"
url="http://www.cs.utexas.edu/users/novak/yaccpaper.htm">
Het bevat nuttige wenken over stijl.
</p>
</sect>

<sect><heading>Dank aan</heading>
<p>
<itemize>
<item>Pete Jinks &lt;pjj%cs.man.ac.uk&gt;</item>
<item>Chris Lattner &lt;sabre%nondot.org&gt;</item>
<item>John W. Millaway &lt;johnmillaway%yahoo.com&gt;</item>
<item>Martin Neitzel &lt;neitzel%gaertner.de&gt;</item>
<item>Esmond Pitt &lt;esmond.pitt%bigpond.com&gt;</item>
<item>Eric S. Raymond </item>
<item>Bob Schmertz &lt;schmertz%wam.umd.edu&gt;</item>
<item>Adam Sulmicki &lt;adam%cfar.umd.edu&gt;</item>
<item>Markus Triska &lt;triska%gmx.at&gt;</item>
<item>Erik Verbruggen &lt;erik%road-warrior.cs.kun.nl&gt;</item>
<item>Gary V. Vaughan &lt;gary%gnu.org&gt; (lees zijn uitstekende <url NAME="Autobook" URL="http://sources.redhat.com/autobook">)</item>
<item><url NAME="Ivo van der Wijk" url="http://vanderwijk.info"> (
<url NAME="Amaze Internet" url="http://www.amaze.nl">)
</item>
</itemize>
</p>
</sect>
</article>
</linuxdoc>
