Die fehleranfällige Programmierung kann man sich sparen, wenn man
einen Generator wie lex oder flex verwendet. Dann muß
man für den Scanner nur noch die folgende kurze Beschreibung erstellen:
%{
#define NUMBER 400
#define COMMENT 401
#define TEXT 402
#define COMMAND 403
#define ERROR 999
%}
%%
[ \t] ;
[0-9]+ |
[0-9]+\.[0-9]* |
\.[0-9]+ {return NUMBER;}
#.* {return COMMENT;}
\"[^\"\n]*\" {return TEXT;}
[a-zA-Z0-9]+ {return COMMAND;}
\n {return '\n';}
. {return ERROR;}
%%
lex erzeugt hieraus eine Funktion yylex() , die das
Quellprogramm einliest und in seine Tokens zerlegt:
#include <stdio.h>
#include ``lex.yy.c''
int main()
{
int val;
while (val = yylex())
printf("value is %d\n", val);
return 0;
}
Die Werte von 0 bis 256 sind durch die ASCII-Zeichen belegt. Daher
beginnen die vom Benutzer definierten Nummern für die Tokens bei 257.
Die Definition
kann durch die define-Anweisung geschehen:
Die Nummer wird mit einer return-Anweisung zurückgegeben:
yytext ist eine globale Variable, die den ''gematchten'' Teil der Eingabe enthält. yylval wird benutzt, um den (ganzzahligen) Wert des Tokens von lex nach yacc zu transportieren. Der Typ von yylval kann auch geändert werden.
Eine Regel besteht aus einem regulärem Ausdruck und
C-Anweisungen, die ausgeführt werden, wenn eine Zeichenkette
erkannt wurde, die zu dem Ausdruck paßt.
Zeichenketten, die mit keinem Ausdruck ''gematched'' werden können,
werden unverändert ausgegeben.
Beispiel:
$ cat sample.l
%%
Hugo printf("Hier ist Hugo");
Emil$ printf("Emil am Zeilenende");
Immer, wenn die Zeichenkette Hugo erkannt wird, soll Hier ist Hugo ausgegeben werden, steht Emil am Zeilenende wird Emil am Zeilenende ausgeben (warum auch immer...). In allen anderen Fällen wird der Eingabetext unverändert ausgegeben.
Die folgenden Anweisungen transformieren die Spezifikation aus dem
letzten Beispiel in ein ausführbares Programm:
$ lex sample.l $ ls lex.yy.c sample.l $ cc -o sample lex.yy.c -ll $ ls lex.yy.c sample* sample.l
Ein Testlauf sieht dann so aus:
$ cat test Erna Hugo und Otto Egon Heinz und Emil $ cat test | sample Erna Hier ist Hugo und Otto Egon Heinz und Emil am Zeilenende
Eine vollständige lex-Spezifikation besteht aus
Definitionen
%%
Regeln
%%
C-Routinen
Die Definitionen enthalten C-Code, der direkt in den erzeugten Scanner
kopiert wird. Ferner kann man Makronamen definieren, die reguläre
Ausdrücke bezeichnen und so die Spezifikation lesbarer gestalten:
%{
int field_count = 1;
extern int field_cut;
%}
newline \n
tab \t
%%
{tab} field_count++;
{newline} {ECHO; field_count = 1;}
[^\t\n] {if (field_count == field_cut);
else printf("%s ", yytext);}
%%
int field_cut;
int main(int argc, char* argv[])
{
if (argc > 1) field_cut = atoi(argv[1]);
else field_cut = 1;
while(yylex());
return 0;
}
Im vorhergehenden Kapitel haben wir OBERON-0 kenengelernt, die Sprache, die N. Wirth in seinem Buch [5] verwendet. Er erstellt allerdings den Scanner per Hand. Die Spezifikation für lex ist aber viel kürzer - wir betrachten einen Ausschnitt:
%{
#include "ober.h"
#include "ober_tab.h"
extern int linecnt;
%}
integer [0-9]+
id [A-Za-z][A-Za-z0-9_]*
nl \n
blank [ \t]+
%%
{nl} {linecnt++;}
{blank} ;
\* {return timesop;}
DIV {return divop;}
MOD {return modop;}
& {return andop;}
\+ {return plusop;}
- {return minusop;}
.....
PROCEDURE {return proceduresy;}
BEGIN {return beginsy;}
MODULE {return modulesy;}
{integer} {
yylval.intval = atoi(YYText());
return number;
}
{id} {
strcpy(yylval.strval, YYText());
return ident;
}
. {return errorsy;}
Prof. Dr. Reinhard Völler