This repo contains code to mirror other repos. It also contains the code that is getting mirrored.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

187 lines
6.9 KiB

  1. from .exceptions import GrammarError
  2. from .utils import suppress
  3. from .lexer import Token
  4. from .grammar import Rule
  5. from .tree import Tree
  6. from .visitors import InlineTransformer # XXX Deprecated
  7. ###{standalone
  8. from functools import partial, wraps
  9. from itertools import repeat, product
  10. class ExpandSingleChild:
  11. def __init__(self, node_builder):
  12. self.node_builder = node_builder
  13. def __call__(self, children):
  14. if len(children) == 1:
  15. return children[0]
  16. else:
  17. return self.node_builder(children)
  18. class PropagatePositions:
  19. def __init__(self, node_builder):
  20. self.node_builder = node_builder
  21. def __call__(self, children):
  22. res = self.node_builder(children)
  23. if isinstance(res, Tree) and getattr(res.meta, 'empty', True):
  24. res.meta.empty = True
  25. for c in children:
  26. if isinstance(c, Tree) and c.children and not c.meta.empty:
  27. res.meta.line = c.meta.line
  28. res.meta.column = c.meta.column
  29. res.meta.start_pos = c.meta.start_pos
  30. res.meta.empty = False
  31. break
  32. elif isinstance(c, Token):
  33. res.meta.line = c.line
  34. res.meta.column = c.column
  35. res.meta.start_pos = c.pos_in_stream
  36. res.meta.empty = False
  37. break
  38. for c in reversed(children):
  39. if isinstance(c, Tree) and c.children and not c.meta.empty:
  40. res.meta.end_line = c.meta.end_line
  41. res.meta.end_column = c.meta.end_column
  42. res.meta.end_pos = c.meta.end_pos
  43. res.meta.empty = False
  44. break
  45. elif isinstance(c, Token):
  46. res.meta.end_line = c.end_line
  47. res.meta.end_column = c.end_column
  48. res.meta.end_pos = c.pos_in_stream + len(c.value)
  49. res.meta.empty = False
  50. break
  51. return res
  52. class ChildFilter:
  53. "Optimized childfilter (assumes no duplication in parse tree, so it's safe to change it)"
  54. def __init__(self, to_include, node_builder):
  55. self.node_builder = node_builder
  56. self.to_include = to_include
  57. def __call__(self, children):
  58. filtered = []
  59. for i, to_expand in self.to_include:
  60. if to_expand:
  61. if filtered:
  62. filtered += children[i].children
  63. else: # Optimize for left-recursion
  64. filtered = children[i].children
  65. else:
  66. filtered.append(children[i])
  67. return self.node_builder(filtered)
  68. def _should_expand(sym):
  69. return not sym.is_term and sym.name.startswith('_')
  70. def maybe_create_child_filter(expansion, keep_all_tokens):
  71. to_include = [(i, _should_expand(sym)) for i, sym in enumerate(expansion)
  72. if keep_all_tokens or not (sym.is_term and sym.filter_out)]
  73. if len(to_include) < len(expansion) or any(to_expand for i, to_expand in to_include):
  74. return partial(ChildFilter, to_include)
  75. class AmbiguousExpander:
  76. """Deal with the case where we're expanding children ('_rule') into a parent but the children
  77. are ambiguous. i.e. (parent->_ambig->_expand_this_rule). In this case, make the parent itself
  78. ambiguous with as many copies as their are ambiguous children, and then copy the ambiguous children
  79. into the right parents in the right places, essentially shifting the ambiguiuty up the tree."""
  80. def __init__(self, to_expand, tree_class, node_builder):
  81. self.node_builder = node_builder
  82. self.tree_class = tree_class
  83. self.to_expand = to_expand
  84. def __call__(self, children):
  85. def _is_ambig_tree(child):
  86. return hasattr(child, 'data') and child.data == '_ambig'
  87. ambiguous = [i for i in self.to_expand if _is_ambig_tree(children[i])]
  88. if ambiguous:
  89. expand = [iter(child.children) if i in ambiguous else repeat(child) for i, child in enumerate(children)]
  90. return self.tree_class('_ambig', [self.node_builder(list(f[0])) for f in product(zip(*expand))])
  91. return self.node_builder(children)
  92. def maybe_create_ambiguous_expander(tree_class, expansion, keep_all_tokens):
  93. to_expand = [i for i, sym in enumerate(expansion)
  94. if keep_all_tokens or ((not (sym.is_term and sym.filter_out)) and _should_expand(sym))]
  95. if to_expand:
  96. return partial(AmbiguousExpander, to_expand, tree_class)
  97. class Callback(object):
  98. pass
  99. def ptb_inline_args(func):
  100. @wraps(func)
  101. def f(children):
  102. return func(*children)
  103. return f
  104. class ParseTreeBuilder:
  105. def __init__(self, rules, tree_class, propagate_positions=False, keep_all_tokens=False, ambiguous=False):
  106. self.tree_class = tree_class
  107. self.propagate_positions = propagate_positions
  108. self.always_keep_all_tokens = keep_all_tokens
  109. self.ambiguous = ambiguous
  110. self.rule_builders = list(self._init_builders(rules))
  111. self.user_aliases = {}
  112. def _init_builders(self, rules):
  113. for rule in rules:
  114. options = rule.options
  115. keep_all_tokens = self.always_keep_all_tokens or (options.keep_all_tokens if options else False)
  116. expand_single_child = options.expand1 if options else False
  117. wrapper_chain = filter(None, [
  118. (expand_single_child and not rule.alias) and ExpandSingleChild,
  119. maybe_create_child_filter(rule.expansion, keep_all_tokens),
  120. self.propagate_positions and PropagatePositions,
  121. self.ambiguous and maybe_create_ambiguous_expander(self.tree_class, rule.expansion, keep_all_tokens),
  122. ])
  123. yield rule, wrapper_chain
  124. def create_callback(self, transformer=None):
  125. callback = Callback()
  126. i = 0
  127. for rule, wrapper_chain in self.rule_builders:
  128. internal_callback_name = '_cb%d_%s' % (i, rule.origin)
  129. i += 1
  130. user_callback_name = rule.alias or rule.origin.name
  131. try:
  132. f = getattr(transformer, user_callback_name)
  133. assert not getattr(f, 'meta', False), "Meta args not supported for internal transformer"
  134. # XXX InlineTransformer is deprecated!
  135. if getattr(f, 'inline', False) or isinstance(transformer, InlineTransformer):
  136. f = ptb_inline_args(f)
  137. except AttributeError:
  138. f = partial(self.tree_class, user_callback_name)
  139. self.user_aliases[rule] = rule.alias
  140. rule.alias = internal_callback_name
  141. for w in wrapper_chain:
  142. f = w(f)
  143. if hasattr(callback, internal_callback_name):
  144. raise GrammarError("Rule '%s' already exists" % (rule,))
  145. setattr(callback, internal_callback_name, f)
  146. return callback
  147. ###}