""" If you know what an abstract syntax tree (ast) is, you'll see that this module is pretty much that. The classes represent syntax elements: ``Import``, ``Function``. A very central class is ``Scope``. It is not used directly by the parser, but inherited. It's used by ``Function``, ``Class``, ``Flow``, etc. A ``Scope`` may have ``subscopes``, ``imports`` and ``statements``. The entire parser is based on scopes, because they also stand for indentation. One special thing: ``Array`` values are statements. But if you think about it, this makes sense. ``[1, 2+33]`` for example would be an Array with two ``Statement`` inside. This is the easiest way to write a parser. The same behaviour applies to ``Param``, which is being used in a function definition. The easiest way to play with this module is to use :class:`parsing.Parser`. :attr:`parsing.Parser.module` holds an instance of :class:`SubModule`: >>> from jedi._compatibility import u >>> from jedi.parser import Parser, load_grammar >>> parser = Parser(load_grammar(), u('import os'), 'example.py') >>> submodule = parser.module >>> submodule Any subclasses of :class:`Scope`, including :class:`SubModule` has attribute :attr:`imports `. This attribute has import statements in this scope. Check this out: >>> submodule.imports [] See also :attr:`Scope.subscopes` and :attr:`Scope.statements`. # TODO New docstring """ import os import re from inspect import cleandoc from itertools import chain import textwrap from jedi._compatibility import (next, Python3Method, encoding, is_py3, literal_eval, use_metaclass, unicode) from jedi import debug from jedi import cache SCOPE_CONTENTS = 'asserts', 'subscopes', 'imports', 'statements', 'returns' def is_node(node, *symbol_names): try: type = node.type except AttributeError: return False else: return type in symbol_names def filter_after_position(names, position): """ Removes all names after a certain position. If position is None, just returns the names list. """ if position is None: return names names_new = [] for n in names: if n.start_pos[0] is not None and n.start_pos < position: names_new.append(n) return names_new class DocstringMixin(object): __slots__ = () @property def raw_doc(self): """ Returns a cleaned version of the docstring token. """ if isinstance(self, SubModule): stmt = self.children[0] else: stmt = self.children[-1] if is_node(stmt, 'suite'): # Normally a suite stmt = stmt.children[2] # -> NEWLINE INDENT stmt if is_node(stmt, 'simple_stmt'): stmt = stmt.children[0] try: first = stmt.children[0] except AttributeError: pass # Probably a pass Keyword (Leaf). else: if isinstance(first, String): # TODO We have to check next leaves until there are no new # leaves anymore that might be part of the docstring. A # docstring can also look like this: ``'foo' 'bar' # Returns a literal cleaned version of the ``Token``. cleaned = cleandoc(literal_eval(first.value)) # Since we want the docstr output to be always unicode, just # force it. if is_py3 or isinstance(cleaned, unicode): return cleaned else: return unicode(cleaned, 'UTF-8', 'replace') return '' class Base(object): """ This is just here to have an isinstance check, which is also used on evaluate classes. But since they have sometimes a special type of delegation, it is important for those classes to override this method. I know that there is a chance to do such things with __instancecheck__, but since Python 2.5 doesn't support it, I decided to do it this way. """ __slots__ = () def isinstance(self, *cls): return isinstance(self, cls) @Python3Method def get_parent_until(self, classes=(), reverse=False, include_current=True): """ Searches the parent "chain" until the object is an instance of classes. If classes is empty return the last parent in the chain (is without a parent). """ if type(classes) not in (tuple, list): classes = (classes,) scope = self if include_current else self.parent while scope.parent is not None: # TODO why if classes? if classes and reverse != scope.isinstance(*classes): break scope = scope.parent return scope def get_parent_scope(self, include_flows=False): """ Returns the underlying scope. """ scope = self.parent while scope.parent is not None: if include_flows and isinstance(scope, Flow): return scope if scope.is_scope(): break scope = scope.parent return scope def is_scope(self): # Default is not being a scope. Just inherit from Scope. return False class Leaf(Base): __slots__ = ('value', 'parent', 'start_pos', 'prefix') def __init__(self, value, start_pos, prefix=''): self.value = value self.start_pos = start_pos self.prefix = prefix self.parent = None @property def end_pos(self): return self.start_pos[0], self.start_pos[1] + len(self.value) def get_code(self): return self.prefix + self.value def next_sibling(self): """ The node immediately following the invocant in their parent's children list. If the invocant does not have a next sibling, it is None """ # Can't use index(); we need to test by identity for i, child in enumerate(self.parent.children): if child is self: try: return self.parent.children[i + 1] except IndexError: return None def prev_sibling(self): """ The node immediately preceding the invocant in their parent's children list. If the invocant does not have a previous sibling, it is None. """ # Can't use index(); we need to test by identity for i, child in enumerate(self.parent.children): if child is self: if i == 0: return None return self.parent.children[i - 1] def __repr__(self): return "<%s: %s>" % (type(self).__name__, repr(self.value)) class LeafWithNewLines(Leaf): @property def end_pos(self): """ Literals and whitespace end_pos are more complicated than normal end_pos, because the containing newlines may change the indexes. """ end_pos_line, end_pos_col = self.start_pos lines = self.value.split('\n') if self.value.endswith('\n'): lines = lines[:-1] lines[-1] += '\n' end_pos_line += len(lines) - 1 # Check for multiline token if self.start_pos[0] == end_pos_line: end_pos_col += len(lines[-1]) else: end_pos_col = len(lines[-1]) return end_pos_line, end_pos_col class Whitespace(LeafWithNewLines): type = 'whitespace' """Contains NEWLINE and ENDMARKER tokens.""" class Name(Leaf): """ A string. Sometimes it is important to know if the string belongs to a name or not. """ type = 'name' def __str__(self): return self.value def __unicode__(self): return self.value def __repr__(self): return "<%s: %s@%s,%s>" % (type(self).__name__, self.value, self.start_pos[0], self.start_pos[1]) def get_definition(self): scope = self.parent while scope.parent is not None: if scope.isinstance(Node): if scope.type == 'testlist_comp': try: if isinstance(scope.children[1], CompFor): return scope.children[1] except IndexError: pass else: break scope = scope.parent return scope def is_definition(self): stmt = self.get_definition() if isinstance(stmt, (Function, Class, Module)): return self == stmt.name elif isinstance(stmt, ForStmt): return self.start_pos < stmt.children[2].start_pos elif isinstance(stmt, Param): return self == stmt.get_name() elif isinstance(stmt, TryStmt): return self.prev_sibling() == 'as' else: return isinstance(stmt, (ExprStmt, Import, CompFor, WithStmt)) \ and self in stmt.get_defined_names() def assignment_indexes(self): """ Returns an array of ints of the indexes that are used in tuple assignments. For example if the name is ``y`` in the following code:: x, (y, z) = 2, '' would result in ``[1, 0]``. """ indexes = [] node = self.parent compare = self while node is not None: if is_node(node, 'testlist_comp', 'testlist_star_expr', 'exprlist'): for i, child in enumerate(node.children): if child == compare: indexes.insert(0, int(i / 2)) break else: raise LookupError("Couldn't find the assignment.") elif isinstance(node, (ExprStmt, CompFor)): break compare = node node = node.parent return indexes class Literal(LeafWithNewLines): def eval(self): return literal_eval(self.value) def __repr__(self): # TODO remove? """ if is_py3: s = self.literal else: s = self.literal.encode('ascii', 'replace') """ return "<%s: %s>" % (type(self).__name__, self.value) class Number(Literal): type = 'number' class String(Literal): type = 'string' class Operator(Leaf): type = 'operator' def __str__(self): return self.value def __eq__(self, other): """ Make comparisons with strings easy. Improves the readability of the parser. """ if isinstance(other, Operator): return self is other else: return self.value == other def __ne__(self, other): """Python 2 compatibility.""" return self.value != other def __hash__(self): return hash(self.value) class Keyword(Leaf): type = 'keyword' def __eq__(self, other): """ Make comparisons with strings easy. Improves the readability of the parser. """ if isinstance(other, Keyword): return self is other return self.value == other def __ne__(self, other): """Python 2 compatibility.""" return not self.__eq__(other) def __hash__(self): return hash(self.value) class Simple(Base): """ The super class for Scope, Import, Name and Statement. Every object in the parser tree inherits from this class. """ __slots__ = ('children', 'parent') def __init__(self, children): """ Initialize :class:`Simple`. :param children: The module in which this Python object locates. """ for c in children: c.parent = self self.children = children self.parent = None def move(self, line_offset, column_offset): """ Move the Node's start_pos. """ for c in self.children: if isinstance(c, Leaf): c.start_pos = (c.start_pos[0] + line_offset, c.start_pos[1] + column_offset) else: c.move(line_offset, column_offset) @property def start_pos(self): return self.children[0].start_pos @property def _sub_module(self): return self.get_parent_until() @property def end_pos(self): return self.children[-1].end_pos def get_code(self): return "".join(c.get_code() for c in self.children) def name_for_position(self, position): for c in self.children: if isinstance(c, Leaf): if isinstance(c, Name) and c.start_pos <= position <= c.end_pos: return c else: result = c.name_for_position(position) if result is not None: return result return None def first_leaf(self): try: return self.children[0].first_leaf() except AttributeError: return self.children[0] def __repr__(self): code = self.get_code().replace('\n', ' ') if not is_py3: code = code.encode(encoding, 'replace') return "<%s: %s@%s,%s>" % \ (type(self).__name__, code, self.start_pos[0], self.start_pos[1]) class Node(Simple): """Concrete implementation for interior nodes.""" def __init__(self, type, children): """ Initializer. Takes a type constant (a symbol number >= 256), a sequence of child nodes, and an optional context keyword argument. As a side effect, the parent pointers of the children are updated. """ super(Node, self).__init__(children) self.type = type def __repr__(self): return "%s(%s, %r)" % (self.__class__.__name__, self.type, self.children) class IsScopeMeta(type): def __instancecheck__(self, other): return other.is_scope() class IsScope(use_metaclass(IsScopeMeta)): pass def _return_empty_list(): """ Necessary for pickling. It needs to be reachable for pickle, cannot be a lambda or a closure. """ return [] class Scope(Simple, DocstringMixin): """ Super class for the parser tree, which represents the state of a python text file. A Scope manages and owns its subscopes, which are classes and functions, as well as variables and imports. It is used to access the structure of python files. :param start_pos: The position (line and column) of the scope. :type start_pos: tuple(int, int) """ __slots__ = ('_doc_token', 'names_dict') def __init__(self, children): super(Scope, self).__init__(children) self._doc_token = None @property def returns(self): # Needed here for fast_parser, because the fast_parser splits and # returns will be in "normal" modules. return self._search_in_scope(ReturnStmt) @property def asserts(self): # Needed here for fast_parser, because the fast_parser splits and # returns will be in "normal" modules. return self._search_in_scope(AssertStmt) @property def subscopes(self): return self._search_in_scope(Scope) @property def flows(self): return self._search_in_scope(Flow) @property def imports(self): return self._search_in_scope(Import) def _search_in_scope(self, typ): def scan(children): elements = [] for element in children: if isinstance(element, typ): elements.append(element) if is_node(element, 'suite', 'simple_stmt', 'decorated') \ or isinstance(element, Flow): elements += scan(element.children) return elements return scan(self.children) @property def statements(self): return self._search_in_scope((ExprStmt, KeywordStatement)) def is_scope(self): return True def get_imports(self): """ Gets also the imports within flow statements """ raise NotImplementedError return [] i = [] + self.imports for s in self.statements: if isinstance(s, Scope): i += s.get_imports() return i @Python3Method def get_defined_names(self): """ Get all defined names in this scope. Useful for autocompletion. >>> from jedi._compatibility import u >>> from jedi.parser import Parser, load_grammar >>> parser = Parser(load_grammar(), u(''' ... a = x ... b = y ... b.c = z ... ''')) >>> parser.module.get_defined_names() [, , ] """ def scan(children): names = [] for c in children: if is_node(c, 'simple_stmt'): names += chain.from_iterable( [s.get_defined_names() for s in c.children if isinstance(s, (ExprStmt, Import))]) elif isinstance(c, (Function, Class)): names.append(c.name) elif isinstance(c, Flow) or is_node(c, 'suite', 'decorated'): names += scan(c.children) return names children = self.children return scan(children) @Python3Method def get_statement_for_position(self, pos, include_imports=False): checks = self.statements + self.asserts if include_imports: checks += self.imports if self.isinstance(Function): checks += self.get_decorators() checks += [r for r in self.returns if r is not None] for s in checks: if isinstance(s, Flow): p = s.get_statement_for_position(pos, include_imports) while s.next and not p: s = s.next p = s.get_statement_for_position(pos, include_imports) if p: return p elif s.start_pos <= pos <= s.end_pos: return s for s in self.subscopes: if s.start_pos <= pos <= s.end_pos: p = s.get_statement_for_position(pos, include_imports) if p: return p def __repr__(self): try: name = self.path except AttributeError: try: name = self.name except AttributeError: name = self.command return "<%s: %s@%s-%s>" % (type(self).__name__, name, self.start_pos[0], self.end_pos[0]) def walk(self): yield self for s in self.subscopes: for scope in s.walk(): yield scope for r in self.statements: while isinstance(r, Flow): for scope in r.walk(): yield scope r = r.next class Module(Base): """ For isinstance checks. fast_parser.Module also inherits from this. """ def is_scope(self): return True class SubModule(Scope, Module): """ The top scope, which is always a module. Depending on the underlying parser this may be a full module or just a part of a module. """ __slots__ = ('path', 'global_names', 'used_names', 'line_offset', 'use_as_parent', 'failed_statement_stacks') def __init__(self, children): """ Initialize :class:`SubModule`. :type path: str :arg path: File path to this module. .. todo:: Document `top_module`. """ super(SubModule, self).__init__(children) self.path = None # Set later. # this may be changed depending on fast_parser self.line_offset = 0 if 0: self.use_as_parent = top_module or self def set_global_names(self, names): """ Global means in these context a function (subscope) which has a global statement. This is only relevant for the top scope. :param names: names of the global. :type names: list of Name """ self.global_names = names def add_global(self, name): # set no parent here, because globals are not defined in this scope. self.global_vars.append(name) def get_defined_names(self): n = super(SubModule, self).get_defined_names() # TODO uncomment #n += self.global_names return n @property @cache.underscore_memoization def name(self): """ This is used for the goto functions. """ if self.path is None: string = '' # no path -> empty name else: sep = (re.escape(os.path.sep),) * 2 r = re.search(r'([^%s]*?)(%s__init__)?(\.py|\.so)?$' % sep, self.path) # Remove PEP 3149 names string = re.sub('\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1)) # Positions are not real, but a module starts at (1, 0) p = (1, 0) name = Name(string, p) name.parent = self return name @property def has_explicit_absolute_import(self): """ Checks if imports in this module are explicitly absolute, i.e. there is a ``__future__`` import. """ # TODO this is a strange scan and not fully correct. I think Python's # parser does it in a different way and scans for the first # statement/import with a tokenizer (to check for syntax changes like # the future print statement). for imp in self.imports: if isinstance(imp, ImportFrom) and imp.level == 0: for path in imp.paths(): if [str(name) for name in path] == ['__future__', 'absolute_import']: return True return False class Decorator(Simple): type = 'decorator' pass class ClassOrFunc(Scope): __slots__ = () @property def name(self): return self.children[1] def get_decorators(self): decorated = self.parent if is_node(decorated, 'decorated'): if is_node(decorated.children[0], 'decorators'): return decorated.children[0].children else: return decorated.children[:1] else: return [] class Class(ClassOrFunc): """ Used to store the parsed contents of a python class. :param name: The Class name. :type name: str :param supers: The super classes of a Class. :type supers: list :param start_pos: The start position (line, column) of the class. :type start_pos: tuple(int, int) """ type = 'classdef' def __init__(self, children): super(Class, self).__init__(children) def get_super_arglist(self): if len(self.children) == 4: # Has no parentheses return None else: if self.children[3] == ')': # Empty parentheses return None else: return self.children[3] @property def doc(self): """ Return a document string including call signature of __init__. """ docstr = "" if self._doc_token is not None: docstr = self.raw_doc for sub in self.subscopes: if str(sub.name) == '__init__': return '%s\n\n%s' % ( sub.get_call_signature(func_name=self.name), docstr) return docstr def scope_names_generator(self, position=None): yield self, filter_after_position(self.get_defined_names(), position) def _create_params(function, lst): if not lst: return [] if is_node(lst[0], 'typedargslist', 'varargslist'): params = [] iterator = iter(lst[0].children) for n in iterator: stars = 0 if n in ('*', '**'): stars = len(n.value) n = next(iterator) op = next(iterator, None) if op == '=': default = next(iterator) next(iterator, None) else: default = None params.append(Param(n, function, default, stars)) return params else: return [Param(lst[0], function)] class Function(ClassOrFunc): """ Used to store the parsed contents of a python function. :param name: The Function name. :type name: str :param params: The parameters (Statement) of a Function. :type params: list :param start_pos: The start position (line, column) the Function. :type start_pos: tuple(int, int) """ __slots__ = ('listeners', 'params') type = 'funcdef' def __init__(self, children): super(Function, self).__init__(children) self.listeners = set() # not used here, but in evaluation. lst = self.children[2].children[1:-1] # After `def foo` self.params = _create_params(self, lst) @property def name(self): return self.children[1] # First token after `def` @property def yields(self): # TODO This is incorrect, yields are also possible in a statement. return self._search_in_scope(YieldExpr) def is_generator(self): return bool(self.yields) def annotation(self): try: return self.children[6] # 6th element: def foo(...) -> bar except IndexError: return None def get_defined_names(self): n = super(Function, self).get_defined_names() for p in self.params: try: n.append(p.get_name()) except IndexError: debug.warning("multiple names in param %s", n) return n def scope_names_generator(self, position=None): yield self, filter_after_position(self.get_defined_names(), position) def get_call_signature(self, width=72, func_name=None): """ Generate call signature of this function. :param width: Fold lines if a line is longer than this value. :type width: int :arg func_name: Override function name when given. :type func_name: str :rtype: str """ func_name = func_name or self.children[1] code = unicode(func_name) + self.children[2].get_code() return '\n'.join(textwrap.wrap(code, width)) @property def doc(self): """ Return a document string including call signature. """ docstr = "" if self._doc_token is not None: docstr = self.raw_doc return '%s\n\n%s' % (self.get_call_signature(), docstr) class Lambda(Function): """ Lambdas are basically trimmed functions, so give it the same interface. """ type = 'lambda' def __init__(self, children): super(Function, self).__init__(children) self.listeners = set() # not used here, but in evaluation. lst = self.children[1:-2] # After `def foo` self.params = _create_params(self, lst) def is_generator(self): return False def yields(self): return [] def __repr__(self): return "<%s@%s>" % (self.__class__.__name__, self.start_pos) class Flow(Simple): pass class IfStmt(Flow): type = 'if_stmt' def check_nodes(self): """ Returns all the `test` nodes that are defined as x, here: if x: pass elif x: pass """ for i, c in enumerate(self.children): if c in ('elif', 'if'): yield self.children[i + 1] def node_in_which_check_node(self, node): for check_node in reversed(list(self.check_nodes())): if check_node.start_pos < node.start_pos: return check_node def node_after_else(self, node): """ Checks if a node is defined after `else`. """ for c in self.children: if c == 'else': if node.start_pos > c.start_pos: return True else: return False class WhileStmt(Flow): type = 'while_stmt' class ForStmt(Flow): type = 'for_stmt' class TryStmt(Flow): type = 'try_stmt' def except_clauses(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' def get_defined_names(self): names = [] for with_item in self.children[1:-2:2]: # Check with items for 'as' names. if is_node(with_item, 'with_item'): names += _defined_names(with_item.children[2]) return names def node_from_name(self, name): node = name while True: node = node.parent if is_node(node, 'with_item'): return node.children[0] class Import(Simple): def get_all_import_names(self): # TODO remove. do we even need this? raise NotImplementedError def path_for_name(self, 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.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' def get_defined_names(self): 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) @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 is_node(last, 'import_as_names'): as_names = last.children[::2] else: as_names = [last] for as_name in as_names: if isinstance(as_name, Name): yield as_name, None else: yield as_name.children[::2] # yields x, y -> ``x as y`` def star_import_name(self): """ The last name defined in a star import. """ return self.paths()[-1][-1] def paths(self): """ The import paths defined in an import statement. Typically an array like this: ``[, ]``. """ for n in self.children[1:]: if n not in ('.', '...'): break if is_node(n, 'dotted_name'): # from x.y import dotted = n.children[::2] elif n == 'import': # from . import dotted = [] else: # from x import dotted = [n] 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' def get_defined_names(self): 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 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 is_node(dotted_as_names, 'dotted_as_names'): as_names = dotted_as_names.children[::2] else: as_names = [dotted_as_names] for as_name in as_names: if is_node(as_name, 'dotted_as_name'): alias = as_name.children[2] as_name = as_name.children[0] else: alias = None if isinstance(as_name, 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 [1 for path, alias in self._dotted_as_names() if alias is None and len(path) > 1] def aliases(self): return dict((alias, path[-1]) for path, alias in self._dotted_as_names() if alias is not None) class KeywordStatement(Simple): """ For the following statements: `assert`, `del`, `global`, `nonlocal`, `raise`, `return`, `yield`, `pass`, `continue`, `break`, `return`, `yield`. """ @property def keyword(self): return self.children[0].value class AssertStmt(KeywordStatement): type = 'assert_stmt' def assertion(self): return self.children[1] class GlobalStmt(KeywordStatement): type = 'global_stmt' def get_defined_names(self): return self.children[1::2] class ReturnStmt(KeywordStatement): type = 'return_stmt' class YieldExpr(Simple): type = 'yield_expr' def _defined_names(current): """ A helper function to find the defined names in statements, for loops and list comprehensions. """ names = [] if is_node(current, 'testlist_star_expr', 'testlist_comp', 'exprlist'): for child in current.children[::2]: names += _defined_names(child) elif is_node(current, 'atom'): names += _defined_names(current.children[1]) elif is_node(current, 'power'): 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(Simple, DocstringMixin): type = 'expr_stmt' def get_defined_names(self): return list(chain.from_iterable(_defined_names(self.children[i]) for i in range(0, len(self.children) - 2, 2) if '=' in self.children[i + 1].value)) def get_rhs(self): """Returns the right-hand-side of the equals.""" return self.children[-1] def first_operation(self): """ Returns `+=`, `=`, etc or None if there is no operation. """ try: return self.children[1] except IndexError: return None class Param(Base): """ The class which shows definitions of params of classes and functions. But this is not to define function calls. A helper class for functions. Read only. """ __slots__ = ('tfpdef', 'default', 'stars', 'parent') # Even though it's not not an official node, just give it one, because that # makes checking more consistent. type = 'param' def __init__(self, tfpdef, parent, default=None, stars=0): self.tfpdef = tfpdef # tfpdef: see grammar.txt self.default = default self.stars = stars self.parent = parent # Here we reset the parent of our name. IMHO this is ok. self.get_name().parent = self def annotation(self): # Generate from tfpdef. raise NotImplementedError @property def children(self): return [] @property def start_pos(self): return self.tfpdef.start_pos def get_name(self): # TODO remove! return self.name @property def name(self): if is_node(self.tfpdef, 'tfpdef'): return self.tfpdef.children[0] else: return self.tfpdef @property def position_nr(self): return self.parent.params.index(self) @property def parent_function(self): return self.get_parent_until(IsScope) def get_code(self): df = '' if self.default is None else '=' + self.default.get_code() return self.tfpdef.get_code() + df def add_annotation(self, annotation_stmt): annotation_stmt.parent = self.use_as_parent self.annotation_stmt = annotation_stmt def __repr__(self): default = '' if self.default is None else '=%s' % self.default return '<%s: %s>' % (type(self).__name__, str(self.tfpdef) + default) class Array(object): # TODO remove this. Just here because we need these names. NOARRAY = None # just brackets, like `1 * (3 + 2)` TUPLE = 'tuple' LIST = 'list' DICT = 'dict' SET = 'set' class CompFor(Simple): type = 'comp_for' def is_scope(self): return True @property def names_dict(self): dct = {} for name in self.get_defined_names(): arr = dct.setdefault(name.value, []) arr.append(name) return dct def get_rhs(self): return self.children[3] def get_defined_names(self): return _defined_names(self.children[1]) def scope_names_generator(self, position): yield self, [] @property def asserts(self): """Since it's a scope, it can be asked for asserts.""" return []