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"); pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer { const leaked = gpa.deinit(); if (leaked == .leak) { std.debug.print("Memory leaked!\n", .{}); } } const allocator = gpa.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=]\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]}); 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; // Parse additional flags for (args[2..]) |arg| { const output_prefix = "--output="; const mocks_prefix = "--mocks="; const json_prefix = "--generate-json="; 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 { std.debug.print("Error: Unknown argument '{s}'\n", .{arg}); std.debug.print("Usage: {s} [--output=] [--mocks=] [--generate-json=]\n", .{args[0]}); return error.InvalidArgument; } } 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 { for (decls) |decl| { switch (decl) { .opaque_type => |opaque_decl| { allocator.free(opaque_decl.name); if (opaque_decl.doc_comment) |doc| allocator.free(doc); }, .typedef_decl => |typedef_decl| { allocator.free(typedef_decl.name); allocator.free(typedef_decl.underlying_type); if (typedef_decl.doc_comment) |doc| allocator.free(doc); }, .function_pointer_decl => |func_ptr_decl| { allocator.free(func_ptr_decl.name); allocator.free(func_ptr_decl.return_type); if (func_ptr_decl.doc_comment) |doc| allocator.free(doc); for (func_ptr_decl.params) |param| { allocator.free(param.name); allocator.free(param.type_name); } allocator.free(func_ptr_decl.params); }, .enum_decl => |enum_decl| { allocator.free(enum_decl.name); if (enum_decl.doc_comment) |doc| allocator.free(doc); for (enum_decl.values) |val| { allocator.free(val.name); if (val.value) |v| allocator.free(v); if (val.comment) |c| allocator.free(c); } allocator.free(enum_decl.values); }, .struct_decl => |struct_decl| { allocator.free(struct_decl.name); if (struct_decl.doc_comment) |doc| allocator.free(doc); for (struct_decl.fields) |field| { allocator.free(field.name); allocator.free(field.type_name); if (field.comment) |c| allocator.free(c); } allocator.free(struct_decl.fields); }, .union_decl => |union_decl| { allocator.free(union_decl.name); if (union_decl.doc_comment) |doc| allocator.free(doc); for (union_decl.fields) |field| { allocator.free(field.name); allocator.free(field.type_name); if (field.comment) |c| allocator.free(c); } allocator.free(union_decl.fields); }, .flag_decl => |flag_decl| { allocator.free(flag_decl.name); allocator.free(flag_decl.underlying_type); if (flag_decl.doc_comment) |doc| allocator.free(doc); for (flag_decl.flags) |flag| { allocator.free(flag.name); allocator.free(flag.value); if (flag.comment) |c| allocator.free(c); } allocator.free(flag_decl.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); }, } } allocator.free(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 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, .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(" - 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 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); } 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", .{}); // Extract missing types from included headers std.debug.print("Resolving dependencies from included headers...\n", .{}); const includes = try dependency_resolver.parseIncludes(allocator, source); defer { for (includes) |inc| allocator.free(inc); allocator.free(includes); } const header_dir = std.fs.path.dirname(header_path) orelse "."; var dependency_decls = std.ArrayList(patterns.Declaration){}; defer { for (dependency_decls.items) |dep_decl| { freeDeclDeep(allocator, dep_decl); } dependency_decls.deinit(allocator); } for (missing_types) |missing_type| { var found = false; for (includes) |include| { const dep_path = try std.fs.path.join( allocator, &[_][]const u8{ header_dir, include } ); defer allocator.free(dep_path); const dep_source = std.fs.cwd().readFileAlloc( allocator, dep_path, 10 * 1024 * 1024 ) catch continue; defer allocator.free(dep_source); if (try dependency_resolver.extractTypeFromHeader(allocator, dep_source, missing_type)) |dep_decl| { try dependency_decls.append(allocator, dep_decl); std.debug.print(" ✓ Found {s} in {s}\n", .{missing_type, include}); found = true; break; } } if (!found) { std.debug.print(" ⚠ Warning: Could not find definition for type: {s}\n", .{missing_type}); } } // Combine declarations (dependencies first!) std.debug.print("\nCombining {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, 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("\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 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 = try ast.renderAlloc(allocator); defer allocator.free(formatted_output); // Write formatted output to file or stdout if (output_file) |file_path| { try std.fs.cwd().writeFile(.{ .sub_path = file_path, .data = formatted_output, }); std.debug.print("Generated: {s}\n", .{file_path}); } else { _ = try std.posix.write(std.posix.STDOUT_FILENO, 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 std.fs.cwd().writeFile(.{ .sub_path = mock_path, .data = mock_output, }); std.debug.print("Generated C mocks: {s}\n", .{mock_path}); } } else { std.debug.print("No missing dependencies found!\n\n", .{}); // Generate code without dependencies const output = try codegen.CodeGen.generate(allocator, decls); 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("\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 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 = try ast.renderAlloc(allocator); defer allocator.free(formatted_output); // Write formatted output to file or stdout if (output_file) |file_path| { try std.fs.cwd().writeFile(.{ .sub_path = file_path, .data = formatted_output, }); std.debug.print("Generated: {s}\n", .{file_path}); } else { _ = try std.posix.write(std.posix.STDOUT_FILENO, formatted_output); } // Generate C mocks if requested if (mock_output_file) |mock_path| { const mock_codegen = @import("mock_codegen.zig"); const mock_output = try mock_codegen.MockCodeGen.generate(allocator, decls); defer allocator.free(mock_output); try std.fs.cwd().writeFile(.{ .sub_path = mock_path, .data = mock_output, }); std.debug.print("Generated C mocks: {s}\n", .{mock_path}); } } } fn freeDeclDeep(allocator: std.mem.Allocator, decl: patterns.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); }, .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); }, } } test "basic test" { try std.testing.expect(true); }