530 lines
17 KiB
Zig
530 lines
17 KiB
Zig
const std = @import("std");
|
|
const fs = std.fs;
|
|
const json = std.json;
|
|
const allocPrint = std.fmt.allocPrint;
|
|
const mapping = @import("type_mapping.zig");
|
|
const intermediate = @import("intermediate.zig");
|
|
|
|
pub const outputFile = "raylib.zig";
|
|
pub const injectFile = "inject.zig";
|
|
|
|
fn trim(s: []const u8) []const u8 {
|
|
return std.mem.trim(u8, s, &[_]u8{ ' ', '\t', '\n' });
|
|
}
|
|
|
|
pub fn main() !void {
|
|
std.log.info("generating raylib.zig ...", .{});
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer {
|
|
if (gpa.deinit() == .leak) {
|
|
std.log.err("memory leak detected", .{});
|
|
}
|
|
}
|
|
const allocator = gpa.allocator();
|
|
var arena = std.heap.ArenaAllocator.init(allocator);
|
|
defer arena.deinit();
|
|
|
|
const bindingsData = try fs.cwd().readFileAlloc(arena.allocator(), intermediate.bindingsJSON, std.math.maxInt(usize));
|
|
|
|
const bindings = try json.parseFromSliceLeaky(mapping.Intermediate, arena.allocator(), bindingsData, .{
|
|
.ignore_unknown_fields = true,
|
|
});
|
|
|
|
var file = try fs.cwd().createFile(outputFile, .{});
|
|
defer file.close();
|
|
|
|
try file.writeAll("\n\n");
|
|
|
|
const inject = try Injections.load(arena.allocator());
|
|
try writeInjections(arena.allocator(), &file, inject);
|
|
|
|
try writeFunctions(arena.allocator(), &file, bindings, inject);
|
|
var h = try fs.cwd().createFile("marshal.h", .{});
|
|
defer h.close();
|
|
var c = try fs.cwd().createFile("marshal.c", .{});
|
|
defer c.close();
|
|
const raylib: mapping.CombinedRaylib = try mapping.CombinedRaylib.load(arena.allocator(), intermediate.jsonFiles);
|
|
try writeCFunctions(arena.allocator(), &h, &c, inject, raylib);
|
|
|
|
try writeStructs(arena.allocator(), &file, bindings, inject);
|
|
try writeEnums(arena.allocator(), &file, bindings, inject);
|
|
try writeDefines(arena.allocator(), &file, bindings, inject);
|
|
|
|
std.log.info("... done", .{});
|
|
}
|
|
|
|
const Injections = struct {
|
|
lines: []const []const u8,
|
|
symbols: []const []const u8,
|
|
|
|
pub fn load(allocator: std.mem.Allocator) !@This() {
|
|
var injectZigLines = std.ArrayList([]const u8).init(allocator);
|
|
var symbols = std.ArrayList([]const u8).init(allocator);
|
|
|
|
var file = try fs.cwd().openFile(injectFile, .{});
|
|
var reader = file.reader();
|
|
while (try reader.readUntilDelimiterOrEofAlloc(allocator, '\n', std.math.maxInt(usize))) |line| {
|
|
if (std.mem.indexOf(u8, line, "pub const ")) |startIndex| {
|
|
if (std.mem.indexOf(u8, line, " = extern struct {")) |endIndex| {
|
|
const s = line["pub const ".len + startIndex .. endIndex];
|
|
try symbols.append(s);
|
|
std.log.debug("inject symbol: {s}", .{s});
|
|
} else if (std.mem.indexOf(u8, line, " = packed struct(u32) {")) |endIndex| {
|
|
const s = line["pub const ".len + startIndex .. endIndex];
|
|
try symbols.append(s);
|
|
std.log.debug("inject symbol: {s}", .{s});
|
|
}
|
|
}
|
|
if (std.mem.indexOf(u8, line, "pub fn ")) |startIndex| {
|
|
if (std.mem.indexOf(u8, line, "(")) |endIndex| {
|
|
const s = line["pub fn ".len + startIndex .. endIndex];
|
|
try symbols.append(s);
|
|
std.log.debug("inject symbol: {s}", .{s});
|
|
}
|
|
}
|
|
try injectZigLines.append(line);
|
|
}
|
|
|
|
return @This(){
|
|
.lines = try injectZigLines.toOwnedSlice(),
|
|
.symbols = try symbols.toOwnedSlice(),
|
|
};
|
|
}
|
|
|
|
pub fn containsSymbol(self: @This(), name: []const u8) bool {
|
|
for (self.symbols) |s| {
|
|
if (eql(s, name)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
fn writeFunctions(
|
|
allocator: std.mem.Allocator,
|
|
file: *fs.File,
|
|
bindings: mapping.Intermediate,
|
|
inject: Injections,
|
|
) !void {
|
|
var buf: [51200]u8 = undefined;
|
|
var fba = std.heap.FixedBufferAllocator.init(&buf);
|
|
|
|
for (bindings.functions) |func| {
|
|
if (inject.containsSymbol(func.name)) continue;
|
|
defer fba.reset();
|
|
|
|
//--- signature -------------------------
|
|
const funcDescription: []const u8 = func.description orelse "";
|
|
try file.writeAll(
|
|
try allocPrint(
|
|
allocator,
|
|
"\n/// {s}\npub fn {s} (\n",
|
|
.{ funcDescription, func.name },
|
|
),
|
|
);
|
|
|
|
for (func.params) |param| {
|
|
if (param.description) |description| {
|
|
try file.writeAll(try allocPrint(
|
|
allocator,
|
|
"/// {s}\n",
|
|
.{description},
|
|
));
|
|
}
|
|
try file.writeAll(try allocPrint(
|
|
allocator,
|
|
"{s}: {s},\n",
|
|
.{ param.name, param.typ },
|
|
));
|
|
}
|
|
|
|
if (func.custom) {
|
|
try file.writeAll(
|
|
try allocPrint(
|
|
allocator,
|
|
") {s} {{\n",
|
|
.{func.returnType},
|
|
),
|
|
);
|
|
} else if (isPointer(func.returnType)) {
|
|
if (eql("u8", (stripType(func.returnType)))) {
|
|
try file.writeAll(
|
|
try allocPrint(
|
|
allocator,
|
|
") [*:0]const {s} {{\n",
|
|
.{stripType(func.returnType)},
|
|
),
|
|
);
|
|
} else {
|
|
try file.writeAll(
|
|
try allocPrint(
|
|
allocator,
|
|
") {s} {{\n",
|
|
.{func.returnType},
|
|
),
|
|
);
|
|
}
|
|
} else {
|
|
try file.writeAll(
|
|
try allocPrint(
|
|
allocator,
|
|
") {s} {{\n",
|
|
.{func.returnType},
|
|
),
|
|
);
|
|
}
|
|
const returnTypeIsVoid = eql(func.returnType, "void");
|
|
|
|
//--- body ------------------------------
|
|
if (isPointer(func.returnType)) {
|
|
try file.writeAll(try allocPrint(allocator, "return @ptrCast({s},\n", .{func.returnType}));
|
|
} else if (isPrimitiveOrPointer(func.returnType)) {
|
|
try file.writeAll("return ");
|
|
} else if (!returnTypeIsVoid) {
|
|
try file.writeAll(try allocPrint(allocator, "var out: {s} = undefined;\n", .{func.returnType}));
|
|
}
|
|
try file.writeAll(try allocPrint(allocator, "raylib.m{s}(\n", .{func.name}));
|
|
|
|
if (!isPrimitiveOrPointer(func.returnType)) {
|
|
if (bindings.containsStruct(stripType(func.returnType))) {
|
|
try file.writeAll(try allocPrint(allocator, "@ptrCast([*c]raylib.{s}, &out),\n", .{func.returnType}));
|
|
} else if (!returnTypeIsVoid) {
|
|
try file.writeAll(try allocPrint(allocator, "@ptrCast([*c]{s}, &out),\n", .{func.returnType}));
|
|
}
|
|
}
|
|
|
|
for (func.params) |param| {
|
|
if (isFunctionPointer(param.typ)) {
|
|
try file.writeAll(try allocPrint(allocator, "@ptrCast({s}),\n", .{param.name}));
|
|
} else if (bindings.containsStruct(stripType(param.typ)) and isPointer(param.typ)) {
|
|
try file.writeAll(try allocPrint(allocator, "@intToPtr([*c]raylib.{s}, @ptrToInt({s})),\n", .{ stripType(param.typ), param.name }));
|
|
} else if (bindings.containsEnum(param.typ)) {
|
|
try file.writeAll(try allocPrint(allocator, "@enumToInt({s}),\n", .{param.name}));
|
|
} else if (bindings.containsStruct(stripType(param.typ))) {
|
|
try file.writeAll(try allocPrint(allocator, "@intToPtr([*c]raylib.{s}, @ptrToInt(&{s})),\n", .{ stripType(param.typ), param.name }));
|
|
} else if (isPointer(param.typ)) {
|
|
if (std.mem.endsWith(u8, param.typ, "anyopaque")) {
|
|
try file.writeAll(try allocPrint(allocator, "{s},\n", .{param.name}));
|
|
} else if (isConst(param.typ)) {
|
|
try file.writeAll(try allocPrint(allocator, "@intToPtr([*c]const {s}, @ptrToInt({s})),\n", .{ stripType(param.typ), param.name }));
|
|
} else {
|
|
try file.writeAll(try allocPrint(allocator, "@ptrCast([*c]{s}, {s}),\n", .{ stripType(param.typ), param.name }));
|
|
}
|
|
} else {
|
|
try file.writeAll(try allocPrint(allocator, "{s},\n", .{param.name}));
|
|
}
|
|
}
|
|
|
|
if (isPointer(func.returnType)) {
|
|
try file.writeAll("),\n);\n");
|
|
} else {
|
|
try file.writeAll(");\n");
|
|
}
|
|
|
|
if (!isPrimitiveOrPointer(func.returnType) and !returnTypeIsVoid) {
|
|
try file.writeAll("return out;\n");
|
|
}
|
|
|
|
try file.writeAll("}\n");
|
|
}
|
|
|
|
std.log.info("generated functions", .{});
|
|
}
|
|
|
|
/// write: RETURN NAME(PARAMS...)
|
|
/// or: void NAME(RETURN*, PARAMS...)
|
|
fn writeCSignature(
|
|
allocator: std.mem.Allocator,
|
|
file: *fs.File,
|
|
func: mapping.RaylibFunction,
|
|
) !void {
|
|
const returnType = func.returnType;
|
|
|
|
//return directly
|
|
if (mapping.isPrimitiveOrPointer(returnType)) {
|
|
try file.writeAll(try allocPrint(allocator, "{s} m{s}(", .{ returnType, func.name }));
|
|
if (func.params == null or func.params.?.len == 0) {
|
|
try file.writeAll("void");
|
|
}
|
|
}
|
|
//wrap return type and put as first function parameter
|
|
else {
|
|
try file.writeAll(try allocPrint(allocator, "void m{s}({s} *out", .{ func.name, returnType }));
|
|
if (func.params != null and func.params.?.len > 0) {
|
|
try file.writeAll(", ");
|
|
}
|
|
}
|
|
|
|
if (func.params) |params| {
|
|
for (params, 0..) |param, i| {
|
|
const paramType = param.type;
|
|
if (mapping.isPrimitiveOrPointer(paramType) or isFunctionPointer(paramType)) {
|
|
try file.writeAll(try allocPrint(allocator, "{s} {s}", .{ paramType, param.name }));
|
|
} else {
|
|
try file.writeAll(try allocPrint(allocator, "{s} *{s}", .{ paramType, param.name }));
|
|
}
|
|
|
|
if (i < params.len - 1) {
|
|
try file.writeAll(", ");
|
|
}
|
|
}
|
|
}
|
|
|
|
try file.writeAll(")");
|
|
}
|
|
|
|
fn writeCFunctions(
|
|
allocator: std.mem.Allocator,
|
|
h: *fs.File,
|
|
c: *fs.File,
|
|
inject: Injections,
|
|
rl: mapping.CombinedRaylib,
|
|
) !void {
|
|
var hInject = try fs.cwd().openFile("inject.h", .{});
|
|
defer hInject.close();
|
|
try h.writeFileAll(hInject, .{});
|
|
var cInject = try fs.cwd().openFile("inject.c", .{});
|
|
defer cInject.close();
|
|
try c.writeFileAll(cInject, .{});
|
|
|
|
for (rl.functions.values()) |func| {
|
|
if (inject.containsSymbol(func.name)) continue;
|
|
|
|
//--- C-HEADER -----------------------------
|
|
|
|
try h.writeAll(try allocPrint(allocator, "// {s}\n", .{func.description}));
|
|
try writeCSignature(allocator, h, func);
|
|
try h.writeAll(";\n\n");
|
|
|
|
try writeCSignature(allocator, c, func);
|
|
try c.writeAll("\n{\n");
|
|
|
|
//--- C-IMPLEMENT -----------------------------
|
|
|
|
if (eql(func.returnType, "void")) {
|
|
try c.writeAll("\t");
|
|
} else if (mapping.isPrimitiveOrPointer(func.returnType)) {
|
|
try c.writeAll("\treturn ");
|
|
} else {
|
|
try c.writeAll("\t*out = ");
|
|
}
|
|
|
|
try c.writeAll(
|
|
try allocPrint(
|
|
allocator,
|
|
"{s}(",
|
|
.{func.name},
|
|
),
|
|
);
|
|
|
|
if (func.params) |params| {
|
|
for (params, 0..) |param, i| {
|
|
if (mapping.isPrimitiveOrPointer(param.type) or isFunctionPointer(param.type)) {
|
|
try c.writeAll(
|
|
try allocPrint(
|
|
allocator,
|
|
"{s}",
|
|
.{param.name},
|
|
),
|
|
);
|
|
} else {
|
|
try c.writeAll(
|
|
try allocPrint(
|
|
allocator,
|
|
"*{s}",
|
|
.{param.name},
|
|
),
|
|
);
|
|
}
|
|
|
|
if (i < params.len - 1) {
|
|
try c.writeAll(", ");
|
|
}
|
|
}
|
|
}
|
|
|
|
try c.writeAll(");\n}\n\n");
|
|
}
|
|
}
|
|
|
|
fn writeStructs(
|
|
allocator: std.mem.Allocator,
|
|
file: *fs.File,
|
|
bindings: mapping.Intermediate,
|
|
inject: Injections,
|
|
) !void {
|
|
var buf: [51200]u8 = undefined;
|
|
var fba = std.heap.FixedBufferAllocator.init(&buf);
|
|
|
|
for (bindings.structs) |s| {
|
|
if (inject.containsSymbol(s.name)) continue;
|
|
defer fba.reset();
|
|
|
|
try file.writeAll(
|
|
try allocPrint(
|
|
allocator,
|
|
"\n/// {?s}\npub const {s} = extern struct {{\n",
|
|
.{ s.description, s.name },
|
|
),
|
|
);
|
|
|
|
for (s.fields) |field| {
|
|
try file.writeAll(try allocPrint(allocator, "/// {?s}\n\t{s}: {s},\n", .{
|
|
field.description,
|
|
field.name,
|
|
field.typ,
|
|
}));
|
|
}
|
|
|
|
try file.writeAll("\n};\n");
|
|
}
|
|
|
|
std.log.info("generated structs", .{});
|
|
}
|
|
|
|
fn writeEnums(
|
|
allocator: std.mem.Allocator,
|
|
file: *fs.File,
|
|
bindings: mapping.Intermediate,
|
|
inject: Injections,
|
|
) !void {
|
|
var buf: [51200]u8 = undefined;
|
|
var fba = std.heap.FixedBufferAllocator.init(&buf);
|
|
|
|
for (bindings.enums) |e| {
|
|
if (inject.containsSymbol(e.name)) continue;
|
|
defer fba.reset();
|
|
|
|
try file.writeAll(
|
|
try allocPrint(
|
|
allocator,
|
|
"\n/// {?s}\npub const {s} = enum(i32) {{\n",
|
|
.{ e.description, e.name },
|
|
),
|
|
);
|
|
|
|
for (e.values) |value| {
|
|
try file.writeAll(try allocPrint(allocator, "/// {?s}\n{s} = {d},\n", .{
|
|
value.description,
|
|
value.name,
|
|
value.value,
|
|
}));
|
|
}
|
|
|
|
try file.writeAll("\n};\n");
|
|
}
|
|
|
|
std.log.info("generated enums", .{});
|
|
}
|
|
|
|
fn writeDefines(
|
|
allocator: std.mem.Allocator,
|
|
file: *fs.File,
|
|
bindings: mapping.Intermediate,
|
|
inject: Injections,
|
|
) !void {
|
|
var buf: [51200]u8 = undefined;
|
|
var fba = std.heap.FixedBufferAllocator.init(&buf);
|
|
|
|
for (bindings.defines) |d| {
|
|
if (inject.containsSymbol(d.name)) continue;
|
|
defer fba.reset();
|
|
|
|
try file.writeAll(
|
|
try allocPrint(
|
|
allocator,
|
|
"\n/// {?s}\npub const {s}: {s} = {s};\n",
|
|
.{ d.description, d.name, d.typ, d.value },
|
|
),
|
|
);
|
|
}
|
|
|
|
std.log.info("generated defines", .{});
|
|
}
|
|
|
|
fn writeInjections(
|
|
_: std.mem.Allocator,
|
|
file: *fs.File,
|
|
inject: Injections,
|
|
) !void {
|
|
var buf: [51200]u8 = undefined;
|
|
var fba = std.heap.FixedBufferAllocator.init(&buf);
|
|
|
|
for (inject.lines) |line| {
|
|
defer fba.reset();
|
|
|
|
try file.writeAll(try allocPrint(fba.allocator(), "{s}\n", .{line}));
|
|
}
|
|
|
|
std.log.info("written inject.zig", .{});
|
|
}
|
|
|
|
fn eql(a: []const u8, b: []const u8) bool {
|
|
return std.mem.eql(u8, a, b);
|
|
}
|
|
|
|
fn startsWith(haystack: []const u8, needle: []const u8) bool {
|
|
return std.mem.startsWith(u8, haystack, needle);
|
|
}
|
|
|
|
fn endsWith(haystack: []const u8, needle: []const u8) bool {
|
|
return std.mem.endsWith(u8, haystack, needle);
|
|
}
|
|
|
|
/// is pointer type
|
|
fn isPointer(z: []const u8) bool {
|
|
return pointerOffset(z) > 0;
|
|
}
|
|
|
|
fn isFunctionPointer(z: []const u8) bool {
|
|
return std.mem.indexOf(u8, z, "fn(") != null or std.mem.endsWith(u8, z, "Callback");
|
|
}
|
|
|
|
fn pointerOffset(z: []const u8) usize {
|
|
if (startsWith(z, "*")) return 1;
|
|
if (startsWith(z, "?*")) return 2;
|
|
if (startsWith(z, "[*]")) return 3;
|
|
if (startsWith(z, "?[*]")) return 4;
|
|
if (startsWith(z, "[*c]")) return 4;
|
|
if (startsWith(z, "[*:0]")) return 5;
|
|
if (startsWith(z, "?[*:0]")) return 6;
|
|
|
|
return 0;
|
|
}
|
|
|
|
fn isConst(z: []const u8) bool {
|
|
return startsWith(z[pointerOffset(z)..], "const ");
|
|
}
|
|
|
|
fn isVoid(z: []const u8) bool {
|
|
return eql(stripType(z), "void");
|
|
}
|
|
|
|
/// strips const and pointer annotations
|
|
/// *const TName -> TName
|
|
fn stripType(z: []const u8) []const u8 {
|
|
var name = z[pointerOffset(z)..];
|
|
name = if (startsWith(name, "const ")) name["const ".len..] else name;
|
|
|
|
return std.mem.trim(u8, name, " \t\n");
|
|
}
|
|
|
|
/// true if Zig type is primitive or a pointer to anything
|
|
/// this means we don't need to wrap it in a pointer
|
|
pub fn isPrimitiveOrPointer(z: []const u8) bool {
|
|
const primitiveTypes = std.ComptimeStringMap(void, .{
|
|
// .{ "void", {} }, // zig void is zero sized while C void is >= 1 byte
|
|
.{ "bool", {} },
|
|
.{ "u8", {} },
|
|
.{ "i8", {} },
|
|
.{ "i16", {} },
|
|
.{ "u16", {} },
|
|
.{ "i32", {} },
|
|
.{ "u32", {} },
|
|
.{ "i64", {} },
|
|
.{ "u64", {} },
|
|
.{ "f32", {} },
|
|
.{ "f64", {} },
|
|
});
|
|
return primitiveTypes.has(stripType(z)) or pointerOffset(z) > 0;
|
|
}
|