Voraussetzungs - Parser mit pyparsing

Code-Stücke können hier veröffentlicht werden.
Antworten
fred.reichbier
User
Beiträge: 155
Registriert: Freitag 29. Dezember 2006, 18:27

Hallo,

dank eurer schnellen und guten Ratschläge habe ich es jetzt endlich geschafft, einen richtigen Parser zu schreiben, der boolesche Ausdrücke mit (sicheren) Funktionsaufrufen verarbeiten kann.
Hier mal der Code: Klick.
Bemerkungen oder Tipps zu meinem Code wären schön ;)
Zudem wollte ich noch fragen, ob das mit der Lizenz so ok ist. Das Ganze basiert auf dem simpleBool.py-Beispiel, das bei pyparsing dabei ist. Pyparsing selbst ist unter der MIT-lizenziert, ist mein Copyrighthinweis so ok?

Gruß Fred

Edit: Da war noch ein kleiner Fehler drin. Sollte nun gehen.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Ich muss sagen, mir missfällt schon der Originalcode, da ist es schwer, deinen Code zu bewerten. Pyparsing soll doch eigentlich lesbare Parser erzeugen. Das ist mit diesem Beispiel IMHO nicht wirklich gelungen. Mich stört insbesondere, dass überall geprüft wird, ob ein Argument wohl ein String (offenbar eine Variable wie p, q, r) oder etwas anderes (offenbar ein BoolOperand) ist. Statt da __nonzero__ zu überschreiben hielte ich es für sauberer, überall eine `eval()`-Methode (oder ähnliches) zu haben und keine solche komischen Fallunterscheidungen zu treffen. Diese sind unnötig, wenn den Parse-Tree richtig baut. Ich vermute, dass `operatorPrecedence` N-stellige und nicht nur zweistellige Operatoren kennt, denn nur so ist zu erklären, warum da überall Schleifen benutzt werden. Oder ist die Zahl da die Anzahl der Operanden? In diesem Fall verstehe ich nicht die Schleifen.

Ich hätte diesen AST erwartet:

Code: Alles auswählen

class And:
  def __init__(self, t): self.a, self.b = t[0][0::2]
  def eval(self): return self.a.eval() & self.b.eval()
class Or:
  def __init__(self, t): self.a, self.b = t[0][0::2]
  def eval(self): return self.a.eval() | self.b.eval()
class Not:
  def __init__(self, t): self.e = t[0][1]
  def eval(self): return not self.e.eval()
class Var:
  def __init__(self, t): self.n = t[0][0]
  def eval(self): return eval(self.n) # nun ja...
class Lit:
  def __init__(self, t): self.v = t[0][0] == 'True'
  def eval(self): return self.v
Für deinen Funktionsaufruf gibt es doch bei Pyparsing bestimmt einen besseren Ausdruck für eine kommaseparierte Liste, doch da die Doku mich nur auf ein O'Reilly-Buch verweist, habe ich keine Lust zu suchen. Ich würde minimal etwas wie `fooExpr + ZeroOrMore("," + fooExpr)` erwarten. Das wäre IMHO schöner als der jetzige Ansatz, der kein "," kennt. Ich hätte auch für den "Call" einen AST-Knoten erwartet und nicht eine direkte Funktion. Was mich auch wundert ist, dass du die Argumente gar nicht auswerten willst und es ausschließlich Strings sein können. Und warum das Ergebnis immer eine einelementige Liste mit einem String ist, wirst du am besten wissen. Ich finde es einfach nur komisch und der Kommentar "done!" klärt nichts auf.

Fehler mit "PRINT" zu melden geht schließlich IMHO gar nicht. Ich würde dort Exceptions werfen.

Stefan
BlackJack

@sma: Die Schleifen sind da, weil gleiche Operatoren zusammengefasst werden. Für 'a or b or c' wird nur *ein* `BoolOr`-Objekt erstellt, dass 'a', 'b' und 'c' als `args` hat.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Das hatte ich ja vermutet, gefällt mir aber nicht. Wenn ich einen 2-stelligen Operator habe, der rechts-Assoziativ ist, müsste PyParsing ja die Parameter drehen... oder muss ich das jetzt auch im Parser wissen? Ich finde es besser, wenn a := b := c zu a := (b := c) wird und ich mich im AST nicht mehr um Assoziativität kümmern muss.

Stefan
BlackJack

Bei rechtsassoziativen Operatoren wird nicht gedreht, sondern es werden, wie von Dir bevorzugt, mehrere Objekte angelegt.
Antworten