--[[ C-to-LuaJIT FFI Bindings Generator Usage: luajit generate_ffi.lua ]] local ffi = require("ffi") local args = { ... } if #args < 2 then io.stderr:write("Usage: luajit generate_ffi.lua \n") os.exit(1) end local header_path = args[1] local output_path = args[2] local file = assert(io.open(header_path, "r"), "Failed to open header: " .. header_path) local header_content = file:read("*a") file:close() local defines = {} for name, value in header_content:gmatch("#define%s+([%w_]+)%s+([%w_]+)") do if tonumber(value) or defines[value] then defines[name] = value end end -- Resolve nested defines. local changed = true while changed do changed = false for k, v in pairs(defines) do if not tonumber(v) and defines[v] then defines[k] = defines[v] changed = true end end end local function clean_for_ffi(str) for name, value in pairs(defines) do str = str:gsub("%f[%w_]" .. name .. "%f[^%w_]", value) end str = str:gsub("BAL_ALIGNED%s*%b()", "") str = str:gsub("BAL_EXPORT", "") str = str:gsub("BAL_RESTRICT", "") str = str:gsub("BAL_HOT", "") str = str:gsub("BAL_COLD", "") str = str:gsub("static_assert%s*%b();?", "") str = str:gsub("_Static_assert%s*%b();?", "") str = str:gsub("%s+", " ") str = str:gsub("^%s+", ""):gsub("%s+$", "") return str end local exported_functions = {} for function_declaration in header_content:gmatch("BAL_EXPORT%s+([^;]+;)") do table.insert(exported_functions, clean_for_ffi(function_declaration)) end local clean_header = header_content clean_header = clean_header:gsub("BAL_ALIGNED%s*%b()", "") clean_header = clean_header:gsub("static_assert%s*%b();?", "") clean_header = clean_header:gsub("_Static_assert%s*%b();?", "") local struct_definitions = {} for struct_block in clean_header:gmatch("(typedef%s+struct[%s%w_]*%b{}[%s%w_]*;)") do table.insert(struct_definitions, clean_for_ffi(struct_block)) end local validation_buffer = {} for _, struct in ipairs(struct_definitions) do table.insert(validation_buffer, struct) end for _, fn in ipairs(exported_functions) do table.insert(validation_buffer, fn) end local validation_string = table.concat(validation_buffer, "\n") if validation_string == "" then io.stderr:write("Warning: No structs or functions extracted from " .. header_path .. "\n") end local ok, err = pcall(ffi.cdef, validation_string) if not ok then io.stderr:write("FFI validation failed for " .. header_path .. ": " .. tostring(err) .. "\n") io.stderr:write("--- Generated C Definitions ---\n") io.stderr:write(tostring(validation_string) .. "\n") io.stderr:write("-------------------------------\n") os.exit(1) end local out = assert(io.open(output_path, "w"), "Failed to open output: " .. output_path) out:write("-- Auto-generated FFI bindings. DO NOT EDIT.\n") out:write("-- Source: " .. header_path .. "\n") out:write("-- Generated by tools/generate_ffi.lua\n\n") out:write("local ffi = require('ffi')\n\n") out:write("ffi.cdef [[\n") for _, struct in ipairs(struct_definitions) do out:write(struct .. "\n\n") end for _, fn in ipairs(exported_functions) do out:write(fn .. "\n") end out:write("]]\n\n") out:write("local M = {}\n") out:write("local C = ffi.C\n\n") for _, struct in ipairs(struct_definitions) do local type_name = struct:match("}%s*([%w_]+)%s*;") if type_name then out:write(string.format("local %s_mt = { __index = {} }\n", type_name)) out:write(string.format("%s_mt.__index.__type = '%s'\n", type_name, type_name)) out:write(string.format("ffi.metatype('%s', %s_mt)\n\n", type_name, type_name)) end end for _, fn in ipairs(exported_functions) do local return_type, function_name, parameters = fn:match("^(%S+)%s+([%w_]+)%s*%((.*)%)%s*;") if function_name then local namespace = function_name:match("^([%w_]-)_[%w_]+$") or "Global" local pascal_namespace = namespace:gsub("_(%w)", string.upper):gsub("^(%w)", string.upper) out:write(string.format("M.%s = M.%s or {}\n", pascal_namespace, pascal_namespace)) out:write(string.format("function M.%s.%s(", pascal_namespace, function_name)) local parameter_names = {} if parameters and parameters ~= "void" and parameters ~= "" then for parameter in parameters:gmatch("[^,]+") do local name = parameter:match("([%w_]+)%s*$") or parameter:match("%*([%w_]+)") if name then table.insert(parameter_names, name) end end end out:write(table.concat(parameter_names, ", ") .. ")\n") out:write(string.format(" return C.%s(%s)\n", function_name, table.concat(parameter_names, ", "))) out:write("end\n\n") end end out:write("return M\n") out:close() io.stdout:write(string.format("[generate_ffi] Generated %d structs, %d functions -> %s\n", #struct_definitions, #exported_functions, output_path))