Replace LALRPOP parser with hand-written parser

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Victor Hugo Gomes
2024-02-19 09:40:00 +05:30
committed by Dhruv Manilawala
parent 461cdad53a
commit 78ee6441a7
253 changed files with 37848 additions and 5629 deletions

View File

@@ -0,0 +1,6 @@
# Check http://editorconfig.org for more information
# This is the main config file for this project:
root = true
[*.py]
insert_final_newline = false

View File

@@ -0,0 +1,6 @@
f(b=20, c)
f(**b, *c)
# Duplicate keyword argument
f(a=20, a=30)

View File

@@ -0,0 +1,7 @@
a = (🐶
# comment 🐶
)
a = (🐶 +
# comment
🐶)

View File

@@ -0,0 +1,2 @@
# TODO(micha): The offset of the generated error message is off by one.
lambda a, b=20, c: 1

View File

@@ -0,0 +1,9 @@
lambda a, a: 1
lambda a, *, a: 1
lambda a, a=20: 1
lambda a, *a: 1
lambda a, *, **a: 1

View File

@@ -0,0 +1,7 @@
a = pass = c
a + b
a = b = pass = c
a + b

View File

@@ -0,0 +1,7 @@
a = = c
a + b

View File

@@ -0,0 +1,2 @@
# TODO(micha): The range of the generated error message is off by one.
def f(a, b=20, c): pass

View File

@@ -0,0 +1,12 @@
def f(a, a): pass
def f2(a, *, a): pass
def f3(a, a=20): pass
def f4(a, *a): pass
def f5(a, *, **a): pass
# TODO(micha): This is inconsistent. All other examples only highlight the argument name.
def f6(a, a: str): pass

View File

@@ -0,0 +1,24 @@
# FIXME: The type param related error message and the parser recovery are looking pretty good **except**
# that the lexer never recovers from the unclosed `[`, resulting in it lexing `NonLogicalNewline` tokens instead of `Newline` tokens.
# That's because the parser has no way of feeding the error recovery back to the lexer,
# so they don't agree on the state of the world which can lead to all kind of errors further down in the file.
# This is not just a problem with parentheses but also with the transformation made by the
# `SoftKeywordTransformer` because the `Parser` and `Transfomer` may not agree if they're
# currently in a position where the `type` keyword is allowed or not.
# That roughly means that any kind of recovery can lead to unrelated syntax errors
# on following lines.
def unclosed[A, *B(test: name):
pass
a + b
def keyword[A, await](): ...
def not_a_type_param[A, |, B](): ...
def multiple_commas[A,,B](): ...
def multiple_trailing_commas[A,,](): ...
def multiple_commas_and_recovery[A,,100](): ...

View File

@@ -0,0 +1,7 @@
if True:
pass
elif False:
pass
elf:
pass

View File

@@ -0,0 +1,3 @@
# FIXME(micha): This creates two syntax errors instead of just one (and overlapping ones)
if True)):
pass

View File

@@ -0,0 +1,8 @@
# Improving the recovery would require changing the lexer to emit an extra dedent token after `a + b`.
if True:
pass
a + b
pass
a = 10

View File

@@ -0,0 +1,6 @@
if True:
a + b
if False: # This if statement has neither an indent nor a newline.

View File

@@ -0,0 +1,4 @@
if True
pass
a = 10

View File

