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.

173 lines
5.6 KiB

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