import Foundation /// Errors that can occur during Swift source code generation. public enum CodeGenError: Error, Equatable { /// Generation failed with a descriptive message. case generationFailed(String) } /// Generates Swift source code from a parsed GIR repository and analysis results. /// /// Takes the intermediate representation produced by the XML parser, filtered /// through an ``Analyzer``/``AnalysisResult``, and emits Swift source text /// containing class wrappers (with GObject reference counting), enumerations, /// and option sets for bitfields. Generated output targets a single Swift file /// per invocation. public struct CodeGenerator { /// The generation configuration controlling output behavior. public let config: GenerationConfig /// Creates a new code generator with the given configuration. /// - Parameter config: The generation configuration. public init(config: GenerationConfig) { self.config = config } /// Generates Swift source code for the specified repository. /// /// Iterates over all namespaces, classes, enumerations, and bitfields in /// the repository, producing Swift declarations only for types included /// in the analysis result. Generated classes include pointer-based storage /// with `g_object_ref_sink`/`g_object_unref` lifetime management, computed /// property accessors, method stubs, and signal connection methods. /// /// - Parameters: /// - repository: The parsed GIR repository to generate code from. /// - analysis: The analysis results specifying which types to generate /// and any per-type overrides. /// - Returns: A string containing the generated Swift source code. /// - Throws: `CodeGenError` if generation fails. public func generate(repository: Repository, analysis: AnalysisResult) throws -> String { var output = """ // Generated by SwiftGtkGen. DO NOT EDIT. // Source: \(config.library) \(config.version) import C\(config.library) import Foundation """ for ns in repository.namespaces where ns.name == config.library { let shouldGenerateAll = analysis.generatedTypes.isEmpty for cls in ns.classes { let fullName = "\(ns.name).\(cls.name)" if !shouldGenerateAll && !analysis.generatedTypes.contains(fullName) { continue } output += generateClass(cls, namespace: ns.name, analysis: analysis) } for enm in ns.enumerations { let fullName = "\(ns.name).\(enm.name)" if !shouldGenerateAll && !analysis.generatedTypes.contains(fullName) { continue } output += generateEnum(enm) } for bf in ns.bitfields { let fullName = "\(ns.name).\(bf.name)" if !shouldGenerateAll && !analysis.generatedTypes.contains(fullName) { continue } output += generateBitfield(bf) } for iface in ns.interfaces { let fullName = "\(ns.name).\(iface.name)" if !shouldGenerateAll && !analysis.generatedTypes.contains(fullName) { continue } output += generateInterface(iface) } for cb in ns.callbacks { let fullName = "\(ns.name).\(cb.name)" if !shouldGenerateAll && !analysis.generatedTypes.contains(fullName) { continue } output += generateCallback(cb) } for fn in ns.functions { let fullName = "\(ns.name).\(fn.name)" if !shouldGenerateAll && !analysis.generatedTypes.contains(fullName) { continue } output += generateGlobalFunction(fn, namespace: ns.name) } } return output } /// Generates Swift source code split into one file per type. /// /// Returns a dictionary mapping filenames (e.g. `"Widget.swift"`) to their /// generated Swift source code. Each file includes a standard header with /// `import Foundation` and the generated C interop import, followed by the /// declaration for that single type. Only types present in the analysis /// result are included. /// /// - Parameters: /// - repository: The parsed GIR repository to generate code from. /// - analysis: The analysis results specifying which types to generate. /// - Returns: A dictionary of filename to generated source code. /// - Throws: `CodeGenError` if generation fails. public func generateFiles(repository: Repository, analysis: AnalysisResult) throws -> [String: String] { var bodies: [String: String] = [:] for ns in repository.namespaces where ns.name == config.library { let shouldGenerateAll = analysis.generatedTypes.isEmpty for cls in ns.classes { let fullName = "\(ns.name).\(cls.name)" if !shouldGenerateAll && !analysis.generatedTypes.contains(fullName) { continue } bodies["\(cls.name).swift"] = generateClass(cls, namespace: ns.name, analysis: analysis) } for enm in ns.enumerations { let fullName = "\(ns.name).\(enm.name)" if !shouldGenerateAll && !analysis.generatedTypes.contains(fullName) { continue } bodies["\(enm.name).swift"] = generateEnum(enm) } for bf in ns.bitfields { let fullName = "\(ns.name).\(bf.name)" if !shouldGenerateAll && !analysis.generatedTypes.contains(fullName) { continue } bodies["\(bf.name).swift"] = generateBitfield(bf) } for iface in ns.interfaces { let fullName = "\(ns.name).\(iface.name)" if !shouldGenerateAll && !analysis.generatedTypes.contains(fullName) { continue } bodies["\(iface.name).swift"] = generateInterface(iface) } for cb in ns.callbacks { let fullName = "\(ns.name).\(cb.name)" if !shouldGenerateAll && !analysis.generatedTypes.contains(fullName) { continue } bodies["\(cb.name).swift"] = generateCallback(cb) } for fn in ns.functions { let fullName = "\(ns.name).\(fn.name)" if !shouldGenerateAll && !analysis.generatedTypes.contains(fullName) { continue } let fnFilename = "\(Self.pascalCaseName(fn.name)).swift" if !bodies.keys.contains(fnFilename) { bodies[fnFilename] = generateGlobalFunction(fn, namespace: ns.name) } } } let header = """ // Generated by SwiftGtkGen. DO NOT EDIT. // Source: \(config.library) \(config.version) import C\(config.library) import Foundation """ + "\n" var files: [String: String] = [:] for (name, body) in bodies { files[name] = header + body } return files } /// Generates the package scaffolding files (Package.swift, module map, umbrella header) /// for a self-contained SwiftPM wrapper package. /// /// Returns a dictionary mapping relative file paths (e.g. `"Package.swift"`, /// `"Sources/CGtk/module.modulemap"`) to their textual content. The library /// name in `config.library` determines the package name, the C interop target /// name (prefixed with `C`). Link names and pkg-config names are derived from /// the repository's namespace `shared-library` attribute, the umbrella header /// from the repository's `` element, and external library link flags /// from `` elements and `config.externalLibraries`. /// /// - Parameters: /// - config: The generation configuration (uses `library` and /// `externalLibraries` fields). /// - repository: The parsed GIR repository containing namespace metadata, /// ``, ``, and `` values. /// - Returns: A dictionary of relative path → file content. public static func generatePackageScaffolding(config: GenerationConfig, repository: Repository) -> [String: String] { var files: [String: String] = [:] let cName = "C\(config.library)" let lowerName = config.library.lowercased() // Extract namespace data from the repository let ns = repository.namespaces.first { $0.name == config.library } let sharedLibrary = ns?.cSharedLibrary ?? "" // Parse shared-library to derive all link names // shared-library format: "libgtk-4.so.1" or "libgio-2.0.so.0,libglib-2.0.so.0" let libraries = sharedLibrary.split(separator: ",").map(String.init) let allLinkNames = libraries.compactMap { extractLinkName(from: $0) } let lowerLibName = config.library.lowercased() // Pick the best primary link: prefer the one matching the library name let linkName = allLinkNames.first { $0.lowercased().contains(lowerLibName) } ?? allLinkNames.first ?? lowerName let pkgConfigName = linkName // Derive umbrella header from repository let cHeader = repository.cHeaderPath let umbrellaHeader = cHeader.isEmpty ? "#include <\(lowerName)/\(lowerName).h>" : "#include <\(cHeader)>" // Build external link names from GIR dependencies + config var externalLinks: [String] = [] for linkName in repository.includedLibraryLinks { if !externalLinks.contains(linkName) { externalLinks.append(linkName) } } for ext in config.externalLibraries { let el = ext.lowercased() if !externalLinks.contains(el) { externalLinks.append(el) } } // Package.swift let packageSwift = """ // swift-tools-version: 6.0 import PackageDescription let package = Package( name: "\(config.library)", products: [ .library( name: "\(config.library)", targets: ["\(config.library)"] ), ], targets: [ .target( name: "\(config.library)", dependencies: ["\(cName)"] ), .systemLibrary( name: "\(cName)", path: "Sources/\(cName)", pkgConfig: "\(pkgConfigName)" ), ] ) """ files["Package.swift"] = packageSwift // Module map: include all shared libraries as links let linkLines = (allLinkNames + externalLinks).map { " link \"\($0)\"" }.joined(separator: "\n") let moduleMap = """ module \(cName) [system] { umbrella "\(lowerName)" \(linkLines) } """ files["Sources/\(cName)/module.modulemap"] = moduleMap files["Sources/\(cName)/\(cName).h"] = umbrellaHeader + "\n" return files } /// Extracts link name from a shared-library string like "libgtk-4.so.1" -> "gtk-4". private static func extractLinkName(from sharedLib: String) -> String? { guard sharedLib.hasPrefix("lib") else { return nil } let withoutLib = String(sharedLib.dropFirst(3)) guard let soRange = withoutLib.range(of: ".so") else { return withoutLib } return String(withoutLib[.. String { var swift = "" let override = analysis.classOverrides["\(namespace).\(cls.name)"] swift += Self.formatDocComment(cls.doc) let isFinal = override?.finalType ?? true let classKeyword = isFinal ? "final class" : "open class" let parentClause = cls.parent.map { ": \($0)" } ?? "" swift += "public \(classKeyword) \(cls.name)\(parentClause) {\n" swift += """ let pointer: UnsafeMutableRawPointer public init(pointer: UnsafeMutableRawPointer) { g_object_ref_sink(pointer) self.pointer = pointer } deinit { g_object_unref(pointer) } """ for ctor in cls.constructors { swift += Self.formatDocComment(ctor.doc, indentation: 4) swift += Self.generateConstructor(constructor: ctor, className: cls.name) } for prop in cls.properties { swift += Self.formatDocComment(prop.doc, indentation: 4) swift += Self.generatePropertyAccessor(property: prop) } // Class-level functions (static methods) for fn in cls.functions { swift += Self.formatDocComment(fn.doc, indentation: 4) let params = Self.nonVarargParameters(fn.parameters) let paramList = params.map { p -> String in let type = Self.typeToSwift(p.type) return "\(Self.swiftifyParameterName(p.name)): \(type)" }.joined(separator: ", ") let returnTypeStr = fn.returnType == .void ? "" : " -> \(Self.typeToSwift(fn.returnType))" let returnStmt = fn.returnType == .void ? "" : "return " let args = params.map { p -> String in Self.cParameterExpression(name: Self.swiftifyParameterName(p.name), type: p.type) }.joined(separator: ", ") let cCall = "\(fn.cIdentifier)(\(args))" let wrappedReturn = Self.wrapCReturnValue(callExpression: cCall, returnType: fn.returnType) swift += """ public static func \(Self.swiftifyMethodName(fn.name))(\(paramList))\(returnTypeStr) { \(returnStmt)\(wrappedReturn) } """ } for method in cls.methods { swift += Self.formatDocComment(method.doc, indentation: 4) let params = Self.nonVarargParameters(method.parameters.filter { !$0.isInstanceParameter }) let paramList = params.map { p -> String in "\(Self.swiftifyParameterName(p.name)): \(Self.typeToSwift(p.type))" }.joined(separator: ", ") let returnTypeStr = method.returnType == .void ? "" : " -> \(Self.typeToSwift(method.returnType))" let returnStmt = method.returnType == .void ? "" : "return " let cCall = Self.generateCFunctionCall(method: method, instancePointer: "pointer") swift += """ public func \(Self.swiftifyMethodName(method.name))(\(paramList))\(returnTypeStr) { \(returnStmt)\(cCall) } """ } for signal in cls.signals { swift += Self.formatDocComment(signal.doc, indentation: 4) swift += Self.generateSignalConnection(signal: signal) } while swift.hasSuffix("\n") { swift = String(swift.dropLast()) } swift += "\n}\n" return swift } // MARK: - Enum Generation /// Generates a Swift `enum` with `Int` raw values from a GIR enumeration. /// /// Each GIR member is mapped to a camelCase Swift case. The enum adopts /// `Int` as its raw value type, matching the underlying C enum layout. /// /// - Parameter enm: The GIR enumeration to generate. /// - Returns: A Swift enum declaration as a string. private func generateEnum(_ enm: Enumeration) -> String { var swift = "" swift += Self.formatDocComment(enm.doc) swift += "public enum \(enm.name): Int {\n" for member in enm.members { let caseName = Self.swiftifyEnumCaseName(member.name) let formattedValue = Self.formatNumericLiteral(member.value) swift += " case \(caseName) = \(formattedValue)\n" } while swift.hasSuffix("\n") { swift = String(swift.dropLast()) } swift += "\n}\n" return swift } // MARK: - Bitfield Generation /// Generates a Swift `OptionSet` conformance from a GIR bitfield definition. /// /// Produces a struct with an `Int` raw value and static constants using /// bit-shifted literals (`1 << N`), matching the C bitmask semantics. /// /// - Parameter bf: The GIR bitfield to generate. /// - Returns: A Swift `OptionSet` struct declaration as a string. private func generateBitfield(_ bf: Bitfield) -> String { var swift = "" swift += Self.formatDocComment(bf.doc) swift += "public struct \(bf.name): OptionSet {\n" swift += " public let rawValue: Int\n" swift += " public init(rawValue: Int) { self.rawValue = rawValue }\n\n" for member in bf.members { let caseName = Self.swiftifyEnumCaseName(member.name) let formattedValue = Self.formatNumericLiteral(member.value) swift += " public static let \(caseName) = \(bf.name)(rawValue: 1 << \(formattedValue))\n" } while swift.hasSuffix("\n") { swift = String(swift.dropLast()) } swift += "\n}\n" return swift } // MARK: - Interface Generation /// Generates a Swift protocol declaration from a GIR interface definition. /// /// Produces a `protocol` with method requirements (excluding the instance /// parameter) and computed property requirements for readable/writable /// properties. Signals are not included since protocol-based signal /// connection is not directly expressible. /// /// - Parameter iface: The GIR interface to generate. /// - Returns: A Swift protocol declaration as a string. private func generateInterface(_ iface: Interface) -> String { var swift = "" swift += Self.formatDocComment(iface.doc) swift += "public protocol \(iface.name) {\n" for method in iface.methods { let params = Self.nonVarargParameters(method.parameters.filter { !$0.isInstanceParameter }) let paramList = params.map { p -> String in "\(Self.swiftifyParameterName(p.name)): \(Self.typeToSwift(p.type))" }.joined(separator: ", ") let returnTypeStr = method.returnType == .void ? "" : " -> \(Self.typeToSwift(method.returnType))" swift += " func \(Self.swiftifyMethodName(method.name))(\(paramList))\(returnTypeStr)\n" } for prop in iface.properties { let propName = Self.swiftifyPropertyName(prop.name) let swiftType = Self.typeToSwift(prop.type) if prop.isReadable && prop.isWritable { swift += " var \(propName): \(swiftType) { get set }\n" } else if prop.isReadable { swift += " var \(propName): \(swiftType) { get }\n" } } while swift.hasSuffix("\n") { swift = String(swift.dropLast()) } swift += "\n}\n" return swift } // MARK: - Callback Generation /// Generates a Swift `typealias` with `@convention(c)` from a GIR callback /// definition. /// /// Produces a public typealias suitable for use as a C function pointer in /// the generated GObject wrapper APIs. Each callback parameter is mapped /// to its Swift type, and the return type is preserved. /// /// - Parameter cb: The GIR callback to generate. /// - Returns: A Swift typealias declaration as a string. private func generateCallback(_ cb: Callback) -> String { var result = Self.formatDocComment(cb.doc) let paramList = cb.parameters.map { p -> String in let type = Self.typeToSwift(p.type) return "\(Self.swiftifyParameterName(p.name)): \(type)" }.joined(separator: ", ") let returnType = cb.returnType == .void ? "Void" : Self.typeToSwift(cb.returnType) result += "public typealias \(cb.name) = @convention(c) (\(paramList)) -> \(returnType)\n" return result } // MARK: - Global Function Generation /// Generates a Swift free function from a GIR global function definition. /// /// Produces a `public func` that wraps the underlying C function call, /// including parameter passing (extracting `.pointer` from GObject types) /// and return value conversion (e.g. `String(cString:)` for strings, /// `!= 0` for booleans). /// /// - Parameters: /// - fn: The GIR global function to generate. /// - namespace: The GIR namespace the function belongs to. /// - Returns: A Swift function declaration as a string. private func generateGlobalFunction(_ fn: GlobalFunction, namespace: String) -> String { let params = Self.nonVarargParameters(fn.parameters) let paramList = params.map { p -> String in "\(Self.swiftifyParameterName(p.name)): \(Self.typeToSwift(p.type))" }.joined(separator: ", ") let returnTypeStr = fn.returnType == .void ? "" : " -> \(Self.typeToSwift(fn.returnType))" let returnStmt = fn.returnType == .void ? "" : "return " let args = params.map { p -> String in Self.cParameterExpression(name: Self.swiftifyParameterName(p.name), type: p.type) }.joined(separator: ", ") let cCall = "\(fn.cIdentifier)(\(args))" let wrappedReturn = Self.wrapCReturnValue(callExpression: cCall, returnType: fn.returnType) var result = Self.formatDocComment(fn.doc) result += "public func \(Self.swiftifyMethodName(fn.name))(\(paramList))\(returnTypeStr) {\n" result += " \(returnStmt)\(wrappedReturn)\n" result += "}\n" return result } // MARK: - C Function Call Generation /// Generates a Swift expression that calls the C function corresponding /// to a GIR method, including return-type wrapping. /// /// Builds the argument list from the method's parameters (replacing the /// instance parameter with the given pointer expression), then wraps /// the call result according to the return type (e.g. `String(cString:)` /// for strings, `!= 0` for booleans). /// /// - Parameters: /// - method: The GIR method to generate a call for. /// - instancePointer: The Swift expression for the instance pointer /// (typically `"pointer"`). /// - Returns: A Swift expression string for the C function call. // MARK: - Property Accessor Generation /// Generates a Swift computed property declaration for a GObject property /// using the `GValue` API (`g_object_get_property`/`g_object_set_property`). /// /// Construct-only properties return an empty string since they are handled /// via constructor parameters rather than runtime setters. /// /// - Parameter property: The GIR property to generate an accessor for. /// - Returns: A Swift computed property declaration string, or empty if /// the property is construct-only. public static func generatePropertyAccessor(property: Property) -> String { guard !property.isConstructOnly else { return "" } let propName = swiftifyPropertyName(property.name) let swiftType = typeToSwift(property.type) let cTypeName = gValueTypeName(for: property.type) let getterFunc = gValueGetterFunc(for: property.type) let setterFunc = gValueSetterFunc(for: property.type) let girPropName = property.name var swift = "" if property.isReadable && property.isWritable { swift += """ public var \(propName): \(swiftType) { get { var value = GValue() g_value_init(&value, \(cTypeName)) g_object_get_property(pointer, "\(girPropName)", &value) let result = \(getterFunc)(&value) g_value_unset(&value) return result } set { var value = GValue() g_value_init(&value, \(cTypeName)) \(setterFunc)(&value, newValue) g_object_set_property(pointer, "\(girPropName)", &value) g_value_unset(&value) } } """ } else if property.isReadable { swift += """ public var \(propName): \(swiftType) { var value = GValue() g_value_init(&value, \(cTypeName)) g_object_get_property(pointer, "\(girPropName)", &value) let result = \(getterFunc)(&value) g_value_unset(&value) return result } """ } else if property.isWritable { swift += """ public var \(propName): \(swiftType) { set { var value = GValue() g_value_init(&value, \(cTypeName)) \(setterFunc)(&value, newValue) g_object_set_property(pointer, "\(girPropName)", &value) g_value_unset(&value) } } """ } return swift } /// Returns the GType name constant for the given GIR type. private static func gValueTypeName(for type: GIRType) -> String { switch type { case .boolean: return "G_TYPE_BOOLEAN" case .int8: return "G_TYPE_INT8" case .int16: return "G_TYPE_INT16" case .int32: return "G_TYPE_INT" case .int64: return "G_TYPE_INT64" case .uint8: return "G_TYPE_UINT8" case .uint16: return "G_TYPE_UINT16" case .uint32: return "G_TYPE_UINT" case .uint64: return "G_TYPE_UINT64" case .float: return "G_TYPE_FLOAT" case .double: return "G_TYPE_DOUBLE" case .string, .filename: return "G_TYPE_STRING" case .typeRef: return "G_TYPE_OBJECT" case .pointer: return "G_TYPE_POINTER" case .array: return "G_TYPE_ARRAY" case .cArray: return "G_TYPE_ARRAY" case .void: return "G_TYPE_NONE" case .optional: return "G_TYPE_NONE" } } /// Returns the GValue getter function name for the given GIR type. private static func gValueGetterFunc(for type: GIRType) -> String { switch type { case .boolean: return "g_value_get_boolean" case .int8: return "g_value_get_schar" case .int16: return "g_value_get_int16" case .int32: return "g_value_get_int" case .int64: return "g_value_get_int64" case .uint8: return "g_value_get_uchar" case .uint16: return "g_value_get_uint16" case .uint32: return "g_value_get_uint" case .uint64: return "g_value_get_uint64" case .float: return "g_value_get_float" case .double: return "g_value_get_double" case .string, .filename: return "g_value_get_string" case .typeRef: return "g_value_get_object" case .pointer: return "g_value_get_pointer" case .array: return "g_value_get_boxed" case .cArray: return "g_value_get_boxed" default: return "g_value_get_pointer" } } /// Returns the GValue setter function name for the given GIR type. private static func gValueSetterFunc(for type: GIRType) -> String { switch type { case .boolean: return "g_value_set_boolean" case .int8: return "g_value_set_schar" case .int16: return "g_value_set_int16" case .int32: return "g_value_set_int" case .int64: return "g_value_set_int64" case .uint8: return "g_value_set_uchar" case .uint16: return "g_value_set_uint16" case .uint32: return "g_value_set_uint" case .uint64: return "g_value_set_uint64" case .float: return "g_value_set_float" case .double: return "g_value_set_double" case .string, .filename: return "g_value_set_string" case .typeRef: return "g_value_set_object" case .pointer: return "g_value_set_pointer" default: return "g_value_set_pointer" } } // MARK: - Signal Connection Generation /// Generates a signal connection method using `g_signal_connect_data`. /// /// The method takes a Swift closure and bridges it to a C function pointer /// via `Unmanaged.passRetained`. The retain/release is managed by /// `GClosureNotify` (the destroy handler passed to `g_signal_connect_data`). /// /// For signals with zero parameters, a complete bridging trampoline is /// generated. For signals with parameters, the generated code uses /// `g_signal_connect_data` with a nil callback, noting manual implementation. /// /// - Parameter signal: The GIR signal to generate. /// - Returns: A signal connection method declaration as a string. public static func generateSignalConnection(signal: Signal) -> String { let signalName = swiftifySignalName(signal.name) let handlerArgs = signal.parameters.map { p -> String in let swiftType = typeToSwift(p.type) return "_: \(swiftType)" }.joined(separator: ", ") let detailClause = signal.isDetailed ? ", \"\(signal.name)\"" : "" if signal.parameters.isEmpty { return """ public func connect\(signalName)(_ handler: @escaping () -> Void) -> Int { let boxed = Unmanaged.passRetained(handler as AnyObject).toOpaque() let callback: @convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?) -> Void = { (_, data) in let stored = Unmanaged.fromOpaque(data!).takeUnretainedValue() as! () -> Void stored() } let destroy: @convention(c) (UnsafeMutableRawPointer?) -> Void = { Unmanaged.fromOpaque($0!).release() } return Int(g_signal_connect_data(pointer, "\(signal.name)", callback, boxed, destroy, 0)) } """ } else { return """ public func connect\(signalName)(_ handler: @escaping (\(handlerArgs)) -> Void) -> Int { // Manual: Signal connection with parameters needs manual implementation return Int(g_signal_connect_data(pointer, "\(signal.name)"\(detailClause), nil, nil, nil, 0)) } """ } } // MARK: - Constructor Generation /// Generates a Swift convenience initializer for a GObject constructor. /// /// Calls the C constructor function (e.g. `gtk_button_new_with_label(label)`) /// and handles the floating reference pattern by calling `g_object_ref_sink` /// on the returned pointer before passing it to `self.init(pointer:)`. /// This is correct for all `GtkWidget` subclasses and is harmless for /// non-floating objects (where `g_object_ref_sink` simply refs once). /// /// - Parameters: /// - constructor: The GIR constructor definition. /// - className: The Swift class name. /// - Returns: A convenience initializer declaration as a string. public static func generateConstructor(constructor: Constructor, className: String) -> String { let params = Self.nonVarargParameters(constructor.parameters) let paramList = params.map { p -> String in "\(Self.swiftifyParameterName(p.name)): \(Self.typeToSwift(p.type))" }.joined(separator: ", ") let args = params.map { p -> String in Self.cParameterExpression(name: Self.swiftifyParameterName(p.name), type: p.type) }.joined(separator: ", ") return """ public convenience init(\(paramList)) { let ptr = \(constructor.cIdentifier)(\(args)) g_object_ref_sink(ptr) self.init(pointer: ptr!) } """ } // MARK: - C Function Call Generation public static func generateCFunctionCall(method: Method, instancePointer: String) -> String { let sortedParams = Self.nonVarargParameters(method.parameters) let args = sortedParams.map { p -> String in if p.isInstanceParameter { return instancePointer } return cParameterExpression(name: Self.swiftifyParameterName(p.name), type: p.type) }.joined(separator: ", ") let cFuncCall = "\(method.cIdentifier)(\(args))" return wrapCReturnValue(callExpression: cFuncCall, returnType: method.returnType) } /// Returns the Swift argument expression for a parameter of the given type. /// /// Extracts `.pointer` from GObject wrapper types and handles optional /// GObject pointers with optional chaining. All other types pass through /// as-is (Swift C interop handles the conversion natively). /// /// - Parameters: /// - name: The Swift parameter name. /// - type: The GIR type of the parameter. /// - Returns: A Swift expression string for the C function argument. private static func cParameterExpression(name: String, type: GIRType) -> String { switch type { case .typeRef: return "\(name).pointer" case .optional(let inner): if case .typeRef = inner { return "\(name)?.pointer" } return name default: return name } } /// Wraps a C function call expression to convert its return value to the /// corresponding Swift type. /// /// - `.void` returns the bare call expression (no `return` statement). /// - `.boolean` compares the result to `0` via `cFunc() != 0`. /// - `.string` / `.filename` wraps with `String(cString:)`. /// - `.typeRef` wraps in the Swift wrapper type's initializer. /// - `.optional(.typeRef)` uses `Optional.map` to wrap non-nil results. /// - All other types pass through directly. /// /// - Parameters: /// - callExpression: The raw C function call expression. /// - returnType: The GIR return type. /// - Returns: A Swift expression string with appropriate type wrapping. private static func wrapCReturnValue(callExpression: String, returnType: GIRType) -> String { switch returnType { case .void: return callExpression case .boolean: return "(\(callExpression) != 0)" case .string, .filename: return "String(cString: \(callExpression))" case .typeRef(let name, _): return "\(name)(pointer: \(callExpression))" case .optional(let inner): if case .typeRef(let name, _) = inner { return "\(callExpression).map { \(name)(pointer: $0) }" } if case .string = inner { return "\(callExpression).map { String(cString: $0) }" } if case .filename = inner { return "\(callExpression).map { String(cString: $0) }" } return callExpression default: return callExpression } } // MARK: - Documentation Comment Formatting /// Formats a documentation string as DocC-compatible `///` comments. /// - Parameter doc: The raw documentation text from the GIR file. /// - Returns: A string of `///`-prefixed lines, or empty string if nil/empty. private static func formatDocComment(_ doc: String?, indentation: Int = 0) -> String { guard let doc = doc, !doc.isEmpty else { return "" } let indent = String(repeating: " ", count: indentation) let lines = doc.split(separator: "\n", omittingEmptySubsequences: false) var result = "" for line in lines { let trimmed = line.trimmingCharacters(in: .whitespaces) if trimmed.isEmpty { result += "\(indent)///\n" } else { result += "\(indent)/// \(trimmed)\n" } } return result } // MARK: - Formatting Helpers /// Formats a numeric literal string with underscore separators for readability. /// For example, `"4294967295"` becomes `"4_294_967_295"`. private static func formatNumericLiteral(_ value: String) -> String { guard value.count > 4, let _ = Int(value) else { return value } var result = "" var remaining = value while remaining.count > 3 { let chunk = remaining.suffix(3) remaining = String(remaining.dropLast(3)) result = "_\(chunk)" + result } return remaining + result } // MARK: - Naming Helpers /// Maps a GIR type to its corresponding Swift type name string. /// /// Handles primitive types (`boolean` → `Bool`, `int32` → `Int32`, etc.), /// type references (looked up by name), and compound types (arrays, /// C-style arrays, optionals). Pointers and filenames map to /// `UnsafeMutableRawPointer` and `String` respectively. /// /// - Parameter type: The GIR type to map. /// - Returns: The Swift type name as a string. private static func typeToSwift(_ type: GIRType) -> String { switch type { case .void: return "Void" case .boolean: return "Bool" case .int8: return "Int8" case .int16: return "Int16" case .int32: return "Int32" case .int64: return "Int64" case .uint8: return "UInt8" case .uint16: return "UInt16" case .uint32: return "UInt32" case .uint64: return "UInt64" case .float: return "Float" case .double: return "Double" case .string: return "String" case .filename: return "String" case .pointer: return "UnsafeMutableRawPointer" case .typeRef(let name, _): return name case .array(let inner): return "[\(typeToSwift(inner))]" case .cArray(let inner): return "UnsafeBufferPointer<\(typeToSwift(inner))>" case .optional(let inner): return "\(typeToSwift(inner))?" } } /// Converts a snake_case GIR name to a camelCase Swift property name. /// /// Splits on underscores and lowercases the first part while capitalizing /// each subsequent part. For example, `"current_page"` becomes /// `"currentPage"`. /// /// - Parameter name: The snake_case GIR property name. /// - Returns: A camelCase Swift property name. private static func swiftifyPropertyName(_ name: String) -> String { name.split { $0 == "_" || $0 == "-" }.enumerated().map { i, part in i == 0 ? String(part).lowercased() : String(part).capitalized }.joined() } /// The set of Swift reserved keywords that cannot be used as identifiers /// without backtick escaping. private static let reservedKeywords: Set = [ "self", "type", "class", "default", "in", "for", "repeat", "while", "switch", "case", "break", "continue", "return", "if", "else", "guard", "defer", "do", "try", "throw", "catch", "import", "let", "var", "func", "static", "struct", "enum", "protocol", "extension", "init", "deinit", "subscript", "where", "operator", "Protocol", "rethrows", "associatedtype", "precedencegroup", "true", "false", "nil", "Self", "Type", "private", "fileprivate", "internal", "public", "open", "is", "as", "async", "await", "nonisolated", "throws", ] /// Converts a GIR parameter name to a valid Swift identifier. /// /// Applies camelCase conversion (via ``swiftifyPropertyName(_:)``) and /// backtick-escapes the result if it collides with a Swift reserved /// keyword such as `self`, `class`, `default`, or `return`. /// /// - Parameter name: The raw GIR parameter name. /// - Returns: A valid Swift parameter label, backtick-escaped if needed. private static func swiftifyParameterName(_ name: String) -> String { let swiftName = swiftifyPropertyName(name) return reservedKeywords.contains(swiftName) ? "`\(swiftName)`" : swiftName } private static func swiftifyMethodName(_ name: String) -> String { let swiftName = Self.swiftifyPropertyName(name) return reservedKeywords.contains(swiftName) ? "`\(swiftName)`" : swiftName } /// Converts a GIR signal name (kebab-case) to a PascalCase Swift identifier. /// /// Splits on hyphens and capitalizes each component. For example, /// `"page-changed"` becomes `"PageChanged"`, suitable for use in /// `connectPageChanged(_:)`. /// /// - Parameter name: The kebab-case GIR signal name. /// - Returns: A PascalCase Swift method name component. private static func swiftifySignalName(_ name: String) -> String { name.split(separator: "-").map { $0.capitalized }.joined() } /// Converts a GIR enum case name (SCREAMING_SNAKE_CASE) to a camelCase Swift case. /// /// Lowercases the entire string, splits on underscores, and lowercases the /// first word while capitalizing subsequent words. For example, /// `"GTK_WIDGET_HELP"` becomes `"gtkWidgetHelp"`. Handles reserved keyword /// collisions (backtick-escapes) and identifiers starting with digits (prefixes /// with underscore). /// /// - Parameter name: The GIR enum case name, typically in SCREAMING_SNAKE_CASE. /// - Returns: A valid camelCase Swift enum case name. private static func swiftifyEnumCaseName(_ name: String) -> String { let lower = name.lowercased() let parts = lower.split { $0 == "_" || $0 == "-" } var result = parts.enumerated().map { i, p in i == 0 ? String(p) : String(p).capitalized }.joined() // Prefix with underscore if starts with a digit if result.first?.isNumber == true { result = "_" + result } // Backtick-escape Swift keywords if reservedKeywords.contains(result) { result = "`\(result)`" } return result } /// Converts a snake_case or kebab-case name to PascalCase. /// /// Splits on underscores and hyphens and capitalizes each component. /// For example, `"gtk_init"` becomes `"GtkInit"`. private static func pascalCaseName(_ name: String) -> String { name.split { $0 == "_" || $0 == "-" }.map { $0.capitalized }.joined() } /// Filters out variadic C parameters (those named `"..."` or empty) from a parameter list. private static func nonVarargParameters(_ params: [Parameter]) -> [Parameter] { params.filter { $0.name != "..." && !$0.name.isEmpty } } }