PageRenderTime 66ms CodeModel.GetById 49ms app.highlight 13ms RepoModel.GetById 0ms app.codeStats 0ms

/VintageEx/vex/parsers/cmd_line.py

https://bitbucket.org/rafaelmoreira/sublime-text
Python | 340 lines | 337 code | 3 blank | 0 comment | 0 complexity | 23e779b068f5dfbe695bb79e881ccaa4 MD5 | raw file
  1import re
  2
  3EOF = -1
  4
  5COMMA = ','
  6SEMICOLON = ';'
  7LINE_REF_SEPARATORS = (COMMA, SEMICOLON)
  8
  9default_range_info = dict(left_ref=None,
 10                          left_offset=None,
 11                          left_search_offsets=[],
 12                          separator=None,
 13                          right_ref=None,
 14                          right_offset=None,
 15                          right_search_offsets=[],
 16                          text_range='')
 17
 18
 19class ParserBase(object):
 20    def __init__(self, source):
 21        self.c = ''
 22        self.source = source
 23        self.result = default_range_info.copy()
 24        self.n = -1
 25        self.consume()
 26
 27    def consume(self):
 28        if self.c == EOF:
 29            raise SyntaxError("End of file reached.")
 30        if self.n == -1 and not self.source:
 31            self.c = EOF
 32            return
 33        else:
 34            self.n += 1
 35            if self.n >= len(self.source):
 36                self.c = EOF
 37                return
 38            self.c = self.source[self.n]
 39
 40
 41class VimParser(ParserBase):
 42    STATE_NEUTRAL = 0
 43    STATE_SEARCH_OFFSET = 1
 44
 45    def __init__(self, *args, **kwargs):
 46        self.state = VimParser.STATE_NEUTRAL
 47        self.current_side = 'left'
 48        ParserBase.__init__(self, *args, **kwargs)
 49
 50    def parse_full_range(self):
 51        # todo: make sure that parse_range throws error for unknown tokens
 52        self.parse_range()
 53        sep = self.match_one(',;')
 54        if sep:
 55            if not self.result[self.current_side + '_offset'] and not self.result[self.current_side + '_ref']:
 56                self.result[self.current_side + '_ref'] = '.'
 57            self.consume()
 58            self.result['separator'] = sep
 59            self.current_side = 'right'
 60            self.parse_range()
 61
 62        if self.c != EOF and not (self.c.isalpha() or self.c in '&!'):
 63            raise SyntaxError("E492 Not an editor command.")
 64
 65        return self.result
 66
 67    def parse_range(self):
 68        if self.c == EOF:
 69            return self.result
 70        line_ref = self.consume_if_in(list('.%$'))
 71        if line_ref:
 72            self.result[self.current_side + "_ref"] = line_ref
 73        while self.c != EOF:
 74            if self.c == "'":
 75                self.consume()
 76                if self.c != EOF and not (self.c.isalpha() or self.c in ("<", ">")):
 77                    raise SyntaxError("E492 Not an editor command.")
 78                self.result[self.current_side + "_ref"] = "'%s" % self.c
 79                self.consume()
 80            elif self.c in ".$%%'" and not self.result[self.current_side + "_ref"]:
 81                if (self.result[self.current_side + "_search_offsets"] or
 82                    self.result[self.current_side + "_offset"]):
 83                        raise SyntaxError("E492 Not an editor command.")
 84            elif self.c.startswith(tuple("01234567890+-")):
 85                offset = self.match_offset()
 86                self.result[self.current_side + '_offset'] = offset
 87            elif self.c.startswith(tuple('/?')):
 88                self.state = VimParser.STATE_SEARCH_OFFSET
 89                search_offests = self.match_search_based_offsets()
 90                self.result[self.current_side + "_search_offsets"] = search_offests
 91                self.state = VimParser.STATE_NEUTRAL
 92            elif self.c not in ':,;&!' and not self.c.isalpha():
 93                raise SyntaxError("E492 Not an editor command.")
 94            else:
 95                break
 96
 97            if (self.result[self.current_side + "_ref"] == '%' and
 98                (self.result[self.current_side + "_offset"] or
 99                 self.result[self.current_side + "_search_offsets"])):
