mirror of
https://github.com/bkaradzic/bgfx.git
synced 2026-10-25 10:20:52 +00:00
python bindings follow-up (#3916)
* docs: update python bindings link * python bindings : generate matching .pyi file (stub file used for type checking / documenting public interface) * python bindings : make only .pyi have documentation comment / docstrings
This commit is contained in:
@@ -71,14 +71,39 @@ local opaque_types = {
|
||||
|
||||
local enum_counts = {}
|
||||
|
||||
local stub_primitive_map = {
|
||||
["bool"] = "bool",
|
||||
["char"] = "bytes",
|
||||
["float"] = "float",
|
||||
["int8_t"] = "int",
|
||||
["int16_t"] = "int",
|
||||
["int32_t"] = "int",
|
||||
["int64_t"] = "int",
|
||||
["uint8_t"] = "int",
|
||||
["uint16_t"] = "int",
|
||||
["uint32_t"] = "int",
|
||||
["uint64_t"] = "int",
|
||||
["uintptr_t"] = "int",
|
||||
["bgfx_view_id_t"] = "int",
|
||||
["va_list"] = "Any",
|
||||
}
|
||||
|
||||
local stub_type_info = {}
|
||||
|
||||
for _, typ in ipairs(idl.types) do
|
||||
if typ.enum then
|
||||
ctype_map[typ.cname] = "ctypes.c_int"
|
||||
enum_counts[typ.typename] = #typ.enum
|
||||
elseif typ.handle or typ.struct then
|
||||
stub_type_info[typ.cname] = { kind = "enum", name = python_type_name(typ) }
|
||||
elseif typ.handle then
|
||||
ctype_map[typ.cname] = python_type_name(typ)
|
||||
stub_type_info[typ.cname] = { kind = "handle", name = python_type_name(typ) }
|
||||
elseif typ.struct then
|
||||
ctype_map[typ.cname] = python_type_name(typ)
|
||||
stub_type_info[typ.cname] = { kind = "struct", name = python_type_name(typ) }
|
||||
elseif typ.args and typ.ret then
|
||||
ctype_map[typ.cname] = python_type_name(typ)
|
||||
stub_type_info[typ.cname] = { kind = "funcptr", name = python_type_name(typ) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -142,6 +167,73 @@ local function convert_ctype(arg, array_as_pointer)
|
||||
return result
|
||||
end
|
||||
|
||||
local function convert_stub_type(arg, array_as_pointer, context)
|
||||
if arg.ctype == "..." then
|
||||
return "Any"
|
||||
end
|
||||
|
||||
local ctype = normalize_ctype(arg.ctype)
|
||||
local is_const = ctype:match("^const ") ~= nil
|
||||
ctype = ctype:gsub("^const%s+", "")
|
||||
|
||||
local pointer_count = 0
|
||||
while ctype:sub(-1) == "*" do
|
||||
pointer_count = pointer_count + 1
|
||||
ctype = ctype:sub(1, -2)
|
||||
end
|
||||
|
||||
if arg.array and array_as_pointer then
|
||||
pointer_count = pointer_count + 1
|
||||
end
|
||||
|
||||
if ctype == "void" then
|
||||
if pointer_count == 0 then
|
||||
return "None"
|
||||
end
|
||||
return "Any"
|
||||
end
|
||||
|
||||
if ctype == "char" and pointer_count == 1 and is_const then
|
||||
return "Optional[bytes]"
|
||||
end
|
||||
|
||||
local info = stub_type_info[ctype]
|
||||
if pointer_count > 0 then
|
||||
if pointer_count == 1 and info and (info.kind == "struct" or info.kind == "handle") then
|
||||
if context == "arg" then
|
||||
return "Optional[Union[" .. info.name .. ", _Pointer[" .. info.name .. "], ctypes.Array]]"
|
||||
end
|
||||
return "_Pointer[" .. info.name .. "]"
|
||||
end
|
||||
return "Any"
|
||||
end
|
||||
|
||||
if info then
|
||||
if info.kind == "enum" then
|
||||
if context == "arg" then
|
||||
return "Union[" .. info.name .. ", int]"
|
||||
end
|
||||
return "int"
|
||||
elseif info.kind == "funcptr" then
|
||||
return "Any"
|
||||
end
|
||||
return info.name
|
||||
end
|
||||
|
||||
return assert(stub_primitive_map[ctype], "Unsupported Python stub type: " .. arg.ctype)
|
||||
end
|
||||
|
||||
local function convert_stub_field(member)
|
||||
if member.array then
|
||||
local ctype = normalize_ctype(member.ctype):gsub("^const%s+", "")
|
||||
if ctype == "char" then
|
||||
return "bytes"
|
||||
end
|
||||
return "ctypes.Array"
|
||||
end
|
||||
return convert_stub_type(member, false, "field")
|
||||
end
|
||||
|
||||
local function array_length(member)
|
||||
local number = member.array:match("^%[%s*(%d+)%s*%]$")
|
||||
if number then
|
||||
@@ -166,19 +258,22 @@ local function comment_lines(out, comments, indent)
|
||||
end
|
||||
end
|
||||
|
||||
local function gen_enums(out)
|
||||
for _, typ in ipairs(idl.types) do
|
||||
if typ.enum then
|
||||
comment_lines(out, typ.comments)
|
||||
local function gen_enums(out, comments)
|
||||
for _, typ in ipairs(idl.types) do
|
||||
if typ.enum then
|
||||
if comments then
|
||||
comment_lines(out, typ.comments)
|
||||
end
|
||||
append(out, "class " .. python_type_name(typ) .. "(enum.IntEnum):")
|
||||
|
||||
for index, item in ipairs(typ.enum) do
|
||||
comment_lines(out, item.comment, "\t")
|
||||
append(out, string.format("\t%s = %d", py_ident(item.name), index - 1))
|
||||
end
|
||||
|
||||
if comments then
|
||||
comment_lines(out, item.comment, "\t")
|
||||
end
|
||||
append(out, string.format("\t%s = %d", py_ident(item.name), index - 1))
|
||||
end
|
||||
append(out, string.format("\tCount = %d", #typ.enum), "")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -204,22 +299,25 @@ local function flag_value(item, lookup)
|
||||
return value
|
||||
end
|
||||
|
||||
local function emit_flag_class(out, name, items)
|
||||
append(out, "class " .. py_ident(name) .. "(enum.IntFlag):")
|
||||
local function emit_flag_class(out, name, items, comments)
|
||||
append(out, "class " .. py_ident(name) .. "(enum.IntFlag):")
|
||||
|
||||
if #items == 0 then
|
||||
append(out, "\tpass", "")
|
||||
return
|
||||
end
|
||||
if #items == 0 then
|
||||
append(out, "\tpass", "")
|
||||
return
|
||||
end
|
||||
|
||||
for _, item in ipairs(items) do
|
||||
comment_lines(out, item.comment, "\t")
|
||||
append(out, string.format("\t%s = 0x%x", py_ident(item.name), item.value))
|
||||
end
|
||||
append(out, "")
|
||||
end
|
||||
for _, item in ipairs(items) do
|
||||
if comments then
|
||||
comment_lines(out, item.comment, "\t")
|
||||
end
|
||||
append(out, string.format("\t%s = 0x%x", py_ident(item.name), item.value))
|
||||
end
|
||||
|
||||
local function gen_flags(out)
|
||||
append(out, "")
|
||||
end
|
||||
|
||||
local function gen_flags(out, comments)
|
||||
local combined = {}
|
||||
local standalone = {}
|
||||
|
||||
@@ -310,15 +408,15 @@ local function gen_flags(out)
|
||||
end
|
||||
|
||||
for _, name in ipairs({ "State", "Stencil", "Buffer", "Texture", "Sampler", "Reset" }) do
|
||||
emit_flag_class(out, name .. "Flags", combined[name].items)
|
||||
end
|
||||
emit_flag_class(out, name .. "Flags", combined[name].items, comments)
|
||||
end
|
||||
|
||||
for _, flag in ipairs(standalone) do
|
||||
emit_flag_class(out, flag.name, flag.items)
|
||||
end
|
||||
for _, flag in ipairs(standalone) do
|
||||
emit_flag_class(out, flag.name, flag.items, comments)
|
||||
end
|
||||
end
|
||||
|
||||
local function gen_struct_declarations(out)
|
||||
local function gen_struct_declarations(out, comments)
|
||||
for _, typ in ipairs(idl.types) do
|
||||
if typ.handle then
|
||||
append(out,
|
||||
@@ -330,12 +428,14 @@ local function gen_struct_declarations(out)
|
||||
"\t\treturn self.idx != 0xffff",
|
||||
""
|
||||
)
|
||||
elseif typ.struct then
|
||||
comment_lines(out, typ.comments)
|
||||
append(out, "class " .. python_type_name(typ) .. "(ctypes.Structure):", "\tpass", "")
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif typ.struct then
|
||||
if comments then
|
||||
comment_lines(out, typ.comments)
|
||||
end
|
||||
append(out, "class " .. python_type_name(typ) .. "(ctypes.Structure):", "\tpass", "")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function gen_funcptrs(out)
|
||||
for _, typ in ipairs(idl.types) do
|
||||
@@ -380,6 +480,49 @@ local function gen_struct_fields(out)
|
||||
end
|
||||
end
|
||||
|
||||
local function gen_stub_structs(out)
|
||||
for _, typ in ipairs(idl.types) do
|
||||
if typ.handle then
|
||||
append(out,
|
||||
"class " .. python_type_name(typ) .. "(ctypes.Structure):",
|
||||
"\tidx: int",
|
||||
"",
|
||||
"\t@property",
|
||||
"\tdef valid(self) -> bool: ...",
|
||||
""
|
||||
)
|
||||
|
||||
elseif typ.struct then
|
||||
comment_lines(out, typ.comments)
|
||||
append(out, "class " .. python_type_name(typ) .. "(ctypes.Structure):")
|
||||
|
||||
if #typ.struct == 0 then
|
||||
append(out, "\tpass")
|
||||
else
|
||||
for _, member in ipairs(typ.struct) do
|
||||
comment_lines(out, member.comment, "\t")
|
||||
append(
|
||||
out,
|
||||
"\t" .. py_ident(member.name) .. ": "
|
||||
.. convert_stub_field(member)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
append(out, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function gen_stub_funcptrs(out)
|
||||
for _, typ in ipairs(idl.types) do
|
||||
if typ.args and typ.ret then
|
||||
comment_lines(out, typ.comments)
|
||||
append(out, python_type_name(typ) .. ": Any", "")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function should_emit_function(func)
|
||||
if func.cpponly then
|
||||
return false
|
||||
@@ -438,6 +581,46 @@ local function gen_functions(out)
|
||||
append(out, "")
|
||||
end
|
||||
|
||||
local function stub_signature(out, name, args, restype)
|
||||
if #args == 0 then
|
||||
append(out, "def " .. name .. "() -> " .. restype .. ": ...")
|
||||
elseif #args <= 5 then
|
||||
append(out, "def " .. name .. "(" .. table.concat(args, ", ") .. ", /) -> " .. restype .. ": ...")
|
||||
else
|
||||
append(out, "def " .. name .. "(")
|
||||
for _, arg in ipairs(args) do
|
||||
append(out, "\t" .. arg .. ",")
|
||||
end
|
||||
append(out, "\t/,", ") -> " .. restype .. ": ...")
|
||||
end
|
||||
end
|
||||
|
||||
local function gen_stub_functions(out)
|
||||
append(out,
|
||||
"def load(path: Union[str, bytes]) -> ctypes.CDLL: ...",
|
||||
""
|
||||
)
|
||||
|
||||
for _, func in ipairs(idl.funcs) do
|
||||
if should_emit_function(func) and not func.vararg then
|
||||
local args = {}
|
||||
if func.this ~= nil then
|
||||
args[#args + 1] = "_this: " .. convert_stub_type(func.this_type, true, "arg")
|
||||
end
|
||||
|
||||
for _, arg in ipairs(func.args) do
|
||||
if arg.ctype ~= "..." then
|
||||
args[#args + 1] = py_ident(arg.name) .. ": " .. convert_stub_type(arg, true, "arg")
|
||||
end
|
||||
end
|
||||
|
||||
comment_lines(out, func.comments)
|
||||
stub_signature(out, "bgfx_" .. func.cname, args, convert_stub_type(func.ret, true, "return"))
|
||||
append(out, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function gen.gen()
|
||||
local out = {
|
||||
"# Copyright 2011-2026 Branimir Karadzic. All rights reserved.",
|
||||
@@ -454,9 +637,9 @@ function gen.gen()
|
||||
"",
|
||||
}
|
||||
|
||||
gen_enums(out)
|
||||
gen_flags(out)
|
||||
gen_struct_declarations(out)
|
||||
gen_enums(out, false)
|
||||
gen_flags(out, false)
|
||||
gen_struct_declarations(out, false)
|
||||
gen_funcptrs(out)
|
||||
gen_struct_fields(out)
|
||||
gen_functions(out)
|
||||
@@ -464,6 +647,38 @@ function gen.gen()
|
||||
return table.concat(out, "\n")
|
||||
end
|
||||
|
||||
function gen.gen_pyi()
|
||||
local out = {
|
||||
"# Copyright 2011-2026 Branimir Karadzic. All rights reserved.",
|
||||
"# License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE",
|
||||
"",
|
||||
"#",
|
||||
"# AUTO GENERATED! DO NOT EDIT!",
|
||||
"#",
|
||||
"",
|
||||
"import ctypes",
|
||||
"import enum",
|
||||
"from typing import Any, Optional, Protocol, TypeVar, Union",
|
||||
"",
|
||||
"ViewId = ctypes.c_uint16",
|
||||
"",
|
||||
"_T = TypeVar(\"_T\", covariant=True)",
|
||||
"",
|
||||
"class _Pointer(Protocol[_T]):",
|
||||
"\t@property",
|
||||
"\tdef contents(self) -> _T: ...",
|
||||
"",
|
||||
}
|
||||
|
||||
gen_enums(out, true)
|
||||
gen_flags(out, true)
|
||||
gen_stub_structs(out)
|
||||
gen_stub_funcptrs(out)
|
||||
gen_stub_functions(out)
|
||||
|
||||
return table.concat(out, "\n")
|
||||
end
|
||||
|
||||
function gen.write(codes, outputfile)
|
||||
local out = assert(io.open(outputfile, "wb"))
|
||||
out:write(codes)
|
||||
|
||||
Reference in New Issue
Block a user