Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/parso/tree.py: 30%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from abc import abstractmethod, abstractproperty
2from typing import List, Optional, Tuple, Union
4from parso.utils import split_lines
7def search_ancestor(node: 'NodeOrLeaf', *node_types: str) -> 'Optional[BaseNode]':
8 """
9 Recursively looks at the parents of a node and returns the first found node
10 that matches ``node_types``. Returns ``None`` if no matching node is found.
12 This function is deprecated, use :meth:`NodeOrLeaf.search_ancestor` instead.
14 :param node: The ancestors of this node will be checked.
15 :param node_types: type names that are searched for.
16 """
17 return node.search_ancestor(*node_types)
20class NodeOrLeaf:
21 """
22 The base class for nodes and leaves.
23 """
24 __slots__ = ('parent',)
25 type: str
26 '''
27 The type is a string that typically matches the types of the grammar file.
28 '''
29 parent: 'Optional[BaseNode]'
30 '''
31 The parent :class:`BaseNode` of this node or leaf.
32 None if this is the root node.
33 '''
35 def get_root_node(self):
36 """
37 Returns the root node of a parser tree. The returned node doesn't have
38 a parent node like all the other nodes/leaves.
39 """
40 scope = self
41 while scope.parent is not None:
42 scope = scope.parent
43 return scope
45 def get_next_sibling(self):
46 """
47 Returns the node immediately following this node in this parent's
48 children list. If this node does not have a next sibling, it is None
49 """
50 parent = self.parent
51 if parent is None:
52 return None
54 # Can't use index(); we need to test by identity
55 for i, child in enumerate(parent.children):
56 if child is self:
57 try:
58 return self.parent.children[i + 1]
59 except IndexError:
60 return None
62 def get_previous_sibling(self):
63 """
64 Returns the node immediately preceding this node in this parent's
65 children list. If this node does not have a previous sibling, it is
66 None.
67 """
68 parent = self.parent
69 if parent is None:
70 return None
72 # Can't use index(); we need to test by identity
73 for i, child in enumerate(parent.children):
74 if child is self:
75 if i == 0:
76 return None
77 return self.parent.children[i - 1]
79 def get_previous_leaf(self):
80 """
81 Returns the previous leaf in the parser tree.
82 Returns `None` if this is the first element in the parser tree.
83 """
84 if self.parent is None:
85 return None
87 node = self
88 while True:
89 c = node.parent.children
90 i = c.index(node)
91 if i == 0:
92 node = node.parent
93 if node.parent is None:
94 return None
95 else:
96 node = c[i - 1]
97 break
99 while True:
100 try:
101 node = node.children[-1]
102 except AttributeError: # A Leaf doesn't have children.
103 return node
105 def get_next_leaf(self):
106 """
107 Returns the next leaf in the parser tree.
108 Returns None if this is the last element in the parser tree.
109 """
110 if self.parent is None:
111 return None
113 node = self
114 while True:
115 c = node.parent.children
116 i = c.index(node)
117 if i == len(c) - 1:
118 node = node.parent
119 if node.parent is None:
120 return None
121 else:
122 node = c[i + 1]
123 break
125 while True:
126 try:
127 node = node.children[0]
128 except AttributeError: # A Leaf doesn't have children.
129 return node
131 @abstractproperty
132 def start_pos(self) -> Tuple[int, int]:
133 """
134 Returns the starting position of the prefix as a tuple, e.g. `(3, 4)`.
136 :return tuple of int: (line, column)
137 """
139 @abstractproperty
140 def end_pos(self) -> Tuple[int, int]:
141 """
142 Returns the end position of the prefix as a tuple, e.g. `(3, 4)`.
144 :return tuple of int: (line, column)
145 """
147 @abstractmethod
148 def get_start_pos_of_prefix(self):
149 """
150 Returns the start_pos of the prefix. This means basically it returns
151 the end_pos of the last prefix. The `get_start_pos_of_prefix()` of the
152 prefix `+` in `2 + 1` would be `(1, 1)`, while the start_pos is
153 `(1, 2)`.
155 :return tuple of int: (line, column)
156 """
158 @abstractmethod
159 def get_first_leaf(self):
160 """
161 Returns the first leaf of a node or itself if this is a leaf.
162 """
164 @abstractmethod
165 def get_last_leaf(self):
166 """
167 Returns the last leaf of a node or itself if this is a leaf.
168 """
170 @abstractmethod
171 def get_code(self, include_prefix=True):
172 """
173 Returns the code that was the input for the parser for this node.
175 :param include_prefix: Removes the prefix (whitespace and comments) of
176 e.g. a statement.
177 """
179 def search_ancestor(self, *node_types: str) -> 'Optional[BaseNode]':
180 """
181 Recursively looks at the parents of this node or leaf and returns the
182 first found node that matches ``node_types``. Returns ``None`` if no
183 matching node is found.
185 :param node_types: type names that are searched for.
186 """
187 node = self.parent
188 while node is not None:
189 if node.type in node_types:
190 return node
191 node = node.parent
192 return None
194 def dump(self, *, indent: Optional[Union[int, str]] = 4) -> str:
195 """
196 Returns a formatted dump of the parser tree rooted at this node or leaf. This is
197 mainly useful for debugging purposes.
199 The ``indent`` parameter is interpreted in a similar way as :py:func:`ast.dump`.
200 If ``indent`` is a non-negative integer or string, then the tree will be
201 pretty-printed with that indent level. An indent level of 0, negative, or ``""``
202 will only insert newlines. ``None`` selects the single line representation.
203 Using a positive integer indent indents that many spaces per level. If
204 ``indent`` is a string (such as ``"\\t"``), that string is used to indent each
205 level.
207 :param indent: Indentation style as described above. The default indentation is
208 4 spaces, which yields a pretty-printed dump.
210 >>> import parso
211 >>> print(parso.parse("lambda x, y: x + y").dump())
212 Module([
213 Lambda([
214 Keyword('lambda', (1, 0)),
215 Param([
216 Name('x', (1, 7), prefix=' '),
217 Operator(',', (1, 8)),
218 ]),
219 Param([
220 Name('y', (1, 10), prefix=' '),
221 ]),
222 Operator(':', (1, 11)),
223 PythonNode('arith_expr', [
224 Name('x', (1, 13), prefix=' '),
225 Operator('+', (1, 15), prefix=' '),
226 Name('y', (1, 17), prefix=' '),
227 ]),
228 ]),
229 EndMarker('', (1, 18)),
230 ])
231 """
232 if indent is None:
233 newline = False
234 indent_string = ''
235 elif isinstance(indent, int):
236 newline = True
237 indent_string = ' ' * indent
238 elif isinstance(indent, str):
239 newline = True
240 indent_string = indent
241 else:
242 raise TypeError(f"expect 'indent' to be int, str or None, got {indent!r}")
244 def _format_dump(node: NodeOrLeaf, indent: str = '', top_level: bool = True) -> str:
245 result = ''
246 node_type = type(node).__name__
247 if isinstance(node, Leaf):
248 result += f'{indent}{node_type}('
249 if isinstance(node, ErrorLeaf):
250 result += f'{node.token_type!r}, '
251 elif isinstance(node, TypedLeaf):
252 result += f'{node.type!r}, '
253 result += f'{node.value!r}, {node.start_pos!r}'
254 if node.prefix:
255 result += f', prefix={node.prefix!r}'
256 result += ')'
257 elif isinstance(node, BaseNode):
258 result += f'{indent}{node_type}('
259 if isinstance(node, Node):
260 result += f'{node.type!r}, '
261 result += '['
262 if newline:
263 result += '\n'
264 for child in node.children:
265 result += _format_dump(child, indent=indent + indent_string, top_level=False)
266 result += f'{indent}])'
267 else: # pragma: no cover
268 # We shouldn't ever reach here, unless:
269 # - `NodeOrLeaf` is incorrectly subclassed else where
270 # - or a node's children list contains invalid nodes or leafs
271 # Both are unexpected internal errors.
272 raise TypeError(f'unsupported node encountered: {node!r}')
273 if not top_level:
274 if newline:
275 result += ',\n'
276 else:
277 result += ', '
278 return result
280 return _format_dump(self)
283class Leaf(NodeOrLeaf):
284 '''
285 Leafs are basically tokens with a better API. Leafs exactly know where they
286 were defined and what text preceeds them.
287 '''
288 __slots__ = ('value', 'line', 'column', 'prefix')
289 prefix: str
291 def __init__(self, value: str, start_pos: Tuple[int, int], prefix: str = '') -> None:
292 self.value = value
293 '''
294 :py:func:`str` The value of the current token.
295 '''
296 self.start_pos = start_pos
297 self.prefix = prefix
298 '''
299 :py:func:`str` Typically a mixture of whitespace and comments. Stuff
300 that is syntactically irrelevant for the syntax tree.
301 '''
302 self.parent: Optional[BaseNode] = None
303 '''
304 The parent :class:`BaseNode` of this leaf.
305 '''
307 @property
308 def start_pos(self) -> Tuple[int, int]:
309 return self.line, self.column
311 @start_pos.setter
312 def start_pos(self, value: Tuple[int, int]) -> None:
313 self.line = value[0]
314 self.column = value[1]
316 def get_start_pos_of_prefix(self):
317 previous_leaf = self.get_previous_leaf()
318 if previous_leaf is None:
319 lines = split_lines(self.prefix)
320 # + 1 is needed because split_lines always returns at least [''].
321 return self.line - len(lines) + 1, 0 # It's the first leaf.
322 return previous_leaf.end_pos
324 def get_first_leaf(self):
325 return self
327 def get_last_leaf(self):
328 return self
330 def get_code(self, include_prefix=True):
331 if include_prefix:
332 return self.prefix + self.value
333 else:
334 return self.value
336 @property
337 def end_pos(self) -> Tuple[int, int]:
338 lines = split_lines(self.value)
339 end_pos_line = self.line + len(lines) - 1
340 # Check for multiline token
341 if self.line == end_pos_line:
342 end_pos_column = self.column + len(lines[-1])
343 else:
344 end_pos_column = len(lines[-1])
345 return end_pos_line, end_pos_column
347 def __repr__(self):
348 value = self.value
349 if not value:
350 value = self.type
351 return "<%s: %s>" % (type(self).__name__, value)
354class TypedLeaf(Leaf):
355 __slots__ = ('type',)
357 def __init__(self, type, value, start_pos, prefix=''):
358 super().__init__(value, start_pos, prefix)
359 self.type = type
362class BaseNode(NodeOrLeaf):
363 """
364 The super class for all nodes.
365 A node has children, a type and possibly a parent node.
366 """
367 __slots__ = ('children',)
369 def __init__(self, children) -> None:
370 self.children = children
371 """
372 A list of :class:`NodeOrLeaf` child nodes.
373 """
374 self.parent: Optional[BaseNode] = None
375 '''
376 The parent :class:`BaseNode` of this node.
377 None if this is the root node.
378 '''
379 for child in children:
380 child.parent = self
382 @property
383 def start_pos(self) -> Tuple[int, int]:
384 return self.children[0].start_pos
386 def get_start_pos_of_prefix(self):
387 return self.children[0].get_start_pos_of_prefix()
389 @property
390 def end_pos(self) -> Tuple[int, int]:
391 return self.children[-1].end_pos
393 def _get_code_for_children(self, children, include_prefix):
394 if include_prefix:
395 return "".join(c.get_code() for c in children)
396 else:
397 first = children[0].get_code(include_prefix=False)
398 return first + "".join(c.get_code() for c in children[1:])
400 def get_code(self, include_prefix=True):
401 return self._get_code_for_children(self.children, include_prefix)
403 def get_leaf_for_position(self, position, include_prefixes=False):
404 """
405 Get the :py:class:`parso.tree.Leaf` at ``position``
407 :param tuple position: A position tuple, row, column. Rows start from 1
408 :param bool include_prefixes: If ``False``, ``None`` will be returned if ``position`` falls
409 on whitespace or comments before a leaf
410 :return: :py:class:`parso.tree.Leaf` at ``position``, or ``None``
411 """
412 def binary_search(lower, upper):
413 if lower == upper:
414 element = self.children[lower]
415 if not include_prefixes and position < element.start_pos:
416 # We're on a prefix.
417 return None
418 # In case we have prefixes, a leaf always matches
419 try:
420 return element.get_leaf_for_position(position, include_prefixes)
421 except AttributeError:
422 return element
424 index = int((lower + upper) / 2)
425 element = self.children[index]
426 if position <= element.end_pos:
427 return binary_search(lower, index)
428 else:
429 return binary_search(index + 1, upper)
431 if not ((1, 0) <= position <= self.children[-1].end_pos):
432 raise ValueError('Please provide a position that exists within this node.')
433 return binary_search(0, len(self.children) - 1)
435 def get_first_leaf(self):
436 return self.children[0].get_first_leaf()
438 def get_last_leaf(self):
439 return self.children[-1].get_last_leaf()
441 def __repr__(self):
442 code = self.get_code().replace('\n', ' ').replace('\r', ' ').strip()
443 return "<%s: %s@%s,%s>" % \
444 (type(self).__name__, code, self.start_pos[0], self.start_pos[1])
447class Node(BaseNode):
448 """Concrete implementation for interior nodes."""
449 __slots__ = ('type',)
451 def __init__(self, type, children):
452 super().__init__(children)
453 self.type = type
455 def __repr__(self):
456 return "%s(%s, %r)" % (self.__class__.__name__, self.type, self.children)
459class ErrorNode(BaseNode):
460 """
461 A node that contains valid nodes/leaves that we're follow by a token that
462 was invalid. This basically means that the leaf after this node is where
463 Python would mark a syntax error.
464 """
465 __slots__ = ()
466 type = 'error_node'
469class ErrorLeaf(Leaf):
470 """
471 A leaf that is either completely invalid in a language (like `$` in Python)
472 or is invalid at that position. Like the star in `1 +* 1`.
473 """
474 __slots__ = ('token_type',)
475 type = 'error_leaf'
477 def __init__(self, token_type, value, start_pos, prefix=''):
478 super().__init__(value, start_pos, prefix)
479 self.token_type = token_type
481 def __repr__(self):
482 return "<%s: %s:%s, %s>" % \
483 (type(self).__name__, self.token_type, repr(self.value), self.start_pos)