Show
Ignore:
Timestamp:
05/06/10 10:22:19 (2 years ago)
Author:
Mark Hammond <mhammond@…>
Branch:
default
Message:

implement a lexer which performs 7x faster for us

Location:
imapclient
Files:
1 added
2 modified

Legend:

Unmodified
Added
Removed
  • imapclient/imapclient.py

    r143 r149  
    55import re 
    66import imaplib 
    7 import shlex 
     7import response_lexer 
    88#imaplib.Debug = 5 
    99 
     
    553553        self._checkok('getacl', typ, data) 
    554554 
    555         parts = shlex.split(data[0]) 
     555        parts = list(response_lexer.Lexer([data[0]])) 
    556556        parts = parts[1:]       # First item is folder name 
    557557 
  • imapclient/response_parser.py

    r144 r149  
    99 
    1010import imaplib 
    11 import shlex 
    12 from cStringIO import StringIO 
     11import response_lexer 
    1312from datetime import datetime 
    1413from fixed_offset import FixedOffset 
     
    2726    Returns nested tuples of appropriately typed objects. 
    2827    """ 
     28    return tuple(gen_parsed_response(text)) 
     29 
     30 
     31def gen_parsed_response(text): 
     32    if not text: 
     33        return 
    2934    src = ResponseTokeniser(text) 
     35    token = None 
    3036    try: 
    31         return tuple(atom(src, token) for token in src) 
     37        for token in src: 
     38            yield atom(src, token) 
    3239    except ParseError: 
    3340        raise 
    3441    except ValueError, err: 
    35         raise ParseError("%s: %s" % (str(err), src.lex.token)) 
     42        raise ParseError("%s: %s" % (str(err), token)) 
    3643 
    3744 
     
    4249    keyed by FETCH field type (eg."RFC822"). 
    4350    """ 
    44     response = iter(parse_response(text)) 
     51    response = gen_parsed_response(text) 
    4552 
    4653    parsed_response = {} 
     
    124131# a string literal is finally processed, we peek into this file-like object 
    125132# to grab the literal. 
    126 class LiteralHandlingReader: 
     133class LiteralHandlingIter: 
    127134    def __init__(self, lexer, resp_record): 
    128135        self.pushed = None 
     
    133140            src_text, self.literal = resp_record 
    134141            assert src_text.endswith("}"), src_text 
    135             # add a token-sep after the text. 
    136             self.src = StringIO(src_text + " ") 
     142            self.src_text = src_text 
    137143        else: 
    138144            # just a line with no literals. 
    139             self.src = StringIO(resp_record) 
     145            self.src_text = resp_record 
    140146            self.literal = None 
    141147 
    142     def read(self, n): 
    143         # Two additional hacks: 
    144         # 1. Hack into the lexer so we get special treatment for backslash 
    145         #    chars - they are only special inside a quoted string. 
    146         # 2. For quoted strings return the quotes around the string so 
    147         #    that atom() can distinguish numbers from strings. Eg. "123" vs 123. 
    148         #    These are stripped off before returning them to the user. 
    149         assert n==1 
    150         if self.pushed is not None: 
    151             ret = self.pushed 
    152             self.pushed = None 
    153         else: 
    154             ret = self.src.read(n) 
    155             if ret == "\\" and self.lexer.state not in '"\\': 
    156                 self.pushed = "\\" 
    157             elif ret == '"' and self.lexer.state != '\\': 
    158                 self.lexer.token += '"' 
    159         return ret 
    160  
    161     def close(self): 
    162         self.src.close() 
    163         self.src = None 
    164         self.literal = None 
     148    def __iter__(self): 
     149        return iter(self.src_text) 
    165150 
    166151 
    167152class ResponseTokeniser(object): 
    168  
    169     CTRL_CHARS = ''.join([chr(ch) for ch in range(32)]) 
    170     SPECIALS = r'()%"' + CTRL_CHARS 
    171     ALL_CHARS = [chr(ch) for ch in range(256)] 
    172     NON_SPECIALS = [ch for ch in ALL_CHARS if ch not in SPECIALS] 
    173  
    174153    def __init__(self, resp_chunks): 
    175154        # initialize the lexer with all the chunks we read. 
    176         self.lex = shlex.shlex('', posix=True) 
    177         for chunk in reversed(resp_chunks): 
    178             self.lex.push_source(LiteralHandlingReader(self.lex, chunk)) 
    179  
    180         self.lex.quotes = '"' 
    181         self.lex.commenters = '' 
    182         self.lex.wordchars = self.NON_SPECIALS 
     155        sources = (LiteralHandlingIter(lex, chunk) for chunk in resp_chunks) 
     156        lex = response_lexer.Lexer(sources) 
     157        self.tok_src = iter(lex) 
     158        self.lex = lex 
    183159 
    184160    def __iter__(self): 
    185         return iter(self.lex) 
    186  
    187     def next(self): 
    188         try: 
    189             return self.lex.next() 
    190         except StopIteration: 
    191             return EOF 
     161        return self.tok_src 
    192162 
    193163 
     
    195165    if token == "(": 
    196166        out = [] 
    197         while True: 
    198             token = src.next() 
     167        for token in src: 
    199168            if token == ")": 
    200169                return tuple(out) 
    201             if token == EOF: 
    202                 preceeding = ' '.join(str(val) for val in out) 
    203                 raise ParseError('Tuple incomplete before "(%s"' % preceeding) 
    204170            out.append(atom(src, token)) 
     171        # oops - no terminator! 
     172        preceeding = ' '.join(str(val) for val in out) 
     173        raise ParseError('Tuple incomplete before "(%s"' % preceeding) 
    205174    elif token == 'NIL': 
    206175        return None 
    207     elif token.startswith('{'): 
     176    elif token[0] == '{': 
    208177        literal_len = int(token[1:-1]) 
    209         literal_text = src.lex.instream.literal 
     178        literal_text = src.lex.current_source.literal 
    210179        if literal_text is None: 
    211180           raise ParseError('No literal corresponds to %r' % token)