Implement JSON output feature for SDL parser

- Add --generate-json flag to output structured JSON representation
- JSON includes all parsed types: opaques, typedefs, function pointers,
  enums, structs, unions, flags, and functions
- Preserves doc comments and inline comments in JSON output
- Proper JSON escaping for special characters
- Tested with SDL_gpu.h and test_small.h
- Validates as proper JSON format
This commit is contained in:
Peterino2 2026-01-22 15:30:02 -08:00
parent 743845de9b
commit b7ec134b0e
4 changed files with 586 additions and 2 deletions

189
lib/sdl3/parser/SDL_gpu.json vendored Normal file

File diff suppressed because one or more lines are too long

344
lib/sdl3/parser/src/json_serializer.zig vendored Normal file
View File

@ -0,0 +1,344 @@
const std = @import("std");
const patterns = @import("patterns.zig");
pub const JsonSerializer = struct {
allocator: std.mem.Allocator,
output: std.ArrayList(u8),
header_name: []const u8,
opaque_types: std.ArrayList(patterns.OpaqueType),
typedefs: std.ArrayList(patterns.TypedefDecl),
function_pointers: std.ArrayList(patterns.FunctionPointerDecl),
enums: std.ArrayList(patterns.EnumDecl),
structs: std.ArrayList(patterns.StructDecl),
unions: std.ArrayList(patterns.UnionDecl),
flags: std.ArrayList(patterns.FlagDecl),
functions: std.ArrayList(patterns.FunctionDecl),
pub fn init(allocator: std.mem.Allocator, header_name: []const u8) JsonSerializer {
return .{
.allocator = allocator,
.output = std.ArrayList(u8){},
.header_name = header_name,
.opaque_types = std.ArrayList(patterns.OpaqueType){},
.typedefs = std.ArrayList(patterns.TypedefDecl){},
.function_pointers = std.ArrayList(patterns.FunctionPointerDecl){},
.enums = std.ArrayList(patterns.EnumDecl){},
.structs = std.ArrayList(patterns.StructDecl){},
.unions = std.ArrayList(patterns.UnionDecl){},
.flags = std.ArrayList(patterns.FlagDecl){},
.functions = std.ArrayList(patterns.FunctionDecl){},
};
}
pub fn deinit(self: *JsonSerializer) void {
self.output.deinit(self.allocator);
self.opaque_types.deinit(self.allocator);
self.typedefs.deinit(self.allocator);
self.function_pointers.deinit(self.allocator);
self.enums.deinit(self.allocator);
self.structs.deinit(self.allocator);
self.unions.deinit(self.allocator);
self.flags.deinit(self.allocator);
self.functions.deinit(self.allocator);
}
pub fn addDeclarations(self: *JsonSerializer, decls: []const patterns.Declaration) !void {
for (decls) |decl| {
switch (decl) {
.opaque_type => |o| try self.opaque_types.append(self.allocator, o),
.typedef_decl => |t| try self.typedefs.append(self.allocator, t),
.function_pointer_decl => |fp| try self.function_pointers.append(self.allocator, fp),
.enum_decl => |e| try self.enums.append(self.allocator, e),
.struct_decl => |s| try self.structs.append(self.allocator, s),
.union_decl => |u| try self.unions.append(self.allocator, u),
.flag_decl => |f| try self.flags.append(self.allocator, f),
.function_decl => |fn_decl| try self.functions.append(self.allocator, fn_decl),
}
}
}
pub fn finalize(self: *JsonSerializer) ![]const u8 {
var writer = self.output.writer(self.allocator);
try writer.writeAll("{\n");
try writer.writeAll(" \"header\": ");
try self.writeString(writer, self.header_name);
try writer.writeAll(",\n");
// Serialize opaque types
try writer.writeAll(" \"opaque_types\": [\n");
for (self.opaque_types.items, 0..) |opaque_type, i| {
try writer.writeAll(" ");
try self.serializeOpaqueType(writer, opaque_type);
if (i < self.opaque_types.items.len - 1) try writer.writeAll(",");
try writer.writeAll("\n");
}
try writer.writeAll(" ],\n");
// Serialize typedefs
try writer.writeAll(" \"typedefs\": [\n");
for (self.typedefs.items, 0..) |typedef, i| {
try writer.writeAll(" ");
try self.serializeTypedef(writer, typedef);
if (i < self.typedefs.items.len - 1) try writer.writeAll(",");
try writer.writeAll("\n");
}
try writer.writeAll(" ],\n");
// Serialize function pointers
try writer.writeAll(" \"function_pointers\": [\n");
for (self.function_pointers.items, 0..) |fp, i| {
try writer.writeAll(" ");
try self.serializeFunctionPointer(writer, fp);
if (i < self.function_pointers.items.len - 1) try writer.writeAll(",");
try writer.writeAll("\n");
}
try writer.writeAll(" ],\n");
// Serialize enums
try writer.writeAll(" \"enums\": [\n");
for (self.enums.items, 0..) |enum_decl, i| {
try writer.writeAll(" ");
try self.serializeEnum(writer, enum_decl);
if (i < self.enums.items.len - 1) try writer.writeAll(",");
try writer.writeAll("\n");
}
try writer.writeAll(" ],\n");
// Serialize structs
try writer.writeAll(" \"structs\": [\n");
for (self.structs.items, 0..) |struct_decl, i| {
try writer.writeAll(" ");
try self.serializeStruct(writer, struct_decl);
if (i < self.structs.items.len - 1) try writer.writeAll(",");
try writer.writeAll("\n");
}
try writer.writeAll(" ],\n");
// Serialize unions
try writer.writeAll(" \"unions\": [\n");
for (self.unions.items, 0..) |union_decl, i| {
try writer.writeAll(" ");
try self.serializeUnion(writer, union_decl);
if (i < self.unions.items.len - 1) try writer.writeAll(",");
try writer.writeAll("\n");
}
try writer.writeAll(" ],\n");
// Serialize flags
try writer.writeAll(" \"flags\": [\n");
for (self.flags.items, 0..) |flag, i| {
try writer.writeAll(" ");
try self.serializeFlag(writer, flag);
if (i < self.flags.items.len - 1) try writer.writeAll(",");
try writer.writeAll("\n");
}
try writer.writeAll(" ],\n");
// Serialize functions
try writer.writeAll(" \"functions\": [\n");
for (self.functions.items, 0..) |func, i| {
try writer.writeAll(" ");
try self.serializeFunction(writer, func);
if (i < self.functions.items.len - 1) try writer.writeAll(",");
try writer.writeAll("\n");
}
try writer.writeAll(" ]\n");
try writer.writeAll("}\n");
return self.output.items;
}
fn serializeOpaqueType(self: *JsonSerializer, writer: anytype, opaque_type: patterns.OpaqueType) !void {
try writer.writeAll("{\"name\": ");
try self.writeString(writer, opaque_type.name);
if (opaque_type.doc_comment) |doc| {
try writer.writeAll(", \"doc\": ");
try self.writeString(writer, doc);
}
try writer.writeAll("}");
}
fn serializeTypedef(self: *JsonSerializer, writer: anytype, typedef: patterns.TypedefDecl) !void {
try writer.writeAll("{\"name\": ");
try self.writeString(writer, typedef.name);
try writer.writeAll(", \"underlying_type\": ");
try self.writeString(writer, typedef.underlying_type);
if (typedef.doc_comment) |doc| {
try writer.writeAll(", \"doc\": ");
try self.writeString(writer, doc);
}
try writer.writeAll("}");
}
fn serializeFunctionPointer(self: *JsonSerializer, writer: anytype, fp: patterns.FunctionPointerDecl) !void {
try writer.writeAll("{\"name\": ");
try self.writeString(writer, fp.name);
try writer.writeAll(", \"return_type\": ");
try self.writeString(writer, fp.return_type);
try writer.writeAll(", \"parameters\": [");
for (fp.params, 0..) |param, i| {
try writer.writeAll("{\"name\": ");
try self.writeString(writer, param.name);
try writer.writeAll(", \"type\": ");
try self.writeString(writer, param.type_name);
try writer.writeAll("}");
if (i < fp.params.len - 1) try writer.writeAll(", ");
}
try writer.writeAll("]");
if (fp.doc_comment) |doc| {
try writer.writeAll(", \"doc\": ");
try self.writeString(writer, doc);
}
try writer.writeAll("}");
}
fn serializeEnum(self: *JsonSerializer, writer: anytype, enum_decl: patterns.EnumDecl) !void {
try writer.writeAll("{\"name\": ");
try self.writeString(writer, enum_decl.name);
try writer.writeAll(", \"values\": [");
for (enum_decl.values, 0..) |value, i| {
try writer.writeAll("{\"name\": ");
try self.writeString(writer, value.name);
if (value.value) |val| {
try writer.writeAll(", \"value\": ");
try self.writeString(writer, val);
}
if (value.comment) |comment| {
try writer.writeAll(", \"comment\": ");
try self.writeString(writer, comment);
}
try writer.writeAll("}");
if (i < enum_decl.values.len - 1) try writer.writeAll(", ");
}
try writer.writeAll("]");
if (enum_decl.doc_comment) |doc| {
try writer.writeAll(", \"doc\": ");
try self.writeString(writer, doc);
}
try writer.writeAll("}");
}
fn serializeStruct(self: *JsonSerializer, writer: anytype, struct_decl: patterns.StructDecl) !void {
try writer.writeAll("{\"name\": ");
try self.writeString(writer, struct_decl.name);
try writer.writeAll(", \"fields\": [");
for (struct_decl.fields, 0..) |field, i| {
try writer.writeAll("{\"name\": ");
try self.writeString(writer, field.name);
try writer.writeAll(", \"type\": ");
try self.writeString(writer, field.type_name);
if (field.comment) |comment| {
try writer.writeAll(", \"comment\": ");
try self.writeString(writer, comment);
}
try writer.writeAll("}");
if (i < struct_decl.fields.len - 1) try writer.writeAll(", ");
}
try writer.writeAll("]");
if (struct_decl.doc_comment) |doc| {
try writer.writeAll(", \"doc\": ");
try self.writeString(writer, doc);
}
try writer.writeAll("}");
}
fn serializeUnion(self: *JsonSerializer, writer: anytype, union_decl: patterns.UnionDecl) !void {
try writer.writeAll("{\"name\": ");
try self.writeString(writer, union_decl.name);
try writer.writeAll(", \"fields\": [");
for (union_decl.fields, 0..) |field, i| {
try writer.writeAll("{\"name\": ");
try self.writeString(writer, field.name);
try writer.writeAll(", \"type\": ");
try self.writeString(writer, field.type_name);
if (field.comment) |comment| {
try writer.writeAll(", \"comment\": ");
try self.writeString(writer, comment);
}
try writer.writeAll("}");
if (i < union_decl.fields.len - 1) try writer.writeAll(", ");
}
try writer.writeAll("]");
if (union_decl.doc_comment) |doc| {
try writer.writeAll(", \"doc\": ");
try self.writeString(writer, doc);
}
try writer.writeAll("}");
}
fn serializeFlag(self: *JsonSerializer, writer: anytype, flag: patterns.FlagDecl) !void {
try writer.writeAll("{\"name\": ");
try self.writeString(writer, flag.name);
try writer.writeAll(", \"underlying_type\": ");
try self.writeString(writer, flag.underlying_type);
try writer.writeAll(", \"values\": [");
for (flag.flags, 0..) |value, i| {
try writer.writeAll("{\"name\": ");
try self.writeString(writer, value.name);
try writer.writeAll(", \"value\": ");
try self.writeString(writer, value.value);
if (value.comment) |comment| {
try writer.writeAll(", \"comment\": ");
try self.writeString(writer, comment);
}
try writer.writeAll("}");
if (i < flag.flags.len - 1) try writer.writeAll(", ");
}
try writer.writeAll("]");
if (flag.doc_comment) |doc| {
try writer.writeAll(", \"doc\": ");
try self.writeString(writer, doc);
}
try writer.writeAll("}");
}
fn serializeFunction(self: *JsonSerializer, writer: anytype, func: patterns.FunctionDecl) !void {
try writer.writeAll("{\"name\": ");
try self.writeString(writer, func.name);
try writer.writeAll(", \"return_type\": ");
try self.writeString(writer, func.return_type);
try writer.writeAll(", \"parameters\": [");
for (func.params, 0..) |param, i| {
try writer.writeAll("{\"name\": ");
try self.writeString(writer, param.name);
try writer.writeAll(", \"type\": ");
try self.writeString(writer, param.type_name);
try writer.writeAll("}");
if (i < func.params.len - 1) try writer.writeAll(", ");
}
try writer.writeAll("]");
if (func.doc_comment) |doc| {
try writer.writeAll(", \"doc\": ");
try self.writeString(writer, doc);
}
try writer.writeAll("}");
}
fn writeString(self: *JsonSerializer, writer: anytype, str: []const u8) !void {
_ = self;
try writer.writeAll("\"");
for (str) |c| {
switch (c) {
'"' => try writer.writeAll("\\\""),
'\\' => try writer.writeAll("\\\\"),
'\n' => try writer.writeAll("\\n"),
'\r' => try writer.writeAll("\\r"),
'\t' => try writer.writeAll("\\t"),
else => try writer.writeByte(c),
}
}
try writer.writeAll("\"");
}
};

