Files
Viper_VRTE/fltk/gen_ffi.py
T

453 lines
16 KiB
Python
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.
# -*- coding: utf-8 -*-
"""
fltk/gen_ffi.py — cfltk (FLTK C API) → Viper extern FFI 生成器
从 cfltk/include/cfltk2/*.h 读取全部 C 声明:
* 解析所有 `#define XXX_DECLARE(param) ...` 宏定义(WIDGET/GROUP/WINDOW/
BUTTON/INPUT/BROWSER/IMAGE/TABLE/DISPLAY/VALUATOR/MENU),
* 按每个头文件里的 `XXX_DECLARE(Class)` 调用展开,
* 连同头文件中的独立函数原型一起转成 extern def。
生成 includes/fltk 下的 .vp FFI 文件:
_types.vp 不透明指针 typedef(每个 Fl_* 类一个 Fl_*Ptr)
_enums.vp cfl_enums.h 的全部枚举常量
_cfl.vp 全局 Fl 函数(cfl.h)
_cfl_<header>.vp 各头文件对应 extern def
用法: python includes/fltk/gen_ffi.py
"""
import os
import re
HERE = os.path.dirname(os.path.abspath(__file__))
SRC = os.path.join(HERE, "cfltk", "include", "cfltk2")
# ============================================================
# C → Viper 类型映射
# ============================================================
# 函数指针类型(Viper 无法表达,一律映射为不透明 bytes)
CALLBACK_TYPES = {
"Fl_Callback", "custom_handler_callback", "custom_draw_callback",
"Fl_Awake_Handler", "Fl_Text_Modify_Cb", "Fl_Text_Predelete_Cb",
}
# Viper/Python 关键字不能作参数名(C 里 `int Fl_compose(int *del)` 之类)
_KEYWORDS = {
"def", "class", "from", "import", "global", "nonlocal", "pass", "lambda",
"del", "in", "is", "and", "or", "not", "for", "while", "if", "elif",
"else", "return", "break", "continue", "try", "except", "finally", "with",
"yield", "assert", "async", "await", "raise", "True", "False", "None",
"int", "str", "float", "bool", "len",
}
# C 类型关键字:作无名参数末尾单词时属于类型的一部分而非参数名
# (`unsigned int` → 末尾 int 不是参数名;`Fl_color_average(unsigned int, ...)`)。
_C_TYPE_WORDS = {
"int", "char", "short", "long", "unsigned", "signed", "float", "double",
"void", "size_t", "uintptr_t",
}
def map_type(t: str) -> str | None:
"""返回 Viper 注解类型;void 返回 None。"""
t = t.strip()
t = re.sub(r"\bconst\b", "", t).strip()
t = re.sub(r"\bstruct\b", "", t).strip()
t = re.sub(r"\s+", " ", t)
if t in CALLBACK_TYPES:
return "bytes"
if "(" in t: # 函数指针(未命名的 typedef 形态)
return "bytes"
if t.endswith("[]"):
t = t[:-2].strip() + "*"
star_cnt = t.count("*")
if star_cnt:
base = t.replace("*", "").strip()
if star_cnt >= 2: # 指针的指针(int** 等)一律按不透明指针
return "bytes"
if base == "char":
return "str"
if base in CALLBACK_TYPES:
return "bytes"
if base.startswith("Fl_") or base.startswith("custom_"):
return base + "Ptr"
return "bytes"
if t == "void":
return None
if t == "int":
return "int"
if t == "unsigned int":
return "t.CUInt32T"
if t == "long":
return "t.CInt32T"
if t == "unsigned long":
return "t.CUInt32T"
if t == "short":
return "t.CInt16T"
if t == "unsigned short":
return "t.CUInt16T"
if t == "char":
return "t.CChar"
if t == "unsigned char":
return "t.CUInt8T"
if t == "double":
return "t.CDouble"
if t == "float":
return "t.CFloat"
if t == "uintptr_t":
return "t.CSizeT"
if t == "size_t":
return "t.CSizeT"
if t.startswith("Fl_") or t.startswith("custom_"):
return t + "Ptr"
return "bytes" # 未知类型一律按不透明指针处理
def split_args(args: str) -> list[tuple[str, str]]:
"""拆分 C 参数串 → [(类型, 参数名)],函数指针/无名参数自动补名。"""
if not args.strip():
return []
parts: list[str] = []
depth = 0
cur: list[str] = []
for ch in args:
if ch == "(":
depth += 1
elif ch == ")":
depth -= 1
if ch == "," and depth == 0:
parts.append("".join(cur).strip())
cur = []
else:
cur.append(ch)
if cur:
parts.append("".join(cur).strip())
out: list[tuple[str, str]] = []
for p in parts:
p = p.strip()
if not p or p == "void": # C 的 (void) 表示无参数
continue
if "(" in p and (")(" in p or "*)" in p or re.search(r"\(\s*\*", p)):
out.append((p, "cb%d" % len(out)))
continue
# C 参数形如 `Fl_Tree *self` / `const char *title`:'*' 紧贴参数名,
# 末尾单词即为参数名,其余部分为类型
m = re.search(r"(\w+)\s*$", p)
if m:
typ = p[:m.start()].strip()
nm = m.group(1)
# 纯类型(无名)参数:无类型部分(`int`)或末尾单词本身是 C 类型
# 关键字(`unsigned int` → 末尾 int 是类型的一部分,非参数名)。
if not typ or nm in _C_TYPE_WORDS:
typ, nm = p, "a%d" % len(out)
if nm in _KEYWORDS:
nm = nm + "_"
out.append((typ, nm))
else:
out.append((p, "a%d" % len(out)))
return out
def emit_extern(name: str, params: list[tuple[str, str]], ret_t: str | None,
indent: str = "") -> str:
ps = ", ".join("%s: %s" % (pn, map_type(pt)) for pt, pn in params)
rt = map_type(ret_t) if ret_t else None
if rt is None: # void / 无返回
return "%sextern def %s(%s)" % (indent, name, ps)
return "%sextern def %s(%s) -> %s" % (indent, name, ps, rt)
# ============================================================
# 头文件解析
# ============================================================
MACRO_CALL_RE = re.compile(r"(?m)^\s*(\w+_DECLARE)\((\w+)\)\s*$")
def strip_macro_blocks(text: str) -> str:
"""删除 #define 宏定义块(含全部反斜杠续行),逐行处理兼容 CRLF。"""
out: list[str] = []
skip = False
for line in text.split("\n"):
if re.match(r"\s*#define\b", line):
skip = line.rstrip().endswith("\\")
continue
if skip:
if line.rstrip().endswith("\\"):
continue
skip = False
continue
out.append(line)
return "\n".join(out)
def strip_preprocessor(text: str) -> str:
text = strip_macro_blocks(text)
text = re.sub(r"typedef[^;]*;", "", text, flags=re.S) # typedef
text = re.sub(r"/\*.*?\*/", "", text, flags=re.S) # 注释
return text
def extract_protos(text: str) -> list[tuple[str, str, str]]:
"""提取独立函数原型 → [(返回类型, 函数名, 参数串)]。
注意 C 的 `类型 *名字` 中 '*' 紧贴函数名、与类型之间才有空格,
因此不能用空白分隔的 `类型 名字(` 拆分;改为取第一个 '(' 之前末尾的单词为
函数名,其余为返回类型。
"""
text = strip_preprocessor(text)
text = re.sub(r"(?m)^\s*#.*$", "", text) # 其余预处理行
text = re.sub(MACRO_CALL_RE, "", text)
protos: list[tuple[str, str, str]] = []
buf: list[str] = []
depth = 0
for ch in text:
if ch == "(":
depth += 1
elif ch == ")":
depth -= 1
if depth == 0 and ch in "{}":
buf = [] # extern "C" { / 块花括号不进入原型缓冲
continue
if ch == ";" and depth == 0:
stmt = "".join(buf).strip()
buf = []
if "(" in stmt and stmt.rstrip().endswith(")"):
open_i = stmt.find("(")
before = stmt[:open_i].strip()
args = stmt[open_i + 1:].rstrip()[:-1].strip()
mm = re.search(r"(\w+)\s*$", before)
if mm:
name = mm.group(1)
if name and not name.startswith(("Fl_EventNames",)):
ret = re.sub(r"\s+", " ", before[:mm.start()].strip())
protos.append((ret, name, args))
continue
buf.append(ch)
return protos
def extract_macro_defs(text: str) -> dict:
"""解析 `#define XXX_DECLARE(param)` 宏体 → 方法模板。
返回 {宏名: (param, [(方法后缀, 参数串, 返回类型, 'SELF'|'PARAMS'), ...])}
'SELF' 表示首参为该类自身指针(展开时替换为 self: <类>Ptr),
'PARAMS' 表示保留参数原样(new/from_dyn_ptr/super_draw 等)。
"""
out: dict = {}
lines = text.split("\n")
i = 0
n = len(lines)
while i < n:
m = re.match(r"#define\s+(\w+_DECLARE)\((\w+)\)\s*\\\s*$", lines[i])
if m:
name, param = m.group(1), m.group(2)
body: list[str] = []
i += 1
while i < n:
line = lines[i]
if line.rstrip().endswith("\\"):
body.append(line.rstrip()[:-1])
i += 1
else:
body.append(line)
break
methods: list = []
body_src = "\n".join(body).replace("##", "") # widget##_x → widget_x
for ret, fname, args in extract_protos(body_src):
fn = fname.replace("##", "")
suffix = fn[len(param):].lstrip("_")
parts = split_args(args)
if parts and parts[0][0].replace("const", "").strip().rstrip("*").strip() == param:
kind = "SELF"
else:
kind = "PARAMS"
methods.append((suffix, args, ret, kind))
out[name] = (param, methods)
i += 1
return out
def extract_typedefs(text: str) -> list[str]:
"""提取顶层 typedef 名称(先剥离 #define 宏体,避免误扫 widget 等宏形参)。"""
names = []
text = strip_macro_blocks(text)
for m in re.finditer(r"typedef\s+(?:struct\s+)?[\w\s\*\(\)]*?(\w+)\s*;", text):
n = m.group(1)
if n and n not in names:
names.append(n)
return names
def eval_enum_expr(s: str | None, last: int) -> int:
if s is None:
return last + 1
s = s.strip()
if not s:
return last + 1
s2 = re.sub(r"0[xX]([0-9a-fA-F]+)", lambda m: str(int(m.group(1), 16)), s)
try:
return int(eval(s2))
except Exception:
return last + 1
def extract_enums(text: str) -> list[tuple[str, int]]:
out = []
for m in re.finditer(r"enum\s+(\w+)\s*\{(.*?)\}", text, re.S):
body = m.group(2)
last = -1
for item in re.split(r"\s*,\s*(?=[A-Za-z_])", body):
item = item.strip()
if not item or item.startswith("/"):
continue
mm = re.match(r"^(\w+)(?:\s*=\s*(.+))?$", item, re.S)
if not mm:
continue
last = eval_enum_expr(mm.group(2), last)
out.append((mm.group(1), last))
return out
# ============================================================
# 生成
# ============================================================
CLASS_PTRS: set[str] = set()
def gen_class_methods(cls: str, param: str, methods) -> list[str]:
lines = []
for suffix, args, ret, kind in methods:
fname = "%s_%s" % (cls, suffix)
ret_t = re.sub(r"\b%s\b" % re.escape(param), cls, ret)
if kind == "SELF":
rest = split_args(args)[1:] # 丢弃宏里的 self 参数
ps = [(cls + " *", "self")] + rest
else:
ps = split_args(args)
ps = [(re.sub(r"\b%s\b" % re.escape(param), cls, t), nm)
for t, nm in ps]
lines.append(emit_extern(fname, ps, ret_t))
return lines
def gen_header(path: str, out_name: str, defs: dict) -> str:
with open(path, "r", encoding="utf-8", errors="replace") as f:
text = f.read()
base = os.path.basename(path)
lines = [
"import t, c",
"from fltk._types import *",
"",
"# ============================================================",
"# fltk/%s — %s 的 extern FFI 声明(对接 cfltk C API)" % (out_name, base),
"# ============================================================",
"",
"# 所有函数直接对应 cfltk 生成的 C 符号(extern \"C\"),链接目标: cfltk2。",
"# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;",
"# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。",
"",
]
# 按头文件中宏调用的顺序展开
for mm in MACRO_CALL_RE.finditer(text):
kind, cls = mm.group(1), mm.group(2)
if kind in defs:
param, methods = defs[kind]
lines.append("# ----- %s (%s) -----" % (cls, kind.lower()))
lines.extend(gen_class_methods(cls, param, methods))
lines.append("")
# 独立原型
protos = extract_protos(text)
for ret, name, args in protos:
lines.append(emit_extern(name, split_args(args), ret))
return "\n".join(lines) + "\n"
def main() -> None:
os.makedirs(HERE, exist_ok=True)
headers = sorted(f for f in os.listdir(SRC) if f.endswith(".h"))
# 1. 收集全部宏定义 + 类指针 typedef
defs: dict = {}
for h in headers:
with open(os.path.join(SRC, h), "r", encoding="utf-8",
errors="replace") as f:
text = f.read()
defs.update(extract_macro_defs(text))
if h == "cfl_macros.h":
for n in extract_typedefs(text):
CLASS_PTRS.add(n + "Ptr")
if h == "cfl_enums.h":
continue
for mm in MACRO_CALL_RE.finditer(text):
ptr_name(mm.group(2))
for ret, name, args in extract_protos(text):
for t, _ in split_args(args):
mt = map_type(t)
if mt and mt.startswith("Fl_") and mt.endswith("Ptr"):
CLASS_PTRS.add(mt)
mt = map_type(ret)
if mt and mt.startswith("Fl_") and mt.endswith("Ptr"):
CLASS_PTRS.add(mt)
# 2. _types.vp
sorted_ptrs = sorted(CLASS_PTRS)
typ_lines = [
"import t, c",
"",
"# ============================================================",
"# fltk/_types.vp — FLTK 不透明对象指针 typedef(对接 cfltk C API)",
"# ============================================================",
"",
"# 所有 FLTK C++ 对象在 FFI 中一律按不透明指针处理,生命周期由",
"# cfltk/fltk 库管理(new/delete 见各 *_delete / *_free)。",
"",
]
for p in sorted_ptrs:
typ_lines.append("%s: t.CTypedef = t.CPtr" % p)
with open(os.path.join(HERE, "_types.vp"), "w", encoding="utf-8") as f:
f.write("\n".join(typ_lines) + "\n")
# 3. _enums.vp
with open(os.path.join(SRC, "cfl_enums.h"), "r", encoding="utf-8",
errors="replace") as f:
enum_text = f.read()
enums = extract_enums(enum_text)
enum_lines = [
"import t, c",
"",
"# ============================================================",
"# fltk/_enums.vp — cfltk 枚举常量(t.CInt)",
"# ============================================================",
"",
]
for name, val in enums:
enum_lines.append("%s: t.CInt = %d" % (name, val))
with open(os.path.join(HERE, "_enums.vp"), "w", encoding="utf-8") as f:
f.write("\n".join(enum_lines) + "\n")
# 4. 各头文件
for h in headers:
if h in ("cfl_macros.h", "cfl_enums.h"):
continue
stem = h[:-2] if h.endswith(".h") else h
if stem == "cfl":
out_name = "_cfl.vp"
elif stem.startswith("cfl_"):
out_name = "_cfl_%s.vp" % stem[4:]
else:
out_name = "_cfl_%s.vp" % stem
content = gen_header(os.path.join(SRC, h), out_name, defs)
with open(os.path.join(HERE, out_name), "w", encoding="utf-8") as f:
f.write(content)
print("generated: _types.vp, _enums.vp, %d header files" % len(headers))
def ptr_name(cls: str) -> str:
p = cls + "Ptr"
CLASS_PTRS.add(p)
return p
if __name__ == "__main__":
main()