From 2c5b39456b5be5bd08b31009f676aba0dcaed32d Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 2 Oct 2026 23:15:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(ast):=20=E6=96=B0=E5=A2=9E=20switch=20?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E5=AD=97=E4=B8=8E=20switch=20=E8=AF=AD?= =?UTF-8?q?=E5=8F=A5=EF=BC=8Cmatch=20=E5=AE=8C=E5=85=A8=E7=A6=81=E6=AD=A2?= =?UTF-8?q?=20fallthrough?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tokens.vp:Keyword 表尾追加 Switch / FallThrough,并 intern "switch"/"fallthrough"(既有编号不变) - stmts.vp:Match 增加 is_switch 字段(switch 语法糖 desugar 的标记);FallThrough 注释改为仅 switch 合法 - parser.vp:match / fallthrough 改为硬关键字分派(去掉软关键字 lookahead);新增 _parse_switch(case 常量/枚举变体/多值 + default:) - lexer.vp:局部变量 match 改名 Matched(match 已为硬关键字) --- ast/lexer.vp | 6 +- ast/parser.vp | 210 +++++++++++++++++++++++++++++++++++--------------- ast/stmts.vp | 26 +++++-- ast/tokens.vp | 13 +++- 4 files changed, 181 insertions(+), 74 deletions(-) diff --git a/ast/lexer.vp b/ast/lexer.vp index a942993..3fd006c 100644 --- a/ast/lexer.vp +++ b/ast/lexer.vp @@ -1003,14 +1003,14 @@ def _read_operator(lx: rawptr[Lexer]): while cur != None: if cur.length <= int(remaining): # Compare cur.name with src[pos:pos+cur.length] - match: int = 1 + Matched: int = 1 i: sizet = 0 while i < sizet(cur.length): if lx.src[lx.pos + i] != cur.name[i]: - match = 0 + Matched = 0 break i += 1 - if match: + if Matched != 0: if cur.length > best_len: best_len = cur.length best_entry = cur diff --git a/ast/parser.vp b/ast/parser.vp index a5db601..8729e16 100644 --- a/ast/parser.vp +++ b/ast/parser.vp @@ -285,60 +285,6 @@ def _is_export_extern_var(ps: rawptr[Parser]) -> int: return 1 -def _is_match_stmt(ps: rawptr[Parser]) -> int: - """match 是软关键字(CPython 3.10+):lookahead 判断是否为 match 语句 - - match 后直接跟 ':'(变量注解,如 lexer.vp 的 `match: int = 1`)、 - '='(赋值,如 `match = 0`)、换行/文件尾 → 按普通标识符; - 否则扫描至行尾,在括号深度 0 处出现 ':' → match 语句(subject 后必须冒号)。 - case/type 同理为软关键字(case 仅在 match 套件内消费,type 无语句分发)。 - """ - nxt: rawptr[Token] = ps.cur.next - if nxt is None: return 0 - if nxt.type == TokenType.NewLine or nxt.type == TokenType.EndMarker: return 0 - if nxt.type == TokenType.Op: - if nxt.op_subtype == TokOp.Colon: return 0 - if nxt.op_subtype == TokOp.Equal: return 0 - walk: rawptr[Token] = nxt - depth: int = 0 - while walk is not None: - if walk.type == TokenType.NewLine or walk.type == TokenType.EndMarker: - return 0 - if walk.type == TokenType.Op: - wop: int = walk.op_subtype - if wop == TokOp.LPar or wop == TokOp.Lsqb or wop == TokOp.LBrace: - depth += 1 - elif wop == TokOp.RPar or wop == TokOp.Rsqb or wop == TokOp.RBrace: - if depth > 0: - depth -= 1 - elif wop == TokOp.Colon and depth == 0: - return 1 - walk = walk.next - return 0 - - -def _is_fallthrough_kw(ps: rawptr[Parser]) -> int: - """fallthrough 是软关键字(语句级):lookahead 判断整条语句是否为 `fallthrough` - - 仅当当前 Name 恰为 `fallthrough` 且后随 换行 / ';' / 文件尾 时命中。 - 作标识符/属性的写法(`fallthrough = 1` 赋值、`fallthrough(x)` 调用、 - `d.fallthrough` 属性)都不命中,语义不受影响。 - """ - if _cur_type(ps) != TokenType.Name: - return 0 - s: str = _cur_str(ps) - if s is None or string.strcmp(s, "fallthrough") != 0: - return 0 - nxt: rawptr[Token] = ps.cur.next - if nxt is None: - return 0 - if nxt.type == TokenType.NewLine or nxt.type == TokenType.EndMarker: - return 1 - if nxt.type == TokenType.Op and nxt.op_subtype == TokOp.Semi: - return 1 - return 0 - - # 解析错误位置与消息(fail-fast 仿 Python SyntaxError 报告,首次错误记录) _g_parse_err_line: int = 0 _g_parse_err_col: int = 0 @@ -2902,7 +2848,7 @@ def _parse_statement_into(ps: rawptr[Parser], parent: rawptr[AST]): if stmt is not None: parent.append(stmt) return - if (kw == Keyword.Match and _is_match_stmt(ps) != 0) or kw == Keyword.Async: + if kw == Keyword.Match or kw == Keyword.Switch or kw == Keyword.Async: stmt = _parse_compound(ps) if stmt is not None: parent.append(stmt) @@ -2965,12 +2911,10 @@ def _parse_simple_stmt_into(ps: rawptr[Parser], parent: rawptr[AST]): def _parse_small_stmt(ps: rawptr[Parser]) -> rawptr[AST]: """small_stmt: various simple statements""" kw: int = _cur_kw(ps) - # fallthrough 软关键字(语句级):仅当整条 small_stmt 就是 `fallthrough` - # (后随 换行 / ';' / 文件尾)时产出 FallThrough 节点。作标识符的写法 - # (`fallthrough = 1` / `fallthrough(x)` / `d.fallthrough`)不命中, - # 仍走下方通用表达式分派(软关键字边界)。语义由 match 生成侧消费: - # case 体含该标记时不自动 break,落到下一个 case 体。 - if _is_fallthrough_kw(ps) != 0: + # fallthrough 关键字(硬):仅在 switch 的 case 体内合法,语义是"落到下一个 + # case 体"。合法位置由生成侧(StmtGenDeclMixin.translate_fallthrough)判定, + # 在 match 内或 switch 外写出即 fail-fast。 + if kw == Keyword.FallThrough: ft_lineno: int = _cur_lineno(ps) ft_col: int = _cur_col(ps) _advance(ps) @@ -3366,8 +3310,10 @@ def _parse_compound(ps: rawptr[Parser]) -> rawptr[AST]: return _parse_with(ps, 1) ps.error_count += 1 return None - if kw == Keyword.Match and _is_match_stmt(ps) != 0: + if kw == Keyword.Match: return _parse_match(ps) + if kw == Keyword.Switch: + return _parse_switch(ps) ps.error_count += 1 return None @@ -4244,7 +4190,7 @@ def _parse_match(ps: rawptr[Parser]) -> rawptr[AST]: subject: rawptr[AST] = _parse_test(ps) _expect_op(ps, TokOp.Colon) cases: rawptr[list[rawptr[AST]]] = list[rawptr[AST]](ps.pool, 8) - node: rawptr[AST] = Match(ps.pool, subject, cases) + node: rawptr[AST] = Match(ps.pool, subject, cases, 0) # Parsing body: Same NewLine/Indent/Dedent pattern as _parse_suite_into_children, # but parses case clauses into a cases list instead of normal statements into children _skip_nl(ps) @@ -4265,6 +4211,144 @@ def _parse_match(ps: rawptr[Parser]) -> rawptr[AST]: return node +# ============================================================ +# switch statement(C switch 语义,desugar 为 Match(is_switch=1)) +# +# switch : +# case <常量 | 枚举变体>: # 可 `case 1 | 2:` 多值 +# ... +# default: +# ... +# +# 规则与 C switch 一致:case 默认**不穿透**(每个 case 体末尾自动跳出 switch), +# 只有 case 体内写 `fallthrough` 才落到下一个 case。case 标签只支持常量字面量 / +# 枚举变体(不支持序列、映射等结构化模式),与 match 的丰富模式区分开。 +# ============================================================ + +def _is_switch_default_kw(ps: rawptr[Parser]) -> int: + """`default:` 是 switch 套件内的上下文关键字:仅当 `default` 后紧跟 ':' 时命中""" + if _cur_type(ps) != TokenType.Name: + return 0 + s: str = _cur_str(ps) + if s is None or string.strcmp(s, "default") != 0: + return 0 + nxt: rawptr[Token] = ps.cur.next + if nxt is None: + return 0 + if nxt.type == TokenType.Op and nxt.op_subtype == TokOp.Colon: + return 1 + return 0 + + +def _RecordSwitchCaseErr(detail: str, lineno: int, col: int): + """记录 switch 的 case 标签非法(fail-fast;只记首次)""" + global _g_parse_err_line, _g_parse_err_col, _g_parse_err_msg + if _g_parse_err_line != 0: + return + _g_parse_err_line = lineno + _g_parse_err_col = col + buf: str = stdlib.malloc(512) + if buf is not None: + stdio.snprintf(buf, 512, + "switch 的 case 标签非法(%s)。switch 只支持常量字面量或枚举变体标签" + "(可多值 `case 1 | 2:`),不接受序列/映射/捕获等结构化模式。" + "正确示例:case 1: / case \"a\": / case Color.Red: / case Opt.Some():", detail) + _g_parse_err_msg = buf + else: + _g_parse_err_msg = "switch 的 case 标签非法" + + +def _CheckSwitchCasePattern(ps: rawptr[Parser], pat: rawptr[AST]) -> int: + """校验 switch 的 case 标签是否合法(只允许常量 / 枚举变体 / 多值或模式)""" + if pat is None: + return 0 + k: int = pat.kind() + if k == ASTKind.MatchValue or k == ASTKind.MatchSingleton or k == ASTKind.MatchClass: + return 1 + if k == ASTKind.MatchOr: + mo: rawptr[MatchOr] = rawptr[MatchOr](pat) + pats: rawptr[list[rawptr[AST]]] = mo.patterns + if pats is None or len(pats) == 0: + return 0 + i: sizet = 0 + n: sizet = len(pats) + while i < n: + sub: rawptr[AST] = pats.get(i) + if sub is None: + return 0 + sk: int = sub.kind() + if sk != ASTKind.MatchValue and sk != ASTKind.MatchSingleton \ + and sk != ASTKind.MatchClass: + return 0 + i = i + 1 + return 1 + return 0 + + +def _parse_switch_case(ps: rawptr[Parser]) -> rawptr[AST]: + """switch 的 `case <常量|枚举变体> ':' suite`(不支持 guard)""" + lineno: int = _cur_lineno(ps) + col: int = _cur_col(ps) + _advance(ps) # case + pattern: rawptr[AST] = _parse_pattern(ps) + if _CheckSwitchCasePattern(ps, pattern) == 0: + _RecordSwitchCaseErr("case 标签不是常量/枚举变体", lineno, col) + ps.error_count += 1 + if _cur_kw(ps) == Keyword.If: + _RecordSwitchCaseErr("switch 的 case 不支持 if 守卫", _cur_lineno(ps), _cur_col(ps)) + ps.error_count += 1 + _expect_op(ps, TokOp.Colon) + node: rawptr[AST] = MatchCase(ps.pool, pattern, None) + _parse_suite_into_children(ps, node) + _set_pos(node, lineno, col, 0, 0) + return node + + +def _parse_switch_default(ps: rawptr[Parser]) -> rawptr[AST]: + """switch 的 `default ':' suite` —— desugar 为 `case _`(MatchAs 通配)""" + lineno: int = _cur_lineno(ps) + col: int = _cur_col(ps) + _advance(ps) # default + _expect_op(ps, TokOp.Colon) + wild: rawptr[AST] = MatchAs(ps.pool, None, None) + _set_pos(wild, lineno, col, 0, 0) + node: rawptr[AST] = MatchCase(ps.pool, wild, None) + _parse_suite_into_children(ps, node) + _set_pos(node, lineno, col, 0, 0) + return node + + +def _parse_switch(ps: rawptr[Parser]) -> rawptr[AST]: + """switch statement: switch subject ':' NewLine Indent (case|default)+ Dedent""" + lineno: int = _cur_lineno(ps) + col: int = _cur_col(ps) + _advance(ps) # switch + subject: rawptr[AST] = _parse_test(ps) + _expect_op(ps, TokOp.Colon) + cases: rawptr[list[rawptr[AST]]] = list[rawptr[AST]](ps.pool, 8) + node: rawptr[AST] = Match(ps.pool, subject, cases, 1) + _skip_nl(ps) + if _cur_type(ps) == TokenType.NewLine: + _advance(ps) + _skip_nl(ps) + if _cur_type(ps) == TokenType.Indent: + _advance(ps) + while _cur_type(ps) != TokenType.Dedent and _cur_type(ps) != TokenType.EndMarker: + if _cur_kw(ps) == Keyword.Case: + case_node: rawptr[AST] = _parse_switch_case(ps) + if case_node is not None: + cases.append(case_node) + elif _is_switch_default_kw(ps) != 0: + def_node: rawptr[AST] = _parse_switch_default(ps) + if def_node is not None: + cases.append(def_node) + else: + _advance(ps) # Skip non-case token to avoid infinite loop + _expect_type(ps, TokenType.Dedent) + _set_pos(node, lineno, col, 0, 0) + return node + + # ============================================================ # Match pattern parsing # ============================================================ diff --git a/ast/stmts.vp b/ast/stmts.vp index d0d4326..62ff11d 100644 --- a/ast/stmts.vp +++ b/ast/stmts.vp @@ -982,11 +982,11 @@ class Continue(Stmt): # ============================================================ -# FallThrough Node(软关键字 `fallthrough`,仅 match-case 体内合法) +# FallThrough Node(关键字 `fallthrough`,仅 switch 的 case 体内合法) # ============================================================ class FallThrough(Stmt): - """FallThrough() —— case 体不自动 break,落到下一个 case 体""" + """FallThrough() —— switch 的 case 体不自动 break,落到下一个 case 体""" def __new__(self, pool: rawptr[memhub.MemManager]): ptr: rawptr[FallThrough] = pool.alloc(FallThrough.__sizeof__()) @@ -1090,23 +1090,33 @@ class ImportFrom(Stmt): # ============================================================ class Match(Stmt): - """Match(subject: AST, cases: list[rawptr[AST]])""" - subject: rawptr[AST] - cases: rawptr[list[rawptr[AST]]] + """Match(subject: AST, cases: list[rawptr[AST]], is_switch: int) + + is_switch=1 表示本节点由 `switch :` 语法糖 desugar 而来(见 parser.vp 的 + _parse_switch):语义是 C 的 switch —— case 默认不穿透,case 体内写 + `fallthrough` 才落到下一个 case。is_switch=0 为真正的 `match` 语句, + **完全禁止 fallthrough**(写出即 fail-fast)。 + """ + subject: rawptr[AST] + cases: rawptr[list[rawptr[AST]]] + is_switch: int def __new__(self, pool: rawptr[memhub.MemManager], subject: rawptr[AST], - cases: rawptr[list[rawptr[AST]]]): + cases: rawptr[list[rawptr[AST]]], + is_switch: int): ptr: rawptr[Match] = pool.alloc(Match.__sizeof__()) if ptr: string.memset(ptr, 0, Match.__sizeof__()) return ptr def __init__(self, pool: rawptr[memhub.MemManager], subject: rawptr[AST], - cases: rawptr[list[rawptr[AST]]]): + cases: rawptr[list[rawptr[AST]]], + is_switch: int): _init_ast(self, pool, 0, 0) self.subject = subject self.cases = cases + self.is_switch = is_switch _set_parent_list(cases, self) def kind(self) -> int: @@ -1123,5 +1133,7 @@ class Match(Stmt): pos = _emit(buf, size, pos, "None") pos = _emit(buf, size, pos, ", cases=") pos = _dump_list(self.cases, buf, size, pos) + pos = _emit(buf, size, pos, ", is_switch=") + pos = _emit_int(buf, size, pos, int64(self.is_switch)) pos = _emit(buf, size, pos, ")") return pos diff --git a/ast/tokens.vp b/ast/tokens.vp index 30c4059..251639c 100644 --- a/ast/tokens.vp +++ b/ast/tokens.vp @@ -142,6 +142,14 @@ class Keyword(t.CEnum): # 全仓 .vp 源码中 `struct` 不作标识符使用(仅出现在注释/字符串与 # build_gep_struct 之类复合标识符里)。 Struct: t.State + # switch 多分支语句关键字(`switch :` + `case <常量>: ` / `default:`, + # 见 parser.vp 的 _parse_switch)。C switch 语义:默认**不穿透**,case 体内 + # 写 `fallthrough` 才落到下一个 case。同样追加在**表尾**:既有关键字编号 + # (kw_subtype)逐字不变;全仓 .vp 源码中 `switch` 不作标识符使用。 + Switch: t.State + # fallthrough 语句关键字(仅在 switch 的 case 体内合法;match 内使用报错)。 + # 追加在**表尾**:既有关键字编号(kw_subtype)逐字不变。 + FallThrough: t.State # ============================================================ @@ -419,7 +427,10 @@ def _init_keywords(pool: rawptr[memhub.MemManager]): _kw_intern(pool, "renum", Keyword.Renum) # struct 纯数据类型定义关键字(语法糖,同 enum/union/renum,见 parser.vp 的 # _parse_structdef:基类 t.CStruct + 伪装饰器 @t.NoVTable,禁 virtual/novirtual、禁 def) - _kw_intern(pool, "struct", Keyword.Struct) + _kw_intern(pool, "struct", Keyword.Struct) + # switch / fallthrough 关键字(见 parser.vp 的 _parse_switch 与 _parse_small_stmt) + _kw_intern(pool, "switch", Keyword.Switch) + _kw_intern(pool, "fallthrough", Keyword.FallThrough) # ============================================================