sdlparser-scrap/src/header_cache.zig

443 lines
17 KiB
Zig

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 <SDL3/SDL_something.h>
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];
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);
},
}
}