View File

@ -2,6 +2,7 @@ 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(.{}){};
@ -17,9 +18,10 @@ pub fn main() !void {
defer std.process.argsFree(allocator, args);
if (args.len < 2) {
std.debug.print("Usage: {s} <header-file> [--output=<output-file>] [--mocks=<mock-file>]\n", .{args[0]});
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;
}
@ -28,18 +30,22 @@ pub fn main() !void {
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>]\n", .{args[0]});
std.debug.print("Usage: {s} <header-file> [--output=<output-file>] [--mocks=<mock-file>] [--generate-json=<json-file>]\n", .{args[0]});
return error.InvalidArgument;
}
}
@ -167,6 +173,29 @@ pub fn main() !void {
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, so we need to write it before deinit
try std.fs.cwd().writeFile(.{
.sub_path = json_path,
.data = json_output,
});
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);

22
lib/sdl3/parser/test_small.json vendored Normal file
View File

@ -0,0 +1,22 @@
{
"header": "test_small.h",
"opaque_types": [
{"name": "SDL_GPUDevice"}
],
"typedefs": [
],
"function_pointers": [
],
"enums": [
{"name": "SDL_GPUPrimitiveType", "values": []}
],
"structs": [
],
"unions": [
],
"flags": [
],
"functions": [
{"name": "SDL_CreateGPUDevice", "return_type": "SDL_GPUDevice*", "parameters": [{"name": "debug_mode", "type": "bool"}]}
]
}