# -*- 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_
.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()