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.

155 lines
4.7 KiB

  1. """This module defines utilities for matching and translation tree templates.
  2. A tree templates is a tree that contains nodes that are template variables.
  3. """
  4. from typing import Union, Optional, Mapping
  5. from lark import Tree, Transformer
  6. TreeOrCode = Union[Tree, str]
  7. class TemplateConf:
  8. """Template Configuration
  9. Allows customization for different uses of Template
  10. """
  11. def __init__(self, parse=None):
  12. self._parse = parse
  13. def test_var(self, var: Union[Tree, str]) -> Optional[str]:
  14. """Given a tree node, if it is a template variable return its name. Otherwise, return None.
  15. This method may be overridden for customization
  16. Parameters:
  17. var: Tree | str - The tree node to test
  18. """
  19. if isinstance(var, str) and var.startswith('$'):
  20. return var.lstrip('$')
  21. if isinstance(var, Tree) and var.data == 'var' and var.children[0].startswith('$'):
  22. return var.children[0].lstrip('$')
  23. def _get_tree(self, template: TreeOrCode):
  24. if isinstance(template, str):
  25. assert self._parse
  26. template = self._parse(template)
  27. assert isinstance(template, Tree)
  28. return template
  29. def __call__(self, template):
  30. return Template(template, conf=self)
  31. def _match_tree_template(self, template, tree):
  32. template_var = self.test_var(template)
  33. if template_var:
  34. return {template_var: tree}
  35. if isinstance(template, str):
  36. if template == tree:
  37. return {}
  38. return
  39. assert isinstance(template, Tree), template
  40. if template.data == tree.data and len(template.children) == len(tree.children):
  41. res = {}
  42. for t1, t2 in zip(template.children, tree.children):
  43. matches = self._match_tree_template(t1, t2)
  44. if matches is None:
  45. return
  46. res.update(matches)
  47. return res
  48. class _ReplaceVars(Transformer):
  49. def __init__(self, conf, vars):
  50. self._conf = conf
  51. self._vars = vars
  52. def __default__(self, data, children, meta):
  53. tree = super().__default__(data, children, meta)
  54. var = self._conf.test_var(tree)
  55. if var:
  56. return self._vars[var]
  57. return tree
  58. class Template:
  59. """Represents a tree templates, tied to a specific configuration
  60. A tree template is a tree that contains nodes that are template variables.
  61. Those variables will match any tree.
  62. (future versions may support annotations on the variables, to allow more complex templates)
  63. """
  64. def __init__(self, tree: Tree, conf = TemplateConf()):
  65. self.conf = conf
  66. self.tree = conf._get_tree(tree)
  67. def match(self, tree: TreeOrCode):
  68. """Match a tree template to a tree.
  69. A tree template without variables will only match ``tree`` if it is equal to the template.
  70. Parameters:
  71. tree (Tree): The tree to match to the template
  72. Returns:
  73. Optional[Dict[str, Tree]]: If match is found, returns a dictionary mapping
  74. template variable names to their matching tree nodes.
  75. If no match was found, returns None.
  76. """
  77. tree = self.conf._get_tree(tree)
  78. return self.conf._match_tree_template(self.tree, tree)
  79. def search(self, tree: TreeOrCode):
  80. """Search for all occurances of the tree template inside ``tree``.
  81. """
  82. tree = self.conf._get_tree(tree)
  83. for subtree in tree.iter_subtrees():
  84. res = self.match(subtree)
  85. if res:
  86. yield subtree, res
  87. def apply_vars(self, vars: Mapping[str, Tree]):
  88. """Apply vars to the template tree
  89. """
  90. return _ReplaceVars(self.conf, vars).transform(self.tree)
  91. def translate(t1: Template, t2: Template, tree: TreeOrCode):
  92. """Search tree and translate each occurrance of t1 into t2.
  93. """
  94. tree = t1.conf._get_tree(tree) # ensure it's a tree, parse if necessary and possible
  95. for subtree, vars in t1.search(tree):
  96. res = t2.apply_vars(vars)
  97. subtree.set(res.data, res.children)
  98. return tree
  99. class TemplateTranslator:
  100. """Utility class for translating a collection of patterns
  101. """
  102. def __init__(self, translations: Mapping[TreeOrCode, TreeOrCode]):
  103. assert all( isinstance(k, Template) and isinstance(v, Template) for k, v in translations.items() )
  104. self.translations = translations
  105. def translate(self, tree: Tree):
  106. for k, v in self.translations.items():
  107. tree = translate(k, v, tree)
  108. return tree