From 74cfaeb917740f7849cefe7c7663782ae259e793 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 2 Oct 2026 23:15:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(switch):=20=E6=96=B0=E5=A2=9E=20switch=20?= =?UTF-8?q?=E8=AF=AD=E5=8F=A5=E5=B9=B6=E7=A6=81=E6=AD=A2=20match=20?= =?UTF-8?q?=E5=86=85=20fallthrough?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - vast/Nodes.py:Match 增加 is_switch 字段(含默认值) - vast/Parser.py:新增 _ParseSwitch(case <常量|枚举变体> / default:,desugar 为 Match(is_switch=True));match / struct / fallthrough 改为硬关键字 - vast/Unparser.py:round-trip 忠实还原 switch 与 default: - HandlesMatch / HandlesBody:新增 in_switch_case 门控,fallthrough 只允许出现在 switch 的 case 体内 - HandlesFor:补齐嵌套函数的 saved_loop_break/continue_targets 定义(修 NameError),并隔离 in_switch_case - LLVMCG/BaseGen:新增 in_switch_case 状态 --- lib/core/Handles/HandlesBody.py | 17 ++-- lib/core/Handles/HandlesFor.py | 10 +++ lib/core/Handles/HandlesMatch.py | 102 +++++++++++------------ lib/core/LLVMCG/BaseGen.py | 4 + lib/vast/Nodes.py | 2 +- lib/vast/Parser.py | 137 +++++++++++++++++++++++-------- lib/vast/Unparser.py | 12 ++- 7 files changed, 187 insertions(+), 97 deletions(-) diff --git a/lib/core/Handles/HandlesBody.py b/lib/core/Handles/HandlesBody.py index 07a9550..b9f2fcb 100644 --- a/lib/core/Handles/HandlesBody.py +++ b/lib/core/Handles/HandlesBody.py @@ -19,11 +19,16 @@ class BodyHandle(BaseHandle): Gen._set_node_info(Node) try: if isinstance(Node, ast.Expr): - # `fallthrough`(vast 软关键字已脱糖为 c.NoBreak())是 match 的 - # 标记语句,本身不产出 IR,语义由 HandlesMatch 消费;后续步骤 - # (含 _EmitTempFrees)保持原样执行。 + # `fallthrough`(vast 关键字已脱糖为 c.NoBreak())是 switch 的 + # 标记语句,本身不产出 IR,语义由 HandlesMatch 消费(case 体因此 + # 不自动跳出 switch);合法位置**只有 switch 的 case 体内**, + # match(完全禁止 fallthrough)或 switch 之外一律 fail-fast。 if self._IsNoBreakMarkerStmt(Node): - pass + if not Gen.in_switch_case: + from lib.core.LLVMCG.FuncGen import FailFastError + raise FailFastError( + "fallthrough 只能用在 switch 的 case 体内" + "(match 完全禁止 fallthrough)") else: self.HandleExprLlvm(Node.value) elif isinstance(Node, ast.Return): @@ -42,11 +47,11 @@ class BodyHandle(BaseHandle): self.Trans.WhileHandler._HandleWhileLlvm(Node) elif isinstance(Node, ast.Break): if not Gen.loop_break_targets: - # fail-fast:循环 / match-case 之外的 break 无处可跳, + # fail-fast:循环 / switch / match-case 之外的 break 无处可跳, # 静默忽略只会让"提前退出"变成无声无效果(不兜底) from lib.core.LLVMCG.FuncGen import FailFastError raise FailFastError( - "break 只能用在循环或 match-case 体内(当前没有可跳出的目标)") + "break 只能用在循环或 switch/match 的 case 体内(当前没有可跳出的目标)") Gen.builder.branch(Gen.loop_break_targets[-1]) elif isinstance(Node, ast.Continue): if not Gen.loop_continue_targets: diff --git a/lib/core/Handles/HandlesFor.py b/lib/core/Handles/HandlesFor.py index b9d698c..d7a2dbb 100644 --- a/lib/core/Handles/HandlesFor.py +++ b/lib/core/Handles/HandlesFor.py @@ -403,6 +403,15 @@ class ForHandle(BaseHandle): Gen.nonlocal_params[Node.name] = [(name, _safe_ptr_type(ptr_type)) for name, ptr_type in extra_params] # Push current variables to scope stack before compiling nested function Gen._variable_scope_stack.append(Gen.variables.copy()) + # 嵌套函数体拥有**独立的**跳转目标:进入前暂存外层循环/switch 目标并清空, + # 退出后恢复。否则嵌套函数里的 break/continue/fallthrough 会误跳外层循环 + # 或误认为仍在 switch 的 case 体内。 + saved_loop_break_targets: list = Gen.loop_break_targets + saved_loop_continue_targets: list = Gen.loop_continue_targets + saved_in_switch_case: bool = Gen.in_switch_case + Gen.loop_break_targets = [] + Gen.loop_continue_targets = [] + Gen.in_switch_case = False saved_builder: ir.IRBuilder = Gen.builder saved_func: ir.Function = Gen.func saved_variables: dict[str, ir.Value] = Gen.variables.copy() @@ -419,6 +428,7 @@ class ForHandle(BaseHandle): Gen.func = saved_func Gen.loop_break_targets = saved_loop_break_targets Gen.loop_continue_targets = saved_loop_continue_targets + Gen.in_switch_case = saved_in_switch_case Gen.variables = saved_variables Gen._reg_values = saved_reg_values Gen.global_vars = saved_global_vars diff --git a/lib/core/Handles/HandlesMatch.py b/lib/core/Handles/HandlesMatch.py index ecca046..4deb20e 100644 --- a/lib/core/Handles/HandlesMatch.py +++ b/lib/core/Handles/HandlesMatch.py @@ -37,8 +37,11 @@ class MatchHandle(BaseHandle): def _HandleMatchLlvm(self, Node: ast.Match) -> None: Gen: "Translator.LlvmGen" = self.Trans.LlvmGen # 重名 case 检测(fail-fast):C 的 switch 不允许重复 case 标签——重复标签会让 - # 某个臂永远不可达,且通常意味着漏改的分支;`case _`(default)同样只允许一个。 - # 在产出任何 IR 之前检查,保证报错即终止。 + # 某个臂永远不可达,且通常意味着漏改的分支;通配臂(match 的 `case _` / + # switch 的 `default`)同样只允许一个。 + # 在产出任何 IR 之前检查,保证报错即终止。报文前缀按 is_switch 区分。 + KindLabel: str = 'switch' if Node.is_switch else 'match' + DefaultLabel: str = '`default`' if Node.is_switch else '`case _`' SeenLabels: dict[str, int] = {} DefaultCount: int = 0 for ci, case in enumerate(Node.cases): @@ -50,13 +53,13 @@ class MatchHandle(BaseHandle): if Key in SeenLabels: from lib.core.LLVMCG.FuncGen import FailFastError raise FailFastError( - f"match 出现重名 case:标签 '{Key}' 已在第 {SeenLabels[Key]} 个 case 使用," + f"{KindLabel} 出现重名 case:标签 '{Key}' 已在第 {SeenLabels[Key]} 个 case 使用," f"第 {ci} 个 case 重复(C switch 不允许重复 case 标签)") SeenLabels[Key] = ci if DefaultCount > 1: from lib.core.LLVMCG.FuncGen import FailFastError raise FailFastError( - f"match 出现 {DefaultCount} 个 `case _`(default 只允许一个)") + f"{KindLabel} 出现 {DefaultCount} 个 {DefaultLabel}(通配/default 只允许一个)") SubjectVal: ir.Value | None = self.HandleExprLlvm(Node.subject) if not SubjectVal: return @@ -230,22 +233,7 @@ class MatchHandle(BaseHandle): else: # 变体名未找到,跳过此 case(fast-fail 已在外层处理) pass - def _HasNoBreak(stmts: list[ast.stmt]) -> bool: - for stmt in stmts: - if isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call): - if isinstance(stmt.value.func, ast.Attribute): - if (isinstance(stmt.value.func.value, ast.Name) and - stmt.value.func.value.id == 'c' and - stmt.value.func.attr == 'NoBreak'): - return True - if getattr(stmt, 'body', None) and isinstance(stmt.body, list): - if _HasNoBreak(stmt.body): - return True - if getattr(stmt, 'orelse', None): - if isinstance(stmt.orelse, list) and _HasNoBreak(stmt.orelse): - return True - return False - HasNoBreak.append(_HasNoBreak(case.body) if case.body else False) + HasNoBreak.append(MatchHandle._HasNoBreakMarker(case.body) if case.body else False) if not HasDefault: CaseBBs.append(DefaultBB) SwitchCases: list[tuple[ir.Value, ir.Block]] = [(val, bb) for val, bb in zip(CaseValues, CaseBBs) if bb != DefaultBB] @@ -260,7 +248,7 @@ class MatchHandle(BaseHandle): for j in range(NumSubCases): if CaseIdx < len(CaseBBs) and CaseBBs[CaseIdx] != DefaultBB: Gen.builder.position_at_start(CaseBBs[CaseIdx]) - self._HandleMatchCaseBody(Gen, case.body, AfterBB) + self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch) if not Gen.builder.block.is_terminated: if not HasNoBreak[i]: Gen.builder.branch(AfterBB) @@ -270,7 +258,7 @@ class MatchHandle(BaseHandle): elif isinstance(pattern, (ast.MatchValue, ast.MatchSingleton)): if CaseIdx < len(CaseBBs) and CaseBBs[CaseIdx] != DefaultBB: Gen.builder.position_at_start(CaseBBs[CaseIdx]) - self._HandleMatchCaseBody(Gen, case.body, AfterBB) + self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch) if not Gen.builder.block.is_terminated: if not HasNoBreak[i]: Gen.builder.branch(AfterBB) @@ -280,7 +268,7 @@ class MatchHandle(BaseHandle): elif isinstance(pattern, ast.MatchAs): if pattern.pattern is None: Gen.builder.position_at_start(DefaultBB) - self._HandleMatchCaseBody(Gen, case.body, AfterBB) + self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch) if not Gen.builder.block.is_terminated: if not HasNoBreak[i]: Gen.builder.branch(AfterBB) @@ -289,7 +277,7 @@ class MatchHandle(BaseHandle): CaseIdx += 1 elif isinstance(pattern, ast.MatchSequence): Gen.builder.position_at_start(DefaultBB) - self._HandleMatchCaseBody(Gen, case.body, AfterBB) + self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch) if not Gen.builder.block.is_terminated: if not HasNoBreak[i]: Gen.builder.branch(AfterBB) @@ -301,7 +289,7 @@ class MatchHandle(BaseHandle): # case 收集阶段已创建 CaseBB,此处生成 body if CaseIdx < len(CaseBBs) and CaseBBs[CaseIdx] != DefaultBB: Gen.builder.position_at_start(CaseBBs[CaseIdx]) - self._HandleMatchCaseBody(Gen, case.body, AfterBB) + self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch) if not Gen.builder.block.is_terminated: if not HasNoBreak[i]: Gen.builder.branch(AfterBB) @@ -316,26 +304,51 @@ class MatchHandle(BaseHandle): Gen.builder.branch(AfterBB) Gen.builder.position_at_start(AfterBB) - def _HandleMatchCaseBody(self, Gen: "Translator.LlvmGen", - Body: list[ast.stmt], AfterBB: ir.Block) -> None: - """翻译 match case 体:case 体内的 break 目标是 match 的 AfterBB + @staticmethod + def _HasNoBreakMarker(Stmts: list[ast.stmt]) -> bool: + """case 体(含其嵌套 body/orelse)是否含 `fallthrough` 标记(c.NoBreak())""" + for stmt in Stmts: + if isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call): + if isinstance(stmt.value.func, ast.Attribute): + if (isinstance(stmt.value.func.value, ast.Name) and + stmt.value.func.value.id == 'c' and + stmt.value.func.attr == 'NoBreak'): + return True + NestedBody: Any = getattr(stmt, 'body', None) + if NestedBody and isinstance(NestedBody, list) and MatchHandle._HasNoBreakMarker(NestedBody): + return True + NestedElse: Any = getattr(stmt, 'orelse', None) + if NestedElse and isinstance(NestedElse, list) and MatchHandle._HasNoBreakMarker(NestedElse): + return True + return False - C switch 语义——case 内 break 跳出 **match**(而非外层循环)。故翻译前把 - AfterBB 压入 loop_break_targets,翻译完弹出(外层循环目标不受影响)。 + def _HandleMatchCaseBody(self, Gen: "Translator.LlvmGen", + Body: list[ast.stmt], AfterBB: ir.Block, + IsSwitch: bool) -> None: + """翻译一个 case 体:case 体内的 break 目标是 AfterBB(C switch 语义) + + - case 内 break 跳出 **match/switch**(而非外层循环):翻译前把 AfterBB + 压入 loop_break_targets,翻译完弹出(外层循环目标不受影响)。 + - 同时把 Gen.in_switch_case 置为 IsSwitch(进出恢复):供 `fallthrough` + 标记语句判定合法性 —— 只有 switch 的 case 体内才允许,写进 match + (完全禁止 fallthrough)或 switch 之外由 HandlesBody fail-fast。 """ Targets: list = Gen.loop_break_targets Targets.append(AfterBB) + PrevInSwitch: bool = Gen.in_switch_case + Gen.in_switch_case = IsSwitch try: self.HandleBodyLlvm(Body) finally: + Gen.in_switch_case = PrevInSwitch Targets.pop() @staticmethod def _FallThroughTarget(CaseBBs: list[ir.Block], CaseIdx: int, AfterBB: ir.Block) -> ir.Block: """fallthrough 的落点:下一个 case 的 body(无后继槽位时落到 AfterBB) - `fallthrough`(vast 软关键字,脱糖为 c.NoBreak())表示"本 case 不自动 - break",直接落到下一个 case 的 body(对齐 C 的 [[fallthrough]])。 + `fallthrough`(vast 关键字,脱糖为 c.NoBreak())表示"本 case 不自动 + 跳出 switch",直接落到下一个 case 的 body(对齐 C 的 [[fallthrough]])。 """ NextIdx: int = CaseIdx + 1 if 0 <= NextIdx < len(CaseBBs): @@ -457,22 +470,7 @@ class MatchHandle(BaseHandle): HasDefault = True CaseBBs.append(DefaultBB) CaseBindings.append([]) - def _HasNoBreak(stmts: list[ast.stmt]) -> bool: - for stmt in stmts: - if isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call): - if isinstance(stmt.value.func, ast.Attribute): - if (isinstance(stmt.value.func.value, ast.Name) and - stmt.value.func.value.id == 'c' and - stmt.value.func.attr == 'NoBreak'): - return True - if getattr(stmt, 'body', None) and isinstance(stmt.body, list): - if _HasNoBreak(stmt.body): - return True - if getattr(stmt, 'orelse', None): - if isinstance(stmt.orelse, list) and _HasNoBreak(stmt.orelse): - return True - return False - HasNoBreak.append(_HasNoBreak(case.body) if case.body else False) + HasNoBreak.append(MatchHandle._HasNoBreakMarker(case.body) if case.body else False) if not HasDefault: CaseBBs.append(DefaultBB) SwitchCases: list[tuple[ir.Value, ir.Block]] = [(val, bb) for val, bb in zip(CaseValues, CaseBBs) if bb != DefaultBB] @@ -512,7 +510,7 @@ class MatchHandle(BaseHandle): if isinstance(elem_ptr.type, ir.PointerType) and elem_ptr.type.pointee != member_type: elem_ptr = Gen.builder.bitcast(elem_ptr, ir.PointerType(member_type), name=f"match_{bind_name}_cast") Gen.variables[bind_name] = elem_ptr - self._HandleMatchCaseBody(Gen, case.body, AfterBB) + self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch) for bind_name, _, _, _ in bindings: if bind_name in Gen.variables: del Gen.variables[bind_name] @@ -525,7 +523,7 @@ class MatchHandle(BaseHandle): elif isinstance(pattern, ast.MatchValue): if CaseIdx < len(CaseBBs) and CaseBBs[CaseIdx] != DefaultBB: Gen.builder.position_at_start(CaseBBs[CaseIdx]) - self._HandleMatchCaseBody(Gen, case.body, AfterBB) + self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch) if not Gen.builder.block.is_terminated: if not HasNoBreak[i]: Gen.builder.branch(AfterBB) @@ -535,7 +533,7 @@ class MatchHandle(BaseHandle): elif isinstance(pattern, ast.MatchAs): if pattern.pattern is None: Gen.builder.position_at_start(DefaultBB) - self._HandleMatchCaseBody(Gen, case.body, AfterBB) + self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch) if not Gen.builder.block.is_terminated: if not HasNoBreak[i]: Gen.builder.branch(AfterBB) @@ -544,7 +542,7 @@ class MatchHandle(BaseHandle): CaseIdx += 1 elif isinstance(pattern, ast.MatchSequence): Gen.builder.position_at_start(DefaultBB) - self._HandleMatchCaseBody(Gen, case.body, AfterBB) + self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch) if not Gen.builder.block.is_terminated: if not HasNoBreak[i]: Gen.builder.branch(AfterBB) diff --git a/lib/core/LLVMCG/BaseGen.py b/lib/core/LLVMCG/BaseGen.py index 3876ccc..05e8b95 100644 --- a/lib/core/LLVMCG/BaseGen.py +++ b/lib/core/LLVMCG/BaseGen.py @@ -350,6 +350,10 @@ class BaseGenMixin: self.class_member_annotations: dict[str, dict[str, Any]] = {} self.loop_break_targets: list[ir.Block] = [] self.loop_continue_targets: list[ir.Block] = [] + # 当前是否正处于 switch 的 case 语句体内(True=是)。供 `fallthrough` + # 标记语句(vast 脱糖为 c.NoBreak())判定合法性:只在 switch 的 case 体内 + # 合法;写进 match(完全禁止 fallthrough)或 switch 之外一律 fail-fast。 + self.in_switch_case: bool = False self.eh_jmp_buf_stack: list[ir.Value] = [] self.eh_except_block_stack: list[ir.Block] = [] self.eh_finally_stack: list[Any] = [] diff --git a/lib/vast/Nodes.py b/lib/vast/Nodes.py index 2321921..a8666ea 100644 --- a/lib/vast/Nodes.py +++ b/lib/vast/Nodes.py @@ -77,7 +77,7 @@ _NODE_DEFS: dict[str, tuple[tuple[str, ...], tuple[str, ...]]] = { 'If': (('test', 'body', 'orelse'), _POS_ATTRS), 'With': (('items', 'body', 'type_comment'), _POS_ATTRS), 'AsyncWith': (('items', 'body', 'type_comment'), _POS_ATTRS), - 'Match': (('subject', 'cases'), _POS_ATTRS), + 'Match': (('subject', 'cases', 'is_switch'), _POS_ATTRS), 'Raise': (('exc', 'cause'), _POS_ATTRS), 'Try': (('body', 'handlers', 'orelse', 'finalbody'), _POS_ATTRS), 'TryStar': (('body', 'handlers', 'orelse', 'finalbody'), _POS_ATTRS), diff --git a/lib/vast/Parser.py b/lib/vast/Parser.py index dd34514..c10176e 100644 --- a/lib/vast/Parser.py +++ b/lib/vast/Parser.py @@ -74,17 +74,21 @@ _VIRTUAL_EXAMPLE: str = ( # (HandlesClassDef._resolve_class_flags / _MARKER_BASES)零新增判据。 # - 允许出现在 REnum 内部(REnum 变体本就是嵌套 ClassDef),也允许 struct 内 # 再写 struct(与 class 内写 class 同口径)。 -# 软关键字边界:只在"语句起始 + 后随标识符"时判定,故 `struct = 5` / `x.struct` -# 等作标识符的写法逐字不变。 +# struct 纯数据类型定义(硬关键字):语句起始一律按 struct 定义解析 +# (`struct = 5` / `x.struct` 之类把 struct 当标识符的写法已不再允许)。 _STRUCT_KEYWORD: str = 'struct' _STRUCT_EXAMPLE: str = ( 'struct Point: x: int / struct Point: x: int(换行缩进)y: int / ' 'struct Node(Base): value: int' ) -# fallthrough 软关键字(语句级):仅当整条语句就是 `fallthrough` 时命中, -# 脱糖为 `c.NoBreak()` 标记调用(match 生成侧据此让 case 体不自动 break, -# 落到下一个 case)。作标识符/属性的写法逐字不变。 +# switch 多分支语句(硬关键字,C switch 语义):`switch :` + `case <常量>:` +# / `default:`。默认不穿透,case 体内写 `fallthrough` 才落到下一个 case。 +# 解析时 desugar 为 is_switch=True 的 N.Match。 +_SWITCH_KEYWORD: str = 'switch' + +# fallthrough 关键字(硬):仅在 switch 的 case 体内合法,脱糖为 `c.NoBreak()` +# 标记调用(switch 生成侧据此让 case 体不自动跳出 switch,落到下一个 case)。 _FALLTHROUGH_KEYWORD: str = 'fallthrough' @@ -170,9 +174,8 @@ class Parser(ExprParserMixin): return self._ParseFunctionDef() if v == 'class': return self._ParseClassDef() - # struct 纯数据类型定义(软关键字):仅"语句起始 + 后随标识符"时判定, - # 故 `struct = 5` / `x.struct` 等作标识符的写法逐字不变。 - if v == _STRUCT_KEYWORD and self._Peek(1).Type == NAME: + # struct 纯数据类型定义(硬关键字):语句起始一律按 struct 定义解析。 + if v == _STRUCT_KEYWORD: return self._ParseStructDef() if v == 'if': return self._ParseIf() @@ -184,19 +187,17 @@ class Parser(ExprParserMixin): return self._ParseTry() if v == 'with': return self._ParseWith() + # match 多模式匹配(硬关键字):语句起始一律按 match 语句解析。 if v == 'match': - # 软关键字 match:尝试 match 语句,失败回退为表达式 - save: int = self.Idx - try: - return self._ParseMatch() - except SyntaxError: - self.Idx = save - # fallthrough 软关键字(语句级):仅当整条语句就是 `fallthrough` - # (后随 NEWLINE / ';' / ENDMARKER)时命中,脱糖为 `c.NoBreak()` - # 标记调用,由 match 生成侧消费(case 体因此不自动 break)。 - # 作标识符/属性的写法(fallthrough = 1 / fallthrough(x) / - # d.fallthrough)不命中,逐字不变。 - if v == _FALLTHROUGH_KEYWORD and self._IsFallThroughStmt(): + return self._ParseMatch() + # switch 多分支(硬关键字,C switch 语义):desugar 为 is_switch=True 的 Match。 + if v == _SWITCH_KEYWORD: + return self._ParseSwitch() + # fallthrough 关键字(硬):仅在 switch 的 case 体内合法,脱糖为 + # `c.NoBreak()` 标记调用,由 switch 生成侧消费(case 体因此不自动 + # 跳出 switch)。合法位置由生成侧判定:写进 match 或 switch 之外 + # 一律 FailFastError。 + if v == _FALLTHROUGH_KEYWORD: return self._ParseFallThrough() if v == 'return': return self._ParseReturn() @@ -237,21 +238,14 @@ class Parser(ExprParserMixin): return self._ParseSimpleStmt() # ================================================================== - # fallthrough 软关键字(语句级) + # fallthrough 关键字(语句级) # ================================================================== - def _IsFallThroughStmt(self) -> bool: - """当前语句是否为软关键字 `fallthrough`(后随 NEWLINE / ';' / ENDMARKER) - - 只在"整条语句就是 fallthrough"时命中;`fallthrough = 1`(赋值)、 - `fallthrough(x)`(调用)、`d.fallthrough`(属性)都不命中。 - """ - nxt: Token = self._Peek(1) - if nxt.Type == NEWLINE or nxt.Type == ENDMARKER: - return True - return nxt.Type == OP and nxt.Value == ';' - def _ParseFallThrough(self) -> N.stmt: - """`fallthrough` 脱糖为 `c.NoBreak()`(match 生成侧识别的 fallthrough 标记)""" + """`fallthrough` 脱糖为 `c.NoBreak()`(switch 生成侧识别的 fallthrough 标记) + + 合法位置只有 switch 的 case 体;写进 match 或 switch 之外由生成侧 + (HandlesMatch / HandlesBody)fail-fast。 + """ tok: Token = self._Peek() self._Next() marker: N.Call = N.Call( @@ -1309,7 +1303,7 @@ class Parser(ExprParserMixin): return node # ================================================================== - # match 语句(PEP 634) + # match 语句(PEP 634)与 switch 语句(C switch 语义) # ================================================================== def _ParseMatch(self) -> N.Match: start: Token = self._Next() # match @@ -1333,10 +1327,83 @@ class Parser(ExprParserMixin): cases.append(mc) self._SkipNEWLINE() self._Expect(DEDENT) - node: N.Match = N.Match(subject=subject, cases=cases) + node: N.Match = N.Match(subject=subject, cases=cases, is_switch=False) self._FixLoc(node, start) return node + def _ParseSwitch(self) -> N.Match: + """switch 语句(C switch 语义):脱糖为 is_switch=True 的 N.Match。 + + switch : + case <常量 | 枚举变体>: # 可 `case 1 | 2:` 多值 + ... + default: + ... + + - case 默认不穿透,case 体内写 `fallthrough` 才落到下一个 case; + - case 标签只支持常量字面量 / 枚举变体 / 多值或模式,不接受序列、 + 映射、捕获等结构化模式;不支持 if 守卫。 + """ + start: Token = self._Next() # switch + subject: N.expr = self.ParseExpr() + self._ExpectOp(':') + self._ExpectNEWLINE() + self._Expect(INDENT) + cases: list[N.match_case] = [] + self._SkipNEWLINE() + while True: + if self._AtName('case'): + case_start: Token = self._Next() # case + pattern: N.pattern = self._ParsePattern() + self._CheckSwitchCasePattern(pattern) + if self._AtName('if'): + raise SyntaxError("switch 的 case 不支持 if 守卫") + self._ExpectOp(':') + case_body: list[N.stmt] = self._ParseSuite() + mc: N.match_case = N.match_case(pattern=pattern, guard=None, body=case_body) + self._FixLoc(mc, case_start) + cases.append(mc) + elif self._AtName('default') and self._PeekIsOp(':'): + def_start: Token = self._Next() # default + self._ExpectOp(':') + body: list[N.stmt] = self._ParseSuite() + # default: 等价 `case _`(MatchAs 通配) + wild: N.pattern = N.MatchAs(pattern=None, name=None) + self._FixLoc(wild, def_start) + mc = N.match_case(pattern=wild, guard=None, body=body) + self._FixLoc(mc, def_start) + cases.append(mc) + else: + break + self._SkipNEWLINE() + self._Expect(DEDENT) + node: N.Match = N.Match(subject=subject, cases=cases, is_switch=True) + self._FixLoc(node, start) + return node + + def _PeekIsOp(self, value: str) -> bool: + """下一个 token 是否为运算符 `value`(不消费)""" + nxt: Token = self._Peek(1) + return nxt.Type == OP and nxt.Value == value + + def _CheckSwitchCasePattern(self, pattern: N.pattern) -> None: + """校验 switch 的 case 标签:只允许常量 / 枚举变体 / 多值或模式。 + + 与其他语法错误一致地 fail-fast(SyntaxError 向上冒泡终止编译), + 绝不静默接受结构化模式。 + """ + def _IsAllowed(p: N.pattern) -> bool: + if isinstance(p, (N.MatchValue, N.MatchSingleton, N.MatchClass)): + return True + if isinstance(p, N.MatchOr): + return len(p.patterns) > 0 and all(_IsAllowed(x) for x in p.patterns) + return False + if not _IsAllowed(pattern): + raise SyntaxError( + "switch 的 case 标签非法:只支持常量字面量或枚举变体" + "(可多值 `case 1 | 2:`),不接受序列/映射/捕获等结构化模式。" + "正确示例:case 1: / case \"a\": / case Color.Red: / case Opt.Some():") + def _ParsePattern(self) -> N.pattern: """解析 match 模式(顶层,处理 | 或模式)。""" start: Token = self._Peek() diff --git a/lib/vast/Unparser.py b/lib/vast/Unparser.py index 8e076df..3857b09 100644 --- a/lib/vast/Unparser.py +++ b/lib/vast/Unparser.py @@ -239,12 +239,18 @@ class Unparser: return 'pass' def _Match(self, node: N.Match) -> str: - out: str = f'match {self._expr(node.subject)}:\n' + # switch 语法糖 desugar 为 is_switch=True 的 Match:还原时用 switch + + # `default:`(而非 match + `case _:`),保证 round-trip 忠实。 + Head: str = 'switch' if node.is_switch else 'match' + out: str = f'{Head} {self._expr(node.subject)}:\n' self._Depth += 1 pad: str = ' ' * self._Depth for case in node.cases: - guard: str = f' if {self._expr(case.guard)}' if case.guard else '' - out += f'{pad}case {self._Pattern(case.pattern)}{guard}:\n' + if node.is_switch and isinstance(case.pattern, N.MatchAs) and case.pattern.pattern is None: + out += f'{pad}default:\n' + else: + guard: str = f' if {self._expr(case.guard)}' if case.guard else '' + out += f'{pad}case {self._Pattern(case.pattern)}{guard}:\n' self._Depth += 1 out += self._Block(case.body) self._Depth -= 1