Backlog/lib/sdl3/parser/src/parser.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);
}