Logo des digitalen Schulbuchs inf-schule.de. Schriftzug in Zustandsübergangsdiagramm eines endlichen Automaten.

Station - Tokenerzeugung mit dem Scanner

Aufgabe des Scanners

Aufgabe eines Scanners ist es, einen Quelltext in lexikalische Einheiten zu zerlegen oder anzuzeigen, dass eine solche Zerlegung nicht möglich ist.

x = 24
y = 15
d = x - y
while d != 0:
    if d > 0:
        x = x - y
    else:
        y = y - x
    #if
    d = x - y
#while

Beim vorliegenden Quelltext ergeben sich folgende lexikalische Einheiten:

'x', '=', '24', 'y', '=', '15', 'd', '=', 'x', '-', 'y', 'while', 'd', '!=', '0', ':', ...

Die lexikalischen Einheiten können unterschiedlichen Mustertypen zugeordnet werden. So gibt es ein Muster, nach dem Variablenbezeichner gebildet werden. Ein weiteres Muster gibt vor, mit welchem Symbol eine Zuweisung dargestellt wird. Für jedes dieser Muster wird ein Name eingeführt, VAR für Variablenbezeichner, ZUW für das Zuweisungssymbol usw..

Ein Scanner muss also erkennen, ob sich der Quelltext so in Zeichenketten zerlegen lässt, dass alle Teilzeichenketten einem der vorgegebenen Muster zugeordnet werden können. Zudem muss er die Folge der Token bestehend aus dem Mustername und der zugehörigen Zeichenkette erzeugen:

(VAR,'x')
(ZUW,'=')
(ZAHL,'24')
(VAR,'y')
(ZUW,'=')
(ZAHL,'15')
(VAR,'d')
(ZUW,'=')
(VAR,'x')
(MINUS,'-')
(VAR,'y')
(WHILE,'while')
(VAR,'d')
(UG,'!=')
(NULL,'0')
(DP,':')
...

Token-Festlegung mit regulären Ausdrücken

Der Aufbau lexikalischer Einheiten wird in der Regel mit Hilfe regulärer Ausdrücke beschrieben.

Als Beispiel betrachten wir Variablenbezeichner. Variablenbezeichner beginnen mit einem Kleinbuchstaben. Danach können beliebig viele Kleinbuchstaben oder Ziffern folgen. Dieses Muster lässt sich mit dem regulären Ausdruck [a-z][a-z0-9]* beschreiben.

Aufgabe 1

Der folgende Auszug aus einer Python-Implementierung zeigt, wie die regulären Ausdrücke zur Beschreibung der Token gebildet werden können.

# Beschreibung der Token
t_VAR    = r'[a-z][a-z0-9]*'
t_ZAHL   = r'[\+|-]?[1-9][0-9]*'
t_NULL   = r'0'
t_WHILE  = r'while'
t_IF     = r'if'
t_ELSE   = r'else'
t_PASS   = r'pass'
t_ENDWH  = r'\#while'
t_ENDIF  = r'\#if'
t_ZUW    = r'='
t_PLUS   = r'\+'
t_MINUS  = r'-'
t_GL     = r'=='
t_UG     = r'!='
t_GR     = r'\>'
t_KL     = r'\<'
t_DP     = r':'

(a) Welche Zeichenketten passen auf das Token-Muster ZAHL?. Gib Beispiele für solche Zeichenketten an. Beachte die Sonderrolle der Zahl Null, für die ein eigenes Token-Muster vorgesehen ist.

(b) Die Festlegung der Token-Muster ist in der vorliegenden Form nicht eindeutig. So passt z.B. die Zeichenkette 'if' auf zwei verschiedene Token-Muster. Welche sind das? Gibt es weitere problematische Zeichenketten?

Tokenerzeugung mit LEX

Das Programm LEX ist in der Lage, ausgehend von einer Tokenbeschreibung in Form regulärer Ausdrücke ein System zur lexikalischen Analyse zu erzeugen. Letztlich generiert LEX aus den regulären Ausdrücken endliche Automaten, die die Analyse von Zeichenketten vornehmen.

Der folgende Quelltext zeigt, wie man die LEX-Implementierung von PLY zur Tokenfestlegung nutzt.

# reservierte Wörter

reserved = {
   'if' : 'IF',
   'else' : 'ELSE',
   'while' : 'WHILE',
   'pass': 'PASS'
}

# Namen der Token

tokens = ['VAR', 'ZAHL', 'NULL', 'ZUW', 'PLUS', 'MINUS', 'GL', 'UG', 'GR', 'KL', 'DP', 'ENDWH', 'ENDIF']
tokens = tokens + list(reserved.values())

# Beschreibung der Token

def t_VAR(t):
    r'[a-z][a-z0-9]*'
    t.type = reserved.get(t.value, 'VAR') # Überprüfung auf reservierte Wörter
    return t

