const std = @import("std"); const patterns = @import("patterns.zig"); const codegen = @import("codegen.zig"); const dependency_resolver = @import("dependency_resolver.zig"); const json_serializer = @import("json_serializer.zig"); const io = @import("io.zig"); const header_cache = @import("header_cache.zig"); pub fn freeDecls(allocator: std.mem.Allocator, decls: []patterns.Declaration) void { for (decls) |decl| { freeDeclDeep(allocator, decl); } allocator.free(decls); } pub fn main() !void { const allocator = std.heap.smp_allocator; const args = try std.process.argsAlloc(allocator); defer std.process.argsFree(allocator, args); if (args.len < 2) { std.debug.print("Usage: {s} [--output=] [--mocks=] [--generate-json=] [--timestamp=] [--basedir=]\n", .{args[0]}); std.debug.print("Example: {s} ../SDL/include/SDL3/SDL_gpu.h --output=gpu.zig\n", .{args[0]}); std.debug.print(" {s} ../SDL/include/SDL3/SDL_gpu.h --output=gpu.zig --mocks=gpu_mock.c\n", .{args[0]}); std.debug.print(" {s} ../SDL/include/SDL3/SDL_gpu.h --generate-json=gpu.json\n", .{args[0]}); std.debug.print(" {s} ../SDL/include/SDL3/SDL_gpu.h > gpu.zig\n", .{args[0]}); std.debug.print(" {s} ../SDL/include/SDL3/SDL_gpu.h --basedir=/path/to/workdir\n", .{args[0]}); return error.MissingArgument; } const header_path = args[1]; var output_file: ?[]const u8 = null; var mock_output_file: ?[]const u8 = null; var json_output_file: ?[]const u8 = null; var timestamp: ?i64 = null; var basedir: ?[]const u8 = null; // Parse additional flags for (args[2..]) |arg| { const output_prefix = "--output="; const mocks_prefix = "--mocks="; const json_prefix = "--generate-json="; const timestamp_prefix = "--timestamp="; const basedir_prefix = "--basedir="; if (std.mem.startsWith(u8, arg, output_prefix)) { output_file = arg[output_prefix.len..]; } else if (std.mem.startsWith(u8, arg, mocks_prefix)) { mock_output_file = arg[mocks_prefix.len..]; } else if (std.mem.startsWith(u8, arg, json_prefix)) { json_output_file = arg[json_prefix.len..]; } else if (std.mem.startsWith(u8, arg, timestamp_prefix)) { timestamp = std.fmt.parseInt(i64, arg[timestamp_prefix.len..], 10) catch null; } else if (std.mem.startsWith(u8, arg, basedir_prefix)) { basedir = arg[basedir_prefix.len..]; } else { std.debug.print("Error: Unknown argument '{s}'\n", .{arg}); std.debug.print("Usage: {s} [--output=] [--mocks=] [--generate-json=] [--timestamp=] [--basedir=]\n", .{args[0]}); return error.InvalidArgument; } } // Change working directory if --basedir is specified if (basedir) |dir| { try std.posix.chdir(dir); std.debug.print("Changed working directory to: {s}\n\n", .{dir}); } // Archive any existing debug directory before this run archiveExistingDebugDir(allocator, timestamp); std.debug.print("SDL3 Header Parser\n", .{}); std.debug.print("==================\n\n", .{}); std.debug.print("Parsing: {s}\n\n", .{header_path}); // Read the header file const source = try std.fs.cwd().readFileAlloc(allocator, header_path, 10 * 1024 * 1024); // 10MB max defer allocator.free(source); // Parse declarations var scanner = patterns.Scanner.init(allocator, source); const decls = try scanner.scan(); defer freeDecls(allocator, decls); std.debug.print("Found {d} declarations\n", .{decls.len}); // Count each type var opaque_count: usize = 0; var typedef_count: usize = 0; var func_ptr_count: usize = 0; var c_type_alias_count: usize = 0; var enum_count: usize = 0; var struct_count: usize = 0; var union_count: usize = 0; var flag_count: usize = 0; var func_count: usize = 0; for (decls) |decl| { switch (decl) { .opaque_type => opaque_count += 1, .typedef_decl => typedef_count += 1, .function_pointer_decl => func_ptr_count += 1, .c_type_alias => c_type_alias_count += 1, .enum_decl => enum_count += 1, .struct_decl => struct_count += 1, .union_decl => union_count += 1, .flag_decl => flag_count += 1, .function_decl => func_count += 1, } } std.debug.print(" - Opaque types: {d}\n", .{opaque_count}); std.debug.print(" - Typedefs: {d}\n", .{typedef_count}); std.debug.print(" - Function pointers: {d}\n", .{func_ptr_count}); std.debug.print(" - C type aliases: {d}\n", .{c_type_alias_count}); std.debug.print(" - Enums: {d}\n", .{enum_count}); std.debug.print(" - Structs: {d}\n", .{struct_count}); std.debug.print(" - Unions: {d}\n", .{union_count}); std.debug.print(" - Flags: {d}\n", .{flag_count}); std.debug.print(" - Functions: {d}\n\n", .{func_count}); // Generate JSON if requested if (json_output_file) |json_path| { std.debug.print("Generating JSON output...\n", .{}); var serializer = json_serializer.JsonSerializer.init(allocator, std.fs.path.basename(header_path)); try serializer.addDeclarations(decls); const json_output = try serializer.finalize(); // json_output is owned by serializer // Parse and re-format JSON with proper indentation const parsed = try std.json.parseFromSlice(std.json.Value, allocator, json_output, .{}); defer parsed.deinit(); var formatted_output = std.ArrayList(u8){}; defer formatted_output.deinit(allocator); const formatter = std.json.fmt(parsed.value, .{ .whitespace = .indent_2 }); try std.fmt.format(formatted_output.writer(allocator), "{f}", .{formatter}); try ensureParentDirExists(json_path); try std.fs.cwd().writeFile(.{ .sub_path = json_path, .data = formatted_output.items, }); serializer.deinit(); std.debug.print("Generated JSON: {s}\n", .{json_path}); // If only JSON was requested, we're done if (output_file == null and mock_output_file == null) { return; } } // Analyze dependencies std.debug.print("Analyzing dependencies...\n", .{}); var resolver = dependency_resolver.DependencyResolver.init(allocator); defer resolver.deinit(); try resolver.analyze(decls); const missing_types = try resolver.getMissingTypes(allocator); defer { for (missing_types) |t| allocator.free(t); allocator.free(missing_types); } // Get hardcoded declarations for special types (like SDL_HitTest) // const hardcoded_decls = try resolver.getHardcodedDeclarations(allocator); // defer { // for (hardcoded_decls) |hd| { // freeDeclDeep(allocator, hd); // } // allocator.free(hardcoded_decls); // } // if (hardcoded_decls.len > 0) { // std.debug.print("Adding {d} hardcoded type declarations\n", .{hardcoded_decls.len}); // } var dependency_decls = std.ArrayList(patterns.Declaration){}; defer { for (dependency_decls.items) |dep_decl| { freeDeclDeep(allocator, dep_decl); } dependency_decls.deinit(allocator); } if (missing_types.len > 0) { std.debug.print("Found {d} missing types:\n", .{missing_types.len}); for (missing_types) |missing| { std.debug.print(" - {s}\n", .{missing}); } std.debug.print("\n", .{}); // PHASE 1: Build complete type cache from all SDL headers std.debug.print("Building type cache from all SDL headers...\n", .{}); var cache = try header_cache.HeaderCache.buildCache(allocator, header_path); defer cache.deinit(); std.debug.print("Type cache built successfully\n\n", .{}); // PHASE 2: Resolve dependencies via cache lookup with recursive dependency resolution std.debug.print("Resolving dependencies from cache...\n", .{}); // Track which types we've already added to avoid duplicates var added_types = std.StringHashMap(void).init(allocator); defer { var it = added_types.keyIterator(); while (it.next()) |key| { allocator.free(key.*); } added_types.deinit(); } for (missing_types) |missing_type| { // Skip if already added if (added_types.contains(missing_type)) continue; // Look up type with all its dependencies const resolved_decls = cache.lookupTypeWithDependencies(missing_type) catch |err| { std.debug.print(" Warning: Could not resolve {s}: {}\n", .{ missing_type, err }); continue; }; // Add all resolved declarations for (resolved_decls) |resolved_decl| { const decl_type_name = switch (resolved_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, // Skip functions }; if (!added_types.contains(decl_type_name)) { try dependency_decls.append(allocator, resolved_decl); try added_types.put(try allocator.dupe(u8, decl_type_name), {}); if (std.mem.eql(u8, decl_type_name, missing_type)) { std.debug.print(" Found {s} in cache\n", .{missing_type}); } else { std.debug.print(" + dependency: {s}\n", .{decl_type_name}); } } else { // Already added, free this duplicate freeDeclDeep(allocator, resolved_decl); } } allocator.free(resolved_decls); } std.debug.print("\n", .{}); } // Combine declarations (hardcoded first, then dependencies, then primary!) std.debug.print("Combining {d} dependency declarations with primary declarations...\n", .{dependency_decls.items.len}); var all_decls = std.ArrayList(patterns.Declaration){}; defer all_decls.deinit(allocator); // try all_decls.appendSlice(allocator, hardcoded_decls); try all_decls.appendSlice(allocator, dependency_decls.items); try all_decls.appendSlice(allocator, decls); // Generate code with all declarations const output = try codegen.CodeGen.generate(allocator, all_decls.items); defer allocator.free(output); // Parse and format the AST for validation const output_z = try allocator.dupeZ(u8, output); defer allocator.free(output_z); var ast = try std.zig.Ast.parse(allocator, output_z, .zig); defer ast.deinit(allocator); // Check for parse errors if (ast.errors.len > 0) { std.debug.print("{s}", .{output_z}); std.debug.print("\nError: {d} syntax errors detected in generated code\n", .{ast.errors.len}); for (ast.errors) |err| { const loc = ast.tokenLocation(0, err.token); std.debug.print(" Line {d}: {s}\n", .{ loc.line + 1, @tagName(err.tag) }); } // Write to debug file try writeDebugFile(allocator, header_path, output); // Write unformatted output for debugging if (output_file) |file_path| { try std.fs.cwd().writeFile(.{ .sub_path = file_path, .data = output, }); std.debug.print("\nGenerated (with errors): {s}\n", .{file_path}); } return error.InvalidSyntax; } // Render formatted output from AST const formatted_output = ast.renderAlloc(allocator) catch |err| { std.debug.print("\nError: AST render failed: {}\n", .{err}); try writeDebugFile(allocator, header_path, output); return err; }; defer allocator.free(formatted_output); // Write formatted output to file or stdout if (output_file) |file_path| { try writeZigFileWithFmt(allocator, header_path, file_path, formatted_output); } else { _ = try io.stdout().write(formatted_output); } // Generate C mocks if requested (with all declarations) if (mock_output_file) |mock_path| { const mock_codegen = @import("mock_codegen.zig"); const mock_output = try mock_codegen.MockCodeGen.generate(allocator, all_decls.items); defer allocator.free(mock_output); try ensureParentDirExists(mock_path); try std.fs.cwd().writeFile(.{ .sub_path = mock_path, .data = mock_output, }); std.debug.print("Generated C mocks: {s}\n", .{mock_path}); } } fn writeZigFileWithFmt(allocator: std.mem.Allocator, header_path: []const u8, output_path: []const u8, content: []const u8) !void { const header_basename = std.fs.path.basename(header_path); const output_basename = std.fs.path.basename(output_path); // Create tmp directory std.fs.cwd().makePath("tmp") catch |err| switch (err) { error.PathAlreadyExists => {}, else => return err, }; // Write to tmp file const tmp_path = try std.fmt.allocPrint(allocator, "tmp/{s}", .{output_basename}); defer allocator.free(tmp_path); try std.fs.cwd().writeFile(.{ .sub_path = tmp_path, .data = content, }); // Run zig ast-check on tmp file var ast_check = std.process.Child.init(&.{ "zig", "ast-check", tmp_path }, allocator); ast_check.stderr_behavior = .Pipe; try ast_check.spawn(); const ast_result = try ast_check.wait(); if (ast_result.Exited != 0) { // zig ast-check failed - copy to debug folder std.fs.cwd().makePath("debug") catch |err| switch (err) { error.PathAlreadyExists => {}, else => return err, }; // Convert header name: SDL_gpu.h -> SDL_gpu_h var safe_name = try allocator.alloc(u8, header_basename.len); defer allocator.free(safe_name); for (header_basename, 0..) |c, i| { safe_name[i] = if (c == '.') '_' else c; } const debug_path = try std.fmt.allocPrint(allocator, "debug/{s}_fmterror.zig", .{safe_name}); defer allocator.free(debug_path); try std.fs.cwd().writeFile(.{ .sub_path = debug_path, .data = content, }); std.debug.print("zig ast-check failed, debug output written to: {s}\n", .{debug_path}); return error.ZigAstCheckFailed; } // Run zig fmt on tmp file var fmt = std.process.Child.init(&.{ "zig", "fmt", tmp_path }, allocator); fmt.stderr_behavior = .Pipe; try fmt.spawn(); _ = try fmt.wait(); // Copy formatted file to final destination try ensureParentDirExists(output_path); const formatted_content = try std.fs.cwd().readFileAlloc(allocator, tmp_path, 10 * 1024 * 1024); defer allocator.free(formatted_content); try std.fs.cwd().writeFile(.{ .sub_path = output_path, .data = formatted_content, }); std.debug.print("Generated: {s}\n", .{output_path}); } fn ensureParentDirExists(path: []const u8) !void { if (std.fs.path.dirname(path)) |dir| { std.fs.cwd().makePath(dir) catch |err| switch (err) { error.PathAlreadyExists => {}, else => return err, }; } } fn archiveExistingDebugDir(allocator: std.mem.Allocator, provided_timestamp: ?i64) void { // Check if debug/ directory exists var debug_dir = std.fs.cwd().openDir("debug", .{}) catch return; debug_dir.close(); // Use provided timestamp or generate one const timestamp = provided_timestamp orelse std.time.timestamp(); const archive_path = std.fmt.allocPrint(allocator, "archive/debug/{d}", .{timestamp}) catch return; defer allocator.free(archive_path); // Create archive/debug directory if needed std.fs.cwd().makePath("archive/debug") catch return; // Move debug/ to archive/debug// std.fs.cwd().rename("debug", archive_path) catch |err| { std.debug.print("Warning: Failed to archive existing debug dir: {}\n", .{err}); return; }; std.debug.print("Archived existing debug/ to {s}\n", .{archive_path}); } fn writeDebugFile(allocator: std.mem.Allocator, header_path: []const u8, output: []const u8) !void { // Get the header basename (e.g., "SDL_gpu.h") const header_basename = std.fs.path.basename(header_path); // Convert to safe filename: SDL_gpu.h -> error_SDL_gpu_h.zig var safe_name = try allocator.alloc(u8, header_basename.len); defer allocator.free(safe_name); for (header_basename, 0..) |c, i| { safe_name[i] = if (c == '.') '_' else c; } const debug_filename = try std.fmt.allocPrint(allocator, "debug/error_{s}.zig", .{safe_name}); defer allocator.free(debug_filename); // Create debug directory if it doesn't exist std.fs.cwd().makeDir("debug") catch |err| switch (err) { error.PathAlreadyExists => {}, else => return err, }; try std.fs.cwd().writeFile(.{ .sub_path = debug_filename, .data = output, }); std.debug.print("Debug output written to: {s}\n", .{debug_filename}); } fn freeDeclDeep(allocator: std.mem.Allocator, decl: patterns.Declaration) void { switch (decl) { inline else => |*d| d.deinit(allocator), } } test "basic test" { try std.testing.expect(true); }