Refactor CodeGen.swift into CodeGen+Signal.swift and CodeGen+Scaffolding.swift
This commit is contained in:
parent
84a536d314
commit
27edea19af
5 changed files with 361 additions and 295 deletions
124
Sources/SwiftGtkGenCore/CodeGen+Scaffolding.swift
Normal file
124
Sources/SwiftGtkGenCore/CodeGen+Scaffolding.swift
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import Foundation
|
||||
|
||||
extension CodeGenerator {
|
||||
// MARK: - Package Scaffolding Generation
|
||||
|
||||
/// 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 `<c:include>` element, and external library link flags
|
||||
/// from `<include>` elements and `config.externalLibraries`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - config: The generation configuration (uses `library` and
|
||||
/// `externalLibraries` fields).
|
||||
/// - repository: The parsed GIR repository containing namespace metadata,
|
||||
/// `<c:include>`, `<include>`, and `<package>` 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
|
||||
// Use <package> name as pkgConfig when available (it matches the system .pc file),
|
||||
// otherwise fall back to the derived link name
|
||||
let pkgConfigName = repository.packageName.isEmpty ? linkName : repository.packageName
|
||||
|
||||
// Derive umbrella header from repository <c:include>
|
||||
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.2
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "\(config.library)",
|
||||
platforms: [.macOS(.v14)],
|
||||
products: [
|
||||
.library(
|
||||
name: "\(config.library)",
|
||||
targets: ["\(config.library)"]
|
||||
)
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "\(config.library)",
|
||||
dependencies: ["\(cName)"],
|
||||
swiftSettings: [
|
||||
.enableExperimentalFeature("StrictConcurrency=complete"),
|
||||
.defaultIsolation(MainActor.self),
|
||||
.enableUpcomingFeature("NonisolatedNonsendingByDefault"),
|
||||
]
|
||||
),
|
||||
.systemLibrary(
|
||||
name: "\(cName)",
|
||||
path: "Sources/\(cName)",
|
||||
pkgConfig: "\(pkgConfigName)"
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
"""
|
||||
files["Package.swift"] = packageSwift
|
||||
|
||||
// Module map: use the generated umbrella header which includes the system header
|
||||
let linkLines = (allLinkNames + externalLinks).map { " link \"\($0)\"" }.joined(separator: "\n")
|
||||
let moduleMap = """
|
||||
module \(cName) [system] {
|
||||
header "\(cName).h"
|
||||
\(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[..<soRange.lowerBound])
|
||||
}
|
||||
}
|
||||
118
Sources/SwiftGtkGenCore/CodeGen+Signal.swift
Normal file
118
Sources/SwiftGtkGenCore/CodeGen+Signal.swift
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import Foundation
|
||||
|
||||
extension CodeGenerator {
|
||||
// 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 parameters, the C callback receives them as
|
||||
/// `UnsafeMutableRawPointer?` values which are extracted into Swift types.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - signal: The GIR signal to generate.
|
||||
/// - inhibit: If `true`, the handler returns `Bool` to control signal propagation.
|
||||
/// - Returns: A signal connection method declaration as a string.
|
||||
public static func generateSignalConnection(signal: Signal, inhibit: Bool = false) -> String {
|
||||
let signalName = swiftifySignalName(signal.name)
|
||||
let handlerReturnType = inhibit ? "Bool" : "Void"
|
||||
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 {
|
||||
let callbackReturn = inhibit ? " -> gboolean" : ""
|
||||
let handlerCall = inhibit ? "let result = stored(); return result ? 1 : 0" : "stored()"
|
||||
return """
|
||||
public func connect\(signalName)(_ handler: @escaping () -> \(handlerReturnType)) -> Int {
|
||||
let boxed = Unmanaged.passRetained(handler as AnyObject).toOpaque()
|
||||
let callback: @convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?)\(callbackReturn) = { (_, data) in
|
||||
let stored = Unmanaged<AnyObject>.fromOpaque(data!).takeUnretainedValue() as! () -> \(handlerReturnType)
|
||||
\(handlerCall)
|
||||
}
|
||||
let destroy: @convention(c) (UnsafeMutableRawPointer?) -> Void = { Unmanaged<AnyObject>.fromOpaque($0!).release() }
|
||||
return Int(g_signal_connect_data(pointer, "\(signal.name)", callback, boxed, destroy, 0))
|
||||
}
|
||||
|
||||
|
||||
"""
|
||||
} else {
|
||||
let rawPointerParams = signal.parameters.map { _ in "UnsafeMutableRawPointer?" }.joined(separator: ", ")
|
||||
let rawPointerNames = signal.parameters.enumerated().map { "p\($0.offset)" }.joined(separator: ", ")
|
||||
let extractionExprs = signal.parameters.enumerated().map { i, p in
|
||||
cSignalParameterExtraction(index: i, type: p.type)
|
||||
}.joined(separator: ", ")
|
||||
let callbackReturn = inhibit ? " -> gboolean" : ""
|
||||
let handlerCall = inhibit
|
||||
? "let result = stored(\(extractionExprs)); return result ? 1 : 0"
|
||||
: "stored(\(extractionExprs))"
|
||||
return """
|
||||
public func connect\(signalName)(_ handler: @escaping (\(handlerArgs)) -> \(handlerReturnType)) -> Int {
|
||||
let boxed = Unmanaged.passRetained(handler as AnyObject).toOpaque()
|
||||
let callback: @convention(c) (\(rawPointerParams), UnsafeMutableRawPointer?)\(callbackReturn) = { (\(rawPointerNames), data) in
|
||||
let stored = Unmanaged<AnyObject>.fromOpaque(data!).takeUnretainedValue() as! (\(handlerArgs)) -> \(handlerReturnType)
|
||||
\(handlerCall)
|
||||
}
|
||||
let destroy: @convention(c) (UnsafeMutableRawPointer?) -> Void = { Unmanaged<AnyObject>.fromOpaque($0!).release() }
|
||||
return Int(g_signal_connect_data(pointer, "\(signal.name)"\(detailClause), callback, boxed, destroy, 0))
|
||||
}
|
||||
|
||||
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a Swift expression to extract a signal parameter value from an
|
||||
/// `UnsafeMutableRawPointer?` received in the C callback trampoline.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - index: The zero-based index of the signal parameter.
|
||||
/// - type: The GIR type of the parameter.
|
||||
/// - Returns: A Swift expression string that converts `p{index}` to the target type.
|
||||
private static func cSignalParameterExtraction(index: Int, type: GIRType) -> String {
|
||||
let p = "p\(index)"
|
||||
switch type {
|
||||
case .boolean:
|
||||
return "Int(bitPattern: \(p)) != 0"
|
||||
case .int8:
|
||||
return "Int8(bitPattern: UInt8(bitPattern: Int8(truncatingIfNeeded: Int(bitPattern: \(p)))))"
|
||||
case .int16:
|
||||
return "Int16(bitPattern: UInt16(bitPattern: Int16(truncatingIfNeeded: Int(bitPattern: \(p)))))"
|
||||
case .int32:
|
||||
return "Int32(bitPattern: Int32(truncatingIfNeeded: Int(bitPattern: \(p))))"
|
||||
case .int64:
|
||||
return "\(p)!.load(as: Int64.self)"
|
||||
case .uint8:
|
||||
return "UInt8(bitPattern: UInt8(truncatingIfNeeded: Int(bitPattern: \(p))))"
|
||||
case .uint16:
|
||||
return "UInt16(bitPattern: UInt16(truncatingIfNeeded: Int(bitPattern: \(p))))"
|
||||
case .uint32:
|
||||
return "UInt32(bitPattern: UInt32(truncatingIfNeeded: Int(bitPattern: \(p))))"
|
||||
case .uint64:
|
||||
return "\(p)!.load(as: UInt64.self)"
|
||||
case .float:
|
||||
return "\(p)!.load(as: Float.self)"
|
||||
case .double:
|
||||
return "\(p)!.load(as: Double.self)"
|
||||
case .string, .filename:
|
||||
return "String(cString: \(p)!.assumingMemoryBound(to: CChar.self))"
|
||||
case .typeRef(let name, _):
|
||||
return "\(name)(pointer: \(p)!)"
|
||||
case .pointer:
|
||||
return "\(p)!"
|
||||
case .optional(let inner):
|
||||
if case .typeRef(let name, _) = inner {
|
||||
return "\(p).map { \(name)(pointer: $0) }"
|
||||
}
|
||||
return cSignalParameterExtraction(index: index, type: inner)
|
||||
default:
|
||||
return "\(p)!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -48,12 +48,14 @@ public struct CodeGenerator {
|
|||
"""
|
||||
|
||||
for ns in repository.namespaces where ns.name == config.library {
|
||||
let classNames = Set(ns.classes.map { $0.name })
|
||||
|
||||
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)
|
||||
output += generateClass(cls, namespace: ns.name, analysis: analysis, classTypeNames: classNames)
|
||||
}
|
||||
for enm in ns.enumerations {
|
||||
let fullName = "\(ns.name).\(enm.name)"
|
||||
|
|
@ -78,12 +80,12 @@ public struct CodeGenerator {
|
|||
for fn in ns.functions {
|
||||
let fullName = "\(ns.name).\(fn.name)"
|
||||
if !shouldGenerateAll && !analysis.generatedTypes.contains(fullName) { continue }
|
||||
output += generateGlobalFunction(fn, namespace: ns.name)
|
||||
output += generateGlobalFunction(fn, namespace: ns.name, classTypeNames: classNames)
|
||||
}
|
||||
for rec in ns.records {
|
||||
let fullName = "\(ns.name).\(rec.name)"
|
||||
if !shouldGenerateAll && !analysis.generatedTypes.contains(fullName) { continue }
|
||||
output += generateRecord(rec, namespace: ns.name, analysis: analysis)
|
||||
output += generateRecord(rec, namespace: ns.name, analysis: analysis, classTypeNames: classNames)
|
||||
}
|
||||
for cst in ns.constants {
|
||||
let fullName = "\(ns.name).\(cst.name)"
|
||||
|
|
@ -117,12 +119,14 @@ public struct CodeGenerator {
|
|||
var bodies: [String: String] = [:]
|
||||
|
||||
for ns in repository.namespaces where ns.name == config.library {
|
||||
let classNames = Set(ns.classes.map { $0.name })
|
||||
|
||||
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)
|
||||
bodies["\(cls.name).swift"] = generateClass(cls, namespace: ns.name, analysis: analysis, classTypeNames: classNames)
|
||||
}
|
||||
for enm in ns.enumerations {
|
||||
let fullName = "\(ns.name).\(enm.name)"
|
||||
|
|
@ -149,13 +153,13 @@ public struct CodeGenerator {
|
|||
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)
|
||||
bodies[fnFilename] = generateGlobalFunction(fn, namespace: ns.name, classTypeNames: classNames)
|
||||
}
|
||||
}
|
||||
for rec in ns.records {
|
||||
let fullName = "\(ns.name).\(rec.name)"
|
||||
if !shouldGenerateAll && !analysis.generatedTypes.contains(fullName) { continue }
|
||||
bodies["\(rec.name).swift"] = generateRecord(rec, namespace: ns.name, analysis: analysis)
|
||||
bodies["\(rec.name).swift"] = generateRecord(rec, namespace: ns.name, analysis: analysis, classTypeNames: classNames)
|
||||
}
|
||||
for cst in ns.constants {
|
||||
let fullName = "\(ns.name).\(cst.name)"
|
||||
|
|
@ -192,117 +196,6 @@ public struct CodeGenerator {
|
|||
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 `<c:include>` element, and external library link flags
|
||||
/// from `<include>` elements and `config.externalLibraries`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - config: The generation configuration (uses `library` and
|
||||
/// `externalLibraries` fields).
|
||||
/// - repository: The parsed GIR repository containing namespace metadata,
|
||||
/// `<c:include>`, `<include>`, and `<package>` 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 <c:include>
|
||||
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[..<soRange.lowerBound])
|
||||
}
|
||||
|
||||
// MARK: - Class Generation
|
||||
|
||||
/// Generates a Swift class declaration from a GIR class definition.
|
||||
|
|
@ -319,7 +212,7 @@ public struct CodeGenerator {
|
|||
/// - namespace: The GIR namespace the class belongs to (e.g. `"Gtk"`).
|
||||
/// - analysis: The analysis result containing per-type overrides.
|
||||
/// - Returns: A Swift class declaration as a string.
|
||||
private func generateClass(_ cls: Class, namespace: String, analysis: AnalysisResult) -> String {
|
||||
private func generateClass(_ cls: Class, namespace: String, analysis: AnalysisResult, classTypeNames: Set<String> = []) -> String {
|
||||
var swift = ""
|
||||
let override = analysis.classOverrides["\(namespace).\(cls.name)"]
|
||||
let concurrency = override?.concurrency ?? .none
|
||||
|
|
@ -349,7 +242,7 @@ public struct CodeGenerator {
|
|||
|
||||
for ctor in cls.constructors {
|
||||
swift += Self.formatDocComment(ctor.doc, indentation: 4)
|
||||
swift += Self.generateConstructor(constructor: ctor, className: cls.name)
|
||||
swift += Self.generateConstructor(constructor: ctor, className: cls.name, classTypeNames: classTypeNames)
|
||||
}
|
||||
|
||||
for prop in cls.properties {
|
||||
|
|
@ -374,7 +267,7 @@ public struct CodeGenerator {
|
|||
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)
|
||||
Self.cParameterExpression(name: Self.swiftifyParameterName(p.name), type: p.type, classTypeNames: classTypeNames)
|
||||
}.joined(separator: ", ")
|
||||
let cCall = "\(fn.cIdentifier)(\(args))"
|
||||
let wrappedReturn = Self.wrapCReturnValue(callExpression: cCall, returnType: fn.returnType)
|
||||
|
|
@ -419,7 +312,7 @@ public struct CodeGenerator {
|
|||
"\(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)
|
||||
Self.cParameterExpression(name: Self.swiftifyParameterName(p.name), type: p.type, classTypeNames: classTypeNames)
|
||||
}.joined(separator: ", ")
|
||||
swift += """
|
||||
\(methodVisibility) convenience init(\(methodName): String? = nil, \(paramList)) {
|
||||
|
|
@ -436,7 +329,7 @@ public struct CodeGenerator {
|
|||
}.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")
|
||||
let cCall = Self.generateCFunctionCall(method: method, instancePointer: "pointer", classTypeNames: classTypeNames)
|
||||
|
||||
swift += """
|
||||
\(methodVisibility) func \(methodName)(\(paramList))\(returnTypeStr) {
|
||||
|
|
@ -516,7 +409,7 @@ public struct CodeGenerator {
|
|||
private func generateEnum(_ enm: Enumeration) -> String {
|
||||
var swift = ""
|
||||
swift += Self.formatDocComment(enm.doc)
|
||||
swift += "public enum \(enm.name): Int {\n"
|
||||
swift += "public enum \(enm.name): Int, Sendable {\n"
|
||||
for member in enm.members {
|
||||
let caseName = Self.swiftifyEnumCaseName(member.name)
|
||||
let formattedValue = Self.formatNumericLiteral(member.value)
|
||||
|
|
@ -539,7 +432,7 @@ public struct CodeGenerator {
|
|||
private func generateBitfield(_ bf: Bitfield) -> String {
|
||||
var swift = ""
|
||||
swift += Self.formatDocComment(bf.doc)
|
||||
swift += "public struct \(bf.name): OptionSet {\n"
|
||||
swift += "public struct \(bf.name): OptionSet, Sendable {\n"
|
||||
swift += " public let rawValue: Int\n"
|
||||
swift += " public init(rawValue: Int) { self.rawValue = rawValue }\n\n"
|
||||
for member in bf.members {
|
||||
|
|
@ -606,14 +499,31 @@ public struct CodeGenerator {
|
|||
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)"
|
||||
let cType = Self.callbackParamType(p.type)
|
||||
return "_ \(Self.swiftifyParameterName(p.name)): \(cType)"
|
||||
}.joined(separator: ", ")
|
||||
let returnType = cb.returnType == .void ? "Void" : Self.typeToSwift(cb.returnType)
|
||||
result += "public typealias \(cb.name) = @convention(c) (\(paramList)) -> \(returnType)\n"
|
||||
return result
|
||||
}
|
||||
|
||||
/// Maps a GIR type to the corresponding Swift type for `@convention(c)` callback parameters.
|
||||
/// GObject type references become `UnsafeMutableRawPointer` since C callbacks
|
||||
/// pass raw pointers. All other types pass through `typeToSwift` unchanged.
|
||||
private static func callbackParamType(_ type: GIRType) -> String {
|
||||
switch type {
|
||||
case .typeRef:
|
||||
return "UnsafeMutableRawPointer"
|
||||
case .optional(let inner):
|
||||
if case .typeRef = inner {
|
||||
return "UnsafeMutableRawPointer?"
|
||||
}
|
||||
return typeToSwift(type)
|
||||
default:
|
||||
return typeToSwift(type)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Global Function Generation
|
||||
|
||||
/// Generates a Swift free function from a GIR global function definition.
|
||||
|
|
@ -627,7 +537,7 @@ public struct CodeGenerator {
|
|||
/// - 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 {
|
||||
private func generateGlobalFunction(_ fn: GlobalFunction, namespace: String, classTypeNames: Set<String> = []) -> String {
|
||||
let params = Self.nonVarargParameters(fn.parameters)
|
||||
let paramList = params.map { p -> String in
|
||||
"\(Self.swiftifyParameterName(p.name)): \(Self.typeToSwift(p.type))"
|
||||
|
|
@ -636,7 +546,7 @@ public struct CodeGenerator {
|
|||
let returnStmt = fn.returnType == .void ? "" : "return "
|
||||
|
||||
let args = params.map { p -> String in
|
||||
Self.cParameterExpression(name: Self.swiftifyParameterName(p.name), type: p.type)
|
||||
Self.cParameterExpression(name: Self.swiftifyParameterName(p.name), type: p.type, classTypeNames: classTypeNames)
|
||||
}.joined(separator: ", ")
|
||||
let cCall = "\(fn.cIdentifier)(\(args))"
|
||||
let wrappedReturn = Self.wrapCReturnValue(callExpression: cCall, returnType: fn.returnType)
|
||||
|
|
@ -648,21 +558,6 @@ public struct CodeGenerator {
|
|||
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
|
||||
|
|
@ -808,121 +703,6 @@ public struct CodeGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
// 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 parameters, the C callback receives them as
|
||||
/// `UnsafeMutableRawPointer?` values which are extracted into Swift types.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - signal: The GIR signal to generate.
|
||||
/// - inhibit: If `true`, the handler returns `Bool` to control signal propagation.
|
||||
/// - Returns: A signal connection method declaration as a string.
|
||||
public static func generateSignalConnection(signal: Signal, inhibit: Bool = false) -> String {
|
||||
let signalName = swiftifySignalName(signal.name)
|
||||
let handlerReturnType = inhibit ? "Bool" : "Void"
|
||||
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 {
|
||||
let callbackReturn = inhibit ? " -> gboolean" : ""
|
||||
let handlerCall = inhibit ? "let result = stored(); return result ? 1 : 0" : "stored()"
|
||||
return """
|
||||
public func connect\(signalName)(_ handler: @escaping () -> \(handlerReturnType)) -> Int {
|
||||
let boxed = Unmanaged.passRetained(handler as AnyObject).toOpaque()
|
||||
let callback: @convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?)\(callbackReturn) = { (_, data) in
|
||||
let stored = Unmanaged<AnyObject>.fromOpaque(data!).takeUnretainedValue() as! () -> \(handlerReturnType)
|
||||
\(handlerCall)
|
||||
}
|
||||
let destroy: @convention(c) (UnsafeMutableRawPointer?) -> Void = { Unmanaged<AnyObject>.fromOpaque($0!).release() }
|
||||
return Int(g_signal_connect_data(pointer, "\(signal.name)", callback, boxed, destroy, 0))
|
||||
}
|
||||
|
||||
|
||||
"""
|
||||
} else {
|
||||
let rawPointerParams = signal.parameters.map { _ in "UnsafeMutableRawPointer?" }.joined(separator: ", ")
|
||||
let rawPointerNames = signal.parameters.enumerated().map { "p\($0.offset)" }.joined(separator: ", ")
|
||||
let extractionExprs = signal.parameters.enumerated().map { i, p in
|
||||
cSignalParameterExtraction(index: i, type: p.type)
|
||||
}.joined(separator: ", ")
|
||||
let callbackReturn = inhibit ? " -> gboolean" : ""
|
||||
let handlerCall = inhibit
|
||||
? "let result = stored(\(extractionExprs)); return result ? 1 : 0"
|
||||
: "stored(\(extractionExprs))"
|
||||
return """
|
||||
public func connect\(signalName)(_ handler: @escaping (\(handlerArgs)) -> \(handlerReturnType)) -> Int {
|
||||
let boxed = Unmanaged.passRetained(handler as AnyObject).toOpaque()
|
||||
let callback: @convention(c) (\(rawPointerParams), UnsafeMutableRawPointer?)\(callbackReturn) = { (\(rawPointerNames), data) in
|
||||
let stored = Unmanaged<AnyObject>.fromOpaque(data!).takeUnretainedValue() as! (\(handlerArgs)) -> \(handlerReturnType)
|
||||
\(handlerCall)
|
||||
}
|
||||
let destroy: @convention(c) (UnsafeMutableRawPointer?) -> Void = { Unmanaged<AnyObject>.fromOpaque($0!).release() }
|
||||
return Int(g_signal_connect_data(pointer, "\(signal.name)"\(detailClause), callback, boxed, destroy, 0))
|
||||
}
|
||||
|
||||
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a Swift expression to extract a signal parameter value from an
|
||||
/// `UnsafeMutableRawPointer?` received in the C callback trampoline.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - index: The zero-based index of the signal parameter.
|
||||
/// - type: The GIR type of the parameter.
|
||||
/// - Returns: A Swift expression string that converts `p{index}` to the target type.
|
||||
private static func cSignalParameterExtraction(index: Int, type: GIRType) -> String {
|
||||
let p = "p\(index)"
|
||||
switch type {
|
||||
case .boolean:
|
||||
return "Int(bitPattern: \(p)) != 0"
|
||||
case .int8:
|
||||
return "Int8(bitPattern: UInt8(bitPattern: Int8(truncatingIfNeeded: Int(bitPattern: \(p)))))"
|
||||
case .int16:
|
||||
return "Int16(bitPattern: UInt16(bitPattern: Int16(truncatingIfNeeded: Int(bitPattern: \(p)))))"
|
||||
case .int32:
|
||||
return "Int32(bitPattern: Int32(truncatingIfNeeded: Int(bitPattern: \(p))))"
|
||||
case .int64:
|
||||
return "\(p)!.load(as: Int64.self)"
|
||||
case .uint8:
|
||||
return "UInt8(bitPattern: UInt8(truncatingIfNeeded: Int(bitPattern: \(p))))"
|
||||
case .uint16:
|
||||
return "UInt16(bitPattern: UInt16(truncatingIfNeeded: Int(bitPattern: \(p))))"
|
||||
case .uint32:
|
||||
return "UInt32(bitPattern: UInt32(truncatingIfNeeded: Int(bitPattern: \(p))))"
|
||||
case .uint64:
|
||||
return "\(p)!.load(as: UInt64.self)"
|
||||
case .float:
|
||||
return "\(p)!.load(as: Float.self)"
|
||||
case .double:
|
||||
return "\(p)!.load(as: Double.self)"
|
||||
case .string, .filename:
|
||||
return "String(cString: \(p)!.assumingMemoryBound(to: CChar.self))"
|
||||
case .typeRef(let name, _):
|
||||
return "\(name)(pointer: \(p)!)"
|
||||
case .pointer:
|
||||
return "\(p)!"
|
||||
case .optional(let inner):
|
||||
if case .typeRef(let name, _) = inner {
|
||||
return "\(p).map { \(name)(pointer: $0) }"
|
||||
}
|
||||
return cSignalParameterExtraction(index: index, type: inner)
|
||||
default:
|
||||
return "\(p)!"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Constructor Generation
|
||||
|
||||
/// Generates a Swift convenience initializer for a GObject constructor.
|
||||
|
|
@ -937,13 +717,13 @@ public struct CodeGenerator {
|
|||
/// - 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 {
|
||||
public static func generateConstructor(constructor: Constructor, className: String, classTypeNames: Set<String>? = nil) -> 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)
|
||||
Self.cParameterExpression(name: Self.swiftifyParameterName(p.name), type: p.type, classTypeNames: classTypeNames)
|
||||
}.joined(separator: ", ")
|
||||
|
||||
return """
|
||||
|
|
@ -959,11 +739,11 @@ public struct CodeGenerator {
|
|||
|
||||
// MARK: - C Function Call Generation
|
||||
|
||||
public static func generateCFunctionCall(method: Method, instancePointer: String) -> String {
|
||||
public static func generateCFunctionCall(method: Method, instancePointer: String, classTypeNames: Set<String>? = nil) -> 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)
|
||||
return cParameterExpression(name: Self.swiftifyParameterName(p.name), type: p.type, classTypeNames: classTypeNames)
|
||||
}.joined(separator: ", ")
|
||||
|
||||
let cFuncCall = "\(method.cIdentifier)(\(args))"
|
||||
|
|
@ -973,20 +753,39 @@ public struct CodeGenerator {
|
|||
/// 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).
|
||||
/// GObject pointers with optional chaining. All other types (enums,
|
||||
/// bitfields, records) pass through as-is since they are value types
|
||||
/// without a `.pointer` property.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - name: The Swift parameter name.
|
||||
/// - type: The GIR type of the parameter.
|
||||
/// - classTypeNames: The set of class type names that have a `.pointer`
|
||||
/// property. Types NOT in this set are passed directly.
|
||||
/// - Returns: A Swift expression string for the C function argument.
|
||||
private static func cParameterExpression(name: String, type: GIRType) -> String {
|
||||
private static func cParameterExpression(name: String, type: GIRType, classTypeNames: Set<String>? = nil) -> String {
|
||||
let usesPointer: Bool
|
||||
switch type {
|
||||
case .typeRef(let refName, _):
|
||||
// When classTypeNames is nil, default to .pointer (backward compatible).
|
||||
// When set, only use .pointer for types in the set.
|
||||
usesPointer = classTypeNames.map { $0.contains(refName) } ?? true
|
||||
case .optional(let inner):
|
||||
if case .typeRef(let refName, _) = inner {
|
||||
usesPointer = classTypeNames.map { $0.contains(refName) } ?? true
|
||||
} else {
|
||||
usesPointer = false
|
||||
}
|
||||
default:
|
||||
usesPointer = false
|
||||
}
|
||||
|
||||
switch type {
|
||||
case .typeRef:
|
||||
return "\(name).pointer"
|
||||
return usesPointer ? "\(name).pointer" : name
|
||||
case .optional(let inner):
|
||||
if case .typeRef = inner {
|
||||
return "\(name)?.pointer"
|
||||
return usesPointer ? "\(name)?.pointer" : name
|
||||
}
|
||||
return name
|
||||
default:
|
||||
|
|
@ -1082,7 +881,7 @@ public struct CodeGenerator {
|
|||
///
|
||||
/// - Parameter type: The GIR type to map.
|
||||
/// - Returns: The Swift type name as a string.
|
||||
private static func typeToSwift(_ type: GIRType) -> String {
|
||||
static func typeToSwift(_ type: GIRType) -> String {
|
||||
switch type {
|
||||
case .void: return "Void"
|
||||
case .boolean: return "Bool"
|
||||
|
|
@ -1160,7 +959,7 @@ public struct CodeGenerator {
|
|||
///
|
||||
/// - Parameter name: The kebab-case GIR signal name.
|
||||
/// - Returns: A PascalCase Swift method name component.
|
||||
private static func swiftifySignalName(_ name: String) -> String {
|
||||
static func swiftifySignalName(_ name: String) -> String {
|
||||
name.split(separator: "-").map { $0.capitalized }.joined()
|
||||
}
|
||||
|
||||
|
|
@ -1219,7 +1018,7 @@ public struct CodeGenerator {
|
|||
/// - namespace: The GIR namespace the record belongs to.
|
||||
/// - analysis: The analysis result containing per-type overrides.
|
||||
/// - Returns: A Swift struct declaration as a string.
|
||||
private func generateRecord(_ rec: Record, namespace: String, analysis: AnalysisResult) -> String {
|
||||
private func generateRecord(_ rec: Record, namespace: String, analysis: AnalysisResult, classTypeNames: Set<String> = []) -> String {
|
||||
var swift = ""
|
||||
swift += Self.formatDocComment(rec.doc)
|
||||
|
||||
|
|
@ -1227,7 +1026,7 @@ public struct CodeGenerator {
|
|||
let concurrency = override?.concurrency ?? .none
|
||||
if concurrency == .mainActor { swift += "@MainActor " }
|
||||
|
||||
swift += "public struct \(rec.name) {\n"
|
||||
swift += "public struct \(rec.name): Sendable {\n"
|
||||
|
||||
for field in rec.fields {
|
||||
swift += Self.formatDocComment(field.doc, indentation: 4)
|
||||
|
|
@ -1243,7 +1042,7 @@ public struct CodeGenerator {
|
|||
let returnTypeStr = method.returnType == .void ? "" : " -> \(Self.typeToSwift(method.returnType))"
|
||||
let returnStmt = method.returnType == .void ? "" : "return "
|
||||
let funcName = Self.swiftifyMethodName(method.name)
|
||||
let cCall = Self.generateCFunctionCall(method: method, instancePointer: "&self")
|
||||
let cCall = Self.generateCFunctionCall(method: method, instancePointer: "&self", classTypeNames: classTypeNames)
|
||||
swift += " public mutating func \(funcName)(\(paramList))\(returnTypeStr) {\n"
|
||||
swift += " \(returnStmt)\(cCall)\n"
|
||||
swift += " }\n\n"
|
||||
|
|
@ -1260,7 +1059,16 @@ public struct CodeGenerator {
|
|||
private func generateConstant(_ cst: Constant) -> String {
|
||||
var swift = Self.formatDocComment(cst.doc)
|
||||
let typeName = Self.typeToSwift(cst.type)
|
||||
swift += "public let \(Self.swiftifyPropertyName(cst.name)): \(typeName) = \(cst.value)\n"
|
||||
let name = Self.swiftifyPropertyName(cst.name)
|
||||
let escapedName = Self.reservedKeywords.contains(name) ? "`\(name)`" : name
|
||||
let formattedValue: String
|
||||
switch cst.type {
|
||||
case .string, .filename:
|
||||
formattedValue = "\"\(cst.value)\""
|
||||
default:
|
||||
formattedValue = cst.value
|
||||
}
|
||||
swift += "public let \(escapedName): \(typeName) = \(formattedValue)\n"
|
||||
return swift
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue