2014-10-27 16 views
6

Używam PyParsing do parsowania niektórych raczej dużych plików tekstowych w formacie podobnym do C (braces and semicolons i tym podobne).Przyrostowe, ale pełne parsowanie za pomocą PyParsing?

PyParsing działa świetnie, ale jest wolny i zużywa bardzo dużo pamięci ze względu na rozmiar moich plików.

Z tego powodu chciałem spróbować zastosować metodę analizy składniowej , w której pojedynczo analizowałem elementy najwyższego poziomu pliku źródłowego. Metoda pyparowania w stylu scanString wydaje się być oczywistym sposobem na zrobienie tego. Jednak chcę się upewnić, że nie ma niepoprawnego/nieodwracalnego tekstu pomiędzy sekcjami analizowanymi przez scanString i nie można znaleźć dobrego sposobu, aby to zrobić.

Oto uproszczony przykład, który pokazuje problem mam:

sample="""f1(1,2,3); f2_no_args(); 
# comment out: foo(4,5,6); 
bar(7,8); 
this should be an error; 
baz(9,10); 
""" 

from pyparsing import * 

COMMENT=Suppress('#' + restOfLine()) 
SEMI,COMMA,LPAREN,RPAREN = map(Suppress,';,()') 

ident = Word(alphas, alphanums+"_") 
integer = Word(nums+"+-",nums) 

statement = ident("fn") + LPAREN + Group(Optional(delimitedList(integer)))("arguments") + RPAREN + SEMI 

p = statement.ignore(COMMENT) 

for res, start, end in p.scanString(sample): 
    print "***** (%d,%d)" % (start, end) 
    print res.dump() 

wyjściowa:

***** (0,10) 
['f1', ['1', '2', '3']] 
- arguments: ['1', '2', '3'] 
- fn: f1 
***** (11,25) 
['f2_no_args', []] 
- arguments: [] 
- fn: f2_no_args 
***** (53,62) 
['bar', ['7', '8']] 
- arguments: ['7', '8'] 
- fn: bar 
***** (88,98) 
['baz', ['9', '10']] 
- arguments: ['9', '10'] 
- fn: baz 

Zakresy zwracane przez scanString mają braki spowodowane nieanalizowanego tekstu między nimi ((0, 10), (11,25), (53,62), (88,98)). Dwie z tych przerw są białymi znakami lub komentarzami, które nie powinny powodować błędu, ale jeden z nich (this should be an error;) zawiera niepodobny tekst, który chcę przechwycić.

Czy istnieje sposób na użycie pyparsing do parsowania pliku przyrostowo, przy jednoczesnym zapewnieniu, że całe dane wejściowe mogą być analizowane z gramatyki określonego parsera?

Odpowiedz

4

Wpadłem na to, co wydaje się całkiem przyzwoitym rozwiązaniem po krótkiej dyskusji on the PyParsing users' mailing list.

Zmodyfikowałem nieznacznie metodę ParserElement.parseString, aby wymyślić parseConsumeString, co robi o tym, co chcę. Ta wersja wielokrotnie wywołuje ParserElement._parse, a następnie ParserElement.preParse.

Oto kod małpy plastra ParserElement metodą parseConsumeString:

from pyparsing import ParseBaseException, ParserElement 

def parseConsumeString(self, instring, parseAll=True, yieldLoc=False): 
    '''Generator version of parseString which does not try to parse 
    the whole string at once. 

    Should be called with a top-level parser that could parse the 
    entire string if called repeatedly on the remaining pieces. 
    Instead of: 

     ZeroOrMore(TopLevel)).parseString(s ...) 

    Use: 

     TopLevel.parseConsumeString(s ...) 

    If yieldLoc==True, it will yield a tuple of (tokens, startloc, endloc). 
    If False, it will yield only tokens (like parseString). 

    If parseAll==True, it will raise an error as soon as a parse 
    error is encountered. If False, it will return as soon as a parse 
    error is encountered (possibly before yielding any tokens).''' 

    if not self.streamlined: 
     self.streamline() 
     #~ self.saveAsList = True 
    for e in self.ignoreExprs: 
     e.streamline() 
    if not self.keepTabs: 
     instring = instring.expandtabs() 
    try: 
     sloc = loc = 0 
     while loc<len(instring): 
      # keeping the cache (if in use) across loop iterations wastes memory (can't backtrack outside of loop) 
      ParserElement.resetCache() 
      loc, tokens = self._parse(instring, loc) 
      if yieldLoc: 
       yield tokens, sloc, loc 
      else: 
       yield tokens 
      sloc = loc = self.preParse(instring, loc) 
    except ParseBaseException as exc: 
     if not parseAll: 
      return 
     elif ParserElement.verbose_stacktrace: 
      raise 
     else: 
      # catch and re-raise exception from here, clears out pyparsing internal stack trace 
      raise exc 

def monkey_patch(): 
    ParserElement.parseConsumeString = parseConsumeString 

Należy zauważyć, że również przeniesiony wywołanie ParserElement.resetCache w każdej iteracji pętli. Ponieważ niemożliwe jest wycofanie się z każdej pętli, nie ma potrzeby zatrzymywania pamięci podręcznej w kolejnych iteracjach. To drastycznie zmniejsza zużycie pamięci podczas korzystania z funkcji PyParsing o nazwie packrat caching. W moich testach z plikiem wejściowym 10 MiB, szczytowe zużycie pamięci spadło z ~ 6G do ~ 100M wartości szczytowej, podczas gdy działanie było o 15-20% szybsze.

Powiązane problemy