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

247 statements  

1from abc import abstractmethod, abstractproperty 

2from typing import List, Optional, Tuple, Union 

3 

4from parso.utils import split_lines 

5 

6 

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. 

11 

12 This function is deprecated, use :meth:`NodeOrLeaf.search_ancestor` instead. 

13 

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) 

18 

19 

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 ''' 

34 

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 

44 

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 

53 

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 

61 

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 

71 

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] 

78 

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 

86 

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 

98 

99 while True: 

100 try: 

101 node = node.children[-1] 

102 except AttributeError: # A Leaf doesn't have children. 

103 return node 

104 

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 

112 

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 

124 

125 while True: 

126 try: 

127 node = node.children[0] 

128 except AttributeError: # A Leaf doesn't have children. 

129 return node 

130 

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)`. 

135 

136 :return tuple of int: (line, column) 

137 """ 

138 

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)`. 

143 

144 :return tuple of int: (line, column) 

145 """ 

146 

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)`. 

154 

155 :return tuple of int: (line, column) 

156 """ 

157 

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 """ 

163 

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 """ 

169 

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. 

174 

175 :param include_prefix: Removes the prefix (whitespace and comments) of 

176 e.g. a statement. 

177 """ 

178 

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. 

184 

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 

193 

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. 

198 

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. 

206 

207 :param indent: Indentation style as described above. The default indentation is 

208 4 spaces, which yields a pretty-printed dump. 

209 

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}") 

243 

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 

279 

280 return _format_dump(self) 

281 

282 

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 

290 

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 ''' 

306 

307 @property 

308 def start_pos(self) -> Tuple[int, int]: 

309 return self.line, self.column 

310 

311 @start_pos.setter 

312 def start_pos(self, value: Tuple[int, int]) -> None: 

313 self.line = value[0] 

314 self.column = value[1] 

315 

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 

323 

324 def get_first_leaf(self): 

325 return self 

326 

327 def get_last_leaf(self): 

328 return self 

329 

330 def get_code(self, include_prefix=True): 

331 if include_prefix: 

332 return self.prefix + self.value 

333 else: 

334 return self.value 

335 

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 

346 

347 def __repr__(self): 

348 value = self.value 

349 if not value: 

350 value = self.type 

351 return "<%s: %s>" % (type(self).__name__, value) 

352 

353 

354class TypedLeaf(Leaf): 

355 __slots__ = ('type',) 

356 

357 def __init__(self, type, value, start_pos, prefix=''): 

358 super().__init__(value, start_pos, prefix) 

359 self.type = type 

360 

361 

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',) 

368 

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 

381 

382 @property 

383 def start_pos(self) -> Tuple[int, int]: 

384 return self.children[0].start_pos 

385 

386 def get_start_pos_of_prefix(self): 

387 return self.children[0].get_start_pos_of_prefix() 

388 

389 @property 

390 def end_pos(self) -> Tuple[int, int]: 

391 return self.children[-1].end_pos 

392 

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:]) 

399 

400 def get_code(self, include_prefix=True): 

401 return self._get_code_for_children(self.children, include_prefix) 

402 

403 def get_leaf_for_position(self, position, include_prefixes=False): 

404 """ 

405 Get the :py:class:`parso.tree.Leaf` at ``position`` 

406 

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 

423 

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) 

430 

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) 

434 

435 def get_first_leaf(self): 

436 return self.children[0].get_first_leaf() 

437 

438 def get_last_leaf(self): 

439 return self.children[-1].get_last_leaf() 

440 

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]) 

445 

446 

447class Node(BaseNode): 

448 """Concrete implementation for interior nodes.""" 

449 __slots__ = ('type',) 

450 

451 def __init__(self, type, children): 

452 super().__init__(children) 

453 self.type = type 

454 

455 def __repr__(self): 

456 return "%s(%s, %r)" % (self.__class__.__name__, self.type, self.children) 

457 

458 

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' 

467 

468 

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' 

476 

477 def __init__(self, token_type, value, start_pos, prefix=''): 

478 super().__init__(value, start_pos, prefix) 

479 self.token_type = token_type 

480 

481 def __repr__(self): 

482 return "<%s: %s:%s, %s>" % \ 

483 (type(self).__name__, self.token_type, repr(self.value), self.start_pos)