From fe0547445967f3ca3543da915fcb38d403b707a1 Mon Sep 17 00:00:00 2001 From: David Halter Date: Fri, 14 Dec 2012 12:19:07 +0100 Subject: [PATCH] added a first version of fast_parser --- jedi/fast_parser.py | 138 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 jedi/fast_parser.py diff --git a/jedi/fast_parser.py b/jedi/fast_parser.py new file mode 100644 index 00000000..f4f09431 --- /dev/null +++ b/jedi/fast_parser.py @@ -0,0 +1,138 @@ +import re +import operator +from functools import reduce + +import parsing +from _compatibility import use_metaclass + +parser_cache = {} + + +class Module(parsing.Simple, parsing.Module): + def __init__(self, parsers): + super(Module, self).__init__((0,0)) + self.parsers = parsers + self.reset_caches() + + self.subscopes = [] + self.imports = [] + self.statements = [] + self.asserts = [] + + def reset_caches(self): + """ This module does a whole lot of caching, because it uses different + parsers. """ + self.cache = {} + self.modules = [p.module for p in self.parsers] + + def _get(self, name, operation, *args, **kwargs): + key = (name, args, frozenset(kwargs.items())) + if key not in self.cache: + objs = (getattr(m, name)(*args, **kwargs) for m in self.modules) + self.cache[key] = reduce(operation, objs) + 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 + } + if name in operators: + return lambda *args, **kwargs: self._get(name, operators[name], + *args, **kwargs) + elif name in properties: + return self._get(name, properties[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(self) + if s: + self.cache[key] = s + break + return self.cache[key] + + @property + def docstr(self): + return self.modules[0].docstr + + @property + def name(self): + return self.modules[0].name + + @property + def is_builtin(self): + return self.modules[0].is_builtin + + def __repr__(self): + return "<%s: %s@%s-%s>" % (type(self).__name__, self.name, + self.start_pos[0], self.end_pos[0]) + + +class CachedFastParser(type): + """ This is a metaclass for caching `FastParser`. """ + def __call__(self, code, module_path=None, user_position=None): + if module_path is None or module_path not in parser_cache: + p = super(CachedFastParser, self).__call__(code, module_path) + parser_cache[module_path] = p + else: + p = parser_cache[module_path] + p.update(code, user_position) + return p + + +class FastParser(use_metaclass(CachedFastParser)): + def __init__(self, code, module_path=None, user_position=None): + # set values like `parsing.Module`. + self.module_path = module_path + self.user_position = user_position + + self.parsers = [] + self.module = Module(self.parsers) + self._parse(code) + + self.reset_caches() + + @property + def user_scope(self): + if self._user_scope is None: + for p in self.parsers: + if p.user_scope: + self._user_scope = p.user_scope + return self._user_scope + + @property + def user_stmt(self): + if self._user_stmt is None: + for p in self.parsers: + if p.user_stmt: + self._user_stmt = p.user_stmt + return self._user_stmt + + def update(self, code, user_position=None): + self.user_position = user_position + self._parse(code) + self.reset_caches() + + def _parse(self, code): + parts = re.split(r'\n(?:def|class).*?(?!\n(?:def|class))') + line_offset = 0 + for p in parts: + lines = p.count('\n') + p = parsing.PyFuzzyParser(p, self.module_path, self.user_position, + line_offset=line_offset, stop_on_scope=True) + line_offset += lines + self.parsers.append(p) + + def reset_caches(self): + self._user_scope = None + self.module.reset_caches()