Implement PEP-634 - Match statement (#568)

* ParenthesizedNode implementation for Box

* match statement rust CST and grammar

* match statement python CST and docs

* run rust unit tests in release mode for now
This commit is contained in:
Zsolt Dollenstein 2021-12-30 10:00:51 +00:00 committed by GitHub
parent 67db03915d
commit 9932a6d339
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 5912 additions and 10 deletions

View file

@ -260,7 +260,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path=native/Cargo.toml
args: --manifest-path=native/Cargo.toml --release
- name: clippy
uses: actions-rs/clippy-check@v1
with:

View file

@ -164,6 +164,23 @@ from libcst._nodes.statement import (
ImportAlias,
ImportFrom,
IndentedBlock,
Match,
MatchAs,
MatchCase,
MatchClass,
MatchKeywordElement,
MatchList,
MatchMapping,
MatchMappingElement,
MatchOr,
MatchOrElement,
MatchPattern,
MatchSequence,
MatchSequenceElement,
MatchSingleton,
MatchStar,
MatchTuple,
MatchValue,
NameItem,
Nonlocal,
Pass,
@ -380,6 +397,23 @@ __all__ = [
"ImportAlias",
"ImportFrom",
"IndentedBlock",
"Match",
"MatchCase",
"MatchAs",
"MatchClass",
"MatchKeywordElement",
"MatchList",
"MatchMapping",
"MatchMappingElement",
"MatchOr",
"MatchOrElement",
"MatchPattern",
"MatchSequence",
"MatchSequenceElement",
"MatchSingleton",
"MatchStar",
"MatchTuple",
"MatchValue",
"NameItem",
"Nonlocal",
"Pass",

View file

@ -6,13 +6,14 @@
import inspect
import re
from abc import ABC, abstractmethod
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import Optional, Pattern, Sequence, Union
from libcst._add_slots import add_slots
from libcst._maybe_sentinel import MaybeSentinel
from libcst._nodes.base import CSTNode, CSTValidationError
from libcst._nodes.expression import (
_BaseParenthesizedNode,
Annotation,
Arg,
Asynchronous,
@ -24,11 +25,15 @@ from libcst._nodes.expression import (
ConcatenatedString,
ExpressionPosition,
From,
LeftCurlyBrace,
LeftParen,
LeftSquareBracket,
List,
Name,
Parameters,
RightCurlyBrace,
RightParen,
RightSquareBracket,
SimpleString,
Tuple,
)
@ -40,7 +45,15 @@ from libcst._nodes.internal import (
visit_sentinel,
visit_sequence,
)
from libcst._nodes.op import AssignEqual, BaseAugOp, Comma, Dot, ImportStar, Semicolon
from libcst._nodes.op import (
AssignEqual,
BaseAugOp,
BitOr,
Comma,
Dot,
ImportStar,
Semicolon,
)
from libcst._nodes.whitespace import (
BaseParenthesizableWhitespace,
EmptyLine,
@ -2566,3 +2579,811 @@ class Nonlocal(BaseSmallStatement):
state.add_token("; ")
elif isinstance(semicolon, Semicolon):
semicolon._codegen(state)
class MatchPattern(_BaseParenthesizedNode, ABC):
"""
A base class for anything that can appear as a pattern in a :class:`Match`
statement.
"""
@add_slots
@dataclass(frozen=True)
class Match(BaseCompoundStatement):
"""
A ``match`` statement.
"""
#: The subject of the match.
subject: BaseExpression
#: A non-empty list of match cases.
cases: Sequence["MatchCase"]
#: Sequence of empty lines appearing before this compound statement line.
leading_lines: Sequence[EmptyLine] = ()
#: Whitespace between the ``match`` keyword and the subject.
whitespace_after_match: SimpleWhitespace = SimpleWhitespace.field(" ")
#: Whitespace after the subject but before the colon.
whitespace_before_colon: SimpleWhitespace = SimpleWhitespace.field("")
#: Any optional trailing comment and the final ``NEWLINE`` at the end of the line.
whitespace_after_colon: TrailingWhitespace = TrailingWhitespace.field()
#: A string represents a specific indentation. A ``None`` value uses the modules's
#: default indentation. This is included because indentation is allowed to be
#: inconsistent across a file, just not ambiguously.
indent: Optional[str] = None
#: Any trailing comments or lines after the dedent that are owned by this match
#: block. Statements own preceeding and same-line trailing comments, but not
#: trailing lines, so it falls on :class:`Match` to own it. In the case
#: that a statement follows a :class:`Match` block, that statement will own the
#: comments and lines that are at the same indent as the statement, and this
#: :class:`Match` will own the comments and lines that are indented further.
footer: Sequence[EmptyLine] = ()
def _validate(self) -> None:
if len(self.cases) == 0:
raise CSTValidationError("A match statement must have at least one case.")
if self.whitespace_after_match.empty:
raise CSTValidationError(
"Must have at least one space after a 'match' keyword"
)
indent = self.indent
if indent is not None:
if len(indent) == 0:
raise CSTValidationError(
"A match statement must have a non-zero width indent."
)
if _INDENT_WHITESPACE_RE.fullmatch(indent) is None:
raise CSTValidationError(
"An indent must be composed of only whitespace characters."
)
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "Match":
return Match(
leading_lines=visit_sequence(
self, "leading_lines", self.leading_lines, visitor
),
whitespace_after_match=visit_required(
self, "whitespace_after_match", self.whitespace_after_match, visitor
),
subject=visit_required(self, "subject", self.subject, visitor),
whitespace_before_colon=visit_required(
self, "whitespace_before_colon", self.whitespace_before_colon, visitor
),
whitespace_after_colon=visit_required(
self, "whitespace_after_colon", self.whitespace_after_colon, visitor
),
indent=self.indent,
cases=visit_sequence(self, "cases", self.cases, visitor),
footer=visit_sequence(self, "footer", self.footer, visitor),
)
def _codegen_impl(self, state: CodegenState) -> None:
for ll in self.leading_lines:
ll._codegen(state)
state.add_indent_tokens()
with state.record_syntactic_position(self, end_node=self.cases[-1]):
state.add_token("match")
self.whitespace_after_match._codegen(state)
self.subject._codegen(state)
self.whitespace_before_colon._codegen(state)
state.add_token(":")
self.whitespace_after_colon._codegen(state)
indent = self.indent
state.increase_indent(state.default_indent if indent is None else indent)
for c in self.cases:
c._codegen(state)
for f in self.footer:
f._codegen(state)
state.decrease_indent()
@add_slots
@dataclass(frozen=True)
class MatchCase(CSTNode):
"""
A single ``case`` block of a :class:`Match` statement.
"""
#: The pattern that ``subject`` will be matched against.
pattern: MatchPattern
#: The body of this case block, to be evaluated if ``pattern`` matches ``subject``
#: and ``guard`` evaluates to a truthy value.
body: BaseSuite
#: Optional expression that will be evaluated if ``pattern`` matches ``subject``.
guard: Optional[BaseExpression] = None
#: Sequence of empty lines appearing before this case block.
leading_lines: Sequence[EmptyLine] = ()
#: Whitespace directly after the ``case`` keyword.
whitespace_after_case: SimpleWhitespace = SimpleWhitespace.field(" ")
#: Whitespace before the ``if`` keyword in case there's a guard expression.
whitespace_before_if: SimpleWhitespace = SimpleWhitespace.field("")
#: Whitespace after the ``if`` keyword in case there's a guard expression.
whitespace_after_if: SimpleWhitespace = SimpleWhitespace.field("")
#: Whitespace before the colon.
whitespace_before_colon: SimpleWhitespace = SimpleWhitespace.field("")
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "CSTNode":
return MatchCase(
leading_lines=visit_sequence(
self, "leading_lines", self.leading_lines, visitor
),
whitespace_after_case=visit_required(
self, "whitespace_after_case", self.whitespace_after_case, visitor
),
pattern=visit_required(self, "pattern", self.pattern, visitor),
whitespace_before_if=visit_optional(
self, "whitespace_before_if", self.whitespace_before_if, visitor
),
whitespace_after_if=visit_optional(
self, "whitespace_after_if", self.whitespace_after_if, visitor
),
guard=visit_optional(self, "guard", self.guard, visitor),
body=visit_required(self, "body", self.body, visitor),
)
def _codegen_impl(self, state: CodegenState) -> None:
for ll in self.leading_lines:
ll._codegen(state)
state.add_indent_tokens()
with state.record_syntactic_position(self, end_node=self.body):
state.add_token("case")
self.whitespace_after_case._codegen(state)
self.pattern._codegen(state)
guard = self.guard
if guard is not None:
self.whitespace_before_if._codegen(state)
state.add_token("if")
self.whitespace_after_if._codegen(state)
guard._codegen(state)
self.whitespace_before_colon._codegen(state)
state.add_token(":")
self.body._codegen(state)
@add_slots
@dataclass(frozen=True)
class MatchValue(MatchPattern):
"""
A match literal or value pattern that compares by equality.
"""
#: an expression to compare to
value: BaseExpression
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "CSTNode":
return MatchValue(value=visit_required(self, "value", self.value, visitor))
def _codegen_impl(self, state: CodegenState) -> None:
with state.record_syntactic_position(self):
self.value._codegen(state)
@property
def lpar(self) -> Sequence[LeftParen]:
return self.value.lpar
@lpar.setter
def lpar(self, value: Sequence[LeftParen]) -> None:
self.value.lpar = value
@add_slots
@dataclass(frozen=True)
class MatchSingleton(MatchPattern):
"""
A match literal pattern that compares by identity.
"""
#: a literal to compare to
value: Name
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "CSTNode":
return MatchSingleton(value=visit_required(self, "value", self.value, visitor))
def _validate(self) -> None:
if self.value.value not in {"True", "False", "None"}:
raise CSTValidationError(
"A match singleton can only be True, False, or None"
)
def _codegen_impl(self, state: CodegenState) -> None:
with state.record_syntactic_position(self):
self.value._codegen(state)
@property
def lpar(self) -> Sequence[LeftParen]:
return self.value.lpar
@lpar.setter
def lpar(self, value: Sequence[LeftParen]) -> None:
self.value.lpar = value
@add_slots
@dataclass(frozen=True)
class MatchSequenceElement(CSTNode):
"""
An element in a sequence match pattern.
"""
value: MatchPattern
#: An optional trailing comma.
comma: Union[Comma, MaybeSentinel] = MaybeSentinel.DEFAULT
def _visit_and_replace_children(
self, visitor: CSTVisitorT
) -> "MatchSequenceElement":
return MatchSequenceElement(
value=visit_required(self, "value", self.value, visitor),
comma=visit_sentinel(self, "comma", self.comma, visitor),
)
def _codegen_impl(
self,
state: CodegenState,
default_comma: bool = False,
default_comma_whitespace: bool = True,
) -> None:
with state.record_syntactic_position(self):
self.value._codegen(state)
comma = self.comma
if comma is MaybeSentinel.DEFAULT and default_comma:
state.add_token(", " if default_comma_whitespace else ",")
elif isinstance(comma, Comma):
comma._codegen(state)
@add_slots
@dataclass(frozen=True)
class MatchStar(CSTNode):
"""
A starred element in a sequence match pattern. Matches the rest of the sequence.
"""
#: The name of the pattern binding. A ``None`` value represents ``*_``.
name: Optional[Name] = None
#: An optional trailing comma.
comma: Union[Comma, MaybeSentinel] = MaybeSentinel.DEFAULT
#: Optional whitespace between the star and the name.
whitespace_before_name: BaseParenthesizableWhitespace = SimpleWhitespace.field("")
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "MatchStar":
return MatchStar(
whitespace_before_name=visit_required(
self, "whitespace_before_name", self.whitespace_before_name, visitor
),
name=visit_optional(self, "name", self.name, visitor),
comma=visit_sentinel(self, "comma", self.comma, visitor),
)
def _codegen_impl(
self,
state: CodegenState,
default_comma: bool = False,
default_comma_whitespace: bool = True,
) -> None:
with state.record_syntactic_position(self):
state.add_token("*")
self.whitespace_before_name._codegen(state)
name = self.name
if name is None:
state.add_token("_")
else:
name._codegen(state)
comma = self.comma
if comma is MaybeSentinel.DEFAULT and default_comma:
state.add_token(", " if default_comma_whitespace else ",")
elif isinstance(comma, Comma):
comma._codegen(state)
class MatchSequence(MatchPattern, ABC):
"""
A match sequence pattern. It's either a :class:`MatchList` or a :class:`MatchTuple`.
Matches a variable length sequence if one of the patterns is a :class:`MatchStar`,
otherwise matches a fixed length sequence.
"""
#: Patterns to be matched against the subject elements if it is a sequence.
patterns: Sequence[Union[MatchSequenceElement, MatchStar]]
@add_slots
@dataclass(frozen=True)
class MatchList(MatchSequence):
"""
A list match pattern. It's either an "open sequence pattern" (without brackets) or a
regular list literal (with brackets).
"""
#: Patterns to be matched against the subject elements if it is a sequence.
patterns: Sequence[Union[MatchSequenceElement, MatchStar]]
#: An optional left bracket. If missing, this is an open sequence pattern.
lbracket: Optional[LeftSquareBracket] = LeftSquareBracket.field()
#: An optional left bracket. If missing, this is an open sequence pattern.
rbracket: Optional[RightSquareBracket] = RightSquareBracket.field()
#: Parenthesis at the beginning of the node
lpar: Sequence[LeftParen] = ()
#: Parentheses after the pattern, but before a comma (if there is one).
rpar: Sequence[RightParen] = ()
def _validate(self) -> None:
if self.lbracket and not self.rbracket:
raise CSTValidationError("Cannot have left bracket without right bracket")
if self.rbracket and not self.lbracket:
raise CSTValidationError("Cannot have right bracket without left bracket")
if not self.patterns and not self.lbracket:
raise CSTValidationError(
"Must have brackets if matching against empty list"
)
super(MatchList, self)._validate()
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "MatchList":
return MatchList(
lpar=visit_sequence(self, "lpar", self.lpar, visitor),
lbracket=visit_optional(self, "lbracket", self.lbracket, visitor),
patterns=visit_sequence(self, "patterns", self.patterns, visitor),
rbracket=visit_optional(self, "rbracket", self.rbracket, visitor),
rpar=visit_sequence(self, "rpar", self.rpar, visitor),
)
def _codegen_impl(self, state: CodegenState) -> None:
with self._parenthesize(state):
lbracket = self.lbracket
if lbracket is not None:
lbracket._codegen(state)
pats = self.patterns
for idx, pat in enumerate(pats):
pat._codegen(state, default_comma=(idx < len(pats) - 1))
rbracket = self.rbracket
if rbracket is not None:
rbracket._codegen(state)
@add_slots
@dataclass(frozen=True)
class MatchTuple(MatchSequence):
"""
A tuple match pattern.
"""
#: Patterns to be matched against the subject elements if it is a sequence.
patterns: Sequence[Union[MatchSequenceElement, MatchStar]]
#: Parenthesis at the beginning of the node
lpar: Sequence[LeftParen] = field(default_factory=lambda: (LeftParen(),))
#: Parentheses after the pattern, but before a comma (if there is one).
rpar: Sequence[RightParen] = field(default_factory=lambda: (RightParen(),))
def _validate(self) -> None:
if len(self.lpar) < 1:
raise CSTValidationError(
"Tuple patterns must have at least pair of parenthesis"
)
super(MatchTuple, self)._validate()
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "MatchTuple":
return MatchTuple(
lpar=visit_sequence(self, "lpar", self.lpar, visitor),
patterns=visit_sequence(self, "patterns", self.patterns, visitor),
rpar=visit_sequence(self, "rpar", self.rpar, visitor),
)
def _codegen_impl(self, state: CodegenState) -> None:
with self._parenthesize(state):
pats = self.patterns
patlen = len(pats)
for idx, pat in enumerate(pats):
pat._codegen(
state,
default_comma=patlen == 1 or (idx < patlen - 1),
default_comma_whitespace=patlen != 1,
)
@add_slots
@dataclass(frozen=True)
class MatchMappingElement(CSTNode):
"""
A ``key: value`` pair in a match mapping pattern.
"""
key: BaseExpression
#: The pattern to be matched corresponding to ``key``.
pattern: MatchPattern
#: An optional trailing comma.
comma: Union[Comma, MaybeSentinel] = MaybeSentinel.DEFAULT
#: Whitespace between ``key`` and the colon.
whitespace_before_colon: BaseParenthesizableWhitespace = SimpleWhitespace.field("")
#: Whitespace between the colon and ``pattern``.
whitespace_after_colon: BaseParenthesizableWhitespace = SimpleWhitespace.field(" ")
def _visit_and_replace_children(
self, visitor: CSTVisitorT
) -> "MatchMappingElement":
return MatchMappingElement(
key=visit_required(self, "key", self.key, visitor),
whitespace_before_colon=visit_required(
self, "whitespace_before_colon", self.whitespace_before_colon, visitor
),
whitespace_after_colon=visit_required(
self, "whitespace_after_colon", self.whitespace_after_colon, visitor
),
pattern=visit_required(self, "pattern", self.pattern, visitor),
comma=visit_sentinel(self, "comma", self.comma, visitor),
)
def _codegen_impl(self, state: CodegenState, default_comma: bool = False) -> None:
with state.record_syntactic_position(self):
self.key._codegen(state)
self.whitespace_before_colon._codegen(state)
state.add_token(":")
self.whitespace_after_colon._codegen(state)
self.pattern._codegen(state)
comma = self.comma
if comma is MaybeSentinel.DEFAULT and default_comma:
state.add_token(", ")
elif isinstance(comma, Comma):
comma._codegen(state)
@add_slots
@dataclass(frozen=True)
class MatchMapping(MatchPattern):
"""
A match mapping pattern.
"""
#: A sequence of mapping elements.
elements: Sequence[MatchMappingElement] = ()
#: Left curly brace at the beginning of the pattern.
lbrace: LeftCurlyBrace = LeftCurlyBrace.field()
#: Right curly brace at the end of the pattern.
rbrace: RightCurlyBrace = RightCurlyBrace.field()
#: An optional name to capture the remaining elements of the mapping.
rest: Optional[Name] = None
#: Optional whitespace between stars and ``rest``.
whitespace_before_rest: SimpleWhitespace = SimpleWhitespace.field("")
#: An optional trailing comma attached to ``rest``.
trailing_comma: Optional[Comma] = None
#: Parenthesis at the beginning of the node
lpar: Sequence[LeftParen] = ()
#: Parentheses after the pattern
rpar: Sequence[RightParen] = ()
def _validate(self) -> None:
if isinstance(self.trailing_comma, Comma) and self.rest is not None:
raise CSTValidationError("Cannot have a trailing comma without **rest")
super(MatchMapping, self)._validate()
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "MatchMapping":
return MatchMapping(
lpar=visit_sequence(self, "lpar", self.lpar, visitor),
lbrace=visit_required(self, "lbrace", self.lbrace, visitor),
elements=visit_sequence(self, "elements", self.elements, visitor),
whitespace_before_rest=visit_required(
self, "whitespace_before_rest", self.whitespace_before_rest, visitor
),
rest=visit_optional(self, "rest", self.rest, visitor),
trailing_comma=visit_optional(
self, "trailing_comma", self.trailing_comma, visitor
),
rbrace=visit_required(self, "rbrace", self.rbrace, visitor),
rpar=visit_sequence(self, "rpar", self.rpar, visitor),
)
def _codegen_impl(self, state: CodegenState) -> None:
with self._parenthesize(state):
self.lbrace._codegen(state)
elems = self.elements
rest = self.rest
for idx, el in enumerate(elems):
el._codegen(
state, default_comma=rest is not None or idx < len(elems) - 1
)
if rest is not None:
state.add_token("**")
self.whitespace_before_rest._codegen(state)
rest._codegen(state)
comma = self.trailing_comma
if comma is not None:
comma._codegen(state)
self.rbrace._codegen(state)
@add_slots
@dataclass(frozen=True)
class MatchKeywordElement(CSTNode):
"""
A key=value pair in a :class:`MatchClass`.
"""
key: Name
#: The pattern to be matched against the attribute named ``key``.
pattern: MatchPattern
#: An optional trailing comma.
comma: Union[Comma, MaybeSentinel] = MaybeSentinel.DEFAULT
#: Whitespace between ``key`` and the equals sign.
whitespace_before_equal: BaseParenthesizableWhitespace = SimpleWhitespace.field("")
#: Whitespace between the equals sign and ``pattern``.
whitespace_after_equal: BaseParenthesizableWhitespace = SimpleWhitespace.field("")
def _visit_and_replace_children(
self, visitor: CSTVisitorT
) -> "MatchKeywordElement":
return MatchKeywordElement(
key=visit_required(self, "key", self.key, visitor),
whitespace_before_equal=visit_required(
self, "whitespace_before_equal", self.whitespace_before_equal, visitor
),
whitespace_after_equal=visit_required(
self, "whitespace_after_equal", self.whitespace_after_equal, visitor
),
pattern=visit_required(self, "pattern", self.pattern, visitor),
comma=visit_sentinel(self, "comma", self.comma, visitor),
)
def _codegen_impl(self, state: CodegenState, default_comma: bool = False) -> None:
with state.record_syntactic_position(self):
self.key._codegen(state)
self.whitespace_before_equal._codegen(state)
state.add_token("=")
self.whitespace_after_equal._codegen(state)
self.pattern._codegen(state)
comma = self.comma
if comma is MaybeSentinel.DEFAULT and default_comma:
state.add_token(", ")
elif isinstance(comma, Comma):
comma._codegen(state)
@add_slots
@dataclass(frozen=True)
class MatchClass(MatchPattern):
"""
A match class pattern.
"""
#: An expression giving the nominal class to be matched.
cls: BaseExpression
#: A sequence of patterns to be matched against the class defined sequence of
#: pattern matching attributes.
patterns: Sequence[MatchSequenceElement] = ()
#: A sequence of additional attribute names and corresponding patterns to be
#: matched.
kwds: Sequence[MatchKeywordElement] = ()
#: Whitespace between the class name and the left parenthesis.
whitespace_after_cls: BaseParenthesizableWhitespace = SimpleWhitespace.field("")
#: Whitespace between the left parenthesis and the first pattern.
whitespace_before_patterns: BaseParenthesizableWhitespace = SimpleWhitespace.field(
""
)
#: Whitespace between the last pattern and the right parenthesis.
whitespace_after_kwds: BaseParenthesizableWhitespace = SimpleWhitespace.field("")
#: Parenthesis at the beginning of the node
lpar: Sequence[LeftParen] = ()
#: Parentheses after the pattern
rpar: Sequence[RightParen] = ()
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "MatchClass":
return MatchClass(
lpar=visit_sequence(self, "lpar", self.lpar, visitor),
cls=visit_required(self, "cls", self.cls, visitor),
whitespace_after_cls=visit_required(
self, "whitespace_after_cls", self.whitespace_after_cls, visitor
),
whitespace_before_patterns=visit_required(
self,
"whitespace_before_patterns",
self.whitespace_before_patterns,
visitor,
),
patterns=visit_sequence(self, "patterns", self.patterns, visitor),
kwds=visit_sequence(self, "kwds", self.kwds, visitor),
whitespace_after_kwds=visit_required(
self, "whitespace_after_kwds", self.whitespace_after_kwds, visitor
),
)
def _codegen_impl(self, state: CodegenState) -> None:
with self._parenthesize(state):
self.cls._codegen(state)
self.whitespace_after_cls._codegen(state)
state.add_token("(")
self.whitespace_before_patterns._codegen(state)
pats = self.patterns
kwds = self.kwds
for idx, pat in enumerate(pats):
pat._codegen(state, default_comma=idx + 1 < len(pats) + len(kwds))
for idx, kwd in enumerate(kwds):
kwd._codegen(state, default_comma=idx + 1 < len(kwds))
self.whitespace_after_kwds._codegen(state)
state.add_token(")")
@add_slots
@dataclass(frozen=True)
class MatchAs(MatchPattern):
"""
A match "as-pattern", capture pattern, or wildcard pattern.
"""
#: The match pattern that the subject will be matched against. If this is ``None``,
#: the node represents a capture pattern (i.e. a bare name) and will always succeed.
pattern: Optional[MatchPattern] = None
#: The name that will be bound if the pattern is successful. If this is ``None``,
#: ``pattern`` must also be ``None`` and the node represents the wildcard pattern
#: (i.e. ``_``).
name: Optional[Name] = None
#: Whitespace between ``pattern`` and the ``as`` keyword (if ``pattern`` is not
#: ``None``)
whitespace_before_as: Union[
BaseParenthesizableWhitespace, MaybeSentinel
] = MaybeSentinel.DEFAULT
#: Whitespace between the ``as`` keyword and ``name`` (if ``pattern`` is not
#: ``None``)
whitespace_after_as: Union[
BaseParenthesizableWhitespace, MaybeSentinel
] = MaybeSentinel.DEFAULT
#: Parenthesis at the beginning of the node
lpar: Sequence[LeftParen] = ()
#: Parentheses after the pattern
rpar: Sequence[RightParen] = ()
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "MatchAs":
return MatchAs(
lpar=visit_sequence(self, "lpar", self.lpar, visitor),
pattern=visit_optional(self, "pattern", self.pattern, visitor),
whitespace_before_as=visit_sentinel(
self, "whitespace_before_as", self.whitespace_before_as, visitor
),
whitespace_after_as=visit_sentinel(
self, "whitespace_after_as", self.whitespace_after_as, visitor
),
name=visit_optional(self, "name", self.name, visitor),
rpar=visit_sequence(self, "rpar", self.rpar, visitor),
)
def _validate(self) -> None:
if self.name is None and self.pattern is not None:
raise CSTValidationError("Pattern must be None if name is None")
super(MatchAs, self)._validate()
def _codegen_impl(self, state: CodegenState) -> None:
with self._parenthesize(state):
pat = self.pattern
name = self.name
if pat is not None:
pat._codegen(state)
ws_before = self.whitespace_before_as
if ws_before is MaybeSentinel.DEFAULT:
state.add_token(" ")
elif isinstance(ws_before, BaseParenthesizableWhitespace):
ws_before._codegen(state)
state.add_token("as")
ws_after = self.whitespace_after_as
if ws_after is MaybeSentinel.DEFAULT:
state.add_token(" ")
elif isinstance(ws_after, BaseParenthesizableWhitespace):
ws_after._codegen(state)
if name is None:
state.add_token("_")
else:
name._codegen(state)
@add_slots
@dataclass(frozen=True)
class MatchOrElement(CSTNode):
"""
An element in a :class:`MatchOr` node.
"""
pattern: MatchPattern
#: An optional ``|`` separator.
separator: Union[BitOr, MaybeSentinel] = MaybeSentinel.DEFAULT
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "MatchOrElement":
return MatchOrElement(
pattern=visit_required(self, "pattern", self.pattern, visitor),
separator=visit_sentinel(self, "separator", self.separator, visitor),
)
def _codegen_impl(
self, state: CodegenState, default_separator: bool = False
) -> None:
with state.record_syntactic_position(self):
self.pattern._codegen(state)
sep = self.separator
if sep is MaybeSentinel.DEFAULT and default_separator:
state.add_token(" | ")
elif isinstance(sep, BitOr):
sep._codegen(state)
@add_slots
@dataclass(frozen=True)
class MatchOr(MatchPattern):
"""
A match "or-pattern". It matches each of its subpatterns in turn to the subject,
until one succeeds. The or-pattern is then deemed to succeed. If none of the
subpatterns succeed the or-pattern fails.
"""
#: The subpatterns to be tried in turn.
patterns: Sequence[MatchOrElement]
#: Parenthesis at the beginning of the node
lpar: Sequence[LeftParen] = ()
#: Parentheses after the pattern
rpar: Sequence[RightParen] = ()
def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "MatchOr":
return MatchOr(
lpar=visit_sequence(self, "lpar", self.lpar, visitor),
patterns=visit_sequence(self, "patterns", self.patterns, visitor),
rpar=visit_sequence(self, "rpar", self.rpar, visitor),
)
def _codegen_impl(self, state: CodegenState) -> None:
with self._parenthesize(state):
pats = self.patterns
for idx, pat in enumerate(pats):
pat._codegen(state, default_separator=idx + 1 < len(pats))

View file

@ -0,0 +1,430 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
from typing import Any
import libcst as cst
from libcst import parse_statement
from libcst._nodes.tests.base import CSTNodeTest
from libcst._parser.entrypoints import is_native
from libcst.testing.utils import data_provider
parser = parse_statement if is_native() else None
class MatchTest(CSTNodeTest):
# pyre-fixme[56]: Invalid decoration - Pyre was not able to infer the type
@data_provider(
(
# Values and singletons
{
"node": cst.Match(
subject=cst.Name("x"),
cases=[
cst.MatchCase(
pattern=cst.MatchSingleton(cst.Name("None")),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase(
pattern=cst.MatchValue(cst.SimpleString('"foo"')),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
],
),
"code": "match x:\n"
+ " case None: pass\n"
+ ' case "foo": pass\n',
"parser": parser,
},
# List patterns
{
"node": cst.Match(
subject=cst.Name("x"),
cases=[
cst.MatchCase( # empty list
pattern=cst.MatchList(
[],
lbracket=cst.LeftSquareBracket(),
rbracket=cst.RightSquareBracket(),
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase( # single element list
pattern=cst.MatchList(
[
cst.MatchSequenceElement(
cst.MatchSingleton(cst.Name("None"))
)
],
lbracket=cst.LeftSquareBracket(),
rbracket=cst.RightSquareBracket(),
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase( # single element list with trailing comma
pattern=cst.MatchList(
[
cst.MatchSequenceElement(
cst.MatchSingleton(cst.Name("None")),
cst.Comma(),
)
],
lbracket=cst.LeftSquareBracket(),
rbracket=cst.RightSquareBracket(),
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
],
),
"code": (
"match x:\n"
+ " case []: pass\n"
+ " case [None]: pass\n"
+ " case [None,]: pass\n"
),
"parser": parser,
},
# Tuple patterns
{
"node": cst.Match(
subject=cst.Name("x"),
cases=[
cst.MatchCase( # empty tuple
pattern=cst.MatchTuple(
[],
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase( # two element tuple
pattern=cst.MatchTuple(
[
cst.MatchSequenceElement(
cst.MatchSingleton(cst.Name("None")),
cst.Comma(),
),
cst.MatchSequenceElement(
cst.MatchSingleton(cst.Name("None")),
),
],
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase( # single element tuple with trailing comma
pattern=cst.MatchTuple(
[
cst.MatchSequenceElement(
cst.MatchSingleton(cst.Name("None")),
cst.Comma(),
)
],
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase( # two element tuple
pattern=cst.MatchTuple(
[
cst.MatchSequenceElement(
cst.MatchSingleton(cst.Name("None")),
cst.Comma(),
),
cst.MatchStar(
comma=cst.Comma(),
),
cst.MatchSequenceElement(
cst.MatchSingleton(cst.Name("None")),
),
],
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
],
),
"code": (
"match x:\n"
+ " case (): pass\n"
+ " case (None,None): pass\n"
+ " case (None,): pass\n"
+ " case (None,*_,None): pass\n"
),
"parser": parser,
},
# Mapping patterns
{
"node": cst.Match(
subject=cst.Name("x"),
cases=[
cst.MatchCase( # empty mapping
pattern=cst.MatchMapping(
[],
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase( # two element mapping
pattern=cst.MatchMapping(
[
cst.MatchMappingElement(
key=cst.SimpleString('"a"'),
pattern=cst.MatchSingleton(cst.Name("None")),
comma=cst.Comma(),
),
cst.MatchMappingElement(
key=cst.SimpleString('"b"'),
pattern=cst.MatchSingleton(cst.Name("None")),
),
],
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase( # single element mapping with trailing comma
pattern=cst.MatchMapping(
[
cst.MatchMappingElement(
key=cst.SimpleString('"a"'),
pattern=cst.MatchSingleton(cst.Name("None")),
comma=cst.Comma(),
)
],
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase( # rest
pattern=cst.MatchMapping(
rest=cst.Name("rest"),
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
],
),
"code": (
"match x:\n"
+ " case {}: pass\n"
+ ' case {"a": None,"b": None}: pass\n'
+ ' case {"a": None,}: pass\n'
+ " case {**rest}: pass\n"
),
"parser": parser,
},
# Class patterns
{
"node": cst.Match(
subject=cst.Name("x"),
cases=[
cst.MatchCase( # empty class
pattern=cst.MatchClass(
cls=cst.Attribute(cst.Name("a"), cst.Name("b")),
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase( # single pattern class
pattern=cst.MatchClass(
cls=cst.Attribute(cst.Name("a"), cst.Name("b")),
patterns=[
cst.MatchSequenceElement(
cst.MatchSingleton(cst.Name("None"))
)
],
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase( # single pattern class with trailing comma
pattern=cst.MatchClass(
cls=cst.Attribute(cst.Name("a"), cst.Name("b")),
patterns=[
cst.MatchSequenceElement(
cst.MatchSingleton(cst.Name("None")),
comma=cst.Comma(),
)
],
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase( # single keyword pattern class
pattern=cst.MatchClass(
cls=cst.Attribute(cst.Name("a"), cst.Name("b")),
kwds=[
cst.MatchKeywordElement(
key=cst.Name("foo"),
pattern=cst.MatchSingleton(cst.Name("None")),
)
],
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase( # single keyword pattern class with trailing comma
pattern=cst.MatchClass(
cls=cst.Attribute(cst.Name("a"), cst.Name("b")),
kwds=[
cst.MatchKeywordElement(
key=cst.Name("foo"),
pattern=cst.MatchSingleton(cst.Name("None")),
comma=cst.Comma(),
)
],
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase( # now all at once
pattern=cst.MatchClass(
cls=cst.Attribute(cst.Name("a"), cst.Name("b")),
patterns=[
cst.MatchSequenceElement(
cst.MatchSingleton(cst.Name("None")),
cst.Comma(),
),
cst.MatchSequenceElement(
cst.MatchSingleton(cst.Name("None")),
cst.Comma(),
),
],
kwds=[
cst.MatchKeywordElement(
key=cst.Name("foo"),
pattern=cst.MatchSingleton(cst.Name("None")),
comma=cst.Comma(),
),
cst.MatchKeywordElement(
key=cst.Name("bar"),
pattern=cst.MatchSingleton(cst.Name("None")),
comma=cst.Comma(),
),
],
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
],
),
"code": (
"match x:\n"
+ " case a.b(): pass\n"
+ " case a.b(None): pass\n"
+ " case a.b(None,): pass\n"
+ " case a.b(foo=None): pass\n"
+ " case a.b(foo=None,): pass\n"
+ " case a.b(None,None,foo=None,bar=None,): pass\n"
),
"parser": parser,
},
# as pattern
{
"node": cst.Match(
subject=cst.Name("x"),
cases=[
cst.MatchCase(
pattern=cst.MatchAs(),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase(
pattern=cst.MatchAs(name=cst.Name("foo")),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase(
pattern=cst.MatchAs(
pattern=cst.MatchSingleton(cst.Name("None")),
name=cst.Name("bar"),
whitespace_before_as=cst.SimpleWhitespace(" "),
whitespace_after_as=cst.SimpleWhitespace(" "),
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
],
),
"code": "match x:\n"
+ " case _: pass\n"
+ " case foo: pass\n"
+ " case None as bar: pass\n",
"parser": parser,
},
# or pattern
{
"node": cst.Match(
subject=cst.Name("x"),
cases=[
cst.MatchCase(
pattern=cst.MatchOr(
[
cst.MatchOrElement(
cst.MatchSingleton(cst.Name("None")),
cst.BitOr(),
),
cst.MatchOrElement(
cst.MatchSingleton(cst.Name("False")),
cst.BitOr(),
),
cst.MatchOrElement(
cst.MatchSingleton(cst.Name("True"))
),
]
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
)
],
),
"code": "match x:\n case None | False | True: pass\n",
"parser": parser,
},
{ # exercise sentinels
"node": cst.Match(
subject=cst.Name("x"),
cases=[
cst.MatchCase(
pattern=cst.MatchList(
[cst.MatchStar(), cst.MatchStar()],
lbracket=None,
rbracket=None,
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase(
pattern=cst.MatchTuple(
[
cst.MatchSequenceElement(
cst.MatchSingleton(cst.Name("None"))
)
]
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase(
pattern=cst.MatchAs(
pattern=cst.MatchTuple(
[
cst.MatchSequenceElement(
cst.MatchSingleton(cst.Name("None"))
)
]
),
name=cst.Name("bar"),
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
cst.MatchCase(
pattern=cst.MatchOr(
[
cst.MatchOrElement(
cst.MatchSingleton(cst.Name("None")),
),
cst.MatchOrElement(
cst.MatchSingleton(cst.Name("False")),
),
cst.MatchOrElement(
cst.MatchSingleton(cst.Name("True"))
),
]
),
body=cst.SimpleStatementSuite((cst.Pass(),)),
),
],
),
"code": "match x:\n"
+ " case *_, *_: pass\n"
+ " case (None,): pass\n"
+ " case (None,) as bar: pass\n"
+ " case None | False | True: pass\n",
"parser": None,
},
)
)
def test_valid(self, **kwargs: Any) -> None:
self.validate_node(**kwargs)

View file

@ -159,6 +159,23 @@ if TYPE_CHECKING:
ImportAlias,
ImportFrom,
IndentedBlock,
Match,
MatchAs,
MatchCase,
MatchClass,
MatchKeywordElement,
MatchList,
MatchMapping,
MatchMappingElement,
MatchOr,
MatchOrElement,
MatchPattern,
MatchSequence,
MatchSequenceElement,
MatchSingleton,
MatchStar,
MatchTuple,
MatchValue,
NameItem,
Nonlocal,
Pass,
@ -3134,6 +3151,636 @@ class CSTTypedBaseFunctions:
def leave_ListComp_rpar(self, node: "ListComp") -> None:
pass
@mark_no_op
def visit_Match(self, node: "Match") -> Optional[bool]:
pass
@mark_no_op
def visit_Match_subject(self, node: "Match") -> None:
pass
@mark_no_op
def leave_Match_subject(self, node: "Match") -> None:
pass
@mark_no_op
def visit_Match_cases(self, node: "Match") -> None:
pass
@mark_no_op
def leave_Match_cases(self, node: "Match") -> None:
pass
@mark_no_op
def visit_Match_leading_lines(self, node: "Match") -> None:
pass
@mark_no_op
def leave_Match_leading_lines(self, node: "Match") -> None:
pass
@mark_no_op
def visit_Match_whitespace_after_match(self, node: "Match") -> None:
pass
@mark_no_op
def leave_Match_whitespace_after_match(self, node: "Match") -> None:
pass
@mark_no_op
def visit_Match_whitespace_before_colon(self, node: "Match") -> None:
pass
@mark_no_op
def leave_Match_whitespace_before_colon(self, node: "Match") -> None:
pass
@mark_no_op
def visit_Match_whitespace_after_colon(self, node: "Match") -> None:
pass
@mark_no_op
def leave_Match_whitespace_after_colon(self, node: "Match") -> None:
pass
@mark_no_op
def visit_Match_indent(self, node: "Match") -> None:
pass
@mark_no_op
def leave_Match_indent(self, node: "Match") -> None:
pass
@mark_no_op
def visit_Match_footer(self, node: "Match") -> None:
pass
@mark_no_op
def leave_Match_footer(self, node: "Match") -> None:
pass
@mark_no_op
def visit_MatchAs(self, node: "MatchAs") -> Optional[bool]:
pass
@mark_no_op
def visit_MatchAs_pattern(self, node: "MatchAs") -> None:
pass
@mark_no_op
def leave_MatchAs_pattern(self, node: "MatchAs") -> None:
pass
@mark_no_op
def visit_MatchAs_name(self, node: "MatchAs") -> None:
pass
@mark_no_op
def leave_MatchAs_name(self, node: "MatchAs") -> None:
pass
@mark_no_op
def visit_MatchAs_whitespace_before_as(self, node: "MatchAs") -> None:
pass
@mark_no_op
def leave_MatchAs_whitespace_before_as(self, node: "MatchAs") -> None:
pass
@mark_no_op
def visit_MatchAs_whitespace_after_as(self, node: "MatchAs") -> None:
pass
@mark_no_op
def leave_MatchAs_whitespace_after_as(self, node: "MatchAs") -> None:
pass
@mark_no_op
def visit_MatchAs_lpar(self, node: "MatchAs") -> None:
pass
@mark_no_op
def leave_MatchAs_lpar(self, node: "MatchAs") -> None:
pass
@mark_no_op
def visit_MatchAs_rpar(self, node: "MatchAs") -> None:
pass
@mark_no_op
def leave_MatchAs_rpar(self, node: "MatchAs") -> None:
pass
@mark_no_op
def visit_MatchCase(self, node: "MatchCase") -> Optional[bool]:
pass
@mark_no_op
def visit_MatchCase_pattern(self, node: "MatchCase") -> None:
pass
@mark_no_op
def leave_MatchCase_pattern(self, node: "MatchCase") -> None:
pass
@mark_no_op
def visit_MatchCase_body(self, node: "MatchCase") -> None:
pass
@mark_no_op
def leave_MatchCase_body(self, node: "MatchCase") -> None:
pass
@mark_no_op
def visit_MatchCase_guard(self, node: "MatchCase") -> None:
pass
@mark_no_op
def leave_MatchCase_guard(self, node: "MatchCase") -> None:
pass
@mark_no_op
def visit_MatchCase_leading_lines(self, node: "MatchCase") -> None:
pass
@mark_no_op
def leave_MatchCase_leading_lines(self, node: "MatchCase") -> None:
pass
@mark_no_op
def visit_MatchCase_whitespace_after_case(self, node: "MatchCase") -> None:
pass
@mark_no_op
def leave_MatchCase_whitespace_after_case(self, node: "MatchCase") -> None:
pass
@mark_no_op
def visit_MatchCase_whitespace_before_if(self, node: "MatchCase") -> None:
pass
@mark_no_op
def leave_MatchCase_whitespace_before_if(self, node: "MatchCase") -> None:
pass
@mark_no_op
def visit_MatchCase_whitespace_after_if(self, node: "MatchCase") -> None:
pass
@mark_no_op
def leave_MatchCase_whitespace_after_if(self, node: "MatchCase") -> None:
pass
@mark_no_op
def visit_MatchCase_whitespace_before_colon(self, node: "MatchCase") -> None:
pass
@mark_no_op
def leave_MatchCase_whitespace_before_colon(self, node: "MatchCase") -> None:
pass
@mark_no_op
def visit_MatchClass(self, node: "MatchClass") -> Optional[bool]:
pass
@mark_no_op
def visit_MatchClass_cls(self, node: "MatchClass") -> None:
pass
@mark_no_op
def leave_MatchClass_cls(self, node: "MatchClass") -> None:
pass
@mark_no_op
def visit_MatchClass_patterns(self, node: "MatchClass") -> None:
pass
@mark_no_op
def leave_MatchClass_patterns(self, node: "MatchClass") -> None:
pass
@mark_no_op
def visit_MatchClass_kwds(self, node: "MatchClass") -> None:
pass
@mark_no_op
def leave_MatchClass_kwds(self, node: "MatchClass") -> None:
pass
@mark_no_op
def visit_MatchClass_whitespace_after_cls(self, node: "MatchClass") -> None:
pass
@mark_no_op
def leave_MatchClass_whitespace_after_cls(self, node: "MatchClass") -> None:
pass
@mark_no_op
def visit_MatchClass_whitespace_before_patterns(self, node: "MatchClass") -> None:
pass
@mark_no_op
def leave_MatchClass_whitespace_before_patterns(self, node: "MatchClass") -> None:
pass
@mark_no_op
def visit_MatchClass_whitespace_after_kwds(self, node: "MatchClass") -> None:
pass
@mark_no_op
def leave_MatchClass_whitespace_after_kwds(self, node: "MatchClass") -> None:
pass
@mark_no_op
def visit_MatchClass_lpar(self, node: "MatchClass") -> None:
pass
@mark_no_op
def leave_MatchClass_lpar(self, node: "MatchClass") -> None:
pass
@mark_no_op
def visit_MatchClass_rpar(self, node: "MatchClass") -> None:
pass
@mark_no_op
def leave_MatchClass_rpar(self, node: "MatchClass") -> None:
pass
@mark_no_op
def visit_MatchKeywordElement(self, node: "MatchKeywordElement") -> Optional[bool]:
pass
@mark_no_op
def visit_MatchKeywordElement_key(self, node: "MatchKeywordElement") -> None:
pass
@mark_no_op
def leave_MatchKeywordElement_key(self, node: "MatchKeywordElement") -> None:
pass
@mark_no_op
def visit_MatchKeywordElement_pattern(self, node: "MatchKeywordElement") -> None:
pass
@mark_no_op
def leave_MatchKeywordElement_pattern(self, node: "MatchKeywordElement") -> None:
pass
@mark_no_op
def visit_MatchKeywordElement_comma(self, node: "MatchKeywordElement") -> None:
pass
@mark_no_op
def leave_MatchKeywordElement_comma(self, node: "MatchKeywordElement") -> None:
pass
@mark_no_op
def visit_MatchKeywordElement_whitespace_before_equal(
self, node: "MatchKeywordElement"
) -> None:
pass
@mark_no_op
def leave_MatchKeywordElement_whitespace_before_equal(
self, node: "MatchKeywordElement"
) -> None:
pass
@mark_no_op
def visit_MatchKeywordElement_whitespace_after_equal(
self, node: "MatchKeywordElement"
) -> None:
pass
@mark_no_op
def leave_MatchKeywordElement_whitespace_after_equal(
self, node: "MatchKeywordElement"
) -> None:
pass
@mark_no_op
def visit_MatchList(self, node: "MatchList") -> Optional[bool]:
pass
@mark_no_op
def visit_MatchList_patterns(self, node: "MatchList") -> None:
pass
@mark_no_op
def leave_MatchList_patterns(self, node: "MatchList") -> None:
pass
@mark_no_op
def visit_MatchList_lbracket(self, node: "MatchList") -> None:
pass
@mark_no_op
def leave_MatchList_lbracket(self, node: "MatchList") -> None:
pass
@mark_no_op
def visit_MatchList_rbracket(self, node: "MatchList") -> None:
pass
@mark_no_op
def leave_MatchList_rbracket(self, node: "MatchList") -> None:
pass
@mark_no_op
def visit_MatchList_lpar(self, node: "MatchList") -> None:
pass
@mark_no_op
def leave_MatchList_lpar(self, node: "MatchList") -> None:
pass
@mark_no_op
def visit_MatchList_rpar(self, node: "MatchList") -> None:
pass
@mark_no_op
def leave_MatchList_rpar(self, node: "MatchList") -> None:
pass
@mark_no_op
def visit_MatchMapping(self, node: "MatchMapping") -> Optional[bool]:
pass
@mark_no_op
def visit_MatchMapping_elements(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def leave_MatchMapping_elements(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def visit_MatchMapping_lbrace(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def leave_MatchMapping_lbrace(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def visit_MatchMapping_rbrace(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def leave_MatchMapping_rbrace(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def visit_MatchMapping_rest(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def leave_MatchMapping_rest(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def visit_MatchMapping_whitespace_before_rest(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def leave_MatchMapping_whitespace_before_rest(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def visit_MatchMapping_trailing_comma(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def leave_MatchMapping_trailing_comma(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def visit_MatchMapping_lpar(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def leave_MatchMapping_lpar(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def visit_MatchMapping_rpar(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def leave_MatchMapping_rpar(self, node: "MatchMapping") -> None:
pass
@mark_no_op
def visit_MatchMappingElement(self, node: "MatchMappingElement") -> Optional[bool]:
pass
@mark_no_op
def visit_MatchMappingElement_key(self, node: "MatchMappingElement") -> None:
pass
@mark_no_op
def leave_MatchMappingElement_key(self, node: "MatchMappingElement") -> None:
pass
@mark_no_op
def visit_MatchMappingElement_pattern(self, node: "MatchMappingElement") -> None:
pass
@mark_no_op
def leave_MatchMappingElement_pattern(self, node: "MatchMappingElement") -> None:
pass
@mark_no_op
def visit_MatchMappingElement_comma(self, node: "MatchMappingElement") -> None:
pass
@mark_no_op
def leave_MatchMappingElement_comma(self, node: "MatchMappingElement") -> None:
pass
@mark_no_op
def visit_MatchMappingElement_whitespace_before_colon(
self, node: "MatchMappingElement"
) -> None:
pass
@mark_no_op
def leave_MatchMappingElement_whitespace_before_colon(
self, node: "MatchMappingElement"
) -> None:
pass
@mark_no_op
def visit_MatchMappingElement_whitespace_after_colon(
self, node: "MatchMappingElement"
) -> None:
pass
@mark_no_op
def leave_MatchMappingElement_whitespace_after_colon(
self, node: "MatchMappingElement"
) -> None:
pass
@mark_no_op
def visit_MatchOr(self, node: "MatchOr") -> Optional[bool]:
pass
@mark_no_op
def visit_MatchOr_patterns(self, node: "MatchOr") -> None:
pass
@mark_no_op
def leave_MatchOr_patterns(self, node: "MatchOr") -> None:
pass
@mark_no_op
def visit_MatchOr_lpar(self, node: "MatchOr") -> None:
pass
@mark_no_op
def leave_MatchOr_lpar(self, node: "MatchOr") -> None:
pass
@mark_no_op
def visit_MatchOr_rpar(self, node: "MatchOr") -> None:
pass
@mark_no_op
def leave_MatchOr_rpar(self, node: "MatchOr") -> None:
pass
@mark_no_op
def visit_MatchOrElement(self, node: "MatchOrElement") -> Optional[bool]:
pass
@mark_no_op
def visit_MatchOrElement_pattern(self, node: "MatchOrElement") -> None:
pass
@mark_no_op
def leave_MatchOrElement_pattern(self, node: "MatchOrElement") -> None:
pass
@mark_no_op
def visit_MatchOrElement_separator(self, node: "MatchOrElement") -> None:
pass
@mark_no_op
def leave_MatchOrElement_separator(self, node: "MatchOrElement") -> None:
pass
@mark_no_op
def visit_MatchPattern(self, node: "MatchPattern") -> Optional[bool]:
pass
@mark_no_op
def visit_MatchSequence(self, node: "MatchSequence") -> Optional[bool]:
pass
@mark_no_op
def visit_MatchSequenceElement(
self, node: "MatchSequenceElement"
) -> Optional[bool]:
pass
@mark_no_op
def visit_MatchSequenceElement_value(self, node: "MatchSequenceElement") -> None:
pass
@mark_no_op
def leave_MatchSequenceElement_value(self, node: "MatchSequenceElement") -> None:
pass
@mark_no_op
def visit_MatchSequenceElement_comma(self, node: "MatchSequenceElement") -> None:
pass
@mark_no_op
def leave_MatchSequenceElement_comma(self, node: "MatchSequenceElement") -> None:
pass
@mark_no_op
def visit_MatchSingleton(self, node: "MatchSingleton") -> Optional[bool]:
pass
@mark_no_op
def visit_MatchSingleton_value(self, node: "MatchSingleton") -> None:
pass
@mark_no_op
def leave_MatchSingleton_value(self, node: "MatchSingleton") -> None:
pass
@mark_no_op
def visit_MatchStar(self, node: "MatchStar") -> Optional[bool]:
pass
@mark_no_op
def visit_MatchStar_name(self, node: "MatchStar") -> None:
pass
@mark_no_op
def leave_MatchStar_name(self, node: "MatchStar") -> None:
pass
@mark_no_op
def visit_MatchStar_comma(self, node: "MatchStar") -> None:
pass
@mark_no_op
def leave_MatchStar_comma(self, node: "MatchStar") -> None:
pass
@mark_no_op
def visit_MatchStar_whitespace_before_name(self, node: "MatchStar") -> None:
pass
@mark_no_op
def leave_MatchStar_whitespace_before_name(self, node: "MatchStar") -> None:
pass
@mark_no_op
def visit_MatchTuple(self, node: "MatchTuple") -> Optional[bool]:
pass
@mark_no_op
def visit_MatchTuple_patterns(self, node: "MatchTuple") -> None:
pass
@mark_no_op
def leave_MatchTuple_patterns(self, node: "MatchTuple") -> None:
pass
@mark_no_op
def visit_MatchTuple_lpar(self, node: "MatchTuple") -> None:
pass
@mark_no_op
def leave_MatchTuple_lpar(self, node: "MatchTuple") -> None:
pass
@mark_no_op
def visit_MatchTuple_rpar(self, node: "MatchTuple") -> None:
pass
@mark_no_op
def leave_MatchTuple_rpar(self, node: "MatchTuple") -> None:
pass
@mark_no_op
def visit_MatchValue(self, node: "MatchValue") -> Optional[bool]:
pass
@mark_no_op
def visit_MatchValue_value(self, node: "MatchValue") -> None:
pass
@mark_no_op
def leave_MatchValue_value(self, node: "MatchValue") -> None:
pass
@mark_no_op
def visit_MatrixMultiply(self, node: "MatrixMultiply") -> Optional[bool]:
pass
@ -5172,6 +5819,74 @@ class CSTTypedVisitorFunctions(CSTTypedBaseFunctions):
def leave_ListComp(self, original_node: "ListComp") -> None:
pass
@mark_no_op
def leave_Match(self, original_node: "Match") -> None:
pass
@mark_no_op
def leave_MatchAs(self, original_node: "MatchAs") -> None:
pass
@mark_no_op
def leave_MatchCase(self, original_node: "MatchCase") -> None:
pass
@mark_no_op
def leave_MatchClass(self, original_node: "MatchClass") -> None:
pass
@mark_no_op
def leave_MatchKeywordElement(self, original_node: "MatchKeywordElement") -> None:
pass
@mark_no_op
def leave_MatchList(self, original_node: "MatchList") -> None:
pass
@mark_no_op
def leave_MatchMapping(self, original_node: "MatchMapping") -> None:
pass
@mark_no_op
def leave_MatchMappingElement(self, original_node: "MatchMappingElement") -> None:
pass
@mark_no_op
def leave_MatchOr(self, original_node: "MatchOr") -> None:
pass
@mark_no_op
def leave_MatchOrElement(self, original_node: "MatchOrElement") -> None:
pass
@mark_no_op
def leave_MatchPattern(self, original_node: "MatchPattern") -> None:
pass
@mark_no_op
def leave_MatchSequence(self, original_node: "MatchSequence") -> None:
pass
@mark_no_op
def leave_MatchSequenceElement(self, original_node: "MatchSequenceElement") -> None:
pass
@mark_no_op
def leave_MatchSingleton(self, original_node: "MatchSingleton") -> None:
pass
@mark_no_op
def leave_MatchStar(self, original_node: "MatchStar") -> None:
pass
@mark_no_op
def leave_MatchTuple(self, original_node: "MatchTuple") -> None:
pass
@mark_no_op
def leave_MatchValue(self, original_node: "MatchValue") -> None:
pass
@mark_no_op
def leave_MatrixMultiply(self, original_node: "MatrixMultiply") -> None:
pass
@ -5521,7 +6236,7 @@ class CSTTypedTransformerFunctions(CSTTypedBaseFunctions):
@mark_no_op
def leave_BitOr(
self, original_node: "BitOr", updated_node: "BitOr"
) -> "BaseBinaryOp":
) -> Union["BaseBinaryOp", MaybeSentinel]:
return updated_node
@mark_no_op
@ -5956,6 +6671,116 @@ class CSTTypedTransformerFunctions(CSTTypedBaseFunctions):
) -> "BaseExpression":
return updated_node
@mark_no_op
def leave_Match(
self, original_node: "Match", updated_node: "Match"
) -> Union["BaseStatement", FlattenSentinel["BaseStatement"], RemovalSentinel]:
return updated_node
@mark_no_op
def leave_MatchAs(
self, original_node: "MatchAs", updated_node: "MatchAs"
) -> "MatchPattern":
return updated_node
@mark_no_op
def leave_MatchCase(
self, original_node: "MatchCase", updated_node: "MatchCase"
) -> "MatchCase":
return updated_node
@mark_no_op
def leave_MatchClass(
self, original_node: "MatchClass", updated_node: "MatchClass"
) -> "MatchPattern":
return updated_node
@mark_no_op
def leave_MatchKeywordElement(
self, original_node: "MatchKeywordElement", updated_node: "MatchKeywordElement"
) -> Union[
"MatchKeywordElement", FlattenSentinel["MatchKeywordElement"], RemovalSentinel
]:
return updated_node
@mark_no_op
def leave_MatchList(
self, original_node: "MatchList", updated_node: "MatchList"
) -> "MatchPattern":
return updated_node
@mark_no_op
def leave_MatchMapping(
self, original_node: "MatchMapping", updated_node: "MatchMapping"
) -> "MatchPattern":
return updated_node
@mark_no_op
def leave_MatchMappingElement(
self, original_node: "MatchMappingElement", updated_node: "MatchMappingElement"
) -> Union[
"MatchMappingElement", FlattenSentinel["MatchMappingElement"], RemovalSentinel
]:
return updated_node
@mark_no_op
def leave_MatchOr(
self, original_node: "MatchOr", updated_node: "MatchOr"
) -> "MatchPattern":
return updated_node
@mark_no_op
def leave_MatchOrElement(
self, original_node: "MatchOrElement", updated_node: "MatchOrElement"
) -> Union["MatchOrElement", FlattenSentinel["MatchOrElement"], RemovalSentinel]:
return updated_node
@mark_no_op
def leave_MatchPattern(
self, original_node: "MatchPattern", updated_node: "MatchPattern"
) -> "MatchPattern":
return updated_node
@mark_no_op
def leave_MatchSequence(
self, original_node: "MatchSequence", updated_node: "MatchSequence"
) -> "MatchPattern":
return updated_node
@mark_no_op
def leave_MatchSequenceElement(
self,
original_node: "MatchSequenceElement",
updated_node: "MatchSequenceElement",
) -> Union[
"MatchSequenceElement", FlattenSentinel["MatchSequenceElement"], RemovalSentinel
]:
return updated_node
@mark_no_op
def leave_MatchSingleton(
self, original_node: "MatchSingleton", updated_node: "MatchSingleton"
) -> "MatchPattern":
return updated_node
@mark_no_op
def leave_MatchStar(
self, original_node: "MatchStar", updated_node: "MatchStar"
) -> "MatchStar":
return updated_node
@mark_no_op
def leave_MatchTuple(
self, original_node: "MatchTuple", updated_node: "MatchTuple"
) -> "MatchPattern":
return updated_node
@mark_no_op
def leave_MatchValue(
self, original_node: "MatchValue", updated_node: "MatchValue"
) -> "MatchPattern":
return updated_node
@mark_no_op
def leave_MatrixMultiply(
self, original_node: "MatrixMultiply", updated_node: "MatrixMultiply"

File diff suppressed because it is too large Load diff

View file

@ -155,6 +155,23 @@ from libcst._nodes.statement import (
ImportAlias,
ImportFrom,
IndentedBlock,
Match,
MatchAs,
MatchCase,
MatchClass,
MatchKeywordElement,
MatchList,
MatchMapping,
MatchMappingElement,
MatchOr,
MatchOrElement,
MatchPattern,
MatchSequence,
MatchSequenceElement,
MatchSingleton,
MatchStar,
MatchTuple,
MatchValue,
NameItem,
Nonlocal,
Pass,
@ -200,7 +217,7 @@ TYPED_FUNCTION_RETURN_MAPPING: TypingDict[Type[CSTNode], object] = {
BitAnd: BaseBinaryOp,
BitAndAssign: BaseAugOp,
BitInvert: BaseUnaryOp,
BitOr: BaseBinaryOp,
BitOr: Union[BaseBinaryOp, MaybeSentinel],
BitOrAssign: BaseAugOp,
BitXor: BaseBinaryOp,
BitXorAssign: BaseAugOp,
@ -270,6 +287,23 @@ TYPED_FUNCTION_RETURN_MAPPING: TypingDict[Type[CSTNode], object] = {
LessThanEqual: BaseCompOp,
List: BaseExpression,
ListComp: BaseExpression,
Match: Union[BaseStatement, RemovalSentinel],
MatchAs: MatchPattern,
MatchCase: MatchCase,
MatchClass: MatchPattern,
MatchKeywordElement: Union[MatchKeywordElement, RemovalSentinel],
MatchList: MatchPattern,
MatchMapping: MatchPattern,
MatchMappingElement: Union[MatchMappingElement, RemovalSentinel],
MatchOr: MatchPattern,
MatchOrElement: Union[MatchOrElement, RemovalSentinel],
MatchPattern: MatchPattern,
MatchSequence: MatchPattern,
MatchSequenceElement: Union[MatchSequenceElement, RemovalSentinel],
MatchSingleton: MatchPattern,
MatchStar: MatchStar,
MatchTuple: MatchPattern,
MatchValue: MatchPattern,
MatrixMultiply: BaseBinaryOp,
MatrixMultiplyAssign: BaseAugOp,
Minus: BaseUnaryOp,

View file

@ -13,8 +13,11 @@ pub use statement::{
AnnAssign, Annotation, AsName, Assert, Assign, AssignTarget, AssignTargetExpression, AugAssign,
Break, ClassDef, CompoundStatement, Continue, Decorator, Del, DelTargetExpression, Else,
ExceptHandler, ExceptStarHandler, Expr, Finally, For, FunctionDef, Global, If, Import,
ImportAlias, ImportFrom, ImportNames, IndentedBlock, NameItem, Nonlocal, OrElse, Pass, Raise,
Return, SimpleStatementLine, SimpleStatementSuite, SmallStatement, Statement, Suite, Try,
ImportAlias, ImportFrom, ImportNames, IndentedBlock, Match, MatchAs, MatchCase, MatchClass,
MatchKeywordElement, MatchList, MatchMapping, MatchMappingElement, MatchOr, MatchOrElement,
MatchPattern, MatchSequence, MatchSequenceElement, MatchSingleton, MatchStar, MatchTuple,
MatchValue, NameItem, Nonlocal, OrElse, Pass, Raise, Return, SimpleStatementLine,
SimpleStatementSuite, SmallStatement, StarrableMatchSequenceElement, Statement, Suite, Try,
TryStar, While, With, WithItem,
};
@ -32,8 +35,8 @@ pub use expression::{
mod op;
pub use op::{
AssignEqual, AugOp, BinaryOp, BooleanOp, Colon, Comma, CompOp, Dot, ImportStar, Semicolon,
UnaryOp,
AssignEqual, AugOp, BinaryOp, BitOr, BooleanOp, Colon, Comma, CompOp, Dot, ImportStar,
Semicolon, UnaryOp,
};
mod module;

View file

@ -1418,3 +1418,33 @@ impl<'a> Codegen<'a> for AugOp<'a> {
aft.codegen(state);
}
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy)]
pub struct BitOr<'a> {
pub whitespace_before: ParenthesizableWhitespace<'a>,
pub whitespace_after: ParenthesizableWhitespace<'a>,
pub(crate) tok: TokenRef<'a>,
}
impl<'a> Inflate<'a> for BitOr<'a> {
fn inflate(mut self, config: &Config<'a>) -> Result<Self> {
self.whitespace_before = parse_parenthesizable_whitespace(
config,
&mut (*self.tok).whitespace_before.borrow_mut(),
)?;
self.whitespace_after = parse_parenthesizable_whitespace(
config,
&mut (*self.tok).whitespace_after.borrow_mut(),
)?;
Ok(self)
}
}
impl<'a> Codegen<'a> for BitOr<'a> {
fn codegen(&self, state: &mut CodegenState<'a>) {
self.whitespace_before.codegen(state);
state.add_token("|");
self.whitespace_after.codegen(state);
}
}

View file

@ -14,7 +14,7 @@ use super::{
use crate::{
nodes::{
traits::{Inflate, Result, WithComma, WithLeadingLines},
Arg, AssignEqual, Asynchronous, AugOp, Element, ParenthesizedNode,
Arg, AssignEqual, Asynchronous, AugOp, BitOr, Element, ParenthesizedNode,
},
tokenizer::{
whitespace_parser::{
@ -23,6 +23,7 @@ use crate::{
},
Token,
},
LeftCurlyBrace, LeftSquareBracket, RightCurlyBrace, RightSquareBracket,
};
use libcst_derive::{Codegen, Inflate, IntoPy, ParenthesizedNode};
@ -55,6 +56,7 @@ pub enum CompoundStatement<'a> {
Try(Try<'a>),
TryStar(TryStar<'a>),
With(With<'a>),
Match(Match<'a>),
}
impl<'a> WithLeadingLines<'a> for CompoundStatement<'a> {
@ -68,6 +70,7 @@ impl<'a> WithLeadingLines<'a> for CompoundStatement<'a> {
Self::Try(t) => &mut t.leading_lines,
Self::TryStar(t) => &mut t.leading_lines,
Self::With(w) => &mut w.leading_lines,
Self::Match(m) => &mut m.leading_lines,
}
}
}
@ -2100,3 +2103,796 @@ impl<'a> Del<'a> {
Self { semicolon, ..self }
}
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy)]
pub struct Match<'a> {
pub subject: Expression<'a>,
pub cases: Vec<MatchCase<'a>>,
pub leading_lines: Vec<EmptyLine<'a>>,
pub whitespace_after_match: SimpleWhitespace<'a>,
pub whitespace_before_colon: SimpleWhitespace<'a>,
pub whitespace_after_colon: TrailingWhitespace<'a>,
pub indent: Option<&'a str>,
pub footer: Vec<EmptyLine<'a>>,
pub(crate) match_tok: TokenRef<'a>,
pub(crate) colon_tok: TokenRef<'a>,
pub(crate) indent_tok: TokenRef<'a>,
pub(crate) dedent_tok: TokenRef<'a>,
}
impl<'a> Codegen<'a> for Match<'a> {
fn codegen(&self, state: &mut CodegenState<'a>) {
for l in &self.leading_lines {
l.codegen(state);
}
state.add_indent();
state.add_token("match");
self.whitespace_after_match.codegen(state);
self.subject.codegen(state);
self.whitespace_before_colon.codegen(state);
state.add_token(":");
self.whitespace_after_colon.codegen(state);
let indent = self.indent.unwrap_or(state.default_indent);
state.indent(indent);
// Note: empty cases is a syntax error
for c in &self.cases {
c.codegen(state);
}
for f in &self.footer {
f.codegen(state);
}
state.dedent();
}
}
impl<'a> Inflate<'a> for Match<'a> {
fn inflate(mut self, config: &Config<'a>) -> Result<Self> {
self.leading_lines = parse_empty_lines(
config,
&mut self.match_tok.whitespace_before.borrow_mut(),
None,
)?;
self.whitespace_after_match =
parse_simple_whitespace(config, &mut self.match_tok.whitespace_after.borrow_mut())?;
self.subject = self.subject.inflate(config)?;
self.whitespace_before_colon =
parse_simple_whitespace(config, &mut self.colon_tok.whitespace_before.borrow_mut())?;
self.whitespace_after_colon =
parse_trailing_whitespace(config, &mut self.colon_tok.whitespace_after.borrow_mut())?;
self.indent = self.indent_tok.relative_indent;
if self.indent == Some(config.default_indent) {
self.indent = None;
}
self.cases = self.cases.inflate(config)?;
// See note about footers in `IndentedBlock`'s inflate fn
self.footer = parse_empty_lines(
config,
&mut self.dedent_tok.whitespace_after.borrow_mut(),
Some(self.indent_tok.whitespace_before.borrow().absolute_indent),
)?;
Ok(self)
}
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy)]
pub struct MatchCase<'a> {
pub pattern: MatchPattern<'a>,
pub guard: Option<Expression<'a>>,
pub body: Suite<'a>,
pub leading_lines: Vec<EmptyLine<'a>>,
pub whitespace_after_case: SimpleWhitespace<'a>,
pub whitespace_before_if: SimpleWhitespace<'a>,
pub whitespace_after_if: SimpleWhitespace<'a>,
pub whitespace_before_colon: SimpleWhitespace<'a>,
pub(crate) case_tok: TokenRef<'a>,
pub(crate) if_tok: Option<TokenRef<'a>>,
pub(crate) colon_tok: TokenRef<'a>,
}
impl<'a> Codegen<'a> for MatchCase<'a> {
fn codegen(&self, state: &mut CodegenState<'a>) {
for l in &self.leading_lines {
l.codegen(state);
}
state.add_indent();
state.add_token("case");
self.whitespace_after_case.codegen(state);
self.pattern.codegen(state);
if let Some(guard) = &self.guard {
self.whitespace_before_if.codegen(state);
state.add_token("if");
self.whitespace_after_if.codegen(state);
guard.codegen(state);
}
self.whitespace_before_colon.codegen(state);
state.add_token(":");
self.body.codegen(state);
}
}
impl<'a> Inflate<'a> for MatchCase<'a> {
fn inflate(mut self, config: &Config<'a>) -> Result<Self> {
self.leading_lines = parse_empty_lines(
config,
&mut self.case_tok.whitespace_before.borrow_mut(),
None,
)?;
self.whitespace_after_case =
parse_simple_whitespace(config, &mut self.case_tok.whitespace_after.borrow_mut())?;
self.pattern = self.pattern.inflate(config)?;
if let Some(if_tok) = self.if_tok.as_mut() {
self.whitespace_before_if =
parse_simple_whitespace(config, &mut if_tok.whitespace_before.borrow_mut())?;
self.whitespace_after_if =
parse_simple_whitespace(config, &mut if_tok.whitespace_after.borrow_mut())?;
self.guard = self.guard.inflate(config)?;
}
self.whitespace_before_colon =
parse_simple_whitespace(config, &mut self.colon_tok.whitespace_before.borrow_mut())?;
self.body = self.body.inflate(config)?;
Ok(self)
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, PartialEq, Eq, Clone, IntoPy, Codegen, Inflate, ParenthesizedNode)]
pub enum MatchPattern<'a> {
Value(MatchValue<'a>),
Singleton(MatchSingleton<'a>),
Sequence(MatchSequence<'a>),
Mapping(MatchMapping<'a>),
Class(MatchClass<'a>),
As(Box<MatchAs<'a>>),
Or(Box<MatchOr<'a>>),
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy)]
pub struct MatchValue<'a> {
pub value: Expression<'a>,
}
impl<'a> ParenthesizedNode<'a> for MatchValue<'a> {
fn lpar(&self) -> &Vec<LeftParen<'a>> {
self.value.lpar()
}
fn rpar(&self) -> &Vec<RightParen<'a>> {
self.value.rpar()
}
fn parenthesize<F>(&self, state: &mut CodegenState<'a>, f: F)
where
F: FnOnce(&mut CodegenState<'a>),
{
self.value.parenthesize(state, f)
}
fn with_parens(self, left: LeftParen<'a>, right: RightParen<'a>) -> Self {
Self {
value: self.value.with_parens(left, right),
}
}
}
impl<'a> Codegen<'a> for MatchValue<'a> {
fn codegen(&self, state: &mut CodegenState<'a>) {
self.value.codegen(state)
}
}
impl<'a> Inflate<'a> for MatchValue<'a> {
fn inflate(mut self, config: &Config<'a>) -> Result<Self> {
self.value = self.value.inflate(config)?;
Ok(self)
}
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy)]
pub struct MatchSingleton<'a> {
pub value: Name<'a>,
}
impl<'a> ParenthesizedNode<'a> for MatchSingleton<'a> {
fn lpar(&self) -> &Vec<LeftParen<'a>> {
self.value.lpar()
}
fn rpar(&self) -> &Vec<RightParen<'a>> {
self.value.rpar()
}
fn parenthesize<F>(&self, state: &mut CodegenState<'a>, f: F)
where
F: FnOnce(&mut CodegenState<'a>),
{
self.value.parenthesize(state, f)
}
fn with_parens(self, left: LeftParen<'a>, right: RightParen<'a>) -> Self {
Self {
value: self.value.with_parens(left, right),
}
}
}
impl<'a> Codegen<'a> for MatchSingleton<'a> {
fn codegen(&self, state: &mut CodegenState<'a>) {
self.value.codegen(state)
}
}
impl<'a> Inflate<'a> for MatchSingleton<'a> {
fn inflate(mut self, config: &Config<'a>) -> Result<Self> {
self.value = self.value.inflate(config)?;
Ok(self)
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, PartialEq, Eq, Clone, IntoPy, Codegen, Inflate, ParenthesizedNode)]
pub enum MatchSequence<'a> {
MatchList(MatchList<'a>),
MatchTuple(MatchTuple<'a>),
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy, ParenthesizedNode)]
pub struct MatchList<'a> {
pub patterns: Vec<StarrableMatchSequenceElement<'a>>,
pub lbracket: Option<LeftSquareBracket<'a>>,
pub rbracket: Option<RightSquareBracket<'a>>,
pub lpar: Vec<LeftParen<'a>>,
pub rpar: Vec<RightParen<'a>>,
}
impl<'a> Codegen<'a> for MatchList<'a> {
fn codegen(&self, state: &mut CodegenState<'a>) {
self.parenthesize(state, |state| {
self.lbracket.codegen(state);
let len = self.patterns.len();
if len == 1 {
self.patterns.first().unwrap().codegen(state, false, false);
} else {
for (idx, pat) in self.patterns.iter().enumerate() {
pat.codegen(state, idx < len - 1, true);
}
}
self.rbracket.codegen(state);
})
}
}
impl<'a> Inflate<'a> for MatchList<'a> {
fn inflate(mut self, config: &Config<'a>) -> Result<Self> {
self.lpar = self.lpar.inflate(config)?;
self.lbracket = self.lbracket.inflate(config)?;
let len = self.patterns.len();
self.patterns = self
.patterns
.into_iter()
.enumerate()
.map(|(idx, el)| el.inflate_element(config, idx + 1 == len))
.collect::<Result<Vec<_>>>()?;
self.rbracket = self.rbracket.inflate(config)?;
self.rpar = self.rpar.inflate(config)?;
Ok(self)
}
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy, ParenthesizedNode)]
pub struct MatchTuple<'a> {
pub patterns: Vec<StarrableMatchSequenceElement<'a>>,
pub lpar: Vec<LeftParen<'a>>,
pub rpar: Vec<RightParen<'a>>,
}
impl<'a> Codegen<'a> for MatchTuple<'a> {
fn codegen(&self, state: &mut CodegenState<'a>) {
self.parenthesize(state, |state| {
let len = self.patterns.len();
if len == 1 {
self.patterns.first().unwrap().codegen(state, true, false);
} else {
for (idx, pat) in self.patterns.iter().enumerate() {
pat.codegen(state, idx < len - 1, true);
}
}
})
}
}
impl<'a> Inflate<'a> for MatchTuple<'a> {
fn inflate(mut self, config: &Config<'a>) -> Result<Self> {
self.lpar = self.lpar.inflate(config)?;
let len = self.patterns.len();
self.patterns = self
.patterns
.into_iter()
.enumerate()
.map(|(idx, el)| el.inflate_element(config, idx + 1 == len))
.collect::<Result<Vec<_>>>()?;
self.rpar = self.rpar.inflate(config)?;
Ok(self)
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, PartialEq, Eq, Clone, IntoPy)]
pub enum StarrableMatchSequenceElement<'a> {
Simple(MatchSequenceElement<'a>),
Starred(MatchStar<'a>),
}
impl<'a> StarrableMatchSequenceElement<'a> {
fn codegen(
&self,
state: &mut CodegenState<'a>,
default_comma: bool,
default_comma_whitespace: bool,
) {
match &self {
Self::Simple(s) => s.codegen(state, default_comma, default_comma_whitespace),
Self::Starred(s) => s.codegen(state, default_comma, default_comma_whitespace),
}
}
fn inflate_element(self, config: &Config<'a>, last_element: bool) -> Result<Self> {
Ok(match self {
Self::Simple(s) => Self::Simple(s.inflate_element(config, last_element)?),
Self::Starred(s) => Self::Starred(s.inflate_element(config, last_element)?),
})
}
}
impl<'a> WithComma<'a> for StarrableMatchSequenceElement<'a> {
fn with_comma(self, comma: Comma<'a>) -> Self {
match self {
Self::Simple(s) => Self::Simple(s.with_comma(comma)),
Self::Starred(s) => Self::Starred(s.with_comma(comma)),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy)]
pub struct MatchSequenceElement<'a> {
pub value: MatchPattern<'a>,
pub comma: Option<Comma<'a>>,
}
impl<'a> MatchSequenceElement<'a> {
fn codegen(
&self,
state: &mut CodegenState<'a>,
default_comma: bool,
default_comma_whitespace: bool,
) {
self.value.codegen(state);
self.comma.codegen(state);
if self.comma.is_none() && default_comma {
state.add_token(if default_comma_whitespace { ", " } else { "," });
}
}
fn inflate_element(mut self, config: &Config<'a>, last_element: bool) -> Result<Self> {
self.value = self.value.inflate(config)?;
self.comma = if last_element {
self.comma.map(|c| c.inflate_before(config)).transpose()
} else {
self.comma.inflate(config)
}?;
Ok(self)
}
}
impl<'a> WithComma<'a> for MatchSequenceElement<'a> {
fn with_comma(self, comma: Comma<'a>) -> Self {
Self {
comma: Some(comma),
..self
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy)]
pub struct MatchStar<'a> {
pub name: Option<Name<'a>>,
pub comma: Option<Comma<'a>>,
pub whitespace_before_name: ParenthesizableWhitespace<'a>,
pub(crate) star_tok: TokenRef<'a>,
}
impl<'a> MatchStar<'a> {
fn codegen(
&self,
state: &mut CodegenState<'a>,
default_comma: bool,
default_comma_whitespace: bool,
) {
state.add_token("*");
self.whitespace_before_name.codegen(state);
if let Some(name) = &self.name {
name.codegen(state);
} else {
state.add_token("_");
}
self.comma.codegen(state);
if self.comma.is_none() && default_comma {
state.add_token(if default_comma_whitespace { ", " } else { "," });
}
}
fn inflate_element(mut self, config: &Config<'a>, last_element: bool) -> Result<Self> {
self.whitespace_before_name = parse_parenthesizable_whitespace(
config,
&mut self.star_tok.whitespace_after.borrow_mut(),
)?;
self.name = self.name.inflate(config)?;
self.comma = if last_element {
self.comma.map(|c| c.inflate_before(config)).transpose()
} else {
self.comma.inflate(config)
}?;
Ok(self)
}
}
impl<'a> WithComma<'a> for MatchStar<'a> {
fn with_comma(self, comma: Comma<'a>) -> Self {
Self {
comma: Some(comma),
..self
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy, ParenthesizedNode)]
pub struct MatchMapping<'a> {
pub elements: Vec<MatchMappingElement<'a>>,
pub rest: Option<Name<'a>>,
pub trailing_comma: Option<Comma<'a>>,
pub lbrace: LeftCurlyBrace<'a>,
pub rbrace: RightCurlyBrace<'a>,
pub lpar: Vec<LeftParen<'a>>,
pub rpar: Vec<RightParen<'a>>,
pub whitespace_before_rest: SimpleWhitespace<'a>,
pub(crate) star_tok: Option<TokenRef<'a>>,
}
impl<'a> Codegen<'a> for MatchMapping<'a> {
fn codegen(&self, state: &mut CodegenState<'a>) {
self.parenthesize(state, |state| {
self.lbrace.codegen(state);
let len = self.elements.len();
for (idx, el) in self.elements.iter().enumerate() {
el.codegen(state, self.rest.is_some() || idx < len - 1);
}
if let Some(rest) = &self.rest {
state.add_token("**");
self.whitespace_before_rest.codegen(state);
rest.codegen(state);
self.trailing_comma.codegen(state);
}
self.rbrace.codegen(state);
})
}
}
impl<'a> Inflate<'a> for MatchMapping<'a> {
fn inflate(mut self, config: &Config<'a>) -> Result<Self> {
self.lpar = self.lpar.inflate(config)?;
self.lbrace = self.lbrace.inflate(config)?;
let len = self.elements.len();
let no_star = self.star_tok.is_none();
self.elements = self
.elements
.into_iter()
.enumerate()
.map(|(idx, el)| el.inflate_element(config, no_star && idx + 1 == len))
.collect::<Result<Vec<_>>>()?;
if let Some(star_tok) = self.star_tok.as_mut() {
self.whitespace_before_rest =
parse_simple_whitespace(config, &mut star_tok.whitespace_after.borrow_mut())?;
self.rest = self.rest.inflate(config)?;
self.trailing_comma = self
.trailing_comma
.map(|c| c.inflate_before(config))
.transpose()?;
}
self.rbrace = self.rbrace.inflate(config)?;
self.rpar = self.rpar.inflate(config)?;
Ok(self)
}
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy)]
pub struct MatchMappingElement<'a> {
pub key: Expression<'a>,
pub pattern: MatchPattern<'a>,
pub comma: Option<Comma<'a>>,
pub whitespace_before_colon: ParenthesizableWhitespace<'a>,
pub whitespace_after_colon: ParenthesizableWhitespace<'a>,
pub(crate) colon_tok: TokenRef<'a>,
}
impl<'a> MatchMappingElement<'a> {
fn codegen(&self, state: &mut CodegenState<'a>, default_comma: bool) {
self.key.codegen(state);
self.whitespace_before_colon.codegen(state);
state.add_token(":");
self.whitespace_after_colon.codegen(state);
self.pattern.codegen(state);
self.comma.codegen(state);
if self.comma.is_none() && default_comma {
state.add_token(", ");
}
}
fn inflate_element(mut self, config: &Config<'a>, last_element: bool) -> Result<Self> {
self.key = self.key.inflate(config)?;
self.whitespace_before_colon = parse_parenthesizable_whitespace(
config,
&mut self.colon_tok.whitespace_before.borrow_mut(),
)?;
self.whitespace_after_colon = parse_parenthesizable_whitespace(
config,
&mut self.colon_tok.whitespace_after.borrow_mut(),
)?;
self.pattern = self.pattern.inflate(config)?;
self.comma = if last_element {
self.comma.map(|c| c.inflate_before(config)).transpose()
} else {
self.comma.inflate(config)
}?;
Ok(self)
}
}
impl<'a> WithComma<'a> for MatchMappingElement<'a> {
fn with_comma(self, comma: Comma<'a>) -> Self {
Self {
comma: Some(comma),
..self
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy, ParenthesizedNode)]
pub struct MatchClass<'a> {
pub cls: NameOrAttribute<'a>,
pub patterns: Vec<MatchSequenceElement<'a>>,
pub kwds: Vec<MatchKeywordElement<'a>>,
pub lpar: Vec<LeftParen<'a>>,
pub rpar: Vec<RightParen<'a>>,
pub whitespace_after_cls: ParenthesizableWhitespace<'a>,
pub whitespace_before_patterns: ParenthesizableWhitespace<'a>,
pub whitespace_after_kwds: ParenthesizableWhitespace<'a>,
pub(crate) lpar_tok: TokenRef<'a>,
pub(crate) rpar_tok: TokenRef<'a>,
}
impl<'a> Codegen<'a> for MatchClass<'a> {
fn codegen(&self, state: &mut CodegenState<'a>) {
self.parenthesize(state, |state| {
self.cls.codegen(state);
self.whitespace_after_cls.codegen(state);
state.add_token("(");
self.whitespace_before_patterns.codegen(state);
let patlen = self.patterns.len();
let kwdlen = self.kwds.len();
for (idx, pat) in self.patterns.iter().enumerate() {
pat.codegen(state, idx < patlen - 1 + kwdlen, patlen == 1 && kwdlen == 0);
}
for (idx, kwd) in self.kwds.iter().enumerate() {
kwd.codegen(state, idx < kwdlen - 1);
}
self.whitespace_after_kwds.codegen(state);
state.add_token(")");
})
}
}
impl<'a> Inflate<'a> for MatchClass<'a> {
fn inflate(mut self, config: &Config<'a>) -> Result<Self> {
self.lpar = self.lpar.inflate(config)?;
self.cls = self.cls.inflate(config)?;
self.whitespace_after_cls = parse_parenthesizable_whitespace(
config,
&mut self.lpar_tok.whitespace_before.borrow_mut(),
)?;
self.whitespace_before_patterns = parse_parenthesizable_whitespace(
config,
&mut self.lpar_tok.whitespace_after.borrow_mut(),
)?;
let patlen = self.patterns.len();
let kwdlen = self.kwds.len();
self.patterns = self
.patterns
.into_iter()
.enumerate()
.map(|(idx, pat)| pat.inflate_element(config, idx + 1 == patlen + kwdlen))
.collect::<Result<_>>()?;
self.kwds = self
.kwds
.into_iter()
.enumerate()
.map(|(idx, kwd)| kwd.inflate_element(config, idx + 1 == kwdlen))
.collect::<Result<_>>()?;
self.whitespace_after_kwds = parse_parenthesizable_whitespace(
config,
&mut self.rpar_tok.whitespace_before.borrow_mut(),
)?;
self.rpar = self.rpar.inflate(config)?;
Ok(self)
}
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy)]
pub struct MatchKeywordElement<'a> {
pub key: Name<'a>,
pub pattern: MatchPattern<'a>,
pub comma: Option<Comma<'a>>,
pub whitespace_before_equal: ParenthesizableWhitespace<'a>,
pub whitespace_after_equal: ParenthesizableWhitespace<'a>,
pub(crate) equal_tok: TokenRef<'a>,
}
impl<'a> MatchKeywordElement<'a> {
fn codegen(&self, state: &mut CodegenState<'a>, default_comma: bool) {
self.key.codegen(state);
self.whitespace_before_equal.codegen(state);
state.add_token("=");
self.whitespace_after_equal.codegen(state);
self.pattern.codegen(state);
self.comma.codegen(state);
if self.comma.is_none() && default_comma {
state.add_token(", ");
}
}
fn inflate_element(mut self, config: &Config<'a>, last_element: bool) -> Result<Self> {
self.key = self.key.inflate(config)?;
self.whitespace_before_equal = parse_parenthesizable_whitespace(
config,
&mut self.equal_tok.whitespace_before.borrow_mut(),
)?;
self.whitespace_after_equal = parse_parenthesizable_whitespace(
config,
&mut self.equal_tok.whitespace_after.borrow_mut(),
)?;
self.pattern = self.pattern.inflate(config)?;
self.comma = if last_element {
self.comma.map(|c| c.inflate_before(config)).transpose()
} else {
self.comma.inflate(config)
}?;
Ok(self)
}
}
impl<'a> WithComma<'a> for MatchKeywordElement<'a> {
fn with_comma(self, comma: Comma<'a>) -> Self {
Self {
comma: Some(comma),
..self
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy, ParenthesizedNode)]
pub struct MatchAs<'a> {
pub pattern: Option<MatchPattern<'a>>,
pub name: Option<Name<'a>>,
pub lpar: Vec<LeftParen<'a>>,
pub rpar: Vec<RightParen<'a>>,
pub whitespace_before_as: Option<ParenthesizableWhitespace<'a>>,
pub whitespace_after_as: Option<ParenthesizableWhitespace<'a>>,
pub(crate) as_tok: Option<TokenRef<'a>>,
}
impl<'a> Codegen<'a> for MatchAs<'a> {
fn codegen(&self, state: &mut CodegenState<'a>) {
self.parenthesize(state, |state| {
if let Some(pat) = &self.pattern {
pat.codegen(state);
self.whitespace_before_as.codegen(state);
state.add_token("as");
self.whitespace_after_as.codegen(state);
}
if let Some(name) = &self.name {
name.codegen(state);
} else {
state.add_token("_");
}
})
}
}
impl<'a> Inflate<'a> for MatchAs<'a> {
fn inflate(mut self, config: &Config<'a>) -> Result<Self> {
self.lpar = self.lpar.inflate(config)?;
self.pattern = self.pattern.inflate(config)?;
if let Some(as_tok) = self.as_tok.as_mut() {
self.whitespace_before_as = Some(parse_parenthesizable_whitespace(
config,
&mut as_tok.whitespace_before.borrow_mut(),
)?);
self.whitespace_after_as = Some(parse_parenthesizable_whitespace(
config,
&mut as_tok.whitespace_after.borrow_mut(),
)?);
}
self.name = self.name.inflate(config)?;
self.rpar = self.rpar.inflate(config)?;
Ok(self)
}
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy)]
pub struct MatchOrElement<'a> {
pub pattern: MatchPattern<'a>,
pub separator: Option<BitOr<'a>>,
}
impl<'a> MatchOrElement<'a> {
fn codegen(&self, state: &mut CodegenState<'a>, default_separator: bool) {
self.pattern.codegen(state);
self.separator.codegen(state);
if self.separator.is_none() && default_separator {
state.add_token(" | ");
}
}
}
impl<'a> Inflate<'a> for MatchOrElement<'a> {
fn inflate(mut self, config: &Config<'a>) -> Result<Self> {
self.pattern = self.pattern.inflate(config)?;
self.separator = self.separator.inflate(config)?;
Ok(self)
}
}
#[derive(Debug, PartialEq, Eq, Clone, IntoPy, ParenthesizedNode)]
pub struct MatchOr<'a> {
pub patterns: Vec<MatchOrElement<'a>>,
pub lpar: Vec<LeftParen<'a>>,
pub rpar: Vec<RightParen<'a>>,
}
impl<'a> Codegen<'a> for MatchOr<'a> {
fn codegen(&self, state: &mut CodegenState<'a>) {
self.parenthesize(state, |state| {
let len = self.patterns.len();
for (idx, pat) in self.patterns.iter().enumerate() {
pat.codegen(state, idx + 1 < len)
}
})
}
}
impl<'a> Inflate<'a> for MatchOr<'a> {
fn inflate(mut self, config: &Config<'a>) -> Result<Self> {
self.lpar = self.lpar.inflate(config)?;
self.patterns = self.patterns.inflate(config)?;
self.rpar = self.rpar.inflate(config)?;
Ok(self)
}
}

View file

@ -7,6 +7,7 @@ use crate::{
tokenizer::whitespace_parser::{Config, WhitespaceError},
Codegen, CodegenState, Comma, EmptyLine, LeftParen, RightParen,
};
use std::ops::Deref;
pub trait WithComma<'a> {
fn with_comma(self, comma: Comma<'a>) -> Self;
@ -32,6 +33,24 @@ pub trait ParenthesizedNode<'a> {
fn with_parens(self, left: LeftParen<'a>, right: RightParen<'a>) -> Self;
}
impl<'a, T: ParenthesizedNode<'a>> ParenthesizedNode<'a> for Box<T> {
fn lpar(&self) -> &Vec<LeftParen<'a>> {
self.deref().lpar()
}
fn rpar(&self) -> &Vec<RightParen<'a>> {
self.deref().rpar()
}
fn parenthesize<F>(&self, state: &mut CodegenState<'a>, f: F)
where
F: FnOnce(&mut CodegenState<'a>),
{
self.deref().parenthesize(state, f)
}
fn with_parens(self, left: LeftParen<'a>, right: RightParen<'a>) -> Self {
Self::new((*self).with_parens(left, right))
}
}
pub trait WithLeadingLines<'a> {
fn leading_lines(&mut self) -> &mut Vec<EmptyLine<'a>>;
}

View file

@ -16,6 +16,7 @@ use TokType::{
};
pub type Result<'a, T> = std::result::Result<T, ParserError<'a>>;
type GrammarResult<T> = std::result::Result<T, &'static str>;
#[derive(Debug)]
pub struct TokVec<'a>(Vec<Rc<Token<'a>>>);
@ -150,6 +151,7 @@ parser! {
/ &lit("try") t:try_stmt() { CompoundStatement::Try(t) }
/ &lit("try") t:try_star_stmt() { CompoundStatement::TryStar(t) }
/ &lit("while") w:while_stmt() { CompoundStatement::While(w) }
/ m:match_stmt() { CompoundStatement::Match(m) }
// Simple statements
@ -529,6 +531,218 @@ parser! {
}
// Match statement
rule match_stmt() -> Match<'a>
= kw:lit("match") subject:subject_expr() col:lit(":") tok(NL, "NEWLINE")
i:tok(Indent, "INDENT") cases:case_block()+ d:tok(Dedent, "DEDENT") {
make_match(kw, subject, col, i, cases, d)
}
rule subject_expr() -> Expression<'a>
= first:star_named_expression() c:comma() rest:star_named_expressions()? {
Expression::Tuple(
make_tuple_from_elements(first.with_comma(c), rest.unwrap_or_default())
)
}
/ named_expression()
rule case_block() -> MatchCase<'a>
= kw:lit("case") pattern:patterns() guard:guard()? col:lit(":") body:block() {
make_case(kw, pattern, guard, col, body)
}
rule guard() -> (TokenRef<'a>, Expression<'a>)
= kw:lit("if") exp:named_expression() { (kw, exp) }
rule patterns() -> MatchPattern<'a>
= pats:open_sequence_pattern() {
MatchPattern::Sequence(make_list_pattern(None, pats, None))
}
/ pattern()
rule pattern() -> MatchPattern<'a>
= as_pattern()
/ or_pattern()
rule as_pattern() -> MatchPattern<'a>
= pat:or_pattern() kw:lit("as") target:pattern_capture_target() {
make_as_pattern(Some(pat), Some(kw), Some(target))
}
rule or_pattern() -> MatchPattern<'a>
= pats:separated(<closed_pattern()>, <lit("|")>) {
make_or_pattern(pats.0, pats.1)
}
rule closed_pattern() -> MatchPattern<'a>
= literal_pattern()
/ capture_pattern()
/ wildcard_pattern()
/ value_pattern()
/ group_pattern()
/ sequence_pattern()
/ mapping_pattern()
/ class_pattern()
rule literal_pattern() -> MatchPattern<'a>
= val:signed_number() !(lit("+") / lit("-")) { make_match_value(val) }
/ val:complex_number() { make_match_value(val) }
/ val:strings() { make_match_value(val.into()) }
/ n:lit("None") { make_match_singleton(make_name(n)) }
/ n:lit("True") { make_match_singleton(make_name(n)) }
/ n:lit("False") { make_match_singleton(make_name(n)) }
rule literal_expr() -> Expression<'a>
= val:signed_number() !(lit("+") / lit("-")) { val }
/ val:complex_number() { val }
/ val:strings() { val.into() }
/ n:lit("None") { Expression::Name(make_name(n)) }
/ n:lit("True") { Expression::Name(make_name(n)) }
/ n:lit("False") { Expression::Name(make_name(n)) }
rule complex_number() -> Expression<'a>
= re:signed_real_number() op:(lit("+")/lit("-")) im:imaginary_number() {?
make_binary_op(re, op, im).map_err(|_| "complex number")
}
rule signed_number() -> Expression<'a>
= n:tok(Number, "number") { make_number(n) }
/ op:lit("-") n:tok(Number, "number") {?
make_unary_op(op, make_number(n)).map_err(|_| "signed number")
}
rule signed_real_number() -> Expression<'a>
= real_number()
/ op:lit("-") n:real_number() {?
make_unary_op(op, n).map_err(|_| "signed real number")
}
rule real_number() -> Expression<'a>
= n:tok(Number, "number") {? ensure_real_number(n) }
rule imaginary_number() -> Expression<'a>
= n:tok(Number, "number") {? ensure_imaginary_number(n) }
rule capture_pattern() -> MatchPattern<'a>
= t:pattern_capture_target() { make_as_pattern(None, None, Some(t)) }
rule pattern_capture_target() -> Name<'a>
= !lit("_") n:name() !(lit(".") / lit("(") / lit("=")) { n }
rule wildcard_pattern() -> MatchPattern<'a>
= lit("_") { make_as_pattern(None, None, None) }
rule value_pattern() -> MatchPattern<'a>
= v:attr() !(lit(".") / lit("(") / lit("=")) {
make_match_value(v.into())
}
// In upstream attr and name_or_attr are mutually recursive, but rust-peg
// doesn't support this yet.
rule attr() -> NameOrAttribute<'a>
= &(name() lit(".")) v:name_or_attr() { v }
#[cache_left_rec]
rule name_or_attr() -> NameOrAttribute<'a>
= val:name_or_attr() d:lit(".") attr:name() {
NameOrAttribute::A(make_attribute(val.into(), d, attr))
}
/ n:name() { NameOrAttribute::N(n) }
rule group_pattern() -> MatchPattern<'a>
= l:lpar() pat:pattern() r:rpar() { pat.with_parens(l, r) }
rule sequence_pattern() -> MatchPattern<'a>
= l:lbrak() pats:maybe_sequence_pattern()? r:rbrak() {
MatchPattern::Sequence(
make_list_pattern(Some(l), pats.unwrap_or_default(), Some(r))
)
}
/ l:lpar() pats:open_sequence_pattern()? r:rpar() {
MatchPattern::Sequence(make_tuple_pattern(l, pats.unwrap_or_default(), r))
}
rule open_sequence_pattern() -> Vec<StarrableMatchSequenceElement<'a>>
= pat:maybe_star_pattern() c:comma() pats:maybe_sequence_pattern()? {
make_open_sequence_pattern(pat, c, pats.unwrap_or_default())
}
rule maybe_sequence_pattern() -> Vec<StarrableMatchSequenceElement<'a>>
= pats:separated_trailer(<maybe_star_pattern()>, <comma()>) {
comma_separate(pats.0, pats.1, pats.2)
}
rule maybe_star_pattern() -> StarrableMatchSequenceElement<'a>
= s:star_pattern() { StarrableMatchSequenceElement::Starred(s) }
/ p:pattern() {
StarrableMatchSequenceElement::Simple(
make_match_sequence_element(p)
)
}
rule star_pattern() -> MatchStar<'a>
= star:lit("*") t:pattern_capture_target() {make_match_star(star, Some(t))}
/ star:lit("*") t:wildcard_pattern() { make_match_star(star, None) }
rule mapping_pattern() -> MatchPattern<'a>
= l:lbrace() r:rbrace() {
make_match_mapping(l, vec![], None, None, None, None, r)
}
/ l:lbrace() rest:double_star_pattern() trail:comma()? r:rbrace() {
make_match_mapping(l, vec![], None, Some(rest.0), Some(rest.1), trail, r)
}
/ l:lbrace() items:items_pattern() c:comma() rest:double_star_pattern()
trail:comma()? r:rbrace() {
make_match_mapping(l, items, Some(c), Some(rest.0), Some(rest.1), trail, r)
}
/ l:lbrace() items:items_pattern() trail:comma()? r:rbrace() {
make_match_mapping(l, items, trail, None, None, None, r)
}
rule items_pattern() -> Vec<MatchMappingElement<'a>>
= pats:separated(<key_value_pattern()>, <comma()>) {
comma_separate(pats.0, pats.1, None)
}
rule key_value_pattern() -> MatchMappingElement<'a>
= key:(literal_expr() / a:attr() {a.into()}) colon:lit(":") pat:pattern() {
make_match_mapping_element(key, colon, pat)
}
rule double_star_pattern() -> (TokenRef<'a>, Name<'a>)
= star:lit("**") n:pattern_capture_target() { (star, n) }
rule class_pattern() -> MatchPattern<'a>
= cls:name_or_attr() l:lit("(") r:lit(")") {
make_class_pattern(cls, l, vec![], None, vec![], None, r)
}
/ cls:name_or_attr() l:lit("(") pats:positional_patterns() c:comma()? r:lit(")") {
make_class_pattern(cls, l, pats, c, vec![], None, r)
}
/ cls:name_or_attr() l:lit("(") kwds:keyword_patterns() c:comma()? r:lit(")") {
make_class_pattern(cls, l, vec![], None, kwds, c, r)
}
/ cls:name_or_attr() l:lit("(") pats:positional_patterns() c:comma()
kwds:keyword_patterns() trail:comma()? r:lit(")") {
make_class_pattern(cls, l, pats, Some(c), kwds, trail, r)
}
rule positional_patterns() -> Vec<MatchSequenceElement<'a>>
= pats:separated(<p:pattern() { make_match_sequence_element(p) }>, <comma()>) {
comma_separate(pats.0, pats.1, None)
}
rule keyword_patterns() -> Vec<MatchKeywordElement<'a>>
= pats:separated(<keyword_pattern()>, <comma()>) {
comma_separate(pats.0, pats.1, None)
}
rule keyword_pattern() -> MatchKeywordElement<'a>
= arg:name() eq:lit("=") value:pattern() {
make_match_keyword_element(arg, eq, value)
}
// Expressions
#[cache]
@ -1977,6 +2191,15 @@ fn make_tuple<'a>(
}
}
fn make_tuple_from_elements<'a>(first: Element<'a>, mut rest: Vec<Element<'a>>) -> Tuple<'a> {
rest.insert(0, first);
Tuple {
elements: rest,
lpar: Default::default(),
rpar: Default::default(),
}
}
fn make_kwarg<'a>(name: Name<'a>, eq: TokenRef<'a>, value: Expression<'a>) -> Arg<'a> {
let equal = Some(make_assign_equal(eq));
let keyword = Some(name);
@ -3047,3 +3270,272 @@ fn make_named_expr<'a>(name: Name<'a>, tok: TokenRef<'a>, expr: Expression<'a>)
walrus_tok: tok,
}
}
fn make_match<'a>(
match_tok: TokenRef<'a>,
subject: Expression<'a>,
colon_tok: TokenRef<'a>,
indent_tok: TokenRef<'a>,
cases: Vec<MatchCase<'a>>,
dedent_tok: TokenRef<'a>,
) -> Match<'a> {
Match {
subject,
cases,
leading_lines: Default::default(),
whitespace_after_match: Default::default(),
whitespace_before_colon: Default::default(),
whitespace_after_colon: Default::default(),
indent: Default::default(),
footer: Default::default(),
match_tok,
colon_tok,
indent_tok,
dedent_tok,
}
}
fn make_case<'a>(
case_tok: TokenRef<'a>,
pattern: MatchPattern<'a>,
guard: Option<(TokenRef<'a>, Expression<'a>)>,
colon_tok: TokenRef<'a>,
body: Suite<'a>,
) -> MatchCase<'a> {
let (if_tok, guard) = match guard {
Some((if_tok, guard)) => (Some(if_tok), Some(guard)),
None => (None, None),
};
MatchCase {
pattern,
guard,
body,
leading_lines: Default::default(),
whitespace_after_case: Default::default(),
whitespace_before_if: Default::default(),
whitespace_after_if: Default::default(),
whitespace_before_colon: Default::default(),
case_tok,
if_tok,
colon_tok,
}
}
fn make_match_value(value: Expression) -> MatchPattern {
MatchPattern::Value(MatchValue { value })
}
fn make_match_singleton(value: Name) -> MatchPattern {
MatchPattern::Singleton(MatchSingleton { value })
}
fn make_list_pattern<'a>(
lbracket: Option<LeftSquareBracket<'a>>,
patterns: Vec<StarrableMatchSequenceElement<'a>>,
rbracket: Option<RightSquareBracket<'a>>,
) -> MatchSequence<'a> {
MatchSequence::MatchList(MatchList {
patterns,
lbracket,
rbracket,
lpar: Default::default(),
rpar: Default::default(),
})
}
fn make_as_pattern<'a>(
pattern: Option<MatchPattern<'a>>,
as_tok: Option<TokenRef<'a>>,
name: Option<Name<'a>>,
) -> MatchPattern<'a> {
MatchPattern::As(Box::new(MatchAs {
pattern,
name,
lpar: Default::default(),
rpar: Default::default(),
whitespace_before_as: Default::default(),
whitespace_after_as: Default::default(),
as_tok,
}))
}
fn make_bit_or(tok: TokenRef) -> BitOr {
BitOr {
whitespace_before: Default::default(),
whitespace_after: Default::default(),
tok,
}
}
fn make_or_pattern<'a>(
first: MatchPattern<'a>,
rest: Vec<(TokenRef<'a>, MatchPattern<'a>)>,
) -> MatchPattern<'a> {
if rest.is_empty() {
return first;
}
let mut patterns = vec![];
let mut current = first;
for (sep, next) in rest {
let op = make_bit_or(sep);
patterns.push(MatchOrElement {
pattern: current,
separator: Some(op),
});
current = next;
}
patterns.push(MatchOrElement {
pattern: current,
separator: None,
});
MatchPattern::Or(Box::new(MatchOr {
patterns,
lpar: Default::default(),
rpar: Default::default(),
}))
}
fn ensure_real_number(tok: TokenRef) -> GrammarResult<Expression> {
match make_number(tok) {
e @ (Expression::Integer(_) | Expression::Float(_)) => Ok(e),
_ => Err("real number"),
}
}
fn ensure_imaginary_number(tok: TokenRef) -> GrammarResult<Expression> {
match make_number(tok) {
e @ Expression::Imaginary(_) => Ok(e),
_ => Err("imaginary number"),
}
}
fn make_tuple_pattern<'a>(
lpar: LeftParen<'a>,
patterns: Vec<StarrableMatchSequenceElement<'a>>,
rpar: RightParen<'a>,
) -> MatchSequence<'a> {
MatchSequence::MatchTuple(MatchTuple {
patterns,
lpar: vec![lpar],
rpar: vec![rpar],
})
}
fn make_open_sequence_pattern<'a>(
first: StarrableMatchSequenceElement<'a>,
comma: Comma<'a>,
mut rest: Vec<StarrableMatchSequenceElement<'a>>,
) -> Vec<StarrableMatchSequenceElement<'a>> {
rest.insert(0, first.with_comma(comma));
rest
}
fn make_match_sequence_element(value: MatchPattern) -> MatchSequenceElement {
MatchSequenceElement {
value,
comma: Default::default(),
}
}
fn make_match_star<'a>(star_tok: TokenRef<'a>, name: Option<Name<'a>>) -> MatchStar<'a> {
MatchStar {
name,
comma: Default::default(),
whitespace_before_name: Default::default(),
star_tok,
}
}
fn make_match_mapping<'a>(
lbrace: LeftCurlyBrace<'a>,
mut elements: Vec<MatchMappingElement<'a>>,
el_comma: Option<Comma<'a>>,
star_tok: Option<TokenRef<'a>>,
rest: Option<Name<'a>>,
trailing_comma: Option<Comma<'a>>,
rbrace: RightCurlyBrace<'a>,
) -> MatchPattern<'a> {
if let Some(c) = el_comma {
if let Some(el) = elements.pop() {
elements.push(el.with_comma(c));
}
// TODO: else raise error
}
MatchPattern::Mapping(MatchMapping {
elements,
rest,
trailing_comma,
lbrace,
rbrace,
lpar: Default::default(),
rpar: Default::default(),
whitespace_before_rest: Default::default(),
star_tok,
})
}
fn make_match_mapping_element<'a>(
key: Expression<'a>,
colon_tok: TokenRef<'a>,
pattern: MatchPattern<'a>,
) -> MatchMappingElement<'a> {
MatchMappingElement {
key,
pattern,
comma: Default::default(),
whitespace_before_colon: Default::default(),
whitespace_after_colon: Default::default(),
colon_tok,
}
}
fn make_class_pattern<'a>(
cls: NameOrAttribute<'a>,
lpar_tok: TokenRef<'a>,
mut patterns: Vec<MatchSequenceElement<'a>>,
pat_comma: Option<Comma<'a>>,
mut kwds: Vec<MatchKeywordElement<'a>>,
kwd_comma: Option<Comma<'a>>,
rpar_tok: TokenRef<'a>,
) -> MatchPattern<'a> {
if let Some(c) = pat_comma {
if let Some(el) = patterns.pop() {
patterns.push(el.with_comma(c));
}
// TODO: else raise error
}
if let Some(c) = kwd_comma {
if let Some(el) = kwds.pop() {
kwds.push(el.with_comma(c));
}
// TODO: else raise error
}
MatchPattern::Class(MatchClass {
cls,
patterns,
kwds,
lpar: Default::default(),
rpar: Default::default(),
whitespace_after_cls: Default::default(),
whitespace_before_patterns: Default::default(),
whitespace_after_kwds: Default::default(),
lpar_tok,
rpar_tok,
})
}
fn make_match_keyword_element<'a>(
key: Name<'a>,
equal_tok: TokenRef<'a>,
pattern: MatchPattern<'a>,
) -> MatchKeywordElement<'a> {
MatchKeywordElement {
key,
pattern,
comma: Default::default(),
whitespace_before_equal: Default::default(),
whitespace_after_equal: Default::default(),
equal_tok,
}
}

View file

@ -0,0 +1,39 @@
# foo
match ( foo ) : #comment
# more comments
case False : # comment
...
case ( True ) : ...
case _ : ...
case ( _ ) : ... # foo
# bar
match x:
case "StringMatchValue" : pass
case [1, 2] : pass
case [ 1 , * foo , * _ , ]: pass
case [ [ _, ] , *_ ]: pass
case {1: _, 2: _}: pass
case { "foo" : bar , ** rest } : pass
case { 1 : {**rest} , } : pass
case Point2D(): pass
case Cls ( 0 , ) : pass
case Cls ( x=0, y = 2) :pass
case Cls ( 0 , 1 , x = 0 , y = 2 ) : pass
case [x] as y: pass
case [x] as y : pass
case (True)as x:pass
case Foo:pass
case (Foo):pass
case ( Foo ) : pass
case [ ( Foo ) , ]: pass
case Foo|Bar|Baz : pass
case Foo | Bar | ( Baz): pass
case x,y , * more :pass
case y.z: pass