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