From b5ad56d116bf03d0572eab0d1601b55ebcc27dd1 Mon Sep 17 00:00:00 2001 From: David Halter Date: Fri, 15 Mar 2013 02:15:45 +0430 Subject: [PATCH 01/43] new part spliting of strings --- jedi/fast_parser.py | 65 ++++++++++++++++++++++++++++++++++ jedi/parsing_representation.py | 1 - 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 8e14b953..c657dca3 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -207,8 +207,73 @@ class FastParser(use_metaclass(CachedFastParser)): return self.scan_user_scope(scope) or scope return None + def _split_parts(self, code): + """ + Split the code into different parts. This makes it possible to parse + each part seperately and therefore cache parts of the file and not + everything. + """ + def add_part(): + txt = '\n'.join(current_lines) + if txt: + parts.append(txt) + current_lines[:] = [] + + flows = ['if', 'else', 'elif', 'while', 'with', 'try', 'except', + 'finally'] + r_keyword = '^[ \t]*(def|class|@|%s)' % '|'.join(flows) + + lines = code.splitlines() + current_lines = [] + parts = [] + is_generator = False + current_indent = 0 + new_indent = False + is_flow = False + # All things within flows are simply being ignored. + for i, l in enumerate(lines): + # check for dedents + m = re.match('^([\t ]*)(.?)', l) + indent = len(m.group(1)) + if m.group(2) in ['', '#']: + current_lines.append(l) # just ignore comments and blank lines + continue + + if indent < current_indent: # -> dedent + current_indent = indent + new_indent = False + if not is_flow: + add_part() + is_flow = False + elif new_indent: + current_indent = indent + new_indent = False + + # Check lines for functions/classes and split the code there. + if not is_flow: + m = re.match(r_keyword, l) + if m: + is_flow = m.group(1) in flows + if not is_generator and not is_flow: + add_part() + current_lines = [] + is_generator = '@' == m.group(1) + if not is_generator: + current_indent += 1 # it must be higher + new_indent = True + + current_lines.append(l) + add_part() + + + print parts[1] + print [p[:20] for p in parts] + exit() + return parts + def _parse(self, code): """ :type code: str """ + parts = self._split_parts(code) r = r'(?:\n(?:def|class|@.*?\n(?:def|class))|^).*?' \ r'(?=\n(?:def|class|@)|$)' parts = re.findall(r, code, re.DOTALL) diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index f429e92f..cad3d840 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -32,7 +32,6 @@ statements in this scope. Check this out: [] See also :attr:`Scope.subscopes` and :attr:`Scope.statements`. - """ import os From b426835d18758844e16ca23fc07bfb6d7213c493 Mon Sep 17 00:00:00 2001 From: David Halter Date: Fri, 15 Mar 2013 13:57:23 +0430 Subject: [PATCH 02/43] parser preparation for the new fast_parser --- jedi/parsing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jedi/parsing.py b/jedi/parsing.py index df52bf94..06036193 100644 --- a/jedi/parsing.py +++ b/jedi/parsing.py @@ -41,11 +41,11 @@ class Parser(object): :param user_position: The line/column, the user is currently on. :type user_position: tuple(int, int) :param no_docstr: If True, a string at the beginning is not a docstr. - :param stop_on_scope: Stop if a scope appears -> for fast_parser + :param is_fast_parser: -> for fast_parser :param top_module: Use this module as a parent instead of `self.module`. """ def __init__(self, source, module_path=None, user_position=None, - no_docstr=False, offset=(0, 0), stop_on_scope=None, + no_docstr=False, offset=(0, 0), is_fast_parser=None, top_module=None): self.user_position = user_position self.user_scope = None @@ -63,7 +63,7 @@ class Parser(object): source = source + '\n' # end with \n, because the parser needs it buf = StringIO(source) self._gen = common.NoErrorTokenizer(buf.readline, offset, - stop_on_scope) + is_fast_parser) self.top_module = top_module or self.module try: self._parse() From 841b0be19a84696bb780e7a699888a27940f312d Mon Sep 17 00:00:00 2001 From: David Halter Date: Fri, 15 Mar 2013 23:03:59 +0430 Subject: [PATCH 03/43] improved parents in fast parser --- jedi/fast_parser.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index c657dca3..e3b74c44 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -265,25 +265,30 @@ class FastParser(use_metaclass(CachedFastParser)): current_lines.append(l) add_part() - - print parts[1] - print [p[:20] for p in parts] - exit() return parts def _parse(self, code): """ :type code: str """ - parts = self._split_parts(code) - r = r'(?:\n(?:def|class|@.*?\n(?:def|class))|^).*?' \ - r'(?=\n(?:def|class|@)|$)' - parts = re.findall(r, code, re.DOTALL) + def set_parent(module): + def get_indent(module): + try: + el = module.subscopes[0] + except IndexError: + try: + el = module.statements[0] + except IndexError: + el = module.imports[0] + return el.start_pos[1] - if len(parts) > 1 and not re.match('def|class|@', parts[0]): - # Merge the first two because `common.NoErrorTokenizer` is not able - # to know if there's a class/func or not. - # Therefore every part has it's own class/func. Exactly one. - parts[0] += parts[1] - parts.pop(1) + if self.parsers: + new_indent = get_indent(module) + old_indent = get_indent(self.parsers[-1].module) + if old_indent < new_indent: + module.parent = self.parsers[-1].module.subscopes[0] + return + p.module.parent = self.module + + parts = self._split_parts(code) if settings.fast_parser_always_reparse: self.parsers[:] = [] @@ -321,12 +326,12 @@ class FastParser(use_metaclass(CachedFastParser)): else: p = parsing.Parser(code[start:], self.module_path, self.user_position, - offset=(line_offset, 0), stop_on_scope=True, + offset=(line_offset, 0), is_fast_parser=True, top_module=self.module) p.hash = h p.code = code_part - p.module.parent = self.module + set_parent(p.module) self.parsers.insert(parser_order, p) parser_order += 1 From df058b93c248b7456d0b0a000a6d531f68941859 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sat, 16 Mar 2013 23:26:20 +0430 Subject: [PATCH 04/43] improve get_code of parsing_representation scopes --- jedi/parsing_representation.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index cad3d840..7e128a38 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -193,15 +193,16 @@ class Scope(Simple, IsScope): string = "" if len(self.docstr) > 0: string += '"""' + self.docstr + '"""\n' - for i in self.imports: - string += i.get_code() - for sub in self.subscopes: - string += sub.get_code(first_indent=True, indention=indention) returns = self.returns if hasattr(self, 'returns') else [] - ret_str = '' if isinstance(self, Lambda) else 'return ' - for stmt in self.statements + returns: - string += (ret_str if stmt in returns else '') + stmt.get_code() + objs = self.subscopes + self.imports + self.statements + returns + for obj in sorted(objs, key=lambda x: x.start_pos): + if isinstance(obj, Scope): + string += obj.get_code(first_indent=True, indention=indention) + else: + if obj in returns and not isinstance(self, Lambda): + string += 'yield ' if self.is_generator else 'return ' + string += obj.get_code() if first_indent: string = common.indent_block(string, indention=indention) @@ -396,7 +397,7 @@ class Class(Scope): string = "\n".join('@' + stmt.get_code() for stmt in self.decorators) string += 'class %s' % (self.name) if len(self.supers) > 0: - sup = ','.join(stmt.get_code() for stmt in self.supers) + sup = ', '.join(stmt.get_code(False) for stmt in self.supers) string += '(%s)' % sup string += ':\n' string += super(Class, self).get_code(True, indention) @@ -448,7 +449,7 @@ class Function(Scope): def get_code(self, first_indent=False, indention=' '): string = "\n".join('@' + stmt.get_code() for stmt in self.decorators) - params = ','.join([stmt.get_code() for stmt in self.params]) + params = ', '.join([stmt.get_code(False) for stmt in self.params]) string += "def %s(%s):\n" % (self.name, params) string += super(Function, self).get_code(True, indention) if self.is_empty(): From a99d9541bd727b688ccd550f3731ec351f4f03d9 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sun, 24 Mar 2013 22:51:17 +0430 Subject: [PATCH 05/43] on the way to a better fast_parser - improved a lot of the positioning stuff --- jedi/common.py | 77 ++++++++++++++++++++++++++++++++++++--------- jedi/fast_parser.py | 43 +++++++++++++++---------- jedi/parsing.py | 21 +++++++------ 3 files changed, 101 insertions(+), 40 deletions(-) diff --git a/jedi/common.py b/jedi/common.py index 35a10f62..cfa374d4 100644 --- a/jedi/common.py +++ b/jedi/common.py @@ -6,6 +6,8 @@ from _compatibility import next import debug import settings +FLOWS = ['if', 'else', 'elif', 'while', 'with', 'try', 'except', 'finally'] + class MultiLevelStopIteration(Exception): """ @@ -56,14 +58,21 @@ class PushBackIterator(object): class NoErrorTokenizer(object): - def __init__(self, readline, offset=(0, 0), stop_on_scope=False): + def __init__(self, readline, offset=(0, 0), is_fast_parser=False): self.readline = readline self.gen = PushBackIterator(tokenize.generate_tokens(readline)) self.offset = offset - self.stop_on_scope = stop_on_scope - self.first_scope = False self.closed = False - self.first = True + self.is_first = True + + # fast parser options + self.is_fast_parser = is_fast_parser + self.current = self.previous = [None, None, (0, 0), (0, 0), ''] + self.in_flow = False + self.new_indent = False + self.parser_indent = 0 + self.is_decorator = False + self.first_stmt = True def push_last_back(self): self.gen.push_back(self.current) @@ -76,6 +85,8 @@ class NoErrorTokenizer(object): if self.closed: raise MultiLevelStopIteration() try: + last_previous = self.previous + self.previous = self.current self.current = next(self.gen) except tokenize.TokenError: # We just ignore this error, I try to handle it earlier - as @@ -99,22 +110,60 @@ class NoErrorTokenizer(object): c = list(self.current) - # stop if a new class or definition is started at position zero. - breaks = ['def', 'class', '@'] - if self.stop_on_scope and c[1] in breaks and c[2][1] == 0: - if self.first_scope: - self.closed = True - raise MultiLevelStopIteration() - elif c[1] != '@': - self.first_scope = True + if c[0] == tokenize.ENDMARKER: + self.current = self.previous + self.previous = last_previous + raise MultiLevelStopIteration() - if self.first: + # this is exactly the same check as in fast_parser, but this time with + # tokenize and therefore precise. + breaks = ['def', 'class', '@'] + + if self.is_first: c[2] = self.offset[0] + c[2][0], self.offset[1] + c[2][1] c[3] = self.offset[0] + c[3][0], self.offset[1] + c[3][1] - self.first = False + self.is_first = False else: c[2] = self.offset[0] + c[2][0], c[2][1] c[3] = self.offset[0] + c[3][0], c[3][1] + print 'h', c, tokenize.tok_name[c[0]], self.current[2:4] + self.current = c + + def close(): + if not self.first_stmt: + self.closed = True + raise MultiLevelStopIteration() + # ignore indents/comments + if self.is_fast_parser \ + and self.previous[0] in (tokenize.INDENT, tokenize.NL, None, + tokenize.NEWLINE, tokenize.DEDENT) \ + and c[0] not in (tokenize.COMMENT, tokenize.INDENT, + tokenize.NL, tokenize.NEWLINE, tokenize.DEDENT): + print c, tokenize.tok_name[c[0]] + + tok = c[1] + indent = c[2][1] + if indent < self.parser_indent: # -> dedent + self.parser_indent = indent + self.new_indent = False + if not self.in_flow: + close() + self.in_flow = False + elif self.new_indent: + self.parser_indent = indent + + if not self.in_flow: + if tok in FLOWS or tok in breaks: + self.in_flow = tok in FLOWS + if not self.is_decorator and not self.in_flow: + close() + self.is_decorator = '@' == tok + if not self.is_decorator: + self.parser_indent += 1 # new scope: must be higher + self.new_indent = True + + if tok != '@': + self.first_stmt = False return c diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index e3b74c44..8811d290 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -11,6 +11,7 @@ import settings import parsing import parsing_representation as pr import cache +import common class Module(pr.Simple, pr.Module): @@ -219,17 +220,15 @@ class FastParser(use_metaclass(CachedFastParser)): parts.append(txt) current_lines[:] = [] - flows = ['if', 'else', 'elif', 'while', 'with', 'try', 'except', - 'finally'] - r_keyword = '^[ \t]*(def|class|@|%s)' % '|'.join(flows) + r_keyword = '^[ \t]*(def|class|@|%s)' % '|'.join(common.FLOWS) lines = code.splitlines() current_lines = [] parts = [] - is_generator = False + is_decorator = False current_indent = 0 new_indent = False - is_flow = False + in_flow = False # All things within flows are simply being ignored. for i, l in enumerate(lines): # check for dedents @@ -242,29 +241,35 @@ class FastParser(use_metaclass(CachedFastParser)): if indent < current_indent: # -> dedent current_indent = indent new_indent = False - if not is_flow: + if not in_flow: add_part() - is_flow = False + in_flow = False elif new_indent: current_indent = indent new_indent = False # Check lines for functions/classes and split the code there. - if not is_flow: + if not in_flow: m = re.match(r_keyword, l) if m: - is_flow = m.group(1) in flows - if not is_generator and not is_flow: + in_flow = m.group(1) in common.FLOWS + if not is_decorator and not in_flow: add_part() current_lines = [] - is_generator = '@' == m.group(1) - if not is_generator: + is_decorator = '@' == m.group(1) + if not is_decorator: current_indent += 1 # it must be higher new_indent = True current_lines.append(l) add_part() + for p in parts: + #print '#####################################' + #print p + #print len(p.splitlines()) + pass + return parts def _parse(self, code): @@ -280,11 +285,12 @@ class FastParser(use_metaclass(CachedFastParser)): el = module.imports[0] return el.start_pos[1] - if self.parsers: + if self.parsers and False: new_indent = get_indent(module) old_indent = get_indent(self.parsers[-1].module) if old_indent < new_indent: - module.parent = self.parsers[-1].module.subscopes[0] + #module.parent = self.parsers[-1].module.subscopes[0] + # TODO set parents + add to subscopes return p.module.parent = self.module @@ -301,7 +307,7 @@ class FastParser(use_metaclass(CachedFastParser)): p = None parser_order = 0 for code_part in parts: - lines = code_part.count('\n') + lines = code_part.count('\n') + 1 # the parser is using additional newlines, therefore substract if p is None or line_offset >= p.end_pos[0] - 2: # check if code_part has already been parsed @@ -336,8 +342,13 @@ class FastParser(use_metaclass(CachedFastParser)): parser_order += 1 line_offset += lines - start += len(code_part) + print line_offset + start += len(code_part) + 1 # +1 for newline self.parsers[parser_order + 1:] = [] + for p in self.parsers: + print(p.module.get_code()) + print(p.module.start_pos, p.module.end_pos) + exit() def reset_caches(self): self._user_scope = None diff --git a/jedi/parsing.py b/jedi/parsing.py index 06036193..0ce923be 100644 --- a/jedi/parsing.py +++ b/jedi/parsing.py @@ -52,13 +52,11 @@ class Parser(object): self.user_stmt = None self.no_docstr = no_docstr + self.start_pos = self.end_pos = 1 + offset[0], offset[1] # initialize global Scope - self.module = pr.SubModule(module_path, (offset[0] + 1, offset[1]), - top_module) + self.module = pr.SubModule(module_path, self.start_pos, top_module) self.scope = self.module self.current = (None, None) - self.start_pos = 1, 0 - self.end_pos = 1, 0 source = source + '\n' # end with \n, because the parser needs it buf = StringIO(source) @@ -79,6 +77,10 @@ class Parser(object): # because of `self.module.used_names`. d.parent = self.module + if self.current[0] in (tokenize.NL, tokenize.NEWLINE): + # we added a newline before, so we need to "remove" it again. + self.end_pos = self._gen.previous[2] + self.start_pos = self.module.start_pos self.module.end_pos = self.end_pos del self._gen @@ -170,8 +172,6 @@ class Parser(object): while True: defunct = False token_type, tok = self.next() - if token_type == tokenize.ENDMARKER: - break if brackets and tok == '\n': self.next() if tok == '(': # python allows only one `(` in the statement. @@ -421,8 +421,10 @@ class Parser(object): def __next__(self): """ Generate the next tokenize pattern. """ try: - typ, tok, self.start_pos, self.end_pos, \ - self.parserline = next(self._gen) + typ, tok, start_pos, end_pos, self.parserline = next(self._gen) + # dedents shouldn't change positions + if typ != tokenize.DEDENT: + self.start_pos, self.end_pos = start_pos, end_pos except (StopIteration, common.MultiLevelStopIteration): # on finish, set end_pos correctly s = self.scope @@ -662,7 +664,6 @@ class Parser(object): self.freshscope = False else: if token_type not in [tokenize.COMMENT, tokenize.INDENT, - tokenize.NEWLINE, tokenize.NL, - tokenize.ENDMARKER]: + tokenize.NEWLINE, tokenize.NL]: debug.warning('token not classified', tok, token_type, self.start_pos[0]) From 5dd05eff1a9d933ebd8f3af4c57a385ce547ebd3 Mon Sep 17 00:00:00 2001 From: David Halter Date: Wed, 10 Apr 2013 22:33:49 +0430 Subject: [PATCH 06/43] a basic approach to the new fast parser --- jedi/common.py | 3 +- jedi/fast_parser.py | 248 ++++++++++++++++++++------------- jedi/parsing_representation.py | 14 +- jedi/settings.py | 7 - 4 files changed, 158 insertions(+), 114 deletions(-) diff --git a/jedi/common.py b/jedi/common.py index cfa374d4..89eec32e 100644 --- a/jedi/common.py +++ b/jedi/common.py @@ -126,7 +126,6 @@ class NoErrorTokenizer(object): else: c[2] = self.offset[0] + c[2][0], c[2][1] c[3] = self.offset[0] + c[3][0], c[3][1] - print 'h', c, tokenize.tok_name[c[0]], self.current[2:4] self.current = c def close(): @@ -139,7 +138,7 @@ class NoErrorTokenizer(object): tokenize.NEWLINE, tokenize.DEDENT) \ and c[0] not in (tokenize.COMMENT, tokenize.INDENT, tokenize.NL, tokenize.NEWLINE, tokenize.DEDENT): - print c, tokenize.tok_name[c[0]] + #print c, tokenize.tok_name[c[0]] tok = c[1] indent = c[2][1] diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 8811d290..592144cb 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -14,13 +14,15 @@ import cache import common +SCOPE_CONTENTS = ['asserts', 'subscopes', 'imports', 'statements', 'returns'] + + class Module(pr.Simple, pr.Module): def __init__(self, parsers): self._end_pos = None, None super(Module, self).__init__(self, (1, 0)) self.parsers = parsers self.reset_caches() - self.line_offset = 0 def reset_caches(self): """ This module does a whole lot of caching, because it uses different @@ -63,18 +65,6 @@ class Module(pr.Simple, pr.Module): else: raise AttributeError("__getattr__ doesn't offer %s" % name) - def get_statement_for_position(self, pos): - key = 'get_statement_for_position', pos - if key not in self.cache: - for p in self.parsers: - s = p.module.get_statement_for_position(pos) - if s: - self.cache[key] = s - break - else: - self.cache[key] = None - return self.cache[key] - @property def used_names(self): if not self.parsers: @@ -92,30 +82,6 @@ class Module(pr.Simple, pr.Module): self.cache[key] = dct return self.cache[key] - @property - def docstr(self): - if not self.parsers: - raise NotImplementedError("Parser doesn't exist.") - return self.parsers[0].module.docstr - - @property - def name(self): - if not self.parsers: - raise NotImplementedError("Parser doesn't exist.") - return self.parsers[0].module.name - - @property - def path(self): - if not self.parsers: - raise NotImplementedError("Parser doesn't exist.") - return self.parsers[0].module.path - - @property - def is_builtin(self): - if not self.parsers: - raise NotImplementedError("Parser doesn't exist.") - return self.parsers[0].module.is_builtin - @property def start_pos(self): """ overwrite start_pos of Simple """ @@ -157,12 +123,93 @@ class CachedFastParser(type): return p +class ParserNode(object): + def __init__(self, parser, code, parent=None): + self.parent = parent + self.parser = parser + self.code = code + self.hash = hash(code) + + self.children = [] + self._checked = True + self.save_contents() + + def save_contents(self): + scope = self._get_content_scope() + self._contents = {} + for c in SCOPE_CONTENTS: + self._contents[c] = list(getattr(scope, c)) + self._is_generator = scope.is_generator + + def _get_content_scope(self): + try: + # with fast_parser we have either 1 subscope or only statements. + return self.parser.module.subscopes[0] + except IndexError: + return self.parser.module + + def reset_contents(self): + self._checked = False + + scope = self._get_content_scope() + for key, c in self._contents.items(): + setattr(scope, key, self.contents.items()) + scope.is_generator = self._is_generator + + for c in self.children: + c.reset_contents() + + def parent_until_indent(self, indent): + if self.indent >= indent: + # check for + for i, c in enumerate(self.children): + if not c._checked: + # all of the following + del self.children[i:] + break + + return self.parent.parent_until_indent(indent) + return self + + @property + def indent(self): + if not self.parent: + return -1 + module = self.parser.module + try: + el = module.subscopes[0] + except IndexError: + try: + el = module.statements[0] + except IndexError: + el = module.imports[0] + return el.start_pos[1] + + def add_node(self, parser, code): + # only compare at the right indent level + insert = 0 + for insert, c in enumerate(self.children): + if not c._checked: + break + node = ParserNode(parser, code, self) + self.children.insert(insert, node) + + # insert parser objects into current structure + scope = self._get_content_scope() + for c in SCOPE_CONTENTS: + content = getattr(scope, c) + content += getattr(parser.module, c) + scope.is_generator |= parser.module.is_generator + return node + + class FastParser(use_metaclass(CachedFastParser)): def __init__(self, code, module_path=None, user_position=None): # set values like `pr.Module`. self.module_path = module_path self.user_position = user_position + self.current_node = None self.parsers = [] self.module = Module(self.parsers) self.reset_caches() @@ -274,83 +321,84 @@ class FastParser(use_metaclass(CachedFastParser)): def _parse(self, code): """ :type code: str """ - def set_parent(module): - def get_indent(module): - try: - el = module.subscopes[0] - except IndexError: - try: - el = module.statements[0] - except IndexError: - el = module.imports[0] - return el.start_pos[1] - - if self.parsers and False: - new_indent = get_indent(module) - old_indent = get_indent(self.parsers[-1].module) - if old_indent < new_indent: - #module.parent = self.parsers[-1].module.subscopes[0] - # TODO set parents + add to subscopes - return - p.module.parent = self.module - parts = self._split_parts(code) + self.parsers[:] = [] - if settings.fast_parser_always_reparse: - self.parsers[:] = [] - - # dict comprehensions are not available in py2.5/2.6 :-( - hashes = dict((p.hash, p) for p in self.parsers) - - line_offset = 0 - start = 0 + self._code = code + self._line_offset = 0 + self._start = 0 p = None - parser_order = 0 + is_first = True for code_part in parts: lines = code_part.count('\n') + 1 - # the parser is using additional newlines, therefore substract - if p is None or line_offset >= p.end_pos[0] - 2: - # check if code_part has already been parsed - h = hash(code_part) - - if h in hashes and hashes[h].code == code_part: - p = hashes[h] - del hashes[h] - m = p.module - m.line_offset += line_offset + 1 - m.start_pos[0] - if self.user_position is not None and \ - m.start_pos <= self.user_position <= m.end_pos: - # It's important to take care of the whole user - # positioning stuff, if no reparsing is being done. - p.user_stmt = m.get_statement_for_position( - self.user_position, include_imports=True) - if p.user_stmt: - p.user_scope = p.user_stmt.parent - else: - p.user_scope = self.scan_user_scope(m) \ - or self.module + if is_first or self._line_offset >= p.end_pos[0] - 1: + indent = len(re.match(r'[ \t]*', code).groups(0)) + if is_first and self.current_node is not None: + nodes = [self] else: - p = parsing.Parser(code[start:], - self.module_path, self.user_position, - offset=(line_offset, 0), is_fast_parser=True, - top_module=self.module) + nodes = [] + if self.current_node is not None: - p.hash = h - p.code = code_part - set_parent(p.module) - self.parsers.insert(parser_order, p) + self.current_node = \ + self.current_node.parent_until_indent(indent) + nodes += self.current_node.children - parser_order += 1 - line_offset += lines - print line_offset - start += len(code_part) + 1 # +1 for newline - self.parsers[parser_order + 1:] = [] + # check if code_part has already been parsed + p = self._get_parser(code, nodes) + + if is_first: + if self.current_node is None: + self.current_node = ParserNode(p, code) + else: + self.current_node.parser = p + self.current_node.save_contents() + else: + self.current_node = self.current_node.add_node(p, code) + self.parsers.append(p) + + is_first = False + + self._line_offset += lines + self._start += len(code_part) + 1 # +1 for newline + print 'hmm' for p in self.parsers: print(p.module.get_code()) print(p.module.start_pos, p.module.end_pos) exit() + del self._code + + def _get_parser(self, code, nodes): + h = hash(code) + hashes = [n.hash for n in nodes] + try: + index = hashes.index(h) + if nodes[index].code != code: + raise ValueError() + except ValueError: + p = parsing.Parser(self._code[self._start:], + self.module_path, self.user_position, + offset=(self._line_offset, 0), + is_fast_parser=True, top_module=self.module) + else: + node = nodes.pop(index) + p = node.parser + m = p.module + m.line_offset += self._line_offset + 1 - m.start_pos[0] + if self.user_position is not None and \ + m.start_pos <= self.user_position <= m.end_pos: + # It's important to take care of the whole user + # positioning stuff, if no reparsing is being done. + p.user_stmt = m.get_statement_for_position( + self.user_position, include_imports=True) + if p.user_stmt: + p.user_scope = p.user_stmt.parent + else: + p.user_scope = self.scan_user_scope(m) or self.module + return p def reset_caches(self): self._user_scope = None self._user_stmt = None self.module.reset_caches() + if self.current_node is not None: + self.current_node.reset_contents() diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index 7e128a38..3a7ff64a 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -149,6 +149,10 @@ class Scope(Simple, IsScope): self.statements = [] self.docstr = '' self.asserts = [] + # Needed here for fast_parser, because the fast_parser splits and + # returns will be in "normal" modules. + self.returns = [] + self.is_generator = False def add_scope(self, sub, decorators): sub.parent = self.use_as_parent @@ -194,13 +198,12 @@ class Scope(Simple, IsScope): if len(self.docstr) > 0: string += '"""' + self.docstr + '"""\n' - returns = self.returns if hasattr(self, 'returns') else [] - objs = self.subscopes + self.imports + self.statements + returns + objs = self.subscopes + self.imports + self.statements + self.returns for obj in sorted(objs, key=lambda x: x.start_pos): if isinstance(obj, Scope): string += obj.get_code(first_indent=True, indention=indention) else: - if obj in returns and not isinstance(self, Lambda): + if obj in self.returns and not isinstance(self, Lambda): string += 'yield ' if self.is_generator else 'return ' string += obj.get_code() @@ -439,8 +442,6 @@ class Function(Scope): p.parent = self.use_as_parent p.parent_function = self.use_as_parent self.decorators = [] - self.returns = [] - self.is_generator = False self.listeners = set() # not used here, but in evaluation. if annotation is not None: @@ -456,6 +457,9 @@ class Function(Scope): string += "pass\n" return string + def is_empty(self): + return super(Function, self).is_empty() and not self.returns + def get_set_vars(self): n = super(Function, self).get_set_vars() for p in self.params: diff --git a/jedi/settings.py b/jedi/settings.py index d8162170..df48e7ac 100644 --- a/jedi/settings.py +++ b/jedi/settings.py @@ -33,7 +33,6 @@ Parser ~~~~~~ .. autodata:: fast_parser -.. autodata:: fast_parser_always_reparse .. autodata:: use_function_definition_cache @@ -150,12 +149,6 @@ something has been changed e.g. to a function. If this happens, only the function is being reparsed. """ -fast_parser_always_reparse = False -""" -This is just a debugging option. Always reparsing means that the fast parser -is basically useless. So don't use it. -""" - use_function_definition_cache = True """ Use the cache (full cache) to generate function_definition's. This may fail From 967d01d4906929808d216cbb218c85de7c626026 Mon Sep 17 00:00:00 2001 From: David Halter Date: Wed, 10 Apr 2013 23:50:39 +0430 Subject: [PATCH 07/43] fast parser produces now trees that are correct --- jedi/fast_parser.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 592144cb..fb9e9194 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -132,26 +132,25 @@ class ParserNode(object): self.children = [] self._checked = True + # must be created before new things are added to it. + try: + # with fast_parser we have either 1 subscope or only statements. + self._content_scope = self.parser.module.subscopes[0] + except IndexError: + self._content_scope = self.parser.module self.save_contents() def save_contents(self): - scope = self._get_content_scope() + scope = self._content_scope self._contents = {} for c in SCOPE_CONTENTS: self._contents[c] = list(getattr(scope, c)) self._is_generator = scope.is_generator - def _get_content_scope(self): - try: - # with fast_parser we have either 1 subscope or only statements. - return self.parser.module.subscopes[0] - except IndexError: - return self.parser.module - def reset_contents(self): self._checked = False - scope = self._get_content_scope() + scope = self._content_scope for key, c in self._contents.items(): setattr(scope, key, self.contents.items()) scope.is_generator = self._is_generator @@ -160,7 +159,7 @@ class ParserNode(object): c.reset_contents() def parent_until_indent(self, indent): - if self.indent >= indent: + if self.indent >= indent and self.parent: # check for for i, c in enumerate(self.children): if not c._checked: @@ -174,7 +173,7 @@ class ParserNode(object): @property def indent(self): if not self.parent: - return -1 + return 0 module = self.parser.module try: el = module.subscopes[0] @@ -195,7 +194,7 @@ class ParserNode(object): self.children.insert(insert, node) # insert parser objects into current structure - scope = self._get_content_scope() + scope = self._content_scope for c in SCOPE_CONTENTS: content = getattr(scope, c) content += getattr(parser.module, c) @@ -332,7 +331,7 @@ class FastParser(use_metaclass(CachedFastParser)): for code_part in parts: lines = code_part.count('\n') + 1 if is_first or self._line_offset >= p.end_pos[0] - 1: - indent = len(re.match(r'[ \t]*', code).groups(0)) + indent = len(re.match(r'[ \t]*', code_part).group(0)) if is_first and self.current_node is not None: nodes = [self] else: @@ -344,7 +343,7 @@ class FastParser(use_metaclass(CachedFastParser)): nodes += self.current_node.children # check if code_part has already been parsed - p = self._get_parser(code, nodes) + p = self._get_parser(code_part, nodes) if is_first: if self.current_node is None: @@ -360,10 +359,11 @@ class FastParser(use_metaclass(CachedFastParser)): self._line_offset += lines self._start += len(code_part) + 1 # +1 for newline - print 'hmm' - for p in self.parsers: - print(p.module.get_code()) - print(p.module.start_pos, p.module.end_pos) + + print(self.parsers[0].module.get_code()) + #for p in self.parsers: + # print(p.module.get_code()) + # print(p.module.start_pos, p.module.end_pos) exit() del self._code From eeb8e0e21d9aa03f6667514e3b1ab94cafd5879f Mon Sep 17 00:00:00 2001 From: David Halter Date: Sat, 13 Apr 2013 13:50:50 +0430 Subject: [PATCH 08/43] first test working again with new fast_parser --- jedi/fast_parser.py | 90 +++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index fb9e9194..61030737 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -44,26 +44,12 @@ class Module(pr.Simple, pr.Module): return self.cache[key] def __getattr__(self, name): - operators = {'get_imports': operator.add, - 'get_code': operator.add, - 'get_set_vars': operator.add, - 'get_defined_names': operator.add, - 'is_empty': operator.and_ - } - properties = {'subscopes': operator.add, - 'imports': operator.add, - 'statements': operator.add, - 'imports': operator.add, - 'asserts': operator.add, - 'global_vars': operator.add - } - if name in operators: - return lambda *args, **kwargs: self._get(name, operators[name], - True, *args, **kwargs) - elif name in properties: - return self._get(name, properties[name]) + if name == 'global_vars': + return self._get(name, operator.add) + elif name.startswith('__'): + raise AttributeError('Not available!') else: - raise AttributeError("__getattr__ doesn't offer %s" % name) + return getattr(self.parsers[0].module, name) @property def used_names(self): @@ -90,7 +76,7 @@ class Module(pr.Simple, pr.Module): @start_pos.setter def start_pos(self): """ ignore """ - pass + raise AttributeError('TODO remove - just a check if everything works fine.') @property def end_pos(self): @@ -131,7 +117,7 @@ class ParserNode(object): self.hash = hash(code) self.children = [] - self._checked = True + self._old_children = [] # must be created before new things are added to it. try: # with fast_parser we have either 1 subscope or only statements. @@ -144,15 +130,13 @@ class ParserNode(object): scope = self._content_scope self._contents = {} for c in SCOPE_CONTENTS: - self._contents[c] = list(getattr(scope, c)) + self._contents[c] = getattr(scope, c) self._is_generator = scope.is_generator def reset_contents(self): - self._checked = False - scope = self._content_scope for key, c in self._contents.items(): - setattr(scope, key, self.contents.items()) + setattr(scope, key, c) scope.is_generator = self._is_generator for c in self.children: @@ -160,13 +144,7 @@ class ParserNode(object): def parent_until_indent(self, indent): if self.indent >= indent and self.parent: - # check for - for i, c in enumerate(self.children): - if not c._checked: - # all of the following - del self.children[i:] - break - + self._old_children = [] return self.parent.parent_until_indent(indent) return self @@ -184,21 +162,30 @@ class ParserNode(object): el = module.imports[0] return el.start_pos[1] - def add_node(self, parser, code): - # only compare at the right indent level - insert = 0 - for insert, c in enumerate(self.children): - if not c._checked: - break - node = ParserNode(parser, code, self) - self.children.insert(insert, node) - + def _set_items(self, parser, set_parent=False): # insert parser objects into current structure scope = self._content_scope for c in SCOPE_CONTENTS: content = getattr(scope, c) - content += getattr(parser.module, c) + items = getattr(parser.module, c) + if set_parent: + for i in items: + i.parent = scope + content += items scope.is_generator |= parser.module.is_generator + + def add_node(self, node): + """Adding a node means adding a node that was already added earlier""" + self.children.append(node) + self._set_items(node.parser) + node._old_children = node.children + node.children = [] + return node + + def add_parser(self, parser, code): + node = ParserNode(parser, code, self) + self._set_items(parser, set_parent=True) + self.children.append(node) return node @@ -333,17 +320,17 @@ class FastParser(use_metaclass(CachedFastParser)): if is_first or self._line_offset >= p.end_pos[0] - 1: indent = len(re.match(r'[ \t]*', code_part).group(0)) if is_first and self.current_node is not None: - nodes = [self] + nodes = [self.current_node] else: nodes = [] if self.current_node is not None: self.current_node = \ self.current_node.parent_until_indent(indent) - nodes += self.current_node.children + nodes += self.current_node._old_children # check if code_part has already been parsed - p = self._get_parser(code_part, nodes) + p, node = self._get_parser(code_part, nodes) if is_first: if self.current_node is None: @@ -352,7 +339,11 @@ class FastParser(use_metaclass(CachedFastParser)): self.current_node.parser = p self.current_node.save_contents() else: - self.current_node = self.current_node.add_node(p, code) + if node is None: + self.current_node = \ + self.current_node.add_parser(p, code) + else: + self.current_node = self.current_node.add_node(node) self.parsers.append(p) is_first = False @@ -360,16 +351,17 @@ class FastParser(use_metaclass(CachedFastParser)): self._line_offset += lines self._start += len(code_part) + 1 # +1 for newline - print(self.parsers[0].module.get_code()) + #print(self.parsers[0].module.get_code()) #for p in self.parsers: # print(p.module.get_code()) # print(p.module.start_pos, p.module.end_pos) - exit() + #exit() del self._code def _get_parser(self, code, nodes): h = hash(code) hashes = [n.hash for n in nodes] + node = None try: index = hashes.index(h) if nodes[index].code != code: @@ -394,7 +386,7 @@ class FastParser(use_metaclass(CachedFastParser)): p.user_scope = p.user_stmt.parent else: p.user_scope = self.scan_user_scope(m) or self.module - return p + return p, node def reset_caches(self): self._user_scope = None From bafb17001b5f2a770e87eb99117152fe6b7c9084 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sat, 13 Apr 2013 15:57:28 +0430 Subject: [PATCH 09/43] fix encoding problems with terminal --- jedi/_compatibility.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jedi/_compatibility.py b/jedi/_compatibility.py index 78abf874..b2980c4d 100644 --- a/jedi/_compatibility.py +++ b/jedi/_compatibility.py @@ -185,5 +185,7 @@ except ImportError: try: encoding = sys.stdout.encoding + if encoding is None: + encoding = 'utf-8' except AttributeError: encoding = 'ascii' From b08390c1368f5bef3119ede6cd61db566076b1a2 Mon Sep 17 00:00:00 2001 From: David Halter Date: Mon, 15 Apr 2013 10:22:27 +0430 Subject: [PATCH 10/43] some parser end positions changed --- jedi/common.py | 7 +++++-- jedi/evaluate.py | 1 + jedi/fast_parser.py | 7 +++++-- jedi/parsing.py | 2 ++ jedi/parsing_representation.py | 2 +- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/jedi/common.py b/jedi/common.py index 89eec32e..b12a5256 100644 --- a/jedi/common.py +++ b/jedi/common.py @@ -85,7 +85,7 @@ class NoErrorTokenizer(object): if self.closed: raise MultiLevelStopIteration() try: - last_previous = self.previous + self.last_previous = self.previous self.previous = self.current self.current = next(self.gen) except tokenize.TokenError: @@ -112,7 +112,7 @@ class NoErrorTokenizer(object): if c[0] == tokenize.ENDMARKER: self.current = self.previous - self.previous = last_previous + self.previous = self.last_previous raise MultiLevelStopIteration() # this is exactly the same check as in fast_parser, but this time with @@ -155,6 +155,9 @@ class NoErrorTokenizer(object): if tok in FLOWS or tok in breaks: self.in_flow = tok in FLOWS if not self.is_decorator and not self.in_flow: + print tok, c + if 6230 < c[2][0] < 6290: + print tok, c close() self.is_decorator = '@' == tok if not self.is_decorator: diff --git a/jedi/evaluate.py b/jedi/evaluate.py index 8d68b050..a7516992 100644 --- a/jedi/evaluate.py +++ b/jedi/evaluate.py @@ -373,6 +373,7 @@ def find_name(scope, name_str, position=None, search_global=False, comparison_func = lambda name: (name.start_pos) for nscope, name_list in scope_generator: + print nscope, name_list break_scopes = [] # here is the position stuff happening (sorting of variables) for name in sorted(name_list, key=comparison_func, reverse=True): diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 61030737..cd5bada8 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -317,7 +317,7 @@ class FastParser(use_metaclass(CachedFastParser)): is_first = True for code_part in parts: lines = code_part.count('\n') + 1 - if is_first or self._line_offset >= p.end_pos[0] - 1: + if is_first or self._line_offset >= p.end_pos[0]: indent = len(re.match(r'[ \t]*', code_part).group(0)) if is_first and self.current_node is not None: nodes = [self.current_node] @@ -330,6 +330,7 @@ class FastParser(use_metaclass(CachedFastParser)): nodes += self.current_node._old_children # check if code_part has already been parsed + print '#'*45,self._line_offset, p and p.end_pos, '\n', code_part p, node = self._get_parser(code_part, nodes) if is_first: @@ -347,11 +348,13 @@ class FastParser(use_metaclass(CachedFastParser)): self.parsers.append(p) is_first = False + else: + print '#'*45, self._line_offset, p.end_pos, 'theheck\n', code_part self._line_offset += lines self._start += len(code_part) + 1 # +1 for newline - #print(self.parsers[0].module.get_code()) + print(self.parsers[0].module.get_code()) #for p in self.parsers: # print(p.module.get_code()) # print(p.module.start_pos, p.module.end_pos) diff --git a/jedi/parsing.py b/jedi/parsing.py index 0ce923be..f01de43d 100644 --- a/jedi/parsing.py +++ b/jedi/parsing.py @@ -80,6 +80,8 @@ class Parser(object): if self.current[0] in (tokenize.NL, tokenize.NEWLINE): # we added a newline before, so we need to "remove" it again. self.end_pos = self._gen.previous[2] + if self.current[0] == tokenize.INDENT: + self.end_pos = self._gen.last_previous[2] self.start_pos = self.module.start_pos self.module.end_pos = self.end_pos diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index 3a7ff64a..df63df80 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -454,7 +454,7 @@ class Function(Scope): string += "def %s(%s):\n" % (self.name, params) string += super(Function, self).get_code(True, indention) if self.is_empty(): - string += "pass\n" + string += indention + 'pass\n' return string def is_empty(self): From 188ed33c4f9c50914f34378fce521d1ee09f05c3 Mon Sep 17 00:00:00 2001 From: David Halter Date: Mon, 15 Apr 2013 11:50:05 +0430 Subject: [PATCH 11/43] fixed parser change problems --- jedi/common.py | 6 +++--- jedi/evaluate.py | 1 - jedi/fast_parser.py | 26 +++++++++++++++++--------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/jedi/common.py b/jedi/common.py index b12a5256..6ab5cbd1 100644 --- a/jedi/common.py +++ b/jedi/common.py @@ -150,14 +150,12 @@ class NoErrorTokenizer(object): self.in_flow = False elif self.new_indent: self.parser_indent = indent + self.new_indent = False if not self.in_flow: if tok in FLOWS or tok in breaks: self.in_flow = tok in FLOWS if not self.is_decorator and not self.in_flow: - print tok, c - if 6230 < c[2][0] < 6290: - print tok, c close() self.is_decorator = '@' == tok if not self.is_decorator: @@ -165,6 +163,8 @@ class NoErrorTokenizer(object): self.new_indent = True if tok != '@': + if self.first_stmt and not self.new_indent: + self.parser_indent = indent self.first_stmt = False return c diff --git a/jedi/evaluate.py b/jedi/evaluate.py index a7516992..8d68b050 100644 --- a/jedi/evaluate.py +++ b/jedi/evaluate.py @@ -373,7 +373,6 @@ def find_name(scope, name_str, position=None, search_global=False, comparison_func = lambda name: (name.start_pos) for nscope, name_list in scope_generator: - print nscope, name_list break_scopes = [] # here is the position stuff happening (sorting of variables) for name in sorted(name_list, key=comparison_func, reverse=True): diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index cd5bada8..b1c9a172 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -112,21 +112,23 @@ class CachedFastParser(type): class ParserNode(object): def __init__(self, parser, code, parent=None): self.parent = parent - self.parser = parser self.code = code self.hash = hash(code) self.children = [] self._old_children = [] # must be created before new things are added to it. + self.save_contents(parser) + + def save_contents(self, parser): + self.parser = parser + try: # with fast_parser we have either 1 subscope or only statements. self._content_scope = self.parser.module.subscopes[0] except IndexError: self._content_scope = self.parser.module - self.save_contents() - def save_contents(self): scope = self._content_scope self._contents = {} for c in SCOPE_CONTENTS: @@ -159,7 +161,10 @@ class ParserNode(object): try: el = module.statements[0] except IndexError: - el = module.imports[0] + try: + el = module.imports[0] + except IndexError: + el = module.returns[0] return el.start_pos[1] def _set_items(self, parser, set_parent=False): @@ -172,6 +177,9 @@ class ParserNode(object): for i in items: i.parent = scope content += items + if str(parser.module.name) == 'ordering': + #print scope.subscopes + pass scope.is_generator |= parser.module.is_generator def add_node(self, node): @@ -330,15 +338,14 @@ class FastParser(use_metaclass(CachedFastParser)): nodes += self.current_node._old_children # check if code_part has already been parsed - print '#'*45,self._line_offset, p and p.end_pos, '\n', code_part + #print '#'*45,self._line_offset, p and p.end_pos, '\n', code_part p, node = self._get_parser(code_part, nodes) if is_first: if self.current_node is None: self.current_node = ParserNode(p, code) else: - self.current_node.parser = p - self.current_node.save_contents() + self.current_node.save_contents(p) else: if node is None: self.current_node = \ @@ -349,12 +356,13 @@ class FastParser(use_metaclass(CachedFastParser)): is_first = False else: - print '#'*45, self._line_offset, p.end_pos, 'theheck\n', code_part + #print '#'*45, self._line_offset, p.end_pos, 'theheck\n', code_part + pass self._line_offset += lines self._start += len(code_part) + 1 # +1 for newline - print(self.parsers[0].module.get_code()) + #print(self.parsers[0].module.get_code()) #for p in self.parsers: # print(p.module.get_code()) # print(p.module.start_pos, p.module.end_pos) From 9181410c479f16d3da5c069b8744f20f77ee6d2b Mon Sep 17 00:00:00 2001 From: David Halter Date: Wed, 17 Apr 2013 10:07:15 +0430 Subject: [PATCH 12/43] fixed a problem with functions in the beginning of a module --- jedi/fast_parser.py | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index b1c9a172..2413dea2 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -318,14 +318,13 @@ class FastParser(use_metaclass(CachedFastParser)): parts = self._split_parts(code) self.parsers[:] = [] - self._code = code - self._line_offset = 0 - self._start = 0 + line_offset = 0 + start = 0 p = None is_first = True for code_part in parts: lines = code_part.count('\n') + 1 - if is_first or self._line_offset >= p.end_pos[0]: + if is_first or line_offset >= p.end_pos[0]: indent = len(re.match(r'[ \t]*', code_part).group(0)) if is_first and self.current_node is not None: nodes = [self.current_node] @@ -338,8 +337,20 @@ class FastParser(use_metaclass(CachedFastParser)): nodes += self.current_node._old_children # check if code_part has already been parsed - #print '#'*45,self._line_offset, p and p.end_pos, '\n', code_part - p, node = self._get_parser(code_part, nodes) + #print '#'*45,line_offset, p and p.end_pos, '\n', code_part + p, node = self._get_parser(code_part, code[start:], + line_offset, nodes) + + if is_first and p.module.subscopes: + # special case, we cannot use a function subscope as a + # base scope, subscopes would save all the other contents + new, temp = self._get_parser('', '', 0, []) + if self.current_node is None: + self.current_node = ParserNode(new, code) + else: + self.current_node.save_contents(new) + self.parsers.append(new) + is_first = False if is_first: if self.current_node is None: @@ -356,20 +367,20 @@ class FastParser(use_metaclass(CachedFastParser)): is_first = False else: - #print '#'*45, self._line_offset, p.end_pos, 'theheck\n', code_part + #print '#'*45, line_offset, p.end_pos, 'theheck\n', code_part pass - self._line_offset += lines - self._start += len(code_part) + 1 # +1 for newline + line_offset += lines + start += len(code_part) + 1 # +1 for newline #print(self.parsers[0].module.get_code()) #for p in self.parsers: # print(p.module.get_code()) # print(p.module.start_pos, p.module.end_pos) #exit() - del self._code + del code - def _get_parser(self, code, nodes): + def _get_parser(self, code, parser_code, line_offset, nodes): h = hash(code) hashes = [n.hash for n in nodes] node = None @@ -378,15 +389,14 @@ class FastParser(use_metaclass(CachedFastParser)): if nodes[index].code != code: raise ValueError() except ValueError: - p = parsing.Parser(self._code[self._start:], - self.module_path, self.user_position, - offset=(self._line_offset, 0), + p = parsing.Parser(parser_code, self.module_path, + self.user_position, offset=(line_offset, 0), is_fast_parser=True, top_module=self.module) else: node = nodes.pop(index) p = node.parser m = p.module - m.line_offset += self._line_offset + 1 - m.start_pos[0] + m.line_offset += line_offset + 1 - m.start_pos[0] if self.user_position is not None and \ m.start_pos <= self.user_position <= m.end_pos: # It's important to take care of the whole user From d82f315ea6832e27a6e1ba6794fc90f160936c67 Mon Sep 17 00:00:00 2001 From: David Halter Date: Wed, 17 Apr 2013 13:39:02 +0430 Subject: [PATCH 13/43] don't use SubModule's in the scope list if it has a parent --- jedi/evaluate.py | 4 ++++ jedi/fast_parser.py | 1 + 2 files changed, 5 insertions(+) diff --git a/jedi/evaluate.py b/jedi/evaluate.py index 8d68b050..d5350a57 100644 --- a/jedi/evaluate.py +++ b/jedi/evaluate.py @@ -159,6 +159,10 @@ def get_names_of_scope(scope, position=None, star_search=True, in_func_scope = scope non_flow = scope.get_parent_until(pr.Flow, reverse=True) while scope: + if isinstance(scope, pr.SubModule) and scope.parent: + # we don't want submodules to report if we have modules. + scope = scope.parent + continue # `pr.Class` is used, because the parent is never `Class`. # Ignore the Flows, because the classes and functions care for that. # InstanceElement of Class is ignored, if it is not the start scope. diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 2413dea2..30724e37 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -392,6 +392,7 @@ class FastParser(use_metaclass(CachedFastParser)): p = parsing.Parser(parser_code, self.module_path, self.user_position, offset=(line_offset, 0), is_fast_parser=True, top_module=self.module) + p.module.parent = self.module else: node = nodes.pop(index) p = node.parser From 4e27e7d33510158a2889b75bd11f766e091aa1a7 Mon Sep 17 00:00:00 2001 From: David Halter Date: Wed, 17 Apr 2013 23:37:21 +0430 Subject: [PATCH 14/43] fixed a positioning bug in the own tokenizer --- jedi/common.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jedi/common.py b/jedi/common.py index 6ab5cbd1..45ec6f7b 100644 --- a/jedi/common.py +++ b/jedi/common.py @@ -102,8 +102,7 @@ class NoErrorTokenizer(object): debug.warning('indentation error on line %s, ignoring it' % self.current[2][0]) # add the starting line of the last position - self.offset = (self.offset[0] + self.current[2][0], - self.current[2][1]) + self.offset = self.current[2] self.gen = PushBackIterator(tokenize.generate_tokens( self.readline)) return self.__next__() From 43306429f96b6843dce39b1e43fabd9d133b67a3 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sun, 21 Apr 2013 22:55:12 +0430 Subject: [PATCH 15/43] fixed some little testing problems --- test/run.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/run.py b/test/run.py index 62178501..d0e9aff0 100755 --- a/test/run.py +++ b/test/run.py @@ -332,7 +332,8 @@ if __name__ == '__main__': cases += collect_dir_tests(completion_test_dir, test_files, True) def file_change(current, tests, fails): - current = os.path.basename(current) + if current is not None: + current = os.path.basename(current) print('%s \t\t %s tests and %s fails.' % (current, tests, fails)) def report(case, actual, desired): @@ -340,7 +341,7 @@ if __name__ == '__main__': return 0 else: print("\ttest fail @%d, actual = %s, desired = %s" - % (case.line_nr, actual, desired)) + % (case.line_nr - 1, actual, desired)) return 1 current = cases[0].path if cases else None From 80cbed2f9fe4f3888201d75efa497553cc2c4149 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sun, 21 Apr 2013 23:06:39 +0430 Subject: [PATCH 16/43] fixed some decorator stuff. --- jedi/fast_parser.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 4f022a5a..36265815 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -176,6 +176,11 @@ class ParserNode(object): if set_parent: for i in items: i.parent = scope + try: + for d in i.decorators: + d.parent = scope + except AttributeError: + pass content += items if str(parser.module.name) == 'ordering': #print scope.subscopes From ddc5b248f15c4a166d7c4c442574c7b2752b4a86 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sun, 21 Apr 2013 23:32:31 +0430 Subject: [PATCH 17/43] use correct parents --- jedi/fast_parser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 36265815..b1040cb8 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -175,10 +175,10 @@ class ParserNode(object): items = getattr(parser.module, c) if set_parent: for i in items: - i.parent = scope + i.parent = scope.use_as_parent try: for d in i.decorators: - d.parent = scope + d.parent = scope.use_as_parent except AttributeError: pass content += items From 45f29bfa318186d730a15b7fc68c0cedf6b2a514 Mon Sep 17 00:00:00 2001 From: David Halter Date: Wed, 24 Apr 2013 12:52:45 +0430 Subject: [PATCH 18/43] fixed a problem with non used decorators when splitting parts --- jedi/fast_parser.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index b1040cb8..555c5f24 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -176,11 +176,9 @@ class ParserNode(object): if set_parent: for i in items: i.parent = scope.use_as_parent - try: + if isinstance(i, (pr.Function, pr.Class)): for d in i.decorators: d.parent = scope.use_as_parent - except AttributeError: - pass content += items if str(parser.module.name) == 'ordering': #print scope.subscopes @@ -263,7 +261,10 @@ class FastParser(use_metaclass(CachedFastParser)): def add_part(): txt = '\n'.join(current_lines) if txt: - parts.append(txt) + if add_to_last and parts: + parts[-1] += '\n' + txt + else: + parts.append(txt) current_lines[:] = [] r_keyword = '^[ \t]*(def|class|@|%s)' % '|'.join(common.FLOWS) @@ -275,6 +276,7 @@ class FastParser(use_metaclass(CachedFastParser)): current_indent = 0 new_indent = False in_flow = False + add_to_last = False # All things within flows are simply being ignored. for i, l in enumerate(lines): # check for dedents @@ -289,6 +291,7 @@ class FastParser(use_metaclass(CachedFastParser)): new_indent = False if not in_flow: add_part() + add_to_last = False in_flow = False elif new_indent: current_indent = indent @@ -301,11 +304,14 @@ class FastParser(use_metaclass(CachedFastParser)): in_flow = m.group(1) in common.FLOWS if not is_decorator and not in_flow: add_part() - current_lines = [] + add_to_last = False is_decorator = '@' == m.group(1) if not is_decorator: current_indent += 1 # it must be higher new_indent = True + elif is_decorator: + is_decorator = False + add_to_last = True current_lines.append(l) add_part() @@ -379,10 +385,6 @@ class FastParser(use_metaclass(CachedFastParser)): start += len(code_part) + 1 # +1 for newline #print(self.parsers[0].module.get_code()) - #for p in self.parsers: - # print(p.module.get_code()) - # print(p.module.start_pos, p.module.end_pos) - #exit() del code def _get_parser(self, code, parser_code, line_offset, nodes): From 199d80fa02fd69d70006f4622300febd53154992 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sat, 27 Apr 2013 20:22:28 +0430 Subject: [PATCH 19/43] fix most rename tests --- jedi/fast_parser.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 555c5f24..7a447003 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -76,7 +76,7 @@ class Module(pr.Simple, pr.Module): @start_pos.setter def start_pos(self): """ ignore """ - raise AttributeError('TODO remove - just a check if everything works fine.') + raise NotImplementedError('TODO remove - just a check if everything works fine.') @property def end_pos(self): @@ -205,6 +205,7 @@ class FastParser(use_metaclass(CachedFastParser)): # set values like `pr.Module`. self.module_path = module_path self.user_position = user_position + self._user_scope = None self.current_node = None self.parsers = [] @@ -218,12 +219,12 @@ class FastParser(use_metaclass(CachedFastParser)): if self._user_scope is None: for p in self.parsers: if p.user_scope: - if self._user_scope is not None and not \ - isinstance(self._user_scope, pr.SubModule): + if isinstance(p.user_scope, pr.SubModule): continue self._user_scope = p.user_scope - if isinstance(self._user_scope, pr.SubModule): + if isinstance(self._user_scope, pr.SubModule) \ + or self._user_scope is None: self._user_scope = self.module return self._user_scope From e99f7a25fe60198d874e78befe89a9a49a15d676 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sat, 27 Apr 2013 20:55:51 +0430 Subject: [PATCH 20/43] exceptions should produce errors even in alternate run display --- test/run.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/run.py b/test/run.py index d0e9aff0..2736af23 100755 --- a/test/run.py +++ b/test/run.py @@ -344,10 +344,17 @@ if __name__ == '__main__': % (case.line_nr - 1, actual, desired)) return 1 + import traceback current = cases[0].path if cases else None count = fails = 0 for c in cases: - if c.run(report): + try: + if c.run(report): + tests_fail += 1 + fails += 1 + except Exception: + traceback.print_exc() + print("\ttest fail @%d" % (c.line_nr - 1)) tests_fail += 1 fails += 1 count += 1 From 4839f3d80e411e80978d6003b53085044d8e8a64 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sat, 27 Apr 2013 21:25:42 +0430 Subject: [PATCH 21/43] fix a scope problem with the new fast parser --- jedi/fast_parser.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 7a447003..d9df708e 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -140,6 +140,7 @@ class ParserNode(object): for key, c in self._contents.items(): setattr(scope, key, c) scope.is_generator = self._is_generator + self.parser.user_scope = None for c in self.children: c.reset_contents() @@ -180,9 +181,9 @@ class ParserNode(object): for d in i.decorators: d.parent = scope.use_as_parent content += items - if str(parser.module.name) == 'ordering': - #print scope.subscopes - pass + if isinstance(parser.user_scope, pr.SubModule) \ + and parser.start_pos <= parser.user_position < parser.end_pos: + parser.user_scope = scope scope.is_generator |= parser.module.is_generator def add_node(self, node): From 3627263cbebbf4ca01bb28f5ebb99054835b3cc0 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sat, 27 Apr 2013 21:36:57 +0430 Subject: [PATCH 22/43] fixed an IndexError --- jedi/fast_parser.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index d9df708e..339f7a01 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -165,7 +165,10 @@ class ParserNode(object): try: el = module.imports[0] except IndexError: - el = module.returns[0] + try: + el = module.returns[0] + except IndexError: + return self.parent.indent + 1 return el.start_pos[1] def _set_items(self, parser, set_parent=False): From 792808d8351ceb6268fa5f2a60c42aec9a2696f7 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sat, 27 Apr 2013 21:47:54 +0430 Subject: [PATCH 23/43] fix another IndexError, due to empty files (e.g. __init__.py) --- jedi/fast_parser.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 339f7a01..8c8e6568 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -331,6 +331,10 @@ class FastParser(use_metaclass(CachedFastParser)): def _parse(self, code): """ :type code: str """ + def empty_parser(): + new, temp = self._get_parser('', '', 0, []) + return new + parts = self._split_parts(code) self.parsers[:] = [] @@ -338,6 +342,7 @@ class FastParser(use_metaclass(CachedFastParser)): start = 0 p = None is_first = True + for code_part in parts: lines = code_part.count('\n') + 1 if is_first or line_offset >= p.end_pos[0]: @@ -360,7 +365,7 @@ class FastParser(use_metaclass(CachedFastParser)): if is_first and p.module.subscopes: # special case, we cannot use a function subscope as a # base scope, subscopes would save all the other contents - new, temp = self._get_parser('', '', 0, []) + new = empty_parser() if self.current_node is None: self.current_node = ParserNode(new, code) else: @@ -389,6 +394,9 @@ class FastParser(use_metaclass(CachedFastParser)): line_offset += lines start += len(code_part) + 1 # +1 for newline + if not self.parsers: + self.parsers.append(empty_parser()) + #print(self.parsers[0].module.get_code()) del code From 26ce32ec6b02f0b9484404a733db0834dd3cbd69 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sat, 27 Apr 2013 22:47:26 +0430 Subject: [PATCH 24/43] fix some end_pos problems --- jedi/fast_parser.py | 15 ++++----------- jedi/parsing.py | 3 ++- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 8c8e6568..c5243cf7 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -19,7 +19,7 @@ SCOPE_CONTENTS = ['asserts', 'subscopes', 'imports', 'statements', 'returns'] class Module(pr.Simple, pr.Module): def __init__(self, parsers): - self._end_pos = None, None + self.end_pos = None, None super(Module, self).__init__(self, (1, 0)) self.parsers = parsers self.reset_caches() @@ -78,16 +78,6 @@ class Module(pr.Simple, pr.Module): """ ignore """ raise NotImplementedError('TODO remove - just a check if everything works fine.') - @property - def end_pos(self): - return self._end_pos - - @end_pos.setter - def end_pos(self, value): - if None in self._end_pos \ - or None not in value and self._end_pos < value: - self._end_pos = value - def __repr__(self): return "<%s: %s@%s-%s>" % (type(self).__name__, self.name, self.start_pos[0], self.end_pos[0]) @@ -397,6 +387,9 @@ class FastParser(use_metaclass(CachedFastParser)): if not self.parsers: self.parsers.append(empty_parser()) + + self.module.end_pos = self.parsers[-1].end_pos + #print(self.parsers[0].module.get_code()) del code diff --git a/jedi/parsing.py b/jedi/parsing.py index 7483567f..a9605174 100644 --- a/jedi/parsing.py +++ b/jedi/parsing.py @@ -430,7 +430,8 @@ class Parser(object): except (StopIteration, common.MultiLevelStopIteration): # on finish, set end_pos correctly s = self.scope - while s is not None: + while s is not None and (not isinstance(s, pr.Module) + and isinstance(s, pr.SubModule)): s.end_pos = self.end_pos s = s.parent raise From aab3b1c627098cc70a0b14278e4011f3a1cd7a6c Mon Sep 17 00:00:00 2001 From: David Halter Date: Sat, 27 Apr 2013 22:49:45 +0430 Subject: [PATCH 25/43] also remove old start_pos stuff --- jedi/fast_parser.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index c5243cf7..0be31057 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -19,11 +19,13 @@ SCOPE_CONTENTS = ['asserts', 'subscopes', 'imports', 'statements', 'returns'] class Module(pr.Simple, pr.Module): def __init__(self, parsers): - self.end_pos = None, None super(Module, self).__init__(self, (1, 0)) self.parsers = parsers self.reset_caches() + self.start_pos = 1, 0 + self.end_pos = None, None + def reset_caches(self): """ This module does a whole lot of caching, because it uses different parsers. """ @@ -68,16 +70,6 @@ class Module(pr.Simple, pr.Module): self.cache[key] = dct return self.cache[key] - @property - def start_pos(self): - """ overwrite start_pos of Simple """ - return 1, 0 - - @start_pos.setter - def start_pos(self): - """ ignore """ - raise NotImplementedError('TODO remove - just a check if everything works fine.') - def __repr__(self): return "<%s: %s@%s-%s>" % (type(self).__name__, self.name, self.start_pos[0], self.end_pos[0]) From 3feac63468241d9b10251f59978fb493373a44bb Mon Sep 17 00:00:00 2001 From: David Halter Date: Sat, 27 Apr 2013 23:45:48 +0430 Subject: [PATCH 26/43] fix global_vars --- jedi/fast_parser.py | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 0be31057..18e2a018 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -29,36 +29,20 @@ class Module(pr.Simple, pr.Module): def reset_caches(self): """ This module does a whole lot of caching, because it uses different parsers. """ - self.cache = {} + self._used_names = None for p in self.parsers: p.user_scope = None p.user_stmt = None - def _get(self, name, operation, execute=False, *args, **kwargs): - key = (name, args, frozenset(kwargs.items())) - if key not in self.cache: - if execute: - objs = (getattr(p.module, name)(*args, **kwargs) - for p in self.parsers) - else: - objs = (getattr(p.module, name) for p in self.parsers) - self.cache[key] = reduce(operation, objs) - return self.cache[key] - def __getattr__(self, name): - if name == 'global_vars': - return self._get(name, operator.add) - elif name.startswith('__'): + if name.startswith('__'): raise AttributeError('Not available!') else: return getattr(self.parsers[0].module, name) @property def used_names(self): - if not self.parsers: - raise NotImplementedError("Parser doesn't exist.") - key = 'used_names' - if key not in self.cache: + if self._used_names is None: dct = {} for p in self.parsers: for k, statement_set in p.module.used_names.items(): @@ -67,8 +51,8 @@ class Module(pr.Simple, pr.Module): else: dct[k] = set(statement_set) - self.cache[key] = dct - return self.cache[key] + self._used_names = dct + return self._used_names def __repr__(self): return "<%s: %s@%s-%s>" % (type(self).__name__, self.name, @@ -107,9 +91,9 @@ class ParserNode(object): try: # with fast_parser we have either 1 subscope or only statements. - self._content_scope = self.parser.module.subscopes[0] + self._content_scope = parser.module.subscopes[0] except IndexError: - self._content_scope = self.parser.module + self._content_scope = parser.module scope = self._content_scope self._contents = {} @@ -124,6 +108,11 @@ class ParserNode(object): scope.is_generator = self._is_generator self.parser.user_scope = None + if self.parent is None: + # Global vars of the first one can be deleted, in the global scope + # they make no sense. + self.parser.module.global_vars = [] + for c in self.children: c.reset_contents() @@ -169,6 +158,13 @@ class ParserNode(object): if isinstance(parser.user_scope, pr.SubModule) \ and parser.start_pos <= parser.user_position < parser.end_pos: parser.user_scope = scope + + # global_vars + cur = self + while cur.parent is not None: + cur = cur.parent + cur.parser.module.global_vars += parser.module.global_vars + scope.is_generator |= parser.module.is_generator def add_node(self, node): @@ -379,7 +375,6 @@ class FastParser(use_metaclass(CachedFastParser)): if not self.parsers: self.parsers.append(empty_parser()) - self.module.end_pos = self.parsers[-1].end_pos #print(self.parsers[0].module.get_code()) From c675706877eee51f185be8c9b62ac64843562947 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sat, 27 Apr 2013 23:47:18 +0430 Subject: [PATCH 27/43] clean up a little bit --- jedi/fast_parser.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 18e2a018..618f4a33 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -4,9 +4,8 @@ anything changes, it only reparses the changed parts. But because it's not finished (and still not working as I want), I won't document it any further. """ import re -import operator -from jedi._compatibility import use_metaclass, reduce, property +from jedi._compatibility import use_metaclass, property from jedi import settings from jedi import parsing from jedi import parsing_representation as pr @@ -299,12 +298,6 @@ class FastParser(use_metaclass(CachedFastParser)): current_lines.append(l) add_part() - for p in parts: - #print '#####################################' - #print p - #print len(p.splitlines()) - pass - return parts def _parse(self, code): From 9988c49097df0a06e98889c76f53a73c7455a0ee Mon Sep 17 00:00:00 2001 From: David Halter Date: Sun, 28 Apr 2013 00:24:52 +0430 Subject: [PATCH 28/43] fix pushback problem with tokenizer --- jedi/common.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jedi/common.py b/jedi/common.py index 1f8a5270..fac6aa7a 100644 --- a/jedi/common.py +++ b/jedi/common.py @@ -85,10 +85,11 @@ class PushBackIterator(object): class NoErrorTokenizer(object): def __init__(self, readline, offset=(0, 0), is_fast_parser=False): self.readline = readline - self.gen = PushBackIterator(tokenize.generate_tokens(readline)) + self.gen = tokenize.generate_tokens(readline) self.offset = offset self.closed = False self.is_first = True + self.push_backs = [] # fast parser options self.is_fast_parser = is_fast_parser @@ -100,7 +101,7 @@ class NoErrorTokenizer(object): self.first_stmt = True def push_last_back(self): - self.gen.push_back(self.current) + self.push_backs.append(self.current) def next(self): """ Python 2 Compatibility """ @@ -109,6 +110,8 @@ class NoErrorTokenizer(object): def __next__(self): if self.closed: raise MultiLevelStopIteration() + if self.push_backs: + return self.push_backs.pop(0) self.last_previous = self.previous self.previous = self.current From 4b36fb2f6f96d4e244ee782e455f0c1035703551 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sun, 28 Apr 2013 21:10:29 +0430 Subject: [PATCH 29/43] corrected some doctest problems --- jedi/evaluate.py | 2 +- jedi/parsing_representation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jedi/evaluate.py b/jedi/evaluate.py index 0d6f1df0..8e8cc921 100644 --- a/jedi/evaluate.py +++ b/jedi/evaluate.py @@ -131,7 +131,7 @@ def get_names_of_scope(scope, position=None, star_search=True, ... def func(): ... y = None ... ''') - >>> scope = parser.scope.subscopes[0] + >>> scope = parser.module.subscopes[0] >>> scope diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index d51f07e2..55d4904f 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -22,7 +22,7 @@ The easiest way to play with this module is to use :class:`parsing.Parser`. >>> parser = Parser('import os', 'example.py') >>> submodule = parser.scope >>> submodule - + Any subclasses of :class:`Scope`, including :class:`SubModule` has attribute :attr:`imports `. This attribute has import From 7ad156bde771374ea9ae6911246952f166b46a77 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sun, 28 Apr 2013 21:19:05 +0430 Subject: [PATCH 30/43] fixed a problem with 'None' returns --- jedi/fast_parser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 618f4a33..16e86d2e 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -136,7 +136,7 @@ class ParserNode(object): el = module.imports[0] except IndexError: try: - el = module.returns[0] + el = [r for r in module.returns if r is not None][0] except IndexError: return self.parent.indent + 1 return el.start_pos[1] @@ -149,6 +149,8 @@ class ParserNode(object): items = getattr(parser.module, c) if set_parent: for i in items: + if i is None: + continue # happens with empty returns i.parent = scope.use_as_parent if isinstance(i, (pr.Function, pr.Class)): for d in i.decorators: From 56ca0cbfaa8731791c9697a2d88686a466a3e0e7 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sun, 28 Apr 2013 21:30:26 +0430 Subject: [PATCH 31/43] skip unit test until this will be fixed --- test/test_regression.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_regression.py b/test/test_regression.py index 3952e6b7..e0a9cf20 100755 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -534,6 +534,8 @@ def test_settings_module(): assert cache.settings is settings + +@unittest.skip("not yet fully fixed") def test_no_duplicate_modules(): """ Make sure that import hack works as expected. From 3eeecff1a195ddfaae93928ca27c817d70053e8e Mon Sep 17 00:00:00 2001 From: David Halter Date: Sun, 28 Apr 2013 22:12:32 +0430 Subject: [PATCH 32/43] fixed some position issues --- jedi/evaluate.py | 6 +++--- jedi/parsing.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/jedi/evaluate.py b/jedi/evaluate.py index 8e8cc921..6dc1bd9c 100644 --- a/jedi/evaluate.py +++ b/jedi/evaluate.py @@ -133,20 +133,20 @@ def get_names_of_scope(scope, position=None, star_search=True, ... ''') >>> scope = parser.module.subscopes[0] >>> scope - + `get_names_of_scope` is a generator. First it yields names from most inner scope. >>> pairs = list(get_names_of_scope(scope)) >>> pairs[0] - (, []) + (, []) Then it yield the names from one level outer scope. For this example, this is the most outer scope. >>> pairs[1] - (, [, ]) + (, [, ]) Finally, it yields names from builtin, if `include_builtin` is true (default). diff --git a/jedi/parsing.py b/jedi/parsing.py index a9605174..26390604 100644 --- a/jedi/parsing.py +++ b/jedi/parsing.py @@ -430,8 +430,8 @@ class Parser(object): except (StopIteration, common.MultiLevelStopIteration): # on finish, set end_pos correctly s = self.scope - while s is not None and (not isinstance(s, pr.Module) - and isinstance(s, pr.SubModule)): + while s is not None and not (isinstance(s, pr.Module) + and not isinstance(s, pr.SubModule)): s.end_pos = self.end_pos s = s.parent raise From 841f4d8423fbf441ba153aa41ae09f9a8a9bb034 Mon Sep 17 00:00:00 2001 From: David Halter Date: Sun, 28 Apr 2013 22:24:19 +0430 Subject: [PATCH 33/43] skip one regression test --- test/test_regression.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_regression.py b/test/test_regression.py index e0a9cf20..57e70ec2 100755 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -18,6 +18,8 @@ from jedi._compatibility import is_py25, utf8, unicode from jedi import api api_classes = api.api_classes +import pytest + #jedi.set_debug_function(jedi.debug.print_to_stdout) @@ -535,7 +537,7 @@ def test_settings_module(): -@unittest.skip("not yet fully fixed") +@pytest.mark.skip("not yet fully fixed") def test_no_duplicate_modules(): """ Make sure that import hack works as expected. From 0f3454f897803468f55450206fe4522a013950c8 Mon Sep 17 00:00:00 2001 From: David Halter Date: Mon, 29 Apr 2013 15:14:17 +0430 Subject: [PATCH 34/43] fix test problems, thx @tkf --- jedi/fast_parser.py | 2 +- test/test_regression.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 16e86d2e..bdf7ecc7 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -10,7 +10,7 @@ from jedi import settings from jedi import parsing from jedi import parsing_representation as pr from jedi import cache -import common +from jedi import common SCOPE_CONTENTS = ['asserts', 'subscopes', 'imports', 'statements', 'returns'] diff --git a/test/test_regression.py b/test/test_regression.py index 57e70ec2..6c872d51 100755 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -536,8 +536,6 @@ def test_settings_module(): assert cache.settings is settings - -@pytest.mark.skip("not yet fully fixed") def test_no_duplicate_modules(): """ Make sure that import hack works as expected. From f535267a9b6cbc86484acfaae0a5becf35143c3f Mon Sep 17 00:00:00 2001 From: David Halter Date: Tue, 30 Apr 2013 21:24:44 +0430 Subject: [PATCH 35/43] fixed a memory leak --- jedi/fast_parser.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index bdf7ecc7..81740a59 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -81,7 +81,6 @@ class ParserNode(object): self.hash = hash(code) self.children = [] - self._old_children = [] # must be created before new things are added to it. self.save_contents(parser) @@ -97,13 +96,16 @@ class ParserNode(object): scope = self._content_scope self._contents = {} for c in SCOPE_CONTENTS: - self._contents[c] = getattr(scope, c) + self._contents[c] = list(getattr(scope, c)) self._is_generator = scope.is_generator + self.old_children = self.children + self.children = [] + def reset_contents(self): scope = self._content_scope for key, c in self._contents.items(): - setattr(scope, key, c) + setattr(scope, key, list(c)) scope.is_generator = self._is_generator self.parser.user_scope = None @@ -115,10 +117,11 @@ class ParserNode(object): for c in self.children: c.reset_contents() - def parent_until_indent(self, indent): - if self.indent >= indent and self.parent: - self._old_children = [] - return self.parent.parent_until_indent(indent) + def parent_until_indent(self, indent=None): + if indent is None or self.indent >= indent and self.parent: + self.old_children = [] + if self.parent: + return self.parent.parent_until_indent(indent) return self @property @@ -172,15 +175,12 @@ class ParserNode(object): """Adding a node means adding a node that was already added earlier""" self.children.append(node) self._set_items(node.parser) - node._old_children = node.children + node.old_children = node.children node.children = [] return node def add_parser(self, parser, code): - node = ParserNode(parser, code, self) - self._set_items(parser, set_parent=True) - self.children.append(node) - return node + return self.add_node(ParserNode(parser, code, self)) class FastParser(use_metaclass(CachedFastParser)): @@ -328,7 +328,7 @@ class FastParser(use_metaclass(CachedFastParser)): self.current_node = \ self.current_node.parent_until_indent(indent) - nodes += self.current_node._old_children + nodes += self.current_node.old_children # check if code_part has already been parsed #print '#'*45,line_offset, p and p.end_pos, '\n', code_part @@ -367,7 +367,9 @@ class FastParser(use_metaclass(CachedFastParser)): line_offset += lines start += len(code_part) + 1 # +1 for newline - if not self.parsers: + if self.parsers: + self.current_node = self.current_node.parent_until_indent() + else: self.parsers.append(empty_parser()) self.module.end_pos = self.parsers[-1].end_pos From 6bf64b2ed7d11a01fecbc01ca3fc4988a0ad9583 Mon Sep 17 00:00:00 2001 From: David Halter Date: Tue, 30 Apr 2013 21:41:46 +0430 Subject: [PATCH 36/43] fixed speed problems --- jedi/fast_parser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 81740a59..e37ee9a1 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -340,7 +340,7 @@ class FastParser(use_metaclass(CachedFastParser)): # base scope, subscopes would save all the other contents new = empty_parser() if self.current_node is None: - self.current_node = ParserNode(new, code) + self.current_node = ParserNode(new, '') else: self.current_node.save_contents(new) self.parsers.append(new) @@ -348,13 +348,13 @@ class FastParser(use_metaclass(CachedFastParser)): if is_first: if self.current_node is None: - self.current_node = ParserNode(p, code) + self.current_node = ParserNode(p, code_part) else: self.current_node.save_contents(p) else: if node is None: self.current_node = \ - self.current_node.add_parser(p, code) + self.current_node.add_parser(p, code_part) else: self.current_node = self.current_node.add_node(node) self.parsers.append(p) From fa32c0a765f334bf2b613de627bed24051ca7f1a Mon Sep 17 00:00:00 2001 From: David Halter Date: Tue, 30 Apr 2013 22:06:46 +0430 Subject: [PATCH 37/43] fix a set_parent problem --- jedi/fast_parser.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index e37ee9a1..4dbc3a61 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -120,7 +120,7 @@ class ParserNode(object): def parent_until_indent(self, indent=None): if indent is None or self.indent >= indent and self.parent: self.old_children = [] - if self.parent: + if self.parent is not None: return self.parent.parent_until_indent(indent) return self @@ -171,16 +171,16 @@ class ParserNode(object): scope.is_generator |= parser.module.is_generator - def add_node(self, node): + def add_node(self, node, set_parent=False): """Adding a node means adding a node that was already added earlier""" self.children.append(node) - self._set_items(node.parser) + self._set_items(node.parser, set_parent=set_parent) node.old_children = node.children node.children = [] return node def add_parser(self, parser, code): - return self.add_node(ParserNode(parser, code, self)) + return self.add_node(ParserNode(parser, code, self), True) class FastParser(use_metaclass(CachedFastParser)): From f5aaeaa4286236bbf023a7a59e1ac09c4c5d4caf Mon Sep 17 00:00:00 2001 From: David Halter Date: Wed, 1 May 2013 14:31:41 +0430 Subject: [PATCH 38/43] fast parser splitting is now working better --- jedi/common.py | 5 +++-- jedi/fast_parser.py | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/jedi/common.py b/jedi/common.py index fac6aa7a..fd0ddfd8 100644 --- a/jedi/common.py +++ b/jedi/common.py @@ -96,7 +96,7 @@ class NoErrorTokenizer(object): self.current = self.previous = [None, None, (0, 0), (0, 0), ''] self.in_flow = False self.new_indent = False - self.parser_indent = 0 + self.parser_indent = self.old_parser_indent = 0 self.is_decorator = False self.first_stmt = True @@ -153,7 +153,7 @@ class NoErrorTokenizer(object): if indent < self.parser_indent: # -> dedent self.parser_indent = indent self.new_indent = False - if not self.in_flow: + if not self.in_flow or indent < self.old_parser_indent: close() self.in_flow = False elif self.new_indent: @@ -167,6 +167,7 @@ class NoErrorTokenizer(object): close() self.is_decorator = '@' == tok if not self.is_decorator: + self.old_parser_indent = self.parser_indent self.parser_indent += 1 # new scope: must be higher self.new_indent = True diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 4dbc3a61..b8188751 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -258,6 +258,7 @@ class FastParser(use_metaclass(CachedFastParser)): parts = [] is_decorator = False current_indent = 0 + old_indent = 0 new_indent = False in_flow = False add_to_last = False @@ -273,7 +274,7 @@ class FastParser(use_metaclass(CachedFastParser)): if indent < current_indent: # -> dedent current_indent = indent new_indent = False - if not in_flow: + if not in_flow or indent < old_indent: add_part() add_to_last = False in_flow = False @@ -291,6 +292,7 @@ class FastParser(use_metaclass(CachedFastParser)): add_to_last = False is_decorator = '@' == m.group(1) if not is_decorator: + old_indent = current_indent current_indent += 1 # it must be higher new_indent = True elif is_decorator: @@ -391,6 +393,9 @@ class FastParser(use_metaclass(CachedFastParser)): is_fast_parser=True, top_module=self.module) p.module.parent = self.module else: + if nodes[index] != self.current_node: + offset = int(nodes[0] == self.current_node) + self.current_node.old_children.pop(index - offset) node = nodes.pop(index) p = node.parser m = p.module From 01be1143864b0307fe23236efcd2ad5fcacbe215 Mon Sep 17 00:00:00 2001 From: David Halter Date: Wed, 1 May 2013 21:07:37 +0430 Subject: [PATCH 39/43] renaming tests are now possible outside of renaming.py --- test/run.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/run.py b/test/run.py index 2736af23..013573ba 100755 --- a/test/run.py +++ b/test/run.py @@ -126,6 +126,10 @@ class IntegrationTestCase(object): self.path = path self.skip = None + @property + def module_name(self): + return re.sub('.*/|\.py', '', self.path) + def __repr__(self): name = os.path.basename(self.path) if self.path else None return '<%s: %s:%s:%s>' % (self.__class__.__name__, @@ -203,7 +207,7 @@ class IntegrationTestCase(object): # this means that there is a module specified wanted.append(pos_tup) else: - wanted.append(('renaming', self.line_nr + pos_tup[0], + wanted.append((self.module_name, self.line_nr + pos_tup[0], pos_tup[1])) return compare_cb(self, compare, sorted(wanted)) From aebfb3d162b3fb4b813f3de1585499d2a21e6ea4 Mon Sep 17 00:00:00 2001 From: David Halter Date: Thu, 2 May 2013 11:33:14 +0430 Subject: [PATCH 40/43] fix pass indentation in functions --- jedi/parsing_representation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jedi/parsing_representation.py b/jedi/parsing_representation.py index 55d4904f..5fd2c580 100644 --- a/jedi/parsing_representation.py +++ b/jedi/parsing_representation.py @@ -455,7 +455,7 @@ class Function(Scope): string += "def %s(%s):\n" % (self.name, params) string += super(Function, self).get_code(True, indention) if self.is_empty(): - string += indention + 'pass\n' + string += 'pass\n' return string def is_empty(self): From b820d1ee27d48c86e05286f74dbcd3545d831df4 Mon Sep 17 00:00:00 2001 From: David Halter Date: Thu, 2 May 2013 17:58:06 +0430 Subject: [PATCH 41/43] set user_scope right in fast_parser --- jedi/fast_parser.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index b8188751..28809d3d 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -89,11 +89,11 @@ class ParserNode(object): try: # with fast_parser we have either 1 subscope or only statements. - self._content_scope = parser.module.subscopes[0] + self.content_scope = parser.module.subscopes[0] except IndexError: - self._content_scope = parser.module + self.content_scope = parser.module - scope = self._content_scope + scope = self.content_scope self._contents = {} for c in SCOPE_CONTENTS: self._contents[c] = list(getattr(scope, c)) @@ -103,11 +103,11 @@ class ParserNode(object): self.children = [] def reset_contents(self): - scope = self._content_scope + scope = self.content_scope for key, c in self._contents.items(): setattr(scope, key, list(c)) scope.is_generator = self._is_generator - self.parser.user_scope = None + self.parser.user_scope = self.parser.module if self.parent is None: # Global vars of the first one can be deleted, in the global scope @@ -146,7 +146,7 @@ class ParserNode(object): def _set_items(self, parser, set_parent=False): # insert parser objects into current structure - scope = self._content_scope + scope = self.content_scope for c in SCOPE_CONTENTS: content = getattr(scope, c) items = getattr(parser.module, c) @@ -159,9 +159,6 @@ class ParserNode(object): for d in i.decorators: d.parent = scope.use_as_parent content += items - if isinstance(parser.user_scope, pr.SubModule) \ - and parser.start_pos <= parser.user_position < parser.end_pos: - parser.user_scope = scope # global_vars cur = self @@ -226,14 +223,12 @@ class FastParser(use_metaclass(CachedFastParser)): self._parse(code) - def scan_user_scope(self, sub_module): - """ Scan with self.user_position. - :type sub_module: pr.SubModule - """ + def _scan_user_scope(self, sub_module): + """ Scan with self.user_position. """ for scope in sub_module.statements + sub_module.subscopes: if isinstance(scope, pr.Scope): if scope.start_pos <= self.user_position <= scope.end_pos: - return self.scan_user_scope(scope) or scope + return self._scan_user_scope(scope) or scope return None def _split_parts(self, code): @@ -359,6 +354,12 @@ class FastParser(use_metaclass(CachedFastParser)): self.current_node.add_parser(p, code_part) else: self.current_node = self.current_node.add_node(node) + + if self.current_node.parent and (isinstance(p.user_scope, + pr.SubModule) or p.user_scope is None) \ + and p.start_pos <= self.user_position < p.end_pos: + p.user_scope = self.current_node.parent.content_scope + self.parsers.append(p) is_first = False @@ -409,7 +410,8 @@ class FastParser(use_metaclass(CachedFastParser)): if p.user_stmt: p.user_scope = p.user_stmt.parent else: - p.user_scope = self.scan_user_scope(m) or self.module + p.user_scope = self._scan_user_scope(m) or m + return p, node def reset_caches(self): From 4bb4176296b4dd15c38cabb39a179a0c2048cdd7 Mon Sep 17 00:00:00 2001 From: David Halter Date: Thu, 2 May 2013 18:56:22 +0430 Subject: [PATCH 42/43] fixed a position problem --- jedi/fast_parser.py | 4 ++-- test/test_regression.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index 28809d3d..f8465410 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -355,7 +355,7 @@ class FastParser(use_metaclass(CachedFastParser)): else: self.current_node = self.current_node.add_node(node) - if self.current_node.parent and (isinstance(p.user_scope, + if self.current_node.parent and (isinstance(p.user_scope, pr.SubModule) or p.user_scope is None) \ and p.start_pos <= self.user_position < p.end_pos: p.user_scope = self.current_node.parent.content_scope @@ -402,7 +402,7 @@ class FastParser(use_metaclass(CachedFastParser)): m = p.module m.line_offset += line_offset + 1 - m.start_pos[0] if self.user_position is not None and \ - m.start_pos <= self.user_position <= m.end_pos: + m.start_pos[0] <= self.user_position[0] <= m.end_pos[0]: # It's important to take care of the whole user # positioning stuff, if no reparsing is being done. p.user_stmt = m.get_statement_for_position( diff --git a/test/test_regression.py b/test/test_regression.py index 6c872d51..686bea72 100755 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -300,10 +300,12 @@ class TestRegression(TestBase): def test_unicode_attribute(self): """ github jedi-vim issue #94 """ - s1 = utf8('#-*- coding: utf-8 -*-\nclass Person():\n name = "e"\n\nPerson().name.') + s1 = utf8('#-*- coding: utf-8 -*-\nclass Person():\n' + ' name = "e"\n\nPerson().name.') completions1 = self.complete(s1) assert 'strip' in [c.word for c in completions1] - s2 = utf8('#-*- coding: utf-8 -*-\nclass Person():\n name = "é"\n\nPerson().name.') + s2 = utf8('#-*- coding: utf-8 -*-\nclass Person():\n' + ' name = "é"\n\nPerson().name.') completions2 = self.complete(s2) assert 'strip' in [c.word for c in completions2] From 302d06de8537b2162e7b374b1134458e06708e9a Mon Sep 17 00:00:00 2001 From: David Halter Date: Fri, 3 May 2013 01:14:41 +0430 Subject: [PATCH 43/43] fix end_pos stuff in parser (no (None, None) end_pos anymore --- jedi/fast_parser.py | 1 + jedi/parsing.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py index f8465410..bab9c3fe 100644 --- a/jedi/fast_parser.py +++ b/jedi/fast_parser.py @@ -357,6 +357,7 @@ class FastParser(use_metaclass(CachedFastParser)): if self.current_node.parent and (isinstance(p.user_scope, pr.SubModule) or p.user_scope is None) \ + and self.user_position \ and p.start_pos <= self.user_position < p.end_pos: p.user_scope = self.current_node.parent.content_scope diff --git a/jedi/parsing.py b/jedi/parsing.py index 26390604..e69dad6b 100644 --- a/jedi/parsing.py +++ b/jedi/parsing.py @@ -430,8 +430,11 @@ class Parser(object): except (StopIteration, common.MultiLevelStopIteration): # on finish, set end_pos correctly s = self.scope - while s is not None and not (isinstance(s, pr.Module) - and not isinstance(s, pr.SubModule)): + while s is not None: + if isinstance(s, pr.Module) \ + and not isinstance(s, pr.SubModule): + self.module.end_pos = self.end_pos + break s.end_pos = self.end_pos s = s.parent raise