t_ZAHL    = r'[\+|-]?[1-9][0-9]*'
t_NULL    = r'0'
t_ZUW     = r'='
t_PLUS    = r'\+'
t_MINUS   = r'-'
t_GL      = r'=='
t_UG      = r'!='
t_GR      = r'\>'
t_KL      = r'\<'
t_DP      = r':'
t_ENDWH   = r'\#while'
t_ENDIF   = r'\#if'

# Ignorierte Zeichen
t_ignore = " \t"

def t_newline(t):
    r'\n+'
    t.lexer.lineno = t.lexer.lineno + t.value.count("\n")

def t_error(t):
    print("Illegal character '%s'" % t.value[0])
    t.lexer.skip(1)

Zunächst werden reservierte Wörter festgelegt. Das sind die Bezeichner, die nicht als Variablenbezeichner betrachtet werden sollen und daher vom Token-Muster VAR ausgeschlossen werden.

Es folgt die Festlegung der Tokennamen und der zugehörigen regulären Ausdrücke.

Beachte, dass weitere Festlegungen vorgenommen werden. So wird beispielsweise festgelegt, dass Leerzeichen, Tabulatoren und Zeilenumbrüche überlesen und nicht als eigene Token betrachtet werden.

Bevor wir LEX aktivieren, speichern wir die Tokenfestlegung in einer Datei (z.B. syntaxWhile.py) ab. Einen Scanner zu den vorgegebenen Tokenmustern kann man jetzt folgendermaßen erzeugen:

import ply.lex as lex
from syntaxWhile import *

# Testprogramm
programm = '''
x = 24
y = 15
d = x - y
while d != 0:
    if d > 0:
        x = x - y
    else:
        y = y - x
    #if
    d = x - y
#while
'''

# Erzeugung des Scanners
scanner = lex.lex(debug=0)

# lexikalische Analyse mit Erzeugung der Token
scanner.input(programm)
token = []
tok = scanner.token()
while tok:
    token = token + [tok]
    tok = scanner.token()

# Ausgabe
for tok in token:
    print(tok)

Der im Programm integrierte Test liefert dann folgende Ausgabe:

>>> 
LexToken(VAR,'x',2,1)
LexToken(ZUW,'=',2,3)
LexToken(ZAHL,'24',2,5)
LexToken(VAR,'y',3,8)
LexToken(ZUW,'=',3,10)
LexToken(ZAHL,'15',3,12)
LexToken(VAR,'d',4,15)
LexToken(ZUW,'=',4,17)
LexToken(VAR,'x',4,19)
LexToken(MINUS,'-',4,21)
LexToken(VAR,'y',4,23)
LexToken(WHILE,'while',5,25)
LexToken(VAR,'d',5,31)
LexToken(UG,'!=',5,33)
LexToken(NULL,'0',5,36)
LexToken(DP,':',5,37)
LexToken(IF,'if',6,43)
LexToken(VAR,'d',6,46)
LexToken(GR,'>',6,48)
LexToken(NULL,'0',6,50)
LexToken(DP,':',6,51)
LexToken(VAR,'x',7,61)
LexToken(ZUW,'=',7,63)
LexToken(VAR,'x',7,65)
LexToken(MINUS,'-',7,67)
LexToken(VAR,'y',7,69)
LexToken(ELSE,'else',8,75)
LexToken(DP,':',8,79)
LexToken(VAR,'y',9,89)
LexToken(ZUW,'=',9,91)
LexToken(VAR,'y',9,93)
LexToken(MINUS,'-',9,95)
LexToken(VAR,'x',9,97)
LexToken(ENDIF,'#if',10,103)
LexToken(VAR,'d',11,111)
LexToken(ZUW,'=',11,113)
LexToken(VAR,'x',11,115)
LexToken(MINUS,'-',11,117)
LexToken(VAR,'y',11,119)
LexToken(ENDWH,'#while',12,121)

Aufgabe 2

(a) Probiere das selbst einmal aus. Teste auch andere Quelltexte. Teste u.a. den Quelltext x=24y=15d=x-ywhiled!=0:ifd>0:x=x-yelse:y=y-x#ifd=x-y#while. Teste auch solche Quelltexte, die sich nicht in die vorgegebenen Token zerlegen lassen. Wie reagiert der Scanner auf Variablenbezeichner der Gestalt 007? Hast du eine Vermutung? Was macht der Scanner mit einem unsinnigen Quelltext wie z.B. :while 7:? Hast du eine Vermutung?

(b) Versuche, durch Tests herauszufinden, welche Bedeutung die zusätzlichen Zahlangaben in den Token haben.

(c) Ändere selbst die Beschreibung der Token in sinnvoller Weise ab und teste die neuen Festlegungen.

Aufgabe 3

Das oben gezeigte MyWhile-Programm würde man in einer Java-ähnlichen Schreibweise so darstellen:

x = 24;
y = 15;
d = x - y;
while (d != 0) {
    if (d > 0) {
        x = x - y;
    }
    else {
        y = y - x;
    };
    d = x - y;
};

Ändere die Beschreibung der Token so ab, dass sie auf die Java-ähnliche Schweibweise passt.

X

Fehler melden

X

Suche