diff --git a/App/lib/core/Gen/Expr/ExprGenOps.vp b/App/lib/core/Gen/Expr/ExprGenOps.vp index dbb6869..cba15c4 100644 --- a/App/lib/core/Gen/Expr/ExprGenOps.vp +++ b/App/lib/core/Gen/Expr/ExprGenOps.vp @@ -647,35 +647,51 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase): # 不同级指针(1 vs 2 等)→ 禁止(fast-fail) self.Gen.TypeGenErr.fatal_error(cmp, f"比较类型不匹配: 不同级指针比较 (lhs 层级={lhs_ptr_level}, rhs 层级={rhs_ptr_level})", "TypeError") else: - # None 比较: None 被翻译为 i8* null(一级指针) - # 如果 lhs 是指针,将 rhs (i8* null) bitcast 为 lhs 的指针类型 - # 如果 lhs 是数值,将 rhs 转为数值 0(走正常整数比较) - # 如果 lhs 是结构体值类型,结构体永远不等于 None,返回常量 i1 + # None 比较:把 None 一侧隐式当作「0 / null」,形态由**另一侧**的类型决定 + # 另一侧是指针 → None 转成同类型 null 指针,走指针相等比较(icmp eq/ne) + # 另一侧是整数 → None 转成整数 0,等价 `x == 0` / `x != 0` + # (整数上的 None 隐式为 0,与 TPC 统一) + # 另一侧是结构体值 → 结构体值永不为 None:==/is 恒 false,!=/is not 恒 true + lhs_is_none_lit: int = 0 + if lhs_node is not None and lhs_node.kind() == ast.ASTKind.Constant: + lhs_none_cn: ast.Constant* = ast.Constant*(lhs_node) + if lhs_none_cn is not None and lhs_none_cn.const_kind == ast.CONST_NONE: + lhs_is_none_lit = 1 lhs_ptr_level_none: int = self.get_ptr_level(lhs.Ty) rhs_ptr_level_none: int = self.get_ptr_level(rhs.Ty) - if lhs_ptr_level_none != 0 and rhs_ptr_level_none != 0: - # 两边都是指针,类型可能不同(如 lhs=Struct*, rhs=i8* null) - # 由下方 bitcast 对齐处理,此处不处理 - pass - elif lhs_ptr_level_none != 0 and rhs_ptr_level_none == 0: - # lhs 是指针, rhs 是数值 → 将 rhs 转为同层级 null 指针 - rhs = llvmlite.ConstNull(pool, lhs.Ty, "null") - elif rhs_ptr_level_none != 0 and lhs_ptr_level_none == 0: - # 检查 lhs 是否是结构体值类型(非指针的 Struct) - lhs_is_struct_none: int = 0 - match lhs.Ty: - case llvmlite.LLVMType.Struct(_, _, _): - lhs_is_struct_none = 1 - case _: - lhs_is_struct_none = 0 - if lhs_is_struct_none != 0: - # 结构体值永远不等于 None:==/is 返回 false,!=/is not 返回 true - if op == ast.OpKind.Eq or op == ast.OpKind.Is: - return llvmlite.const_int(pool, 1, 0) - else: # Ne, IsNot + if lhs_is_none_lit != 0: + # None 在左(`None is x` / `None == x`):按右值类型决定 None 的形态 + if rhs_ptr_level_none == 0: + rhs_is_struct_none: int = 0 + match rhs.Ty: + case llvmlite.LLVMType.Struct(_, _, _): + rhs_is_struct_none = 1 + case _: + rhs_is_struct_none = 0 + if rhs_is_struct_none != 0: + if op == ast.OpKind.Eq or op == ast.OpKind.Is: + return llvmlite.const_int(pool, 1, 0) return llvmlite.const_int(pool, 1, 1) - # lhs 是数值, rhs 是 i8* null → 将 rhs 转为数值 0(保持 lhs 不变) - rhs = llvmlite.const_int32(pool, 0) + # 右值是数值 → 把 None 侧转为整数 0(保持 rhs 不变) + lhs = llvmlite.const_int32(pool, 0) + else: + # None 在右(`x is None` / `x == None`):按左值类型决定 None 的形态 + if lhs_ptr_level_none == 0: + # 检查 lhs 是否是结构体值类型(非指针的 Struct) + lhs_is_struct_none: int = 0 + match lhs.Ty: + case llvmlite.LLVMType.Struct(_, _, _): + lhs_is_struct_none = 1 + case _: + lhs_is_struct_none = 0 + if lhs_is_struct_none != 0: + # 结构体值永远不等于 None:==/is 返回 false,!=/is not 返回 true + if op == ast.OpKind.Eq or op == ast.OpKind.Is: + return llvmlite.const_int(pool, 1, 0) + else: # Ne, IsNot + return llvmlite.const_int(pool, 1, 1) + # lhs 是数值, rhs 是 i8* null → 将 rhs 转为数值 0(保持 lhs 不变) + rhs = llvmlite.const_int32(pool, 0) # 类型对齐:icmp 要求两边类型相同 # 仅对数值类型做对齐(指针类型无需对齐,LLVM 允许同层级指针直接 icmp) diff --git a/App/lib/core/Gen/ImportsGenMixin.vp b/App/lib/core/Gen/ImportsGenMixin.vp index e4e7264..834ff78 100644 --- a/App/lib/core/Gen/ImportsGenMixin.vp +++ b/App/lib/core/Gen/ImportsGenMixin.vp @@ -778,8 +778,12 @@ novirtual class ImportsGenMixin: return 1 # t/c 是内部库,不参与 includes 扫描,豁免检查 # ctraits 是纯编译期宏库(.vpi 后缀,IncludesScanner 不扫描 .vpi),豁免检查 + # viperbuild 是编译器内建宏库(无磁盘模块、无 .vpi;配置由 Pass 0 的 + # lib.core.builtin.ViperBuild 从依赖闭包提取),同样豁免检查 if string.strcmp(mod_name, "t") == 0 or string.strcmp(mod_name, "c") == 0 or string.strcmp(mod_name, "ctraits") == 0: return 1 + if string.strcmp(mod_name, "viperbuild") == 0: + return 1 mod_sha1: str = StubMergerStore.LookupSha1ByModName(mod_name) if mod_sha1 is None: # App 兄弟模块豁免:当前文件同目录存在 {mod_name}.vp/.py diff --git a/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp b/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp index a0569fd..ddbb44c 100644 --- a/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp +++ b/App/lib/core/Gen/Stmt/StmtGenCifMixin.vp @@ -848,6 +848,61 @@ novirtual class StmtGenCifMixin: llvmlite.position_at_end(builder, merge_bb) return 0 + # ============================================================ + # 循环 else 子句支持(for-else / while-else) + # + # Python 语义:循环因「条件变假 / 迭代耗尽」正常结束时执行 else; + # 因 break 退出时跳过 else。实现:自然退出边指向 else_bb(无 else 时 + # 仍指向 end_bb),break 恒指向 end_bb;循环收尾在 else_bb 里翻译 orelse + # 后 br end_bb。 + # ============================================================ + def _LoopElseCount(self, orelse_list: list[ast.AST*]*) -> int: + """orelse 非空返回 1(有 else 子句)""" + if orelse_list is None: + return 0 + if len(orelse_list) > 0: + return 1 + return 0 + + def _MakeLoopElseBlock(self, pool: memhub.MemBuddy*, + func: llvmlite.Function*, + name_buf: str, kind: str, cnt: int, + has_else: int) -> rawptr[llvmlite.BasicBlock]: + """has_else=0 返回 None;否则 snprintf(name_buf,32,"%s.else.%d",kind,cnt) 并 create_block""" + if has_else == 0: + return None + if func is None: + return None + viperlib.snprintf(name_buf, 32, "%s.else.%d", kind, cnt) + return llvmlite.create_block(pool, func, name_buf) + + def _EmitLoopElse(self, trans: HT.Translator*, + orelse_list: list[ast.AST*]*, + else_bb: rawptr[llvmlite.BasicBlock], + end_bb: rawptr[llvmlite.BasicBlock]) -> int: + """else_bb 非空:position_at_end(else_bb) → 逐条 translate_stmt(orelse) → + 若当前块未终止则 build_br(end_bb) 并返回 1;否则返回 0。 + else_bb 为空:直接返回 0(调用方自己 position_at_end(end_bb))。 + 翻译 orelse 前须置 trans._ctrl_merge_reachable = 1(对齐 translate_if 的做法)。""" + if else_bb is None: + return 0 + builder: llvmlite.IRBuilder* = trans._cur_builder + if builder is None: + return 0 + # 对齐 translate_if:翻译分支前重置 merge 可达标志,防上层残留值污染 + trans._ctrl_merge_reachable = 1 + llvmlite.position_at_end(builder, else_bb) + if orelse_list is not None: + n: sizet = len(orelse_list) + for i in range(n): + stmt: ast.AST* = orelse_list.get(i) + if stmt is not None: + self.Gen.StmtGen.translate_stmt(trans, stmt) + if llvmlite.builder_cur_block_is_terminated(builder) == 0: + llvmlite.build_br(builder, end_bb) + return 1 + return 0 + # ============================================================ # translate_while - while 循环语句翻译(迁移自 HandlesWhile.WhileHandle.Handle) # @@ -891,6 +946,10 @@ novirtual class StmtGenCifMixin: viperlib.snprintf(name_buf, 32, "while.end.%d", cnt) end_bb: rawptr[llvmlite.BasicBlock] = llvmlite.create_block(pool, func, name_buf) + # 循环 else:自然退出(条件变假)边指向 else_bb(无 else 时仍指向 end_bb) + has_else_while: int = self._LoopElseCount(while_node.orelse) + else_bb_while: rawptr[llvmlite.BasicBlock] = self._MakeLoopElseBlock(pool, func, name_buf, "while", cnt, has_else_while) + # 2. 跳转到 cond 块 llvmlite.build_br(builder, cond_bb) @@ -913,7 +972,7 @@ novirtual class StmtGenCifMixin: else: zero: llvmlite.Value* = llvmlite.const_int32(pool, 0) cond_i1 = llvmlite.build_icmp(builder, llvmlite.ICMP_NE, cond_val, zero) - llvmlite.build_cond_br(builder, cond_i1, body_bb, end_bb) + llvmlite.build_cond_br(builder, cond_i1, body_bb, else_bb_while if else_bb_while is not None else end_bb) # 4. body 块: 翻译循环体,跳回 cond llvmlite.position_at_end(builder, body_bb) @@ -939,16 +998,20 @@ novirtual class StmtGenCifMixin: if llvmlite.builder_cur_block_is_terminated(builder) == 0: llvmlite.build_br(builder, cond_bb) - # 5. 定位到 end 块 + # 5. 循环 else 收尾(自然退出时先执行 orelse),再定位到 end 块 + ret_while: int = self._EmitLoopElse(trans, while_node.orelse, else_bb_while, end_bb) llvmlite.position_at_end(builder, end_bb) - # 追加90:while 的 cond_br 恒引用 end_bb → merge 恒可达(需正常收尾) + # 追加90:有 else 时 endif;无 else 时 while 的 cond_br 恒引用 end_bb + # → merge 恒可达(需正常收尾) trans._ctrl_merge_reachable = 1 # 无限循环(条件常量真 1/True)且循环体无本层 break → 运行时无任何边 # 到达 end 块(merge 结构性不可达)→ 置 0,函数收尾对末句 While 生成 # unreachable(追加90:无 br 到达 merge 时才安全;否则 llc O2 把 # br-to-unreachable 优化为 fall-through → 双分支执行崩溃)。 # 修复 vrandom.gammavariate 的 while True 全 return 被误判"流程掉出末尾"。 - if self._IsConstTrue(while_node.test) != 0 and self._StmtHasBreak(body) == 0: + # [else 子句] 有 else 时 else_bb 会 br 到 end_bb ⇒ merge 结构性可达, + # 不能再置 0(否则 end 块被 unreachable 收尾,与 else 的 br 冲突)。 + if has_else_while == 0 and self._IsConstTrue(while_node.test) != 0 and self._StmtHasBreak(body) == 0: trans._ctrl_merge_reachable = 0 return 0 @@ -1358,6 +1421,10 @@ novirtual class StmtGenCifMixin: viperlib.snprintf(name_buf, 32, "for.end.%d", cnt) end_bb: rawptr[llvmlite.BasicBlock] = llvmlite.create_block(pool, func, name_buf) + # 循环 else:迭代耗尽(i 越界)边指向 else_bb(无 else 时仍指向 end_bb) + has_else_for: int = self._LoopElseCount(for_node.orelse) + else_bb_for: rawptr[llvmlite.BasicBlock] = self._MakeLoopElseBlock(pool, func, name_buf, "for", cnt, has_else_for) + # 7. 跳转到 cond 块 llvmlite.build_br(builder, cond_bb) @@ -1389,7 +1456,7 @@ novirtual class StmtGenCifMixin: else: range_cmp_op = llvmlite.ICMP_ULT cond_i1: llvmlite.Value* = llvmlite.build_icmp(builder, range_cmp_op, cmp_lhs, cmp_rhs) - llvmlite.build_cond_br(builder, cond_i1, body_bb, end_bb) + llvmlite.build_cond_br(builder, cond_i1, body_bb, else_bb_for if else_bb_for is not None else end_bb) # 9. body 块: 翻译循环体,跳到 incr llvmlite.position_at_end(builder, body_bb) @@ -1432,7 +1499,8 @@ novirtual class StmtGenCifMixin: llvmlite.build_store(builder, next_i, var_alloca) llvmlite.build_br(builder, cond_bb) - # 11. 定位到 end 块 + # 11. 循环 else 收尾(迭代耗尽时先执行 orelse),再定位到 end 块 + ret_for: int = self._EmitLoopElse(trans, for_node.orelse, else_bb_for, end_bb) llvmlite.position_at_end(builder, end_bb) # 追加90:for 循环正常结束路径 br 到 end_bb → merge 恒可达(需正常收尾) trans._ctrl_merge_reachable = 1 @@ -1669,6 +1737,10 @@ novirtual class StmtGenCifMixin: viperlib.snprintf(name_buf, 32, "seq.end.%d", cnt) end_bb: rawptr[llvmlite.BasicBlock] = llvmlite.create_block(pool, func, name_buf) + # 循环 else:迭代耗尽(任一源越界/到终止符)边指向 else_bb(无 else 时仍指向 end_bb) + has_else_seq: int = self._LoopElseCount(for_node.orelse) + else_bb_seq: rawptr[llvmlite.BasicBlock] = self._MakeLoopElseBlock(pool, func, name_buf, "seq", cnt, has_else_seq) + llvmlite.build_br(builder, cond_bb) # cond 块: 源 1 done 检查(zip 需源 2 也 done) @@ -1697,7 +1769,7 @@ novirtual class StmtGenCifMixin: elif kind2 == GenTypes.SEQ_KIND_LIST: done2 = llvmlite.build_icmp(builder, llvmlite.ICMP_SLT, cur_idx, l2_count) cont = llvmlite.build_and(builder, done1, done2) - llvmlite.build_cond_br(builder, cont, body_bb, end_bb) + llvmlite.build_cond_br(builder, cont, body_bb, else_bb_seq if else_bb_seq is not None else end_bb) # body 块: 获取元素 → store → 循环体 llvmlite.position_at_end(builder, body_bb) @@ -1772,7 +1844,8 @@ novirtual class StmtGenCifMixin: llvmlite.build_store(builder, next_idx, idx_alloca) llvmlite.build_br(builder, cond_bb) - # 定位到 end 块 + # 循环 else 收尾(迭代耗尽时先执行 orelse),再定位到 end 块 + ret_seq: int = self._EmitLoopElse(trans, for_node.orelse, else_bb_seq, end_bb) llvmlite.position_at_end(builder, end_bb) # 追加90:for 循环正常结束路径 br 到 end_bb → merge 恒可达(需正常收尾) trans._ctrl_merge_reachable = 1 @@ -1873,6 +1946,10 @@ novirtual class StmtGenCifMixin: viperlib.snprintf(name_buf, 32, "ptr.end.%d", cnt) end_bb: rawptr[llvmlite.BasicBlock] = llvmlite.create_block(pool, func, name_buf) + # 循环 else:迭代到终止元素(解引用为空)边指向 else_bb(无 else 时仍指向 end_bb) + has_else_ptr: int = self._LoopElseCount(for_node.orelse) + else_bb_ptr: rawptr[llvmlite.BasicBlock] = self._MakeLoopElseBlock(pool, func, name_buf, "ptr", cnt, has_else_ptr) + # 跳转到 cond llvmlite.build_br(builder, cond_bb) @@ -1895,7 +1972,7 @@ novirtual class StmtGenCifMixin: is_null = llvmlite.build_icmp(builder, llvmlite.ICMP_EQ, cur_elem, null_val) if is_null is None: is_null = llvmlite.const_int(pool, 1, 0) - llvmlite.build_cond_br(builder, is_null, end_bb, body_bb) + llvmlite.build_cond_br(builder, is_null, else_bb_ptr if else_bb_ptr is not None else end_bb, body_bb) # body 块: store elem to var, 翻译循环体 llvmlite.position_at_end(builder, body_bb) @@ -1929,7 +2006,8 @@ novirtual class StmtGenCifMixin: llvmlite.build_store(builder, next_idx, idx_alloca) llvmlite.build_br(builder, cond_bb) - # 定位到 end 块 + # 循环 else 收尾(迭代耗尽时先执行 orelse),再定位到 end 块 + ret_ptr: int = self._EmitLoopElse(trans, for_node.orelse, else_bb_ptr, end_bb) llvmlite.position_at_end(builder, end_bb) # 追加90:for 循环正常结束路径 br 到 end_bb → merge 恒可达(需正常收尾) trans._ctrl_merge_reachable = 1 @@ -2023,6 +2101,10 @@ novirtual class StmtGenCifMixin: viperlib.snprintf(name_buf, 32, "arr.end.%d", cnt) end_bb: rawptr[llvmlite.BasicBlock] = llvmlite.create_block(pool, func, name_buf) + # 循环 else:越界或到终止元素(自然结束)边指向 else_bb(无 else 时仍指向 end_bb) + has_else_arr: int = self._LoopElseCount(for_node.orelse) + else_bb_arr: rawptr[llvmlite.BasicBlock] = self._MakeLoopElseBlock(pool, func, name_buf, "arr", cnt, has_else_arr) + llvmlite.build_br(builder, cond_bb) # cond 块: 先查越界(idx < count,越界时不能 GEP+load 判空) @@ -2030,7 +2112,7 @@ novirtual class StmtGenCifMixin: cur_idx: llvmlite.Value* = llvmlite.build_load(builder, i32_ty, idx_alloca) count_val: llvmlite.Value* = llvmlite.const_int32(pool, elem_count) in_bounds: llvmlite.Value* = llvmlite.build_icmp(builder, llvmlite.ICMP_SLT, cur_idx, count_val) - llvmlite.build_cond_br(builder, in_bounds, elem_check_bb, end_bb) + llvmlite.build_cond_br(builder, in_bounds, elem_check_bb, else_bb_arr if else_bb_arr is not None else end_bb) # elem_check 块: GEP(0, idx) → load elem → 空值判定(NULL 终止) # 用户语义:整数/指针/浮点元素按"解引用为空"提前终止(如 split 尾哨兵 @@ -2055,7 +2137,7 @@ novirtual class StmtGenCifMixin: zero_ai: llvmlite.Value* = llvmlite.const_int(pool, elem_bits_a, 0) is_empty_a = llvmlite.build_icmp(builder, llvmlite.ICMP_EQ, cur_elem, zero_ai) if is_empty_a is not None: - llvmlite.build_cond_br(builder, is_empty_a, end_bb, body_bb) + llvmlite.build_cond_br(builder, is_empty_a, else_bb_arr if else_bb_arr is not None else end_bb, body_bb) else: llvmlite.build_br(builder, body_bb) else: @@ -2091,7 +2173,8 @@ novirtual class StmtGenCifMixin: llvmlite.build_store(builder, next_idx, idx_alloca) llvmlite.build_br(builder, cond_bb) - # 定位到 end 块 + # 循环 else 收尾(迭代耗尽时先执行 orelse),再定位到 end 块 + ret_arr: int = self._EmitLoopElse(trans, for_node.orelse, else_bb_arr, end_bb) llvmlite.position_at_end(builder, end_bb) # 追加90:for 循环正常结束路径 br 到 end_bb → merge 恒可达(需正常收尾) trans._ctrl_merge_reachable = 1 @@ -2262,13 +2345,17 @@ novirtual class StmtGenCifMixin: viperlib.snprintf(name_buf, 32, "lst.end.%d", cnt) end_bb: rawptr[llvmlite.BasicBlock] = llvmlite.create_block(pool, func, name_buf) + # 循环 else:迭代耗尽(idx 越界)边指向 else_bb(无 else 时仍指向 end_bb) + has_else_lst: int = self._LoopElseCount(for_node.orelse) + else_bb_lst: rawptr[llvmlite.BasicBlock] = self._MakeLoopElseBlock(pool, func, name_buf, "lst", cnt, has_else_lst) + llvmlite.build_br(builder, cond_bb) # cond 块: load index, icmp slt idx, count llvmlite.position_at_end(builder, cond_bb) cur_idx: llvmlite.Value* = llvmlite.build_load(builder, i64_ty, idx_alloca) in_bounds: llvmlite.Value* = llvmlite.build_icmp(builder, llvmlite.ICMP_SLT, cur_idx, count_val) - llvmlite.build_cond_br(builder, in_bounds, body_bb, end_bb) + llvmlite.build_cond_br(builder, in_bounds, body_bb, else_bb_lst if else_bb_lst is not None else end_bb) # body 块: __data__ + idx*__elem_size__ → load 元素 → store to var llvmlite.position_at_end(builder, body_bb) @@ -2308,7 +2395,8 @@ novirtual class StmtGenCifMixin: llvmlite.build_store(builder, next_idx, idx_alloca) llvmlite.build_br(builder, cond_bb) - # 定位到 end 块 + # 循环 else 收尾(迭代耗尽时先执行 orelse),再定位到 end 块 + ret_lst: int = self._EmitLoopElse(trans, for_node.orelse, else_bb_lst, end_bb) llvmlite.position_at_end(builder, end_bb) # 追加90:for 循环正常结束路径 br 到 end_bb → merge 恒可达(需正常收尾) trans._ctrl_merge_reachable = 1 @@ -2368,13 +2456,17 @@ novirtual class StmtGenCifMixin: viperlib.snprintf(name_buf, 32, "dict.end.%d", cnt) end_bb: rawptr[llvmlite.BasicBlock] = llvmlite.create_block(pool, func, name_buf) + # 循环 else:迭代耗尽(idx 越界)边指向 else_bb(无 else 时仍指向 end_bb) + has_else_dict: int = self._LoopElseCount(for_node.orelse) + else_bb_dict: rawptr[llvmlite.BasicBlock] = self._MakeLoopElseBlock(pool, func, name_buf, "dict", cnt, has_else_dict) + llvmlite.build_br(builder, cond_bb) # cond 块: load index, icmp slt idx, count llvmlite.position_at_end(builder, cond_bb) cur_idx: llvmlite.Value* = llvmlite.build_load(builder, i64_ty, idx_alloca) in_bounds: llvmlite.Value* = llvmlite.build_icmp(builder, llvmlite.ICMP_SLT, cur_idx, count_val) - llvmlite.build_cond_br(builder, in_bounds, body_bb, end_bb) + llvmlite.build_cond_br(builder, in_bounds, body_bb, else_bb_dict if else_bb_dict is not None else end_bb) # body 块: __keys__ + idx*8 → load key (i8*) → store to var llvmlite.position_at_end(builder, body_bb) @@ -2413,7 +2505,8 @@ novirtual class StmtGenCifMixin: llvmlite.build_store(builder, next_idx, idx_alloca) llvmlite.build_br(builder, cond_bb) - # 定位到 end 块 + # 循环 else 收尾(迭代耗尽时先执行 orelse),再定位到 end 块 + ret_dict: int = self._EmitLoopElse(trans, for_node.orelse, else_bb_dict, end_bb) llvmlite.position_at_end(builder, end_bb) # 追加90:for 循环正常结束路径 br 到 end_bb → merge 恒可达(需正常收尾) trans._ctrl_merge_reachable = 1 @@ -2535,6 +2628,10 @@ novirtual class StmtGenCifMixin: if cond_bb is None or body_bb is None or step_bb is None or end_bb is None: return 0 + # 循环 else:__next__ 返回 stop(迭代耗尽)边指向 else_bb(无 else 时仍指向 end_bb) + has_else_oop: int = self._LoopElseCount(for_node.orelse) + else_bb_oop: rawptr[llvmlite.BasicBlock] = self._MakeLoopElseBlock(pool, func, name_buf, "oop", cnt, has_else_oop) + llvmlite.build_br(builder, cond_bb) # 6. cond 块: 重置 flag → 调 __next__(iter, flag) → 检查 flag 判结束 @@ -2565,7 +2662,7 @@ novirtual class StmtGenCifMixin: continue_i1 = llvmlite.build_icmp(builder, llvmlite.ICMP_EQ, stop_val, flag_zero2) if continue_i1 is None: return 0 - llvmlite.build_cond_br(builder, continue_i1, body_bb, end_bb) + llvmlite.build_cond_br(builder, continue_i1, body_bb, else_bb_oop if else_bb_oop is not None else end_bb) # 7. body 块: store 元素 → 翻译循环体 → br step llvmlite.position_at_end(builder, body_bb) @@ -2587,9 +2684,10 @@ novirtual class StmtGenCifMixin: if llvmlite.builder_cur_block_is_terminated(builder) == 0: llvmlite.build_br(builder, step_bb) - # 8. step 块 → cond;定位到 end 块 + # 8. step 块 → cond;循环 else 收尾(迭代耗尽时先执行 orelse),再定位到 end 块 llvmlite.position_at_end(builder, step_bb) llvmlite.build_br(builder, cond_bb) + ret_oop: int = self._EmitLoopElse(trans, for_node.orelse, else_bb_oop, end_bb) llvmlite.position_at_end(builder, end_bb) # 追加90:for 循环正常结束路径 br 到 end_bb → merge 恒可达(需正常收尾) trans._ctrl_merge_reachable = 1 diff --git a/App/lib/core/Gen/Translator.vp b/App/lib/core/Gen/Translator.vp index a8938b1..3f7278f 100644 --- a/App/lib/core/Gen/Translator.vp +++ b/App/lib/core/Gen/Translator.vp @@ -473,11 +473,15 @@ novirtual class Translator: # --traceback: reset per-module DWARF state (no-op when DI is disabled). DebugInfoGen.BeginModule(mod, GenApi.get_current_file()) - # 设置目标(优先使用 project.vpj 中的配置) + # 设置目标(优先使用 project.vpj / viperbuild 中的配置) triple: str = Config.TargetTriple if triple is None: triple = "x86_64-pc-windows-msvc" llvmlite.module_set_target(mod, triple) + # 数据布局:project.vpj / viperbuild 的 target.datalayout(None 时不输出该行)。 + # 写入 .ll 模块头,使生成的 IR 自带 LLVM 布局信息,与目标 triple 配套。 + if Config.TargetDataLayout is not None: + llvmlite.module_set_datalayout(mod, Config.TargetDataLayout) # 类型 i32_ty: llvmlite.LLVMType* = llvmlite.Int32(pool) @@ -575,7 +579,13 @@ novirtual class Translator: # 依赖签名 coerce)。现场:includes/socket.vp 的 extern def connect 被 # w32.winsock2 的 connect 顶掉 → socket.vp:236 报 # 「指针隐式转指针已禁止:SocketAddr* → WSASocketAddr*」。 - GenApi.register_file_funcs(pool, tree) + GenApi.register_file_funcs(pool, tree) + # viperbuild 宏语句擦除:把模块级 `import viperbuild` 配套的宏语句 + # proj = vb.Project(...) / proj.<字段> = ... / proj.config(...) / proj.build(...) + # 从 tree.children 里摘掉(Pass 0 已在配置面消费;翻译期若留着重放会被当真实 + # 调用 codegen ⇒ `vb.Project` 未定义类)。位置必须在下面 import 处理/变量预注册/ + # translate_children 之前,使三者看到同一份已擦除的 children。 + self._erase_viperbuild_macros(pool, tree) # 先处理导入语句,再创建前向声明,解决前向引用问题。 # 两道门分开: # ① 导入处理 + 自动容器可见性 + 依赖 vpi 注入 —— 全量翻译**与 App 预注册** @@ -651,6 +661,208 @@ novirtual class Translator: GenApi.forward_declare_functions(self, tree) added: int = GenApi.translate_children(self, tree) + # ============================================================ + # _erase_viperbuild_macros - 擦除模块级 viperbuild 宏语句 + # ------------------------------------------------------------ + # viperbuild 是**编译器内建宏库**(编译期配置面),其用法形如: + # import viperbuild as vb + # proj = vb.Project("Test", "1.0.0") + # proj.build(selfmachine=True) + # 这些是**宏语句**:Pass 0(lib.core.builtin.ViperBuild)已从依赖闭包把它们消费成 + # Config 配置;翻译期若原样放行,`vb.Project`/`proj.build` 会被当真实调用 + # codegen(未定义类/函数)⇒ 报错。故此处把整组语句从 tree.children 摘除。 + # 只处理**模块级顶层语句**(配置不允许写在函数体里)。 + # ============================================================ + def _vb_in_proj_vars(self, name: str, proj_vars: list[str]*) -> int: + """name 是否为已登记的 Project 宏变量;1=是""" + if name is None or proj_vars is None: + return 0 + n: sizet = len(proj_vars) + i: sizet = 0 + while i < n: + s: str = proj_vars.get(i) + if s is not None and string.strcmp(s, name) == 0: + return 1 + i += 1 + return 0 + + def _vb_is_project_ctor(self, value: ast.AST*, alias: str, ctor: str) -> int: + """value 是否为 `vb.Project(...)` / `Project(...)`;1=是""" + if value is None or value.kind() != ast.ASTKind.Call: + return 0 + cl: ast.Call* = ast.Call*(value) + if cl is None or cl.func is None: + return 0 + fk: int = cl.func.kind() + if fk == ast.ASTKind.Attribute: + if alias is None: + return 0 + at: ast.Attribute* = ast.Attribute*(cl.func) + if at is None or at.attr is None or at.value is None: + return 0 + if at.value.kind() != ast.ASTKind.Name: + return 0 + bn: ast.Name* = ast.Name*(at.value) + if bn is None or bn.id is None: + return 0 + if string.strcmp(bn.id, alias) != 0: + return 0 + if string.strcmp(at.attr, "Project") == 0: + return 1 + return 0 + if fk == ast.ASTKind.Name: + if ctor is None: + return 0 + nm: ast.Name* = ast.Name*(cl.func) + if nm is None or nm.id is None: + return 0 + if string.strcmp(nm.id, ctor) == 0: + return 1 + return 0 + + def _vb_is_macro_assign(self, target: ast.AST*, value: ast.AST*, + alias: str, ctor: str, + proj_vars: list[str]*) -> int: + """赋值是否为 viperbuild 宏语句(Project 绑定 / 字段赋值);1=是且已登记""" + if target is None or value is None: + return 0 + tk: int = target.kind() + if tk == ast.ASTKind.Name: + nm: ast.Name* = ast.Name*(target) + if nm is None or nm.id is None: + return 0 + if self._vb_is_project_ctor(value, alias, ctor) != 0: + proj_vars.append(nm.id) + return 1 + return 0 + if tk == ast.ASTKind.Attribute: + at: ast.Attribute* = ast.Attribute*(target) + if at is None or at.value is None: + return 0 + if at.value.kind() != ast.ASTKind.Name: + return 0 + bn: ast.Name* = ast.Name*(at.value) + if bn is None or bn.id is None: + return 0 + if self._vb_in_proj_vars(bn.id, proj_vars) != 0: + return 1 + return 0 + + def _vb_is_macro_call(self, value: ast.AST*, proj_vars: list[str]*) -> int: + """表达式是否为 `proj.config(...)` / `proj.build(...)`;1=是""" + if value is None or value.kind() != ast.ASTKind.Call: + return 0 + cl: ast.Call* = ast.Call*(value) + if cl is None or cl.func is None: + return 0 + if cl.func.kind() != ast.ASTKind.Attribute: + return 0 + at: ast.Attribute* = ast.Attribute*(cl.func) + if at is None or at.attr is None or at.value is None: + return 0 + if at.value.kind() != ast.ASTKind.Name: + return 0 + bn: ast.Name* = ast.Name*(at.value) + if bn is None or bn.id is None: + return 0 + if self._vb_in_proj_vars(bn.id, proj_vars) == 0: + return 0 + if string.strcmp(at.attr, "config") == 0: + return 1 + if string.strcmp(at.attr, "build") == 0: + return 1 + return 0 + + def _vb_is_macro_stmt(self, child: ast.AST*, alias: str, ctor: str, + proj_vars: list[str]*) -> int: + """单条模块级语句是否为 viperbuild 宏语句;1=是(应擦除)""" + if child is None: + return 0 + kd: int = child.kind() + if kd == ast.ASTKind.Assign: + a: ast.Assign* = ast.Assign*(child) + if a is None or a.targets is None or a.value is None: + return 0 + if len(a.targets) != 1: + return 0 + return self._vb_is_macro_assign(a.targets.get(0), a.value, alias, ctor, proj_vars) + if kd == ast.ASTKind.AnnAssign: + an: ast.AnnAssign* = ast.AnnAssign*(child) + if an is None or an.target is None or an.value is None: + return 0 + return self._vb_is_macro_assign(an.target, an.value, alias, ctor, proj_vars) + if kd == ast.ASTKind.Expr: + ex: ast.Expr* = ast.Expr*(child) + if ex is None or ex.value is None: + return 0 + return self._vb_is_macro_call(ex.value, proj_vars) + return 0 + + def _erase_viperbuild_macros(self, pool: memhub.MemBuddy*, tree: ast.AST*) -> int: + """擦除模块级 viperbuild 宏语句(就地替换 tree.children);1=发生擦除""" + if tree is None or tree.children is None: + return 0 + ch: list[ast.AST*]* = tree.children + cn: sizet = len(ch) + if cn == 0: + return 0 + # ① 找 viperbuild 的本地名 / Project 构造名 + alias: str = None + ctor: str = None + ci: sizet = 0 + while ci < cn: + child: ast.AST* = ch.get(ci) + if child is not None: + kd: int = child.kind() + if kd == ast.ASTKind.Import: + names: list[ast.AST*]* = ast.Import*(child).names + if names is not None: + ni: sizet = 0 + while ni < len(names): + al: ast.Alias* = ast.Alias*(names.get(ni)) + if al is not None and al.name is not None: + if string.strcmp(al.name, "viperbuild") == 0: + if al.asname is not None: + alias = al.asname + else: + alias = "viperbuild" + ni += 1 + elif kd == ast.ASTKind.ImportFrom: + ifr: ast.ImportFrom* = ast.ImportFrom*(child) + if ifr is not None and ifr.level == 0 and ifr.names is not None: + if ifr.module is not None and string.strcmp(ifr.module, "viperbuild") == 0: + ni: sizet = 0 + while ni < len(ifr.names): + al: ast.Alias* = ast.Alias*(ifr.names.get(ni)) + if al is not None and al.name is not None: + if string.strcmp(al.name, "Project") == 0: + if al.asname is not None: + ctor = al.asname + else: + ctor = "Project" + ni += 1 + ci += 1 + if alias is None and ctor is None: + return 0 + # ② 过滤:先登记 Project 宏变量,再摘掉相关语句 + proj_vars: list[str]* = list[str](pool, 8) + kept: list[ast.AST*]* = list[ast.AST*](pool, 8) + if proj_vars is None or kept is None: + return 0 + erased: int = 0 + ci = 0 + while ci < cn: + child = ch.get(ci) + if child is not None: + if self._vb_is_macro_stmt(child, alias, ctor, proj_vars) != 0: + erased = 1 + else: + kept.append(child) + ci += 1 + if erased != 0: + tree.children = kept + return erased + # 容器模块自动可见性注入(对齐 TPC _inject_auto_imports) # TPC 在翻译前自动注入 `import _list / import _dict (+json)`,使 list/dict 内建容器类 # 在符号表中可见;TPV 用可见性机制等效替代:_list.vp/_dict.vp 已由 Phase1 注册, diff --git a/App/lib/core/builtin/ViperBuild.vp b/App/lib/core/builtin/ViperBuild.vp new file mode 100644 index 0000000..9e9827b --- /dev/null +++ b/App/lib/core/builtin/ViperBuild.vp @@ -0,0 +1,1425 @@ +"""ViperBuild —— `viperbuild` 内建库的编译器侧实现(`--src` Pass 0 配置发现) + +定位: + `viperbuild` 是编译器内建的**编译期宏库**(与 `t`/`c`/`ctraits` 同级), + 不产生任何运行时代码。编译器在入口文件的**依赖闭包**内识别 + `import viperbuild as vb` / `from viperbuild import Project`,并据此提取 + 构建配置(源目录 / 构建根目录 / 编译器 / 链接器 / 目标三元组等)。 + + 本模块只负责「配置面」: + ① BFS 展开入口文件的依赖闭包(工程本地模块;解析不到的模块视为 stdlib 跳过); + ② 解析每个文件顶层语句,提取 `vb.Project(...)` / `proj.<字段>=...` / + `proj.config(...)` / `proj.build(...)`; + ③ 常量求值:字符串字面量、名字引用(`define`/模块级常量)、`+` 拼接、 + `list[str]` 字面量与 `*展开`; + ④ 填充默认值并写回 `Config` 全局字段; + ⑤ 全程 fail-fast:没找到 / 找到但坏,一律报错终止,绝不静默兜底。 + +约束(与 v5 设计一致): + · 一个依赖闭包内**只允许一个** Project 对象,出现第二个即 fatal。 + · Project 是宏类型,**禁止读取**(当右值用即 fatal)。 + · `source` 缺省 = 入口文件所在目录;显式指定时入口文件必须位于其下。 + · `buildroot` 缺省 = `source + "/.tpv_build"`。 + · `includes` 缺省 = 从 exe 所在目录向上 0..3 层探测 `includes` 目录,命中即用。 + · `build()` 显式 triple/datalayout 与 `selfmachine=True` 互斥(同时给 fatal); + 二者都未给时等价 `selfmachine=True`。 + +内存纪律: + · AST / lexer / token 等瞬时数据从主 MemBuddy 池分配,**每文件 `reset_to(mark)`**; + · BFS 工作集(worklist / visited / 模块名 / 常量表)从本模块私有 scratch 池分配, + 不受 `reset_to` 影响; + · 最终写回 `Config` 的字符串一律 `stdlib.malloc` 深拷贝,跨池存活。 +""" +import t, c +from stdint import * +from w32.win32base import * +import memhub +import string +import stdio +import stdlib +import viperlib +import ast +import w32.win32file as win32file +import lib.Projectrans.Utils as Utils +import lib.Projectrans.Config as Config +import lib.core.VLogger as VLogger +import lib.core.IncludesScanner as IncludesScanner + +# kernel32!GetEnvironmentVariableA —— 本模块自声明(与 includes/traceback.vp 同范式; +# win32base.vp 未声明它,kernel32 反正会链上),仅用于 selfmachine 运行时探测本机架构。 +# DWORD GetEnvironmentVariableA(LPCSTR lpName, LPSTR lpBuffer, DWORD nSize); +extern def GetEnvironmentVariableA(lpName: LPCSTR, lpBuffer: CHARPTR, nSize: DWORD) -> DWORD + +# 路径/源码缓冲上限(与 main.vp 的 SRC_BUF_SIZE 保持一致口径) +define VB_PATH_CAP = 1024 +define VB_SRC_CAP = 1048576 +# scratch 池大小(BFS 工作集 + 常量表;不参与主池 reset) +define VB_SCRATCH = 8388608 +# visited 上限(防御成环,闭包规模远超即 fatal) +define VB_MAX_VISIT = 4096 +# 列表常量的元素分隔符:换行符的**字符码**(源码路径 / 编译 flag 均不含换行)。 +# 注意必须存字符码再 char() 转换——Viper 中 'c' 字面量是**单字符字符串**(i8*), +# 直接 define 成 '\n' 会得到 i8*,赋给 char 槽位触发"指针隐式转整数已禁止"。 +define VB_SEP_CODE = 10 + +# ============================================================ +# 模块状态 +# ============================================================ +_scratch: memhub.MemBuddy* = None +_entry_dir: str = None # 入口文件所在目录(source 缺省值;裸文件名时规整成 ".") +_search_dirs: str = None # 闭包搜索根(空格分隔):入口目录 优先,其次当前目录 "." +_work: list[str]* = None # BFS 待处理文件路径 +_visited: list[str]* = None # 已处理文件路径 +_pending: list[str]* = None # 单文件收集到的 import 模块名(BFS 待解析) +_proj_vars: list[str]* = None # 绑定了 vb.Project(...) 实例的变量名 +_alias: str = None # viperbuild 的本地别名(import viperbuild as vb → "vb") +_ctor_name: str = None # from viperbuild import Project as P → "P" + +# 单文件常量表(仅在该文件扫描期间有效,池分配) +_f_str_names: list[str]* = None +_f_str_vals: list[str]* = None +_f_list_names: list[str]* = None +_f_list_vals: list[str]* = None + +# 常量求值输出槽 +_g_out_str: str = None + +# 提取出的配置(字符串一律 stdlib.malloc) +_c_name: str = None +_c_version: str = None +_c_source: str = None +_c_buildroot: str = None +_c_includes: str = None +_c_compiler: str = None +_c_linker: str = None +_c_static_libs: str = None +_c_dynamic_libs: str = None +_c_output: str = None +_c_triple: str = None +_c_datalayout: str = None +_c_selfmachine: int = 0 +_c_has_project: int = 0 +_c_has_build: int = 0 + +# 闭包内是否出现过 `import viperbuild` / `from viperbuild import ...`(仅用于诊断区分) +_saw_viperbuild: int = 0 + +# 是否已成功应用(供 main.vp 判断用 viperbuild 的 linker.output) +_applied: int = 0 + + +# ============================================================ +# 基础工具 +# ============================================================ +def _DupStd(s: str) -> str: + """stdlib.malloc 深拷贝(跨 reset_to 存活)""" + if s is None: + return None + n: sizet = string.strlen(s) + buf: str = stdlib.malloc(n + 1) + if buf is None: + return None + string.strcpy(buf, s) + return buf + + +def _DupScratch(s: str) -> str: + """scratch 池深拷贝(不受主池 reset_to 影响)""" + if s is None or _scratch is None: + return None + n: sizet = string.strlen(s) + buf: str = _scratch.alloc(n + 1) + if buf is None: + return None + string.strcpy(buf, s) + return buf + + +def _Join2(a: str, b: str) -> str: + """a + "/" + b(a 为空返回 b)""" + if a is None: + return b + if b is None: + return a + al: sizet = string.strlen(a) + if al == 0: + return b + bl: sizet = string.strlen(b) + buf: str = stdlib.malloc(al + 1 + bl + 1) + if buf is None: + return None + string.strcpy(buf, a) + buf[al] = '/' + string.strcpy(buf + al + 1, b) + return buf + + +def _DirOf(path: str) -> str: + """返回 path 的目录(不含结尾分隔符);无分隔符返回 stdlib 分配的 """"" + if path is None: + return None + n: sizet = string.strlen(path) + i: sizet = n + while i > 0: + i -= 1 + ch: char = path[i] + if ch == '/' or ch == '\\': + buf: str = stdlib.malloc(i + 1) + if buf is None: + return None + string.strncpy(buf, path, i) + buf[i] = '\0' + return buf + return _DupStd("") + + +def _BaseNameOf(path: str) -> str: + """返回 path 的文件名(含扩展名)""" + if path is None: + return None + n: sizet = string.strlen(path) + start: sizet = 0 + i: sizet = 0 + while i < n: + ch: char = path[i] + if ch == '/' or ch == '\\': + start = i + 1 + i += 1 + return _DupStd(path + start) + + +def _IsDir(path: str) -> int: + """路径是否为已存在目录(1=是)""" + if path is None: + return 0 + attr: ULONG = win32file.GetFileAttributesA(path) + if attr == 0xFFFFFFFF: + return 0 + if (attr & win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: + return 1 + return 0 + + +def _HasPrefix(s: str, prefix: str) -> int: + """s 是否以 prefix 开头(1=是;prefix 为 None/空/"." 视为真) + + 两侧各自剥掉一个前导 "./",避免 `--src App/main.vp` 与 `proj.source="./App"` + 因写法差异被判成"入口不在 source 之下";prefix 为 "." 时(入口是裸文件名, + source 规整成当前目录)恒真。 + """ + if s is None: + return 0 + if prefix is None: + return 1 + if string.strcmp(prefix, ".") == 0: + return 1 + if string.strncmp(s, "./", 2) == 0: + s = s + 2 + if string.strncmp(prefix, "./", 2) == 0: + prefix = prefix + 2 + pl: sizet = string.strlen(prefix) + if pl == 0: + return 1 + sl: sizet = string.strlen(s) + if sl < pl: + return 0 + if string.strncmp(s, prefix, pl) == 0: + return 1 + return 0 + + +# ============================================================ +# 常量表 +# ============================================================ +def _FindInList(names: list[str]*, name: str) -> int: + """在 names 中线性查找 name,返回下标;未找到返回 -1""" + if names is None or name is None: + return -1 + n: sizet = names.__len__() + i: sizet = 0 + while i < n: + s: str = names.get(i) + if s is not None: + if string.strcmp(s, name) == 0: + return int(i) + i += 1 + return -1 + + +def _LookupStrConst(name: str) -> int: + """名字 → 字符串常量(写 _g_out_str);0=失败""" + global _g_out_str + idx: int = _FindInList(_f_str_names, name) + if idx < 0: + return 0 + v: str = _f_str_vals.get(sizet(idx)) + if v is None: + return 0 + _g_out_str = v + return 1 + + +def _LookupListConst(name: str, out: list[str]*) -> int: + """名字 → 列表常量(按 VB_SEP 拆分后写入 out);0=失败""" + idx: int = _FindInList(_f_list_names, name) + if idx < 0: + return 0 + v: str = _f_list_vals.get(sizet(idx)) + if v is None: + return 0 + return _SplitInto(out, v) + + +def _SplitInto(out: list[str]*, joined: str) -> int: + """把 VB_SEP_CODE 连接的串拆成元素写入 out(空串表示空列表)""" + if out is None or joined is None: + return 0 + n: sizet = string.strlen(joined) + if n == 0: + return 1 + start: sizet = 0 + i: sizet = 0 + while i <= n: + if i == n or joined[i] == char(VB_SEP_CODE): + seg: str = stdlib.malloc(i - start + 1) + if seg is None: + return 0 + string.strncpy(seg, joined + start, i - start) + seg[i - start] = '\0' + out.append(_DupScratch(seg)) + stdlib.free(seg) + start = i + 1 + i += 1 + return 1 + + +def _JoinSep(lst: list[str]*) -> str: + """list[str] → VB_SEP_CODE 连接串(池分配;空列表返回 None)""" + if lst is None: + return None + n: sizet = lst.__len__() + if n == 0: + return None + # 先测总长 + total: sizet = 0 + i: sizet = 0 + while i < n: + s: str = lst.get(i) + if s is not None: + total += string.strlen(s) + 1 + i += 1 + if total == 0: + return None + buf: str = _scratch.alloc(total + 1) + if buf is None: + return None + pos: sizet = 0 + i = 0 + while i < n: + s = lst.get(i) + if s is not None: + sl: sizet = string.strlen(s) + if pos > 0: + buf[pos] = char(VB_SEP_CODE) + pos += 1 + string.strcpy(buf + pos, s) + pos += sl + i += 1 + buf[pos] = '\0' + return buf + + +def _JoinSpace(lst: list[str]*) -> str: + """list[str] → 空格连接串(stdlib 分配;空列表返回 None)""" + if lst is None: + return None + n: sizet = lst.__len__() + if n == 0: + return None + total: sizet = 0 + i: sizet = 0 + while i < n: + s: str = lst.get(i) + if s is not None: + total += string.strlen(s) + 1 + i += 1 + if total == 0: + return None + buf: str = stdlib.malloc(total + 1) + if buf is None: + return None + pos: sizet = 0 + i = 0 + while i < n: + s = lst.get(i) + if s is not None: + sl: sizet = string.strlen(s) + if pos > 0: + buf[pos] = ' ' + pos += 1 + string.strcpy(buf + pos, s) + pos += sl + i += 1 + buf[pos] = '\0' + return buf + + +def _FirstSpaceTok(joined: str) -> str: + """取空格分隔串的首个 token(stdlib 分配);空串/None 返回 None""" + if joined is None: + return None + n: sizet = string.strlen(joined) + if n == 0: + return None + sp: sizet = n + i: sizet = 0 + while i < n: + if joined[i] == ' ': + sp = i + break + i += 1 + seg: str = stdlib.malloc(sp + 1) + if seg is None: + return None + string.strncpy(seg, joined, sp) + seg[sp] = '\0' + return seg + + +# ============================================================ +# 常量表达式求值 +# ============================================================ +def _EvalStr(node: ast.AST*) -> int: + """字符串常量表达式求值(结果写 _g_out_str);0=失败 + + 支持:字符串字面量、名字引用(同文件先定义的 str 常量 / define 常量)、 + `+` 拼接(左/右递归)。 + """ + global _g_out_str + if node is None: + return 0 + k: int = node.kind() + if k == ast.ASTKind.Constant: + cn: ast.Constant* = ast.Constant*(node) + if cn is None or cn.const_kind != ast.CONST_STR: + return 0 + _g_out_str = cn.str_val + return 1 + if k == ast.ASTKind.Name: + nm: ast.Name* = ast.Name*(node) + if nm is None or nm.id is None: + return 0 + return _LookupStrConst(nm.id) + if k == ast.ASTKind.BinOp: + bop: ast.BinOp* = ast.BinOp*(node) + if bop is None or bop.op != ast.OpKind.Add: + return 0 + if _EvalStr(bop.left) == 0: + return 0 + lv: str = _g_out_str + if _EvalStr(bop.right) == 0: + return 0 + rv: str = _g_out_str + _g_out_str = _ConcatStd(lv, rv) + return 1 + return 0 + + +def _ConcatStd(a: str, b: str) -> str: + """字符串拼接(stdlib 分配)""" + if a is None: + return b + if b is None: + return a + al: sizet = string.strlen(a) + bl: sizet = string.strlen(b) + buf: str = stdlib.malloc(al + bl + 1) + if buf is None: + return None + string.strcpy(buf, a) + string.strcpy(buf + al, b) + return buf + + +def _EvalList(node: ast.AST*, out: list[str]*) -> int: + """list[str] 常量表达式求值(元素写入 out);0=失败 + + 支持:list 字面量(元素为 str 常量表达式)、`*名字` 展开(引用 list 常量)、 + `+` 拼接(左/右递归)。 + """ + if node is None or out is None: + return 0 + k: int = node.kind() + if k == ast.ASTKind.Name: + nm: ast.Name* = ast.Name*(node) + if nm is None or nm.id is None: + return 0 + return _LookupListConst(nm.id, out) + if k == ast.ASTKind.List: + lst: ast.List* = ast.List*(node) + if lst is None or lst.elts is None: + return 0 + n: sizet = lst.elts.__len__() + i: sizet = 0 + while i < n: + e: ast.AST* = lst.elts.get(i) + if e is None: + return 0 + if e.kind() == ast.ASTKind.Starred: + st: ast.Starred* = ast.Starred*(e) + if st is None or st.value is None: + return 0 + if _EvalList(st.value, out) == 0: + return 0 + else: + if _EvalStr(e) == 0: + return 0 + out.append(_DupScratch(_g_out_str)) + i += 1 + return 1 + if k == ast.ASTKind.BinOp: + bop: ast.BinOp* = ast.BinOp*(node) + if bop is None or bop.op != ast.OpKind.Add: + return 0 + if _EvalList(bop.left, out) == 0: + return 0 + if _EvalList(bop.right, out) == 0: + return 0 + return 1 + return 0 + + +def _EvalBool(node: ast.AST*) -> int: + """布尔常量求值;-1=非布尔常量""" + if node is None: + return -1 + if node.kind() != ast.ASTKind.Constant: + return -1 + cn: ast.Constant* = ast.Constant*(node) + if cn is None or cn.const_kind != ast.CONST_BOOL: + return -1 + if cn.int_val != 0: + return 1 + return 0 + + +# ============================================================ +# 宏引用检测(禁止读取) +# ============================================================ +def _IsProjRef(node: ast.AST*) -> int: + """节点是否直接引用 Project 宏对象(Name 或 Attribute 的 base);1=是""" + if node is None: + return 0 + k: int = node.kind() + if k == ast.ASTKind.Name: + nm: ast.Name* = ast.Name*(node) + if nm is None or nm.id is None: + return 0 + if _FindInList(_proj_vars, nm.id) >= 0: + return 1 + return 0 + if k == ast.ASTKind.Attribute: + at: ast.Attribute* = ast.Attribute*(node) + if at is None or at.value is None: + return 0 + return _IsProjRef(at.value) + return 0 + + +# ============================================================ +# 单文件扫描 +# ============================================================ +def _ResetFileTables(pool: memhub.MemBuddy*) -> int: + """重建单文件常量表(池分配)""" + global _f_str_names, _f_str_vals, _f_list_names, _f_list_vals + _f_str_names = list[str](pool, 8) + _f_str_vals = list[str](pool, 8) + _f_list_names = list[str](pool, 8) + _f_list_vals = list[str](pool, 8) + if _f_str_names is None or _f_str_vals is None: + return 1 + if _f_list_names is None or _f_list_vals is None: + return 1 + return 0 + + +def _AddPending(mod_name: str) -> None: + """把待解析模块名记入 BFS 队列(scratch 池深拷贝)""" + if mod_name is None or _pending is None: + return + if string.strlen(mod_name) == 0: + return + _pending.append(_DupScratch(mod_name)) + + +def _HandleImport(stmt: ast.Import*) -> int: + """处理 `import X [as Y]`:识别 viperbuild,其余模块登记入 BFS 队列""" + global _alias, _saw_viperbuild + if stmt is None or stmt.names is None: + return 0 + n: sizet = stmt.names.__len__() + i: sizet = 0 + while i < n: + al: ast.Alias* = ast.Alias*(stmt.names.get(i)) + if al is not None and al.name is not None: + if string.strcmp(al.name, "viperbuild") == 0: + _saw_viperbuild = 1 + if al.asname is not None: + _alias = _DupScratch(al.asname) + else: + _alias = _DupScratch("viperbuild") + else: + _AddPending(al.name) + i += 1 + return 0 + + +def _HandleImportFrom(stmt: ast.ImportFrom*) -> int: + """处理 `from X import Y`:识别 `from viperbuild import Project`,其余登记 BFS""" + global _ctor_name, _saw_viperbuild + if stmt is None: + return 0 + if stmt.level != 0: + # 相对导入不参与工程本地闭包展开(无包上下文,跳过) + return 0 + if stmt.module is None: + return 0 + if string.strcmp(stmt.module, "viperbuild") != 0: + _AddPending(stmt.module) + return 0 + _saw_viperbuild = 1 + if stmt.names is None: + return 0 + n: sizet = stmt.names.__len__() + i: sizet = 0 + while i < n: + al: ast.Alias* = ast.Alias*(stmt.names.get(i)) + if al is not None and al.name is not None: + if string.strcmp(al.name, "Project") == 0: + if al.asname is not None: + _ctor_name = _DupScratch(al.asname) + else: + _ctor_name = _DupScratch("Project") + i += 1 + return 0 + + +def _IsProjectCtor(vnode: ast.AST*) -> int: + """判断表达式是否为 `vb.Project(...)` / `Project(...)`;1=是""" + if vnode is None: + return 0 + if vnode.kind() != ast.ASTKind.Call: + return 0 + cl: ast.Call* = ast.Call*(vnode) + if cl is None or cl.func is None: + return 0 + fk: int = cl.func.kind() + if fk == ast.ASTKind.Attribute: + at: ast.Attribute* = ast.Attribute*(cl.func) + if at is None or at.attr is None or at.value is None: + return 0 + if at.value.kind() != ast.ASTKind.Name: + return 0 + bn: ast.Name* = ast.Name*(at.value) + if bn is None or bn.id is None or _alias is None: + return 0 + if string.strcmp(bn.id, _alias) != 0: + return 0 + if string.strcmp(at.attr, "Project") == 0: + return 1 + return 0 + if fk == ast.ASTKind.Name: + nm: ast.Name* = ast.Name*(cl.func) + if nm is None or nm.id is None or _ctor_name is None: + return 0 + if string.strcmp(nm.id, _ctor_name) == 0: + return 1 + return 0 + + +def _ApplyCtorArgs(cl: ast.Call*) -> int: + """`vb.Project("Test", "1.0.0")` / `vb.Project(name=, version=)` → _c_name / _c_version + + 位置参数与关键字参数都支持(位置在前:args[0]=name, args[1]=version)。 + """ + global _c_name, _c_version + if cl is None: + return 0 + if cl.args is not None: + an: sizet = cl.args.__len__() + if an > 2: + VLogger.error("viperbuild: Project(...) 位置参数最多 2 个(name, version)", "viperbuild") + return 1 + if an >= 1: + if _EvalStr(cl.args.get(0)) == 0: + VLogger.error("viperbuild: Project(name) 必须是字符串常量", "viperbuild") + return 1 + _c_name = _DupStd(_g_out_str) + if an >= 2: + if _EvalStr(cl.args.get(1)) == 0: + VLogger.error("viperbuild: Project(version) 必须是字符串常量", "viperbuild") + return 1 + _c_version = _DupStd(_g_out_str) + if cl.keywords is None: + return 0 + n: sizet = cl.keywords.__len__() + i: sizet = 0 + while i < n: + kw: ast.Keyword* = ast.Keyword*(cl.keywords.get(i)) + if kw is not None and kw.arg is not None and kw.value is not None: + if string.strcmp(kw.arg, "name") == 0: + if _EvalStr(kw.value) == 0: + VLogger.error("viperbuild: Project(name=...) 必须是字符串常量", "viperbuild") + return 1 + _c_name = _DupStd(_g_out_str) + elif string.strcmp(kw.arg, "version") == 0: + if _EvalStr(kw.value) == 0: + VLogger.error("viperbuild: Project(version=...) 必须是字符串常量", "viperbuild") + return 1 + _c_version = _DupStd(_g_out_str) + else: + VLogger.error(f"viperbuild: Project(...) 不支持的关键字 {kw.arg}", "viperbuild") + return 1 + i += 1 + return 0 + + +def _SetField(fname: str, vnode: ast.AST*) -> int: + """`proj. = ` / `config(=)`;0=成功""" + global _c_source, _c_buildroot, _c_includes, _c_compiler, _c_linker + global _c_static_libs, _c_dynamic_libs, _c_output, _c_name, _c_version + if fname is None or vnode is None: + return 1 + # ---- 字符串字段 ---- + if string.strcmp(fname, "source") == 0: + if _EvalStr(vnode) == 0: + VLogger.error("viperbuild: source 必须是字符串常量表达式", "viperbuild") + return 1 + _c_source = _DupStd(_g_out_str) + return 0 + if string.strcmp(fname, "buildroot") == 0: + if _EvalStr(vnode) == 0: + VLogger.error("viperbuild: buildroot 必须是字符串常量表达式", "viperbuild") + return 1 + _c_buildroot = _DupStd(_g_out_str) + return 0 + if string.strcmp(fname, "output") == 0: + if _EvalStr(vnode) == 0: + VLogger.error("viperbuild: output 必须是字符串常量表达式", "viperbuild") + return 1 + _c_output = _DupStd(_g_out_str) + return 0 + if string.strcmp(fname, "name") == 0: + if _EvalStr(vnode) == 0: + VLogger.error("viperbuild: name 必须是字符串常量表达式", "viperbuild") + return 1 + _c_name = _DupStd(_g_out_str) + return 0 + if string.strcmp(fname, "version") == 0: + if _EvalStr(vnode) == 0: + VLogger.error("viperbuild: version 必须是字符串常量表达式", "viperbuild") + return 1 + _c_version = _DupStd(_g_out_str) + return 0 + # ---- 列表字段 ---- + if string.strcmp(fname, "includes") == 0: + return _SetListField(0, vnode) + if string.strcmp(fname, "compiler") == 0: + return _SetListField(1, vnode) + if string.strcmp(fname, "linker") == 0: + return _SetListField(2, vnode) + if string.strcmp(fname, "static_libs") == 0: + return _SetListField(3, vnode) + if string.strcmp(fname, "dynamic_libs") == 0: + return _SetListField(4, vnode) + VLogger.error(f"viperbuild: 未知字段 proj.{fname}", "viperbuild") + return 1 + + +def _SetListField(slot: int, vnode: ast.AST*) -> int: + """列表字段赋值(slot: 0=includes 1=compiler 2=linker 3=static_libs 4=dynamic_libs)""" + global _c_includes, _c_compiler, _c_linker, _c_static_libs, _c_dynamic_libs + if _scratch is None: + return 1 + tmp: list[str]* = list[str](_scratch, 8) + if tmp is None: + return 1 + if _EvalList(vnode, tmp) == 0: + VLogger.error("viperbuild: 列表字段必须是 list[str] 常量表达式", "viperbuild") + return 1 + joined: str = _JoinSpace(tmp) + if slot == 0: + _c_includes = joined + elif slot == 1: + _c_compiler = joined + elif slot == 2: + _c_linker = joined + elif slot == 3: + _c_static_libs = joined + else: + _c_dynamic_libs = joined + return 0 + + +def _HandleAssign(stmt: ast.Assign*) -> int: + """处理赋值语句:常量定义 / `proj = vb.Project(...)` / `proj.<字段> = ...`""" + if stmt is None or stmt.targets is None or stmt.value is None: + return 0 + if stmt.targets.__len__() != 1: + return 0 + tgt: ast.AST* = stmt.targets.get(0) + if tgt is None: + return 0 + tk: int = tgt.kind() + if tk == ast.ASTKind.Name: + return _HandleNameBind(ast.Name*(tgt), stmt.value) + if tk == ast.ASTKind.Attribute: + return _HandleAttrAssign(ast.Attribute*(tgt), stmt.value) + return 0 + + +def _HandleAnnAssign(stmt: ast.AnnAssign*) -> int: + """处理注解赋值(含 `define NAME = <字面量>` 脱糖出的 AnnAssign)""" + if stmt is None or stmt.target is None or stmt.value is None: + return 0 + if stmt.target.kind() != ast.ASTKind.Name: + return 0 + return _HandleNameBind(ast.Name*(stmt.target), stmt.value) + + +def _HandleNameBind(tgt: ast.Name*, vnode: ast.AST*) -> int: + """`NAME = `:Project 实例绑定 / 字符串常量 / 列表常量""" + global _c_has_project + if tgt is None or vnode is None or tgt.id is None: + return 0 + # 右侧读取 Project 宏对象 ⇒ fatal(禁止读取) + if _IsProjRef(vnode) != 0: + VLogger.error(f"viperbuild: 禁止读取 Project 宏对象({tgt.id} = proj ...)", "viperbuild") + return 1 + # Project 实例绑定 + if _IsProjectCtor(vnode) != 0: + if _c_has_project != 0: + VLogger.error("viperbuild: 一个依赖闭包内只允许一个 Project 对象", "viperbuild") + return 1 + _c_has_project = 1 + _proj_vars.append(_DupScratch(tgt.id)) + cl: ast.Call* = ast.Call*(vnode) + return _ApplyCtorArgs(cl) + # 字符串常量 + if _EvalStr(vnode) != 0: + _f_str_names.append(tgt.id) + _f_str_vals.append(_DupScratch(_g_out_str)) + return 0 + # 列表常量 + if _scratch is not None: + tmp: list[str]* = list[str](_scratch, 8) + if tmp is not None: + if _EvalList(vnode, tmp) != 0: + _f_list_names.append(tgt.id) + _f_list_vals.append(_JoinSep(tmp)) + return 0 + tmp.clear() + return 0 + + +def _HandleAttrAssign(tgt: ast.Attribute*, vnode: ast.AST*) -> int: + """`proj.<字段> = <常量表达式>`""" + if tgt is None or tgt.attr is None or tgt.value is None: + return 0 + if tgt.value.kind() != ast.ASTKind.Name: + return 0 + bn: ast.Name* = ast.Name*(tgt.value) + if bn is None or bn.id is None: + return 0 + if _FindInList(_proj_vars, bn.id) < 0: + return 0 + if _IsProjRef(vnode) != 0: + VLogger.error("viperbuild: 禁止读取 Project 宏对象", "viperbuild") + return 1 + return _SetField(tgt.attr, vnode) + + +def _HandleConfigCall(cl: ast.Call*) -> int: + """`proj.config(kw=...)`""" + if cl is None or cl.keywords is None: + return 0 + n: sizet = cl.keywords.__len__() + i: sizet = 0 + while i < n: + kw: ast.Keyword* = ast.Keyword*(cl.keywords.get(i)) + if kw is not None and kw.arg is not None and kw.value is not None: + if _SetField(kw.arg, kw.value) != 0: + return 1 + i += 1 + return 0 + + +def _HandleBuildCall(cl: ast.Call*) -> int: + """`proj.build(triple=, datalayout=, selfmachine=)`""" + global _c_triple, _c_datalayout, _c_selfmachine, _c_has_build + if cl is None: + return 1 + if _c_has_build != 0: + VLogger.error("viperbuild: proj.build() 只能调用一次", "viperbuild") + return 1 + _c_has_build = 1 + if cl.keywords is None: + return 0 + n: sizet = cl.keywords.__len__() + i: sizet = 0 + while i < n: + kw: ast.Keyword* = ast.Keyword*(cl.keywords.get(i)) + if kw is not None and kw.arg is not None and kw.value is not None: + if string.strcmp(kw.arg, "triple") == 0: + if _EvalStr(kw.value) == 0: + VLogger.error("viperbuild: build(triple=...) 必须是字符串常量", "viperbuild") + return 1 + _c_triple = _DupStd(_g_out_str) + elif string.strcmp(kw.arg, "datalayout") == 0: + if _EvalStr(kw.value) == 0: + VLogger.error("viperbuild: build(datalayout=...) 必须是字符串常量", "viperbuild") + return 1 + _c_datalayout = _DupStd(_g_out_str) + elif string.strcmp(kw.arg, "selfmachine") == 0: + b: int = _EvalBool(kw.value) + if b < 0: + VLogger.error("viperbuild: build(selfmachine=...) 必须是布尔常量", "viperbuild") + return 1 + _c_selfmachine = b + else: + VLogger.error(f"viperbuild: build(...) 不支持的关键字 {kw.arg}", "viperbuild") + return 1 + i += 1 + return 0 + + +def _CallArgsHaveProjRef(cl: ast.Call*) -> int: + """调用的实参/关键字里是否引用了 Project 宏对象(读即 fatal);1=是""" + if cl is None: + return 0 + if cl.args is not None: + n: sizet = cl.args.__len__() + i: sizet = 0 + while i < n: + if _IsProjRef(cl.args.get(i)) != 0: + return 1 + i += 1 + if cl.keywords is not None: + n = cl.keywords.__len__() + i = 0 + while i < n: + kw: ast.Keyword* = ast.Keyword*(cl.keywords.get(i)) + if kw is not None and kw.value is not None: + if _IsProjRef(kw.value) != 0: + return 1 + i += 1 + return 0 + + +def _HandleExprStmt(stmt: ast.Expr*) -> int: + """表达式语句:识别 `proj.config(...)` / `proj.build(...)`;其余检查读取""" + if stmt is None or stmt.value is None: + return 0 + v: ast.AST* = stmt.value + if v.kind() != ast.ASTKind.Call: + return 0 + cl: ast.Call* = ast.Call*(v) + if cl is None or cl.func is None: + return 0 + # 非 Project 方法调用:若实参里读了宏对象 ⇒ fatal(禁止读取) + if cl.func.kind() != ast.ASTKind.Attribute: + if _CallArgsHaveProjRef(cl) != 0: + VLogger.error("viperbuild: 禁止读取 Project 宏对象", "viperbuild") + return 1 + return 0 + at: ast.Attribute* = ast.Attribute*(cl.func) + if at is None or at.attr is None or at.value is None: + return 0 + if at.value.kind() != ast.ASTKind.Name: + if _CallArgsHaveProjRef(cl) != 0: + VLogger.error("viperbuild: 禁止读取 Project 宏对象", "viperbuild") + return 1 + return 0 + bn: ast.Name* = ast.Name*(at.value) + if bn is None or bn.id is None: + return 0 + if _FindInList(_proj_vars, bn.id) < 0: + if _CallArgsHaveProjRef(cl) != 0: + VLogger.error("viperbuild: 禁止读取 Project 宏对象", "viperbuild") + return 1 + return 0 + if string.strcmp(at.attr, "config") == 0: + return _HandleConfigCall(cl) + if string.strcmp(at.attr, "build") == 0: + return _HandleBuildCall(cl) + VLogger.error(f"viperbuild: Project 对象不支持的方法 {at.attr}()", "viperbuild") + return 1 + + +def _ScanFile(pool: memhub.MemBuddy*, path: str) -> int: + """解析并扫描单个 .vp 文件;0=成功,1=失败(已报错),2=可跳过(不可读/非 .vp)""" + if path is None: + return 2 + sl: sizet = string.strlen(path) + if sl == 0: + return 2 + if Utils.is_vp_at(path, 0, sl) == 0: + # 非 .vp:不参与闭包(.vpi/.vpnci 对编译器不可见) + return 2 + + src_buf: bytes = Utils.read_file_to_buf(path, VB_SRC_CAP) + if src_buf is None: + VLogger.warning(f"viperbuild: 无法读取依赖文件 {path}", "viperbuild") + return 2 + + if _ResetFileTables(pool) != 0: + stdlib.free(src_buf) + VLogger.error("viperbuild: 常量表分配失败", "viperbuild") + return 1 + + mark: sizet = pool.mark() + lx: ast.Lexer* = ast.new_lexer(pool) + if lx is None: + pool.reset_to(mark) + stdlib.free(src_buf) + VLogger.error("viperbuild: lexer 分配失败", "viperbuild") + return 1 + ast._lexer_init(lx, src_buf, pool) + tokens: ast.Token* = ast.tokenize(lx) + if tokens is None: + pool.reset_to(mark) + stdlib.free(src_buf) + VLogger.error(f"viperbuild: 词法失败 {path}", "viperbuild") + return 1 + tree: ast.AST* = ast.parse_tokens(pool, tokens) + if tree is None: + pool.reset_to(mark) + stdlib.free(src_buf) + VLogger.error(f"viperbuild: 语法失败 {path}", "viperbuild") + return 1 + + body: list[ast.AST*]* = tree.children + rc: int = 0 + if body is not None: + cn: sizet = len(body) + ci: sizet = 0 + while ci < cn: + child: ast.AST* = body.get(ci) + if child is not None: + kd: int = child.kind() + if kd == ast.ASTKind.Import: + _HandleImport(ast.Import*(child)) + elif kd == ast.ASTKind.ImportFrom: + _HandleImportFrom(ast.ImportFrom*(child)) + elif kd == ast.ASTKind.Assign: + rc = _HandleAssign(ast.Assign*(child)) + elif kd == ast.ASTKind.AnnAssign: + rc = _HandleAnnAssign(ast.AnnAssign*(child)) + elif kd == ast.ASTKind.Expr: + rc = _HandleExprStmt(ast.Expr*(child)) + if rc != 0: + break + ci += 1 + + pool.reset_to(mark) + stdlib.free(src_buf) + return rc + + +# ============================================================ +# 依赖闭包 BFS +# ============================================================ +def _ResolveModuleToFile(module_name: str) -> str: + """把模块名解析为工程本地文件路径(搜索根 = 入口文件所在目录);未命中返回 None + + 输出槽契约与 StubMergerBuild 一致:槽为 8 字节缓冲,写侧 sizet(ptr)、 + 读侧 rawptr(slot[0]);ResolveModuleToPath 返回的 abs/rel/inc 串由调用方 free。 + """ + if module_name is None or _search_dirs is None: + return None + if string.strlen(module_name) == 0: + return None + out_abs: t.CPtr = stdlib.malloc(8) + out_rel: t.CPtr = stdlib.malloc(8) + out_inc: t.CPtr = stdlib.malloc(8) + if out_abs is None or out_rel is None or out_inc is None: + if out_abs is not None: stdlib.free(out_abs) + if out_rel is not None: stdlib.free(out_rel) + if out_inc is not None: stdlib.free(out_inc) + return None + abs_slot: sizet* = (sizet*)(out_abs) + rel_slot: sizet* = (sizet*)(out_rel) + inc_slot: sizet* = (sizet*)(out_inc) + abs_slot[0] = 0 + rel_slot[0] = 0 + inc_slot[0] = 0 + rcode: int = IncludesScanner.ResolveModuleToPath(_search_dirs, module_name, + out_abs, out_rel, out_inc) + result: str = None + if rcode == 0 and abs_slot[0] != 0: + abs_path: str = rawptr(abs_slot[0]) + if abs_path is not None: + result = _DupScratch(abs_path) + if abs_slot[0] != 0: + stdlib.free(rawptr(abs_slot[0])) + if rel_slot[0] != 0: + stdlib.free(rawptr(rel_slot[0])) + if inc_slot[0] != 0: + stdlib.free(rawptr(inc_slot[0])) + stdlib.free(out_abs) + stdlib.free(out_rel) + stdlib.free(out_inc) + return result + + +def _Enqueue(path: str) -> None: + """把文件路径加入 BFS 队列(worklist/visited 去重)""" + if path is None or _work is None: + return + if _FindInList(_visited, path) >= 0: + return + if _FindInList(_work, path) >= 0: + return + _work.append(_DupScratch(path)) + + +def _RunBfs(pool: memhub.MemBuddy*, entry_path: str) -> int: + """从入口文件出发展开依赖闭包;0=成功""" + global _pending + _work.append(_DupScratch(entry_path)) + while _work.__len__() > 0: + cur: str = _work.pop() + if cur is None: + continue + if _FindInList(_visited, cur) >= 0: + continue + if _visited.__len__() >= VB_MAX_VISIT: + VLogger.error(f"viperbuild: 依赖闭包超过 {VB_MAX_VISIT} 个文件,疑似成环", "viperbuild") + return 1 + _visited.append(_DupScratch(cur)) + + _pending = list[str](_scratch, 8) + if _pending is None: + VLogger.error("viperbuild: BFS 队列分配失败", "viperbuild") + return 1 + rc: int = _ScanFile(pool, cur) + if rc == 1: + return 1 + # 解析本文件收集到的模块名 + pn: sizet = _pending.__len__() + pi: sizet = 0 + while pi < pn: + mod: str = _pending.get(pi) + if mod is not None: + dep: str = _ResolveModuleToFile(mod) + if dep is not None: + _Enqueue(dep) + pi += 1 + return 0 + + +# ============================================================ +# 默认值 / 校验 / 应用 +# ============================================================ +def _DetectHost() -> int: + """运行时探测本机 triple / datalayout(写 _c_triple / _c_datalayout);0=成功 + + 读 PROCESSOR_ARCHITECTURE 环境变量判定本机架构。当前仅支持 AMD64 + (编译器自身的目标平台 x86_64-pc-windows-gnu);其余架构 fail-fast。 + """ + global _c_triple, _c_datalayout + buf: CHARPTR = stdlib.malloc(64) + if buf is None: + VLogger.error("viperbuild: selfmachine 探测缓冲分配失败", "viperbuild") + return 1 + buf[0] = char(0) + n: DWORD = GetEnvironmentVariableA("PROCESSOR_ARCHITECTURE", buf, DWORD(64)) + if n == 0 or n >= DWORD(64): + stdlib.free(buf) + VLogger.error("viperbuild: selfmachine 探测失败(PROCESSOR_ARCHITECTURE 不可用)", "viperbuild") + return 1 + buf[int(n)] = char(0) + # "AMD64\0"(A=65 M=77 D=68 6=54 4=52) + is_amd64: int = 0 + if buf[0] == char(65) and buf[1] == char(77) and buf[2] == char(68) \ + and buf[3] == char(54) and buf[4] == char(52) and buf[5] == char(0): + is_amd64 = 1 + if is_amd64 != 0: + _c_triple = "x86_64-pc-windows-gnu" + _c_datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + stdlib.free(buf) + return 0 + stdlib.free(buf) + VLogger.error("viperbuild: selfmachine 暂不支持本机架构(当前仅支持 AMD64)", "viperbuild") + return 1 + + +def _ProbeIncludes() -> str: + """按 exe 所在目录向上 0..3 层探测 includes 目录;命中返回绝对路径,否则 None""" + exe_buf: bytes = stdlib.malloc(VB_PATH_CAP) + if exe_buf is None: + return None + exe_buf[0] = '\0' + n_exe: ULONG = win32file.GetModuleFileNameA(None, exe_buf, ULONG(VB_PATH_CAP)) + if n_exe <= 0: + stdlib.free(exe_buf) + return None + el: int = int(n_exe) + if el >= VB_PATH_CAP: + el = VB_PATH_CAP - 1 + exe_buf[el] = '\0' + exe_dir: str = _DirOf(exe_buf) + stdlib.free(exe_buf) + if exe_dir is None: + return None + k: int = 0 + while k <= 3: + base: str = _DupStd(exe_dir) + j: int = 0 + while j < k: + base = _Join2(base, "..") + j += 1 + cand: str = _Join2(base, "includes") + if _IsDir(cand) != 0: + return cand + k += 1 + return None + + +def _DeriveOutputFromEntry(entry_path: str) -> str: + """从入口文件名推导默认输出名(去扩展名 + ".exe")""" + base: str = _BaseNameOf(entry_path) + if base is None: + return "app.exe" + n: sizet = string.strlen(base) + cut: sizet = n + i: sizet = 0 + while i < n: + if base[i] == '.': + cut = i + break + i += 1 + head: str = stdlib.malloc(cut + 5) + if head is None: + return "app.exe" + string.strncpy(head, base, cut) + head[cut] = '.' + head[cut + 1] = 'e' + head[cut + 2] = 'x' + head[cut + 3] = 'e' + head[cut + 4] = '\0' + return head + + +def _FinalizeCfg(entry_path: str) -> int: + """填充默认值 + 校验 + 写回 Config;0=成功""" + global _c_source, _c_buildroot, _c_includes, _c_compiler, _c_linker + global _c_static_libs, _c_dynamic_libs, _c_output, _c_triple, _c_datalayout + global _c_selfmachine + + if _c_has_project == 0: + VLogger.error("viperbuild: 依赖闭包内未找到 vb.Project(...) 定义", "viperbuild") + return 1 + if _c_has_build == 0: + VLogger.error("viperbuild: 未调用 proj.build(...),无法确定构建目标", "viperbuild") + return 1 + + # ---- 默认值 ---- + if _c_source is None: + _c_source = _DupStd(_entry_dir) + if _c_buildroot is None: + _c_buildroot = _Join2(_c_source, ".tpv_build") + if _c_includes is None: + probed: str = _ProbeIncludes() + if probed is None: + VLogger.error("viperbuild: 未能探测到 includes 目录(exe 目录向上 0..3 层均无 includes)", "viperbuild") + return 1 + _c_includes = probed + if _c_compiler is None: + _c_compiler = _DupStd("llc -filetype=obj -relocation-model=pic") + if _c_linker is None: + # 内置默认 = 本仓 TransPyV/project.vpj 那份**已验证可用**的链接参数。 + # 注意:不要带 -lgcc —— msys64/mingw64 + lld 下没有 libgcc, + # 会以 "lld: error: unable to find library -lgcc" 直接链接失败。 + _c_linker = _DupStd("clang++ -fuse-ld=lld -lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -Wl,--allow-multiple-definition -Wl,--stack,0x1000000 -Wl,--no-insert-timestamp -Wl,--export-all-symbols -g") + if _c_output is None: + _c_output = _DeriveOutputFromEntry(entry_path) + + # ---- 入口文件必须位于 source 之下 ---- + if _HasPrefix(entry_path, _c_source) == 0: + VLogger.error(f"viperbuild: 入口文件不在 source 目录之下\n 入口: {entry_path}\n source: {_c_source}", "viperbuild") + return 1 + + # ---- 目标平台 ---- + explicit_triple: int = 0 + if _c_triple is not None or _c_datalayout is not None: + explicit_triple = 1 + if _c_selfmachine != 0 and explicit_triple != 0: + VLogger.error("viperbuild: build(selfmachine=True) 与显式 triple/datalayout 互斥", "viperbuild") + return 1 + if explicit_triple != 0: + if _c_triple is None or _c_datalayout is None: + VLogger.error("viperbuild: build() 的 triple 与 datalayout 必须成对给出", "viperbuild") + return 1 + else: + # 都未给 或 selfmachine=True ⇒ 运行时探测本机 + if _DetectHost() != 0: + return 1 + + # ---- 写回 Config ---- + Config.ProjectName = _c_name + Config.ProjectVersion = _c_version + Config.SourceDir = _c_source + Config.BuildDir = _c_buildroot + Config.IncludesDirs = _c_includes + Config.IncludesDir = _FirstSpaceTok(_c_includes) + if Config.IncludesDir is None: + Config.IncludesDir = _c_includes + Config.TargetTriple = _c_triple + Config.TargetDataLayout = _c_datalayout + Config.LinkerStaticLibs = _c_static_libs + Config.LinkerDynamicLibs = _c_dynamic_libs + Config.LinkerOutput = _c_output + Config.Sha1SliceLevel = 1 + Config.StrictMode = 1 + _SplitToolCmd(_c_compiler, 0) + _SplitToolCmd(_c_linker, 1) + + Config.resolve_paths(_c_source) + return 0 + + +def _SplitToolCmd(joined: str, which: int) -> None: + """把 `["cmd", flag...]` 空格连接串拆成 Config 的 Cmd/Flags(which: 0=compiler 1=linker)""" + if joined is None: + return + n: sizet = string.strlen(joined) + sp: sizet = n + i: sizet = 0 + while i < n: + if joined[i] == ' ': + sp = i + break + i += 1 + cmd: str = stdlib.malloc(sp + 1) + if cmd is not None: + string.strncpy(cmd, joined, sp) + cmd[sp] = '\0' + flags: str = None + if sp < n: + flags = _DupStd(joined + sp + 1) + if which == 0: + Config.CompilerCmd = cmd + Config.CompilerFlags = flags + else: + Config.LinkerCmd = cmd + Config.LinkerFlags = flags + + +# ============================================================ +# 入口 +# ============================================================ +def _ResetAll() -> None: + """重置模块状态(每次 DiscoverAndApply 调用前)""" + global _alias, _ctor_name, _g_out_str, _applied, _entry_dir, _search_dirs + global _c_name, _c_version, _c_source, _c_buildroot, _c_includes + global _c_compiler, _c_linker, _c_static_libs, _c_dynamic_libs, _c_output + global _c_triple, _c_datalayout, _c_selfmachine, _c_has_project, _c_has_build + global _saw_viperbuild + _alias = None + _ctor_name = None + _g_out_str = None + _applied = 0 + _entry_dir = None + _search_dirs = None + _saw_viperbuild = 0 + _c_name = None + _c_version = None + _c_source = None + _c_buildroot = None + _c_includes = None + _c_compiler = None + _c_linker = None + _c_static_libs = None + _c_dynamic_libs = None + _c_output = None + _c_triple = None + _c_datalayout = None + _c_selfmachine = 0 + _c_has_project = 0 + _c_has_build = 0 + + +def DiscoverAndApply(mb: memhub.MemBuddy*, entry_path: str) -> int: + """`--src` Pass 0:发现并应用 viperbuild 配置;0=成功,非 0=失败(已报错) + + Args: + mb: 主 MemBuddy(AST 用;每文件 reset_to 回收) + entry_path: 入口 .vp 文件路径(--src) + + Returns: + 0 成功(Config 已填充);1 失败(fail-fast,调用方须终止编译) + """ + global _scratch, _work, _visited, _pending, _proj_vars, _entry_dir, _applied + global _search_dirs + + if mb is None or entry_path is None: + VLogger.error("viperbuild: 参数为空", "viperbuild") + return 1 + + _ResetAll() + + # 入口目录(source 缺省值 + 闭包搜索根) + _entry_dir = _DirOf(entry_path) + if _entry_dir is None: + VLogger.error(f"viperbuild: 无法解析入口文件目录 {entry_path}", "viperbuild") + return 1 + # 裸文件名(无目录分隔符)→ _DirOf 返回空串;必须规整成 ".",否则 + # ResolveModuleToPath("") 一个候选目录都不会尝试,入口同目录的依赖 + # (往往是 viperbuild 配置所在模块)永远解析不到 ⇒ 误报"未找到 import viperbuild"。 + if string.strlen(_entry_dir) == 0: + _entry_dir = _DupStd(".") + # 闭包搜索根:入口目录优先,其次当前目录 "."(配置模块可能不在入口目录内) + _search_dirs = _ConcatStd(_DupStd(_entry_dir), _DupStd(" .")) + + # scratch 池(BFS 工作集,不参与主池 reset) + arena: bytes = stdlib.malloc(VB_SCRATCH) + if arena is None: + VLogger.error("viperbuild: scratch 内存分配失败", "viperbuild") + return 1 + _scratch = memhub.MemBuddy(arena, VB_SCRATCH, 0, 0) + if _scratch is None: + VLogger.error("viperbuild: scratch 池初始化失败", "viperbuild") + return 1 + + _work = list[str](_scratch, 8) + _visited = list[str](_scratch, 8) + _pending = list[str](_scratch, 8) + _proj_vars = list[str](_scratch, 8) + if _work is None or _visited is None or _pending is None or _proj_vars is None: + VLogger.error("viperbuild: 工作集分配失败", "viperbuild") + return 1 + + # 关键字/操作符表须在主池 reset 窗口之前建立(否则被回收 → 悬空崩溃) + ast._init_tables(mb) + + if _RunBfs(mb, entry_path) != 0: + return 1 + + if _proj_vars.__len__() == 0: + if _saw_viperbuild == 0: + VLogger.error(f"viperbuild: 入口文件依赖闭包内未找到 `import viperbuild`(入口: {entry_path};已扫描 {_visited.__len__()} 个文件)", "viperbuild") + else: + VLogger.error(f"viperbuild: 找到 `import viperbuild` 但依赖闭包内没有 `proj = vb.Project(...)` 绑定(入口: {entry_path})", "viperbuild") + return 1 + + if _FinalizeCfg(entry_path) != 0: + return 1 + + _applied = 1 + return 0 + + +def IsApplied() -> int: + """本次进程是否已成功应用 viperbuild 配置(供 main.vp 判断 linker.output 来源)""" + return _applied diff --git a/App/lib/core/builtin/__init__.vp b/App/lib/core/builtin/__init__.vp new file mode 100644 index 0000000..39f2c31 --- /dev/null +++ b/App/lib/core/builtin/__init__.vp @@ -0,0 +1,3 @@ +# ============================================================ +# builtin 包入口 —— 编译器内建库(如 viperbuild)的编译器侧实现 +# ============================================================ diff --git a/App/main.vp b/App/main.vp index 475b062..8c7725c 100644 --- a/App/main.vp +++ b/App/main.vp @@ -11,6 +11,7 @@ import lib.core.CompilerFactory as CompilerFactory import lib.core.Gen.Generator as GenGenerator import lib.Projectrans.Config as Config import lib.Projectrans.Utils as Utils +import lib.core.builtin.ViperBuild as ViperBuild import lib from stdint import * import stdio @@ -241,6 +242,17 @@ Target: %s phase: str = args.get_str("phase") cc: str = args.get_str("cc") build_dir: str = args.get_str("build-dir") + + # === --project 与 --src 互斥 === + # --project:传统 project.vpj 工程模式;--src:viperbuild 内建配置模式(入口文件)。 + # 两者的配置来源互斥,同时给无法确定以谁为准 ⇒ fail-fast。 + if project is not None and src is not None: + stdio.printf("FAIL: --project 与 --src 互斥\n") + stdio.printf(" 原因: --project 走 project.vpj 工程模式,--src 走入口文件的 viperbuild 内建配置\n") + stdio.printf(" 处理: 二选一;--src 模式下构建配置写在入口文件依赖闭包的 import viperbuild 里\n") + argparse.release(args) + return 2 + _gb_clean: bool = args.get_bool("clean") show_args: bool = args.get_bool("show-args") if show_args: @@ -348,13 +360,15 @@ Target: %s log.warning(f"无法加载 project.vpj: {proj_path}", "config") else: # 尝试默认路径 project.vpj(当前目录和上级目录) - proj_path = "project.vpj" - if Config.Load_project_config(proj_path) == 0: - proj_loaded = 1 - else: - proj_path = "../project.vpj" + # --src 模式走 viperbuild 内建配置,不得再隐式加载 project.vpj(否则污染配置来源) + if src is None: + proj_path = "project.vpj" if Config.Load_project_config(proj_path) == 0: proj_loaded = 1 + else: + proj_path = "../project.vpj" + if Config.Load_project_config(proj_path) == 0: + proj_loaded = 1 if proj_loaded == 1: # 提取 project.vpj 所在目录,将相对路径解析为基于该目录的路径 proj_len: sizet = string.strlen(proj_path) @@ -377,6 +391,17 @@ Target: %s Config.resolve_paths(proj_dir) Config.print_config() + # === --src: viperbuild 内建配置发现与应用(Pass 0)=== + # 位置:project.vpj 加载之后、--asan/--clean/--phase 之前 —— 后续这些开关都要读 + # Config.TempDir/BuildDir/static_libs,必须先在 Pass 0 里从入口文件的依赖闭包 + # 提取 `import viperbuild` 配置并写回 Config。没找到 / 找到但坏一律 fail-fast。 + if src is not None: + log.banner("viperbuild") + if ViperBuild.DiscoverAndApply(mb, src) != 0: + argparse.release(args) + return 1 + Config.print_config() + # === --repl: M1 交互式循环(表达式片段 → IR → llc → clang++ -shared → DLL → 调用回显)=== # 位置:工程配置加载之后(Repl 经 Config 取 compiler/linker 命令与 target triple; # 未加载 project.vpj 时 Repl 退回内置默认 llc/clang++),仍在 Phase1/Phase2 之前。 @@ -464,9 +489,11 @@ Target: %s argparse.release(args) return 1 - # === 多文件项目模式 === - # 当 --project 指定但 --src 未指定时,扫描 source_dir 下所有 .vp/.py 文件编译 - if src_path is None and project is not None and Config.SourceDir is not None: + # === 工程模式(多文件) === + # --project:project.vpj 工程模式(扫 source_dir 下所有 .vp 编译); + # --src + viperbuild:Pass 0 已把配置写回 Config,同样按工程模式构建(SourceDir 为根)。 + # 二者在参数解析处已判定互斥,不会同时成立。 + if Config.SourceDir is not None and (project is not None or ViperBuild.IsApplied() != 0): mf_temp: str = Config.TempDir if Config.TempDir is not None else "." mf_output: str = Config.OutputDir if Config.OutputDir is not None else "." mf_cc: str = Config.CompilerCmd if Config.CompilerCmd is not None else "llc" @@ -487,224 +514,7 @@ Target: %s log.success("TransPyV 完成") return mf_ret - # === 单文件模式:--src 指定时使用指定文件;--project 但无 SourceDir 时回退 === - if src_path is None and project is not None: - if Config.SourceDir is not None: - # 构造入口路径: SourceDir/main.vp(不再做 vp -> py 后缀识别回退) - sd_len: sizet = string.strlen(Config.SourceDir) - path_buf: bytes = stdlib.malloc(sd_len + 16) - if path_buf is not None: - viperlib.snprintf(path_buf, sd_len + 16, "%s/main.vp", Config.SourceDir) - src_path = path_buf - log.info(f"入口文件: {src_path}", "project") - - if src_path is not None: - log.banner("AST 解析") - # 打开文件 - f: fileio.File = fileio.File(src_path, fileio.MODE.R) - if f.closed: - log.error(f"无法打开文件: {src_path}", "fileio") - argparse.release(args) - return 1 - # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被**静默截断**; - # 这里先按文件大小拒绝, 否则拿截断后的源码去 parse 只会得到莫名其妙的语法错误 - if f.size() >= SRC_BUF_SIZE: - log.error(f"源文件超过 {SRC_BUF_SIZE} 字节上限, 拒绝读取: {src_path}", "fileio") - f.close() - argparse.release(args) - return 1 - # 分配源代码缓冲区 - src_buf: bytes = stdlib.malloc(SRC_BUF_SIZE) - if src_buf is None: - log.error("malloc for src_buf failed", "memhub") - f.close() - argparse.release(args) - return 1 - # 读取文件内容 - bytes_read: LONG = f.read_all(src_buf, SRC_BUF_SIZE) - f.close() - if bytes_read < 0: - log.error(f"读取文件失败 (错误码: {bytes_read})", "fileio") - argparse.release(args) - return 1 - # 添加 null 终止符,确保 strlen 和 SHA1 计算正确 - if bytes_read < SRC_BUF_SIZE: - src_buf[bytes_read] = 0 - # 解析 AST(拆分为词法+语法两阶段,便于调试) - ast._init_tables(mb) - lx: ast.Lexer* = ast.new_lexer(mb) - ast._lexer_init(lx, src_buf, mb) - tokens: ast.Token* = ast.tokenize(lx) - tree: ast.AST* = ast.parse_tokens(mb, tokens) - if tree is None: - # 解析失败:行/列 + 原因一并进日志行(原因由 parser.vp 的 _record_* / - # parse_tokens 兜底补记提供) - log.error(f"AST 解析失败(第 {ast.get_parse_err_line()} 行第 {ast.get_parse_err_col()} 列: {ast.get_parse_err_msg()})", "ast") - argparse.release(args) - return 1 - - # === 翻译 AST → LLVM IR === - log.banner("LLVM IR 翻译") - tr: rawptr[HandlesTranslator.Translator] = HandlesTranslator.Translator() - result: int = tr.translate(tree) - if result == 0: - IR_BUF_SIZE: sizet = 262144 - ir_buf: bytes = stdlib.malloc(IR_BUF_SIZE) - if ir_buf is not None: - tr.dump_ir(ir_buf, IR_BUF_SIZE, llvmlite.OUTPUT_FULL) - - # === 编译管线: .ll → .obj → .exe === - log.banner("编译管线") - - # module_name 用源代码内容的 SHA1(前 16 字符),与 Projectrans.py 一致 - # output_name 从文件名推导(如 test.vp -> test.exe),保持可读性 - module_name: str = Utils.compute_sha1(mb, src_buf) - if module_name is None: - VLogger.error("SHA1 计算失败", "main") - output_name: str = "test_prog.exe" - # 从文件名提取 output_name(去掉路径和 .vp/.py 扩展名) - src_len: sizet = string.strlen(src_path) - if src_len > 0: - # 找到最后一个 / 或 \ 之后的部分 - base_start: sizet = src_len - i: sizet = src_len - while i > 0: - i -= 1 - ch: char = src_path[i] - if ch == '/' or ch == '\\': - base_start = i + 1 - break - if i == 0: - base_start = 0 - # 找到 .vp/.py 扩展名 - base_end: sizet = src_len - j: sizet = base_start - while j < src_len: - if src_path[j] == '.': - base_end = j - break - j += 1 - # 输出名 = 文件名 + ".exe" - name_len: sizet = base_end - base_start - if name_len > 0: - out_len: sizet = name_len + 5 - output_name = stdlib.malloc(out_len) - if output_name is not None: - # 手动复制文件名并追加 .exe(viperlib.snprintf 不支持 %.*s,string 无 strcat) - string.strncpy(output_name, src_path + base_start, name_len) - output_name[name_len] = '.' - output_name[name_len + 1] = 'e' - output_name[name_len + 2] = 'x' - output_name[name_len + 3] = 'e' - output_name[name_len + 4] = '\0' - # 使用 project.vpj 中的配置 - temp_dir: str = Config.TempDir if Config.TempDir is not None else "." - output_dir: str = Config.OutputDir if Config.OutputDir is not None else "." - # 设置全局 temp_dir(供跨模块 CDefine 查找使用) - GenApi.set_temp_dir(temp_dir) - cc_cmd: str = Config.CompilerCmd if Config.CompilerCmd is not None else "llc" - cc_flags: str = "-filetype=obj -relocation-model=pic" - linker_cmd: str = Config.LinkerCmd if Config.LinkerCmd is not None else "clang++" - linker_flags: str = "-lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -lgcc -Wl,--allow-multiple-definition" - linker_static_libs: str = Config.LinkerStaticLibs - linker_dynamic_libs: str = Config.LinkerDynamicLibs - # --project 模式且未显式指定 --src 时:使用 config 中的 linker_output(如 TransPyV.exe) - # 显式指定 --src 时:使用从文件名推导的 output_name(如 test.exe),避免覆盖正在运行的 exe - if project is not None and Config.LinkerOutput is not None and args.get_str("src") is None: - linker_output: str = Config.LinkerOutput - else: - linker_output: str = output_name - - # 计算 includes.binary 目录路径(链接时附加预编译 .obj) - includes_binary_dir: str = Config.get_includes_binary_dir() - - ir_len: sizet = string.strlen(ir_buf) - - # (--phase 参数已在配置加载后处理,此处直接使用 do_phase1/do_phase2) - - # === Phase1 stub 分离:使用 OUTPUT_STUB/TEXT 模式分别生成 stub.ll 和 text.ll === - if do_phase1 != 0 and temp_dir is not None and module_name is not None: - log.banner("stub 分离") - SF_IR_SIZE: sizet = 262144 - td_len_sf: sizet = string.strlen(temp_dir) - # 保存 stub.ll - sf_stub_buf: bytes = stdlib.malloc(SF_IR_SIZE) - if sf_stub_buf is not None: - tr.dump_ir(sf_stub_buf, SF_IR_SIZE, llvmlite.OUTPUT_STUB) - sf_stub_len: sizet = string.strlen(sf_stub_buf) - sf_stub_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_sf, module_name, "stub.ll") - if sf_stub_path is not None: - sf_f: rawptr[fileio.File] = fileio.File(sf_stub_path, fileio.MODE.W) - if not sf_f.closed: - sf_f.write(sf_stub_buf, sf_stub_len) - sf_f.close() - stdlib.free(sf_stub_path) - stdlib.free(sf_stub_buf) - # 保存 text.ll - sf_text_buf: bytes = stdlib.malloc(SF_IR_SIZE) - if sf_text_buf is not None: - tr.dump_ir(sf_text_buf, SF_IR_SIZE, llvmlite.OUTPUT_TEXT) - sf_text_len: sizet = string.strlen(sf_text_buf) - sf_text_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_sf, module_name, "text.ll") - if sf_text_path is not None: - sf_tf: rawptr[fileio.File] = fileio.File(sf_text_path, fileio.MODE.W) - if not sf_tf.closed: - sf_tf.write(sf_text_buf, sf_text_len) - sf_tf.close() - stdlib.free(sf_text_path) - stdlib.free(sf_text_buf) - - # phase=1 模式:仅生成 stub,不执行编译 - if do_phase2 == 0: - log.success("Phase1 完成(仅 stub 分离,跳过编译)") - argparse.release(args) - return 0 - - # === Phase2: 组合本地 stub + 依赖 stubs + 本地 text → 完整 IR === - final_ir: bytes = ir_buf - final_ir_len: sizet = ir_len - if temp_dir is not None and module_name is not None: - log.banner("IR 组合") - SF_COMBINED_SIZE: sizet = 4194304 # 4MB - combined_buf: bytes = stdlib.malloc(SF_COMBINED_SIZE) - if combined_buf is not None: - combined_len: sizet = StubMergerBuild.BuildCombinedIR(temp_dir, module_name, combined_buf, SF_COMBINED_SIZE) - if combined_len > 0: - final_ir = combined_buf - final_ir_len = combined_len - else: - stdlib.free(combined_buf) - VLogger.error("BuildCombinedIR 失败,终止编译", "stub") - - br: rawptr[BuildPipeline.BuildResult] = BuildPipeline.run_pipeline( - final_ir, final_ir_len, temp_dir, output_dir, module_name, - cc_cmd, cc_flags, linker_cmd, linker_flags, linker_output, - includes_binary_dir, linker_static_libs, linker_dynamic_libs - ) - if br is not None and br.Success == 1: - log.success("编译管线完成") - log.info(f"输出: {output_dir}/{linker_output}") - # --run 模式:执行生成的可执行文件 - # phase2command 模式:exe 尚未链接(命令在 bat 中),跳过 --run - if args.get_bool("run") and BuildPipeline._phase2_cmd_path is None: - exe_path: bytes = stdlib.malloc(string.strlen(output_dir) + string.strlen(linker_output) + 2) - if exe_path is not None: - viperlib.snprintf(exe_path, 256, "%s/%s", output_dir, linker_output) - log.info(f"执行: {exe_path}") - r: rawptr[subprocess.CompletedProcess] = subprocess.run(exe_path, False, False) - if r is not None: - log.info(f"退出码: {r.returncode}") - else: - log.error("编译管线失败", "pipeline") - if br is not None and br.ErrorMsg is not None: - log.error(f"编译管线失败: {br.ErrorMsg}", "FATAL") - sys.exit(1) - else: - log.error("malloc for IR buffer failed", "memhub") - else: - log.error("翻译失败", "translator") - + # 走到这里说明没有任何可构建的工程根(SourceDir 缺失)—— fail-fast,不再静默"完成" + log.error("无可构建的工程根:project.vpj 缺 source_dir,或 viperbuild 未提供 source", "config") argparse.release(args) - log.success("TransPyV 完成") - - return 0 + return 1 diff --git a/README.md b/README.md index 861a3bc..6c02850 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,7 @@ source_dir 下全部 .vp/.py(多文件模式:递归扫描所有源文件) - [TransPyC 编译器仓库(种子)](https://git.gvsds.com/GVSDS/TransPyC) - [Viper 语言文档(wiki 目录)](https://git.gvsds.com/GVSDS/TransPyC/tree/main/wiki) - [自举可行性分析与进展](https://git.gvsds.com/GVSDS/TransPyC/blob/main/wiki/13-bootstrapping.md) -- [工程配置 `project.vpj` 说明](https://git.gvsds.com/GVSDS/TransPyC/blob/main/wiki/12-project.md) +- [编译系统(工程配置 `project.vpj` / `viperbuild` 元编程)](https://git.gvsds.com/GVSDS/TransPyC/blob/main/wiki/12-compilation-system.md) - [ViperOS 操作系统(用 Viper 写的 OS)](https://git.gvsds.com/GVSDS/ViperOS) --- @@ -430,5 +430,5 @@ out-of-bounds writes. - [TransPyC compiler repo (the seed)](https://git.gvsds.com/GVSDS/TransPyC) - [Viper language docs (wiki)](https://git.gvsds.com/GVSDS/TransPyC/tree/main/wiki) - [Bootstrapping analysis and progress](https://git.gvsds.com/GVSDS/TransPyC/blob/main/wiki/13-bootstrapping.md) -- [Project config `project.vpj`](https://git.gvsds.com/GVSDS/TransPyC/blob/main/wiki/12-project.md) +- [Compilation system (`project.vpj` / `viperbuild` metaprogramming)](https://git.gvsds.com/GVSDS/TransPyC/blob/main/wiki/12-compilation-system.md) - [ViperOS (an OS written in Viper)](https://git.gvsds.com/GVSDS/ViperOS)