From c27ebc1b698e558550707a227a3941f9a88934b1 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 1 Oct 2026 23:07:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(struct):=20=E6=96=B0=E5=A2=9E=20struct=20?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E5=AD=97=EF=BC=88class=20=E5=90=8C=E5=BD=A2?= =?UTF-8?q?=EF=BC=8C=E7=BA=AF=E6=95=B0=E6=8D=AE=E7=B1=BB=E5=9E=8B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 语义(用户 2026-10-01 定):与 class 同形,但只能表示数据类型: ① 禁止 virtual / novirtual 修饰(struct 天然无虚表); ② 禁止在 struct 内定义 def; ③ 允许在 REnum 内作为变体(与 class 一样); ④ 允许 struct 内再写 struct(与 class 内写 class 同口径,语法层不额外禁止)。 实现(双端同一套表示,零新增下游判据): · tokens.vp:Keyword 枚举**表尾**追加 Struct(既有 kw_subtype 编号逐字不变) + _kw_intern("struct");struct 在 .vp 源码中不作标识符。 · parser.vp:新增 _parse_structdef + _RecordStructErr,复用 class 的 基类/类型参数/类体解析路径,解析期合成 `@t.NoVTable` 伪装饰器并**追加** `t.CStruct` 标记基类(真实父类仍在 bases[0])——与手写 `@t.NoVTable class X(t.CStruct):` 逐字段同形;类体解析后检查直接子节点, 遇 FunctionDef 即 fail-fast。 · parser.vp:_ParseVirtualModified 显式拦截 `virtual struct` / `novirtual struct`。 验证:struct 内 def / virtual struct 均报错;`struct = 5` 仍按普通赋值解析。 --- ast/parser.vp | 163 ++++++++++++++++++++++++++++++++++++++++++++++++-- ast/tokens.vp | 14 ++++- 2 files changed, 169 insertions(+), 8 deletions(-) diff --git a/ast/parser.vp b/ast/parser.vp index 327ccd6..a37422b 100644 --- a/ast/parser.vp +++ b/ast/parser.vp @@ -635,11 +635,20 @@ def _ParseVirtualModified(ps: rawptr[Parser], parent: rawptr[AST]): nxt_s: str = _cur_str(ps) if nxt_s is None: nxt_s = "(none)" - _RecordVirtualErr(f"`{mod_name}` 后随 '{nxt_s}'(非 class/def)", - mod_lineno, mod_col, - f"`{mod_name}` 只在 class/def 前生效;" - f"若此处本意是普通标识符名,请改名" - f"(该词在语句起始会被判为修饰符)。") + if kw2 == Keyword.Struct: + # struct 是纯数据类型:禁止 virtual / novirtual 修饰(用户 2026-10-01 定)。 + # 单独报文(而不是落到通用"后随非 class/def")以便直接指出修法: + # struct 解析期已内置 @t.NoVTable,不需要也无法再显式声明虚/非虚。 + _RecordStructErr(f"`{mod_name} struct` 非法:struct 禁止 virtual/novirtual 修饰", + mod_lineno, mod_col, + "struct 天然无虚表(解析期已合成 @t.NoVTable)," + "请直接写 `struct X:`;需要虚表语义请改用 class。") + else: + _RecordVirtualErr(f"`{mod_name}` 后随 '{nxt_s}'(非 class/def)", + mod_lineno, mod_col, + f"`{mod_name}` 只在 class/def 前生效;" + f"若此处本意是普通标识符名,请改名" + f"(该词在语句起始会被判为修饰符)。") ps.error_count += 1 # 余下 token 交给通用语句分派消费(不制造死循环,也不覆盖首次错误)。 _parse_simple_stmt_into(ps, parent) @@ -2849,7 +2858,8 @@ def _parse_statement_into(ps: rawptr[Parser], parent: rawptr[AST]): parent.append(stmt) return if kw == Keyword.With or _is_funcdef_kw(ps, kw) != 0 or kw == Keyword.Class \ - or kw == Keyword.Enum or kw == Keyword.Union or kw == Keyword.Renum: + or kw == Keyword.Enum or kw == Keyword.Union or kw == Keyword.Renum \ + or kw == Keyword.Struct: stmt = _parse_compound(ps) if stmt is not None: parent.append(stmt) @@ -3294,6 +3304,8 @@ def _parse_compound(ps: rawptr[Parser]) -> rawptr[AST]: return _parse_uniondef(ps) if kw == Keyword.Renum: return _parse_renumdef(ps) + if kw == Keyword.Struct: + return _parse_structdef(ps) if kw == Keyword.Async: _advance(ps) if _is_funcdef_kw(ps, _cur_kw(ps)) != 0: @@ -4035,6 +4047,145 @@ def _parse_renumdef(ps: rawptr[Parser]) -> rawptr[AST]: return node +def _RecordStructErr(detail: str, lineno: int, col: int, extra: str): + """记录 struct 的非法形态(fail-fast;只记首次)。 + + 与 _RecordVirtualErr 同一上报路径(parse_tokens → dump_parse_traceback 补全 + `文件:行:列` 与源码行 + `^` 列标记)。报文一律用 f-string 构建:本语言的 str + 是 i8*,没有 `str + str` 的语义,`"a" + x` 会退化成裸指针 add(非法 IR)。 + """ + 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(1536) + if buf is not None: + stdio.snprintf(buf, 1536, + "[TPV] struct 定义非法(%s)。struct 与 class 同形,但**只能表示数据类型**:" + "① 禁止 virtual / novirtual 修饰(struct 天然无虚表,已内置 @t.NoVTable);" + "② 禁止在 struct 内定义 def(需要方法请改用 class);" + "③ 允许在 REnum 内作为变体、也允许 struct 内再写 struct。" + "正确示例:struct Point: x: int / struct Point: x: int 换行 4 空格 y: int。%s" + "(struct 的约束不会被静默忽略:非法形态一律终止编译)", detail, extra) + _g_parse_err_msg = buf + else: + _g_parse_err_msg = detail + + +def _parse_structdef(ps: rawptr[Parser]) -> rawptr[AST]: + """struct NAME: / struct NAME(Base): 语法糖 → 与 + `@t.NoVTable class NAME(t.CStruct):` 逐字段同形的 ClassDef。 + + 语义(用户 2026-10-01 定):与 class 同形,但只能表示数据类型: + · 强制无虚表:合成伪装饰器 @t.NoVTable(与手写 @t.NoVTable 逐字段同形, + ClassGenBase._has_decorator / TPC _resolve_class_flags 零新增判据)。 + 即使继承自带虚表的父类,本类也不生成虚表(_detect_vtable_status 被 + 类级 NoVTable 关闭),与"C 的 struct 没有虚表"一致。 + · 数据类标记:基类列表**追加** Attribute(Name('t'), 'CStruct'),追加在用户 + 基类**之后**(不移位真实父类的解析顺序)。TPV ClassGenBase._is_marker_base + 与 TPC lib/includes/t.py 的 _MARKER_BASES 都认 CStruct(都不当真实父类), + 两侧对称;于是"纯数据类(无方法无虚表无 __new__)"的模块级变量保持 + **值实例**(TranslatorGenMixin 既有口径)。 + · 禁止类体内定义 def:解析完类体后检查直接子节点,遇 FunctionDef 即 + fail-fast(_RecordStructErr)。嵌套 struct 由它自己的解析负责自检。 + · 禁止 virtual/novirtual struct:见 _ParseVirtualModified。 + 允许出现在 REnum 内部(与 class 一样:REnum 的变体本就是嵌套 ClassDef), + 也允许 struct 内再写 struct —— 与 class 内写 class **完全同口径**(语法层不禁止; + 内层是否发射为独立结构体由类体 ClassDef 子节点的既有路径决定)。 + 行号/列号继承自语句本身,报错回显定位到真实源码行。 + """ + lineno: int = _cur_lineno(ps) + col: int = _cur_col(ps) + _advance(ps) # struct + if _cur_type(ps) != TokenType.Name: + _RecordStructErr("`struct` 后缺少类型名", lineno, col, + "正确写法:struct Point:(类型名紧跟 struct)。") + ps.error_count += 1 + return None + name: str = _cur_str(ps) + _advance(ps) + # 类型参数 [T] / [T, U](与 class 同口径,PEP 695 泛型) + type_params: rawptr[list[str]] = None + if _accept_op(ps, TokOp.Lsqb): + type_params = list[str](ps.pool, 8) + while not _match_op(ps, TokOp.Rsqb): + if _cur_type(ps) == TokenType.EndMarker: break + if _cur_type(ps) == TokenType.Name: + tp_name: str = _cur_str(ps) + _advance(ps) + if _match_op(ps, TokOp.Colon): + _advance(ps) + _parse_test(ps) + type_params.append(tp_name) + if not _accept_op(ps, TokOp.Comma): + break + _expect_op(ps, TokOp.Rsqb) + bases: rawptr[list[rawptr[AST]]] = list[rawptr[AST]](ps.pool, 8) + keywords: rawptr[list[rawptr[AST]]] = list[rawptr[AST]](ps.pool, 8) + # bases / keywords(与 _parse_classdef 同一解析路径,含跨行与 keyword=value) + if _accept_op(ps, TokOp.LPar): + while not _match_op(ps, TokOp.RPar): + _skip_nl(ps) + if _cur_type(ps) == TokenType.EndMarker: break + if _cur_type(ps) == TokenType.Name: + e: rawptr[AST] = _parse_or_test(ps) + if _match_op(ps, TokOp.Equal): + _advance(ps) + v: rawptr[AST] = _parse_test(ps) + kw_node: rawptr[AST] = KwNode(ps.pool, e, v) + keywords.append(kw_node) + else: + bases.append(e) + if not _accept_op(ps, TokOp.Comma): + break + _skip_nl(ps) + _expect_op(ps, TokOp.RPar) + # 数据类标记基类:追加在用户基类之后(真实父类仍在 bases[0],解析顺序不变) + bases.append(Attribute(ps.pool, Name(ps.pool, "t", ASTCtx.Load), + "CStruct", ASTCtx.Load)) + _expect_op(ps, TokOp.Colon) + # 伪装饰器 @t.NoVTable:追加在用户装饰器之后(与 _ParseVirtualModified 同款合并) + dec_list: rawptr[list[rawptr[AST]]] = ps.pending_decorators + ps.pending_decorators = None + marker: rawptr[AST] = Attribute(ps.pool, Name(ps.pool, "t", ASTCtx.Load), + "NoVTable", ASTCtx.Load) + _set_pos(marker, lineno, col, 0, 0) + merged: rawptr[list[rawptr[AST]]] = list[rawptr[AST]](ps.pool, 8) + if merged is not None: + if dec_list is not None: + dn_s: sizet = len(dec_list) + di_s: sizet = 0 + while di_s < dn_s: + merged.append(dec_list.get(di_s)) + di_s += 1 + merged.append(marker) + node: rawptr[ClassDef] = ClassDef(ps.pool, name, bases, keywords, merged, type_params) + # owner_is_class=1:与 class 同口径(类体期间 ps.in_class_body=1) + _parse_suite_into_children(ps, node, 1) + # struct 禁止 def:检查直接子节点 + chk: rawptr[list[rawptr[AST]]] = node.children + if chk is not None: + cn_s: sizet = len(chk) + ci_s: sizet = 0 + while ci_s < cn_s: + it_s: rawptr[AST] = chk.get(ci_s) + if it_s is not None and it_s.kind() == ASTKind.FunctionDef: + fd_s: rawptr[FunctionDef] = (rawptr[FunctionDef])(it_s) + fname_s: str = "(anonymous)" + if fd_s is not None and fd_s.name is not None: + fname_s = fd_s.name + _RecordStructErr(f"struct '{name}' 内定义 def '{fname_s}'", + it_s.lineno, it_s.col_offset, + "struct 只表示数据类型(与 C 的 struct 同口径);" + "若确实需要方法与虚表,请把该类型写成 class。") + ps.error_count += 1 + break + ci_s += 1 + _set_pos(node, lineno, col, 0, 0) + return node + + def _parse_match(ps: rawptr[Parser]) -> rawptr[AST]: """match statement: match subject ':' NewLine Indent case+ Dedent""" lineno: int = _cur_lineno(ps) diff --git a/ast/tokens.vp b/ast/tokens.vp index 43d8738..30c4059 100644 --- a/ast/tokens.vp +++ b/ast/tokens.vp @@ -134,7 +134,14 @@ class Keyword(t.CEnum): # renum 变体类型定义关键字(语法糖:`renum X:` 与 `class X(t.REnum):` 等价, # 见 parser.vp 的 _parse_renumdef)。同样追加在**表尾**:既有关键字编号 # (kw_subtype)逐字不变;全仓 .vp 源码中 `renum` 不作标识符使用。 - Renum: t.State + Renum: t.State + # struct 纯数据类型定义关键字(语法糖:`struct X:` 与 + # `class X(t.CStruct):` + `@t.NoVTable` 等价,见 parser.vp 的 _parse_structdef): + # 与 class 同形,但**只能表示数据类型**——禁止 virtual/novirtual 修饰、禁止类体内 + # 定义 def。同样追加在**表尾**:既有关键字编号(kw_subtype)逐字不变; + # 全仓 .vp 源码中 `struct` 不作标识符使用(仅出现在注释/字符串与 + # build_gep_struct 之类复合标识符里)。 + Struct: t.State # ============================================================ @@ -409,7 +416,10 @@ def _init_keywords(pool: rawptr[memhub.MemManager]): _kw_intern(pool, "novirtual", Keyword.Novirtual) _kw_intern(pool, "union", Keyword.Union) # renum 变体类型定义关键字(语法糖,同 enum/union,见 parser.vp 的 _parse_renumdef) - _kw_intern(pool, "renum", Keyword.Renum) + _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) # ============================================================