1
0
forked from VimPlug/jedi

Added a grammar param to the parser.

This commit is contained in:
Dave Halter
2014-11-24 01:10:39 +01:00
parent c152a1c58b
commit 9f45f18ad1
14 changed files with 60 additions and 44 deletions

View File

@@ -15,7 +15,7 @@ import sys
from itertools import chain from itertools import chain
from jedi._compatibility import next, unicode, builtins from jedi._compatibility import next, unicode, builtins
from jedi.parser import Parser from jedi.parser import Parser, load_grammar
from jedi.parser.tokenize import source_tokens from jedi.parser.tokenize import source_tokens
from jedi.parser import tree as pr from jedi.parser import tree as pr
from jedi.parser.user_context import UserContext, UserContextParser from jedi.parser.user_context import UserContext, UserContextParser
@@ -100,9 +100,11 @@ class Script(object):
cache.clear_time_caches() cache.clear_time_caches()
debug.reset_time() debug.reset_time()
self._grammar = load_grammar('grammar3.4')
self._user_context = UserContext(self.source, self._pos) self._user_context = UserContext(self.source, self._pos)
self._parser = UserContextParser(self.source, path, self._pos, self._user_context) self._parser = UserContextParser(self._grammar, self.source, path,
self._evaluator = Evaluator() self._pos, self._user_context)
self._evaluator = Evaluator(self._grammar)
debug.speed('init') debug.speed('init')
@property @property
@@ -277,7 +279,7 @@ class Script(object):
def _get_under_cursor_stmt(self, cursor_txt): def _get_under_cursor_stmt(self, cursor_txt):
tokenizer = source_tokens(cursor_txt, line_offset=self._pos[0] - 1) tokenizer = source_tokens(cursor_txt, line_offset=self._pos[0] - 1)
r = Parser(cursor_txt, no_docstr=True, tokenizer=tokenizer) r = Parser(self._grammar, cursor_txt, tokenizer=tokenizer)
try: try:
# Take the last statement available. # Take the last statement available.
stmt = r.module.statements[-1] stmt = r.module.statements[-1]
@@ -674,11 +676,10 @@ def defined_names(source, path=None, encoding='utf-8'):
:rtype: list of classes.Definition :rtype: list of classes.Definition
""" """
parser = Parser( grammar = load_grammar('grammar3.4')
common.source_to_unicode(source, encoding), parser = Parser(grammar, common.source_to_unicode(source, encoding),
module_path=path, module_path=path)
) return classes.defined_names(Evaluator(grammar), parser.module)
return classes.defined_names(Evaluator(), parser.module)
def names(source=None, path=None, encoding='utf-8', all_scopes=False, def names(source=None, path=None, encoding='utf-8', all_scopes=False,

View File

@@ -60,7 +60,7 @@ def usages(evaluator, definition_names, mods):
compare_definitions = compare_array(definition_names) compare_definitions = compare_array(definition_names)
mods |= set([d.get_parent_until() for d in definition_names]) mods |= set([d.get_parent_until() for d in definition_names])
definitions = [] definitions = []
for m in imports.get_modules_containing_name(mods, search_name): for m in imports.get_modules_containing_name(evaluator, mods, search_name):
try: try:
check_names = m.used_names[search_name] check_names = m.used_names[search_name]
except KeyError: except KeyError:

View File

@@ -91,7 +91,8 @@ from jedi.evaluate.helpers import FakeStatement, deep_ast_copy, call_of_name
class Evaluator(object): class Evaluator(object):
def __init__(self): def __init__(self, grammar):
self._grammar = grammar
self.memoize_cache = {} # for memoize decorators self.memoize_cache = {} # for memoize decorators
self.import_cache = {} # like `sys.modules`. self.import_cache = {} # like `sys.modules`.
self.compiled_cache = {} # see `compiled.create()` self.compiled_cache = {} # see `compiled.create()`

View File

@@ -8,8 +8,7 @@ import os
import inspect import inspect
from jedi._compatibility import is_py3, builtins, unicode from jedi._compatibility import is_py3, builtins, unicode
from jedi.parser import Parser from jedi.parser import Parser, load_grammar
from jedi.parser import tokenize
from jedi.parser.tree import Class from jedi.parser.tree import Class
from jedi.evaluate.helpers import FakeName from jedi.evaluate.helpers import FakeName
@@ -31,7 +30,8 @@ def _load_faked_module(module):
except IOError: except IOError:
modules[module_name] = None modules[module_name] = None
return return
module = Parser(unicode(source), module_name).module grammar = load_grammar('grammar3.4')
module = Parser(grammar, unicode(source), module_name).module
modules[module_name] = module modules[module_name] = module
if module_name == 'builtins' and not is_py3: if module_name == 'builtins' and not is_py3:

View File

@@ -127,7 +127,7 @@ def _evaluate_for_statement_string(evaluator, string, module):
# (e.g., 'threading' in 'threading.Thread'). # (e.g., 'threading' in 'threading.Thread').
string = 'import %s\n' % element + string string = 'import %s\n' % element + string
p = Parser(code % indent_block(string), no_docstr=True) p = Parser(evaluator.grammar, code % indent_block(string), no_docstr=True)
pseudo_cls = p.module.subscopes[0] pseudo_cls = p.module.subscopes[0]
try: try:
stmt = pseudo_cls.statements[-1] stmt = pseudo_cls.statements[-1]

View File

@@ -174,7 +174,7 @@ def search_params(evaluator, param):
try: try:
result = [] result = []
# This is like backtracking: Get the first possible result. # This is like backtracking: Get the first possible result.
for mod in imports.get_modules_containing_name([current_module], func_name): for mod in imports.get_modules_containing_name(evaluator, [current_module], func_name):
result = get_params_for_module(mod) result = get_params_for_module(mod)
if result: if result:
break break

View File

@@ -492,8 +492,8 @@ def get_names_of_scope(evaluator, scope, position=None, star_search=True, includ
the current scope is function: the current scope is function:
>>> from jedi._compatibility import u >>> from jedi._compatibility import u
>>> from jedi.parser import Parser >>> from jedi.parser import Parser, load_grammar
>>> parser = Parser(u(''' >>> parser = Parser(load_grammar('python3.4'), u('''
... x = ['a', 'b', 'c'] ... x = ['a', 'b', 'c']
... def func(): ... def func():
... y = None ... y = None

View File

@@ -184,7 +184,7 @@ class ImportWrapper2(pr.Base):
rel_path = os.path.join(self._importer.get_relative_path(), rel_path = os.path.join(self._importer.get_relative_path(),
'__init__.py') '__init__.py')
if os.path.exists(rel_path): if os.path.exists(rel_path):
m = _load_module(rel_path) m = _load_module(self.evaluator, rel_path)
names += m.get_defined_names() names += m.get_defined_names()
else: else:
# flask # flask
@@ -590,9 +590,9 @@ class _Importer(object):
else: else:
source = current_namespace[0].read() source = current_namespace[0].read()
current_namespace[0].close() current_namespace[0].close()
return _load_module(path, source, sys_path=sys_path), rest return _load_module(self.evaluator, path, source, sys_path=sys_path), rest
else: else:
return _load_module(name=path, sys_path=sys_path), rest return _load_module(self.evaluator, name=path, sys_path=sys_path), rest
def follow_imports(evaluator, scopes): def follow_imports(evaluator, scopes):
@@ -633,7 +633,7 @@ def remove_star_imports(evaluator, scope, ignored_modules=()):
return set(modules) return set(modules)
def _load_module(path=None, source=None, name=None, sys_path=None): def _load_module(evaluator, path=None, source=None, name=None, sys_path=None):
def load(source): def load(source):
dotted_path = path and compiled.dotted_from_fs_path(path, sys_path) dotted_path = path and compiled.dotted_from_fs_path(path, sys_path)
if path is not None and path.endswith('.py') \ if path is not None and path.endswith('.py') \
@@ -644,7 +644,7 @@ def _load_module(path=None, source=None, name=None, sys_path=None):
else: else:
return compiled.load_module(path, name) return compiled.load_module(path, name)
p = path or name p = path or name
p = fast.FastParser(common.source_to_unicode(source), p) p = fast.FastParser(evaluator.grammar, common.source_to_unicode(source), p)
cache.save_parser(path, name, p) cache.save_parser(path, name, p)
return p.module return p.module
@@ -652,7 +652,7 @@ def _load_module(path=None, source=None, name=None, sys_path=None):
return load(source) if cached is None else cached.module return load(source) if cached is None else cached.module
def get_modules_containing_name(mods, name): def get_modules_containing_name(evaluator, mods, name):
""" """
Search a name in the directories of modules. Search a name in the directories of modules.
""" """
@@ -669,7 +669,7 @@ def get_modules_containing_name(mods, name):
with open(path, 'rb') as f: with open(path, 'rb') as f:
source = source_to_unicode(f.read()) source = source_to_unicode(f.read())
if name in source: if name in source:
return _load_module(path, source) return _load_module(evaluator, path, source)
# skip non python modules # skip non python modules
mods = set(m for m in mods if not isinstance(m, compiled.CompiledObject)) mods = set(m for m in mods if not isinstance(m, compiled.CompiledObject))

View File

@@ -227,7 +227,7 @@ def collections_namedtuple(evaluator, obj, params):
) )
# Parse source # Parse source
generated_class = Parser(unicode(source)).module.subscopes[0] generated_class = Parser(evaluator.grammar, unicode(source)).module.subscopes[0]
return [er.Class(evaluator, generated_class)] return [er.Class(evaluator, generated_class)]

View File

@@ -140,7 +140,7 @@ def sys_path_with_modifications(evaluator, module):
except IOError: except IOError:
pass pass
else: else:
p = Parser(common.source_to_unicode(source), module_path) p = Parser(evaluator.grammar, common.source_to_unicode(source), module_path)
for path in _check_module(p.module): for path in _check_module(p.module):
if path not in buildout_paths: if path not in buildout_paths:
buildout_paths.add(path) buildout_paths.add(path)

View File

@@ -15,16 +15,14 @@ within the statement. This lowers memory usage and cpu time and reduces the
complexity of the ``Parser`` (there's another parser sitting inside complexity of the ``Parser`` (there's another parser sitting inside
``Statement``, which produces ``Array`` and ``Call``). ``Statement``, which produces ``Array`` and ``Call``).
""" """
import keyword
import logging import logging
import os
from jedi._compatibility import next, unicode from jedi._compatibility import next
from jedi import debug
from jedi import common from jedi import common
from jedi.parser import tree as pt from jedi.parser import tree as pt
from jedi.parser import tokenize from jedi.parser import tokenize
from jedi.parser import pytree from jedi.parser import pytree
from jedi.parser.pgen2 import Driver
from jedi.parser import pgen2 from jedi.parser import pgen2
OPERATOR_KEYWORDS = 'and', 'for', 'if', 'else', 'in', 'is', 'lambda', 'not', 'or' OPERATOR_KEYWORDS = 'and', 'for', 'if', 'else', 'in', 'is', 'lambda', 'not', 'or'
@@ -33,20 +31,33 @@ STATEMENT_KEYWORDS = 'assert', 'del', 'global', 'nonlocal', 'raise', \
'return', 'yield', 'pass', 'continue', 'break' 'return', 'yield', 'pass', 'continue', 'break'
_loaded_grammars = {}
def load_grammar(file):
global _loaded_grammars
path = os.path.join(os.path.dirname(__file__), file) + '.txt'
try:
return _loaded_grammars[path]
except KeyError:
return _loaded_grammars.setdefault(path, pgen2.load_grammar(path))
class Parser(object): class Parser(object):
""" """
This class is used to parse a Python file, it then divides them into a This class is used to parse a Python file, it then divides them into a
class structure of different scopes. class structure of different scopes.
:param source: The codebase for the parser. :param grammar: The grammar object of pgen2. Loaded by load_grammar.
:type source: str :param source: The codebase for the parser. Must be unicode.
:param module_path: The path of the module in the file system, may be None. :param module_path: The path of the module in the file system, may be None.
:type module_path: str :type module_path: str
:param no_docstr: If True, a string at the beginning is not a docstr.
:param top_module: Use this module as a parent instead of `self.module`. :param top_module: Use this module as a parent instead of `self.module`.
""" """
def __init__(self, source, module_path=None, no_docstr=False, def __init__(self, grammar, source, module_path=None, tokenizer=None):
tokenizer=None, top_module=None): """
This is the way I imagine a parser describing the init function
"""
if not source.endswith('\n'): if not source.endswith('\n'):
source += '\n' source += '\n'
@@ -90,8 +101,8 @@ class Parser(object):
self.used_names = {} self.used_names = {}
self.scope_names_stack = [{}] self.scope_names_stack = [{}]
logger = logging.getLogger("Jedi-Parser") logger = logging.getLogger("Jedi-Parser")
d = Driver(pytree.python_grammar, self.convert_node, self.convert_leaf, d = pgen2.Driver(grammar, self.convert_node,
self.error_recovery, logger=logger) self.convert_leaf, self.error_recovery, logger=logger)
self.module = d.parse_string(source).get_parent_until() self.module = d.parse_string(source).get_parent_until()
self.module.used_names = self.used_names self.module.used_names = self.used_names

View File

@@ -56,13 +56,13 @@ class Module(pr.Module, pr.Simple):
class CachedFastParser(type): class CachedFastParser(type):
""" This is a metaclass for caching `FastParser`. """ """ This is a metaclass for caching `FastParser`. """
def __call__(self, source, module_path=None): def __call__(self, grammar, source, module_path=None):
if not settings.fast_parser: if not settings.fast_parser:
return Parser(source, module_path) return Parser(grammar, source, module_path)
pi = cache.parser_cache.get(module_path, None) pi = cache.parser_cache.get(module_path, None)
if pi is None or isinstance(pi.parser, Parser): if pi is None or isinstance(pi.parser, Parser):
p = super(CachedFastParser, self).__call__(source, module_path) p = super(CachedFastParser, self).__call__(grammar, source, module_path)
else: else:
p = pi.parser # pi is a `cache.ParserCacheItem` p = pi.parser # pi is a `cache.ParserCacheItem`
p.update(source) p.update(source)
@@ -186,8 +186,9 @@ class FastParser(use_metaclass(CachedFastParser)):
_keyword_re = re.compile('^[ \t]*(def|class|@|%s)' % '|'.join(tokenize.FLOWS)) _keyword_re = re.compile('^[ \t]*(def|class|@|%s)' % '|'.join(tokenize.FLOWS))
def __init__(self, code, module_path=None): def __init__(self, grammar, code, module_path=None):
# set values like `pr.Module`. # set values like `pr.Module`.
self._grammar = grammar
self.module_path = module_path self.module_path = module_path
self.current_node = None self.current_node = None

View File

@@ -18,6 +18,7 @@ from . import pgen2
# The grammar file # The grammar file
_GRAMMAR_FILE = os.path.join(os.path.dirname(__file__), "grammar3.4.txt") _GRAMMAR_FILE = os.path.join(os.path.dirname(__file__), "grammar3.4.txt")
python_grammar = pgen2.load_grammar(_GRAMMAR_FILE)
class Symbols(object): class Symbols(object):

View File

@@ -202,7 +202,8 @@ class UserContext(object):
class UserContextParser(object): class UserContextParser(object):
def __init__(self, source, path, position, user_context): def __init__(self, grammar, source, path, position, user_context):
self._grammar = grammar
self._source = source self._source = source
self._path = path and os.path.abspath(path) self._path = path and os.path.abspath(path)
self._position = position self._position = position
@@ -211,7 +212,7 @@ class UserContextParser(object):
@cache.underscore_memoization @cache.underscore_memoization
def _parser(self): def _parser(self):
cache.invalidate_star_import_cache(self._path) cache.invalidate_star_import_cache(self._path)
parser = FastParser(self._source, self._path) parser = FastParser(self._grammar, self._source, self._path)
# Don't pickle that module, because the main module is changing quickly # Don't pickle that module, because the main module is changing quickly
cache.save_parser(self._path, None, parser, pickling=False) cache.save_parser(self._path, None, parser, pickling=False)
return parser return parser