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.

156 lines
5.0 KiB

  1. from .common import is_terminal, GrammarError
  2. from .utils import suppress
  3. from .lexer import Token
  4. from .grammar import Rule
  5. ###{standalone
  6. from functools import partial
  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. filtered += children[i].children
  48. else:
  49. filtered.append(children[i])
  50. return self.node_builder(filtered)
  51. class ChildFilterLALR(ChildFilter):
  52. "Optimized childfilter for LALR (assumes no duplication in parse tree, so it's safe to change it)"
  53. def __call__(self, children):
  54. filtered = []
  55. for i, to_expand in self.to_include:
  56. if to_expand:
  57. if filtered:
  58. filtered += children[i].children
  59. else: # Optimize for left-recursion
  60. filtered = children[i].children
  61. else:
  62. filtered.append(children[i])
  63. return self.node_builder(filtered)
  64. def _should_expand(sym):
  65. return not sym.is_term and sym.name.startswith('_')
  66. def maybe_create_child_filter(expansion, filter_out, ambiguous):
  67. to_include = [(i, _should_expand(sym)) for i, sym in enumerate(expansion) if sym not in filter_out]
  68. if len(to_include) < len(expansion) or any(to_expand for i, to_expand in to_include):
  69. return partial(ChildFilter if ambiguous else ChildFilterLALR, to_include)
  70. class Callback(object):
  71. pass
  72. class ParseTreeBuilder:
  73. def __init__(self, rules, tree_class, propagate_positions=False, keep_all_tokens=False, ambiguous=False):
  74. self.tree_class = tree_class
  75. self.propagate_positions = propagate_positions
  76. self.always_keep_all_tokens = keep_all_tokens
  77. self.ambiguous = ambiguous
  78. self.rule_builders = list(self._init_builders(rules))
  79. self.user_aliases = {}
  80. def _init_builders(self, rules):
  81. filter_out = {rule.origin for rule in rules if rule.options and rule.options.filter_out}
  82. filter_out |= {sym for rule in rules for sym in rule.expansion if sym.is_term and sym.filter_out}
  83. assert all(t.filter_out for t in filter_out)
  84. for rule in rules:
  85. options = rule.options
  86. keep_all_tokens = self.always_keep_all_tokens or (options.keep_all_tokens if options else False)
  87. expand_single_child = options.expand1 if options else False
  88. create_token = options.create_token if options else False
  89. wrapper_chain = filter(None, [
  90. create_token and partial(CreateToken, create_token),
  91. (expand_single_child and not rule.alias) and ExpandSingleChild,
  92. maybe_create_child_filter(rule.expansion, () if keep_all_tokens else filter_out, self.ambiguous),
  93. self.propagate_positions and PropagatePositions,
  94. ])
  95. yield rule, wrapper_chain
  96. def create_callback(self, transformer=None):
  97. callback = Callback()
  98. for rule, wrapper_chain in self.rule_builders:
  99. internal_callback_name = '_callback_%s_%s' % (rule.origin, '_'.join(x.name for x in rule.expansion))
  100. user_callback_name = rule.alias or rule.origin.name
  101. try:
  102. f = transformer._get_func(user_callback_name)
  103. except AttributeError:
  104. f = partial(self.tree_class, user_callback_name)
  105. self.user_aliases[rule] = rule.alias
  106. rule.alias = internal_callback_name
  107. for w in wrapper_chain:
  108. f = w(f)
  109. if hasattr(callback, internal_callback_name):
  110. raise GrammarError("Rule '%s' already exists" % (rule,))
  111. setattr(callback, internal_callback_name, f)
  112. return callback
  113. ###}