PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/lepl/matchers/matcher.py

https://code.google.com/p/lepl/
Python | 221 lines | 135 code | 14 blank | 72 comment | 0 complexity | 82c5cbf4d91b8f195a9185e4a7db3463 MD5 | raw file
  1. # The contents of this file are subject to the Mozilla Public License
  2. # (MPL) Version 1.1 (the "License"); you may not use this file except
  3. # in compliance with the License. You may obtain a copy of the License
  4. # at http://www.mozilla.org/MPL/
  5. #
  6. # Software distributed under the License is distributed on an "AS IS"
  7. # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  8. # the License for the specific language governing rights and
  9. # limitations under the License.
  10. #
  11. # The Original Code is LEPL (http://www.acooke.org/lepl)
  12. # The Initial Developer of the Original Code is Andrew Cooke.
  13. # Portions created by the Initial Developer are Copyright (C) 2009-2010
  14. # Andrew Cooke (andrew@acooke.org). All Rights Reserved.
  15. #
  16. # Alternatively, the contents of this file may be used under the terms
  17. # of the LGPL license (the GNU Lesser General Public License,
  18. # http://www.gnu.org/licenses/lgpl.html), in which case the provisions
  19. # of the LGPL License are applicable instead of those above.
  20. #
  21. # If you wish to allow use of your version of this file only under the
  22. # terms of the LGPL License and not to allow others to use your version
  23. # of this file under the MPL, indicate your decision by deleting the
  24. # provisions above and replace them with the notice and other provisions
  25. # required by the LGPL License. If you do not delete the provisions
  26. # above, a recipient may use your version of this file under either the
  27. # MPL or the LGPL License.
  28. '''
  29. Base class for matchers.
  30. '''
  31. from abc import ABCMeta
  32. from types import FunctionType
  33. from lepl.support.lib import fmt, singleton, identity
  34. # pylint: disable-msg=C0103, W0105
  35. # Python 2.6
  36. #class Matcher(metaclass=ABCMeta):
  37. _Matcher = ABCMeta('_Matcher', (object, ), {})
  38. '''
  39. ABC used to identify matchers.
  40. Note that graph traversal assumes subclasses are hashable and iterable.
  41. '''
  42. class Matcher(_Matcher):
  43. def __init__(self):
  44. self._small_str = self.__class__.__name__
  45. # @abstractmethod
  46. def _match(self, stream):
  47. '''
  48. This is the core method called during recursive decent. It must
  49. yield (stream, results) pairs until the matcher has exhausted all
  50. possible matches.
  51. To evaluate a sub-matcher it should yield the result of calling
  52. this method on the sub-matcher:
  53. generator = sub_matcher._match(stream_in)
  54. try:
  55. while True:
  56. # evaluate the sub-matcher
  57. (stream_out, result) = yield generator
  58. ....
  59. # return the result from this matcher
  60. yield (stream_out, result)
  61. except StopIteration:
  62. ...
  63. The implementation should be decorated with @tagged in almost all
  64. cases.
  65. '''
  66. # @abstractmethod
  67. def indented_repr(self, indent, key=None):
  68. '''
  69. Called by repr; should recursively call contents.
  70. '''
  71. # Python 2.6
  72. #class FactoryMatcher(metaclass=ABCMeta):
  73. _FactoryMatcher = ABCMeta('_FactoryMatcher', (object, ), {})
  74. '''
  75. ABC used to identify factory matchers (have a property factory that
  76. identifies the matcher they generate).
  77. '''
  78. class FactoryMatcher(_FactoryMatcher):
  79. '''
  80. Imagine an abstract property called 'factory' below.
  81. '''
  82. class MatcherTypeException(Exception):
  83. '''
  84. Used to flag problems related to matcher types.
  85. '''
  86. def raiseException(msg):
  87. raise MatcherTypeException(msg)
  88. def case_type(matcher, if_factory, if_matcher):
  89. if isinstance(matcher, FunctionType) and hasattr(matcher, 'factory'):
  90. return if_factory(matcher.factory)
  91. elif issubclass(matcher, Matcher):
  92. return if_matcher(matcher)
  93. else:
  94. raise MatcherTypeException(
  95. fmt('{0!s} ({1}) does not appear to be a matcher type',
  96. matcher, type(matcher)))
  97. def case_instance(matcher, if_wrapper, if_matcher):
  98. from lepl.matchers.support import FactoryMatcher
  99. try:
  100. if isinstance(matcher, FactoryMatcher):
  101. return if_wrapper(matcher.factory)
  102. except TypeError:
  103. pass # bug in python impl
  104. # may already be unpacked
  105. if isinstance(matcher, FunctionType):
  106. return if_wrapper(matcher)
  107. if isinstance(matcher, Matcher):
  108. return if_matcher(matcher)
  109. else:
  110. raise MatcherTypeException(
  111. fmt('{0!s} ({1}) does not appear to be a matcher',
  112. matcher, type(matcher)))
  113. def canonical_matcher_type(matcher):
  114. '''
  115. Given a "constructor" (either a real constructor, or an annotated
  116. function), generate something that uniquely identifies that (the class
  117. for real constructors, and the embedded function for the output from
  118. the factories).
  119. '''
  120. return case_type(matcher, identity, identity)
  121. def matcher_type(matcher, fail=True):
  122. '''
  123. '''
  124. try:
  125. return case_instance(matcher, identity, type)
  126. except MatcherTypeException as e:
  127. if fail:
  128. raise e
  129. else:
  130. return False
  131. def matcher_map(map_):
  132. '''
  133. Rewrite a map whose keys are matchers to use canonical_matcher_type.
  134. '''
  135. return dict((canonical_matcher_type(key), map_[key]) for key in map_)
  136. def matcher_instance(matcher):
  137. return case_instance(matcher, identity, identity)
  138. class Relations(object):
  139. '''
  140. Some kind of parent/child management for wrapped classes that I no longer
  141. understand, but which appears to be used and working (it doesn't look
  142. like rocket science, but until it breaks I don't care enough to know
  143. more...)
  144. '''
  145. def __init__(self, base):
  146. self.base = base
  147. self.factories = set()
  148. def add_child(self, child):
  149. return case_type(child,
  150. lambda m: self.factories.add(m),
  151. lambda m: self.base.register(m))
  152. def child_of(self, child):
  153. return case_instance(child,
  154. lambda m: m is self.base or m in self.factories,
  155. lambda m: isinstance(self.base, type)
  156. and isinstance(m, self.base))
  157. def relations(base):
  158. # if base is a factory then we want the related type
  159. try:
  160. base = canonical_matcher_type(base)
  161. except MatcherTypeException:
  162. pass
  163. table = singleton(Relations, dict)
  164. if base not in table:
  165. table[base] = Relations(base)
  166. return table[base]
  167. def is_child(child, base, fail=True):
  168. try:
  169. return relations(base).child_of(child)
  170. except MatcherTypeException as e:
  171. if fail:
  172. raise e
  173. else:
  174. return False
  175. def add_child(base, child):
  176. relations(base).add_child(child)
  177. def add_children(base, *children):
  178. for child in children:
  179. add_child(base, child)