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

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 this node or leaf, excluding its 

135 prefix, as a tuple, e.g. `(3, 4)`. 

136 

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

138 """ 

139 

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

145 

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

147 """ 

148 

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

156 

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

158 """ 

159 

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

165 

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

171 

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. 

176 

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

181 

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. 

187 

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 

196 

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. 

201 

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. 

209 

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

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

212 

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

246 

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 

282 

283 return _format_dump(self) 

284 

285 

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 

293 

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

309 

310 @property 

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

312 return self.line, self.column 

313 

314 @start_pos.setter 

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

316 self.line = value[0] 

317 self.column = value[1] 

318 

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 

326 

327 def get_first_leaf(self): 

328 return self 

329 

330 def get_last_leaf(self): 

331 return self 

332 

333 def get_code(self, include_prefix=True): 

334 if include_prefix: 

335 return self.prefix + self.value 

336 else: 

337 return self.value 

338 

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 

349 

350 def __repr__(self): 

351 value = self.value 

352 if not value: 

353 value = self.type 

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

355 

356 

357class TypedLeaf(Leaf): 

358 __slots__ = ('type',) 

359 

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

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

362 self.type = type 

363 

364 

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

371 

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 

384 

385 @property 

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

387 return self.children[0].start_pos 

388 

389 def get_start_pos_of_prefix(self): 

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

391 

392 @property 

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

394 return self.children[-1].end_pos 

395 

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

402 

403 def get_code(self, include_prefix=True): 

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

405 

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

407 """ 

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

409 

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 

426 

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) 

433 

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) 

437 

438 def get_first_leaf(self): 

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

440 

441 def get_last_leaf(self): 

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

443 

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

448 

449 

450class Node(BaseNode): 

451 """Concrete implementation for interior nodes.""" 

452 __slots__ = ('type',) 

453 

454 def __init__(self, type, children): 

455 super().__init__(children) 

456 self.type = type 

457 

458 def __repr__(self): 

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

460 

461 

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' 

470 

471 

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' 

479 

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

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

482 self.token_type = token_type 

483 

484 def __repr__(self): 

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

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