2014-10-27 16 views
6

Tôi đang sử dụng PyParsing để phân tích cú pháp một số tệp văn bản khá lớn có định dạng giống như C (braces and semicolons và tất cả những điều đó).Phân tích gia tăng nhưng đầy đủ với PyParsing?

PyParsing hoạt động rất tốt, nhưng tốc độ chậm và tiêu tốn rất nhiều bộ nhớ do kích thước tệp của tôi.

Vì lý do này, tôi muốn thử triển khai phương pháp phân tích gia tăng phân tích cú pháp trong đó tôi sẽ phân tích cú pháp các phần tử cấp cao nhất của tệp nguồn từng cái một. Phương pháp pyparsing scanString có vẻ giống như cách rõ ràng để thực hiện điều này. Tuy nhiên, tôi muốn đảm bảo rằng không có văn bản không hợp lệ/không thể phân bổ ở giữa các phần được phân tích cú pháp theo scanString và không thể tìm ra cách tốt để thực hiện việc này.

Dưới đây là một ví dụ đơn giản đó cho thấy vấn đề tôi đang gặp:

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

Output:

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

Các dãy được trả về bởi scanString có những khoảng trống do văn bản chưa phân tích giữa chúng ((0, 10), (11,25), (53,62), (88,98)). Hai trong số những khoảng trống này là khoảng trống hoặc nhận xét, không nên kích hoạt lỗi, nhưng một trong số chúng (this should be an error;) chứa văn bản không được phân loại mà tôi muốn nắm bắt.

Có cách nào để sử dụng pyparsing để phân tích cú pháp tệp tăng dần trong khi vẫn đảm bảo rằng toàn bộ đầu vào có thể được phân tích cú pháp bằng ngữ pháp phân tích cú pháp được chỉ định không?

Trả lời

4

Tôi nghĩ ra giải pháp khá tốt sau một cuộc thảo luận ngắn gọn on the PyParsing users' mailing list.

Tôi đã sửa đổi phương thức ParserElement.parseString một chút để tìm ra parseConsumeString, điều đó thực hiện về những gì tôi muốn. Phiên bản này gọi ParserElement._parse theo sau là ParserElement.preParse nhiều lần.

Dưới đây là code để khỉ vá ParserElement với phương pháp 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 

Chú ý rằng tôi cũng chuyển cuộc gọi đến ParserElement.resetCache vào mỗi lần lặp. Bởi vì nó không thể quay trở lại trong mỗi vòng lặp, không cần phải giữ lại bộ đệm ẩn trên các lần lặp. Điều này làm giảm đáng kể mức tiêu thụ bộ nhớ khi sử dụng tính năng packrat caching của PyParsing. Trong các thử nghiệm của tôi với một tập tin đầu vào 10 MiB, mức tiêu thụ bộ nhớ cao nhất giảm từ ~ 6G xuống ~ 100M, trong khi chạy nhanh hơn khoảng 15-20%.

Các vấn đề liên quan