feat(ast): 新增 switch 关键字与 switch 语句,match 完全禁止 fallthrough

- 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 已为硬关键字)
This commit is contained in:
TermiNexus committed 2026-10-02 23:15:13 +08:00
1 parent b85f49ab35
commit 2c5b39456b
4 files changed
+181 -74

No files matched your search

+3 -3
View File
@@ -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
+147 -63
View File
@@ -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 <expr>:
# 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
# ============================================================
+19 -7
View File
@@ -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 <expr>:` 语法糖 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
+12 -1
View File
@@ -142,6 +142,14 @@ class Keyword(t.CEnum):
# 全仓 .vp 源码中 `struct` 不作标识符使用(仅出现在注释/字符串与
# build_gep_struct 之类复合标识符里)。
Struct: t.State
# switch 多分支语句关键字(`switch <expr>:` + `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)
# ============================================================