453 lines
16 KiB
Python
453 lines
16 KiB
Python
# -*- 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()
|