@@ -0,0 +1,11 @@
from abc import a, b,
a + b
from abc import ,,
from abc import
from abc import (a, b, c
a + b

View File

@@ -0,0 +1,53 @@
# Regression test: https://github.com/astral-sh/ruff/issues/6895
# First we test, broadly, that various kinds of assignments are now
# rejected by the parser. e.g., `5 = 3`, `5 += 3`, `(5): int = 3`.
5 = 3
5 += 3
(5): int = 3
# Now we exhaustively test all possible cases where assignment can fail.
x or y = 42
(x := 5) = 42
x + y = 42
-x = 42
(lambda _: 1) = 42
a if b else c = 42
{"a": 5} = 42
{a} = 42
[x for x in xs] = 42
{x for x in xs} = 42
{x: x * 2 for x in xs} = 42
(x for x in xs) = 42
await x = 42
(yield x) = 42
(yield from xs) = 42
a < b < c = 42
foo() = 42
# N.B. It looks like the parser can't generate a top-level
# FormattedValue, where as the official Python AST permits
# representing a single f-string containing just a variable as a
# FormattedValue directly.
#
# Bottom line is that because of this, this test is (at present)
# duplicative with the `fstring` test. That is, in theory these tests
# could fail independently, but in practice their failure or success
# is coupled.
#
# See: https://docs.python.org/3/library/ast.html#ast.FormattedValue
f"{quux}" = 42
f"{foo} and {bar}" = 42
"foo" = 42
b"foo" = 42
123 = 42
True = 42
None = 42
... = 42
*foo() = 42
[x, foo(), y] = [42, 42, 42]
[[a, b], [[42]], d] = [[1, 2], [[3]], 4]
(x, foo(), y) = (42, 42, 42)

View File

@@ -0,0 +1,3 @@
# This test previously passed before the assignment operator checking
# above, but we include it here for good measure.
(5 := 3)

View File

@@ -0,0 +1 @@
x = {y for y in (1, 2, 3)}

View File

@@ -0,0 +1,11 @@
lambda: 1
lambda a, b, c: 1
lambda a, b=20, c=30: 1
lambda *, a, b, c: 1
lambda *, a, b=20, c=30: 1
lambda a, b, c, *, d, e: 0

View File

@@ -0,0 +1 @@
x = [y for y in (1, 2, 3)]

View File

@@ -0,0 +1,4 @@
if x := 1:
pass
(x := 5)

View File

@@ -0,0 +1 @@
x: int = 1

View File

@@ -0,0 +1,40 @@
x = (1, 2, 3)
(x, y) = (1, 2, 3)
[x, y] = (1, 2, 3)
x.y = (1, 2, 3)
x[y] = (1, 2, 3)
(x, *y) = (1, 2, 3)
# This last group of tests checks that assignments we expect to be parsed
# (including some interesting ones) continue to be parsed successfully.
*foo = 42
[x, y, z] = [1, 2, 3]
(x, y, z) = (1, 2, 3)
x[0] = 42
# This is actually a type error, not a syntax error. So check that it
# doesn't fail parsing.
5[0] = 42
x[1:2] = [42]
# This is actually a type error, not a syntax error. So check that it
# doesn't fail parsing.
5[1:2] = [42]
foo.bar = 42
# This is actually an attribute error, not a syntax error. So check that
# it doesn't fail parsing.
"foo".y = 42
foo = 42

View File

@@ -0,0 +1,3 @@
x += 1
x.y += (1, 2, 3)
x[y] += (1, 2, 3)

View File

@@ -0,0 +1,3 @@
del x
del x.y
del x[y]

View File

@@ -0,0 +1,2 @@
for x in (1, 2, 3):
pass

View File

@@ -0,0 +1,38 @@
def no_parameters():
pass
def positional_parameters(a, b, c):
pass
def positional_parameters_with_default_values(a, b=20, c=30):
pass
def keyword_only_parameters(*, a, b, c):
pass
def keyword_only_parameters_with_defaults(*, a, b=20, c=30):
pass
def positional_and_keyword_parameters(a, b, c, *, d, e, f):
pass
def positional_and_keyword_parameters_with_defaults(a, b, c, *, d, e=20, f=30):
pass
def positional_and_keyword_parameters_with_defaults_and_varargs(
a, b, c, *args, d, e=20, f=30
):
pass
def positional_and_keyword_parameters_with_defaults_and_varargs_and_kwargs(
a, b, c, *args, d, e=20, f=30, **kwargs
):
pass

View File

@@ -0,0 +1,2 @@
with 1 as x:
pass