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

/mercurial/parser.py

https://bitbucket.org/mirror/mercurial/
Python | 98 lines | 76 code | 4 blank | 18 comment | 25 complexity | 20fbb17ec7403021b9d51e1cf4d1483f MD5 | raw file
Possible License(s): GPL-2.0
  1. # parser.py - simple top-down operator precedence parser for mercurial
  2. #
  3. # Copyright 2010 Matt Mackall <mpm@selenic.com>
  4. #
  5. # This software may be used and distributed according to the terms of the
  6. # GNU General Public License version 2 or any later version.
  7. # see http://effbot.org/zone/simple-top-down-parsing.htm and
  8. # http://eli.thegreenplace.net/2010/01/02/top-down-operator-precedence-parsing/
  9. # for background
  10. # takes a tokenizer and elements
  11. # tokenizer is an iterator that returns type, value pairs
  12. # elements is a mapping of types to binding strength, prefix and infix actions
  13. # an action is a tree node name, a tree label, and an optional match
  14. # __call__(program) parses program into a labeled tree
  15. import error
  16. from i18n import _
  17. class parser(object):
  18. def __init__(self, tokenizer, elements, methods=None):
  19. self._tokenizer = tokenizer
  20. self._elements = elements
  21. self._methods = methods
  22. self.current = None
  23. def _advance(self):
  24. 'advance the tokenizer'
  25. t = self.current
  26. try:
  27. self.current = self._iter.next()
  28. except StopIteration:
  29. pass
  30. return t
  31. def _match(self, m, pos):
  32. 'make sure the tokenizer matches an end condition'
  33. if self.current[0] != m:
  34. raise error.ParseError(_("unexpected token: %s") % self.current[0],
  35. self.current[2])
  36. self._advance()
  37. def _parse(self, bind=0):
  38. token, value, pos = self._advance()
  39. # handle prefix rules on current token
  40. prefix = self._elements[token][1]
  41. if not prefix:
  42. raise error.ParseError(_("not a prefix: %s") % token, pos)
  43. if len(prefix) == 1:
  44. expr = (prefix[0], value)
  45. else:
  46. if len(prefix) > 2 and prefix[2] == self.current[0]:
  47. self._match(prefix[2], pos)
  48. expr = (prefix[0], None)
  49. else:
  50. expr = (prefix[0], self._parse(prefix[1]))
  51. if len(prefix) > 2:
  52. self._match(prefix[2], pos)
  53. # gather tokens until we meet a lower binding strength
  54. while bind < self._elements[self.current[0]][0]:
  55. token, value, pos = self._advance()
  56. e = self._elements[token]
  57. # check for suffix - next token isn't a valid prefix
  58. if len(e) == 4 and not self._elements[self.current[0]][1]:
  59. suffix = e[3]
  60. expr = (suffix[0], expr)
  61. else:
  62. # handle infix rules
  63. if len(e) < 3 or not e[2]:
  64. raise error.ParseError(_("not an infix: %s") % token, pos)
  65. infix = e[2]
  66. if len(infix) == 3 and infix[2] == self.current[0]:
  67. self._match(infix[2], pos)
  68. expr = (infix[0], expr, (None))
  69. else:
  70. expr = (infix[0], expr, self._parse(infix[1]))
  71. if len(infix) == 3:
  72. self._match(infix[2], pos)
  73. return expr
  74. def parse(self, message, lookup=None):
  75. 'generate a parse tree from a message'
  76. if lookup:
  77. self._iter = self._tokenizer(message, lookup)
  78. else:
  79. self._iter = self._tokenizer(message)
  80. self._advance()
  81. res = self._parse()
  82. token, value, pos = self.current
  83. return res, pos
  84. def eval(self, tree):
  85. 'recursively evaluate a parse tree using node methods'
  86. if not isinstance(tree, tuple):
  87. return tree
  88. return self._methods[tree[0]](*[self.eval(t) for t in tree[1:]])
  89. def __call__(self, message):
  90. 'parse a message into a parse tree and evaluate if methods given'
  91. t = self.parse(message)
  92. if self._methods:
  93. return self.eval(t)
  94. return t