diff --git a/parso/grammar.py b/parso/grammar.py index 322d486..3b34d53 100644 --- a/parso/grammar.py +++ b/parso/grammar.py @@ -1,8 +1,9 @@ import hashlib import os import sys +import re -from parso._compatibility import FileNotFoundError +from parso._compatibility import FileNotFoundError, unicode from parso.pgen2.pgen import generate_grammar from parso.utils import splitlines, source_to_unicode from parso.python.parser import remove_last_newline @@ -135,22 +136,48 @@ class Grammar(object): return '<%s:%s>' % (self.__class__.__name__, txt) +def _parse_version(version): + match = re.match('(\d+)(?:\.(\d)(?:\.\d+)?)?$', version) + if match is None: + raise ValueError('The given version is not in the right format. ' + 'Use something like "3.2" or "3".') + + major = match.group(1) + minor = match.group(2) + if minor is None: + # Use the latest Python in case it's not exactly defined, because the + # grammars are typically backwards compatible? + if major == "2": + minor = "7" + elif major == "3": + minor = "6" + else: + raise NotImplementedError("Sorry, no support yet for those fancy new/old versions.") + return int(major + minor) + + def load_grammar(version=None): """ - Loads a Python grammar. The default version is always the latest. + Loads a Python grammar. The default version is the current Python version. If you need support for a specific version, please use e.g. `version='3.3'`. """ if version is None: - version = '3.6' + version = '%s.%s' % sys.version_info[:2] + if not isinstance(version, (unicode, str)): + raise TypeError("version must be a string like 3.2.") - if version in ('3.2', '3.3'): - version = '3.4' - elif version == '2.6': - version = '2.7' + version_int = _parse_version(version) - file = 'python/grammar' + version + '.txt' + # For these versions we use the same grammar files, because nothing + # changed. + if version_int == 33: + version_int = 34 + elif version_int == 26: + version_int = 27 + + file = 'python/grammar' + str(version_int) + '.txt' global _loaded_grammars path = os.path.join(os.path.dirname(__file__), file) @@ -160,8 +187,9 @@ def load_grammar(version=None): try: with open(path) as f: bnf_text = f.read() + grammar = Grammar(bnf_text, parser=PythonParser, diff_parser=DiffParser) return _loaded_grammars.setdefault(path, grammar) except FileNotFoundError: - # Just load the default if the file does not exist. - return load_grammar() + message = "Python version %s is currently not supported." % version + raise NotImplementedError(message) diff --git a/test/test_load_grammar.py b/test/test_load_grammar.py new file mode 100644 index 0000000..8ba083d --- /dev/null +++ b/test/test_load_grammar.py @@ -0,0 +1,31 @@ +import pytest +from parso.grammar import load_grammar +from parso import grammar + + +def test_load_inexisting_grammar(): + # This version shouldn't be out for a while, but if we ever do, wow! + with pytest.raises(NotImplementedError): + load_grammar('15.8') + # The same is true for very old grammars (even though this is probably not + # going to be an issue. + with pytest.raises(NotImplementedError): + load_grammar('1.5') + + +@pytest.mark.parametrize(('string', 'result'), [ + ('2', 27), ('3', 36), ('1.1', 11), ('1.1.1', 11), ('300.1.31', 3001) +]) +def test_parse_version(string, result): + assert grammar._parse_version(string) == result + + +@pytest.mark.parametrize('string', ['1.', 'a', '#', '1.3.4.5', '1.12']) +def test_invalid_grammar_version(string): + with pytest.raises(ValueError): + load_grammar(string) + + +def test_grammar_int_version(): + with pytest.raises(TypeError): + load_grammar(3.2) diff --git a/test/test_parser.py b/test/test_parser.py index 987f798..ee1becc 100644 --- a/test/test_parser.py +++ b/test/test_parser.py @@ -158,15 +158,6 @@ def test_python3_octal(): assert module.children[0].type == 'error_node' -def test_load_newer_grammar(): - # This version shouldn't be out for a while, but if we somehow get this it - # should just take the latest Python grammar. - load_grammar('15.8') - # The same is true for very old grammars (even though this is probably not - # going to be an issue. - load_grammar('1.5') - - @pytest.mark.parametrize('code', ['foo "', 'foo """\n', 'foo """\nbar']) def test_open_string_literal(code): """