saving
This commit is contained in:
parent
2d59109994
commit
eee1bd265e
|
|
@ -0,0 +1,43 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
// Parser executable
|
||||
const parser_exe = b.addExecutable(.{
|
||||
.name = "sdl-parser",
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("parser.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
}),
|
||||
});
|
||||
|
||||
b.installArtifact(parser_exe);
|
||||
|
||||
// Run command
|
||||
const run_cmd = b.addRunArtifact(parser_exe);
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
const run_step = b.step("run", "Run the SDL3 header parser");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
|
||||
// Tests
|
||||
const parser_tests = b.addTest(.{
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("parser.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
}),
|
||||
});
|
||||
|
||||
const run_tests = b.addRunArtifact(parser_tests);
|
||||
|
||||
const test_step = b.step("test", "Run parser tests");
|
||||
test_step.dependOn(&run_tests.step);
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
/// Remove SDL_ prefix from a name
|
||||
pub fn stripSDLPrefix(name: []const u8) []const u8 {
|
||||
if (std.mem.startsWith(u8, name, "SDL_")) {
|
||||
return name[4..];
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/// Convert SDL type name to Zig type name
|
||||
/// SDL_GPUDevice -> GPUDevice
|
||||
pub fn typeNameToZig(c_name: []const u8) []const u8 {
|
||||
return stripSDLPrefix(c_name);
|
||||
}
|
||||
|
||||
/// Convert SDL function name to Zig function name
|
||||
/// SDL_CreateGPUDevice -> createGPUDevice
|
||||
pub fn functionNameToZig(c_name: []const u8, allocator: Allocator) ![]const u8 {
|
||||
const without_prefix = stripSDLPrefix(c_name);
|
||||
if (without_prefix.len == 0) return try allocator.dupe(u8, c_name);
|
||||
|
||||
// Lowercase the first character
|
||||
var result = try allocator.dupe(u8, without_prefix);
|
||||
if (result.len > 0) {
|
||||
result[0] = std.ascii.toLower(result[0]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Detect common prefix in a list of names
|
||||
/// Returns the longest common prefix
|
||||
pub fn detectCommonPrefix(names: []const []const u8, allocator: Allocator) ![]const u8 {
|
||||
if (names.len == 0) return try allocator.dupe(u8, "");
|
||||
if (names.len == 1) return try allocator.dupe(u8, names[0]);
|
||||
|
||||
const first = names[0];
|
||||
var prefix_len: usize = 0;
|
||||
|
||||
// Find longest common prefix
|
||||
outer: for (first, 0..) |c, i| {
|
||||
for (names[1..]) |name| {
|
||||
if (i >= name.len or name[i] != c) {
|
||||
break :outer;
|
||||
}
|
||||
}
|
||||
prefix_len = i + 1;
|
||||
}
|
||||
|
||||
return try allocator.dupe(u8, first[0..prefix_len]);
|
||||
}
|
||||
|
||||
/// Convert enum value name to Zig
|
||||
/// SDL_GPU_PRIMITIVETYPE_TRIANGLELIST -> primitivetypeTrianglelist
|
||||
pub fn enumValueToZig(c_name: []const u8, prefix: []const u8, allocator: Allocator) ![]const u8 {
|
||||
// Remove prefix
|
||||
var name = c_name;
|
||||
if (std.mem.startsWith(u8, name, prefix)) {
|
||||
name = name[prefix.len..];
|
||||
}
|
||||
|
||||
// Convert SCREAMING_SNAKE_CASE to camelCase
|
||||
return try screaminToLowerCamel(name, allocator);
|
||||
}
|
||||
|
||||
/// Convert flag name to Zig
|
||||
/// SDL_GPU_TEXTUREUSAGE_SAMPLER -> textureusageSampler
|
||||
pub fn flagNameToZig(c_name: []const u8, prefix: []const u8, allocator: Allocator) ![]const u8 {
|
||||
return enumValueToZig(c_name, prefix, allocator);
|
||||
}
|
||||
|
||||
/// Convert SCREAMING_SNAKE_CASE to lowerCamelCase
|
||||
fn screaminToLowerCamel(s: []const u8, allocator: Allocator) ![]const u8 {
|
||||
if (s.len == 0) return try allocator.dupe(u8, "");
|
||||
|
||||
var result = try std.ArrayList(u8).initCapacity(allocator, s.len);
|
||||
errdefer result.deinit(allocator);
|
||||
|
||||
var capitalize_next = false;
|
||||
var is_first = true;
|
||||
|
||||
for (s) |c| {
|
||||
if (c == '_') {
|
||||
capitalize_next = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_first) {
|
||||
try result.append(allocator, std.ascii.toLower(c));
|
||||
is_first = false;
|
||||
} else if (capitalize_next) {
|
||||
try result.append(allocator, std.ascii.toUpper(c));
|
||||
capitalize_next = false;
|
||||
} else {
|
||||
try result.append(allocator, std.ascii.toLower(c));
|
||||
}
|
||||
}
|
||||
|
||||
return try result.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
test "strip SDL prefix" {
|
||||
try std.testing.expectEqualStrings("GPUDevice", stripSDLPrefix("SDL_GPUDevice"));
|
||||
try std.testing.expectEqualStrings("Foo", stripSDLPrefix("SDL_Foo"));
|
||||
try std.testing.expectEqualStrings("Bar", stripSDLPrefix("Bar"));
|
||||
}
|
||||
|
||||
test "type name to Zig" {
|
||||
try std.testing.expectEqualStrings("GPUDevice", typeNameToZig("SDL_GPUDevice"));
|
||||
try std.testing.expectEqualStrings("GPUPrimitiveType", typeNameToZig("SDL_GPUPrimitiveType"));
|
||||
}
|
||||
|
||||
test "function name to Zig" {
|
||||
const name1 = try functionNameToZig("SDL_CreateGPUDevice", std.testing.allocator);
|
||||
defer std.testing.allocator.free(name1);
|
||||
try std.testing.expectEqualStrings("createGPUDevice", name1);
|
||||
|
||||
const name2 = try functionNameToZig("SDL_DestroyGPUDevice", std.testing.allocator);
|
||||
defer std.testing.allocator.free(name2);
|
||||
try std.testing.expectEqualStrings("destroyGPUDevice", name2);
|
||||
}
|
||||
|
||||
test "detect common prefix" {
|
||||
const names = [_][]const u8{
|
||||
"SDL_GPU_PRIMITIVETYPE_TRIANGLELIST",
|
||||
"SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP",
|
||||
"SDL_GPU_PRIMITIVETYPE_LINELIST",
|
||||
};
|
||||
|
||||
const prefix = try detectCommonPrefix(&names, std.testing.allocator);
|
||||
defer std.testing.allocator.free(prefix);
|
||||
try std.testing.expectEqualStrings("SDL_GPU_PRIMITIVETYPE_", prefix);
|
||||
}
|
||||
|
||||
test "enum value to Zig" {
|
||||
const result = try enumValueToZig(
|
||||
"SDL_GPU_PRIMITIVETYPE_TRIANGLELIST",
|
||||
"SDL_GPU_PRIMITIVETYPE_",
|
||||
std.testing.allocator,
|
||||
);
|
||||
defer std.testing.allocator.free(result);
|
||||
try std.testing.expectEqualStrings("trianglelist", result);
|
||||
}
|
||||
|
||||
test "screaming to lower camel" {
|
||||
const result1 = try screaminToLowerCamel("TRIANGLE_LIST", std.testing.allocator);
|
||||
defer std.testing.allocator.free(result1);
|
||||
try std.testing.expectEqualStrings("triangleList", result1);
|
||||
|
||||
const result2 = try screaminToLowerCamel("SAMPLER", std.testing.allocator);
|
||||
defer std.testing.allocator.free(result2);
|
||||
try std.testing.expectEqualStrings("sampler", result2);
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
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} <path-to-sdl3-headers>\n", .{args[0]});
|
||||
std.debug.print("Example: {s} ../SDL/include/SDL3\n", .{args[0]});
|
||||
return error.MissingArgument;
|
||||
}
|
||||
|
||||
const headers_path = args[1];
|
||||
|
||||
std.debug.print("SDL3 Header Parser\n", .{});
|
||||
std.debug.print("==================\n\n", .{});
|
||||
std.debug.print("Scanning headers in: {s}\n\n", .{headers_path});
|
||||
|
||||
// Open the directory
|
||||
var dir = std.fs.cwd().openDir(headers_path, .{ .iterate = true }) catch |err| {
|
||||
std.debug.print("Error: Could not open directory '{s}': {}\n", .{ headers_path, err });
|
||||
return err;
|
||||
};
|
||||
defer dir.close();
|
||||
|
||||
// Iterate over files
|
||||
var iter = dir.iterate();
|
||||
var count: usize = 0;
|
||||
|
||||
while (try iter.next()) |entry| {
|
||||
if (entry.kind != .file) continue;
|
||||
|
||||
// Check if it's a .h file
|
||||
if (std.mem.endsWith(u8, entry.name, ".h")) {
|
||||
count += 1;
|
||||
std.debug.print(" [{d}] {s}\n", .{ count, entry.name });
|
||||
}
|
||||
}
|
||||
|
||||
std.debug.print("\nTotal headers found: {d}\n", .{count});
|
||||
}
|
||||
|
||||
test "basic test" {
|
||||
try std.testing.expect(true);
|
||||
}
|
||||
|
|
@ -0,0 +1,577 @@
|
|||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
// Simple data structures to hold extracted declarations
|
||||
pub const Declaration = union(enum) {
|
||||
opaque_type: OpaqueType,
|
||||
enum_decl: EnumDecl,
|
||||
struct_decl: StructDecl,
|
||||
flag_decl: FlagDecl,
|
||||
function_decl: FunctionDecl,
|
||||
};
|
||||
|
||||
pub const OpaqueType = struct {
|
||||
name: []const u8, // SDL_GPUDevice
|
||||
doc_comment: ?[]const u8,
|
||||
};
|
||||
|
||||
pub const EnumDecl = struct {
|
||||
name: []const u8, // SDL_GPUPrimitiveType
|
||||
values: []EnumValue,
|
||||
doc_comment: ?[]const u8,
|
||||
};
|
||||
|
||||
pub const EnumValue = struct {
|
||||
name: []const u8, // SDL_GPU_PRIMITIVETYPE_TRIANGLELIST
|
||||
value: ?[]const u8, // Optional explicit value
|
||||
comment: ?[]const u8, // Inline comment
|
||||
};
|
||||
|
||||
pub const StructDecl = struct {
|
||||
name: []const u8, // SDL_GPUViewport
|
||||
fields: []FieldDecl,
|
||||
doc_comment: ?[]const u8,
|
||||
};
|
||||
|
||||
pub const FieldDecl = struct {
|
||||
name: []const u8, // x
|
||||
type_name: []const u8, // float
|
||||
comment: ?[]const u8,
|
||||
};
|
||||
|
||||
pub const FlagDecl = struct {
|
||||
name: []const u8, // SDL_GPUTextureUsageFlags
|
||||
underlying_type: []const u8, // Uint32
|
||||
flags: []FlagValue,
|
||||
doc_comment: ?[]const u8,
|
||||
};
|
||||
|
||||
pub const FlagValue = struct {
|
||||
name: []const u8, // SDL_GPU_TEXTUREUSAGE_SAMPLER
|
||||
value: []const u8, // (1u << 0)
|
||||
comment: ?[]const u8,
|
||||
};
|
||||
|
||||
pub const FunctionDecl = struct {
|
||||
name: []const u8, // SDL_CreateGPUDevice
|
||||
return_type: []const u8, // SDL_GPUDevice *
|
||||
params: []ParamDecl,
|
||||
doc_comment: ?[]const u8,
|
||||
};
|
||||
|
||||
pub const ParamDecl = struct {
|
||||
name: []const u8, // format_flags
|
||||
type_name: []const u8, // SDL_GPUShaderFormat
|
||||
};
|
||||
|
||||
pub const Scanner = struct {
|
||||
source: []const u8,
|
||||
pos: usize,
|
||||
allocator: Allocator,
|
||||
pending_doc_comment: ?[]const u8,
|
||||
|
||||
pub fn init(allocator: Allocator, source: []const u8) Scanner {
|
||||
return .{
|
||||
.source = source,
|
||||
.pos = 0,
|
||||
.allocator = allocator,
|
||||
.pending_doc_comment = null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn scan(self: *Scanner) ![]Declaration {
|
||||
var decls = try std.ArrayList(Declaration).initCapacity(self.allocator, 100);
|
||||
|
||||
while (!self.isAtEnd()) {
|
||||
// Try to extract doc comment
|
||||
if (self.peekDocComment()) |comment| {
|
||||
self.pending_doc_comment = comment;
|
||||
}
|
||||
|
||||
// Try each pattern
|
||||
if (try self.scanOpaque()) |opaque_decl| {
|
||||
try decls.append(self.allocator, .{ .opaque_type = opaque_decl });
|
||||
} else if (try self.scanEnum()) |enum_decl| {
|
||||
try decls.append(self.allocator, .{ .enum_decl = enum_decl });
|
||||
} else if (try self.scanStruct()) |struct_decl| {
|
||||
try decls.append(self.allocator, .{ .struct_decl = struct_decl });
|
||||
} else if (try self.scanFlagTypedef()) |flag_decl| {
|
||||
try decls.append(self.allocator, .{ .flag_decl = flag_decl });
|
||||
} else if (try self.scanFunction()) |func| {
|
||||
try decls.append(self.allocator, .{ .function_decl = func });
|
||||
} else {
|
||||
// Skip this line
|
||||
self.skipLine();
|
||||
}
|
||||
}
|
||||
|
||||
return try decls.toOwnedSlice(self.allocator);
|
||||
}
|
||||
|
||||
// Pattern: typedef struct SDL_Foo SDL_Foo;
|
||||
fn scanOpaque(self: *Scanner) !?OpaqueType {
|
||||
const start = self.pos;
|
||||
|
||||
// Read the whole line first
|
||||
const line = try self.readLine();
|
||||
defer self.allocator.free(line);
|
||||
|
||||
// Check if it matches the pattern
|
||||
if (!std.mem.startsWith(u8, line, "typedef struct ")) {
|
||||
self.pos = start;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract name from "typedef struct SDL_Foo SDL_Foo;"
|
||||
var iter = std.mem.tokenizeScalar(u8, line, ' ');
|
||||
_ = iter.next(); // typedef
|
||||
_ = iter.next(); // struct
|
||||
const name1 = iter.next() orelse {
|
||||
self.pos = start;
|
||||
return null;
|
||||
};
|
||||
const name2 = iter.next() orelse {
|
||||
self.pos = start;
|
||||
return null;
|
||||
};
|
||||
|
||||
// Check they match and end with semicolon
|
||||
const name2_clean = std.mem.trimRight(u8, name2, ";");
|
||||
if (!std.mem.eql(u8, name1, name2_clean)) {
|
||||
self.pos = start;
|
||||
return null;
|
||||
}
|
||||
|
||||
// This is an opaque type (not a struct definition with braces)
|
||||
// Make sure it doesn't have braces
|
||||
if (std.mem.indexOfScalar(u8, line, '{')) |_| {
|
||||
self.pos = start;
|
||||
return null;
|
||||
}
|
||||
|
||||
const name = try self.allocator.dupe(u8, name1);
|
||||
const doc = self.consumePendingDocComment();
|
||||
|
||||
return OpaqueType{
|
||||
.name = name,
|
||||
.doc_comment = doc,
|
||||
};
|
||||
}
|
||||
|
||||
// Pattern: typedef enum SDL_Foo { ... } SDL_Foo;
|
||||
fn scanEnum(self: *Scanner) !?EnumDecl {
|
||||
const start = self.pos;
|
||||
|
||||
if (!self.matchPrefix("typedef enum ")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the enum name from first line
|
||||
const first_line = try self.readLine();
|
||||
defer self.allocator.free(first_line);
|
||||
|
||||
var iter = std.mem.tokenizeScalar(u8, first_line, ' ');
|
||||
_ = iter.next(); // typedef
|
||||
_ = iter.next(); // enum
|
||||
const name = iter.next() orelse {
|
||||
self.pos = start;
|
||||
return null;
|
||||
};
|
||||
|
||||
// Read until we find the closing brace and name
|
||||
const body = try self.readBracedBlock();
|
||||
defer self.allocator.free(body);
|
||||
|
||||
// Parse enum values from body
|
||||
var values = try std.ArrayList(EnumValue).initCapacity(self.allocator, 20);
|
||||
var lines = std.mem.splitScalar(u8, body, '\n');
|
||||
while (lines.next()) |line| {
|
||||
const trimmed = std.mem.trim(u8, line, " \t\r");
|
||||
if (trimmed.len == 0) continue;
|
||||
if (std.mem.startsWith(u8, trimmed, "//")) continue;
|
||||
if (std.mem.startsWith(u8, trimmed, "/*")) continue;
|
||||
|
||||
if (try self.parseEnumValue(trimmed)) |value| {
|
||||
try values.append(self.allocator, value);
|
||||
}
|
||||
}
|
||||
|
||||
const doc = self.consumePendingDocComment();
|
||||
|
||||
return EnumDecl{
|
||||
.name = try self.allocator.dupe(u8, name),
|
||||
.values = try values.toOwnedSlice(self.allocator),
|
||||
.doc_comment = doc,
|
||||
};
|
||||
}
|
||||
|
||||
fn parseEnumValue(self: *Scanner, line: []const u8) !?EnumValue {
|
||||
// Format: SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, /**< comment */
|
||||
// or: SDL_GPU_PRIMITIVETYPE_TRIANGLELIST = 5, /**< comment */
|
||||
|
||||
var parts = std.mem.splitScalar(u8, line, ',');
|
||||
const first = std.mem.trim(u8, parts.next() orelse return null, " \t");
|
||||
if (first.len == 0) return null;
|
||||
|
||||
// Extract name and optional value
|
||||
var name: []const u8 = undefined;
|
||||
var value: ?[]const u8 = null;
|
||||
|
||||
if (std.mem.indexOf(u8, first, "=")) |eq_pos| {
|
||||
name = std.mem.trim(u8, first[0..eq_pos], " \t");
|
||||
value = try self.allocator.dupe(u8, std.mem.trim(u8, first[eq_pos + 1 ..], " \t"));
|
||||
} else {
|
||||
name = first;
|
||||
}
|
||||
|
||||
// Extract inline comment if present
|
||||
var comment: ?[]const u8 = null;
|
||||
const remainder = parts.rest();
|
||||
if (std.mem.indexOf(u8, remainder, "/**<")) |start| {
|
||||
if (std.mem.indexOf(u8, remainder[start..], "*/")) |end_offset| {
|
||||
const comment_text = remainder[start + 4 .. start + end_offset];
|
||||
comment = try self.allocator.dupe(u8, std.mem.trim(u8, comment_text, " \t"));
|
||||
}
|
||||
}
|
||||
|
||||
return EnumValue{
|
||||
.name = try self.allocator.dupe(u8, name),
|
||||
.value = value,
|
||||
.comment = comment,
|
||||
};
|
||||
}
|
||||
|
||||
// Pattern: typedef struct SDL_Foo { ... } SDL_Foo;
|
||||
fn scanStruct(self: *Scanner) !?StructDecl {
|
||||
const start = self.pos;
|
||||
|
||||
if (!self.matchPrefix("typedef struct ")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the struct name from first line
|
||||
const first_line = try self.readLine();
|
||||
defer self.allocator.free(first_line);
|
||||
|
||||
var iter = std.mem.tokenizeScalar(u8, first_line, ' ');
|
||||
_ = iter.next(); // typedef
|
||||
_ = iter.next(); // struct
|
||||
const name = iter.next() orelse {
|
||||
self.pos = start;
|
||||
return null;
|
||||
};
|
||||
|
||||
// Check if this is actually an opaque type (no opening brace)
|
||||
if (std.mem.indexOf(u8, first_line, "{") == null) {
|
||||
self.pos = start;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read the struct body
|
||||
const body = try self.readBracedBlock();
|
||||
defer self.allocator.free(body);
|
||||
|
||||
// Parse fields
|
||||
var fields = try std.ArrayList(FieldDecl).initCapacity(self.allocator, 20);
|
||||
var lines = std.mem.splitScalar(u8, body, '\n');
|
||||
while (lines.next()) |line| {
|
||||
if (try self.parseStructField(line)) |field| {
|
||||
try fields.append(self.allocator, field);
|
||||
}
|
||||
}
|
||||
|
||||
const doc = self.consumePendingDocComment();
|
||||
|
||||
return StructDecl{
|
||||
.name = try self.allocator.dupe(u8, name),
|
||||
.fields = try fields.toOwnedSlice(self.allocator),
|
||||
.doc_comment = doc,
|
||||
};
|
||||
}
|
||||
|
||||
fn parseStructField(self: *Scanner, line: []const u8) !?FieldDecl {
|
||||
const trimmed = std.mem.trim(u8, line, " \t\r");
|
||||
if (trimmed.len == 0) return null;
|
||||
if (std.mem.startsWith(u8, trimmed, "//")) return null;
|
||||
if (std.mem.startsWith(u8, trimmed, "/*")) return null;
|
||||
|
||||
// Remove trailing semicolon
|
||||
const no_semi = std.mem.trimRight(u8, trimmed, ";");
|
||||
|
||||
// Extract inline comment
|
||||
var comment: ?[]const u8 = null;
|
||||
var field_part = no_semi;
|
||||
if (std.mem.indexOf(u8, no_semi, "/**<")) |comment_start| {
|
||||
field_part = no_semi[0..comment_start];
|
||||
if (std.mem.indexOf(u8, no_semi[comment_start..], "*/")) |end_offset| {
|
||||
const comment_text = no_semi[comment_start + 4 .. comment_start + end_offset];
|
||||
comment = try self.allocator.dupe(u8, std.mem.trim(u8, comment_text, " \t"));
|
||||
}
|
||||
}
|
||||
|
||||
// Parse "type name" - find last space
|
||||
const field_trimmed = std.mem.trim(u8, field_part, " \t");
|
||||
if (std.mem.lastIndexOfScalar(u8, field_trimmed, ' ')) |last_space| {
|
||||
const type_name = std.mem.trim(u8, field_trimmed[0..last_space], " \t");
|
||||
const name = std.mem.trim(u8, field_trimmed[last_space + 1 ..], " \t");
|
||||
|
||||
if (name.len > 0 and type_name.len > 0) {
|
||||
return FieldDecl{
|
||||
.name = try self.allocator.dupe(u8, name),
|
||||
.type_name = try self.allocator.dupe(u8, type_name),
|
||||
.comment = comment,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Pattern: typedef Uint32 SDL_FooFlags;
|
||||
fn scanFlagTypedef(self: *Scanner) !?FlagDecl {
|
||||
const start = self.pos;
|
||||
|
||||
if (!self.matchPrefix("typedef ")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const line = try self.readLine();
|
||||
defer self.allocator.free(line);
|
||||
|
||||
// Check if it's a flag type (ends with Flags)
|
||||
var iter = std.mem.tokenizeScalar(u8, line, ' ');
|
||||
_ = iter.next(); // typedef
|
||||
const underlying = iter.next() orelse {
|
||||
self.pos = start;
|
||||
return null;
|
||||
};
|
||||
const name = iter.next() orelse {
|
||||
self.pos = start;
|
||||
return null;
|
||||
};
|
||||
|
||||
const clean_name = std.mem.trimRight(u8, name, ";");
|
||||
if (!std.mem.endsWith(u8, clean_name, "Flags")) {
|
||||
self.pos = start;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Now collect following #define lines
|
||||
var flags = try std.ArrayList(FlagValue).initCapacity(self.allocator, 10);
|
||||
|
||||
// Look ahead for #define lines
|
||||
while (!self.isAtEnd()) {
|
||||
const define_start = self.pos;
|
||||
if (!self.matchPrefix("#define ")) {
|
||||
self.pos = define_start;
|
||||
break;
|
||||
}
|
||||
|
||||
const define_line = try self.readLine();
|
||||
defer self.allocator.free(define_line);
|
||||
|
||||
if (try self.parseFlagDefine(define_line)) |flag| {
|
||||
try flags.append(self.allocator, flag);
|
||||
} else {
|
||||
// Not a flag define, restore position
|
||||
self.pos = define_start;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const doc = self.consumePendingDocComment();
|
||||
|
||||
return FlagDecl{
|
||||
.name = try self.allocator.dupe(u8, clean_name),
|
||||
.underlying_type = try self.allocator.dupe(u8, underlying),
|
||||
.flags = try flags.toOwnedSlice(self.allocator),
|
||||
.doc_comment = doc,
|
||||
};
|
||||
}
|
||||
|
||||
fn parseFlagDefine(self: *Scanner, line: []const u8) !?FlagValue {
|
||||
// Format: #define SDL_GPU_TEXTUREUSAGE_SAMPLER (1u << 0) /**< comment */
|
||||
var parts = std.mem.splitSequence(u8, line, " ");
|
||||
_ = parts.next(); // #define
|
||||
const name = parts.next() orelse return null;
|
||||
|
||||
// Collect the value part (everything until comment)
|
||||
var value_parts = try std.ArrayList(u8).initCapacity(self.allocator, 32);
|
||||
defer value_parts.deinit(self.allocator);
|
||||
|
||||
while (parts.next()) |part| {
|
||||
if (std.mem.indexOf(u8, part, "/**<")) |_| break;
|
||||
if (value_parts.items.len > 0) try value_parts.append(self.allocator, ' ');
|
||||
try value_parts.appendSlice(self.allocator, part);
|
||||
}
|
||||
|
||||
if (value_parts.items.len == 0) return null;
|
||||
|
||||
// Extract comment
|
||||
var comment: ?[]const u8 = null;
|
||||
if (std.mem.indexOf(u8, line, "/**<")) |comment_start| {
|
||||
if (std.mem.indexOf(u8, line[comment_start..], "*/")) |end_offset| {
|
||||
const comment_text = line[comment_start + 4 .. comment_start + end_offset];
|
||||
comment = try self.allocator.dupe(u8, std.mem.trim(u8, comment_text, " \t"));
|
||||
}
|
||||
}
|
||||
|
||||
return FlagValue{
|
||||
.name = try self.allocator.dupe(u8, name),
|
||||
.value = try value_parts.toOwnedSlice(self.allocator),
|
||||
.comment = comment,
|
||||
};
|
||||
}
|
||||
|
||||
// Pattern: extern SDL_DECLSPEC Type SDLCALL SDL_Name(...);
|
||||
fn scanFunction(self: *Scanner) !?FunctionDecl {
|
||||
_ = self;
|
||||
// TODO: Implement function parsing
|
||||
return null;
|
||||
}
|
||||
|
||||
fn scanFunctionTODO(self: *Scanner) !?FunctionDecl {
|
||||
if (!self.matchPrefix("extern SDL_DECLSPEC ")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read until we find the semicolon (may span multiple lines)
|
||||
var func_text = try std.ArrayList(u8).initCapacity(self.allocator, 256);
|
||||
defer func_text.deinit(self.allocator);
|
||||
|
||||
while (!self.isAtEnd()) {
|
||||
const line = try self.readLine();
|
||||
defer self.allocator.free(line);
|
||||
|
||||
try func_text.appendSlice(self.allocator, line);
|
||||
try func_text.append(self.allocator, ' ');
|
||||
|
||||
if (std.mem.indexOfScalar(u8, line, ';')) |_| break;
|
||||
}
|
||||
|
||||
// Parse: extern SDL_DECLSPEC ReturnType SDLCALL FunctionName(params);
|
||||
// This is simplified - just extract the basics
|
||||
const doc = self.consumePendingDocComment();
|
||||
|
||||
// For now, store the raw declaration
|
||||
// We'll parse it properly in codegen
|
||||
return FunctionDecl{
|
||||
.name = try self.allocator.dupe(u8, "TODO"),
|
||||
.return_type = try self.allocator.dupe(u8, "TODO"),
|
||||
.params = &[_]ParamDecl{},
|
||||
.doc_comment = doc,
|
||||
};
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
fn isAtEnd(self: *Scanner) bool {
|
||||
return self.pos >= self.source.len;
|
||||
}
|
||||
|
||||
fn matchPrefix(self: *Scanner, prefix: []const u8) bool {
|
||||
if (self.pos + prefix.len > self.source.len) return false;
|
||||
const slice = self.source[self.pos .. self.pos + prefix.len];
|
||||
if (std.mem.eql(u8, slice, prefix)) {
|
||||
self.pos += prefix.len;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn readLine(self: *Scanner) ![]const u8 {
|
||||
const start = self.pos;
|
||||
while (self.pos < self.source.len and self.source[self.pos] != '\n') {
|
||||
self.pos += 1;
|
||||
}
|
||||
if (self.pos < self.source.len) self.pos += 1; // Skip newline
|
||||
|
||||
return self.allocator.dupe(u8, self.source[start .. self.pos - 1]);
|
||||
}
|
||||
|
||||
fn skipLine(self: *Scanner) void {
|
||||
while (self.pos < self.source.len and self.source[self.pos] != '\n') {
|
||||
self.pos += 1;
|
||||
}
|
||||
if (self.pos < self.source.len) self.pos += 1; // Skip newline
|
||||
}
|
||||
|
||||
fn readBracedBlock(self: *Scanner) ![]const u8 {
|
||||
// Assumes we're at the opening brace or just after it
|
||||
var depth: i32 = 0;
|
||||
const start = self.pos;
|
||||
var found_open = false;
|
||||
|
||||
while (self.pos < self.source.len) {
|
||||
const c = self.source[self.pos];
|
||||
if (c == '{') {
|
||||
depth += 1;
|
||||
found_open = true;
|
||||
} else if (c == '}') {
|
||||
depth -= 1;
|
||||
if (found_open and depth == 0) {
|
||||
self.pos += 1;
|
||||
// Skip to end of line (to consume the typedef name)
|
||||
self.skipLine();
|
||||
return self.allocator.dupe(u8, self.source[start..self.pos]);
|
||||
}
|
||||
}
|
||||
self.pos += 1;
|
||||
}
|
||||
|
||||
return error.UnmatchedBrace;
|
||||
}
|
||||
|
||||
fn peekDocComment(self: *Scanner) ?[]const u8 {
|
||||
// Look for /** ... */ doc comments
|
||||
const start = self.pos;
|
||||
|
||||
// Skip whitespace
|
||||
while (self.pos < self.source.len) {
|
||||
const c = self.source[self.pos];
|
||||
if (c != ' ' and c != '\t' and c != '\n' and c != '\r') break;
|
||||
self.pos += 1;
|
||||
}
|
||||
|
||||
if (self.pos + 3 < self.source.len and
|
||||
self.source[self.pos] == '/' and
|
||||
self.source[self.pos + 1] == '*' and
|
||||
self.source[self.pos + 2] == '*')
|
||||
{
|
||||
const comment_start = self.pos;
|
||||
self.pos += 3;
|
||||
|
||||
// Find end
|
||||
while (self.pos + 1 < self.source.len) {
|
||||
if (self.source[self.pos] == '*' and self.source[self.pos + 1] == '/') {
|
||||
self.pos += 2;
|
||||
// Return the comment (we'll process it later)
|
||||
return self.source[comment_start..self.pos];
|
||||
}
|
||||
self.pos += 1;
|
||||
}
|
||||
}
|
||||
|
||||
self.pos = start;
|
||||
return null;
|
||||
}
|
||||
|
||||
fn consumePendingDocComment(self: *Scanner) ?[]const u8 {
|
||||
const comment = self.pending_doc_comment;
|
||||
self.pending_doc_comment = null;
|
||||
return comment;
|
||||
}
|
||||
};
|
||||
|
||||
test "scan opaque typedef" {
|
||||
const source = "typedef struct SDL_GPUDevice SDL_GPUDevice;";
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const allocator = arena.allocator();
|
||||
|
||||
var scanner = Scanner.init(allocator, source);
|
||||
const decls = try scanner.scan();
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 1), decls.len);
|
||||
try std.testing.expect(decls[0] == .opaque_type);
|
||||
try std.testing.expectEqualStrings("SDL_GPUDevice", decls[0].opaque_type.name);
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
/// Convert C type to Zig type
|
||||
/// Simple table-based conversion for SDL3 types
|
||||
pub fn convertType(c_type: []const u8, allocator: Allocator) ![]const u8 {
|
||||
const trimmed = std.mem.trim(u8, c_type, " \t");
|
||||
|
||||
// Primitives
|
||||
if (std.mem.eql(u8, trimmed, "void")) return try allocator.dupe(u8, "void");
|
||||
if (std.mem.eql(u8, trimmed, "bool")) return try allocator.dupe(u8, "bool");
|
||||
if (std.mem.eql(u8, trimmed, "SDL_bool")) return try allocator.dupe(u8, "bool");
|
||||
if (std.mem.eql(u8, trimmed, "float")) return try allocator.dupe(u8, "f32");
|
||||
if (std.mem.eql(u8, trimmed, "double")) return try allocator.dupe(u8, "f64");
|
||||
if (std.mem.eql(u8, trimmed, "char")) return try allocator.dupe(u8, "u8");
|
||||
if (std.mem.eql(u8, trimmed, "int")) return try allocator.dupe(u8, "c_int");
|
||||
|
||||
// SDL integer types
|
||||
if (std.mem.eql(u8, trimmed, "Uint8")) return try allocator.dupe(u8, "u8");
|
||||
if (std.mem.eql(u8, trimmed, "Uint16")) return try allocator.dupe(u8, "u16");
|
||||
if (std.mem.eql(u8, trimmed, "Uint32")) return try allocator.dupe(u8, "u32");
|
||||
if (std.mem.eql(u8, trimmed, "Uint64")) return try allocator.dupe(u8, "u64");
|
||||
if (std.mem.eql(u8, trimmed, "Sint8")) return try allocator.dupe(u8, "i8");
|
||||
if (std.mem.eql(u8, trimmed, "Sint16")) return try allocator.dupe(u8, "i16");
|
||||
if (std.mem.eql(u8, trimmed, "Sint32")) return try allocator.dupe(u8, "i32");
|
||||
if (std.mem.eql(u8, trimmed, "Sint64")) return try allocator.dupe(u8, "i64");
|
||||
if (std.mem.eql(u8, trimmed, "size_t")) return try allocator.dupe(u8, "usize");
|
||||
|
||||
// Common pointer types
|
||||
if (std.mem.eql(u8, trimmed, "const char *")) return try allocator.dupe(u8, "[*c]const u8");
|
||||
if (std.mem.eql(u8, trimmed, "char *")) return try allocator.dupe(u8, "[*c]u8");
|
||||
if (std.mem.eql(u8, trimmed, "void *")) return try allocator.dupe(u8, "?*anyopaque");
|
||||
if (std.mem.eql(u8, trimmed, "const void *")) return try allocator.dupe(u8, "?*const anyopaque");
|
||||
|
||||
// Handle SDL types with pointers
|
||||
if (std.mem.startsWith(u8, trimmed, "const ")) {
|
||||
const rest = trimmed[6..];
|
||||
if (std.mem.endsWith(u8, rest, " *")) {
|
||||
const base_type = rest[0 .. rest.len - 2];
|
||||
if (std.mem.startsWith(u8, base_type, "SDL_")) {
|
||||
// const SDL_Foo * -> *const Foo
|
||||
const zig_type = base_type[4..]; // Remove SDL_
|
||||
return std.fmt.allocPrint(allocator, "*const {s}", .{zig_type});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (std.mem.endsWith(u8, trimmed, " *")) {
|
||||
const base_type = trimmed[0 .. trimmed.len - 2];
|
||||
if (std.mem.startsWith(u8, base_type, "SDL_")) {
|
||||
// SDL_Foo * -> *Foo
|
||||
const zig_type = base_type[4..]; // Remove SDL_
|
||||
return std.fmt.allocPrint(allocator, "*{s}", .{zig_type});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle SDL types without pointers
|
||||
if (std.mem.startsWith(u8, trimmed, "SDL_")) {
|
||||
// SDL_Foo -> Foo
|
||||
return try allocator.dupe(u8, trimmed[4..]);
|
||||
}
|
||||
|
||||
// Fallback: return as-is
|
||||
return try allocator.dupe(u8, trimmed);
|
||||
}
|
||||
|
||||
/// Determine the appropriate cast for a given type when calling C functions
|
||||
pub fn getCastType(zig_type: []const u8) CastType {
|
||||
// Opaque pointers need @ptrCast
|
||||
if (std.mem.startsWith(u8, zig_type, "*") and !std.mem.startsWith(u8, zig_type, "*anyopaque")) {
|
||||
return .ptr_cast;
|
||||
}
|
||||
|
||||
// Enums need @intFromEnum
|
||||
// We'll detect these by naming convention or explicit marking
|
||||
// For now, assume types ending in certain patterns are enums
|
||||
if (std.mem.indexOf(u8, zig_type, "Type") != null or
|
||||
std.mem.indexOf(u8, zig_type, "Mode") != null or
|
||||
std.mem.indexOf(u8, zig_type, "Op") != null)
|
||||
{
|
||||
return .int_from_enum;
|
||||
}
|
||||
|
||||
// Flags (packed structs) need @bitCast
|
||||
if (std.mem.endsWith(u8, zig_type, "Flags") or
|
||||
std.mem.endsWith(u8, zig_type, "Format"))
|
||||
{
|
||||
return .bit_cast;
|
||||
}
|
||||
|
||||
return .none;
|
||||
}
|
||||
|
||||
pub const CastType = enum {
|
||||
none,
|
||||
ptr_cast,
|
||||
bit_cast,
|
||||
int_from_enum,
|
||||
enum_from_int,
|
||||
};
|
||||
|
||||
test "convert primitive types" {
|
||||
const t1 = try convertType("float", std.testing.allocator);
|
||||
defer std.testing.allocator.free(t1);
|
||||
try std.testing.expectEqualStrings("f32", t1);
|
||||
|
||||
const t2 = try convertType("Uint32", std.testing.allocator);
|
||||
defer std.testing.allocator.free(t2);
|
||||
try std.testing.expectEqualStrings("u32", t2);
|
||||
|
||||
const t3 = try convertType("bool", std.testing.allocator);
|
||||
defer std.testing.allocator.free(t3);
|
||||
try std.testing.expectEqualStrings("bool", t3);
|
||||
}
|
||||
|
||||
test "convert SDL types" {
|
||||
const t1 = try convertType("SDL_GPUDevice", std.testing.allocator);
|
||||
defer std.testing.allocator.free(t1);
|
||||
try std.testing.expectEqualStrings("GPUDevice", t1);
|
||||
|
||||
const t2 = try convertType("SDL_GPUDevice *", std.testing.allocator);
|
||||
defer std.testing.allocator.free(t2);
|
||||
try std.testing.expectEqualStrings("*GPUDevice", t2);
|
||||
|
||||
const t3 = try convertType("const SDL_GPUViewport *", std.testing.allocator);
|
||||
defer std.testing.allocator.free(t3);
|
||||
try std.testing.expectEqualStrings("*const GPUViewport", t3);
|
||||
}
|
||||
|
||||
test "convert pointer types" {
|
||||
const t1 = try convertType("const char *", std.testing.allocator);
|
||||
defer std.testing.allocator.free(t1);
|
||||
try std.testing.expectEqualStrings("[*c]const u8", t1);
|
||||
|
||||
const t2 = try convertType("void *", std.testing.allocator);
|
||||
defer std.testing.allocator.free(t2);
|
||||
try std.testing.expectEqualStrings("?*anyopaque", t2);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue