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.

240 lines
7.3 KiB

  1. try:
  2. from future_builtins import filter # type: ignore
  3. except ImportError:
  4. pass
  5. import sys
  6. from copy import deepcopy
  7. from typing import List, Callable, Iterator, Union, Optional, TYPE_CHECKING
  8. if TYPE_CHECKING:
  9. from .lexer import TerminalDef
  10. if sys.version_info >= (3, 8):
  11. from typing import Literal
  12. else:
  13. from typing_extensions import Literal
  14. ###{standalone
  15. from collections import OrderedDict
  16. class Meta:
  17. empty: bool
  18. line: int
  19. column: int
  20. start_pos: int
  21. end_line: int
  22. end_column: int
  23. end_pos: int
  24. orig_expansion: 'List[TerminalDef]'
  25. match_tree: bool
  26. def __init__(self):
  27. self.empty = True
  28. class Tree:
  29. """The main tree class.
  30. Creates a new tree, and stores "data" and "children" in attributes of the same name.
  31. Trees can be hashed and compared.
  32. Parameters:
  33. data: The name of the rule or alias
  34. children: List of matched sub-rules and terminals
  35. meta: Line & Column numbers (if ``propagate_positions`` is enabled).
  36. meta attributes: line, column, start_pos, end_line, end_column, end_pos
  37. """
  38. data: str
  39. children: 'List[Union[str, Tree]]'
  40. def __init__(self, data: str, children: 'List[Union[str, Tree]]', meta: Optional[Meta]=None) -> None:
  41. self.data = data
  42. self.children = children
  43. self._meta = meta
  44. @property
  45. def meta(self) -> Meta:
  46. if self._meta is None:
  47. self._meta = Meta()
  48. return self._meta
  49. def __repr__(self):
  50. return 'Tree(%r, %r)' % (self.data, self.children)
  51. def _pretty_label(self):
  52. return self.data
  53. def _pretty(self, level, indent_str):
  54. if len(self.children) == 1 and not isinstance(self.children[0], Tree):
  55. return [indent_str*level, self._pretty_label(), '\t', '%s' % (self.children[0],), '\n']
  56. l = [indent_str*level, self._pretty_label(), '\n']
  57. for n in self.children:
  58. if isinstance(n, Tree):
  59. l += n._pretty(level+1, indent_str)
  60. else:
  61. l += [indent_str*(level+1), '%s' % (n,), '\n']
  62. return l
  63. def pretty(self, indent_str: str=' ') -> str:
  64. """Returns an indented string representation of the tree.
  65. Great for debugging.
  66. """
  67. return ''.join(self._pretty(0, indent_str))
  68. def __eq__(self, other):
  69. try:
  70. return self.data == other.data and self.children == other.children
  71. except AttributeError:
  72. return False
  73. def __ne__(self, other):
  74. return not (self == other)
  75. def __hash__(self) -> int:
  76. return hash((self.data, tuple(self.children)))
  77. def iter_subtrees(self) -> 'Iterator[Tree]':
  78. """Depth-first iteration.
  79. Iterates over all the subtrees, never returning to the same node twice (Lark's parse-tree is actually a DAG).
  80. """
  81. queue = [self]
  82. subtrees = OrderedDict()
  83. for subtree in queue:
  84. subtrees[id(subtree)] = subtree
  85. queue += [c for c in reversed(subtree.children)
  86. if isinstance(c, Tree) and id(c) not in subtrees]
  87. del queue
  88. return reversed(list(subtrees.values()))
  89. def find_pred(self, pred: 'Callable[[Tree], bool]') -> 'Iterator[Tree]':
  90. """Returns all nodes of the tree that evaluate pred(node) as true."""
  91. return filter(pred, self.iter_subtrees())
  92. def find_data(self, data: str) -> 'Iterator[Tree]':
  93. """Returns all nodes of the tree whose data equals the given data."""
  94. return self.find_pred(lambda t: t.data == data)
  95. ###}
  96. def expand_kids_by_index(self, *indices: int) -> None:
  97. """Expand (inline) children at the given indices"""
  98. for i in sorted(indices, reverse=True): # reverse so that changing tail won't affect indices
  99. kid = self.children[i]
  100. self.children[i:i+1] = kid.children
  101. def expand_kids_by_data(self, *data_values):
  102. """Expand (inline) children with any of the given data values. Returns True if anything changed"""
  103. changed = False
  104. for i in range(len(self.children)-1, -1, -1):
  105. child = self.children[i]
  106. if isinstance(child, Tree) and child.data in data_values:
  107. self.children[i:i+1] = child.children
  108. changed = True
  109. return changed
  110. def scan_values(self, pred: 'Callable[[Union[str, Tree]], bool]') -> Iterator[str]:
  111. """Return all values in the tree that evaluate pred(value) as true.
  112. This can be used to find all the tokens in the tree.
  113. Example:
  114. >>> all_tokens = tree.scan_values(lambda v: isinstance(v, Token))
  115. """
  116. for c in self.children:
  117. if isinstance(c, Tree):
  118. for t in c.scan_values(pred):
  119. yield t
  120. else:
  121. if pred(c):
  122. yield c
  123. def iter_subtrees_topdown(self):
  124. """Breadth-first iteration.
  125. Iterates over all the subtrees, return nodes in order like pretty() does.
  126. """
  127. stack = [self]
  128. while stack:
  129. node = stack.pop()
  130. if not isinstance(node, Tree):
  131. continue
  132. yield node
  133. for n in reversed(node.children):
  134. stack.append(n)
  135. def __deepcopy__(self, memo):
  136. return type(self)(self.data, deepcopy(self.children, memo), meta=self._meta)
  137. def copy(self) -> 'Tree':
  138. return type(self)(self.data, self.children)
  139. def set(self, data: str, children: 'List[Union[str, Tree]]') -> None:
  140. self.data = data
  141. self.children = children
  142. class SlottedTree(Tree):
  143. __slots__ = 'data', 'children', 'rule', '_meta'
  144. def pydot__tree_to_png(tree: Tree, filename: str, rankdir: 'Literal["TB", "LR", "BT", "RL"]'="LR", **kwargs) -> None:
  145. graph = pydot__tree_to_graph(tree, rankdir, **kwargs)
  146. graph.write_png(filename)
  147. def pydot__tree_to_dot(tree, filename, rankdir="LR", **kwargs):
  148. graph = pydot__tree_to_graph(tree, rankdir, **kwargs)
  149. graph.write(filename)
  150. def pydot__tree_to_graph(tree, rankdir="LR", **kwargs):
  151. """Creates a colorful image that represents the tree (data+children, without meta)
  152. Possible values for `rankdir` are "TB", "LR", "BT", "RL", corresponding to
  153. directed graphs drawn from top to bottom, from left to right, from bottom to
  154. top, and from right to left, respectively.
  155. `kwargs` can be any graph attribute (e. g. `dpi=200`). For a list of
  156. possible attributes, see https://www.graphviz.org/doc/info/attrs.html.
  157. """
  158. import pydot # type: ignore
  159. graph = pydot.Dot(graph_type='digraph', rankdir=rankdir, **kwargs)
  160. i = [0]
  161. def new_leaf(leaf):
  162. node = pydot.Node(i[0], label=repr(leaf))
  163. i[0] += 1
  164. graph.add_node(node)
  165. return node
  166. def _to_pydot(subtree):
  167. color = hash(subtree.data) & 0xffffff
  168. color |= 0x808080
  169. subnodes = [_to_pydot(child) if isinstance(child, Tree) else new_leaf(child)
  170. for child in subtree.children]
  171. node = pydot.Node(i[0], style="filled", fillcolor="#%x" % color, label=subtree.data)
  172. i[0] += 1
  173. graph.add_node(node)
  174. for subnode in subnodes:
  175. graph.add_edge(pydot.Edge(node, subnode))
  176. return node
  177. _to_pydot(tree)
  178. return graph