Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 29 additions & 15 deletions Grammar/python.gram
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,37 @@ parse(Parser *p)

// Run parser
void *result = NULL;
if (p->start_rule_func == START) {
result = start_rule(p);
} else if (p->start_rule_func == EXPRESSIONS) {
result = expressions_rule(p);
if (p->start_rule_func == Py_file_input) {
result = file_rule(p);
} else if (p->start_rule_func == Py_single_input) {
result = interactive_rule(p);
} else if (p->start_rule_func == Py_eval_input) {
result = eval_rule(p);
} else if (p->start_rule_func == Py_fstring_input) {
result = fstring_rule(p);
}

return result;
}

// The end
'''
file[mod_ty]: a=[statements] ENDMARKER { Module(a, NULL, p->arena) }
interactive[mod_ty]: a=statement_newline { Interactive(a, p->arena) }
eval[mod_ty]: a=expressions NEWLINE* { Expression(a, p->arena) }
fstring[expr_ty]: star_expressions

start[mod_ty]: a=[statements] ENDMARKER { Module(a, NULL, p->arena) }
statements[asdl_seq*]: a=statement+ { seq_flatten(p, a) }

statement[asdl_seq*]: a=compound_stmt { singleton_seq(p, a) } | simple_stmt
statement_newline[asdl_seq*]: a=compound_stmt NEWLINE { singleton_seq(p, a) } | simple_stmt
simple_stmt[asdl_seq*]:
| a=small_stmt !';' NEWLINE { singleton_seq(p, a) } # Not needed, there for speedup
| a=';'.small_stmt+ [';'] NEWLINE { a }
# NOTE: assignment MUST precede expression, else parsing a simple assignment
# will throw a SyntaxError.
small_stmt[stmt_ty] (memo):
| assignment
| e=expressions { _Py_Expr(e, EXTRA) }
| e=star_expressions { _Py_Expr(e, EXTRA) }
| &'return' return_stmt
| &('import' | 'from') import_stmt
| &'raise' raise_stmt
Expand Down Expand Up @@ -62,9 +69,9 @@ assignment:
| a=('(' b=inside_paren_ann_assign_target ')' { b }
| ann_assign_subscript_attribute_target) ':' b=expression c=['=' d=annotated_rhs { d }] {
_Py_AnnAssign(a, b, c, 0, EXTRA)}
| a=(z=star_targets '=' { z })+ b=(yield_expr | expressions) {
| a=(z=star_targets '=' { z })+ b=(yield_expr | star_expressions) {
_Py_Assign(a, b, NULL, EXTRA) }
| a=target b=augassign c=(yield_expr | expressions) {
| a=target b=augassign c=(yield_expr | star_expressions) {
_Py_AugAssign(a, b->kind, c, EXTRA) }

augassign[AugOperator*]:
Expand Down Expand Up @@ -131,9 +138,9 @@ while_stmt[stmt_ty]:
| 'while' a=named_expression ':' b=block c=[else_block] { _Py_While(a, b, c, EXTRA) }

for_stmt[stmt_ty]:
| ASYNC 'for' t=star_targets 'in' ex=expressions ':' b=block el=[else_block] {
| ASYNC 'for' t=star_targets 'in' ex=star_expressions ':' b=block el=[else_block] {
_Py_AsyncFor(t, ex, b, el, NULL, EXTRA) }
| 'for' t=star_targets 'in' ex=expressions ':' b=block el=[else_block] {
| 'for' t=star_targets 'in' ex=star_expressions ':' b=block el=[else_block] {
_Py_For(t, ex, b, el, NULL, EXTRA) }

with_stmt[stmt_ty]:
Expand All @@ -158,7 +165,7 @@ except_block[excepthandler_ty]:
finally_block[asdl_seq*]: 'finally' ':' a=block { a }

return_stmt[stmt_ty]:
| 'return' a=[expressions] { _Py_Return(a, EXTRA) }
| 'return' a=[star_expressions] { _Py_Return(a, EXTRA) }

raise_stmt[stmt_ty]:
| 'raise' a=expression b=['from' z=expression { z }] { _Py_Raise(a, b, EXTRA) }
Expand Down Expand Up @@ -223,7 +230,7 @@ class_def_raw[stmt_ty]:
block[asdl_seq*] (memo): NEWLINE INDENT a=statements DEDENT { a } | simple_stmt

expressions_list[asdl_seq*]: a=','.star_expression+ [','] { a }
expressions[expr_ty]:
star_expressions[expr_ty]:
| a=star_expression b=(',' c=star_expression { c })+ [','] {
_Py_Tuple(CHECK(seq_insert_in_front(p, a, b)), Load, EXTRA) }
| a=star_expression ',' { _Py_Tuple(CHECK(singleton_seq(p, a)), Load, EXTRA) }
Expand All @@ -239,7 +246,14 @@ star_named_expression[expr_ty]:
named_expression[expr_ty]:
| a=NAME ':=' b=expression { _Py_NamedExpr(CHECK(set_expr_context(p, a, Store)), b, EXTRA) }
| expression
annotated_rhs[expr_ty]: yield_expr | expressions

annotated_rhs[expr_ty]: yield_expr | star_expressions

expressions[expr_ty]:
| a=expression b=(',' c=expression { c })+ [','] {
_Py_Tuple(CHECK(seq_insert_in_front(p, a, b)), Load, EXTRA) }
| a=expression ',' { _Py_Tuple(CHECK(singleton_seq(p, a)), Load, EXTRA) }
| expression
expression[expr_ty] (memo):
| a=disjunction 'if' b=disjunction 'else' c=expression { _Py_IfExp(b, a, c, EXTRA) }
| disjunction
Expand Down Expand Up @@ -408,7 +422,7 @@ for_if_clauses[asdl_seq*]:

yield_expr[expr_ty]:
| 'yield' 'from' a=expression { _Py_YieldFrom(a, EXTRA) }
| 'yield' a=[expressions] { _Py_Yield(a, EXTRA) }
| 'yield' a=[star_expressions] { _Py_Yield(a, EXTRA) }

arguments[expr_ty] (memo):
| a=args [','] { a }
Expand Down
3 changes: 3 additions & 0 deletions Include/compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,7 @@ PyAPI_FUNC(int) _PyAST_Optimize(struct _mod *, PyArena *arena, _PyASTOptimizeSta
#define Py_eval_input 258
#define Py_func_type_input 345

/* This doesn't need to match anything */
#define Py_fstring_input 0

#endif /* !Py_COMPILE_H */
8 changes: 4 additions & 4 deletions Include/pegen_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ extern "C" {
#include "Python.h"
#include "Python-ast.h"

PyAPI_FUNC(mod_ty) PyPegen_ASTFromFile(const char *filename, PyArena *arena);
PyAPI_FUNC(mod_ty) PyPegen_ASTFromString(const char *str, PyArena *arena);
PyAPI_FUNC(PyCodeObject *) PyPegen_CodeObjectFromFile(const char *filename);
PyAPI_FUNC(PyCodeObject *) PyPegen_CodeObjectFromString(const char *str);
PyAPI_FUNC(mod_ty) PyPegen_ASTFromFile(const char *filename, int mode, PyArena *arena);
PyAPI_FUNC(mod_ty) PyPegen_ASTFromString(const char *str, int mode, PyArena *arena);
PyAPI_FUNC(PyCodeObject *) PyPegen_CodeObjectFromFile(const char *filename, int mode);
PyAPI_FUNC(PyCodeObject *) PyPegen_CodeObjectFromString(const char *str, int mode);

#ifdef __cplusplus
}
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_peg_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,12 @@ def f():
),
}

EXPRESSIONS_TEST_CASES = [
("expression_add", "1+1"),
("expression_add_2", "a+b"),
("expression_call", "f(a, b=2, **kw)"),
]


def cleanup_source(source: Any) -> str:
if isinstance(source, str):
Expand Down Expand Up @@ -662,6 +668,10 @@ def prepare_test_cases(

FAIL_TEST_IDS, FAIL_SOURCES = prepare_test_cases(FAIL_TEST_CASES)

EXPRESSIONS_TEST_IDS, EXPRESSIONS_TEST_SOURCES = prepare_test_cases(
EXPRESSIONS_TEST_CASES
)


class ASTGenerationTest(unittest.TestCase):
def test_correct_ast_generation_on_source_files(self) -> None:
Expand Down Expand Up @@ -706,3 +716,13 @@ def test_fstring_parse_error_tracebacks(self) -> None:
with self.assertRaises(SyntaxError) as se:
peg_parser.parse_string(dedent(source))
self.assertEqual(error_text, se.exception.text)

def test_correct_ast_generatrion_eval(self) -> None:
for source in EXPRESSIONS_TEST_SOURCES:
actual_ast = peg_parser.parse_string(source, mode='eval')
expected_ast = ast.parse(source, mode='eval')
self.assertEqual(
ast.dump(actual_ast, include_attributes=True),
ast.dump(expected_ast, include_attributes=True),
f"Wrong AST generation for source: {source}",
)
39 changes: 30 additions & 9 deletions Modules/peg_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,33 @@
#include <pegen_interface.h>

PyObject *
_Py_parse_file(PyObject *self, PyObject *args)
_Py_parse_file(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *keywords[] = {"file", "mode", NULL};
char *filename;
char *mode_str = "exec";

if (!PyArg_ParseTuple(args, "s", &filename)) {
if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|s", keywords, &filename, &mode_str)) {
return NULL;
}

int mode;
if (strcmp(mode_str, "exec") == 0) {
mode = Py_file_input;
} else if (strcmp(mode_str, "eval") == 0) {
mode = Py_eval_input;
} else {
return PyErr_Format(PyExc_ValueError, "mode must be either 'exec' or 'eval'");
}

PyArena *arena = PyArena_New();
if (arena == NULL) {
return NULL;
}

PyObject *result = NULL;

mod_ty res = PyPegen_ASTFromFile(filename, arena);
mod_ty res = PyPegen_ASTFromFile(filename, mode, arena);
if (res == NULL) {
goto error;
}
Expand All @@ -29,22 +40,33 @@ _Py_parse_file(PyObject *self, PyObject *args)
}

PyObject *
_Py_parse_string(PyObject *self, PyObject *args)
_Py_parse_string(PyObject *self, PyObject *args, PyObject *kwds)
{
static char *keywords[] = {"string", "mode", NULL};
char *the_string;
char *mode_str = "exec";

if (!PyArg_ParseTuple(args, "s", &the_string)) {
if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|s", keywords, &the_string, &mode_str)) {
return NULL;
}

int mode;
if (strcmp(mode_str, "exec") == 0) {
mode = Py_file_input;
} else if (strcmp(mode_str, "eval") == 0) {
mode = Py_eval_input;
} else {
return PyErr_Format(PyExc_ValueError, "mode must be either 'exec' or 'eval'");
}

PyArena *arena = PyArena_New();
if (arena == NULL) {
return NULL;
}

PyObject *result = NULL;

mod_ty res = PyPegen_ASTFromString(the_string, arena);
mod_ty res = PyPegen_ASTFromString(the_string, mode, arena);
if (res == NULL) {
goto error;
}
Expand All @@ -56,9 +78,8 @@ _Py_parse_string(PyObject *self, PyObject *args)
}

static PyMethodDef ParseMethods[] = {
{"parse_file", (PyCFunction)(void (*)(void))_Py_parse_file, METH_VARARGS, "Parse a file."},
{"parse_string", (PyCFunction)(void (*)(void))_Py_parse_string, METH_VARARGS,
"Parse a string."},
{"parse_file", (PyCFunction)(void (*)(void))_Py_parse_file, METH_VARARGS|METH_KEYWORDS, "Parse a file."},
{"parse_string", (PyCFunction)(void (*)(void))_Py_parse_string, METH_VARARGS|METH_KEYWORDS,"Parse a string."},
{NULL, NULL, 0, NULL} /* Sentinel */
};

Expand Down
Loading