class Grammar:
def __init__(self, sourceName):
self._readRules(sourceName)
def _readRules(self, sourceName):
self.rules = {}
for rawRule in open(sourceName).readlines():
if rawRule.startswith("="):
self._setStartSymbol(rawRule)
else:
self._addRule(Rule(rawRule))
def _addRule(self, rule):
self.rules.setdefault(rule.getLeft(),
[]).append(rule)
def _setStartSymbol(self, rawSym):
self.startSymbol = rawSym[1]
def getRulesForNonTerm(self, nonTerm):
return self.rules.get(nonTerm, [])
def deriveLeftNonTerm(self, nonTerm, word):
res = []
for rule in self.getRulesForNonTerm(nonTerm):
res.append(rule.applyToLeftmost(word))
return res
def getStartSymbol(self):
return self.startSymbol
Anmerkungen:
- Der Konstruktor muss hier die Grammatik aus der angegebenen Datei lesen – wir haben diese
Funktionalität aber in eine separate Methode verlegt, um den Konstruktor übersichtlich zu halten (was bei
komplexeren Klassen manchmal eine echte Herausforderung ist).
- In _readRules wird eine Instanzvariable rules
(korrekt mit self davor, denn sonst würde es die Variable ja nur in _readRules geben) auf das leere Dictionary
initialisiert, das dann in _addRules gefüllt wird.
- Ein paar Methoden haben Underscores vor dem Namen. Das soll
andeuten, dass sie nur klassenintern verwendet und nicht von außen aufgerufen werden sollen. Python erzwingt das
aber nicht. Namen mit zwei Underscores am Anfang werden von Python in der Tat “versteckt”. Wer nicht versteht,
warum man das vielleicht haben möchte, soll sich vorerst keine Sorgen machen.
- Wir haben die alte
readRules-Funktion aufgespalten in die Methoden _readRules und _addRules. Das folgt der Logik,
dass eine Funktion eine Sache tun soll (und die gut). In diesem Fall ist plausibel, dass das Parsen einer
Regelspezifikation gemäß irgendeiner Vereinbarung unabhängig sein sollte vom Hinzufügen einer bereits
maschinengemäß repräsentierten Regel zur Regelbasis der Grammatik. Im Zweifel sollte man Methoden
lieber weiter aufspalten – wenn man testen will, ob zwei Aktionen in einer Methode bleiben dürfen, kann
man sich überlegen, ob mindestens eine der Aktionen mit einer dritten sinnvoll kombinierbar wäre. Im
vorliegenden Fall wäre es beispielsweise denkbar, dass ein zweites Grammatikobjekt einer Grammatik bereits
geparste Regeln hinzufügen möchte oder wir Regeln auch in einem anderen Format verstehen wollen.
- In dem
Dictionary steht jetzt auch kein nackter String mehr, sondern Objekte einer neuen Klasse, Rule (die wir
natürlich auch noch schreiben müssen).
- Der alte Dictionary-Zugiff wird ersetzt durch eine Methode
getRulesForNonTerm, was klarer macht, was der Zugriff eigentlich bedeutet.
- Natürlich sollte auch hinter der Zeile, die
die Klassendefinition einleitet, ein Docstring stehen, der grob umreißt, was die Klasse soll und was sie
kann.
Inhaltlich haben noch eine Veränderung vorgenommen: In der Grammatikbeschreibung kann man jetzt nach den
Regeln der Kunst auch ein Startsymbol angeben, und zwar in einer Zeile, in der nur ein = und danach das Startsymbol
steht. Natürlich sollte _setStartSymbol etwas besser aufpassen, was es da eigentlich vorgesetzt kriegt und
ggf. Fehler auslösen, aber das soll uns jetzt noch egal sein.
Zum Setzen der Instanzvariable startSymbol stellen wir eine Methode getStartSymbol zur Verfügung. Der
Underscore vor _setStartSymbol sagt wie oben vereinbart: Klienten sollen diese Methode nicht verwenden
(und in der Tat würde sie auch etwas anderes tun als ihr Name die Klienten erwarten lassen würde).