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 const trimmed = std.mem.trim(u8, line, " \t\r"); if (std.mem.startsWith(u8, trimmed, "#include ")) |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); }, } }