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.
Voraussetzungs - Parser mit pyparsing
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:
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
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
Fehler mit "PRINT" zu melden geht schließlich IMHO gar nicht. Ich würde dort Exceptions werfen.
Stefan
@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.
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
Stefan
Bei rechtsassoziativen Operatoren wird nicht gedreht, sondern es werden, wie von Dir bevorzugt, mehrere Objekte angelegt.