2009-11-14 13 views
5

Próbuję parsować słowa, które mogą być podzielone na wiele linii z kombinacji backslash-newline ("\\n") przy użyciu pyparsing. Oto co zrobiłem:Korzystanie z pyparsing do parsowania wyrazu escape-split na wiele linii

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''') 

Wyjście mogę to ['super'], zaś oczekiwany wynik jest ['super', 'cali', fragi', 'listic']. Jeszcze lepiej byłoby je wszystkie dołączył jako jedno słowo (które myślę, że mogę po prostu zrobić z multi_line_word.parseAction(lambda t: ''.join(t)).

Próbowałem patrząc na ten kod w pyparsing helper, ale daje mi błąd, maximum recursion depth exceeded.

EDIT 2009-11-15: Uświadomiłem sobie później, że pyparsing robi się trochę hojny w odniesieniu do białej przestrzeni, a to prowadzi do pewnych złych założeń, że to, o czym myślałem, że analizowałem było dużo luźniejsze. nie widzę żadnej białej przerwy między częścią słowa, ucieczką i znakiem EOL. mały przykładowy ciąg powyżej jest niewystarczający jako przypadek testowy, dlatego napisałem następujące testy jednostkowe. Kod, który przejdzie te testy, powinien być w stanie dopasować to, co intuicyjnie uważam za słowo "ucieczkę-split" — i tylko słowo "ucieczka". Nie pasują do podstawowego słowa, które nie jest podzielone przez ucieczkę. Możemy: — i uważam, że powinienem użyć innej konstrukcji gramatycznej. Utrzymuje to wszystko w porządku, mając dwie oddzielne.

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() 

Odpowiedz

5

Po wywiercenie na trochę więcej, natknąłem this help thread tam, gdzie to było zauważalną nieco

często widzę nieefektywnych gramatyk gdy ktoś realizuje gramatyki pyparsing bezpośrednio z definicji BNF. BNF nie posiada koncepcję „jednego lub więcej” lub „zero lub więcej” lub „opcjonalne” ...

z tym, wpadłem na pomysł, aby zmienić te dwie linie

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

aby

multi_line_word = ZeroOrMore(split_word) + word 

ten dostał się do wyjścia, co szukałem: ['super', 'cali', fragi', 'listic'].

Następnie dodałem działań parsującej które dołączyć te znaki razem:

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

Daje to ostateczny wyjście ['supercalifragilistic'].

Wiadomość o odebraniu w domu, której się nauczyłem, polega na tym, że nie oznacza to po prostu walk into Mordor.

Po prostu żartuję.

Komunikat "zabierz do domu" polega na tym, że nie można po prostu zaimplementować tłumaczenia BNF w trybie jeden-do-jednego z pyparstwem. Niektóre sztuczki z użyciem typów iteracyjnych powinny być uruchamiane.

EDIT 2009-11-25: Aby zrekompensować bardziej uciążliwych przypadkach testowych, I zmodyfikowany kod do następujących:

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)) 

ta ma tę zaletę, upewniając się, że nie ma miejsca pochodzi między dowolnymi elementów (z wyjątkiem nowych linii po ucieczkowych ukośnikach odwrotnych).

+1

Użycie 'Combine' również nie wymusza żadnych odstępów. – PaulMcG

+0

Interesujące. wypróbowany 'multi_line_word = Combine (Combine (OneOrMore (split_word)) + Opcjonalne (słowo))' , ale łamie się w przypadku '' sh \\\ nin '', ponieważ nie powoduje wyjątku, ale zamiast tego zwraca '['sh']'. Czy czegoś brakuje? – gotgenes

+0

Cóż, twoje słowo to nie tylko litery rozciągające się na "\" - znak nowej linii, ale jest tam to miejsce przed literą "i", która liczy się jako podział słów, więc połączenie zatrzymuje się po "sh". Możesz * modyfikować * Kombinuj z sąsiednim argumentem = False konstruktor, ale uważaj - możesz skończyć, wysysając cały plik jako pojedyncze słowo! Możesz też ponownie zdefiniować definicję kontynuacji, aby uwzględnić wszystkie spacje po linii, jeśli chcesz także zwinąć wszystkie spacje wiodące. – PaulMcG

5

Jesteś całkiem blisko ze swoim kodem. Każda z tych modów będzie działać:

# '|' 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)) 

Jak można znaleźć w pyparsing googling, BNF-> tłumaczenia pyparsing powinny być wykonane ze specjalnego celu korzystania pyparsing możliwości zamiast BNF, um, niedociągnięć. Byłem w trakcie komponowania dłuższej odpowiedzi, wchodząc w więcej kwestii związanych z tłumaczeniami BNF, ale już znalazłeś ten materiał (zakładam, że na wiki).