469 lines
16 KiB
Zig
469 lines
16 KiB
Zig
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const patterns = @import("patterns.zig");
|
|
const Declaration = patterns.Declaration;
|
|
|
|
pub const TypeReference = struct {
|
|
name: []const u8,
|
|
source_location: []const u8,
|
|
};
|
|
|
|
pub const DependencyResolver = struct {
|
|
allocator: Allocator,
|
|
referenced_types: std.StringHashMap(void),
|
|
defined_types: std.StringHashMap(void),
|
|
|
|
pub fn init(allocator: Allocator) DependencyResolver {
|
|
return .{
|
|
.allocator = allocator,
|
|
.referenced_types = std.StringHashMap(void).init(allocator),
|
|
.defined_types = std.StringHashMap(void).init(allocator),
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *DependencyResolver) void {
|
|
// Free all owned keys in referenced_types
|
|
var it = self.referenced_types.keyIterator();
|
|
while (it.next()) |key| {
|
|
self.allocator.free(key.*);
|
|
}
|
|
self.referenced_types.deinit();
|
|
self.defined_types.deinit();
|
|
}
|
|
|
|
pub fn analyze(self: *DependencyResolver, decls: []const Declaration) !void {
|
|
try self.collectDefinedTypes(decls);
|
|
try self.collectReferencedTypes(decls);
|
|
}
|
|
|
|
pub fn getMissingTypes(self: *DependencyResolver, allocator: Allocator) ![][]const u8 {
|
|
var missing = std.ArrayList([]const u8){};
|
|
|
|
var it = self.referenced_types.keyIterator();
|
|
while (it.next()) |key| {
|
|
if (!self.defined_types.contains(key.*)) {
|
|
try missing.append(allocator, try allocator.dupe(u8, key.*));
|
|
}
|
|
}
|
|
|
|
return try missing.toOwnedSlice(allocator);
|
|
}
|
|
|
|
fn collectDefinedTypes(self: *DependencyResolver, decls: []const Declaration) !void {
|
|
for (decls) |decl| {
|
|
const type_name = switch (decl) {
|
|
.opaque_type => |o| o.name,
|
|
.typedef_decl => |t| t.name,
|
|
.function_pointer_decl => |fp| fp.name,
|
|
.c_type_alias => |a| a.name,
|
|
.enum_decl => |e| e.name,
|
|
.struct_decl => |s| s.name,
|
|
.union_decl => |u| u.name,
|
|
.flag_decl => |f| f.name,
|
|
.function_decl => continue,
|
|
};
|
|
try self.defined_types.put(type_name, {});
|
|
}
|
|
}
|
|
|
|
fn collectReferencedTypes(self: *DependencyResolver, decls: []const Declaration) !void {
|
|
for (decls) |decl| {
|
|
switch (decl) {
|
|
.function_decl => |func| {
|
|
try self.scanType(func.return_type);
|
|
for (func.params) |param| {
|
|
try self.scanType(param.type_name);
|
|
}
|
|
},
|
|
.function_pointer_decl => |func_ptr| {
|
|
try self.scanType(func_ptr.return_type);
|
|
for (func_ptr.params) |param| {
|
|
try self.scanType(param.type_name);
|
|
}
|
|
},
|
|
.struct_decl => |struct_decl| {
|
|
for (struct_decl.fields) |field| {
|
|
try self.scanType(field.type_name);
|
|
}
|
|
},
|
|
.union_decl => |union_decl| {
|
|
for (union_decl.fields) |field| {
|
|
try self.scanType(field.type_name);
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
}
|
|
|
|
fn scanType(self: *DependencyResolver, type_str: []const u8) !void {
|
|
const base_type = extractBaseType(type_str);
|
|
if (base_type.len > 0 and isSDLType(base_type)) {
|
|
// Only add if not already present (avoids duplicates)
|
|
if (!self.referenced_types.contains(base_type)) {
|
|
// We need to own the string since base_type is a slice into type_str
|
|
// which might not have a stable lifetime
|
|
const owned = try self.allocator.dupe(u8, base_type);
|
|
try self.referenced_types.put(owned, {});
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn extractBaseType(type_str: []const u8) []const u8 {
|
|
var result = type_str;
|
|
|
|
// Remove leading qualifiers and pointer markers
|
|
while (true) {
|
|
// Trim whitespace
|
|
result = std.mem.trim(u8, result, " \t");
|
|
|
|
// Remove "const"
|
|
if (std.mem.startsWith(u8, result, "const ")) {
|
|
result = result["const ".len..];
|
|
continue;
|
|
}
|
|
|
|
// Remove "struct"
|
|
if (std.mem.startsWith(u8, result, "struct ")) {
|
|
result = result["struct ".len..];
|
|
continue;
|
|
}
|
|
|
|
// Remove leading "?" (nullable)
|
|
if (std.mem.startsWith(u8, result, "?")) {
|
|
result = result[1..];
|
|
continue;
|
|
}
|
|
|
|
// Remove leading "*" (pointer)
|
|
if (std.mem.startsWith(u8, result, "*")) {
|
|
result = result[1..];
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// Trim again
|
|
result = std.mem.trim(u8, result, " \t");
|
|
|
|
// If it contains [*c], extract the part after
|
|
if (std.mem.indexOf(u8, result, "[*c]")) |idx| {
|
|
result = result[idx + "[*c]".len ..];
|
|
result = std.mem.trim(u8, result, " \t");
|
|
// Remove const again if present
|
|
if (std.mem.startsWith(u8, result, "const ")) {
|
|
result = result["const ".len..];
|
|
}
|
|
result = std.mem.trim(u8, result, " \t");
|
|
}
|
|
|
|
// Remove trailing pointer markers and const qualifiers
|
|
while (true) {
|
|
result = std.mem.trim(u8, result, " \t");
|
|
|
|
// Remove trailing "*const" (common pattern)
|
|
if (std.mem.endsWith(u8, result, "*const")) {
|
|
result = result[0 .. result.len - "*const".len];
|
|
continue;
|
|
}
|
|
|
|
// Remove trailing "*"
|
|
if (std.mem.endsWith(u8, result, "*")) {
|
|
result = result[0 .. result.len - 1];
|
|
continue;
|
|
}
|
|
|
|
// Remove trailing "const"
|
|
if (std.mem.endsWith(u8, result, " const")) {
|
|
result = result[0 .. result.len - " const".len];
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// Final trim
|
|
result = std.mem.trim(u8, result, " \t");
|
|
|
|
return result;
|
|
}
|
|
|
|
fn isSDLType(type_str: []const u8) bool {
|
|
// Check if it's an SDL type (starts with SDL_ or is a known SDL type)
|
|
if (std.mem.startsWith(u8, type_str, "SDL_")) {
|
|
return true;
|
|
}
|
|
|
|
// Check for known SDL types that don't have SDL_ prefix in Zig bindings
|
|
// These are types that would already be converted from SDL_ to their Zig name
|
|
const known_types = [_][]const u8{
|
|
"Window",
|
|
"Rect",
|
|
"FColor",
|
|
"Color",
|
|
"FPoint",
|
|
"FlipMode",
|
|
"PropertiesID",
|
|
"Surface",
|
|
"PixelFormat",
|
|
};
|
|
|
|
for (known_types) |known| {
|
|
if (std.mem.eql(u8, type_str, known)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
pub fn parseIncludes(allocator: Allocator, source: []const u8) ![][]const u8 {
|
|
var includes = std.ArrayList([]const u8){};
|
|
|
|
var lines = std.mem.splitScalar(u8, source, '\n');
|
|
while (lines.next()) |line| {
|
|
// Match: #include <SDL3/SDL_something.h>
|
|
const trimmed = std.mem.trim(u8, line, " \t\r");
|
|
if (std.mem.startsWith(u8, trimmed, "#include <SDL3/")) {
|
|
const after_open = "#include <SDL3/".len;
|
|
if (std.mem.indexOf(u8, trimmed[after_open..], ">")) |end| {
|
|
const header_name = trimmed[after_open..][0..end];
|
|
try includes.append(allocator, try allocator.dupe(u8, header_name));
|
|
}
|
|
}
|
|
}
|
|
|
|
return try includes.toOwnedSlice(allocator);
|
|
}
|
|
|
|
pub fn extractTypeFromHeader(
|
|
allocator: Allocator,
|
|
header_source: []const u8,
|
|
type_name: []const u8,
|
|
) !?Declaration {
|
|
var scanner = patterns.Scanner.init(allocator, header_source);
|
|
const all_decls = try scanner.scan();
|
|
defer {
|
|
for (all_decls) |decl| {
|
|
freeDeclaration(allocator, decl);
|
|
}
|
|
allocator.free(all_decls);
|
|
}
|
|
|
|
// Find matching declaration
|
|
for (all_decls) |decl| {
|
|
const decl_name = switch (decl) {
|
|
.opaque_type => |o| o.name,
|
|
.typedef_decl => |t| t.name,
|
|
.enum_decl => |e| e.name,
|
|
.struct_decl => |s| s.name,
|
|
.union_decl => |u| u.name,
|
|
.flag_decl => |f| f.name,
|
|
else => continue,
|
|
};
|
|
|
|
if (std.mem.eql(u8, decl_name, type_name)) {
|
|
return try cloneDeclaration(allocator, decl);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
fn cloneDeclaration(allocator: Allocator, decl: Declaration) !Declaration {
|
|
return switch (decl) {
|
|
.opaque_type => |o| .{
|
|
.opaque_type = .{
|
|
.name = try allocator.dupe(u8, o.name),
|
|
.doc_comment = if (o.doc_comment) |doc| try allocator.dupe(u8, doc) else null,
|
|
},
|
|
},
|
|
.typedef_decl => |t| .{
|
|
.typedef_decl = .{
|
|
.name = try allocator.dupe(u8, t.name),
|
|
.underlying_type = try allocator.dupe(u8, t.underlying_type),
|
|
.doc_comment = if (t.doc_comment) |doc| try allocator.dupe(u8, doc) else null,
|
|
},
|
|
},
|
|
.function_pointer_decl => |fp| .{
|
|
.function_pointer_decl = .{
|
|
.name = try allocator.dupe(u8, fp.name),
|
|
.return_type = try allocator.dupe(u8, fp.return_type),
|
|
.doc_comment = if (fp.doc_comment) |doc| try allocator.dupe(u8, doc) else null,
|
|
.params = try cloneParams(allocator, fp.params),
|
|
},
|
|
},
|
|
.c_type_alias => |a| .{
|
|
.c_type_alias = .{
|
|
.name = try allocator.dupe(u8, a.name),
|
|
.doc_comment = if (a.doc_comment) |doc| try allocator.dupe(u8, doc) else null,
|
|
},
|
|
},
|
|
.enum_decl => |e| .{
|
|
.enum_decl = .{
|
|
.name = try allocator.dupe(u8, e.name),
|
|
.doc_comment = if (e.doc_comment) |doc| try allocator.dupe(u8, doc) else null,
|
|
.values = try cloneEnumValues(allocator, e.values),
|
|
},
|
|
},
|
|
.struct_decl => |s| .{
|
|
.struct_decl = .{
|
|
.name = try allocator.dupe(u8, s.name),
|
|
.doc_comment = if (s.doc_comment) |doc| try allocator.dupe(u8, doc) else null,
|
|
.fields = try cloneFields(allocator, s.fields),
|
|
},
|
|
},
|
|
.union_decl => |u| .{
|
|
.union_decl = .{
|
|
.name = try allocator.dupe(u8, u.name),
|
|
.doc_comment = if (u.doc_comment) |doc| try allocator.dupe(u8, doc) else null,
|
|
.fields = try cloneFields(allocator, u.fields),
|
|
},
|
|
},
|
|
.flag_decl => |f| .{
|
|
.flag_decl = .{
|
|
.name = try allocator.dupe(u8, f.name),
|
|
.underlying_type = try allocator.dupe(u8, f.underlying_type),
|
|
.doc_comment = if (f.doc_comment) |doc| try allocator.dupe(u8, doc) else null,
|
|
.flags = try cloneFlagValues(allocator, f.flags),
|
|
},
|
|
},
|
|
.function_decl => |func| .{
|
|
.function_decl = .{
|
|
.name = try allocator.dupe(u8, func.name),
|
|
.return_type = try allocator.dupe(u8, func.return_type),
|
|
.doc_comment = if (func.doc_comment) |doc| try allocator.dupe(u8, doc) else null,
|
|
.params = try cloneParams(allocator, func.params),
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
fn cloneEnumValues(allocator: Allocator, values: []const patterns.EnumValue) ![]patterns.EnumValue {
|
|
const cloned = try allocator.alloc(patterns.EnumValue, values.len);
|
|
for (values, 0..) |val, i| {
|
|
cloned[i] = .{
|
|
.name = try allocator.dupe(u8, val.name),
|
|
.value = if (val.value) |v| try allocator.dupe(u8, v) else null,
|
|
.comment = if (val.comment) |c| try allocator.dupe(u8, c) else null,
|
|
};
|
|
}
|
|
return cloned;
|
|
}
|
|
|
|
fn cloneFields(allocator: Allocator, fields: []const patterns.FieldDecl) ![]patterns.FieldDecl {
|
|
const cloned = try allocator.alloc(patterns.FieldDecl, fields.len);
|
|
for (fields, 0..) |field, i| {
|
|
cloned[i] = .{
|
|
.name = try allocator.dupe(u8, field.name),
|
|
.type_name = try allocator.dupe(u8, field.type_name),
|
|
.comment = if (field.comment) |c| try allocator.dupe(u8, c) else null,
|
|
};
|
|
}
|
|
return cloned;
|
|
}
|
|
|
|
fn cloneFlagValues(allocator: Allocator, flags: []const patterns.FlagValue) ![]patterns.FlagValue {
|
|
const cloned = try allocator.alloc(patterns.FlagValue, flags.len);
|
|
for (flags, 0..) |flag, i| {
|
|
cloned[i] = .{
|
|
.name = try allocator.dupe(u8, flag.name),
|
|
.value = try allocator.dupe(u8, flag.value),
|
|
.comment = if (flag.comment) |c| try allocator.dupe(u8, c) else null,
|
|
};
|
|
}
|
|
return cloned;
|
|
}
|
|
|
|
fn cloneParams(allocator: Allocator, params: []const patterns.ParamDecl) ![]patterns.ParamDecl {
|
|
const cloned = try allocator.alloc(patterns.ParamDecl, params.len);
|
|
for (params, 0..) |param, i| {
|
|
cloned[i] = .{
|
|
.name = try allocator.dupe(u8, param.name),
|
|
.type_name = try allocator.dupe(u8, param.type_name),
|
|
};
|
|
}
|
|
return cloned;
|
|
}
|
|
|
|
fn freeDeclaration(allocator: Allocator, decl: Declaration) void {
|
|
switch (decl) {
|
|
.opaque_type => |o| {
|
|
allocator.free(o.name);
|
|
if (o.doc_comment) |doc| allocator.free(doc);
|
|
},
|
|
.typedef_decl => |t| {
|
|
allocator.free(t.name);
|
|
allocator.free(t.underlying_type);
|
|
if (t.doc_comment) |doc| allocator.free(doc);
|
|
},
|
|
.function_pointer_decl => |fp| {
|
|
allocator.free(fp.name);
|
|
allocator.free(fp.return_type);
|
|
if (fp.doc_comment) |doc| allocator.free(doc);
|
|
for (fp.params) |param| {
|
|
allocator.free(param.name);
|
|
allocator.free(param.type_name);
|
|
}
|
|
allocator.free(fp.params);
|
|
},
|
|
.c_type_alias => |a| {
|
|
allocator.free(a.name);
|
|
if (a.doc_comment) |doc| allocator.free(doc);
|
|
},
|
|
.enum_decl => |e| {
|
|
allocator.free(e.name);
|
|
if (e.doc_comment) |doc| allocator.free(doc);
|
|
for (e.values) |val| {
|
|
allocator.free(val.name);
|
|
if (val.value) |v| allocator.free(v);
|
|
if (val.comment) |c| allocator.free(c);
|
|
}
|
|
allocator.free(e.values);
|
|
},
|
|
.struct_decl => |s| {
|
|
allocator.free(s.name);
|
|
if (s.doc_comment) |doc| allocator.free(doc);
|
|
for (s.fields) |field| {
|
|
allocator.free(field.name);
|
|
allocator.free(field.type_name);
|
|
if (field.comment) |c| allocator.free(c);
|
|
}
|
|
allocator.free(s.fields);
|
|
},
|
|
.union_decl => |u| {
|
|
allocator.free(u.name);
|
|
if (u.doc_comment) |doc| allocator.free(doc);
|
|
for (u.fields) |field| {
|
|
allocator.free(field.name);
|
|
allocator.free(field.type_name);
|
|
if (field.comment) |c| allocator.free(c);
|
|
}
|
|
allocator.free(u.fields);
|
|
},
|
|
.flag_decl => |f| {
|
|
allocator.free(f.name);
|
|
allocator.free(f.underlying_type);
|
|
if (f.doc_comment) |doc| allocator.free(doc);
|
|
for (f.flags) |flag| {
|
|
allocator.free(flag.name);
|
|
allocator.free(flag.value);
|
|
if (flag.comment) |c| allocator.free(c);
|
|
}
|
|
allocator.free(f.flags);
|
|
},
|
|
.function_decl => |func| {
|
|
allocator.free(func.name);
|
|
allocator.free(func.return_type);
|
|
if (func.doc_comment) |doc| allocator.free(doc);
|
|
for (func.params) |param| {
|
|
allocator.free(param.name);
|
|
allocator.free(param.type_name);
|
|
}
|
|
allocator.free(func.params);
|
|
},
|
|
}
|
|
}
|