2014-10-27 12 views
6

C gibi bir metin formatıyla (braces and semicolons ve tüm bunların dışında) bazı oldukça büyük metin dosyalarını ayrıştırmak için PyParsing kullanıyorum.PyParsing ile artımlı ama eksiksiz ayrıştırma?

PyParsing sadece büyük çalışır, ancak yavaş ve bağlı benim dosyaların boyutuna bellek çok büyük miktarda tüketir.

Bu nedenle, kaynak dosyanın üst düzey öğelerini tek tek ayrıştırdığım bir artımsal ayrıştırma yaklaşımını uygulamak istedim. Püskürtme işleminin scanString yöntemi, bunu yapmanın açık yolu gibi görünüyor. Ancak, scanString tarafından ayrıştırılan bölümler arasında geçersiz/ayrıştırılamaz metin olmadığından emin olmak istiyorum ve bunu yapmak için iyi bir yol bulamıyor.

İşte problemi yaşıyorum gösteren basitleştirilmiş bir örnek:

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

Çıktı: scanString tarafından döndürülen

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

aralıkları aralarında çözümlenmemiş metni ((0 dolayı boşluklar var, 10), (11,25), (53,62), (88,98)). Bu boşluklardan ikisi, bir hatayı tetiklememesi gereken, boşluk veya yorumlardır, ancak bunlardan biri (this should be an error;) yakalamak istediğim ayrılmaz bir metin içerir.

Tüm girdinin belirtilen ayrıştırıcı dilbilgisi ile ayrıştırılabildiğinden emin olmakla birlikte, bir dosyayı aşamalı olarak ayrıştırmak için pıhtılaşmayı kullanmanın bir yolu var mı?

cevap

4

on the PyParsing users' mailing list kısa bir tartışmanın ardından oldukça iyi bir çözüm gibi görünen bir şeyle geldim.

yöntemini, ne istediğimi yapan parseConsumeString ile biraz değiştirdim. Bu sürüm, ParserElement._parse'u tekrar tekrar ParserElement.preParse çağırır. Ayrıca, her döngü tekrarında içine ParserElement.resetCache çağrısını taşındı

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 

Uyarı: Burada

parseConsumeString yöntemi ile maymun yama ParserElement için koddur. Her döngüden geriye doğru izlenememesi nedeniyle, önbelleği yineleme boyunca tutmaya gerek yoktur. Bu, PyParsing'ün packrat caching özelliğini kullanırken bellek tüketimini büyük ölçüde azaltır. 10 MiB'lik bir giriş dosyasıyla yaptığım testlerde, tepe bellek tüketimi yaklaşık% 15-20 daha hızlı çalışırken ~ 6G'den ~ 100M'e düşüyor.