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.

142 lines
4.5 KiB

  1. from functools import partial
  2. from .common import is_terminal, GrammarError
  3. from .utils import suppress
  4. from .lexer import Token
  5. from .grammar import Rule
  6. ###{standalone
  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 CreateToken:
  16. "Used for fixing the results of scanless parsing"
  17. def __init__(self, token_name, node_builder):
  18. self.node_builder = node_builder
  19. self.token_name = token_name
  20. def __call__(self, children):
  21. return self.node_builder( [Token(self.token_name, ''.join(children))] )
  22. class PropagatePositions:
  23. def __init__(self, node_builder):
  24. self.node_builder = node_builder
  25. def __call__(self, children):
  26. res = self.node_builder(children)
  27. if children:
  28. for a in children:
  29. with suppress(AttributeError):
  30. res.line = a.line
  31. res.column = a.column
  32. break
  33. for a in reversed(children):
  34. with suppress(AttributeError):
  35. res.end_line = a.end_line
  36. res.end_column = a.end_column
  37. break
  38. return res
  39. class ChildFilter:
  40. def __init__(self, to_include, node_builder):
  41. self.node_builder = node_builder
  42. self.to_include = to_include
  43. def __call__(self, children):
  44. filtered = []
  45. for i, to_expand in self.to_include:
  46. if to_expand:
  47. if filtered:
  48. filtered += children[i].children
  49. else: # Optimize for left-recursion
  50. filtered = children[i].children
  51. else:
  52. filtered.append(children[i])
  53. return self.node_builder(filtered)
  54. def _should_expand(sym):
  55. return not is_terminal(sym) and sym.startswith('_')
  56. def maybe_create_child_filter(expansion, filter_out):
  57. to_include = [(i, _should_expand(sym)) for i, sym in enumerate(expansion) if sym not in filter_out]
  58. if len(to_include) < len(expansion) or any(to_expand for i, to_expand in to_include):
  59. return partial(ChildFilter, to_include)
  60. class Callback(object):
  61. pass
  62. class ParseTreeBuilder:
  63. def __init__(self, rules, tree_class, propagate_positions=False, keep_all_tokens=False):
  64. self.tree_class = tree_class
  65. self.propagate_positions = propagate_positions
  66. self.always_keep_all_tokens = keep_all_tokens
  67. self.rule_builders = list(self._init_builders(rules))
  68. self.user_aliases = {}
  69. def _init_builders(self, rules):
  70. filter_out = {rule.origin for rule in rules if rule.options and rule.options.filter_out}
  71. filter_out |= {sym for rule in rules for sym in rule.expansion if is_terminal(sym) and sym.startswith('_')}
  72. assert all(x.startswith('_') for x in filter_out)
  73. for rule in rules:
  74. options = rule.options
  75. keep_all_tokens = self.always_keep_all_tokens or (options.keep_all_tokens if options else False)
  76. expand_single_child = options.expand1 if options else False
  77. create_token = options.create_token if options else False
  78. wrapper_chain = filter(None, [
  79. create_token and partial(CreateToken, create_token),
  80. (expand_single_child and not rule.alias) and ExpandSingleChild,
  81. maybe_create_child_filter(rule.expansion, () if keep_all_tokens else filter_out),
  82. self.propagate_positions and PropagatePositions,
  83. ])
  84. yield rule, wrapper_chain
  85. def create_callback(self, transformer=None):
  86. callback = Callback()
  87. for rule, wrapper_chain in self.rule_builders:
  88. internal_callback_name = '_callback_%s_%s' % (rule.origin, '_'.join(rule.expansion))
  89. user_callback_name = rule.alias or rule.origin
  90. try:
  91. f = transformer._get_func(user_callback_name)
  92. except AttributeError:
  93. f = partial(self.tree_class, user_callback_name)
  94. self.user_aliases[rule] = rule.alias
  95. rule.alias = internal_callback_name
  96. for w in wrapper_chain:
  97. f = w(f)
  98. if hasattr(callback, internal_callback_name):
  99. raise GrammarError("Rule '%s' already exists" % (rule,))
  100. setattr(callback, internal_callback_name, f)
  101. return callback
  102. ###}