const std = @import("std"); const Allocator = std.mem.Allocator; const patterns = @import("patterns.zig"); const Declaration = patterns.Declaration; pub const HeaderCache = struct { // Map from type name to its declaration type_cache: std.StringHashMap(Declaration), // Track which headers we've already parsed (avoid re-parsing) parsed_headers: std.StringHashMap(void), allocator: Allocator, pub fn init(allocator: Allocator) HeaderCache { return .{ .type_cache = std.StringHashMap(Declaration).init(allocator), .parsed_headers = std.StringHashMap(void).init(allocator), .allocator = allocator, }; } pub fn deinit(self: *HeaderCache) void { // Free type cache keys var type_it = self.type_cache.keyIterator(); while (type_it.next()) |key| { self.allocator.free(key.*); } // Free cached declarations var val_it = self.type_cache.valueIterator(); while (val_it.next()) |decl| { freeDeclaration(self.allocator, decl.*); } self.type_cache.deinit(); // Free parsed headers keys var header_it = self.parsed_headers.keyIterator(); while (header_it.next()) |key| { self.allocator.free(key.*); } self.parsed_headers.deinit(); } pub fn buildCache(allocator: Allocator, header_path: []const u8) !HeaderCache { var cache = HeaderCache.init(allocator); errdefer cache.deinit(); const header_dir = std.fs.path.dirname(header_path) orelse "."; try cache.parseHeaderRecursive(header_dir, std.fs.path.basename(header_path)); return cache; } fn parseHeaderRecursive(self: *HeaderCache, header_dir: []const u8, filename: []const u8) !void { // Skip non-SDL headers (stdlib, system headers, etc) if (!std.mem.startsWith(u8, filename, "SDL_")) { return; } // Skip if already parsed if (self.parsed_headers.contains(filename)) { return; } // Mark as parsed try self.parsed_headers.put(try self.allocator.dupe(u8, filename), {}); // Build full path const full_path = try std.fs.path.join(self.allocator, &[_][]const u8{ header_dir, filename }); defer self.allocator.free(full_path); std.debug.print(" Caching types from: {s}\n", .{filename}); // Read and parse this header const content = std.fs.cwd().readFileAlloc(self.allocator, full_path, 10_000_000) catch |err| { std.debug.print(" Warning: Could not read {s}: {}\n", .{ filename, err }); return; }; defer self.allocator.free(content); var scanner = patterns.Scanner.init(self.allocator, content); const decls = scanner.scan() catch |err| { std.debug.print(" Warning: Could not parse {s}: {}\n", .{ filename, err }); return; }; defer self.allocator.free(decls); // Add all type definitions to cache for (decls) |decl| { const type_name = getTypeName(decl); if (type_name) |name| { // Don't cache if already present (first occurrence wins) if (!self.type_cache.contains(name)) { const name_copy = try self.allocator.dupe(u8, name); const decl_copy = try cloneDeclaration(self.allocator, decl); try self.type_cache.put(name_copy, decl_copy); } } } // Free the original declarations (we made copies) for (decls) |decl| { freeDeclaration(self.allocator, decl); } // Find and recursively parse all #include "SDL_*.h" directives const includes = try findSDLIncludes(self.allocator, content); defer { for (includes) |inc| self.allocator.free(inc); self.allocator.free(includes); } for (includes) |inc_filename| { try self.parseHeaderRecursive(header_dir, inc_filename); } } pub fn lookupType(self: *HeaderCache, type_name: []const u8) ?Declaration { const decl = self.type_cache.get(type_name) orelse return null; // Return a copy so caller owns it return cloneDeclaration(self.allocator, decl) catch null; } /// Lookup a type and recursively resolve all its dependencies /// Returns an array of declarations: [0] is the requested type, [1..] are dependencies pub fn lookupTypeWithDependencies(self: *HeaderCache, type_name: []const u8) ![]Declaration { const dependency_resolver = @import("dependency_resolver.zig"); var result = try std.ArrayList(Declaration).initCapacity(self.allocator, 10); errdefer { for (result.items) |decl| { freeDeclaration(self.allocator, decl); } result.deinit(self.allocator); } // Track which types we've already processed var processed = std.StringHashMap(void).init(self.allocator); defer { var it = processed.keyIterator(); while (it.next()) |key| { self.allocator.free(key.*); } processed.deinit(); } // Work queue for types to resolve var queue = try std.ArrayList([]const u8).initCapacity(self.allocator, 10); defer { for (queue.items) |item| { self.allocator.free(item); } queue.deinit(self.allocator); } // Start with the requested type try queue.append(self.allocator, try self.allocator.dupe(u8, type_name)); // Process queue until empty while (queue.items.len > 0) { const current_type = queue.orderedRemove(0); defer self.allocator.free(current_type); // Skip if already processed if (processed.contains(current_type)) continue; try processed.put(try self.allocator.dupe(u8, current_type), {}); // Look up the type const decl = self.type_cache.get(current_type) orelse continue; // Add cloned declaration to result const decl_copy = try cloneDeclaration(self.allocator, decl); try result.append(self.allocator, decl_copy); // Analyze this declaration to find its dependencies var resolver = dependency_resolver.DependencyResolver.init(self.allocator); defer resolver.deinit(); const single_decl = [_]Declaration{decl}; try resolver.analyze(&single_decl); const missing = try resolver.getMissingTypes(self.allocator); defer { for (missing) |m| self.allocator.free(m); self.allocator.free(missing); } // Add missing types to queue if not already processed for (missing) |missing_type| { if (!processed.contains(missing_type) and self.type_cache.contains(missing_type)) { try queue.append(self.allocator, try self.allocator.dupe(u8, missing_type)); } } } return try result.toOwnedSlice(self.allocator); } fn getTypeName(decl: Declaration) ?[]const u8 { return 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 => null, // Functions don't define types }; } fn findSDLIncludes(allocator: Allocator, source: []const u8) ![][]const u8 { var includes = std.ArrayList([]const u8){}; errdefer { for (includes.items) |inc| allocator.free(inc); includes.deinit(allocator); } var lines = std.mem.splitScalar(u8, source, '\n'); while (lines.next()) |line| { const trimmed = std.mem.trim(u8, line, " \t\r"); // Match: #include if (std.mem.startsWith(u8, trimmed, "#include ")) |end| { const header_name = trimmed[after_open..][0..end]; if (std.mem.startsWith(u8, header_name, "SDL_")) { try includes.append(allocator, try allocator.dupe(u8, header_name)); } } } // Also match: #include "SDL_something.h" else if (std.mem.startsWith(u8, trimmed, "#include \"SDL_")) { const after_open = "#include \"".len; if (std.mem.indexOf(u8, trimmed[after_open..], "\"")) |end| { const header_name = trimmed[after_open..][0..end]; if (std.mem.startsWith(u8, header_name, "SDL_")) { try includes.append(allocator, try allocator.dupe(u8, header_name)); } } } } return try includes.toOwnedSlice(allocator); } }; fn cloneDeclaration(allocator: Allocator, decl: Declaration) !Declaration { return switch (decl) { .opaque_type => |o| Declaration{ .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| Declaration{ .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, }, }, .enum_decl => |e| Declaration{ .enum_decl = .{ .name = try allocator.dupe(u8, e.name), .values = try cloneEnumValues(allocator, e.values), .doc_comment = if (e.doc_comment) |doc| try allocator.dupe(u8, doc) else null, }, }, .struct_decl => |s| Declaration{ .struct_decl = .{ .name = try allocator.dupe(u8, s.name), .fields = try cloneFields(allocator, s.fields), .doc_comment = if (s.doc_comment) |doc| try allocator.dupe(u8, doc) else null, .has_unions = s.has_unions, }, }, .union_decl => |u| Declaration{ .union_decl = .{ .name = try allocator.dupe(u8, u.name), .fields = try cloneFields(allocator, u.fields), .doc_comment = if (u.doc_comment) |doc| try allocator.dupe(u8, doc) else null, }, }, .flag_decl => |f| Declaration{ .flag_decl = .{ .name = try allocator.dupe(u8, f.name), .underlying_type = try allocator.dupe(u8, f.underlying_type), .flags = try cloneFlagValues(allocator, f.flags), .doc_comment = if (f.doc_comment) |doc| try allocator.dupe(u8, doc) else null, }, }, .function_decl => |func| Declaration{ .function_decl = .{ .name = try allocator.dupe(u8, func.name), .return_type = try allocator.dupe(u8, func.return_type), .params = try cloneParams(allocator, func.params), .doc_comment = if (func.doc_comment) |doc| try allocator.dupe(u8, doc) else null, }, }, .function_pointer_decl => |fp| Declaration{ .function_pointer_decl = .{ .name = try allocator.dupe(u8, fp.name), .return_type = try allocator.dupe(u8, fp.return_type), .params = try cloneParams(allocator, fp.params), .doc_comment = if (fp.doc_comment) |doc| try allocator.dupe(u8, doc) else null, }, }, .c_type_alias => |a| Declaration{ .c_type_alias = .{ .name = try allocator.dupe(u8, a.name), .doc_comment = if (a.doc_comment) |doc| try allocator.dupe(u8, doc) else null, }, }, }; } fn cloneEnumValues(allocator: Allocator, values: []const patterns.EnumValue) ![]patterns.EnumValue { const new_values = try allocator.alloc(patterns.EnumValue, values.len); for (values, 0..) |val, i| { new_values[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 new_values; } fn cloneFlagValues(allocator: Allocator, flags: []const patterns.FlagValue) ![]patterns.FlagValue { const new_flags = try allocator.alloc(patterns.FlagValue, flags.len); for (flags, 0..) |flag, i| { new_flags[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 new_flags; } fn cloneFields(allocator: Allocator, fields: []const patterns.FieldDecl) ![]patterns.FieldDecl { const new_fields = try allocator.alloc(patterns.FieldDecl, fields.len); for (fields, 0..) |field, i| { new_fields[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 new_fields; } fn cloneParams(allocator: Allocator, params: []const patterns.ParamDecl) ![]patterns.ParamDecl { const new_params = try allocator.alloc(patterns.ParamDecl, params.len); for (params, 0..) |param, i| { new_params[i] = .{ .name = try allocator.dupe(u8, param.name), .type_name = try allocator.dupe(u8, param.type_name), }; } return new_params; } 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); }, .enum_decl => |e| { allocator.free(e.name); 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); if (e.doc_comment) |doc| allocator.free(doc); }, .struct_decl => |s| { allocator.free(s.name); for (s.fields) |field| { allocator.free(field.name); allocator.free(field.type_name); if (field.comment) |c| allocator.free(c); } allocator.free(s.fields); if (s.doc_comment) |doc| allocator.free(doc); }, .union_decl => |u| { allocator.free(u.name); for (u.fields) |field| { allocator.free(field.name); allocator.free(field.type_name); if (field.comment) |c| allocator.free(c); } allocator.free(u.fields); if (u.doc_comment) |doc| allocator.free(doc); }, .flag_decl => |f| { allocator.free(f.name); allocator.free(f.underlying_type); for (f.flags) |flag| { allocator.free(flag.name); allocator.free(flag.value); if (flag.comment) |c| allocator.free(c); } allocator.free(f.flags); if (f.doc_comment) |doc| allocator.free(doc); }, .function_decl => |func| { allocator.free(func.name); allocator.free(func.return_type); for (func.params) |param| { allocator.free(param.name); allocator.free(param.type_name); } allocator.free(func.params); if (func.doc_comment) |doc| allocator.free(doc); }, .function_pointer_decl => |fp| { allocator.free(fp.name); allocator.free(fp.return_type); for (fp.params) |param| { allocator.free(param.name); allocator.free(param.type_name); } allocator.free(fp.params); if (fp.doc_comment) |doc| allocator.free(doc); }, .c_type_alias => |a| { allocator.free(a.name); if (a.doc_comment) |doc| allocator.free(doc); }, } }