NoPy hat geschrieben:Ich werde es gewiss nicht so machen, weil ich es nicht verstehe und nicht einfach mit copy/paste irgend etwas zusammenschubsen will.
Statt selber Parserkombinatoren zu bauen, kannst du eine entsprechende Bibliothek verwenden. Ich mag
Parcon. Dazu unten mehr.
NoPy hat geschrieben:Also ich habe grob verstanden, worauf Du hinauswillst, glaube ich. So eine Art "Teile und Herrsche".
Wie immer beim Programmieren.
NoPy hat geschrieben:Meine ersten Fragen (ich fange mal hinten an):
Was genau passiert hier? EXCEL_ADDRESS sorgt für das Erzeugen eines Objektes der Klasse InfixOperator, die von Parser erbt und als Parameter für den Konstruktor eine Oder- Verknüfung aller möglichen Beschreibungen für gültige Excel- Bereiche (wobei - soweit ich das überblickt habe - die Überprüfung, ob die von- Spalte/Zeile kleiner ist, als die bis- Spalte/Zeile noch nicht integriert ist, oder? Also C10:A3 wäre gültig, oder?)
Wie ist die Konstruktion EXCEL_ADDRESS = (Funktionsaufruf()) zu verstehen? Was wird damit definiert?
Man muss das alles aus ein paar Meter Entfernung betrachten, um die Grobstruktur erkennen zu können. Die übergreifende Idee ist, kleine Parser für Teilaufgaben zu verwenden und diese "zusammenzustecken", wodurch man wieder Parser erhält. Der Then-Parser beispielsweise bekommt bei der Initialisierung zwei Parser als Argumente. Er parst, indem er erst den ersten Parser aufruft, und erhält von diesem dessen Parsingergebnis und den restlichen, noch nicht geparsten Text. Mit diesem Text ruft Then dann den zweiten Parser auf, erhält von diesem dessen Parsingergebnis und den nicht geparsten restlichen Text. Die beiden Ergebnisse werden zu einer Liste zusammengefügt und diese Liste - durch self.join() transformiert - und der restliche Text werden als Returnwert zurückgegeben. Ein Parser ist also - mathematisch betrachtet - eine Funktion vom Typ
Text --> (ParsingResult, Text). Damit Parser Objekte dieses Interface zur Verfügung stellen, müssen sie aufrufbar sein und in Python macht man das, indem man die __call__()-Methode implementiert. Ein Objekt x mit dieser Methode kann ich mittels x(...) aufrufen. Dadurch, dass ich lediglich die Aufrufbarkeit verlange, erreiche ich, dass man auch einfache Parser-Funktionen statt Klassen schreiben kann.
Zur Vereinfachung der Parser-Kombination habe ich zwei Operatoren implementiert. p1 + p2 bedeutet damit dasselbe wie Then(p1, p2). p1 | p2 bedeutet dasselbe wie Either(p1, p2), und Either versucht erst mit p1 zu parsen und dann und nur dann mit p2, wenn p1 scheitert.
Dass man mit + aus zwei Parsern einen neuen machen kann ist im Prinzip dasselbe, wie wenn man mit + aus zwei Zahlen eine neue macht.
Die Prüfung darauf, dass der zweite Zeilen-/Spaltenwert nie kleiner ist als der vorhergehende, passiert in den make_*_range() Funktionen. In den make_*() Funktionen werden die Datenstrukturen aus der Listenform in eine aussagekräftigere Datenstruktur übersetzt, zusammen mit den genannten Prüfungen. Diese Funktionen werden den Parsern mithilfe des Subskriptionsoperators mitgegeben, eine Idee, die ich von Parcon geklaut habe. Meine Implementierung ist nicht ganz korrekt,
hier &
hier habe ich es besser gemacht.
Die Idee ist also, sowohl die Parser zu neuen Parsern kombinieren zu können, als auch die Ergebnisse so zu transformieren, wie man es haben möchte, ohne dazu in den Parserklassen herumfummeln zu müssen.
NoPy hat geschrieben:Code: Alles auswählen
def parse(self, t0):
result, rest = self(t0)
if rest:
raise ValueError('Found trailing string: {}'.format(rest))
else:
return result
was passiert bei self(t0)?
Wie oben schon beschrieben, ist auf Parsern die __call__()-Methode definiert, deswegen sind Parser aufrufbar. Und self ist ja ein Parser.
Hier noch eine Implementierung mit Parcon für Python 2.7:
http://www.python-forum.de/pastebin.php?mode=view&s=467
Der Grund, warum es monadische Bind und Return Parser gibt, ist übrigens, damit man kontextsensitive Grammatiken parsen kann. Angenommen Strings der Form <Länge n in Dezimalschreibweise>:<n Buchstaben>, dann kann man das mit Parcon ganz einfach so machen:
Code: Alles auswählen
from parcon import OneOrMore, Chars, digit, concat, Bind, End
LEN = OneOrMore(digit)[concat][int]
STR = Bind(LEN + ':', Chars) + End()
print(STR.parse_string('5:hallo'))
Ergebnis:
Wenn ich dagegen das hier versuche:
ist das Ergebnis:
Code: Alles auswählen
Traceback (most recent call last):
File "parc.py", line 158, in <module>
main1()
File "parc.py", line 154, in main1
print(STR.parse_string('5:halloabc'))
File "/home/mick/.virtualenvs/parcon/local/lib/python2.7/site-packages/parcon/__init__.py", line 619, in parse_string
raise ParseException("Parse failure: " + format_failure(result.expected))
parcon.ParseException: Parse failure: At position 7: expected one of EOF
Mit yacc, bison oder - Gott bewahre! - Regulären Ausdrücken kann man sowas nur schwer bis gar nicht parsen.
In specifications, Murphy's Law supersedes Ohm's.