Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/parso/tree.py: 69%

242 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 07:36 +0000

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 n = node.parent 

18 while n is not None: 

19 if n.type in node_types: 

20 return n 

21 n = n.parent 

22 return None 

23 

24 

25class NodeOrLeaf: 

26 """ 

27 The base class for nodes and leaves. 

28 """ 

29 __slots__ = ('parent',) 

30 type: str 

31 ''' 

32 The type is a string that typically matches the types of the grammar file. 

33 ''' 

34 parent: 'Optional[BaseNode]' 

35 ''' 

36 The parent :class:`BaseNode` of this node or leaf. 

37 None if this is the root node. 

38 ''' 

39 

40 def get_root_node(self): 

41 """ 

42 Returns the root node of a parser tree. The returned node doesn't have 

43 a parent node like all the other nodes/leaves. 

44 """ 

45 scope = self 

46 while scope.parent is not None: 

47 scope = scope.parent 

48 return scope 

49 

50 def get_next_sibling(self): 

51 """ 

52 Returns the node immediately following this node in this parent's 

53 children list. If this node does not have a next sibling, it is None 

54 """ 

55 parent = self.parent 

56 if parent is None: 

57 return None 

58 

59 # Can't use index(); we need to test by identity 

60 for i, child in enumerate(parent.children): 

61 if child is self: 

62 try: 

63 return self.parent.children[i + 1] 

64 except IndexError: 

65 return None 

66 

67 def get_previous_sibling(self): 

68 """ 

69 Returns the node immediately preceding this node in this parent's 

70 children list. If this node does not have a previous sibling, it is 

71 None. 

72 """ 

73 parent = self.parent 

74 if parent is None: 

75 return None 

76 

77 # Can't use index(); we need to test by identity 

78 for i, child in enumerate(parent.children): 

79 if child is self: 

80 if i == 0: 

81 return None 

82 return self.parent.children[i - 1] 

83 

84 def get_previous_leaf(self): 

85 """ 

86 Returns the previous leaf in the parser tree. 

87 Returns `None` if this is the first element in the parser tree. 

88 """ 

89 if self.parent is None: 

90 return None 

91 

92 node = self 

93 while True: 

94 c = node.parent.children 

95 i = c.index(node) 

96 if i == 0: 

97 node = node.parent 

98 if node.parent is None: 

99 return None 

100 else: 

101 node = c[i - 1] 

102 break 

103 

104 while True: 

105 try: 

106 node = node.children[-1] 

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

108 return node 

109 

110 def get_next_leaf(self): 

111 """ 

112 Returns the next leaf in the parser tree. 

113 Returns None if this is the last element in the parser tree. 

114 """ 

115 if self.parent is None: 

116 return None 

117 

118 node = self 

119 while True: 

120 c = node.parent.children 

121 i = c.index(node) 

122 if i == len(c) - 1: 

123 node = node.parent 

124 if node.parent is None: 

125 return None 

126 else: 

127 node = c[i + 1] 

128 break 

129 

130 while True: 

131 try: 

132 node = node.children[0] 

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

134 return node 

135 

136 @abstractproperty 

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

138 """ 

139 Returns the starting position of the prefix as a tuple, e.g. `(3, 4)`. 

140 

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

142 """ 

143 

144 @abstractproperty 

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

146 """ 

147 Returns the end position of the prefix as a tuple, e.g. `(3, 4)`. 

148 

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

150 """ 

151 

152 @abstractmethod 

153 def get_start_pos_of_prefix(self): 

154 """ 

155 Returns the start_pos of the prefix. This means basically it returns 

156 the end_pos of the last prefix. The `get_start_pos_of_prefix()` of the 

157 prefix `+` in `2 + 1` would be `(1, 1)`, while the start_pos is 

158 `(1, 2)`. 

159 

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

161 """ 

162 

163 @abstractmethod 

164 def get_first_leaf(self): 

165 """ 

166 Returns the first leaf of a node or itself if this is a leaf. 

167 """ 

168 

169 @abstractmethod 

170 def get_last_leaf(self): 

171 """ 

172 Returns the last leaf of a node or itself if this is a leaf. 

173 """ 

174 

175 @abstractmethod 

176 def get_code(self, include_prefix=True): 

177 """ 

178 Returns the code that was the input for the parser for this node. 

179 

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

181 e.g. a statement. 

182 """ 

183 

184 def search_ancestor(self, *node_types: str) -> 'Optional[BaseNode]': 

185 """ 

186 Recursively looks at the parents of this node or leaf and returns the 

187 first found node that matches ``node_types``. Returns ``None`` if no 

188 matching node is found. 

189 

190 :param node_types: type names that are searched for. 

191 """ 

192 node = self.parent 

193 while node is not None: 

194 if node.type in node_types: 

