2014-10-27 6 views
6

मैं सी-जैसे प्रारूप (braces and semicolons और यह सब कुछ) के साथ कुछ बड़ी टेक्स्ट फ़ाइलों को पार्स करने के लिए पाइपर्सिंग का उपयोग कर रहा हूं।पीपर्सिंग के साथ बढ़ती लेकिन पूर्ण पार्सिंग?

पायपार्सिंग बहुत बढ़िया काम करता है, लेकिन यह धीमा है और मेरी फाइलों के आकार के कारण स्मृति की एक बड़ी मात्रा में उपभोग करता है।

इस वजह से, मैं वृद्धिशील पार्सिंग दृष्टिकोण को लागू करने का प्रयास करना चाहता था जिसमें मैं स्रोत फ़ाइल के शीर्ष-स्तर तत्वों को एक-एक करके पार्स करना चाहता हूं। scanString पाइपर्सिंग की विधि ऐसा करने के स्पष्ट तरीके की तरह लगती है। हालांकि, मैं यह सुनिश्चित करना चाहता हूं कि scanString द्वारा पार्स किए गए अनुभागों के बीच कोई अमान्य/अयोग्य पाठ नहीं है, और ऐसा करने का एक अच्छा तरीका नहीं पता है।

यहाँ एक सरल उदाहरण है कि समस्या मैं आ रही है पता चलता है:

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

आउटपुट:

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

पर्वतमाला scanString द्वारा वापस उन दोनों के बीच अन-पार्स पाठ ((0 की वजह से अंतराल है, 10), (11,25), (53,62), (88,98))। इनमें से दो अंतराल सफेद जगह या टिप्पणियां हैं, जो किसी त्रुटि को ट्रिगर नहीं करना चाहिए, लेकिन उनमें से एक (this should be an error;) में अनपेक्षित टेक्स्ट है, जिसे मैं पकड़ना चाहता हूं।

क्या अभी भी यह सुनिश्चित करने के लिए पिपारिंग का उपयोग करने का कोई तरीका है कि पूरे इनपुट को निर्दिष्ट पार्सर व्याकरण के साथ पार्स किया जा सके?

उत्तर

4

मैं एक संक्षिप्त चर्चा on the PyParsing users' mailing list के बाद एक सुंदर सभ्य समाधान प्रतीत होता हूं।

मैंने ParserElement.parseString विधि को parseConsumeString के साथ आने के लिए थोड़ा सा संशोधित किया, जो कि मैं चाहता हूं। यह संस्करण ParserElement._parse पर ParserElement.preParse बार-बार कॉल करता है।

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 

सूचना है कि मैं भी एक पाश यात्रा में ParserElement.resetCache करने के लिए कॉल स्थानांतरित किया गया:

यहाँ parseConsumeString विधि के साथ बंदर-पैच ParserElement करने के लिए कोड है। चूंकि प्रत्येक पाश से पीछे हटना असंभव है, फिर भी पूरे पुनरावृत्ति में कैश को बनाए रखने की आवश्यकता नहीं है। PyParsing की packrat caching सुविधा का उपयोग करते समय यह स्मृति खपत को काफी हद तक कम करता है। 10 एमआईबी इनपुट फ़ाइल के साथ अपने परीक्षणों में, पीक मेमोरी खपत ~ 6 जी से ~ 100 मीटर चोटी तक गिर जाती है, जबकि लगभग 15-20% तेजी से चलती है।

संबंधित मुद्दे