484 lines
19 KiB
Zig
484 lines
19 KiB
Zig
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} <header-file> [--output=<output-file>] [--mocks=<mock-file>] [--generate-json=<json-file>]\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} <header-file> [--output=<output-file>] [--mocks=<mock-file>] [--generate-json=<json-file>]\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);
|
|
}
|