Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
22 changes: 22 additions & 0 deletions Grammar/python.gram
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ compound_stmt[stmt_ty]:
| &'try' try_stmt
| &'while' while_stmt
| match_stmt
| perchance_stmt

# SIMPLE STATEMENTS
# =================
Expand Down Expand Up @@ -481,6 +482,21 @@ finally_block[asdl_stmt_seq*]:
| invalid_finally_stmt
| 'finally' &&':' a=block { a }

# Perchance statement
# -------------------
# "perchance E1, E2: block" is sugar for "try: block / except (E1, E2): pass"
# and is desugared to a Try node right here in the parser.

perchance_stmt[stmt_ty]:
| "perchance" e=expressions ':' b=block {
CHECK_VERSION(stmt_ty, 16, "Perchance statements are",
_PyAST_Try(b,
CHECK(asdl_excepthandler_seq*, _PyPegen_singleton_seq(p,
CHECK(excepthandler_ty, _PyAST_ExceptHandler(e, NULL,
CHECK(asdl_stmt_seq*, _PyPegen_singleton_seq(p,
CHECK(stmt_ty, _PyAST_Pass(EXTRA)))), EXTRA)))),
NULL, NULL, EXTRA)) }

# Match statement
# ---------------

Expand Down Expand Up @@ -719,12 +735,18 @@ expression[expr_ty] (memo):
| invalid_expression
| invalid_legacy_expression
| if_expression
| perchance_expression
| disjunction
| lambdef

if_expression[expr_ty]:
| a=disjunction 'if' b=disjunction 'else' c=expression { _PyAST_IfExp(b, a, c, EXTRA) }

perchance_expression[expr_ty] (memo):
| a=perchance_expression "perchance" f=disjunction g=['from' e=disjunction { e }] {
CHECK_VERSION(expr_ty, 16, "Perchance expressions are", _PyAST_Perchance(a, f, g, EXTRA)) }
| disjunction

yield_expr[expr_ty]:
| 'yield' 'from' a=expression { _PyAST_YieldFrom(a, EXTRA) }
| 'yield' a=[star_expressions] { _PyAST_Yield(a, EXTRA) }
Expand Down
25 changes: 17 additions & 8 deletions Include/internal/pycore_ast.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Include/internal/pycore_ast_state.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Include/internal/pycore_opcode_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ extern "C" {
#define CONSTANT_MINUS_ONE 11
#define CONSTANT_BUILTIN_FROZENSET 12
#define CONSTANT_EMPTY_TUPLE 13
#define NUM_COMMON_CONSTANTS 14
#define CONSTANT_EXCEPTION 14
#define NUM_COMMON_CONSTANTS 15

/* Values used in the oparg for RESUME */
#define RESUME_AT_FUNC_START 0
Expand Down
11 changes: 11 additions & 0 deletions Lib/_ast_unparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,17 @@ def visit_IfExp(self, node):
self.set_precedence(_Precedence.TEST, node.orelse)
self.traverse(node.orelse)

def visit_Perchance(self, node):
with self.require_parens(_Precedence.TEST, node):
self.set_precedence(_Precedence.TEST.next(), node.value, node.fallback)
self.traverse(node.value)
self.write(" perchance ")
self.traverse(node.fallback)
if node.guard:
self.set_precedence(_Precedence.TEST.next(), node.guard)
self.write(" from ")
self.traverse(node.guard)

def visit_Set(self, node):
if node.elts:
with self.delimit("{", "}"):
Expand Down
42 changes: 41 additions & 1 deletion Lib/_pyrepl/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,10 @@ def gen_colors_from_token_stream(
is_def_name = True
elif (
keyword.issoftkeyword(token.string)
and bracket_level == 0
# perchance expressions are common inside brackets
# (comprehensions); the other soft keywords only start
# statements and cannot appear there
and (bracket_level == 0 or token.string == "perchance")
and is_soft_keyword_used(prev_token, token, next_token)
):
span = Span.from_token(token, line_lengths)
Expand All @@ -247,6 +250,18 @@ def gen_colors_from_token_stream(
keyword_first_sets_case = frozenset({"False", "None", "True"})


def _starts_expression(token: TI | None) -> bool:
match token:
case TI(T.NUMBER | T.STRING | T.FSTRING_START | T.TSTRING_START):
return True
case TI(T.OP, string="(" | "[" | "{" | "-" | "+" | "~" | "..."):
return True
case TI(T.NAME, string=s):
return not keyword.iskeyword(s) or s in keyword_first_sets_match
case _:
return False


def is_soft_keyword_used(*tokens: TI | None) -> bool:
"""Returns True if the current token is a keyword in this context.

Expand Down Expand Up @@ -299,6 +314,31 @@ def is_soft_keyword_used(*tokens: TI | None) -> bool:
TI(string="import") | TI(string="from")
):
return True
# expression form: <expr> perchance <fallback>; the previous token
# must be able to end an expression and the next one to start one
case (
TI(T.NUMBER | T.STRING | T.FSTRING_END | T.TSTRING_END)
| TI(T.OP, string=")" | "]" | "}" | "..."),
TI(string="perchance"),
next_tok
) if _starts_expression(next_tok):
return True
case (
TI(T.NAME, string=p),
TI(string="perchance"),
next_tok
) if (
(not keyword.iskeyword(p) or p in keyword_first_sets_case)
and _starts_expression(next_tok)
):
return True
# statement form: perchance E1, E2: <block>
case (
None | TI(T.NEWLINE) | TI(T.INDENT) | TI(T.DEDENT),
TI(string="perchance"),
next_tok
) if _starts_expression(next_tok):
return True
case _:
return False

Expand Down
1 change: 1 addition & 0 deletions Lib/keyword.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
builtins.set,
# Append-only — must match CONSTANT_* in
# Include/internal/pycore_opcode_utils.h.
None, "", True, False, -1, builtins.frozenset, ()]
None, "", True, False, -1, builtins.frozenset, (),
builtins.Exception]
_nb_ops = _opcode.get_nb_ops()

hascompare = [opmap["COMPARE_OP"]]
Expand Down
1 change: 1 addition & 0 deletions Parser/Python.asdl
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ module Python
| UnaryOp(unaryop op, expr operand)
| Lambda(arguments args, expr body)
| IfExp(expr test, expr body, expr orelse)
| Perchance(expr value, expr fallback, expr? guard)
| Dict(expr?* keys, expr* values)
| Set(expr* elts)
| ListComp(expr elt, comprehension* generators)
Expand Down
Loading
Loading