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); }