PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/twisted-trunk/twisted/words/xish/xpathparser.py

https://bitbucket.org/cfbolz/benchmarks-pypy-phd
Python | 508 lines | 414 code | 21 blank | 73 comment | 9 complexity | a0011fba0365c85fc6023ca1e0bbf4b0 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, Apache-2.0
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. # DO NOT EDIT xpathparser.py!
  4. #
  5. # It is generated from xpathparser.g using Yapps. Make needed changes there.
  6. # This also means that the generated Python may not conform to Twisted's coding
  7. # standards.
  8. # HOWTO Generate me:
  9. #
  10. # 1.) Grab a copy of yapps2, version 2.1.1:
  11. # http://theory.stanford.edu/~amitp/Yapps/
  12. #
  13. # Note: Do NOT use the package in debian/ubuntu as it has incompatible
  14. # modifications.
  15. #
  16. # 2.) Generate the grammar:
  17. #
  18. # yapps2 xpathparser.g xpathparser.py.proto
  19. #
  20. # 3.) Edit the output to depend on the embedded runtime, not yappsrt.
  21. #
  22. # sed -e '/^import yapps/d' -e '/^[^#]/s/yappsrt\.//g' \
  23. # xpathparser.py.proto > xpathparser.py
  24. """
  25. XPath Parser.
  26. Besides the parser code produced by Yapps, this module also defines the
  27. parse-time exception classes, a scanner class, a base class for parsers
  28. produced by Yapps, and a context class that keeps track of the parse stack.
  29. These have been copied from the Yapps runtime.
  30. """
  31. import sys, re
  32. class SyntaxError(Exception):
  33. """When we run into an unexpected token, this is the exception to use"""
  34. def __init__(self, charpos=-1, msg="Bad Token", context=None):
  35. Exception.__init__(self)
  36. self.charpos = charpos
  37. self.msg = msg
  38. self.context = context
  39. def __str__(self):
  40. if self.charpos < 0: return 'SyntaxError'
  41. else: return 'SyntaxError@char%s(%s)' % (repr(self.charpos), self.msg)
  42. class NoMoreTokens(Exception):
  43. """Another exception object, for when we run out of tokens"""
  44. pass
  45. class Scanner:
  46. """Yapps scanner.
  47. The Yapps scanner can work in context sensitive or context
  48. insensitive modes. The token(i) method is used to retrieve the
  49. i-th token. It takes a restrict set that limits the set of tokens
  50. it is allowed to return. In context sensitive mode, this restrict
  51. set guides the scanner. In context insensitive mode, there is no
  52. restriction (the set is always the full set of tokens).
  53. """
  54. def __init__(self, patterns, ignore, input):
  55. """Initialize the scanner.
  56. @param patterns: [(terminal, uncompiled regex), ...] or C{None}
  57. @param ignore: [terminal,...]
  58. @param input: string
  59. If patterns is C{None}, we assume that the subclass has defined
  60. C{self.patterns} : [(terminal, compiled regex), ...]. Note that the
  61. patterns parameter expects uncompiled regexes, whereas the
  62. C{self.patterns} field expects compiled regexes.
  63. """
  64. self.tokens = [] # [(begin char pos, end char pos, token name, matched text), ...]
  65. self.restrictions = []
  66. self.input = input
  67. self.pos = 0
  68. self.ignore = ignore
  69. self.first_line_number = 1
  70. if patterns is not None:
  71. # Compile the regex strings into regex objects
  72. self.patterns = []
  73. for terminal, regex in patterns:
  74. self.patterns.append( (terminal, re.compile(regex)) )
  75. def get_token_pos(self):
  76. """Get the current token position in the input text."""
  77. return len(self.tokens)
  78. def get_char_pos(self):
  79. """Get the current char position in the input text."""
  80. return self.pos
  81. def get_prev_char_pos(self, i=None):
  82. """Get the previous position (one token back) in the input text."""
  83. if self.pos == 0: return 0
  84. if i is None: i = -1
  85. return self.tokens[i][0]
  86. def get_line_number(self):
  87. """Get the line number of the current position in the input text."""
  88. # TODO: make this work at any token/char position
  89. return self.first_line_number + self.get_input_scanned().count('\n')
  90. def get_column_number(self):
  91. """Get the column number of the current position in the input text."""
  92. s = self.get_input_scanned()
  93. i = s.rfind('\n') # may be -1, but that's okay in this case
  94. return len(s) - (i+1)
  95. def get_input_scanned(self):
  96. """Get the portion of the input that has been tokenized."""
  97. return self.input[:self.pos]
  98. def get_input_unscanned(self):
  99. """Get the portion of the input that has not yet been tokenized."""
  100. return self.input[self.pos:]
  101. def token(self, i, restrict=None):
  102. """Get the i'th token in the input.
  103. If C{i} is one past the end, then scan for another token.
  104. @param i: token index
  105. @param restrict: [token, ...] or C{None}; if restrict is
  106. C{None}, then any token is allowed. You may call
  107. token(i) more than once. However, the restrict set
  108. may never be larger than what was passed in on the
  109. first call to token(i).
  110. """
  111. if i == len(self.tokens):
  112. self.scan(restrict)
  113. if i < len(self.tokens):
  114. # Make sure the restriction is more restricted. This
  115. # invariant is needed to avoid ruining tokenization at
  116. # position i+1 and higher.
  117. if restrict and self.restrictions[i]:
  118. for r in restrict:
  119. if r not in self.restrictions[i]:
  120. raise NotImplementedError("Unimplemented: restriction set changed")
  121. return self.tokens[i]
  122. raise NoMoreTokens()
  123. def __repr__(self):
  124. """Print the last 10 tokens that have been scanned in"""
  125. output = ''
  126. for t in self.tokens[-10:]:
  127. output = '%s\n (@%s) %s = %s' % (output,t[0],t[2],repr(t[3]))
  128. return output
  129. def scan(self, restrict):
  130. """Should scan another token and add it to the list, self.tokens,
  131. and add the restriction to self.restrictions"""
  132. # Keep looking for a token, ignoring any in self.ignore
  133. while 1:
  134. # Search the patterns for the longest match, with earlier
  135. # tokens in the list having preference
  136. best_match = -1
  137. best_pat = '(error)'
  138. for p, regexp in self.patterns:
  139. # First check to see if we're ignoring this token
  140. if restrict and p not in restrict and p not in self.ignore:
  141. continue
  142. m = regexp.match(self.input, self.pos)
  143. if m and len(m.group(0)) > best_match:
  144. # We got a match that's better than the previous one
  145. best_pat = p
  146. best_match = len(m.group(0))
  147. # If we didn't find anything, raise an error
  148. if best_pat == '(error)' and best_match < 0:
  149. msg = 'Bad Token'
  150. if restrict:
  151. msg = 'Trying to find one of '+', '.join(restrict)
  152. raise SyntaxError(self.pos, msg)
  153. # If we found something that isn't to be ignored, return it
  154. if best_pat not in self.ignore:
  155. # Create a token with this data
  156. token = (self.pos, self.pos+best_match, best_pat,
  157. self.input[self.pos:self.pos+best_match])
  158. self.pos = self.pos + best_match
  159. # Only add this token if it's not in the list
  160. # (to prevent looping)
  161. if not self.tokens or token != self.tokens[-1]:
  162. self.tokens.append(token)
  163. self.restrictions.append(restrict)
  164. return
  165. else:
  166. # This token should be ignored ..
  167. self.pos = self.pos + best_match
  168. class Parser:
  169. """Base class for Yapps-generated parsers.
  170. """
  171. def __init__(self, scanner):
  172. self._scanner = scanner
  173. self._pos = 0
  174. def _peek(self, *types):
  175. """Returns the token type for lookahead; if there are any args
  176. then the list of args is the set of token types to allow"""
  177. tok = self._scanner.token(self._pos, types)
  178. return tok[2]
  179. def _scan(self, type):
  180. """Returns the matched text, and moves to the next token"""
  181. tok = self._scanner.token(self._pos, [type])
  182. if tok[2] != type:
  183. raise SyntaxError(tok[0], 'Trying to find '+type+' :'+ ' ,'.join(self._scanner.restrictions[self._pos]))
  184. self._pos = 1 + self._pos
  185. return tok[3]
  186. class Context:
  187. """Class to represent the parser's call stack.
  188. Every rule creates a Context that links to its parent rule. The
  189. contexts can be used for debugging.
  190. """
  191. def __init__(self, parent, scanner, tokenpos, rule, args=()):
  192. """Create a new context.
  193. @param parent: Context object or C{None}
  194. @param scanner: Scanner object
  195. @param tokenpos: scanner token position
  196. @type tokenpos: L{int}
  197. @param rule: name of the rule
  198. @type rule: L{str}
  199. @param args: tuple listing parameters to the rule
  200. """
  201. self.parent = parent
  202. self.scanner = scanner
  203. self.tokenpos = tokenpos
  204. self.rule = rule
  205. self.args = args
  206. def __str__(self):
  207. output = ''
  208. if self.parent: output = str(self.parent) + ' > '
  209. output += self.rule
  210. return output
  211. def print_line_with_pointer(text, p):
  212. """Print the line of 'text' that includes position 'p',
  213. along with a second line with a single caret (^) at position p"""
  214. # TODO: separate out the logic for determining the line/character
  215. # location from the logic for determining how to display an
  216. # 80-column line to stderr.
  217. # Now try printing part of the line
  218. text = text[max(p-80, 0):p+80]
  219. p = p - max(p-80, 0)
  220. # Strip to the left
  221. i = text[:p].rfind('\n')
  222. j = text[:p].rfind('\r')
  223. if i < 0 or (0 <= j < i): i = j
  224. if 0 <= i < p:
  225. p = p - i - 1
  226. text = text[i+1:]
  227. # Strip to the right
  228. i = text.find('\n', p)
  229. j = text.find('\r', p)
  230. if i < 0 or (0 <= j < i): i = j
  231. if i >= 0:
  232. text = text[:i]
  233. # Now shorten the text
  234. while len(text) > 70 and p > 60:
  235. # Cut off 10 chars
  236. text = "..." + text[10:]
  237. p = p - 7
  238. # Now print the string, along with an indicator
  239. print >>sys.stderr, '> ',text
  240. print >>sys.stderr, '> ',' '*p + '^'
  241. def print_error(input, err, scanner):
  242. """Print error messages, the parser stack, and the input text -- for human-readable error messages."""
  243. # NOTE: this function assumes 80 columns :-(
  244. # Figure out the line number
  245. line_number = scanner.get_line_number()
  246. column_number = scanner.get_column_number()
  247. print >>sys.stderr, '%d:%d: %s' % (line_number, column_number, err.msg)
  248. context = err.context
  249. if not context:
  250. print_line_with_pointer(input, err.charpos)
  251. while context:
  252. # TODO: add line number
  253. print >>sys.stderr, 'while parsing %s%s:' % (context.rule, tuple(context.args))
  254. print_line_with_pointer(input, context.scanner.get_prev_char_pos(context.tokenpos))
  255. context = context.parent
  256. def wrap_error_reporter(parser, rule):
  257. try:
  258. return getattr(parser, rule)()
  259. except SyntaxError, e:
  260. input = parser._scanner.input
  261. print_error(input, e, parser._scanner)
  262. except NoMoreTokens:
  263. print >>sys.stderr, 'Could not complete parsing; stopped around here:'
  264. print >>sys.stderr, parser._scanner
  265. from twisted.words.xish.xpath import AttribValue, BooleanValue, CompareValue
  266. from twisted.words.xish.xpath import Function, IndexValue, LiteralValue
  267. from twisted.words.xish.xpath import _AnyLocation, _Location
  268. # Begin -- grammar generated by Yapps
  269. import sys, re
  270. class XPathParserScanner(Scanner):
  271. patterns = [
  272. ('","', re.compile(',')),
  273. ('"@"', re.compile('@')),
  274. ('"\\)"', re.compile('\\)')),
  275. ('"\\("', re.compile('\\(')),
  276. ('"\\]"', re.compile('\\]')),
  277. ('"\\["', re.compile('\\[')),
  278. ('"//"', re.compile('//')),
  279. ('"/"', re.compile('/')),
  280. ('\\s+', re.compile('\\s+')),
  281. ('INDEX', re.compile('[0-9]+')),
  282. ('WILDCARD', re.compile('\\*')),
  283. ('IDENTIFIER', re.compile('[a-zA-Z][a-zA-Z0-9_\\-]*')),
  284. ('ATTRIBUTE', re.compile('\\@[a-zA-Z][a-zA-Z0-9_\\-]*')),
  285. ('FUNCNAME', re.compile('[a-zA-Z][a-zA-Z0-9_]*')),
  286. ('CMP_EQ', re.compile('\\=')),
  287. ('CMP_NE', re.compile('\\!\\=')),
  288. ('STR_DQ', re.compile('"([^"]|(\\"))*?"')),
  289. ('STR_SQ', re.compile("'([^']|(\\'))*?'")),
  290. ('OP_AND', re.compile('and')),
  291. ('OP_OR', re.compile('or')),
  292. ('END', re.compile('$')),
  293. ]
  294. def __init__(self, str):
  295. Scanner.__init__(self,None,['\\s+'],str)
  296. class XPathParser(Parser):
  297. Context = Context
  298. def XPATH(self, _parent=None):
  299. _context = self.Context(_parent, self._scanner, self._pos, 'XPATH', [])
  300. PATH = self.PATH(_context)
  301. result = PATH; current = result
  302. while self._peek('END', '"/"', '"//"') != 'END':
  303. PATH = self.PATH(_context)
  304. current.childLocation = PATH; current = current.childLocation
  305. if self._peek() not in ['END', '"/"', '"//"']:
  306. raise SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['END', '"/"', '"//"']))
  307. END = self._scan('END')
  308. return result
  309. def PATH(self, _parent=None):
  310. _context = self.Context(_parent, self._scanner, self._pos, 'PATH', [])
  311. _token = self._peek('"/"', '"//"')
  312. if _token == '"/"':
  313. self._scan('"/"')
  314. result = _Location()
  315. else: # == '"//"'
  316. self._scan('"//"')
  317. result = _AnyLocation()
  318. _token = self._peek('IDENTIFIER', 'WILDCARD')
  319. if _token == 'IDENTIFIER':
  320. IDENTIFIER = self._scan('IDENTIFIER')
  321. result.elementName = IDENTIFIER
  322. else: # == 'WILDCARD'
  323. WILDCARD = self._scan('WILDCARD')
  324. result.elementName = None
  325. while self._peek('"\\["', 'END', '"/"', '"//"') == '"\\["':
  326. self._scan('"\\["')
  327. PREDICATE = self.PREDICATE(_context)
  328. result.predicates.append(PREDICATE)
  329. self._scan('"\\]"')
  330. if self._peek() not in ['"\\["', 'END', '"/"', '"//"']:
  331. raise SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['"\\["', 'END', '"/"', '"//"']))
  332. return result
  333. def PREDICATE(self, _parent=None):
  334. _context = self.Context(_parent, self._scanner, self._pos, 'PREDICATE', [])
  335. _token = self._peek('INDEX', '"\\("', '"@"', 'FUNCNAME', 'STR_DQ', 'STR_SQ')
  336. if _token != 'INDEX':
  337. EXPR = self.EXPR(_context)
  338. return EXPR
  339. else: # == 'INDEX'
  340. INDEX = self._scan('INDEX')
  341. return IndexValue(INDEX)
  342. def EXPR(self, _parent=None):
  343. _context = self.Context(_parent, self._scanner, self._pos, 'EXPR', [])
  344. FACTOR = self.FACTOR(_context)
  345. e = FACTOR
  346. while self._peek('OP_AND', 'OP_OR', '"\\)"', '"\\]"') in ['OP_AND', 'OP_OR']:
  347. BOOLOP = self.BOOLOP(_context)
  348. FACTOR = self.FACTOR(_context)
  349. e = BooleanValue(e, BOOLOP, FACTOR)
  350. if self._peek() not in ['OP_AND', 'OP_OR', '"\\)"', '"\\]"']:
  351. raise SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['OP_AND', 'OP_OR', '"\\)"', '"\\]"']))
  352. return e
  353. def BOOLOP(self, _parent=None):
  354. _context = self.Context(_parent, self._scanner, self._pos, 'BOOLOP', [])
  355. _token = self._peek('OP_AND', 'OP_OR')
  356. if _token == 'OP_AND':
  357. OP_AND = self._scan('OP_AND')
  358. return OP_AND
  359. else: # == 'OP_OR'
  360. OP_OR = self._scan('OP_OR')
  361. return OP_OR
  362. def FACTOR(self, _parent=None):
  363. _context = self.Context(_parent, self._scanner, self._pos, 'FACTOR', [])
  364. _token = self._peek('"\\("', '"@"', 'FUNCNAME', 'STR_DQ', 'STR_SQ')
  365. if _token != '"\\("':
  366. TERM = self.TERM(_context)
  367. return TERM
  368. else: # == '"\\("'
  369. self._scan('"\\("')
  370. EXPR = self.EXPR(_context)
  371. self._scan('"\\)"')
  372. return EXPR
  373. def TERM(self, _parent=None):
  374. _context = self.Context(_parent, self._scanner, self._pos, 'TERM', [])
  375. VALUE = self.VALUE(_context)
  376. t = VALUE
  377. if self._peek('CMP_EQ', 'CMP_NE', 'OP_AND', 'OP_OR', '"\\)"', '"\\]"') in ['CMP_EQ', 'CMP_NE']:
  378. CMP = self.CMP(_context)
  379. VALUE = self.VALUE(_context)
  380. t = CompareValue(t, CMP, VALUE)
  381. return t
  382. def VALUE(self, _parent=None):
  383. _context = self.Context(_parent, self._scanner, self._pos, 'VALUE', [])
  384. _token = self._peek('"@"', 'FUNCNAME', 'STR_DQ', 'STR_SQ')
  385. if _token == '"@"':
  386. self._scan('"@"')
  387. IDENTIFIER = self._scan('IDENTIFIER')
  388. return AttribValue(IDENTIFIER)
  389. elif _token == 'FUNCNAME':
  390. FUNCNAME = self._scan('FUNCNAME')
  391. f = Function(FUNCNAME); args = []
  392. self._scan('"\\("')
  393. if self._peek('"\\)"', '"@"', 'FUNCNAME', '","', 'STR_DQ', 'STR_SQ') not in ['"\\)"', '","']:
  394. VALUE = self.VALUE(_context)
  395. args.append(VALUE)
  396. while self._peek('","', '"\\)"') == '","':
  397. self._scan('","')
  398. VALUE = self.VALUE(_context)
  399. args.append(VALUE)
  400. if self._peek() not in ['","', '"\\)"']:
  401. raise SyntaxError(charpos=self._scanner.get_prev_char_pos(), context=_context, msg='Need one of ' + ', '.join(['","', '"\\)"']))
  402. self._scan('"\\)"')
  403. f.setParams(*args); return f
  404. else: # in ['STR_DQ', 'STR_SQ']
  405. STR = self.STR(_context)
  406. return LiteralValue(STR[1:len(STR)-1])
  407. def CMP(self, _parent=None):
  408. _context = self.Context(_parent, self._scanner, self._pos, 'CMP', [])
  409. _token = self._peek('CMP_EQ', 'CMP_NE')
  410. if _token == 'CMP_EQ':
  411. CMP_EQ = self._scan('CMP_EQ')
  412. return CMP_EQ
  413. else: # == 'CMP_NE'
  414. CMP_NE = self._scan('CMP_NE')
  415. return CMP_NE
  416. def STR(self, _parent=None):
  417. _context = self.Context(_parent, self._scanner, self._pos, 'STR', [])
  418. _token = self._peek('STR_DQ', 'STR_SQ')
  419. if _token == 'STR_DQ':
  420. STR_DQ = self._scan('STR_DQ')
  421. return STR_DQ
  422. else: # == 'STR_SQ'
  423. STR_SQ = self._scan('STR_SQ')
  424. return STR_SQ
  425. def parse(rule, text):
  426. P = XPathParser(XPathParserScanner(text))
  427. return wrap_error_reporter(P, rule)
  428. if __name__ == '__main__':
  429. from sys import argv, stdin
  430. if len(argv) >= 2:
  431. if len(argv) >= 3:
  432. f = open(argv[2],'r')
  433. else:
  434. f = stdin
  435. print parse(argv[1], f.read())
  436. else: print >>sys.stderr, 'Args: <rule> [<filename>]'
  437. # End -- grammar generated by Yapps