[ Inhalt ] [ Index ]

Next: Syntaktische Analyse Up: Lexikalische Analyse Previous: Endliche Automaten als Scanner

Scannergenerierung mit lex

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;
}

Variablen und Tokens

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:

# define NUMBER 257

Die Nummer wird mit einer return-Anweisung zurückgegeben:

[0-9]+ {yylval = atoi(yytext); return NUMBER;}

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.

Regeln

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.

Von der Spezifikation zum Programm

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:

Ein Beispiel

%{
  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;
}

Ein Scanner für OBERON-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;}




Next: Syntaktische Analyse Up: Lexikalische Analyse Previous: Endliche Automaten als Scanner

Prof. Dr. Reinhard Völler