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.

165 lines
5.4 KiB

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