Initial import of TransPyV

This commit is contained in:
Viper
2026-07-19 13:18:46 +08:00
commit e7eaf3e9a1
75 changed files with 23037 additions and 0 deletions

View File

@@ -0,0 +1,299 @@
import t, c
from stdint import *
import memhub
import string
import stdio
import viperlib
from json.__parser import parse as json_parse
from json import JsonValue, JSON_OBJECT, JSON_INT
import w32.fileio as fileio
# ============================================================
# Config - 工程配置
# ============================================================
# 全局 mbuddy 指针
_mbuddy: memhub.MemManager | t.CPtr
# 配置值(从 project.vpj 加载)
SourceDir: str
TempDir: str
OutputDir: str
ProjectName: str
ProjectVersion: str
CompilerCmd: str
CompilerFlags: str # 编译器参数(空格分隔,从 compiler.flags 数组拼接)
LinkerCmd: str
LinkerFlags: str # 链接器参数(空格分隔,从 linker.flags 数组拼接)
LinkerOutput: str
TargetTriple: str
TargetDataLayout: str
SliceLevel: t.CInt
StrictMode: t.CInt
IncludesDir: str # includes 目录路径(从 includes 数组首元素)
def Load_project_config(path: str) -> int:
"""加载工程配置文件project.vpj
Args:
path: 配置文件路径
Returns:
0 表示成功,非 0 表示失败
"""
global SourceDir, TempDir, OutputDir, ProjectName, ProjectVersion
global CompilerCmd, CompilerFlags, LinkerCmd, LinkerFlags, LinkerOutput
global TargetTriple, TargetDataLayout, SliceLevel, StrictMode
global IncludesDir
if path is None:
return 1
if _mbuddy is None:
return 1
# 打开文件
f: fileio.File | t.CPtr = fileio.File(path, fileio.MODE.R)
if f.closed:
return 1
# 分配读取缓冲区
CFG_BUF_SIZE: t.CSizeT = 8192
buf: bytes = _mbuddy.alloc(CFG_BUF_SIZE)
if buf is None:
f.close()
return 1
bytes_read: t.CInt64T = f.read_all(buf, CFG_BUF_SIZE)
f.close()
if bytes_read <= 0:
return 1
# 解析 JSON
root: JsonValue | t.CPtr = json_parse(_mbuddy, buf)
if root is None:
return 1
if not root.is_object():
return 1
# 读取顶层字段
SourceDir = root["source_dir"].as_string() if root["source_dir"] is not None else None
TempDir = root["temp_dir"].as_string() if root["temp_dir"] is not None else None
OutputDir = root["output_dir"].as_string() if root["output_dir"] is not None else None
ProjectName = root["name"].as_string() if root["name"] is not None else None
ProjectVersion = root["version"].as_string() if root["version"] is not None else None
# 读取 compiler 子对象
compiler: JsonValue | t.CPtr = root["compiler"]
if compiler is not None and compiler.is_object():
CompilerCmd = compiler["cmd"].as_string() if compiler["cmd"] is not None else None
# 读取 compiler.flags 数组,拼接成空格分隔的字符串
cc_flags_val: JsonValue | t.CPtr = compiler["flags"]
CompilerFlags = _join_flags_array(cc_flags_val)
# 读取 linker 子对象
linker: JsonValue | t.CPtr = root["linker"]
if linker is not None and linker.is_object():
LinkerCmd = linker["cmd"].as_string() if linker["cmd"] is not None else None
LinkerOutput = linker["output"].as_string() if linker["output"] is not None else None
# 读取 linker.flags 数组,拼接成空格分隔的字符串
ld_flags_val: JsonValue | t.CPtr = linker["flags"]
LinkerFlags = _join_flags_array(ld_flags_val)
# 读取 target 子对象
target: JsonValue | t.CPtr = root["target"]
if target is not None and target.is_object():
TargetTriple = target["triple"].as_string() if target["triple"] is not None else None
TargetDataLayout = target["datalayout"].as_string() if target["datalayout"] is not None else None
# 读取 options 子对象
options: JsonValue | t.CPtr = root["options"]
if options is not None and options.is_object():
sl: JsonValue | t.CPtr = options["slice_level"]
if sl is not None and sl.is_int():
SliceLevel = sl.as_int()
sm: JsonValue | t.CPtr = options["strict_mode"]
if sm is not None and sm.is_bool():
StrictMode = 1 if sm.as_bool() else 0
# 读取 includes 数组(取首个元素作为 includes 目录)
includes_val: JsonValue | t.CPtr = root["includes"]
if includes_val is not None and includes_val.is_array():
arr_len: t.CSizeT = includes_val.__len__()
if arr_len > 0:
first_inc: JsonValue | t.CPtr = includes_val.get_item(0)
if first_inc is not None and first_inc.is_string():
IncludesDir = first_inc.as_string()
return 0
def _join_flags_array(flags_val: JsonValue | t.CPtr) -> str:
"""将 JSON 字符串数组拼接成空格分隔的字符串
用于 linker.flags 和 compiler.flags 数组。
用 while + 索引遍历,避免 list 迭代器 bug。
Args:
flags_val: JsonValue应为字符串数组
Returns:
空格分隔的字符串,或 None 如果 flags_val 为 None 或非数组或空数组
"""
if flags_val is None:
return None
if not flags_val.is_array():
return None
arr_len: t.CSizeT = flags_val.__len__()
if arr_len == 0:
return None
# 第一遍:计算总长度(所有 flag 长度 + 空格分隔符)
total_len: t.CSizeT = 0
i: t.CSizeT = 0
while i < arr_len:
item: JsonValue | t.CPtr = flags_val.get_item(i)
if item is not None and item.is_string():
s: str = item.as_string()
if s is not None:
total_len += string.strlen(s) + 1 # +1 for space
i += 1
if total_len == 0:
return None
# 分配缓冲区total_len 已含每个 flag 后的空格,最后一个是 '\0'
buf: str = _mbuddy.alloc(total_len + 1)
if buf is None:
return None
# 第二遍拼接flag 之间用空格分隔)
pos: t.CSizeT = 0
i = 0
while i < arr_len:
item = flags_val.get_item(i)
if item is not None and item.is_string():
s = item.as_string()
if s is not None:
slen: t.CSizeT = string.strlen(s)
if pos > 0:
buf[pos] = ' '
pos += 1
string.strcpy(buf + pos, s)
pos += slen
i += 1
buf[pos] = '\0'
return buf
def _join_path(rel: str, project_dir: str) -> str:
"""将相对路径与 project_dir 拼接为绝对路径
- ./App + Test → Test/App
- ./temp + Test → Test/temp
- ../includes + Test → Test/../includesOS 解析为 includes
- 绝对路径保持不变
- None 返回 None
"""
if rel is None:
return None
# 绝对路径(以 / 或 \ 开头)保持不变
if rel[0] == '/' or rel[0] == '\\':
return rel
rel_len: t.CSizeT = string.strlen(rel)
dir_len: t.CSizeT = string.strlen(project_dir)
# 处理 ../ 或 ..\\ 前缀:拼接 project_dir/../...
if rel_len >= 3 and rel[0] == '.' and rel[1] == '.':
if rel[2] == '/' or rel[2] == '\\':
if dir_len == 0:
return rel
total_len: t.CSizeT = dir_len + 1 + rel_len + 1
buf: str = _mbuddy.alloc(total_len)
if buf is None:
return rel
viperlib.snprintf(buf, total_len, "%s/%s", project_dir, rel)
return buf
# 处理 ./ 或 .\\ 前缀:拼接 project_dir + rel[2:]
rel_off: t.CSizeT = 0
if rel_len >= 2 and rel[0] == '.':
if rel[1] == '/' or rel[1] == '\\':
rel_off = 2
else:
return rel # 不是 ./ 开头,保持不变
else:
return rel # 不是 . 开头,保持不变
if dir_len == 0:
# project_dir 为空(当前目录),去掉 ./ 前缀即可
return rel + rel_off
# 拼接: project_dir + "/" + rel[rel_off:]
total_len = dir_len + 1 + (rel_len - rel_off) + 1
buf = _mbuddy.alloc(total_len)
if buf is None:
return rel # 分配失败,返回原路径
viperlib.snprintf(buf, total_len, "%s/%s", project_dir, rel + rel_off)
return buf
def resolve_paths(project_dir: str) -> int:
"""解析并规范化工程路径src/temp/output/includes
将 project.vpj 中的相对路径(以 ./ 开头)转换为基于 project_dir 的路径。
Args:
project_dir: 工程根目录project.vpj 所在目录)
Returns:
0 表示成功,非 0 表示失败
"""
global SourceDir, TempDir, OutputDir, IncludesDir
if project_dir is None:
return 1
if _mbuddy is None:
return 1
SourceDir = _join_path(SourceDir, project_dir)
TempDir = _join_path(TempDir, project_dir)
OutputDir = _join_path(OutputDir, project_dir)
# includes 路径可能是 ../includes 形式,需要基于 project_dir 解析
if IncludesDir is not None:
IncludesDir = _join_path(IncludesDir, project_dir)
return 0
def get_includes_binary_dir() -> str:
"""获取 includes.binary 目录路径includes 目录 + .binary 后缀)
includes 目录如 ../includes则 includes.binary 为 ../includes.binary
"""
if IncludesDir is None:
return None
inc_len: t.CSizeT = string.strlen(IncludesDir)
buf: str = _mbuddy.alloc(inc_len + 8)
if buf is None:
return None
viperlib.snprintf(buf, inc_len + 8, "%s.binary", IncludesDir)
return buf
def print_config():
"""打印当前配置(调试用)"""
if ProjectName is not None:
stdio.printf(" project: %s v%s\n", ProjectName, ProjectVersion if ProjectVersion is not None else "?")
if SourceDir is not None:
stdio.printf(" source_dir: %s\n", SourceDir)
if TempDir is not None:
stdio.printf(" temp_dir: %s\n", TempDir)
if OutputDir is not None:
stdio.printf(" output_dir: %s\n", OutputDir)
if CompilerCmd is not None:
stdio.printf(" compiler: %s\n", CompilerCmd)
if LinkerCmd is not None:
stdio.printf(" linker: %s -> %s\n", LinkerCmd, LinkerOutput if LinkerOutput is not None else "?")
if IncludesDir is not None:
stdio.printf(" includes: %s\n", IncludesDir)