100                    raise SyntaxError("E492 Not an editor command.")
101
102        end = max(0, min(self.n, len(self.source)))
103        self.result['text_range'] = self.source[:end]
104        return self.result
105
106    def consume_if_in(self, items):
107        rv = None
108        if self.c in items:
109            rv = self.c
110            self.consume()
111        return rv
112
113    def match_search_based_offsets(self):
114        offsets = []
115        while self.c != EOF and self.c.startswith(tuple('/?')):
116            new_offset = []
117            new_offset.append(self.c)
118            search = self.match_one_search_offset()
119            new_offset.append(search)
120            # numeric_offset = self.consume_while_match('^[0-9+-]') or '0'
121            numeric_offset = self.match_offset()
122            new_offset.append(numeric_offset)
123            offsets.append(new_offset)
124        return offsets
125
126    def match_one_search_offset(self):
127        search_kind = self.c
128        rv = ''
129        self.consume()
130        while self.c != EOF and self.c != search_kind:
131            if self.c == '\\':
132                self.consume()
133                if self.c != EOF:
134                    rv += self.c
135                    self.consume()
136            else:
137                rv += self.c
138                self.consume()
139        if self.c == search_kind:
140            self.consume()
141        return rv
142
143    def match_offset(self):
144        offsets = []
145        sign = 1
146        is_num_or_sign = re.compile('^[0-9+-]')
147        while self.c != EOF and is_num_or_sign.match(self.c):
148            if self.c in '+-':
149                signs = self.consume_while_match('^[+-]')
150                if self.state == VimParser.STATE_NEUTRAL and len(signs) > 1 and not self.result[self.current_side + '_ref']:
151                    self.result[self.current_side + '_ref'] = '.'
152                if self.c != EOF and self.c.isdigit():
153                    if self.state == VimParser.STATE_NEUTRAL and not self.result[self.current_side + '_ref']:
154                        self.result[self.current_side + '_ref'] = '.'
155                    sign = -1 if signs[-1] == '-' else 1
156                    signs = signs[:-1] if signs else []
157                subtotal = 0
158                for item in signs:
159                    subtotal += 1 if item == '+' else -1 
160                offsets.append(subtotal)
161            elif self.c.isdigit():
162                nr = self.consume_while_match('^[0-9]')
163                offsets.append(sign * int(nr))
164                sign = 1
165            else:
166                break
167
168        return sum(offsets)
169        # self.result[self.current_side + '_offset'] = sum(offsets)
170
171    def match_one(self, seq):
172        if self.c != EOF and self.c in seq:
173            return self.c
174
175
176    def consume_while_match(self, regex):
177        rv = ''
178        r = re.compile(regex)
179        while self.c != EOF and r.match(self.c):
180            rv += self.c
181            self.consume()
182        return rv
183
184
185class CommandLineParser(ParserBase):
186    def __init__(self, source, *args, **kwargs):
187        ParserBase.__init__(self, source, *args, **kwargs)     
188        self.range_parser = VimParser(source)
189        self.result = dict(range=None, commands=[], errors=[])
190
191    def parse_cmd_line(self):
192        try:
193            rng = self.range_parser.parse_full_range()
194        except SyntaxError, e:
195            rng = None
196            self.result["errors"].append(str(e))
197            return self.result
198
199        self.result['range'] = rng
200        # sync up with range parser the dumb way
201        self.n = self.range_parser.n
202        self.c = self.range_parser.c
203        while self.c != EOF and self.c == ' ':
204            self.consume()
205        self.parse_commands()
206
207        if not self.result['commands'][0]['cmd']:
208            self.result['commands'][0]['cmd'] = ':'
209        return self.result
210
211    def parse_commands(self):
212        name = ''
213        cmd = {}
214        while self.c != EOF:
215            if self.c.isalpha() and '&' not in name:
216                name += self.c
217                self.consume()
218            elif self.c == '&' and (not name or name == '&'):
219                name += self.c
220                self.consume()
221            else:
222                break
223
224        if not name and self.c  == '!':
225            name = '!'
226            self.consume()
227
228        cmd['cmd'] = name
229        cmd['forced'] = self.c == '!'
230        if cmd['forced']:
231            self.consume()
232
233        while self.c != EOF and self.c == ' ':
234            self.consume()
235        cmd['args'] = ''
236        if not self.c == EOF:
237            cmd['args'] = self.source[self.n:]
238        self.result['commands'].append(cmd)
239
240
241class AddressParser(ParserBase):
242    STATE_NEUTRAL = 1
243    STATE_SEARCH_OFFSET = 2
244
245    def __init__(self, source, *args, **kwargs):
246        ParserBase.__init__(self, source, *args, **kwargs)
247        self.result = dict(ref=None, offset=None, search_offsets=[])
248        self.state = AddressParser.STATE_NEUTRAL
249
250    def parse(self):
251        if self.c == EOF:
252            return self.result
253        ref = self.consume_if_in(list('.$'))
254        if ref:
255            self.result["ref"] = ref
256
257        while self.c != EOF:
258            if self.c in '0123456789+-':
259                rv = self.match_offset()
260                self.result['offset'] = rv
261            elif self.c in '?/':
262                rv = self.match_search_based_offsets()
263                self.result['search_offsets'] = rv
264
265        return self.result
266
267    def match_search_based_offsets(self):
268        offsets = []
269        while self.c != EOF and self.c.startswith(tuple('/?')):
270            new_offset = []
271            new_offset.append(self.c)
272            search = self.match_one_search_offset()
273            new_offset.append(search)
274            # numeric_offset = self.consume_while_match('^[0-9+-]') or '0'
275            numeric_offset = self.match_offset()
276            new_offset.append(numeric_offset)
277            offsets.append(new_offset)
278        return offsets
279
280    def match_one_search_offset(self):
281        search_kind = self.c
282        rv = ''
283        self.consume()
284        while self.c != EOF and self.c != search_kind:
285            if self.c == '\\':
286                self.consume()
287                if self.c != EOF:
288                    rv += self.c
289                    self.consume()
290            else:
291                rv += self.c
292                self.consume()
293        if self.c == search_kind:
294            self.consume()
295        return rv
296
297    def match_offset(self):
298        offsets = []
299        sign = 1
300        is_num_or_sign = re.compile('^[0-9+-]')
301        while self.c != EOF and is_num_or_sign.match(self.c):
302            if self.c in '+-':
303                signs = self.consume_while_match('^[+-]')
304                if self.state == AddressParser.STATE_NEUTRAL and len(signs) > 0 and not self.result['ref']:
305                    self.result['ref'] = '.'
306                if self.c != EOF and self.c.isdigit():
307                    sign = -1 if signs[-1] == '-' else 1
308                    signs = signs[:-1] if signs else []
309                subtotal = 0
310                for item in signs:
311                    subtotal += 1 if item == '+' else -1 
312                offsets.append(subtotal)
313            elif self.c.isdigit():
314                nr = self.consume_while_match('^[0-9]')
315                offsets.append(sign * int(nr))
316                sign = 1
317            else:
318                break
319
320        return sum(offsets)
321
322    def match_one(self, seq):
323        if self.c != EOF and self.c in seq:
324            return self.c
325
326
327    def consume_while_match(self, regex):
328        rv = ''
329        r = re.compile(regex)
330        while self.c != EOF and r.match(self.c):
331            rv += self.c
332            self.consume()
333        return rv
334
335    def consume_if_in(self, items):
336        rv = None
337        if self.c in items:
338            rv = self.c
339            self.consume()
340        return rv