gir2def.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. #!/usr/bin/env python3
  2. """gir2def.py - generate GNU Modula-2 FOR "C" .def bindings from GIR XML.
  3. This is the optional broad-coverage companion to the hand-written bindings
  4. in src/. It reads a GObject-Introspection .gir file (for example
  5. /usr/share/gir-1.0/Gtk-4.0.gir) and emits, per class/interface/record,
  6. a DEFINITION MODULE FOR "C" containing its constructors, methods and
  7. functions, plus modules for namespace-level functions and enumerations.
  8. Design goals and limits
  9. -----------------------
  10. * Exact C symbol names: the procedure name is the GIR `c:identifier`,
  11. so the generated .def links straight against the C library.
  12. * One module per class/interface/record, named <Namespace><Name>
  13. (GtkButton, ...), mirroring the hand-written layout.
  14. * All instance/opaque/unknown/pointer types map to ADDRESS; scalars map
  15. to the closest Modula-2 type. Value structs are therefore treated as
  16. opaque - fine for pointers, not for by-value records.
  17. * GError** parameters, closure/destroy user-data and array-length
  18. parameters are dropped. Variadic functions are skipped.
  19. * Return values are always emitted as ignorable, `: [ T ]`.
  20. * Everything is written into the output directory (default gen/); the
  21. generated files are meant to be reviewed and cherry-picked, not to
  22. replace src/.
  23. Usage:
  24. tools/gir2def.py Gtk-4.0 -o gen
  25. tools/gir2def.py GLib-2.0 --gir-dir /usr/share/gir-1.0 -o gen
  26. """
  27. import argparse
  28. import os
  29. import re
  30. import sys
  31. import xml.etree.ElementTree as ET
  32. CORE = "http://www.gtk.org/introspection/core/1.0"
  33. C = "http://www.gtk.org/introspection/c/1.0"
  34. GLIB = "http://www.gtk.org/introspection/glib/1.0"
  35. def q(ns, tag):
  36. return "{%s}%s" % (ns, tag)
  37. def kind(el):
  38. return el.tag.split("}")[-1]
  39. # scalar GIR type name -> Modula-2 type
  40. SCALAR = {
  41. "gboolean": "INTEGER", "gint": "INTEGER", "int": "INTEGER",
  42. "gshort": "INTEGER", "short": "INTEGER",
  43. "gint8": "INTEGER", "gint16": "INTEGER", "gint32": "INTEGER",
  44. "glong": "LONGINT", "gint64": "LONGINT", "gssize": "LONGINT",
  45. "goffset": "LONGINT", "long": "LONGINT",
  46. "guint": "CARDINAL", "unsigned int": "CARDINAL", "gushort": "CARDINAL",
  47. "guint8": "CARDINAL", "guint16": "CARDINAL", "guint32": "CARDINAL",
  48. "gunichar": "CARDINAL",
  49. "gulong": "LONGCARD", "guint64": "LONGCARD", "gsize": "LONGCARD",
  50. "size_t": "LONGCARD",
  51. "GType": "LONGCARD", "gtype": "LONGCARD",
  52. "gchar": "CHAR", "guchar": "CHAR", "char": "CHAR",
  53. "gfloat": "SHORTREAL", "float": "SHORTREAL",
  54. "gdouble": "REAL", "double": "REAL",
  55. "gpointer": "ADDRESS", "gconstpointer": "ADDRESS", "va_list": "ADDRESS",
  56. }
  57. SCALAR_TYPES = set(SCALAR.values()) - {"ADDRESS"}
  58. STRING_TYPES = {"utf8", "filename"}
  59. # Modula-2 reserved words that cannot be used verbatim as a parameter name.
  60. RESERVED = {
  61. "and", "array", "begin", "by", "case", "const", "definition", "div",
  62. "do", "else", "elsif", "end", "exit", "export", "for", "from",
  63. "if", "implementation", "import", "in", "loop", "mod", "module",
  64. "not", "of", "or", "pointer", "procedure", "qualified", "record",
  65. "repeat", "return", "set", "then", "to", "type", "until", "var",
  66. "while", "with", "as", "class", "generic",
  67. }
  68. def ctype_of(t):
  69. if t is None:
  70. return ""
  71. return (t.get(q(C, "type")) or "").replace("const ", "").strip()
  72. def is_string(t):
  73. if t is None:
  74. return False
  75. name = t.get("name")
  76. if name not in STRING_TYPES:
  77. return False
  78. ct = ctype_of(t)
  79. return ct in ("char*", "gchar*", "gchar**", "char**") or ct.endswith("char*")
  80. def has_array(t):
  81. return t is not None and t.find(q(CORE, "array")) is not None
  82. class Namespace:
  83. def __init__(self, root):
  84. self.root = root
  85. self.ns = root.find(q(CORE, "namespace"))
  86. self.name = self.ns.get("name")
  87. self.enums = set()
  88. for ch in self.ns:
  89. if kind(ch) in ("enumeration", "bitfield"):
  90. self.enums.add(ch.get("name"))
  91. def scalar_of(self, t):
  92. """Map a type used as a plain value (no VAR/pointer semantics)."""
  93. if t is None:
  94. return None
  95. name = t.get("name")
  96. if name in self.enums:
  97. return "CARDINAL"
  98. return SCALAR.get(name)
  99. def map_in(self, t):
  100. if t is None:
  101. return "ADDRESS"
  102. if has_array(t):
  103. return "ADDRESS"
  104. if is_string(t):
  105. return "ARRAY OF CHAR"
  106. if ctype_of(t).endswith("*"):
  107. return "ADDRESS"
  108. name = t.get("name")
  109. if name in self.enums:
  110. return "CARDINAL"
  111. return SCALAR.get(name, "ADDRESS")
  112. def map_return(self, t):
  113. if t is None:
  114. return None
  115. if has_array(t) or is_string(t):
  116. return "ADDRESS"
  117. if ctype_of(t).endswith("*"):
  118. return "ADDRESS"
  119. name = t.get("name")
  120. if name in self.enums:
  121. return "CARDINAL"
  122. return SCALAR.get(name, "ADDRESS")
  123. def map_param(self, t, direction):
  124. if direction in ("out", "inout"):
  125. if t is None or has_array(t) or is_string(t):
  126. return "ADDRESS"
  127. scalar = self.scalar_of(t)
  128. if scalar in SCALAR_TYPES:
  129. return "VAR " + scalar
  130. return "ADDRESS"
  131. return self.map_in(t)
  132. def visible_params(params_el):
  133. """Return (list of parameter elements) with GIR-internal ones removed."""
  134. if params_el is None:
  135. return []
  136. plist = list(params_el)
  137. hidden = set()
  138. for i, p in enumerate(plist):
  139. if "closure" in p.attrib or "destroy" in p.attrib:
  140. hidden.add(i)
  141. arr = p.find(q(CORE, "array"))
  142. if arr is not None and arr.get("length"):
  143. hidden.add(int(arr.get("length")))
  144. out = []
  145. for i, p in enumerate(plist):
  146. if i in hidden:
  147. continue
  148. t = p.find(q(CORE, "type"))
  149. if t is not None and t.get("name") == "GLib.Error":
  150. continue
  151. out.append(p)
  152. return out
  153. def sanitize(name, used):
  154. if not name:
  155. name = "arg"
  156. name = re.sub(r"\W", "_", name)
  157. if name in RESERVED:
  158. name += "_"
  159. base = name
  160. k = 2
  161. while name in used:
  162. name = "%s_%d" % (base, k)
  163. k += 1
  164. used.add(name)
  165. return name
  166. def emit_callable(ns, el, self_alias):
  167. """Emit one PROCEDURE line for a constructor/method/function, or None."""
  168. cid = el.get(q(C, "identifier"))
  169. if not cid:
  170. return None
  171. if el.get("moved-to") is not None:
  172. return None
  173. if el.find(q(CORE, "varargs")) is not None:
  174. return None
  175. parts = []
  176. used = set()
  177. inst = el.find(q(CORE, "instance-parameter"))
  178. params = []
  179. if inst is not None:
  180. params.append(inst)
  181. params.extend(visible_params(el.find(q(CORE, "parameters"))))
  182. inst_el = inst
  183. for p in params:
  184. t = p.find(q(CORE, "type"))
  185. direction = p.get("direction", "in")
  186. tname = t.get("name") if t is not None else None
  187. if tname == ns.ns.get("name") or (self_alias and tname is not None
  188. and tname == self_alias[0]):
  189. mtype = self_alias[1]
  190. else:
  191. mtype = ns.map_param(t, direction)
  192. pname = sanitize(p.get("name"), used)
  193. if mtype.startswith("VAR "):
  194. parts.append("VAR %s: %s" % (pname, mtype[4:]))
  195. else:
  196. parts.append("%s: %s" % (pname, mtype))
  197. rv = el.find(q(CORE, "return-value"))
  198. rtype = None
  199. if rv is not None:
  200. t = rv.find(q(CORE, "type"))
  201. if t is not None and t.get("name") != "none":
  202. rtype = ns.map_return(t)
  203. sig = "PROCEDURE %s (%s)" % (cid, "; ".join(parts))
  204. if rtype:
  205. sig += " : [ %s ];" % rtype
  206. else:
  207. sig += ";"
  208. return cid, sig
  209. def emit_type_module(ns, el, outdir):
  210. name = el.get("name")
  211. if not name:
  212. return 0
  213. modname = ns.name + name
  214. type_name = el.get(q(GLIB, "type-name")) or modname
  215. procs = []
  216. seen = set()
  217. for child in el:
  218. if kind(child) in ("constructor", "method", "function"):
  219. res = emit_callable(ns, child, (name, type_name))
  220. if res and res[0] not in seen:
  221. seen.add(res[0])
  222. procs.append(res)
  223. if not procs:
  224. return 0
  225. lines = []
  226. lines.append('DEFINITION MODULE FOR "C" %s ;' % modname)
  227. lines.append("")
  228. lines.append("(* Generated by tools/gir2def.py from %s %s. *)"
  229. % (ns.name, ns.ns.get("version")))
  230. lines.append("")
  231. lines.append("FROM SYSTEM IMPORT ADDRESS;")
  232. lines.append("")
  233. lines.append("EXPORT UNQUALIFIED")
  234. lines.append(" %s," % type_name)
  235. for i, (cid, _) in enumerate(procs):
  236. end = "," if i < len(procs) - 1 else ";"
  237. lines.append(" %s%s" % (cid, end))
  238. lines.append("")
  239. lines.append("TYPE")
  240. lines.append(" %s = ADDRESS;" % type_name)
  241. lines.append("")
  242. for cid, sig in procs:
  243. lines.append("(* %s *)" % cid)
  244. lines.append(sig)
  245. lines.append("")
  246. lines.append("END %s." % modname)
  247. lines.append("")
  248. with open(os.path.join(outdir, modname + ".def"), "w") as f:
  249. f.write("\n".join(lines))
  250. return len(procs)
  251. def emit_functions_module(ns, outdir):
  252. procs = []
  253. seen = set()
  254. for child in ns.ns:
  255. if kind(child) == "function":
  256. res = emit_callable(ns, child, None)
  257. if res and res[0] not in seen:
  258. seen.add(res[0])
  259. procs.append(res)
  260. if not procs:
  261. return 0
  262. modname = ns.name + "Functions"
  263. lines = []
  264. lines.append('DEFINITION MODULE FOR "C" %s ;' % modname)
  265. lines.append("")
  266. lines.append("(* Generated by tools/gir2def.py: namespace-level functions. *)")
  267. lines.append("")
  268. lines.append("FROM SYSTEM IMPORT ADDRESS;")
  269. lines.append("")
  270. lines.append("EXPORT UNQUALIFIED")
  271. for i, (cid, _) in enumerate(procs):
  272. end = "," if i < len(procs) - 1 else ";"
  273. lines.append(" %s%s" % (cid, end))
  274. lines.append("")
  275. for cid, sig in procs:
  276. lines.append("(* %s *)" % cid)
  277. lines.append(sig)
  278. lines.append("")
  279. lines.append("END %s." % modname)
  280. lines.append("")
  281. with open(os.path.join(outdir, modname + ".def"), "w") as f:
  282. f.write("\n".join(lines))
  283. return len(procs)
  284. def emit_enums_module(ns, outdir):
  285. consts = []
  286. seen = set()
  287. for child in ns.ns:
  288. if kind(child) not in ("enumeration", "bitfield"):
  289. continue
  290. value = 0
  291. for member in child:
  292. if kind(member) != "member":
  293. continue
  294. cid = member.get(q(C, "identifier"))
  295. if not cid or cid in seen:
  296. continue
  297. seen.add(cid)
  298. v = member.get("value")
  299. if v is not None:
  300. value = int(v)
  301. consts.append((cid, value))
  302. value += 1
  303. if not consts:
  304. return 0
  305. modname = ns.name + "Enums"
  306. lines = []
  307. lines.append('DEFINITION MODULE FOR "C" %s ;' % modname)
  308. lines.append("")
  309. lines.append("(* Generated by tools/gir2def.py: enumerations and bitfields. *)")
  310. lines.append("")
  311. lines.append("EXPORT UNQUALIFIED")
  312. for i, (cid, _) in enumerate(consts):
  313. end = "," if i < len(consts) - 1 else ";"
  314. lines.append(" %s%s" % (cid, end))
  315. lines.append("")
  316. lines.append("CONST")
  317. for cid, value in consts:
  318. if value >= 0:
  319. lines.append(" %s = %d;" % (cid, value))
  320. else:
  321. lines.append(" %s = %d;" % (cid, value))
  322. lines.append("")
  323. lines.append("END %s." % modname)
  324. lines.append("")
  325. with open(os.path.join(outdir, modname + ".def"), "w") as f:
  326. f.write("\n".join(lines))
  327. return len(consts)
  328. def collect_identifiers(ns):
  329. """All C function identifiers this generator would emit for a namespace."""
  330. ids = []
  331. for child in ns.ns:
  332. k = kind(child)
  333. if k in ("class", "interface", "record", "boxed"):
  334. for sub in child:
  335. if kind(sub) in ("constructor", "method", "function"):
  336. cid = sub.get(q(C, "identifier"))
  337. if (cid and sub.get("moved-to") is None
  338. and sub.find(q(CORE, "varargs")) is None):
  339. ids.append(cid)
  340. elif k == "function":
  341. cid = child.get(q(C, "identifier"))
  342. if (cid and child.get("moved-to") is None
  343. and child.find(q(CORE, "varargs")) is None):
  344. ids.append(cid)
  345. return ids
  346. def main():
  347. ap = argparse.ArgumentParser(description=__doc__,
  348. formatter_class=argparse.RawDescriptionHelpFormatter)
  349. ap.add_argument("namespace", help="e.g. Gtk-4.0 or GLib-2.0")
  350. ap.add_argument("-o", "--output", default="gen", help="output directory")
  351. ap.add_argument("--gir-dir", default="/usr/share/gir-1.0",
  352. help="directory containing <namespace>.gir")
  353. args = ap.parse_args()
  354. gir_path = os.path.join(args.gir_dir, args.namespace + ".gir")
  355. if not os.path.exists(gir_path):
  356. sys.stderr.write("gir2def: not found: %s\n" % gir_path)
  357. return 2
  358. os.makedirs(args.output, exist_ok=True)
  359. root = ET.parse(gir_path).getroot()
  360. ns = Namespace(root)
  361. modules = 0
  362. procs = 0
  363. for child in ns.ns:
  364. if kind(child) in ("class", "interface", "record", "boxed"):
  365. n = emit_type_module(ns, child, args.output)
  366. if n:
  367. modules += 1
  368. procs += n
  369. procs += emit_functions_module(ns, args.output)
  370. consts = emit_enums_module(ns, args.output)
  371. print("gir2def: %s -> %s (%d type modules, %d procedures, %d enum constants)"
  372. % (args.namespace, args.output, modules, procs, consts))
  373. return 0
  374. if __name__ == "__main__":
  375. sys.exit(main())