View File

@@ -0,0 +1,117 @@
import t, c
from stdint import *
import memhub
import string
import stdlib
import viperlib
import hashlib
import w32.win32file
import w32.win32base
# ============================================================
# Utils - 工程工具函数
#
# 提供 SHA1 计算和目录清理等通用工具函数。
# ============================================================
# 全局 mbuddy 指针
_mbuddy: memhub.MemManager | t.CPtr
# ============================================================
# compute_sha1 - 计算字符串的 SHA1返回前 16 个十六进制字符
#
# 使用 includes/hashlib 库的 sha1 类计算摘要,转为十六进制字符串。
# 参考 Projectrans.py 的 compute_sha1hashlib.sha1().hexdigest()[:16])。
# ============================================================
def compute_sha1(pool: memhub.MemBuddy | t.CPtr, content: str) -> str:
"""计算字符串的 SHA1返回前 16 个十六进制字符(用于文件命名)"""
if content is None or pool is None:
return None
# 构造 sha1 对象__init__ 会初始化 state/count/buf
# ctx 在栈上分配alloca函数内使用完即丢弃无需堆分配
ctx: hashlib.sha1 | t.CPtr = hashlib.sha1()
if ctx is None:
return None
# 计算摘要
ctx.update(content)
digest: bytes = pool.alloc(hashlib.SHA1_DIGEST_LEN)
if digest is None:
return None
ctx.final(digest)
# 转为十六进制字符串(取前 8 字节 = 16 个十六进制字符)
hex_buf: str = pool.alloc(17)
if hex_buf is None:
return None
for i in range(8):
hi: int = (digest[i] >> 4) & 0xF
lo: int = digest[i] & 0xF
if hi < 10:
hex_buf[i * 2] = '0' + hi
else:
hex_buf[i * 2] = 'a' + (hi - 10)
if lo < 10:
hex_buf[i * 2 + 1] = '0' + lo
else:
hex_buf[i * 2 + 1] = 'a' + (lo - 10)
hex_buf[16] = '\0'
return hex_buf
# ============================================================
# CleanDir - 删除目录中所有文件(非递归,保留目录本身)
#
# 用于 --clean 选项:清理 temp_dir 和 output_dir 中的旧文件。
# ============================================================
def CleanDir(dir_path: str) -> int:
"""删除目录中所有文件(非递归),返回删除的文件数,-1 表示错误"""
if dir_path is None:
return -1
dir_len: t.CSizeT = string.strlen(dir_path)
pattern: bytes = stdlib.malloc(dir_len + 8)
if pattern is None:
return -1
viperlib.snprintf(pattern, dir_len + 8, "%s/*", dir_path)
find_data: w32.win32file.WIN32_FIND_DATAA | t.CPtr = stdlib.malloc(w32.win32file.WIN32_FIND_DATAA.__sizeof__())
if find_data is None:
stdlib.free(pattern)
return -1
string.memset(find_data, 0, w32.win32file.WIN32_FIND_DATAA.__sizeof__())
handle: w32.win32base.HANDLE = w32.win32file.FindFirstFileA(pattern, find_data)
if handle == w32.win32base.INVALID_HANDLE_VALUE:
stdlib.free(pattern)
stdlib.free(find_data)
return 0
deleted: int = 0
while 1:
fname: str = find_data.cFileName
if fname is not None:
# 跳过 . 和 ..
if fname[0] == '.':
if fname[1] == '\0':
fname = None
elif fname[1] == '.' and fname[2] == '\0':
fname = None
if fname is not None:
fname_len: t.CSizeT = string.strlen(fname)
full_path: bytes = stdlib.malloc(dir_len + fname_len + 2)
if full_path is not None:
viperlib.snprintf(full_path, dir_len + fname_len + 2, "%s/%s", dir_path, fname)
if w32.win32file.DeleteFileA(full_path) != 0:
deleted += 1
stdlib.free(full_path)
if w32.win32file.FindNextFileA(handle, find_data) == 0:
break
w32.win32file.FindClose(handle)
stdlib.free(pattern)
stdlib.free(find_data)
return deleted

View File

@@ -0,0 +1,12 @@
import t, c
from stdint import *
from .Config import Load_project_config, resolve_paths
from .Utils import compute_sha1, CleanDir
__all__ = [
'Load_project_config',
'resolve_paths',
'compute_sha1',
'CleanDir',
]