35 Commits

Author SHA1 Message Date
Dave Halter
be9f5a401f Prepare release 0.8.5
Some checks failed
Build / lint (push) Has been cancelled
Build / test (false, 3.10) (push) Has been cancelled
Build / test (false, 3.11) (push) Has been cancelled
Build / test (false, 3.12) (push) Has been cancelled
Build / test (false, 3.13) (push) Has been cancelled
Build / test (false, 3.8) (push) Has been cancelled
Build / test (false, 3.9) (push) Has been cancelled
Build / coverage (push) Has been cancelled
2025-08-23 17:12:20 +02:00
Dave Halter
7e4777b775 Merge pull request #234 from A5rocks/future-compatibility
Load newest grammar in face of a future grammar
2025-08-23 15:09:10 +00:00
A5rocks
e99dbdd536 Remove redundant warnings import 2025-08-23 17:00:06 +09:00
A5rocks
e22dc67aa1 Avoid warning 2025-08-23 16:38:08 +09:00
A5rocks
baa3c90d85 Load newest grammar in face of a future grammar 2025-08-22 23:35:46 +09:00
A5rocks
23b1cdf73d Drop Python 3.7 in CI
Some checks failed
Build / lint (push) Has been cancelled
Build / test (false, 3.10) (push) Has been cancelled
Build / test (false, 3.11) (push) Has been cancelled
Build / test (false, 3.12) (push) Has been cancelled
Build / test (false, 3.13) (push) Has been cancelled
Build / test (false, 3.8) (push) Has been cancelled
Build / test (false, 3.9) (push) Has been cancelled
Build / coverage (push) Has been cancelled
Update some versions in various places (#233)
2025-08-22 14:03:23 +00:00
Dave Halter
a73af5c709 Fix pip install -e in docs
Some checks failed
Build / lint (push) Has been cancelled
Build / test (false, 3.10) (push) Has been cancelled
Build / test (false, 3.11) (push) Has been cancelled
Build / test (false, 3.12) (push) Has been cancelled
Build / test (false, 3.13) (push) Has been cancelled
Build / test (false, 3.7) (push) Has been cancelled
Build / test (false, 3.8) (push) Has been cancelled
Build / test (false, 3.9) (push) Has been cancelled
Build / coverage (push) Has been cancelled
2025-06-24 12:29:39 +02:00
Jon Crall
9328cffce3 Update classifiers in setup.py (#230)
Some checks failed
Build / lint (push) Has been cancelled
Build / test (false, 3.10) (push) Has been cancelled
Build / test (false, 3.11) (push) Has been cancelled
Build / test (false, 3.12) (push) Has been cancelled
Build / test (false, 3.13) (push) Has been cancelled
Build / test (false, 3.7) (push) Has been cancelled
Build / test (false, 3.8) (push) Has been cancelled
Build / test (false, 3.9) (push) Has been cancelled
Build / coverage (push) Has been cancelled
2025-03-10 22:13:22 +00:00
Thomas A Caswell
f670e6e7dc ENH: add grammar file for py314 (#229)
Some checks failed
Build / lint (push) Has been cancelled
Build / test (false, 3.10) (push) Has been cancelled
Build / test (false, 3.11) (push) Has been cancelled
Build / test (false, 3.12) (push) Has been cancelled
Build / test (false, 3.13) (push) Has been cancelled
Build / test (false, 3.7) (push) Has been cancelled
Build / test (false, 3.8) (push) Has been cancelled
Build / test (false, 3.9) (push) Has been cancelled
Build / coverage (push) Has been cancelled
Following  #78 and #220  copied the py313 grammar file.
2024-12-27 10:18:25 +00:00
dheeraj
338a576027 Updated readme installation documentation, now we can copy it easier (#228)
Some checks failed
Build / lint (push) Has been cancelled
Build / test (false, 3.10) (push) Has been cancelled
Build / test (false, 3.11) (push) Has been cancelled
Build / test (false, 3.12) (push) Has been cancelled
Build / test (false, 3.13) (push) Has been cancelled
Build / test (false, 3.7) (push) Has been cancelled
Build / test (false, 3.8) (push) Has been cancelled
Build / test (false, 3.9) (push) Has been cancelled
Build / coverage (push) Has been cancelled
2024-11-24 14:31:05 +00:00
Dave Halter
9ddffca4da Merge pull request #227 from juliangilbey/python3.13-test-fixes
Some checks failed
Build / lint (push) Has been cancelled
Build / test (false, 3.10) (push) Has been cancelled
Build / test (false, 3.11) (push) Has been cancelled
Build / test (false, 3.12) (push) Has been cancelled
Build / test (false, 3.13) (push) Has been cancelled
Build / test (false, 3.7) (push) Has been cancelled
Build / test (false, 3.8) (push) Has been cancelled
Build / test (false, 3.9) (push) Has been cancelled
Build / coverage (push) Has been cancelled
Fix tests so they work with Python 3.12/3.13
2024-11-22 19:52:17 +00:00
Julian Gilbey
06db036e23 Conditionally include failing examples rather than handle them in the testing code 2024-11-22 11:40:13 +00:00
Julian Gilbey
c792ae546c Add Python 3.12 and 3.13 to test matrix 2024-11-22 09:44:49 +00:00
Julian Gilbey
1c01dafc2b Fix tests so they work with Python 3.12/3.13 2024-11-21 09:50:06 +00:00
Dave Halter
1ca6b1f3e8 Merge pull request #225 from nilbest/fix-malicious-link-224
Fix #224: Remove Potential Malicious Link in Parso's Acknowledgement README.rst
2024-06-27 13:05:02 +00:00
Nils Bestehorn
5a9349ae58 Deleted 'Salome Schneider' Link to suspicious side 2024-06-27 12:26:23 +02:00
Dave Halter
279fd6903e Add a readthedocs config file 2024-04-21 10:50:21 +02:00
Dave Halter
e255b69cb8 Add a security policy 2024-04-21 10:38:28 +02:00
Dave Halter
744f2ac39e Prepare 0.8.4 2024-04-05 10:43:56 +02:00
Dave Halter
3c04eef132 Merge pull request #220 from tacaswell/py313
ENH: add grammar file from py313
2023-06-18 23:54:47 +00:00
Thomas A Caswell
f7bea28bcc ENH: add grammar file from py313
Following https://github.com/davidhalter/parso/pull/78 copied the py312
grammar file.
2023-06-17 22:00:09 -04:00
Dave Halter
27af7ef106 Merge pull request #216 from PeterJCLaw/update-mypy
Update mypy
2023-02-14 00:43:52 +00:00
Peter Law
171fd33cb6 Be stricter about mypy needing error codes
These make it clearer what's being ignored and harder to
accidentally ignore more than expected.
2023-02-13 19:55:49 +00:00
Peter Law
4eba7d697f Also typecheck setup.py now we can 2023-02-13 19:55:49 +00:00
Peter Law
cf240c7d2b Update mypy to the latest which supports Python 3.6 2023-02-13 19:55:48 +00:00
Dave Halter
ffadfca81b Merge pull request #215 from PeterJCLaw/update-flake8
Update flake8
2023-02-13 19:50:21 +00:00
Peter Law
378e645bbc Update flake8 2023-02-13 19:42:33 +00:00
Dave Halter
df34112b5b Merge pull request #211 from jspricke/test_python3_10
Fix unit tests in Python 3.10 (Closes: #192)
2022-12-06 20:20:10 +00:00
Jochen Sprickerhof
e0a1caecc4 Drop pytest version restriction
Not compatible with Python >= 3.10:

https://github.com/pytest-dev/pytest/discussions/9195
2022-12-05 22:26:00 +01:00
Jochen Sprickerhof
7d43001f9d CI: update tested Python versions 2022-12-05 08:52:20 +01:00
Jochen Sprickerhof
cf5969d7a1 Fix unit tests in Python 3.10 (Closes: #192) 2022-12-05 08:52:19 +01:00
Dave Halter
6b6b59f6d7 Merge pull request #209 from hauntsaninja/implicit-optional
Explicitly allow implicit optionals
2022-09-28 17:49:01 +00:00
hauntsaninja
7af5259159 Explicitly allow implicit optionals
Keeps things working when you upgrade mypy versions.
The other way of solving this problem is in #208
2022-09-27 13:56:26 -07:00
Dave Halter
8ee84d005e Merge pull request #204 from ariebovenberg/fix-slots
Add missing slots to base classes
2022-02-15 00:25:40 +01:00
Arie Bovenberg
0740450899 add missing slots to base classes 2022-02-14 12:56:49 +01:00
20 changed files with 561 additions and 51 deletions

View File

@@ -22,18 +22,15 @@ jobs:
# Ignore F401, which are unused imports. flake8 is a primitive tool and is sometimes wrong.
run: flake8 --extend-ignore F401 parso test/*.py setup.py scripts/
- name: Run Mypy
run: mypy parso
run: mypy parso setup.py
test:
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
python-version: ['3.6', '3.7', '3.8', '3.9']
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
experimental: [false]
# include:
# - python-version: '3.10-dev'
# experimental: true
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}

16
.readthedocs.yml Normal file
View File

@@ -0,0 +1,16 @@
version: 2
python:
install:
- method: pip
path: .
extra_requirements:
- docs
submodules:
include: all
build:
os: ubuntu-22.04
tools:
python: "3.11"

View File

@@ -6,6 +6,16 @@ Changelog
Unreleased
++++++++++
0.8.5 (2025-08-23)
++++++++++++++++++
- Add a fallback grammar for Python 3.14+
0.8.4 (2024-04-05)
++++++++++++++++++
- Add basic support for Python 3.13
0.8.3 (2021-11-30)
++++++++++++++++++

View File

@@ -68,6 +68,8 @@ Resources
Installation
============
.. code-block:: bash
pip install parso
Future
@@ -88,8 +90,7 @@ Acknowledgements
- Guido van Rossum (@gvanrossum) for creating the parser generator pgen2
(originally used in lib2to3).
- `Salome Schneider <https://www.crepes-schnaegg.ch/cr%C3%AApes-schn%C3%A4gg/kunst-f%C3%BCrs-cr%C3%AApes-mobil/>`_
for the extremely awesome parso logo.
- Salome Schneider for the extremely awesome parso logo.
.. _jedi: https://github.com/davidhalter/jedi

9
SECURITY.md Normal file
View File

@@ -0,0 +1,9 @@
# Security Policy
If security issues arise, we will try to fix those as soon as possible.
Due to Parso's nature, Security Issues will probably be extremely rare, but we will of course treat them seriously.
## Reporting Security Problems
If you need to report a security vulnerability, please send an email to davidhalter88@gmail.com. Typically, I will respond in the next few business days.

View File

@@ -13,7 +13,7 @@ from parso.utils import parse_version_string
collect_ignore = ["setup.py"]
_SUPPORTED_VERSIONS = '3.6', '3.7', '3.8', '3.9', '3.10'
_SUPPORTED_VERSIONS = '3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14'
@pytest.fixture(scope='session')

View File

@@ -16,7 +16,7 @@ From git
--------
If you want to install the current development version (master branch)::
sudo pip install -e git://github.com/davidhalter/parso.git#egg=parso
sudo pip install -e git+https://github.com/davidhalter/parso.git#egg=parso
Manual installation from a downloaded package (not recommended)

View File

@@ -43,7 +43,7 @@ from parso.grammar import Grammar, load_grammar
from parso.utils import split_lines, python_bytes_to_unicode
__version__ = '0.8.3'
__version__ = '0.8.5'
def parse(code=None, **kwargs):

View File

@@ -106,14 +106,14 @@ class Grammar(Generic[_NodeT]):
if file_io is None:
if code is None:
file_io = FileIO(path) # type: ignore
file_io = FileIO(path) # type: ignore[arg-type]
else:
file_io = KnownContentFileIO(path, code)
if cache and file_io.path is not None:
module_node = load_module(self._hashed, file_io, cache_path=cache_path)
if module_node is not None:
return module_node # type: ignore
return module_node # type: ignore[no-any-return]
if code is None:
code = file_io.read()
@@ -132,7 +132,7 @@ class Grammar(Generic[_NodeT]):
module_node = module_cache_item.node
old_lines = module_cache_item.lines
if old_lines == lines:
return module_node # type: ignore
return module_node # type: ignore[no-any-return]
new_node = self._diff_parser(
self._pgen_grammar, self._tokenizer, module_node
@@ -144,7 +144,7 @@ class Grammar(Generic[_NodeT]):
# Never pickle in pypy, it's slow as hell.
pickling=cache and not is_pypy,
cache_path=cache_path)
return new_node # type: ignore
return new_node # type: ignore[no-any-return]
tokens = self._tokenizer(lines)
@@ -160,7 +160,7 @@ class Grammar(Generic[_NodeT]):
# Never pickle in pypy, it's slow as hell.
pickling=cache and not is_pypy,
cache_path=cache_path)
return root_node # type: ignore
return root_node # type: ignore[no-any-return]
def _get_token_namespace(self):
ns = self._token_namespace
@@ -239,7 +239,16 @@ def load_grammar(*, version: str = None, path: str = None):
:param str version: A python version string, e.g. ``version='3.8'``.
:param str path: A path to a grammar file
"""
version_info = parse_version_string(version)
# NOTE: this (3, 14) should be updated to the latest version parso supports.
# (if this doesn't happen, users will get older syntaxes and spurious warnings)
passed_version_info = parse_version_string(version)
version_info = min(passed_version_info, PythonVersionInfo(3, 14))
# # NOTE: this is commented out until parso properly supports newer Python grammars.
# if passed_version_info != version_info:
# warnings.warn('parso does not support %s.%s yet.' % (
# passed_version_info.major, passed_version_info.minor
# ))
file = path or os.path.join(
'python',

View File

@@ -276,7 +276,7 @@ def generate_grammar(bnf_grammar: str, token_namespace) -> Grammar:
dfa_state.transitions[transition] = DFAPlan(next_dfa)
_calculate_tree_traversal(rule_to_dfas)
return Grammar(start_nonterminal, rule_to_dfas, reserved_strings) # type: ignore
return Grammar(start_nonterminal, rule_to_dfas, reserved_strings) # type: ignore[arg-type]
def _make_transition(token_namespace, reserved_syntax_strings, label):

View File

@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import codecs
import sys
import warnings
import re
from contextlib import contextmanager
@@ -33,7 +34,10 @@ def _get_rhs_name(node, version):
return "literal"
else:
if second.children[1] == ":" or second.children[0] == "**":
return "dict display"
if version < (3, 10):
return "dict display"
else:
return "dict literal"
else:
return "set display"
elif (
@@ -47,7 +51,10 @@ def _get_rhs_name(node, version):
elif first == "[":
return "list"
elif first == "{" and second == "}":
return "dict display"
if version < (3, 10):
return "dict display"
else:
return "dict literal"
elif first == "{" and len(node.children) > 2:
return "set display"
elif type_ == "keyword":
@@ -58,7 +65,10 @@ def _get_rhs_name(node, version):
else:
return str(node.value)
elif type_ == "operator" and node.value == "...":
return "Ellipsis"
if version < (3, 10):
return "Ellipsis"
else:
return "ellipsis"
elif type_ == "comparison":
return "comparison"
elif type_ in ("string", "number", "strings"):
@@ -83,7 +93,10 @@ def _get_rhs_name(node, version):
or "_test" in type_
or type_ in ("term", "factor")
):
return "operator"
if version < (3, 10):
return "operator"
else:
return "expression"
elif type_ == "star_expr":
return "starred"
elif type_ == "testlist_star_expr":
@@ -610,7 +623,10 @@ class _NameChecks(SyntaxRule):
@ErrorFinder.register_rule(type='string')
class _StringChecks(SyntaxRule):
message = "bytes can only contain ASCII literal characters."
if sys.version_info < (3, 10):
message = "bytes can only contain ASCII literal characters."
else:
message = "bytes can only contain ASCII literal characters"
def is_issue(self, leaf):
string_prefix = leaf.string_prefix.lower()
@@ -1043,14 +1059,20 @@ class _CheckAssignmentRule(SyntaxRule):
error = 'literal'
else:
if second.children[1] == ':':
error = 'dict display'
if self._normalizer.version < (3, 10):
error = 'dict display'
else:
error = 'dict literal'
else:
error = 'set display'
elif first == "{" and second == "}":
if self._normalizer.version < (3, 8):
error = 'literal'
else:
error = "dict display"
if self._normalizer.version < (3, 10):
error = "dict display"
else:
error = "dict literal"
elif first == "{" and len(node.children) > 2:
if self._normalizer.version < (3, 8):
error = 'literal'
@@ -1083,7 +1105,10 @@ class _CheckAssignmentRule(SyntaxRule):
error = str(node.value)
elif type_ == 'operator':
if node.value == '...':
error = 'Ellipsis'
if self._normalizer.version < (3, 10):
error = 'Ellipsis'
else:
error = 'ellipsis'
elif type_ == 'comparison':
error = 'comparison'
elif type_ in ('string', 'number', 'strings'):
@@ -1098,7 +1123,10 @@ class _CheckAssignmentRule(SyntaxRule):
if node.children[0] == 'await':
error = 'await expression'
elif node.children[-2] == '**':
error = 'operator'
if self._normalizer.version < (3, 10):
error = 'operator'
else:
error = 'expression'
else:
# Has a trailer
trailer = node.children[-1]
@@ -1120,7 +1148,10 @@ class _CheckAssignmentRule(SyntaxRule):
elif ('expr' in type_ and type_ != 'star_expr' # is a substring
or '_test' in type_
or type_ in ('term', 'factor')):
error = 'operator'
if self._normalizer.version < (3, 10):
error = 'operator'
else:
error = 'expression'
elif type_ == "star_expr":
if is_deletion:
if self._normalizer.version >= (3, 9):

169
parso/python/grammar313.txt Normal file
View File

@@ -0,0 +1,169 @@
# Grammar for Python
# NOTE WELL: You should also follow all the steps listed at
# https://devguide.python.org/grammar/
# 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() functions.
# NB: compound_stmt in single_input is followed by extra NEWLINE!
single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
file_input: stmt* ENDMARKER
eval_input: testlist NEWLINE* ENDMARKER
decorator: '@' namedexpr_test NEWLINE
decorators: decorator+
decorated: decorators (classdef | funcdef | async_funcdef)
async_funcdef: 'async' funcdef
funcdef: 'def' NAME parameters ['->' test] ':' suite
parameters: '(' [typedargslist] ')'
typedargslist: (
(tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [',' [ tfpdef ['=' test] (
',' tfpdef ['=' test])* ([',' [
'*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
| '**' tfpdef [',']]])
| '*' [tfpdef] (',' tfpdef ['=' test])* ([',' ['**' tfpdef [',']]])
| '**' tfpdef [',']]] )
| (tfpdef ['=' test] (',' tfpdef ['=' test])* [',' [
'*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
| '**' tfpdef [',']]]
| '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
| '**' tfpdef [','])
)
tfpdef: NAME [':' test]
varargslist: vfpdef ['=' test ](',' vfpdef ['=' test])* ',' '/' [',' [ (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [
'*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
| '**' vfpdef [',']]]
| '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
| '**' vfpdef [',']) ]] | (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [
'*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
| '**' vfpdef [',']]]
| '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
| '**' vfpdef [',']
)
vfpdef: NAME
stmt: simple_stmt | compound_stmt | NEWLINE
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
small_stmt: (expr_stmt | del_stmt | pass_stmt | flow_stmt |
import_stmt | global_stmt | nonlocal_stmt | assert_stmt)
expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
('=' (yield_expr|testlist_star_expr))*)
annassign: ':' test ['=' (yield_expr|testlist_star_expr)]
testlist_star_expr: (test|star_expr) (',' (test|star_expr))* [',']
augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' |
'<<=' | '>>=' | '**=' | '//=')
# For normal and annotated assignments, additional restrictions enforced by the interpreter
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_star_expr]
yield_stmt: yield_expr
raise_stmt: 'raise' [test ['from' test]]
import_stmt: import_name | import_from
import_name: 'import' dotted_as_names
# note below: the ('.' | '...') is necessary because '...' is tokenized as ELLIPSIS
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)*
nonlocal_stmt: 'nonlocal' NAME (',' NAME)*
assert_stmt: 'assert' test [',' test]
compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated | async_stmt
async_stmt: 'async' (funcdef | with_stmt | for_stmt)
if_stmt: 'if' namedexpr_test ':' suite ('elif' namedexpr_test ':' suite)* ['else' ':' suite]
while_stmt: 'while' namedexpr_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 (',' with_item)* ':' suite
with_item: test ['as' expr]
# NB compile.c makes sure that the default except clause is last
except_clause: 'except' [test ['as' NAME]]
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
namedexpr_test: test [':=' test]
test: or_test ['if' or_test 'else' test] | lambdef
lambdef: 'lambda' [varargslist] ':' test
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)*
# <> isn't actually a valid comparison operator in Python. It's here for the
# sake of a __future__ import described in PEP 401 (which really works :-)
comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not'
star_expr: '*' expr
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_expr ['**' factor]
atom_expr: ['await'] atom trailer*
atom: ('(' [yield_expr|testlist_comp] ')' |
'[' [testlist_comp] ']' |
'{' [dictorsetmaker] '}' |
NAME | NUMBER | strings | '...' | 'None' | 'True' | 'False')
testlist_comp: (namedexpr_test|star_expr) ( comp_for | (',' (namedexpr_test|star_expr))* [','] )
trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
subscriptlist: subscript (',' subscript)* [',']
subscript: test [':=' test] | [test] ':' [test] [sliceop]
sliceop: ':' [test]
exprlist: (expr|star_expr) (',' (expr|star_expr))* [',']
testlist: test (',' test)* [',']
dictorsetmaker: ( ((test ':' test | '**' expr)
(comp_for | (',' (test ':' test | '**' expr))* [','])) |
((test [':=' test] | star_expr)
(comp_for | (',' (test [':=' test] | star_expr))* [','])) )
classdef: 'class' NAME ['(' [arglist] ')'] ':' suite
arglist: argument (',' argument)* [',']
# The reason that keywords are test nodes instead of NAME is that using NAME
# results in an ambiguity. ast.c makes sure it's a NAME.
# "test '=' test" is really "keyword '=' test", but we have no such token.
# These need to be in a single rule to avoid grammar that is ambiguous
# to our LL(1) parser. Even though 'test' includes '*expr' in star_expr,
# we explicitly match '*' here, too, to give it proper precedence.
# Illegal combinations and orderings are blocked in ast.c:
# multiple (test comp_for) arguments are blocked; keyword unpackings
# that precede iterable unpackings are blocked; etc.
argument: ( test [comp_for] |
test ':=' test |
test '=' test |
'**' test |
'*' test )
comp_iter: comp_for | comp_if
sync_comp_for: 'for' exprlist 'in' or_test [comp_iter]
comp_for: ['async'] sync_comp_for
comp_if: 'if' or_test [comp_iter]
# not used in grammar, but may appear in "node" passed from Parser to Compiler
encoding_decl: NAME
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist_star_expr
strings: (STRING | fstring)+
fstring: FSTRING_START fstring_content* FSTRING_END
fstring_content: FSTRING_STRING | fstring_expr
fstring_conversion: '!' NAME
fstring_expr: '{' (testlist_comp | yield_expr) ['='] [ fstring_conversion ] [ fstring_format_spec ] '}'
fstring_format_spec: ':' fstring_content*

169
parso/python/grammar314.txt Normal file
View File

@@ -0,0 +1,169 @@
# Grammar for Python
# NOTE WELL: You should also follow all the steps listed at
# https://devguide.python.org/grammar/
# 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() functions.
# NB: compound_stmt in single_input is followed by extra NEWLINE!
single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
file_input: stmt* ENDMARKER
eval_input: testlist NEWLINE* ENDMARKER
decorator: '@' namedexpr_test NEWLINE
decorators: decorator+
decorated: decorators (classdef | funcdef | async_funcdef)
async_funcdef: 'async' funcdef
funcdef: 'def' NAME parameters ['->' test] ':' suite
parameters: '(' [typedargslist] ')'
typedargslist: (
(tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [',' [ tfpdef ['=' test] (
',' tfpdef ['=' test])* ([',' [
'*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
| '**' tfpdef [',']]])
| '*' [tfpdef] (',' tfpdef ['=' test])* ([',' ['**' tfpdef [',']]])
| '**' tfpdef [',']]] )
| (tfpdef ['=' test] (',' tfpdef ['=' test])* [',' [
'*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
| '**' tfpdef [',']]]
| '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
| '**' tfpdef [','])
)
tfpdef: NAME [':' test]
varargslist: vfpdef ['=' test ](',' vfpdef ['=' test])* ',' '/' [',' [ (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [
'*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
| '**' vfpdef [',']]]
| '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
| '**' vfpdef [',']) ]] | (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [
'*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
| '**' vfpdef [',']]]
| '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
| '**' vfpdef [',']
)
vfpdef: NAME
stmt: simple_stmt | compound_stmt | NEWLINE
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
small_stmt: (expr_stmt | del_stmt | pass_stmt | flow_stmt |
import_stmt | global_stmt | nonlocal_stmt | assert_stmt)
expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
('=' (yield_expr|testlist_star_expr))*)
annassign: ':' test ['=' (yield_expr|testlist_star_expr)]
testlist_star_expr: (test|star_expr) (',' (test|star_expr))* [',']
augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' |
'<<=' | '>>=' | '**=' | '//=')
# For normal and annotated assignments, additional restrictions enforced by the interpreter
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_star_expr]
yield_stmt: yield_expr
raise_stmt: 'raise' [test ['from' test]]
import_stmt: import_name | import_from
import_name: 'import' dotted_as_names
# note below: the ('.' | '...') is necessary because '...' is tokenized as ELLIPSIS
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)*
nonlocal_stmt: 'nonlocal' NAME (',' NAME)*
assert_stmt: 'assert' test [',' test]
compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated | async_stmt
async_stmt: 'async' (funcdef | with_stmt | for_stmt)
if_stmt: 'if' namedexpr_test ':' suite ('elif' namedexpr_test ':' suite)* ['else' ':' suite]
while_stmt: 'while' namedexpr_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 (',' with_item)* ':' suite
with_item: test ['as' expr]
# NB compile.c makes sure that the default except clause is last
except_clause: 'except' [test ['as' NAME]]
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
namedexpr_test: test [':=' test]
test: or_test ['if' or_test 'else' test] | lambdef
lambdef: 'lambda' [varargslist] ':' test
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)*
# <> isn't actually a valid comparison operator in Python. It's here for the
# sake of a __future__ import described in PEP 401 (which really works :-)
comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not'
star_expr: '*' expr
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_expr ['**' factor]
atom_expr: ['await'] atom trailer*
atom: ('(' [yield_expr|testlist_comp] ')' |
'[' [testlist_comp] ']' |
'{' [dictorsetmaker] '}' |
NAME | NUMBER | strings | '...' | 'None' | 'True' | 'False')
testlist_comp: (namedexpr_test|star_expr) ( comp_for | (',' (namedexpr_test|star_expr))* [','] )
trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
subscriptlist: subscript (',' subscript)* [',']
subscript: test [':=' test] | [test] ':' [test] [sliceop]
sliceop: ':' [test]
exprlist: (expr|star_expr) (',' (expr|star_expr))* [',']
testlist: test (',' test)* [',']
dictorsetmaker: ( ((test ':' test | '**' expr)
(comp_for | (',' (test ':' test | '**' expr))* [','])) |
((test [':=' test] | star_expr)
(comp_for | (',' (test [':=' test] | star_expr))* [','])) )
classdef: 'class' NAME ['(' [arglist] ')'] ':' suite
arglist: argument (',' argument)* [',']
# The reason that keywords are test nodes instead of NAME is that using NAME
# results in an ambiguity. ast.c makes sure it's a NAME.
# "test '=' test" is really "keyword '=' test", but we have no such token.
# These need to be in a single rule to avoid grammar that is ambiguous
# to our LL(1) parser. Even though 'test' includes '*expr' in star_expr,
# we explicitly match '*' here, too, to give it proper precedence.
# Illegal combinations and orderings are blocked in ast.c:
# multiple (test comp_for) arguments are blocked; keyword unpackings
# that precede iterable unpackings are blocked; etc.
argument: ( test [comp_for] |
test ':=' test |
test '=' test |
'**' test |
'*' test )
comp_iter: comp_for | comp_if
sync_comp_for: 'for' exprlist 'in' or_test [comp_iter]
comp_for: ['async'] sync_comp_for
comp_if: 'if' or_test [comp_iter]
# not used in grammar, but may appear in "node" passed from Parser to Compiler
encoding_decl: NAME
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist_star_expr
strings: (STRING | fstring)+
fstring: FSTRING_START fstring_content* FSTRING_END
fstring_content: FSTRING_STRING | fstring_expr
fstring_conversion: '!' NAME
fstring_expr: '{' (testlist_comp | yield_expr) ['='] [ fstring_conversion ] [ fstring_format_spec ] '}'
fstring_format_spec: ':' fstring_content*

View File

@@ -295,6 +295,8 @@ class FStringEnd(PythonLeaf):
class _StringComparisonMixin:
__slots__ = ()
def __eq__(self, other):
"""
Make comparisons with strings easy.
@@ -544,6 +546,7 @@ class Function(ClassOrFunc):
4. annotation (if present)
"""
type = 'funcdef'
__slots__ = ()
def __init__(self, children):
super().__init__(children)

View File

@@ -10,3 +10,6 @@ norecursedirs = .* docs scripts normalizer_issue_files build
# fine as long as we are using `clean_jedi_cache` as a session scoped
# fixture.
usefixtures = clean_parso_cache
# Disallow warnings
filterwarnings = error

View File

@@ -13,6 +13,9 @@ ignore =
[mypy]
show_error_codes = true
enable_error_code = ignore-without-code
disallow_subclassing_any = True
# Avoid creating future gotchas emerging from bad typing
@@ -23,3 +26,4 @@ warn_unused_configs = True
warn_unreachable = True
strict_equality = True
no_implicit_optional = False

View File

@@ -40,6 +40,11 @@ setup(
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: 3.14',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Text Editors :: Integrated Development Environments (IDE)',
'Topic :: Utilities',
@@ -47,12 +52,16 @@ setup(
],
extras_require={
'testing': [
'pytest<6.0.0',
'pytest',
'docopt',
],
'qa': [
'flake8==3.8.3',
'mypy==0.782',
# Latest version which supports Python 3.6
'flake8==5.0.4',
# Latest version which supports Python 3.6
'mypy==0.971',
# Arbitrary pins, latest at the time of pinning
'types-setuptools==67.2.0.1',
],
},
)

View File

@@ -29,7 +29,6 @@ FAILING_EXAMPLES = [
'from foo import a,',
'from __future__ import whatever',
'from __future__ import braces',
'from .__future__ import whatever',
'def f(x=3, y): pass',
'lambda x=3, y: x',
'__debug__ = 1',
@@ -216,7 +215,6 @@ FAILING_EXAMPLES = [
'f"{\'\\\'}"',
'f"{#}"',
"f'{1!b}'",
"f'{1:{5:{3}}}'",
"f'{'",
"f'{'",
"f'}'",
@@ -227,8 +225,6 @@ FAILING_EXAMPLES = [
"f'{1;1}'",
"f'{a;}'",
"f'{b\"\" \"\"}'",
# f-string expression part cannot include a backslash
r'''f"{'\n'}"''',
'async def foo():\n yield x\n return 1',
'async def foo():\n yield x\n return 1',
@@ -413,3 +409,17 @@ if sys.version_info[:2] >= (3, 8):
FAILING_EXAMPLES += [
"f'{1=!b}'",
]
if sys.version_info[:2] < (3, 12):
FAILING_EXAMPLES += [
# f-string expression part cannot include a backslash before 3.12
r'''f"{'\n'}"''',
# this compiles successfully but fails when evaluated in 3.12
"f'{1:{5:{3}}}'",
]
if sys.version_info[:2] < (3, 13):
# this compiles successfully but fails when evaluated in 3.13
FAILING_EXAMPLES += [
'from .__future__ import whatever',
]

View File

@@ -4,15 +4,20 @@ from parso import utils
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(version='15.8')
# The same is true for very old grammars (even though this is probably not
# going to be an issue.
# We support future grammars assuming future compatibility,
# but we don't know how to parse old grammars.
with pytest.raises(NotImplementedError):
load_grammar(version='1.5')
def test_load_grammar_uses_older_syntax():
load_grammar(version='4.0')
def test_load_grammar_doesnt_warn(each_version):
load_grammar(version=each_version)
@pytest.mark.parametrize(('string', 'result'), [
('2', (2, 7)), ('3', (3, 6)), ('1.1', (1, 1)), ('1.1.1', (1, 1)), ('300.1.31', (300, 1))
])

View File

@@ -1,6 +1,7 @@
"""
Testing if parso finds syntax errors and indentation errors.
"""
import re
import sys
import warnings
@@ -117,26 +118,90 @@ def _get_actual_exception(code):
assert False, "The piece of code should raise an exception."
# SyntaxError
if wanted == 'SyntaxError: assignment to keyword':
# Some errors have changed error message in later versions of Python,
# and we give a translation table here. We deal with special cases
# below.
translations = {
'SyntaxError: f-string: unterminated string':
'SyntaxError: EOL while scanning string literal',
"SyntaxError: f-string: expecting '}'":
'SyntaxError: EOL while scanning string literal',
'SyntaxError: f-string: empty expression not allowed':
'SyntaxError: invalid syntax',
"SyntaxError: f-string expression part cannot include '#'":
'SyntaxError: invalid syntax',
"SyntaxError: f-string: single '}' is not allowed":
'SyntaxError: invalid syntax',
'SyntaxError: cannot use starred expression here':
"SyntaxError: can't use starred expression here",
'SyntaxError: f-string: cannot use starred expression here':
"SyntaxError: f-string: can't use starred expression here",
'SyntaxError: unterminated string literal':
'SyntaxError: EOL while scanning string literal',
'SyntaxError: parameter without a default follows parameter with a default':
'SyntaxError: non-default argument follows default argument',
"SyntaxError: 'yield from' outside function":
"SyntaxError: 'yield' outside function",
"SyntaxError: f-string: valid expression required before '}'":
'SyntaxError: invalid syntax',
"SyntaxError: '{' was never closed":
'SyntaxError: invalid syntax',
"SyntaxError: f-string: invalid conversion character 'b': expected 's', 'r', or 'a'":
"SyntaxError: f-string: invalid conversion character: expected 's', 'r', or 'a'",
"SyntaxError: (value error) Invalid format specifier ' 5' for object of type 'int'":
'SyntaxError: f-string: expressions nested too deeply',
"SyntaxError: f-string: expecting a valid expression after '{'":
'SyntaxError: f-string: invalid syntax',
"SyntaxError: f-string: expecting '=', or '!', or ':', or '}'":
'SyntaxError: f-string: invalid syntax',
"SyntaxError: f-string: expecting '=', or '!', or ':', or '}'":
'SyntaxError: f-string: invalid syntax',
}
if wanted in translations:
wanted = translations[wanted]
elif wanted == 'SyntaxError: assignment to keyword':
return [wanted, "SyntaxError: can't assign to keyword",
'SyntaxError: cannot assign to __debug__'], line_nr
elif wanted == 'SyntaxError: f-string: unterminated string':
wanted = 'SyntaxError: EOL while scanning string literal'
elif wanted == 'SyntaxError: f-string expression part cannot include a backslash':
return [
wanted,
"SyntaxError: EOL while scanning string literal",
"SyntaxError: unexpected character after line continuation character",
], line_nr
elif wanted == "SyntaxError: f-string: expecting '}'":
wanted = 'SyntaxError: EOL while scanning string literal'
elif wanted == 'SyntaxError: f-string: empty expression not allowed':
wanted = 'SyntaxError: invalid syntax'
elif wanted == "SyntaxError: f-string expression part cannot include '#'":
wanted = 'SyntaxError: invalid syntax'
elif wanted == "SyntaxError: f-string: single '}' is not allowed":
wanted = 'SyntaxError: invalid syntax'
return [wanted], line_nr
elif "Maybe you meant '==' instead of '='?" in wanted:
wanted = wanted.removesuffix(" here. Maybe you meant '==' instead of '='?")
elif re.match(
r"SyntaxError: unterminated string literal \(detected at line \d+\)", wanted
):
wanted = "SyntaxError: EOL while scanning string literal"
elif re.match(
r"SyntaxError: unterminated triple-quoted string literal \(detected at line \d+\)",
wanted,
):
wanted = 'SyntaxError: EOF while scanning triple-quoted string literal'
elif re.match(
r"IndentationError: expected an indented block after '[^']*' statement on line \d",
wanted,
):
wanted = 'IndentationError: expected an indented block'
# The following two errors are produced for both some f-strings and
# some non-f-strings in Python 3.13:
elif wanted == "SyntaxError: can't use starred expression here":
wanted = [
"SyntaxError: can't use starred expression here",
"SyntaxError: f-string: can't use starred expression here"
]
elif wanted == 'SyntaxError: cannot mix bytes and nonbytes literals':
wanted = [
'SyntaxError: cannot mix bytes and nonbytes literals',
'SyntaxError: f-string: cannot mix bytes and nonbytes literals'
]
if isinstance(wanted, list):
return wanted, line_nr
else:
return [wanted], line_nr
def test_default_except_error_postition():