# Development Guide Guide for contributing to and extending the SDL3 header parser. ## Quick Start for Developers ```bash # Clone and build cd lib/sdl3/parser zig build # Run tests zig build test # Make changes # ... edit src/*.zig ... # Test your changes zig build test zig build run -- ../SDL/include/SDL3/SDL_gpu.h --output=test.zig ``` ## Project Structure ``` parser/ ├── src/ │ ├── parser.zig # Main entry point, CLI │ ├── patterns.zig # Pattern matching & scanning │ ├── types.zig # C to Zig type conversion │ ├── naming.zig # Naming conventions │ ├── codegen.zig # Zig code generation │ ├── mock_codegen.zig # C mock generation │ └── dependency_resolver.zig # Dependency analysis ├── test/ │ └── (test files) ├── docs/ │ └── (documentation) └── build.zig ``` ## Zig 0.15 API Changes - CRITICAL **This project uses Zig 0.15**. Key API changes from 0.14: ### ArrayList Changes **Old (0.14)**: ```zig var list = std.ArrayList(T).init(allocator); defer list.deinit(); try list.append(item); ``` **New (0.15)** - REQUIRED: ```zig var list = std.ArrayList(T){}; defer list.deinit(allocator); try list.append(allocator, item); ``` **Key Points**: - Initialize with `{}` or `initCapacity()` - All methods take allocator: `append(allocator, item)` - Deinit takes allocator: `deinit(allocator)` ### AST Rendering **Old**: `ast.render(allocator)` **New**: `ast.renderAlloc(allocator)` ## Adding New Pattern Support ### Example: Adding Union Support 1. **Add to Declaration union** (patterns.zig): ```zig pub const Declaration = union(enum) { // ... existing variants union_decl: UnionDecl, // NEW }; pub const UnionDecl = struct { name: []const u8, fields: []FieldDecl, doc_comment: ?[]const u8, }; ``` 2. **Add scanner function** (patterns.zig): ```zig fn scanUnion(self: *Scanner) !?UnionDecl { // Pattern matching logic // Return UnionDecl or null } ``` 3. **Add to scan chain** (patterns.zig): ```zig if (try self.scanOpaque()) |opaque_decl| { // ... } else if (try self.scanUnion()) |union_decl| { try decls.append(self.allocator, .{ .union_decl = union_decl }); } else if (try self.scanEnum()) |enum_decl| { // ... } ``` 4. **Update cleanup code** (parser.zig): ```zig defer { for (decls) |decl| { switch (decl) { // ... existing cases .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); }, } } } ``` 5. **Update dependency resolver** (dependency_resolver.zig): ```zig // In collectDefinedTypes: .union_decl => |u| u.name, // In cloneDeclaration: .union_decl => |u| .{ .union_decl = .{ .name = try allocator.dupe(u8, u.name), .fields = try cloneFields(allocator, u.fields), .doc_comment = if (u.doc_comment) |doc| try allocator.dupe(u8, doc) else null, }, }, // In freeDeclaration: .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); }, ``` 6. **Add code generator** (codegen.zig): ```zig fn writeUnion(self: *CodeGen, union_decl: patterns.UnionDecl) !void { const zig_name = naming.typeNameToZig(union_decl.name); if (union_decl.doc_comment) |doc| { try self.writeDocComment(doc); } try self.output.writer(self.allocator).print( "pub const {s} = extern union {{\n", .{zig_name} ); for (union_decl.fields) |field| { const zig_type = try types.convertType(field.type_name, self.allocator); defer self.allocator.free(zig_type); try self.output.writer(self.allocator).print( " {s}: {s},\n", .{field.name, zig_type} ); } try self.output.appendSlice(self.allocator, "};\n\n"); } ``` 7. **Update writeDeclarations** (codegen.zig): ```zig switch (decl) { // ... existing cases .union_decl => |union_decl| try self.writeUnion(union_decl), } ``` 8. **Add tests**: ```zig test "parse union" { const source = \\typedef union SDL_Color { \\ Uint32 rgba; \\ struct { Uint8 r, g, b, a; }; \\} SDL_Color; ; var scanner = patterns.Scanner.init(allocator, source); const decls = try scanner.scan(); // ... test expectations } ``` ## Testing Guidelines ### Unit Tests Place tests in `test/` or at bottom of source files: ```zig test "descriptive test name" { const allocator = std.testing.allocator; // Setup const source = "..."; var scanner = patterns.Scanner.init(allocator, source); // Execute const result = try scanner.scan(); defer allocator.free(result); // Assert try std.testing.expectEqual(expected, actual); } ``` ### Integration Tests Test with real SDL headers: ```bash zig build run -- ../SDL/include/SDL3/SDL_gpu.h --output=test.zig zig ast-check test.zig ``` ### Memory Testing Always run tests with GPA to detect leaks: ```zig test "my test" { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Test code using allocator } ``` ## Code Style ### Naming - **Functions**: camelCase (`parseStructField`) - **Types**: PascalCase (`FieldDecl`) - **Constants**: PascalCase (`Declaration`) - **Variables**: camelCase (`decl_name`) ### Comments Only comment code that needs clarification: ```zig // Good: Explains WHY // Must check flags before typedefs - pattern order matters if (try self.scanFlagTypedef()) |flag_decl| { ... } // Bad: Explains WHAT (obvious from code) // Append to list try list.append(allocator, item); ``` ### Error Handling Use graceful degradation: ```zig // Good: Continue on error const decl = extractType(source, name) catch |err| { std.debug.print("Warning: {}\n", .{err}); continue; }; // Bad: Fail immediately (unless truly fatal) const decl = try extractType(source, name); ``` ## Memory Management Rules ### Ownership 1. **Scanner owns strings** during parsing 2. **Caller owns result** of scan() 3. **HashMap owns keys** when they're duped 4. **Cloned declarations own strings** after cloning ### Cleanup Pattern Always use defer for cleanup: ```zig const decls = try scanner.scan(); defer { for (decls) |decl| { freeDeclDeep(allocator, decl); } allocator.free(decls); } ``` ### HashMap Keys Must be owned (not slices into temporary data): ```zig // Wrong: try map.put(type_str, {}); // type_str might be freed! // Right: if (!map.contains(type_str)) { const owned = try allocator.dupe(u8, type_str); try map.put(owned, {}); } ``` ## Common Patterns ### Pattern Matching ```zig fn scanSomething(self: *Scanner) !?SomeDecl { const start = self.pos; const line = try self.readLine(); defer self.allocator.free(line); // Check pattern if (!std.mem.startsWith(u8, line, "expected_start")) { self.pos = start; // Reset position return null; } // Parse and return return SomeDecl{ ... }; } ``` ### String Building ```zig var buf = std.ArrayList(u8){}; defer buf.deinit(allocator); try buf.appendSlice(allocator, "pub const "); try buf.appendSlice(allocator, name); try buf.appendSlice(allocator, " = "); return try buf.toOwnedSlice(allocator); ``` ### HashMap Usage ```zig var map = std.StringHashMap(void).init(allocator); defer { var it = map.keyIterator(); while (it.next()) |key| { allocator.free(key.*); // Free owned keys } map.deinit(); } // Add items const owned_key = try allocator.dupe(u8, key); try map.put(owned_key, {}); ``` ## Debugging Tips ### Print Debugging ```zig std.debug.print("Debug: value = {s}\n", .{value}); std.debug.print("Type: {}\n", .{@TypeOf(variable)}); ``` ### Memory Leak Detection Run with GPA and check output: ```bash zig build run -- header.h 2>&1 | grep "memory address" ``` ### AST Debugging Check what Zig thinks is wrong: ```bash zig ast-check generated.zig ``` ## Performance Optimization ### Guidelines 1. **Avoid allocations in hot paths** - Use stack when possible 2. **Reuse buffers** - Clear and reuse instead of allocating new 3. **Early exit** - Return as soon as answer is known 4. **HashMap for lookups** - O(1) instead of O(n) searches ### Profiling ```bash # Build with profiling zig build -Drelease-safe # Run with timing time zig build run -- large_header.h --output=out.zig ``` ## Contributing Workflow 1. **Create branch** from `dev/sdl3-parser` 2. **Make changes** in focused commits 3. **Run tests** - All must pass 4. **Update docs** if behavior changes 5. **Commit** with descriptive message 6. **Push** and create PR ### Commit Message Format ``` feat: Add union type support Implements parsing and code generation for C union types. - Added UnionDecl to Declaration union - Implemented scanUnion() pattern matcher - Added writeUnion() code generator - Created comprehensive test suite (5 tests) Results: - Successfully parses SDL union types - Generates proper extern unions - All tests passing Closes: #123 ``` ## Test-Driven Development Recommended workflow: 1. **Write test first**: ```zig test "parse union type" { const source = "typedef union { int x; float y; } SDL_Union;"; var scanner = patterns.Scanner.init(allocator, source); const decls = try scanner.scan(); try testing.expectEqual(@as(usize, 1), decls.len); try testing.expect(decls[0] == .union_decl); } ``` 2. **Run test** (it will fail) 3. **Implement feature** until test passes 4. **Add more tests** for edge cases 5. **Refactor** if needed ## Architecture Decisions ### Why Single-File Output? **Alternative**: Generate separate file per type **Decision**: Single file with dependencies first **Reasons**: - Simpler for users (one import) - Zig's structural typing handles it - Type ordering guaranteed - Less build system complexity ### Why On-Demand Resolution? **Alternative**: Always parse all includes **Decision**: Only parse when missing types detected **Reasons**: - Better performance - Minimal overhead for self-contained headers - Users see only relevant dependencies ### Why Conservative Error Handling? **Alternative**: Fail on any error **Decision**: Warn and continue **Reasons**: - Partial success is better than no success - Users can manually fix issues - Allows incremental improvement ## Extending the Parser ### Adding Type Conversions Edit `src/types.zig`: ```zig pub fn convertType(c_type: []const u8, allocator: Allocator) ![]const u8 { // Check for your pattern first if (std.mem.eql(u8, c_type, "MyCustomType")) { return try allocator.dupe(u8, "MyZigType"); } // Fall through to existing logic // ... } ``` ### Adding Naming Rules Edit `src/naming.zig`: ```zig pub fn typeNameToZig(c_name: []const u8) []const u8 { // Handle special cases if (std.mem.eql(u8, c_name, "SDL_bool")) { return "Bool"; // Custom mapping } // Default logic return stripSDLPrefix(c_name); } ``` ### Adding Pattern Matchers 1. Implement `scan*()` function in `patterns.zig` 2. Add to scan chain with proper ordering 3. Update all switch statements 4. Add code generator 5. Write tests See "Adding New Pattern Support" section above for full example. ## Common Issues When Developing ### Issue: ArrayList API Changed **Error**: `error: no field named 'init' in struct 'ArrayList'` **Solution**: Use Zig 0.15 API (see above) ### Issue: HashMap Key Lifetime **Error**: Memory corruption or use-after-free **Solution**: Always dupe keys before inserting: ```zig const owned = try allocator.dupe(u8, key); try map.put(owned, {}); ``` ### Issue: Pattern Matching Order **Error**: Wrong scanner function matches **Solution**: Order matters! More specific patterns first: ```zig // Correct order: if (try self.scanFlagTypedef()) { ... } // Specific else if (try self.scanTypedef()) { ... } // General // Wrong order: if (try self.scanTypedef()) { ... } // Too general - catches flags! else if (try self.scanFlagTypedef()) { ... } // Never reached ``` ## Performance Considerations ### Current Performance - Small headers: ~100ms - Large headers (SDL_gpu.h): ~520ms - Memory: ~2-5MB peak ### Bottlenecks 1. **Dependency extraction**: 300ms (58% of time) - Parsing multiple headers - Could cache parsed headers 2. **String allocations**: Many small allocations - Could use arena allocator - String interning would help ### Optimization Ideas ```zig // Cache parsed headers var header_cache = std.StringHashMap([]Declaration).init(allocator); // Use arena for temporary allocations var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const temp_alloc = arena.allocator(); ``` ## Resources ### Zig Documentation - [Zig Language Reference](https://ziglang.org/documentation/master/) - [Zig Standard Library](https://ziglang.org/documentation/master/std/) ### SDL Documentation - [SDL3 API](https://wiki.libsdl.org/SDL3/) - [SDL3 Headers](https://github.com/libsdl-org/SDL) ### Project Documentation - [Architecture](ARCHITECTURE.md) - How the parser works - [Dependency Flow](DEPENDENCY_FLOW.md) - Detailed flow - [Visual Flow](VISUAL_FLOW.md) - Diagrams ## Getting Help - Check existing tests for examples - Read [Architecture](ARCHITECTURE.md) for design - See [Dependency Flow](DEPENDENCY_FLOW.md) for details - Review git history for patterns --- **Ready to contribute?** Start with the tests, understand the existing patterns, then extend!