Files
ViperC/App/lib/core/builtin/ViperBuild.vp
T

1246 lines
47 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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.<fname> = <vnode>` / `config(<fname>=<vnode>)`;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 = <value>`: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