Station - Syntaxanalyse mit dem Parser
Aufgabe des Parsers
Ein Parser analysiert, ob eine Tokenfolge zu einem Programmquelltext die vorgegebenen Grammatikregeln befolgt.
Wir betrachten wieder den folgenden Quelltext:
links while nichtVorWand: ziegelHinlegen schritt #while
Ein Scanner erzeugt mit Hilfe vorgegebener Tokenmuster aus diesem Quelltext eine Tokenfolge:
(ELANW,'links') (WH,'while') (BED,'nichtVorWand') (DP,':') (ELANW,'ziegelHinlegen') (ELANW,'schritt') (WH_ENDE,'#while')
Der Parser muss jetzt u.a. überprüfen, ob eine Wiederholung aus Token der Gestalt
WH BED : ... WH_ENDE
besteht. Hierzu wird zunächst
die Grammatik der Sprache While festgelegt.
Festlegung der Grammatik
Die Terminalsymbole der Grammatik sind die Tokennamen. Die Nichtterminalsymbole ergeben sich aus den linken Seiten der folgenden Produktionen. Startsymbol ist das Symbol auf der linken Seite der ersten Produktion.
# Produktionen anweisungsfolge -> anweisung anweisungsfolge anweisungsfolge -> anweisung anweisung -> ELANW anweisung -> PASS anweisung -> WH bedingung DP anweisungsfolge WH_ENDE ... bedingung -> BED
Nutzung von YACC
Das Programm YACC ist in der Lage, ausgehend von einer Grammatikbeschreibung ein System zur syntaktischen Analyse zu erzeugen. Letztlich generiert YACC einen Shift-Reduce-Parser (siehe ...), der die syntaktische Analyse vornimmt.
Bevor wir YACC aktivieren, speichern wir die Grammatik in einer bestimmten Form
zusätzlich zur Tokenfestlegung in einer Datei (z.B. syntaxMyKa.py
) ab.
# Namen der Token tokens = ['ELANW', 'BED', 'DP', 'WH', 'WH_ENDE', 'IF', 'IF_ENDE', 'ELSE', 'PASS'] # Beschreibung der Token t_ELANW = r'schritt|links|rechts|ziegelHinlegen|ziegelAufheben|markeSetzen|markeLoeschen' t_BED = r'vorZiegel|nichtVorZiegel|vorWand|nichtVorWand|aufMarke|nichtAufMarke' t_DP = r':' t_WH = r'while' t_WH_ENDE = r'\#while' t_IF = r'if' t_IF_ENDE = r'\#if' t_ELSE = r'else' t_PASS = r'pass' # 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) # Produktionen def p_anweisungsfolge_anweisung_anweisungsfolge(p): 'anweisungsfolge : anweisung anweisungsfolge' p[0] = None def p_anweisungsfolge_anweisung(p): 'anweisungsfolge : anweisung' p[0] = None def p_anweisung_elementar(p): 'anweisung : ELANW' p[0] = None def p_anweisung_pass(p): 'anweisung : PASS' p[0] = None def p_anweisung_wh(p): 'anweisung : WH bedingung DP anweisungsfolge WH_ENDE' p[0] = None # def p_anweisung_if(p): def p_bedingung(p): 'bedingung : BED' p[0] = None # Fehlermeldung def p_error(p): print("Syntaxfehler!")
Beachte, dass die Grammatikregeln hier innerhalb von Prozedurdeklarationen vorkommen. Die Bedeutung der zur Grammatikregel gehörenden Prozedur wird erst im nächsten Abschnitt klar werden.
Scanner und Parser zur Syntax von MyKa kann man jetzt folgendermaßen erzeugen:
import ply.lex as lex import ply.yacc as yacc from syntaxMyKa import * # Testprogramm programm = ''' links while nichtVorWand: ziegelHinlegen schritt #while ''' # Erzeugung des Scanners scanner = lex.lex(debug=0) # Erzeugung des Parsers parser = yacc.yacc(debug=False) # syntaktische Analyse parser.parse(programm, debug=0) # Ausgabe print("ok")
Aufgabe 1
Probiere das selbst einmal aus. Teste Quelltexte, die den Syntaxregeln von MyKa entsprechen und teste auch fehlerhafte Quelltexte. Wie zeigt sich in der vorliegenden Implementierung, ob der Quelltext fehlerfrei ist?
Aufgabe 2
Ergänze die noch fehlende Syntaxregel für Fallunterscheidungen. Teste deinen Vorschlag.