Seite 1 von 1

(E)BNF parser mit Unicode support ???

Verfasst: Donnerstag 2. Dezember 2010, 10:19
von pyrom
Hallo zusammen,

Schrei um Hilfe... 8))

ich suche einen guten und leicht lesbaren (Grammatik) (E)BNF Parser mit Unicode Support.

Ich habe unterschiedliche Textdateien, die mit einer (mehreren) Grammatik(en) beschreiben werden sollen.

Habe SimpleParse 2.1 ausprobiert, aber unicode support hat er nicht.

Vielleicht benutzt oder kennt jemand einen ähnlichen Parser?

Vielen Dank im Voraus,

pyrom

Re: (E)BNF parser mit Unicode support ???

Verfasst: Donnerstag 2. Dezember 2010, 10:33
von BlackJack
@pyrom: Soweit ich mich erinnere, kann man `pyparsing` mit Unicode füttern. Also auch in der Grammatik selbst. Mit der Einschränkung, dass die Parser-Objekte selbst nicht mehr mit `str()`/`repr()` in eine Zeichenkette umgewandelt werden können. Das gibt dann einen `UnicodeEncodeError`.

Re: (E)BNF parser mit Unicode support ???

Verfasst: Donnerstag 2. Dezember 2010, 20:00
von Leonidas
Nachdem LEPL mit Python 3 läuft gehe ich stark davon aus, dass es auch Unicode unterstützt...

Re: (E)BNF parser mit Unicode support ???

Verfasst: Freitag 3. Dezember 2010, 00:06
von derdon
PLY kann ich auch in den Raum werfen.

Re: (E)BNF parser mit Unicode support ???

Verfasst: Freitag 3. Dezember 2010, 10:57
von pyrom
Danke,

wrde heute LEPL und PLY probieren.... poste hier die Ergebnisse.

eine Frage haben die beide als parse result ein Tree, List oder Dict ??

Re: (E)BNF parser mit Unicode support ???

Verfasst: Freitag 3. Dezember 2010, 12:23
von sma
Abhängig davon, wie kompliziert die Grammatik ist, kann man's auch einfach selbst machen. Eine EBNF-Grammatik hat ja gerade die Eigenschaft, dass man sie per Hand in einen rekursiv absteigenden LL(1) Parser verwandeln kann (zeigt Wirth als Erfinder dieser BNF-Variante sehr schön in seinem kleinen Compilierbau-Büchlein).

Beispiel: Gegeben sei folgende Grammatik:

Code: Alles auswählen

sexp = atom | list
atom = SYMBOL | NUMBER
list = "(" {sexp} ["." sexp] ")"
Dann kann ich wie folgt einen Parser dafür bauen. Ich baue ihn klassisch in zwei Teilen. Zunächst erkenne ich SYMBOL und NUMBER:

Code: Alles auswählen

class Parser:
    def __init__(self, source):
        self.source = source
    
    def peek(self):
        "Return the next char or the empty string."
        return self.source[0] if self.source else ""
    
    def consume(self):
        "Return the next char, consuming it."
        ch = self.peek()
        self.source = self.source[1:]
        return ch
    
    def next_token(self):
        "Return the next token (one of `().NS`) and (in case of `NS`) its value."
        while self.peek() in "\n ": self.consume()
        if self.peek() == "": return "", None
        if self.peek() == "(": return self.consume(), None
        if self.peek() == ")": return self.consume(), None
        if self.peek() == ".": return self.consume(), None
        s = self.consume()
        while self.peek() and self.peek() not in "\n (.)":
            s += self.consume()
        if s.isdigit():
            return "N", int(s)
        else:
            return "S", s
Testen wir schnell, ob `next_token` funktioniert:

Code: Alles auswählen

p = Parser("(defn fac (n) (? (= n 0) 1 (* n (fac (1- n)))))")
n, v = p.next_token()
while n:
    print(n, v)
    n, v = p.next_token()
