443 lines
17 KiB
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);
|
|
},
|
|
}
|
|
}
|