Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/parso/tree.py: 79%
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 this node or leaf, excluding its
135 prefix, as a tuple, e.g. `(3, 4)`.
137 :return tuple of int: (line, column)
138 """
140 @abstractproperty
141 def end_pos(self) -> Tuple[int, int]:
142 """
143 Returns the position immediately after this node or leaf as a tuple,
144 e.g. `(3, 4)`.
146 :return tuple of int: (line, column)
147 """
149 @abstractmethod
150 def get_start_pos_of_prefix(self):
151 """
152 Returns the starting position of the prefix (whitespace and comments).
153 For a leaf with a previous leaf, this is the previous leaf's end_pos.
154 For example, the `+` leaf in `2 + 1` has a prefix starting at `(1, 1)`,
155 while its start_pos is `(1, 2)`.
157 :return tuple of int: (line, column)
158 """
160 @abstractmethod
161 def get_first_leaf(self):
162 """
163 Returns the first leaf of a node or itself if this is a leaf.
164 """
166 @abstractmethod
167 def get_last_leaf(self):
168 """
169 Returns the last leaf of a node or itself if this is a leaf.
170 """
172 @abstractmethod
173 def get_code(self, include_prefix=True):
174 """
175 Returns the code that was the input for the parser for this node.
177 :param bool include_prefix: Include the leading prefix (whitespace and
178 comments) of this node or leaf. If False, omit only that prefix;
179 prefixes within a node are preserved.
180 """
182 def search_ancestor(self, *node_types: str) -> 'Optional[BaseNode]':
183 """
184 Recursively looks at the parents of this node or leaf and returns the
185 first found node that matches ``node_types``. Returns ``None`` if no
186 matching node is found.
188 :param node_types: type names that are searched for.
189 """
190 node = self.parent
191 while node is not None:
192 if node.type in node_types:
193 return node
194 node = node.parent
195 return None
197 def dump(self, *, indent: Optional[Union[int, str]] = 4) -> str:
198 """
199 Returns a formatted dump of the parser tree rooted at this node or leaf. This is
200 mainly useful for debugging purposes.
202 The ``indent`` parameter is interpreted in a similar way as :py:func:`ast.dump`.
203 If ``indent`` is a non-negative integer or string, then the tree will be
204 pretty-printed with that indent level. An indent level of 0, negative, or ``""``
205 will only insert newlines. ``None`` selects the single line representation.
206 Using a positive integer indent indents that many spaces per level. If
207 ``indent`` is a string (such as ``"\\t"``), that string is used to indent each
208 level.
210 :param indent: Indentation style as described above. The default indentation is
211 4 spaces, which yields a pretty-printed dump.
213 >>> import parso
214 >>> print(parso.parse("lambda x, y: x + y").dump())
215 Module([
216 Lambda([
217 Keyword('lambda', (1, 0)),
218 Param([
219 Name('x', (1, 7), prefix=' '),
220 Operator(',', (1, 8)),
221 ]),
222 Param([
223 Name('y', (1, 10), prefix=' '),
224 ]),
225 Operator(':', (1, 11)),
226 PythonNode('arith_expr', [
227 Name('x', (1, 13), prefix=' '),
228 Operator('+', (1, 15), prefix=' '),
229 Name('y', (1, 17), prefix=' '),
230 ]),
231 ]),
232 EndMarker('', (1, 18)),
233 ])
234 """
235 if indent is None:
236 newline = False
237 indent_string = ''
238 elif isinstance(indent, int):
239 newline = True
240 indent_string = ' ' * indent
241 elif isinstance(indent, str):
242 newline = True
243 indent_string = indent
244 else:
245 raise TypeError(f"expect 'indent' to be int, str or None, got {indent!r}")
247 def _format_dump(node: NodeOrLeaf, indent: str = '', top_level: bool = True) -> str:
248 result = ''
249 node_type = type(node).__name__
250 if isinstance(node, Leaf):
251 result += f'{indent}{node_type}('
252 if isinstance(node, ErrorLeaf):
253 result += f'{node.token_type!r}, '
254 elif isinstance(node, TypedLeaf):
255 result += f'{node.type!r}, '
256 result += f'{node.value!r}, {node.start_pos!r}'
257 if node.prefix:
258 result += f', prefix={node.prefix!r}'
259 result += ')'
260 elif isinstance(node, BaseNode):
261 result += f'{indent}{node_type}('
262 if isinstance(node, Node):
263 result += f'{node.type!r}, '
264 result += '['
265 if newline:
266 result += '\n'
267 for child in node.children:
268 result += _format_dump(child, indent=indent + indent_string, top_level=False)
269 result += f'{indent}])'
270 else: # pragma: no cover
271 # We shouldn't ever reach here, unless:
272 # - `NodeOrLeaf` is incorrectly subclassed else where
273 # - or a node's children list contains invalid nodes or leafs
274 # Both are unexpected internal errors.
275 raise TypeError(f'unsupported node encountered: {node!r}')
276 if not top_level:
277 if newline:
278 result += ',\n'
279 else:
280 result += ', '
281 return result
283 return _format_dump(self)
286class Leaf(NodeOrLeaf):
287 '''
288 Leafs are basically tokens with a better API. Leafs exactly know where they
289 were defined and what text preceeds them.
290 '''
291 __slots__ = ('value', 'line', 'column', 'prefix')
292 prefix: str
294 def __init__(self, value: str, start_pos: Tuple[int, int], prefix: str = '') -> None:
295 self.value = value
296 '''
297 :py:func:`str` The value of the current token.
298 '''
299 self.start_pos = start_pos
300 self.prefix = prefix
301 '''
302 :py:func:`str` Typically a mixture of whitespace and comments. Stuff
303 that is syntactically irrelevant for the syntax tree.
304 '''
305 self.parent: Optional[BaseNode] = None
306 '''
307 The parent :class:`BaseNode` of this leaf.
308 '''
310 @property
311 def start_pos(self) -> Tuple[int, int]:
312 return self.line, self.column
314 @start_pos.setter
315 def start_pos(self, value: Tuple[int, int]) -> None:
316 self.line = value[0]
317 self.column = value[1]
319 def get_start_pos_of_prefix(self):
320 previous_leaf = self.get_previous_leaf()
321 if previous_leaf is None:
322 lines = split_lines(self.prefix)
323 # + 1 is needed because split_lines always returns at least [''].
324 return self.line - len(lines) + 1, 0 # It's the first leaf.
325 return previous_leaf.end_pos
327 def get_first_leaf(self):
328 return self
330 def get_last_leaf(self):
331 return self
333 def get_code(self, include_prefix=True):
334 if include_prefix:
335 return self.prefix + self.value
336 else:
337 return self.value
339 @property
340 def end_pos(self) -> Tuple[int, int]:
341 lines = split_lines(self.value)
342 end_pos_line = self.line + len(lines) - 1
343 # Check for multiline token
344 if self.line == end_pos_line:
345 end_pos_column = self.column + len(lines[-1])
346 else:
347 end_pos_column = len(lines[-1])
348 return end_pos_line, end_pos_column
350 def __repr__(self):
351 value = self.value
352 if not value:
353 value = self.type
354 return "<%s: %s>" % (type(self).__name__, value)
357class TypedLeaf(Leaf):
358 __slots__ = ('type',)
360 def __init__(self, type, value, start_pos, prefix=''):
361 super().__init__(value, start_pos, prefix)
362 self.type = type
365class BaseNode(NodeOrLeaf):
366 """
367 The super class for all nodes.
368 A node has children, a type and possibly a parent node.
369 """
370 __slots__ = ('children',)
372 def __init__(self, children) -> None:
373 self.children = children
374 """
375 A list of :class:`NodeOrLeaf` child nodes.
376 """
377 self.parent: Optional[BaseNode] = None
378 '''
379 The parent :class:`BaseNode` of this node.
380 None if this is the root node.
381 '''
382 for child in children:
383 child.parent = self
385 @property
386 def start_pos(self) -> Tuple[int, int]:
387 return self.children[0].start_pos
389 def get_start_pos_of_prefix(self):
390 return self.children[0].get_start_pos_of_prefix()
392 @property
393 def end_pos(self) -> Tuple[int, int]:
394 return self.children[-1].end_pos
396 def _get_code_for_children(self, children, include_prefix):
397 if include_prefix:
398 return "".join(c.get_code() for c in children)
399 else:
400 first = children[0].get_code(include_prefix=False)
401 return first + "".join(c.get_code() for c in children[1:])
403 def get_code(self, include_prefix=True):
404 return self._get_code_for_children(self.children, include_prefix)
406 def get_leaf_for_position(self, position, include_prefixes=False):
407 """
408 Get the :py:class:`parso.tree.Leaf` at ``position``
410 :param tuple position: A position tuple, row, column. Rows start from 1
411 :param bool include_prefixes: If ``False``, ``None`` will be returned if ``position`` falls
412 on whitespace or comments before a leaf
413 :return: :py:class:`parso.tree.Leaf` at ``position``, or ``None``
414 """
415 def binary_search(lower, upper):
416 if lower == upper:
417 element = self.children[lower]
418 if not include_prefixes and position < element.start_pos:
419 # We're on a prefix.
420 return None
421 # In case we have prefixes, a leaf always matches
422 try:
423 return element.get_leaf_for_position(position, include_prefixes)
424 except AttributeError:
425 return element
427 index = int((lower + upper) / 2)
428 element = self.children[index]
429 if position <= element.end_pos:
430 return binary_search(lower, index)
431 else:
432 return binary_search(index + 1, upper)
434 if not ((1, 0) <= position <= self.children[-1].end_pos):
435 raise ValueError('Please provide a position that exists within this node.')
436 return binary_search(0, len(self.children) - 1)
438 def get_first_leaf(self):
439 return self.children[0].get_first_leaf()
441 def get_last_leaf(self):
442 return self.children[-1].get_last_leaf()
444 def __repr__(self):
445 code = self.get_code().replace('\n', ' ').replace('\r', ' ').strip()
446 return "<%s: %s@%s,%s>" % \
447 (type(self).__name__, code, self.start_pos[0], self.start_pos[1])
450class Node(BaseNode):
451 """Concrete implementation for interior nodes."""
452 __slots__ = ('type',)
454 def __init__(self, type, children):
455 super().__init__(children)
456 self.type = type
458 def __repr__(self):
459 return "%s(%s, %r)" % (self.__class__.__name__, self.type, self.children)
462class ErrorNode(BaseNode):
463 """
464 A node that contains valid nodes/leaves that we're follow by a token that
465 was invalid. This basically means that the leaf after this node is where
466 Python would mark a syntax error.
467 """
468 __slots__ = ()
469 type = 'error_node'
472class ErrorLeaf(Leaf):
473 """
474 A leaf that is either completely invalid in a language (like `$` in Python)
475 or is invalid at that position. Like the star in `1 +* 1`.
476 """
477 __slots__ = ('token_type',)
478 type = 'error_leaf'
480 def __init__(self, token_type, value, start_pos, prefix=''):
481 super().__init__(value, start_pos, prefix)
482 self.token_type = token_type
484 def __repr__(self):
485 return "<%s: %s:%s, %s>" % \
486 (type(self).__name__, self.token_type, repr(self.value), self.start_pos)