195 return node 

196 node = node.parent 

197 return None 

198 

199 def dump(self, *, indent: Optional[Union[int, str]] = 4) -> str: 

200 """ 

201 Returns a formatted dump of the parser tree rooted at this node or leaf. This is 

202 mainly useful for debugging purposes. 

203 

204 The ``indent`` parameter is interpreted in a similar way as :py:func:`ast.dump`. 

205 If ``indent`` is a non-negative integer or string, then the tree will be 

206 pretty-printed with that indent level. An indent level of 0, negative, or ``""`` 

207 will only insert newlines. ``None`` selects the single line representation. 

208 Using a positive integer indent indents that many spaces per level. If 

209 ``indent`` is a string (such as ``"\\t"``), that string is used to indent each 

210 level. 

211 

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

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

214 

215 >>> import parso 

216 >>> print(parso.parse("lambda x, y: x + y").dump()) 

217 Module([ 

218 Lambda([ 

219 Keyword('lambda', (1, 0)), 

220 Param([ 

221 Name('x', (1, 7), prefix=' '), 

222 Operator(',', (1, 8)), 

223 ]), 

224 Param([ 

225 Name('y', (1, 10), prefix=' '), 

226 ]), 

227 Operator(':', (1, 11)), 

228 PythonNode('arith_expr', [ 

229 Name('x', (1, 13), prefix=' '), 

230 Operator('+', (1, 15), prefix=' '), 

231 Name('y', (1, 17), prefix=' '), 

232 ]), 

233 ]), 

234 EndMarker('', (1, 18)), 

235 ]) 

236 """ 

237 if indent is None: 

238 newline = False 

239 indent_string = '' 

240 elif isinstance(indent, int): 

241 newline = True 

242 indent_string = ' ' * indent 

243 elif isinstance(indent, str): 

244 newline = True 

245 indent_string = indent 

246 else: 

247 raise TypeError(f"expect 'indent' to be int, str or None, got {indent!r}") 

248 

249 def _format_dump(node: NodeOrLeaf, indent: str = '', top_level: bool = True) -> str: 

250 result = '' 

251 node_type = type(node).__name__ 

252 if isinstance(node, Leaf): 

253 result += f'{indent}{node_type}(' 

254 if isinstance(node, ErrorLeaf): 

255 result += f'{node.token_type!r}, ' 

256 elif isinstance(node, TypedLeaf): 

257 result += f'{node.type!r}, ' 

258 result += f'{node.value!r}, {node.start_pos!r}' 

259 if node.prefix: 

260 result += f', prefix={node.prefix!r}' 

261 result += ')' 

262 elif isinstance(node, BaseNode): 

263 result += f'{indent}{node_type}(' 

264 if isinstance(node, Node): 

265 result += f'{node.type!r}, ' 

266 result += '[' 

267 if newline: 

268 result += '\n' 

269 for child in node.children: 

270 result += _format_dump(child, indent=indent + indent_string, top_level=False) 

271 result += f'{indent}])' 

272 else: # pragma: no cover 

273 # We shouldn't ever reach here, unless: 

274 # - `NodeOrLeaf` is incorrectly subclassed else where 

275 # - or a node's children list contains invalid nodes or leafs 

276 # Both are unexpected internal errors. 

277 raise TypeError(f'unsupported node encountered: {node!r}') 

278 if not top_level: 

279 if newline: 

280 result += ',\n' 

281 else: 

282 result += ', ' 

283 return result 

284 

285 return _format_dump(self) 

286 

287 

288class Leaf(NodeOrLeaf): 

289 ''' 

290 Leafs are basically tokens with a better API. Leafs exactly know where they 

291 were defined and what text preceeds them. 

292 ''' 

293 __slots__ = ('value', 'line', 'column', 'prefix') 

294 prefix: str 

295 

296 def __init__(self, value: str, start_pos: Tuple[int, int], prefix: str = '') -> None: 

297 self.value = value 

298 ''' 

299 :py:func:`str` The value of the current token. 

300 ''' 

301 self.start_pos = start_pos 

302 self.prefix = prefix 

303 ''' 

304 :py:func:`str` Typically a mixture of whitespace and comments. Stuff 

305 that is syntactically irrelevant for the syntax tree. 

306 ''' 

307 self.parent: Optional[BaseNode] = None 

308 ''' 

309 The parent :class:`BaseNode` of this leaf. 

310 ''' 

311 

312 @property 

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

314 return self.line, self.column 

315 

316 @start_pos.setter 

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

318 self.line = value[0] 

319 self.column = value[1] 

320 

321 def get_start_pos_of_prefix(self): 

322 previous_leaf = self.get_previous_leaf() 

323 if previous_leaf is None: 

324 lines = split_lines(self.prefix) 

