FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

GitHub Viewer

""" This is the syntax tree for Python syntaxes (2 & 3). The classes represent syntax elements like functions and imports. All of the nodes can be traced back to the `Python grammar file `_. If you want to know how a tree is structured, just analyse that file (for each Python version it's a bit different). There's a lot of logic here that makes it easier for Jedi (and other libraries) to deal with a Python syntax tree. By using :py:meth:`parso.tree.NodeOrLeaf.get_code` on a module, you can get back the 1-to-1 representation of the input given to the parser. This is important if you want to refactor a parser tree. >>> from parso import parse >>> parser = parse('import os') >>> module = parser.get_root_node() >>> module Any subclasses of :class:`Scope`, including :class:`Module` has an attribute :attr:`iter_imports `: >>> list(module.iter_imports()) [] Changes to the Python Grammar ----------------------------- A few things have changed when looking at Python grammar files: - :class:`Param` does not exist in Python grammar files. It is essentially a part of a ``parameters`` node. |parso| splits it up to make it easier to analyse parameters. However this just makes it easier to deal with the syntax tree, it doesn't actually change the valid syntax. - A few nodes like `lambdef` and `lambdef_nocond` have been merged in the syntax tree to make it easier to do deal with them. Parser Tree Classes ------------------- """ import re from parso._compatibility import utf8_repr, unicode from parso.tree import Node, BaseNode, Leaf, ErrorNode, ErrorLeaf, \ search_ancestor from parso.python.prefix import split_prefix _FLOW_CONTAINERS = set(['if_stmt', 'while_stmt', 'for_stmt', 'try_stmt', 'with_stmt', 'async_stmt', 'suite']) _RETURN_STMT_CONTAINERS = set(['suite', 'simple_stmt']) | _FLOW_CONTAINERS _FUNC_CONTAINERS = set(['suite', 'simple_stmt', 'decorated']) | _FLOW_CONTAINERS _GET_DEFINITION_TYPES = set([ 'expr_stmt', 'comp_for', 'with_stmt', 'for_stmt', 'import_name', 'import_from', 'param' ]) _IMPORTS = set(['import_name', 'import_from']) class DocstringMixin(object): __slots__ = () def get_doc_node(self): """ Returns the string leaf of a docstring. e.g. ``r'''foo'''``. """ if self.type == 'file_input': node = self.children[0] elif self.type in ('funcdef', 'classdef'): node = self.children[self.children.index(':') + 1] if node.type == 'suite': # Normally a suite node = node.children[1] # -> NEWLINE stmt else: # ExprStmt simple_stmt = self.parent c = simple_stmt.parent.children index = c.index(simple_stmt) if not index: return None node = c[index - 1] if node.type == 'simple_stmt': node = node.children[0] if node.type == 'string': return node return None class PythonMixin(object): """ Some Python specific utitilies. """ __slots__ = () def get_name_of_position(self, position): """ Given a (line, column) tuple, returns a :py:class:`Name` or ``None`` if there is no name at that position. """ for c in self.children: if isinstance(c, Leaf): if c.type == 'name' and c.start_pos c.start_pos: return True else: return False class WhileStmt(Flow): type = 'while_stmt' __slots__ = () class ForStmt(Flow): type = 'for_stmt' __slots__ = () def get_testlist(self): """ Returns the input node ``y`` from: ``for x in y:``. """ return self.children[3] def get_defined_names(self): return _defined_names(self.children[1]) class TryStmt(Flow): type = 'try_stmt' __slots__ = () def get_except_clause_tests(self): """ Returns the ``test`` nodes found in ``except_clause`` nodes. Returns ``[None]`` for except clauses without an exception given. """ for node in self.children: if node.type == 'except_clause': yield node.children[1] elif node == 'except': yield None class WithStmt(Flow): type = 'with_stmt' __slots__ = () def get_defined_names(self): """ Returns the a list of `Name` that the with statement defines. The defined names are set after `as`. """ names = [] for with_item in self.children[1:-2:2]: # Check with items for 'as' names. if with_item.type == 'with_item': names += _defined_names(with_item.children[2]) return names def get_test_node_from_name(self, name): node = name.parent if node.type != 'with_item': raise ValueError('The name is not actually part of a with statement.') return node.children[0] class Import(PythonBaseNode): __slots__ = () def get_path_for_name(self, name): """ The path is the list of names that leads to the searched name. :return list of Name: """ try: # The name may be an alias. If it is, just map it back to the name. name = self._aliases()[name] except KeyError: pass for path in self.get_paths(): if name in path: return path[:path.index(name) + 1] raise ValueError('Name should be defined in the import itself') def is_nested(self): return False # By default, sub classes may overwrite this behavior def is_star_import(self): return self.children[-1] == '*' class ImportFrom(Import): type = 'import_from' __slots__ = () def get_defined_names(self): """ Returns the a list of `Name` that the import defines. The defined names are set after `import` or in case an alias - `as` - is present that name is returned. """ return [alias or name for name, alias in self._as_name_tuples()] def _aliases(self): """Mapping from alias to its corresponding name.""" return dict((alias, name) for name, alias in self._as_name_tuples() if alias is not None) def get_from_names(self): for n in self.children[1:]: if n not in ('.', '...'): break if n.type == 'dotted_name': # from x.y import return n.children[::2] elif n == 'import': # from . import return [] else: # from x import return [n] @property def level(self): """The level parameter of ``__import__``.""" level = 0 for n in self.children[1:]: if n in ('.', '...'): level += len(n.value) else: break return level def _as_name_tuples(self): last = self.children[-1] if last == ')': last = self.children[-2] elif last == '*': return # No names defined directly. if last.type == 'import_as_names': as_names = last.children[::2] else: as_names = [last] for as_name in as_names: if as_name.type == 'name': yield as_name, None else: yield as_name.children[::2] # yields x, y -> ``x as y`` def get_paths(self): """ The import paths defined in an import statement. Typically an array like this: ``[, ]``. :return list of list of Name: """ dotted = self.get_from_names() if self.children[-1] == '*': return [dotted] return [dotted + [name] for name, alias in self._as_name_tuples()] class ImportName(Import): """For ``import_name`` nodes. Covers normal imports without ``from``.""" type = 'import_name' __slots__ = () def get_defined_names(self): """ Returns the a list of `Name` that the import defines. The defined names is always the first name after `import` or in case an alias - `as` - is present that name is returned. """ return [alias or path[0] for path, alias in self._dotted_as_names()] @property def level(self): """The level parameter of ``__import__``.""" return 0 # Obviously 0 for imports without from. def get_paths(self): return [path for path, alias in self._dotted_as_names()] def _dotted_as_names(self): """Generator of (list(path), alias) where alias may be None.""" dotted_as_names = self.children[1] if dotted_as_names.type == 'dotted_as_names': as_names = dotted_as_names.children[::2] else: as_names = [dotted_as_names] for as_name in as_names: if as_name.type == 'dotted_as_name': alias = as_name.children[2] as_name = as_name.children[0] else: alias = None if as_name.type == 'name': yield [as_name], alias else: # dotted_names yield as_name.children[::2], alias def is_nested(self): """ This checks for the special case of nested imports, without aliases and from statement:: import foo.bar """ return bool([1 for path, alias in self._dotted_as_names() if alias is None and len(path) > 1]) def _aliases(self): """ :return list of Name: Returns all the alias """ return dict((alias, path[-1]) for path, alias in self._dotted_as_names() if alias is not None) class KeywordStatement(PythonBaseNode): """ For the following statements: `assert`, `del`, `global`, `nonlocal`, `raise`, `return`, `yield`, `return`, `yield`. `pass`, `continue` and `break` are not in there, because they are just simple keywords and the parser reduces it to a keyword. """ __slots__ = () @property def type(self): """ Keyword statements start with the keyword and end with `_stmt`. You can crosscheck this with the Python grammar. """ return '%s_stmt' % self.keyword @property def keyword(self): return self.children[0].value class AssertStmt(KeywordStatement): __slots__ = () @property def assertion(self): return self.children[1] class GlobalStmt(KeywordStatement): __slots__ = () def get_global_names(self): return self.children[1::2] class ReturnStmt(KeywordStatement): __slots__ = () class YieldExpr(PythonBaseNode): type = 'yield_expr' __slots__ = () def _defined_names(current): """ A helper function to find the defined names in statements, for loops and list comprehensions. """ names = [] if current.type in ('testlist_star_expr', 'testlist_comp', 'exprlist', 'testlist'): for child in current.children[::2]: names += _defined_names(child) elif current.type in ('atom', 'star_expr'): names += _defined_names(current.children[1]) elif current.type in ('power', 'atom_expr'): if current.children[-2] != '**': # Just if there's no operation trailer = current.children[-1] if trailer.children[0] == '.': names.append(trailer.children[1]) else: names.append(current) return names class ExprStmt(PythonBaseNode, DocstringMixin): type = 'expr_stmt' __slots__ = () def get_defined_names(self): """ Returns a list of `Name` defined before the `=` sign. """ names = [] if self.children[1].type == 'annassign': names = _defined_names(self.children[0]) return [ name for i in range(0, len(self.children) - 2, 2) if '=' in self.children[i + 1].value for name in _defined_names(self.children[i]) ] + names def get_rhs(self): """Returns the right-hand-side of the equals.""" return self.children[-1] def yield_operators(self): """ Returns a generator of `+=`, `=`, etc. or None if there is no operation. """ first = self.children[1] if first.type == 'annassign': if len(first.children)

Back | FazBrowse Home | New Git URL