2009-11-14 6 views
5

Ich versuche, Wörter zu analysieren, die über mehrere Zeilen mit einer Backslash-Newline-Kombination (\\n) pyparsing aufgeteilt werden können. Hier ist, was ich getan habe:Mit pyparsing ein Wort Escape-Split über mehrere Zeilen analysieren

from pyparsing import * 

continued_ending = Literal('\\') + lineEnd 
word = Word(alphas) 
split_word = word + Suppress(continued_ending) 
multi_line_word = Forward() 
multi_line_word << (word | (split_word + multi_line_word)) 

print multi_line_word.parseString(
'''super\\ 
cali\\ 
fragi\\ 
listic''') 

Der Ausgang ich erhalte, ist ['super'], während die erwartete Ausgabe ['super', 'cali', fragi', 'listic'] ist. Besser wäre noch alle von ihnen als ein Wort verbunden (was ich denke, ich kann mit multi_line_word.parseAction(lambda t: ''.join(t)) gerade tun.

habe ich versucht, auf diesem Code in pyparsing helper suchen, aber es gibt mir einen Fehler, maximum recursion depth exceeded.

EDIT 2009-11-15: Später wurde mir klar, dass das Puparsing in Bezug auf Leerraum ein wenig großzügiger wird, und das führt zu einigen schlechten Annahmen, dass das, wofür ich dachte, viel loser war, das heißt, wir wollen es sehe keinen Leerraum zwischen irgendeinem der Teile des Wortes, des Escape und des EOL-Zeichens

Ich erkannte, dass die Der obige kleine Beispielstring ist als Testfall nicht ausreichend, daher schrieb ich die folgenden Komponententests. Code, der diese Tests besteht, sollte in der Lage sein, das, was ich intuitiv als Escape-Split-Wort — und nur ein Escape-Split-Wort denken. Sie werden keinem Grundwort entsprechen, das nicht aus der Not geteilt ist. Wir können — und ich glaube, dass — ein anderes grammatikalisches Konstrukt dafür verwenden sollte. Dies hält alles sauber, die zwei getrennt zu haben.

import unittest 
import pyparsing 

# Assumes you named your module 'multiline.py' 
import multiline 

class MultiLineTests(unittest.TestCase): 

    def test_continued_ending(self): 

     case = '\\\n' 
     expected = ['\\', '\n'] 
     result = multiline.continued_ending.parseString(case).asList() 
     self.assertEqual(result, expected) 


    def test_continued_ending_space_between_parse_error(self): 

     case = '\\ \n' 
     self.assertRaises(
      pyparsing.ParseException, 
      multiline.continued_ending.parseString, 
      case 
     ) 


    def test_split_word(self): 

     cases = ('shiny\\', 'shiny\\\n', ' shiny\\') 
     expected = ['shiny'] 
     for case in cases: 
      result = multiline.split_word.parseString(case).asList() 
      self.assertEqual(result, expected) 


    def test_split_word_no_escape_parse_error(self): 

     case = 'shiny' 
     self.assertRaises(
      pyparsing.ParseException, 
      multiline.split_word.parseString, 
      case 
     ) 


    def test_split_word_space_parse_error(self): 

     cases = ('shiny \\', 'shiny\r\\', 'shiny\t\\', 'shiny\\ ') 
     for case in cases: 
      self.assertRaises(
       pyparsing.ParseException, 
       multiline.split_word.parseString, 
       case 
      ) 


    def test_multi_line_word(self): 

     cases = (
       'shiny\\', 
       'shi\\\nny', 
       'sh\\\ni\\\nny\\\n', 
       ' shi\\\nny\\', 
       'shi\\\nny ' 
       'shi\\\nny captain' 
     ) 
     expected = ['shiny'] 
     for case in cases: 
      result = multiline.multi_line_word.parseString(case).asList() 
      self.assertEqual(result, expected) 


    def test_multi_line_word_spaces_parse_error(self): 

     cases = (
       'shi \\\nny', 
       'shi\\ \nny', 
       'sh\\\n iny', 
       'shi\\\n\tny', 
     ) 
     for case in cases: 
      self.assertRaises(
       pyparsing.ParseException, 
       multiline.multi_line_word.parseString, 
       case 
      ) 


if __name__ == '__main__': 
    unittest.main() 

Antwort

5

Nach etwa mehr für ein bisschen Stossen, kam ich auf this help thread, wo es dieses bemerkenswerte Bit

war

Ich sehe oft ineffizient Grammatiken, wenn jemand eine pyparsing Grammatik direkt von einer BNF-Definition implementiert. BNF hat kein Konzept „ein oder mehr“ oder „null oder mehr“ oder „optional“ ...

Damit hatte ich die Idee, diese beiden Linien zu ändern

multi_line_word = Forward() 
multi_line_word << (word | (split_word + multi_line_word)) 

um

multi_line_word = ZeroOrMore(split_word) + word 

Das hat es ausgeben, was ich suchte: ['super', 'cali', fragi', 'listic'].

Als nächstes Ich habe eine Parse-Aktion, die diese Token miteinander verbinden würde:

multi_line_word.setParseAction(lambda t: ''.join(t)) 

Dies ergibt eine endgültige Ausgabe von ['supercalifragilistic'].

Die Take-Home-Nachricht, die ich gelernt habe, ist, dass man nicht einfach walk into Mordor.

Nur Spaß.

Die Take-Home-Nachricht ist, dass man nicht einfach eine Eins-zu-Eins-Übersetzung von BNF mit Pyparsing implementieren kann. Einige Tricks mit den iterativen Typen sollten in Gebrauch genommen werden.

EDIT 2009-11-25: für die anstrengenden Testfälle zu kompensieren, modifizierte ich den Code auf den folgende:

no_space = NotAny(White(' \t\r')) 
# make sure that the EOL immediately follows the escape backslash 
continued_ending = Literal('\\') + no_space + lineEnd 
word = Word(alphas) 
# make sure that the escape backslash immediately follows the word 
split_word = word + NotAny(White()) + Suppress(continued_ending) 
multi_line_word = OneOrMore(split_word + NotAny(White())) + Optional(word) 
multi_line_word.setParseAction(lambda t: ''.join(t)) 

Dies den Vorteil, dass Sie sicher hat, dass kein Raum zwischen jedem kommt der Elemente (mit Ausnahme von Zeilenumbrüchen nach den ausgehenden Backslashes).

+1

Die Verwendung von "Kombinieren" erzwingt auch keine dazwischen liegenden Leerräume. – PaulMcG

+0

Interessant. versucht 'multi_line_word = Kombinieren (Kombinieren (OneOrMore (split_word)) + Optional (Wort)) aber es bricht auf die" sh \\\ n iny "Fall in dem es keine Ausnahme, sondern statt gibt '[' sh '] 'zurück. Fehle ich etwas? – gotgenes

+0

Nun, dein Wort besteht nicht nur aus Buchstaben, die ein '\' - Zeilenumbruch überspannen, aber da ist dieser Raum vor dem Buchstaben 'i', der als Wortbruch zählt, also hört Combine nach dem 'sh' auf. Sie * können * ändern Kombinieren Sie mit einem benachbarten = False-Konstruktor-Argument, aber Vorsicht - Sie könnten am Ende die gesamte Datei als ein einziges Wort aufzusaugen! Oder Sie können Ihre Definition von continuous_ending neu definieren, um nach der ZeileEnde beliebige Leerzeichen einzufügen, wenn Sie auch führende Leerzeichen ausblenden möchten. – PaulMcG

5

Sie sind ziemlich nah mit Ihrem Code. Jede dieser Mods funktionieren würde:

# '|' means MatchFirst, so you had a left-recursive expression 
# reversing the order of the alternatives makes this work 
multi_line_word << ((split_word + multi_line_word) | word) 

# '^' means Or/MatchLongest, but beware using this inside a Forward 
multi_line_word << (word^(split_word + multi_line_word)) 

# an unusual use of delimitedList, but it works 
multi_line_word = delimitedList(word, continued_ending) 

# in place of your parse action, you can wrap in a Combine 
multi_line_word = Combine(delimitedList(word, continued_ending)) 

Wie Sie in Ihrem pyparsing Googeln gefunden, BNF-> pyparsing Übersetzungen sollte mit einem besonderen Blick auf die Verwendung pyparsing Funktionen anstelle von BNF, um, Mängel erfolgen. Ich war gerade dabei, eine längere Antwort zu verfassen, in mehr BNF-Übersetzungsthemen zu gehen, aber Sie haben dieses Material bereits gefunden (auf dem Wiki, nehme ich an).