( None
S defn
S fac
...
Nun kann ich den eigentlichen Parser beginnen. Als LL(1)-Parser muss ich immer Zugriff auf das nächste Symbol haben, bei mir ein Tupel aus Typ und Wert. In Python 3, was ich hier benutze, gibt es leider nicht mehr die IMHO praktische Möglichkeit, ein übergebenes Tupel sofort in der Argumentliste zu zerlegen. Daher muss ich hässlicherweise mit tv[0] und tv[1] arbeiten. Möglicherweise wären zwei Exemplarvariablen doch die bessere Modellierung gewesen.

Code: Alles auswählen

    def parse(self):
        return self.parse_sexp(self.next_token())
    
    def parse_sexp(self, tv):
        if tv[0] in "NS": return self.parse_atom(tv)
        if tv[0] == "(": return self.parse_list(self.next_token())
        raise SyntaxError
    
    def parse_atom(self, tv):
        return tv[1]
    
    def parse_list(self, tv):
        if tv[0] == ")": return None
        if tv[0] == ".":
            v = self.parse_sexp(self.next_token())
            t, _ = self.next_token()
            if t == ")": return v
            raise SyntaxError
        return (self.parse_sexp(tv), self.parse_list(self.next_token()))
Mein Parser wandet den Lisp S-Ausdruck in Cons-Zellen, repräsentiert durch Python-Tupel und Atome, repräsentiert durch Python Strings und Integers, um. Los geht's mit `parse`, welches die erste Regel, `parse_sex` mit dem ersten Token aufruft. In EBNF sieht diese Regel so aus: "sexp = atom | list". Die Theorie spricht jetzt von einer first(R)-Menge, die man für jede Regel R definieren kann und die jeweils die Token enthält, mit denen jede Alternative einer enthaltenen Regel beginnt bzw. die Token (aka Nichtterminalsymbole) selbst. Es ist schnell zu sehen, dass

Code: Alles auswählen

first(atom) = NUMBER, SYMBOL
first(list) = "("
first(sexp) = first(atom) + first(list)
Um also zu entscheiden, ob wir ein Atom oder eine Liste parsen wollen, müssen wir uns anschauen, welches Token wir vorliegen haben. Dann verzweigen wir zu `parse_atom` oder `parse_list`, wobei wir hier das ( konsumieren und die Methode mit den nächsten Token aufrufen. Bei `parse_atom` ist nichts weiter zu tun als einfach den Wert des Tokens zurückzugeben. Die Methode `next_token` (mein Scanner), macht das "zufällig" schon richtig.

Bei `parse_list` ist es wichtig, die rest(R)-Menge zu kennen. Was passiert, wenn weder das {} noch das [] zutrifft? Dann haben wir ein ")". In diesem Fall haben wir die komplette Liste erkannt. Das [] wird zu einem "if". Das {} wird in der Regel zu seinem "while", doch ich habe mich hinreißen lassen und die rekursive Struktur von aus Cons-Zellen bestehenden Lisp-Listen ausgenutzt und einen rekursiven Ansatz gewählt. Es funktioniert:

Code: Alles auswählen

p = Parser("(defn fac (n) (? (= n 0) 1 (* n (fac (1- n)))))")
print(p.parse())
('defn', ('fac', (('n', None), (('?', (('=', ('n', (0, None))), (1, (('*', ('n', (('fac', (('1-', ('n', None)), None)), None))), None)))), None))))
Da ich neben proper lists auch improper lists bzw. dotted pairs erkenne, kann ich nicht einfach eine Python-Liste als Repräsentation benutzen. Aber ich kann so tun und wenigstens den Parser dafür ohne Rekursion schreiben. Dadurch wird klarer, wie die Struktur der Grammatik eins zu eins in ein Programm umgewandelt werden kann:

Code: Alles auswählen

    def parse_list(self, tv):
        while tv[0] not in ".)":
            self.parse_sexpr(tv)
            tv = self.next_token()
        if tv[0] == ".":
            self.parse_sexpr(self.next_token())
        tv = self.next_token()
        if tv[0] == ")": return
        raise SyntaxError
Stefan

Re: (E)BNF parser mit Unicode support ???

Verfasst: Montag 6. Dezember 2010, 11:38
von pyrom
@Stefan: Danke Stefan, ich würde gerne diesen Weg schlagen, aber ich suchte mehr etwas fertiges, um die Zeit daran nicht zu verlieren... Trotz Danke. Ich werde diesen Beispiel woanders einsetzen...

Ich habe am FR mit pyparsing experimentiert und habe es hin gekriegt unicode Daten zu parsen... Danke an alle!

Eine Frage habe ich doch: an Non-terminals kann man Namen setzen, so dass man später über die auf Daten in Dict zugreifen kann...:

value = alphas
name = alphas
entry = name("NAME") Suppress("=") value("VAL")

Gibt es die Möglichkeit, dass pyparsing die Namen zu Non-terminals selbst automatisch in jeder Grammatik setzt?

Danke an alle!

Re: (E)BNF parser mit Unicode support ???

Verfasst: Montag 6. Dezember 2010, 13:42
von Dauerbaustelle
Du könntest auch mal dparser ausprobieren, das ist quasi Parser und Lexer in einem und der ist relativ einfach zu bedienen. (http://dparser.sf.net)

Die Bindings sind kaputt für aktuelle Python-Versionen (oder Swing-Versionen?), drum hab' ich die vor ner Weile mal repariert, hier gibts nen dparser mit funktionierenden Python-Bindings.

Re: (E)BNF parser mit Unicode support ???

Verfasst: Dienstag 7. Dezember 2010, 18:16
von pyrom
Danke, werde morgen früh dparser probieren...

By the way: alle Parser, die ich ausprobiert habe, können Texte parsen.... aber können sich auch die geparsten Daten (mit angehängten neuen Daten) wieder in die ursprüngliche Datei gespeichert werden (mitsamt Kommentare und etc. an selben Positionen, außer man fügt etwas hinzu oder löscht im ParseResult)?

Vielen Dank für Eure Posts..

Re: (E)BNF parser mit Unicode support ???

Verfasst: Dienstag 7. Dezember 2010, 18:20
von Leonidas
Wenn du Whitespace-Tokens mitführst, kann schon sein. Allerdings fand ich es mit Pyparsing sehr unbequem Whitespace für irgendwas zu nutzen, der hat gerne drauf bestanden Whitespace zu ignorieren.

Re: (E)BNF parser mit Unicode support ???

Verfasst: Dienstag 7. Dezember 2010, 18:30
von pyrom
mal angenommen, es wir jedes Zeichen geparst (Daten und "[ |\t\n]")...

wie schreibe ich die Daten aus ResultTree zurück in die Datei? (Ohne eigenen Deserializer zu schreiben.. sonst lohnt es sich nicht, denn die unterschiedlichen Texte unterschiedliche Objekte beinhalten).