325 # + 1 is needed because split_lines always returns at least ['']. 

326 return self.line - len(lines) + 1, 0 # It's the first leaf. 

327 return previous_leaf.end_pos 

328 

329 def get_first_leaf(self): 

330 return self 

331 

332 def get_last_leaf(self): 

333 return self 

334 

335 def get_code(self, include_prefix=True): 

336 if include_prefix: 

337 return self.prefix + self.value 

338 else: 

339 return self.value 

340 

341 @property 

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

343 lines = split_lines(self.value) 

344 end_pos_line = self.line + len(lines) - 1 

345 # Check for multiline token 

346 if self.line == end_pos_line: 

347 end_pos_column = self.column + len(lines[-1]) 

348 else: 

349 end_pos_column = len(lines[-1]) 

350 return end_pos_line, end_pos_column 

351 

352 def __repr__(self): 

353 value = self.value 

354 if not value: 

355 value = self.type 

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

357 

358 

359class TypedLeaf(Leaf): 

360 __slots__ = ('type',) 

361 

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

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

364 self.type = type 

365 

366 

367class BaseNode(NodeOrLeaf): 

368 """ 

369 The super class for all nodes. 

370 A node has children, a type and possibly a parent node. 

371 """ 

372 __slots__ = ('children',) 

373 

374 def __init__(self, children: List[NodeOrLeaf]) -> None: 

375 self.children = children 

376 """ 

377 A list of :class:`NodeOrLeaf` child nodes. 

378 """ 

379 self.parent: Optional[BaseNode] = None 

380 ''' 

381 The parent :class:`BaseNode` of this node. 

382 None if this is the root node. 

383 ''' 

384 for child in children: 

385 child.parent = self 

386 

387 @property 

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

389 return self.children[0].start_pos 

390 

391 def get_start_pos_of_prefix(self): 

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

393 

394 @property 

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

396 return self.children[-1].end_pos 

397 

398 def _get_code_for_children(self, children, include_prefix): 

399 if include_prefix: 

400 return "".join(c.get_code() for c in children) 

401 else: 

402 first = children[0].get_code(include_prefix=False) 

403 return first + "".join(c.get_code() for c in children[1:]) 

404 

405 def get_code(self, include_prefix=True): 

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

407 

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

409 """ 

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

411 

412 :param tuple position: A position tuple, row, column. Rows start from 1 

413 :param bool include_prefixes: If ``False``, ``None`` will be returned if ``position`` falls 

414 on whitespace or comments before a leaf 

415 :return: :py:class:`parso.tree.Leaf` at ``position``, or ``None`` 

416 """ 

417 def binary_search(lower, upper): 

418 if lower == upper: 

419 element = self.children[lower] 

420 if not include_prefixes and position < element.start_pos: 

421 # We're on a prefix. 

422 return None 

423 # In case we have prefixes, a leaf always matches 

424 try: 

425 return element.get_leaf_for_position(position, include_prefixes) 

426 except AttributeError: 

427 return element 

428 

429 index = int((lower + upper) / 2) 

430 element = self.children[index] 

431 if position <= element.end_pos: 

432 return binary_search(lower, index) 

433 else: 

434 return binary_search(index + 1, upper) 

435 

436 if not ((1, 0) <= position <= self.children[-1].end_pos): 

437 raise ValueError('Please provide a position that exists within this node.') 

438 return binary_search(0, len(self.children) - 1) 

439 

440 def get_first_leaf(self): 

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

442 

443 def get_last_leaf(self): 

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

445 

446 def __repr__(self): 

447 code = self.get_code().replace('\n', ' ').replace('\r', ' ').strip() 

448 return "<%s: %s@%s,%s>" % \ 

449 (type(self).__name__, code, self.start_pos[0], self.start_pos[1]) 

450 

451 

452class Node(BaseNode): 

453 """Concrete implementation for interior nodes.""" 

454 __slots__ = ('type',) 

455 

456 def __init__(self, type, children): 

457 super().__init__(children) 

458 self.type = type 

459 

460 def __repr__(self): 

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

462 

463 

464class ErrorNode(BaseNode): 

465 """ 

466 A node that contains valid nodes/leaves that we're follow by a token that 

467 was invalid. This basically means that the leaf after this node is where 

468 Python would mark a syntax error. 

469 """ 

470 __slots__ = () 

471 type = 'error_node' 

472 

473 

474class ErrorLeaf(Leaf): 

475 """ 

476 A leaf that is either completely invalid in a language (like `$` in Python) 

477 or is invalid at that position. Like the star in `1 +* 1`. 

478 """ 

479 __slots__ = ('token_type',) 

480 type = 'error_leaf' 

481 

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

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

484 self.token_type = token_type 

485 

486 def __repr__(self): 

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

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