diff --git a/conftest.py b/conftest.py index 55c8a38..2a2504e 100644 --- a/conftest.py +++ b/conftest.py @@ -152,6 +152,12 @@ def works_in_py2(each_version): return Checker(each_version, each_version.startswith('2')) +@pytest.fixture +def works_ge_py27(each_version): + version_info = parse_version_string(each_version) + return Checker(each_version, version_info >= (2, 7)) + + @pytest.fixture def works_ge_py3(each_version): version_info = parse_version_string(each_version) diff --git a/parso/grammar.py b/parso/grammar.py index 22f04f6..3b075ff 100644 --- a/parso/grammar.py +++ b/parso/grammar.py @@ -154,12 +154,9 @@ def load_grammar(**kwargs): def load_grammar(version=None): version_info = parse_version_string(version) - # For these versions we use the same grammar files, because nothing - # changed. + # The 3.3 grammar didn't change with the update to 3.4. if version_info == (3, 3): version_info = parse_version_string('3.4') - elif version_info == (2, 6): - version_info = parse_version_string('2.7') file = 'python/grammar%s%s.txt' % (version_info.major, version_info.minor) diff --git a/parso/python/grammar26.txt b/parso/python/grammar26.txt new file mode 100644 index 0000000..b972a41 --- /dev/null +++ b/parso/python/grammar26.txt @@ -0,0 +1,158 @@ +# Grammar for Python + +# Note: Changing the grammar specified in this file will most likely +# require corresponding changes in the parser module +# (../Modules/parsermodule.c). If you can't make the changes to +# that module yourself, please co-ordinate the required changes +# with someone who can; ask around on python-dev for help. Fred +# Drake will probably be listening there. + +# NOTE WELL: You should also follow all the steps listed in PEP 306, +# "How to Change Python's Grammar" + +# Commands for Kees Blom's railroad program +#diagram:token NAME +#diagram:token NUMBER +#diagram:token STRING +#diagram:token NEWLINE +#diagram:token ENDMARKER +#diagram:token INDENT +#diagram:output\input python.bla +#diagram:token DEDENT +#diagram:output\textwidth 20.04cm\oddsidemargin 0.0cm\evensidemargin 0.0cm +#diagram:rules + +# Start symbols for the grammar: +# single_input is a single interactive statement; +# file_input is a module or sequence of commands read from an input file; +# eval_input is the input for the eval() and input() functions. +# NB: compound_stmt in single_input is followed by extra NEWLINE! +single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE +file_input: (NEWLINE | stmt)* ENDMARKER +eval_input: testlist NEWLINE* ENDMARKER + +decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE +decorators: decorator+ +decorated: decorators (classdef | funcdef) +funcdef: 'def' NAME parameters ':' suite +parameters: '(' [varargslist] ')' +varargslist: ((fpdef ['=' test] ',')* + ('*' NAME [',' '**' NAME] | '**' NAME) | + fpdef ['=' test] (',' fpdef ['=' test])* [',']) +fpdef: NAME | '(' fplist ')' +fplist: fpdef (',' fpdef)* [','] + +stmt: simple_stmt | compound_stmt +simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE +small_stmt: (expr_stmt | print_stmt | del_stmt | pass_stmt | flow_stmt | + import_stmt | global_stmt | exec_stmt | assert_stmt) +expr_stmt: testlist (augassign (yield_expr|testlist) | + ('=' (yield_expr|testlist))*) +augassign: ('+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | + '<<=' | '>>=' | '**=' | '//=') +# For normal assignments, additional restrictions enforced by the interpreter +print_stmt: 'print' ( [ test (',' test)* [','] ] | + '>>' test [ (',' test)+ [','] ] ) +del_stmt: 'del' exprlist +pass_stmt: 'pass' +flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt +break_stmt: 'break' +continue_stmt: 'continue' +return_stmt: 'return' [testlist] +yield_stmt: yield_expr +raise_stmt: 'raise' [test [',' test [',' test]]] +import_stmt: import_name | import_from +import_name: 'import' dotted_as_names +import_from: ('from' ('.'* dotted_name | '.'+) + 'import' ('*' | '(' import_as_names ')' | import_as_names)) +import_as_name: NAME ['as' NAME] +dotted_as_name: dotted_name ['as' NAME] +import_as_names: import_as_name (',' import_as_name)* [','] +dotted_as_names: dotted_as_name (',' dotted_as_name)* +dotted_name: NAME ('.' NAME)* +global_stmt: 'global' NAME (',' NAME)* +exec_stmt: 'exec' expr ['in' test [',' test]] +assert_stmt: 'assert' test [',' test] + +compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated +if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite] +while_stmt: 'while' test ':' suite ['else' ':' suite] +for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite] +try_stmt: ('try' ':' suite + ((except_clause ':' suite)+ + ['else' ':' suite] + ['finally' ':' suite] | + 'finally' ':' suite)) +with_stmt: 'with' with_item ':' suite +# Dave: Python2.6 actually defines a little bit of a different label called +# 'with_var'. However in 2.7+ this is the default. Apply it for +# consistency reasons. +with_item: test ['as' expr] +# NB compile.c makes sure that the default except clause is last +except_clause: 'except' [test [('as' | ',') test]] +suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT + +# Backward compatibility cruft to support: +# [ x for x in lambda: True, lambda: False if x() ] +# even while also allowing: +# lambda x: 5 if x else 2 +# (But not a mix of the two) +testlist_safe: old_test [(',' old_test)+ [',']] +old_test: or_test | old_lambdef +old_lambdef: 'lambda' [varargslist] ':' old_test + +test: or_test ['if' or_test 'else' test] | lambdef +or_test: and_test ('or' and_test)* +and_test: not_test ('and' not_test)* +not_test: 'not' not_test | comparison +comparison: expr (comp_op expr)* +comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not' +expr: xor_expr ('|' xor_expr)* +xor_expr: and_expr ('^' and_expr)* +and_expr: shift_expr ('&' shift_expr)* +shift_expr: arith_expr (('<<'|'>>') arith_expr)* +arith_expr: term (('+'|'-') term)* +term: factor (('*'|'/'|'%'|'//') factor)* +factor: ('+'|'-'|'~') factor | power +power: atom trailer* ['**' factor] +atom: ('(' [yield_expr|testlist_comp] ')' | + '[' [listmaker] ']' | + '{' [dictorsetmaker] '}' | + '`' testlist1 '`' | + NAME | NUMBER | STRING+) +listmaker: test ( list_for | (',' test)* [','] ) +# Dave: Renamed testlist_gexpr to testlist_comp, because in 2.7+ this is the +# default. It's more consistent like this. +testlist_comp: test ( gen_for | (',' test)* [','] ) +lambdef: 'lambda' [varargslist] ':' test +trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME +subscriptlist: subscript (',' subscript)* [','] +subscript: '.' '.' '.' | test | [test] ':' [test] [sliceop] +sliceop: ':' [test] +exprlist: expr (',' expr)* [','] +testlist: test (',' test)* [','] +# Dave: Rename from dictmaker to dictorsetmaker, because this is more +# consistent with the following grammars. +dictorsetmaker: test ':' test (',' test ':' test)* [','] + +classdef: 'class' NAME ['(' [testlist] ')'] ':' suite + +arglist: (argument ',')* (argument [','] + |'*' test (',' argument)* [',' '**' test] + |'**' test) +argument: test [gen_for] | test '=' test # Really [keyword '='] test + +list_iter: list_for | list_if +list_for: 'for' exprlist 'in' testlist_safe [list_iter] +list_if: 'if' old_test [list_iter] + +gen_iter: gen_for | gen_if +gen_for: 'for' exprlist 'in' or_test [gen_iter] +gen_if: 'if' old_test [gen_iter] + +testlist1: test (',' test)* + +# not used in grammar, but may appear in "node" passed from Parser to Compiler +encoding_decl: NAME + +yield_expr: 'yield' [testlist] diff --git a/parso/python/normalizer.py b/parso/python/normalizer.py index f9ebeae..b081aeb 100644 --- a/parso/python/normalizer.py +++ b/parso/python/normalizer.py @@ -482,7 +482,6 @@ class ErrorFinder(Normalizer): self._check_assignment(before_equal) augassign = node.children[1] - print(augassign) if augassign != '=' and augassign.type != 'annassign': # Is augassign. if node.children[0].type in ('testlist_star_expr', 'atom', 'testlist'): message = "illegal expression for augmented assignment" diff --git a/parso/python/parser.py b/parso/python/parser.py index 50211d6..15f80e6 100644 --- a/parso/python/parser.py +++ b/parso/python/parser.py @@ -42,6 +42,8 @@ class Parser(BaseParser): # avoid extreme amounts of work around the subtle difference of 2/3 # grammar in list comoprehensions. 'list_for': tree.CompFor, + # Same here. This just exists in Python 2.6. + 'gen_for': tree.CompFor, 'decorator': tree.Decorator, 'lambdef': tree.Lambda, 'old_lambdef': tree.Lambda, diff --git a/test/test_pgen2.py b/test/test_pgen2.py index a6b2a07..9d2fabd 100644 --- a/test/test_pgen2.py +++ b/test/test_pgen2.py @@ -166,17 +166,17 @@ def test_except_old(works_in_py2): # Adapted from Python 3's Lib/test/test_grammar.py:GrammarTests.testAtoms -def test_set_literal_1(each_version): - _parse("""x = {'one'}""", each_version) +def test_set_literal_1(works_ge_py27): + works_ge_py27.parse("""x = {'one'}""") -def test_set_literal_2(each_version): - _parse("""x = {'one', 1,}""", each_version) +def test_set_literal_2(works_ge_py27): + works_ge_py27.parse("""x = {'one', 1,}""") -def test_set_literal_3(each_version): - _parse("""x = {'one', 'two', 'three'}""", each_version) +def test_set_literal_3(works_ge_py27): + works_ge_py27.parse("""x = {'one', 'two', 'three'}""") -def test_set_literal_4(each_version): - _parse("""x = {2, 3, 4,}""", each_version) +def test_set_literal_4(works_ge_py27): + works_ge_py27.parse("""x = {2, 3, 4,}""") def test_new_octal_notation(each_version): diff --git a/test/test_python_errors.py b/test/test_python_errors.py index f670a3b..4e1328c 100644 --- a/test/test_python_errors.py +++ b/test/test_python_errors.py @@ -58,7 +58,6 @@ FAILING_EXAMPLES = [ 'a, b += 3', '(a, b) += 3', '[a, b] += 3', - '[a, 1] += 3', # All assignment tests 'lambda a: 1 = 1', '[x for x in y] = 1', @@ -221,6 +220,10 @@ if sys.version_info >= (3,): # A symtable error that raises only a SyntaxWarning in Python 2. 'def x(): from math import *', ] +if sys.version_info >= (2, 7): + # This is something that raises a different error in 2.6 than in the other + # versions. Just skip it for 2.6. + FAILING_EXAMPLES.append('[a, 1] += 3') def _get_error_list(code, version=None): @@ -306,6 +309,12 @@ def _get_actual_exception(code): wanted = 'SyntaxError: positional argument follows keyword argument' elif wanted == 'SyntaxError: assignment to keyword': return [wanted, "SyntaxError: can't assign to keyword"], line_nr + elif wanted == 'SyntaxError: assignment to None': + # Python 2.6 does has a slightly different error. + return [wanted, 'SyntaxError: cannot assign to None'], line_nr + elif wanted == 'SyntaxError: can not assign to __debug__': + # Python 2.6 does has a slightly different error. + return [wanted, 'SyntaxError: cannot assign to __debug__'], line_nr return [wanted], line_nr