1
0
Fork 0
gobject-generator/Sources/GObjectGeneratorCore/XMLParser.swift

771 lines
33 KiB
Swift

// XMLParser.swift
// GIR XML IR. The delegate maintains an explicit element stack so that every
// element attaches to its true parent, and nested constructs (container element
// types, callback-typed parameters, virtual-method bodies) nest correctly
// instead of leaking into whatever `current*` slot happened to be set.
import Foundation
#if canImport(FoundationXML)
import FoundationXML
#endif
/// Errors that can occur during GIR XML parsing.
public enum GIRParserError: Error {
/// The XML document is malformed or could not be parsed.
/// - Parameter description: A human-readable error description.
case invalidXML(String)
/// An unexpected XML element was encountered.
/// - Parameter description: Details about the unexpected element.
case unexpectedElement(String)
/// A required attribute is missing from an XML element.
/// - Parameter description: Description of the missing attribute.
case missingAttribute(String)
}
/// Parses GIR XML files into the intermediate representation model.
///
/// Uses Foundation's `XMLParser` in a SAX-style delegate pattern, driving an
/// explicit element stack. All GIR element types are handled: classes,
/// interfaces, records, enumerations, bitfields, callbacks, methods,
/// properties, signals, functions, constants, and aliases.
///
/// The parser is deliberately non-judgemental: it records what the GIR says,
/// including symbols that cannot be bound. Deciding what to *bind* is the
/// planner's job, which reports skips with reasons.
///
/// ### Example
/// ```swift
/// let parser = GIRParser()
/// let repo = try parser.parse(fileURL: URL(fileURLWithPath: "/usr/share/gir-1.0/GLib-2.0.gir"))
/// print(repo.namespaces.first?.classes.count ?? 0)
/// ```
public struct GIRParser {
/// Creates a new GIR parser.
public init() {}
/// Parses a GIR XML document from a string.
/// - Parameter xmlString: The XML content to parse.
/// - Returns: A `Repository` containing all parsed namespaces and types.
/// - Throws: `GIRParserError` if the XML cannot be encoded as UTF-8 or parsing fails.
public func parse(xmlString: String) throws -> Repository {
guard let data = xmlString.data(using: .utf8) else {
throw GIRParserError.invalidXML("Failed to encode XML string as UTF-8")
}
return try parse(data: data)
}
/// Parses a GIR XML document from a file URL.
/// - Parameter fileURL: The URL of the .gir file to parse.
/// - Returns: A `Repository` containing all parsed namespaces and types.
/// - Throws: `GIRParserError` if the file cannot be read or parsing fails.
public func parse(fileURL: URL) throws -> Repository {
let data = try Data(contentsOf: fileURL)
return try parse(data: data)
}
/// Parses raw XML data using a SAX-style delegate.
/// - Parameter data: The XML data to parse.
/// - Returns: A `Repository` with the parsed content.
/// - Throws: `GIRParserError` if parsing fails.
private func parse(data: Data) throws -> Repository {
let delegate = GIRXMLDelegate()
let parser = XMLParser(data: data)
parser.delegate = delegate
if parser.parse(), delegate.parseError == nil {
return delegate.repository
}
throw delegate.parseError ?? GIRParserError.invalidXML(parser.parserError?.localizedDescription ?? "unknown error")
}
}
// MARK: - Element Stack
/// A partially-built `<type>` element.
///
/// Nested `<type>` children supply container element types, e.g.
/// `<type name="GLib.List"><type name="utf8"/></type>`.
struct TypeBuilder {
/// The GIR type name attribute, e.g. `"utf8"` or `"GLib.List"`.
var name: String
/// The `c:type` attribute, e.g. `"gchar*"`.
var cType: String
/// Element types collected from nested `<type>` children.
var children: [GIRType] = []
}
/// A partially-built `<array>` element.
struct ArrayBuilder {
/// The array's `name` attribute; set for GLib container arrays.
var name: String?
/// Length metadata gathered from the element's attributes.
var info: ArrayInfo
/// The element type, from the nested `<type>` or `<array>` child.
var children: [GIRType] = []
}
/// One open XML element during parsing.
///
/// The delegate pushes a frame on every element it tracks and pops it on close,
/// attaching the completed value to the frame beneath. Untracked elements push
/// ``ignored`` so that their children attach to nothing rather than leaking into
/// an ancestor.
indirect enum Frame {
case namespace(Namespace)
case klass(Class)
case interface(Interface)
case record(Record)
case enumeration(Enumeration)
case bitfield(Bitfield)
case callback(Callback)
case constructor(Constructor)
case method(Method)
case function(GlobalFunction)
case signal(Signal)
case property(Property)
case parameterList([Parameter])
case parameter(Parameter)
case returnValue(ReturnValue)
case field(Field)
case constant(Constant)
case alias(Alias)
case type(TypeBuilder)
case array(ArrayBuilder)
case doc
/// An element whose content is deliberately discarded (virtual methods,
/// unions, source positions, and anything else not modelled).
case ignored(String)
}
// MARK: - XMLParser Delegate
/// SAX-style delegate for `XMLParser` that builds a `Repository` from GIR XML.
///
/// Maintains an explicit ``Frame`` stack: `didStartElement` pushes a frame,
/// `didEndElement` pops it and attaches the finished value to its parent. This
/// makes parent-child relationships exact, which flat `current*` slots could not
/// express for nested constructs.
final class GIRXMLDelegate: NSObject, XMLParserDelegate {
/// The repository being populated during parsing.
var repository = Repository()
/// Set to the first error encountered, or `nil` if parsing succeeds.
var parseError: GIRParserError?
/// The stack of currently-open elements, outermost first.
private var stack: [Frame] = []
/// Accumulated character data for the innermost `<doc>` element.
private var currentText: String = ""
// MARK: Element start
func parser(_ parser: XMLParser, didStartElement elementName: String,
namespaceURI: String?, qualifiedName: String?,
attributes attributeDict: [String: String] = [:]) {
switch elementName {
case "c:include":
if let name = attributeDict["name"] { repository.cHeaderPath = name }
stack.append(.ignored(elementName))
case "package":
if let name = attributeDict["name"] { repository.packageName = name }
stack.append(.ignored(elementName))
case "include":
// GIR dependency includes appear before the <namespace> element.
// Derive the link name as lowercase(library) + "-" + major version,
// e.g. "Gdk-4.0" "gdk-4".
if let name = attributeDict["name"], let version = attributeDict["version"] {
let majorVersion = version.split(separator: ".").first.map(String.init) ?? version
repository.includedLibraryLinks.append("\(name.lowercased())-\(majorVersion)")
repository.includedPackages.append(IncludeEntry(name: name, version: version))
}
stack.append(.ignored(elementName))
case "namespace":
guard let name = requireAttribute("name", from: attributeDict, for: elementName, parser: parser),
let version = requireAttribute("version", from: attributeDict, for: elementName, parser: parser) else { return }
stack.append(.namespace(Namespace(
name: name, version: version,
cSharedLibrary: attributeDict["shared-library"] ?? "",
cIdentifierPrefix: attributeDict["c:identifier-prefixes"] ?? ""
)))
case "class":
guard let name = requireAttribute("name", from: attributeDict, for: elementName, parser: parser) else { return }
stack.append(.klass(Class(
name: name,
cType: attributeDict["c:type"] ?? "",
parent: attributeDict["parent"],
isAbstract: attributeDict["abstract"] == "1",
isFinal: attributeDict["final"] == "1",
getTypeFunction: attributeDict["glib:get-type"],
typeName: attributeDict["glib:type-name"],
symbolInfo: Self.symbolInfo(from: attributeDict),
refFunc: attributeDict["glib:ref-func"],
unrefFunc: attributeDict["glib:unref-func"]
)))
case "interface":
guard let name = requireAttribute("name", from: attributeDict, for: elementName, parser: parser) else { return }
stack.append(.interface(Interface(
name: name,
cType: attributeDict["c:type"] ?? "",
getTypeFunction: attributeDict["glib:get-type"],
typeName: attributeDict["glib:type-name"],
symbolInfo: Self.symbolInfo(from: attributeDict)
)))
case "prerequisite":
// GIR spells prerequisites as child elements, not an attribute.
if let name = attributeDict["name"] {
mutateTop { if case .interface(var iface) = $0 { iface.prereqs.append(name); $0 = .interface(iface) } }
}
stack.append(.ignored(elementName))
case "record":
guard let name = attributeDict["name"] else {
// Anonymous nested <record> (a C struct inside a <union>, e.g.
// GskPathPoint). Not a namespace-level type; discard it and its
// fields record fields are deferred and the parent union is
// already .ignored.
stack.append(.ignored(elementName))
return
}
stack.append(.record(Record(
name: name,
cType: attributeDict["c:type"] ?? "",
isOpaque: attributeDict["opaque"] == "1",
isDisguised: attributeDict["disguised"] == "1",
isGTypeStructFor: attributeDict["glib:is-gtype-struct-for"],
getTypeFunction: attributeDict["glib:get-type"],
typeName: attributeDict["glib:type-name"],
copyFunction: attributeDict["copy-function"],
freeFunction: attributeDict["free-function"],
symbolInfo: Self.symbolInfo(from: attributeDict)
)))
case "enumeration":
guard let name = requireAttribute("name", from: attributeDict, for: elementName, parser: parser) else { return }
stack.append(.enumeration(Enumeration(
name: name,
cType: attributeDict["c:type"] ?? "",
getTypeFunction: attributeDict["glib:get-type"],
typeName: attributeDict["glib:type-name"],
errorDomain: attributeDict["glib:error-domain"],
symbolInfo: Self.symbolInfo(from: attributeDict)
)))
case "bitfield":
guard let name = requireAttribute("name", from: attributeDict, for: elementName, parser: parser) else { return }
stack.append(.bitfield(Bitfield(
name: name,
cType: attributeDict["c:type"] ?? "",
getTypeFunction: attributeDict["glib:get-type"],
typeName: attributeDict["glib:type-name"],
symbolInfo: Self.symbolInfo(from: attributeDict)
)))
case "member":
guard let name = requireAttribute("name", from: attributeDict, for: elementName, parser: parser),
let value = requireAttribute("value", from: attributeDict, for: elementName, parser: parser),
let cid = requireAttribute("c:identifier", from: attributeDict, for: elementName, parser: parser) else { return }
let member = EnumMember(name: name, value: value, cIdentifier: cid)
mutateTop {
switch $0 {
case .enumeration(var e): e.members.append(member); $0 = .enumeration(e)
case .bitfield(var b): b.members.append(member); $0 = .bitfield(b)
default: break
}
}
stack.append(.ignored(elementName))
case "callback":
guard let name = requireAttribute("name", from: attributeDict, for: elementName, parser: parser) else { return }
stack.append(.callback(Callback(
name: name,
cType: attributeDict["c:type"] ?? "",
throwsGError: attributeDict["throws"] == "1",
symbolInfo: Self.symbolInfo(from: attributeDict)
)))
case "constructor":
guard let name = requireAttribute("name", from: attributeDict, for: elementName, parser: parser),
let cid = requireAttribute("c:identifier", from: attributeDict, for: elementName, parser: parser) else { return }
stack.append(.constructor(Constructor(
name: name, cIdentifier: cid,
throwsGError: attributeDict["throws"] == "1",
symbolInfo: Self.symbolInfo(from: attributeDict)
)))
case "method":
guard let name = requireAttribute("name", from: attributeDict, for: elementName, parser: parser),
let cid = requireAttribute("c:identifier", from: attributeDict, for: elementName, parser: parser) else { return }
stack.append(.method(Method(
name: name, cIdentifier: cid,
throwsGError: attributeDict["throws"] == "1",
symbolInfo: Self.symbolInfo(from: attributeDict)
)))
case "function":
guard let name = requireAttribute("name", from: attributeDict, for: elementName, parser: parser),
let cid = requireAttribute("c:identifier", from: attributeDict, for: elementName, parser: parser) else { return }
stack.append(.function(GlobalFunction(
name: name, cIdentifier: cid,
throwsGError: attributeDict["throws"] == "1",
symbolInfo: Self.symbolInfo(from: attributeDict)
)))
case "signal", "glib:signal":
guard let name = attributeDict["name"] ?? attributeDict["glib:name"] else {
parseError = .missingAttribute("Missing 'name' on <\(elementName)>")
parser.abortParsing()
return
}
stack.append(.signal(Signal(
name: name,
isDetailed: attributeDict["detailed"] == "1",
symbolInfo: Self.symbolInfo(from: attributeDict)
)))
case "property":
guard let name = requireAttribute("name", from: attributeDict, for: elementName, parser: parser) else { return }
stack.append(.property(Property(
name: name, type: .void,
isReadable: attributeDict["readable"] != "0",
isWritable: attributeDict["writable"] == "1",
isConstructOnly: attributeDict["construct-only"] == "1",
isNullable: attributeDict["nullable"] == "1",
transferOwnership: TransferOwnership(rawValue: attributeDict["transfer-ownership"] ?? "none") ?? .none,
getter: attributeDict["getter"],
setter: attributeDict["setter"],
symbolInfo: Self.symbolInfo(from: attributeDict)
)))
case "parameters":
stack.append(.parameterList([]))
case "parameter", "instance-parameter":
guard let name = requireAttribute("name", from: attributeDict, for: elementName, parser: parser) else { return }
stack.append(.parameter(Parameter(
name: name, type: .void,
cType: attributeDict["c:type"] ?? "",
isNullable: attributeDict["nullable"] == "1",
isOptional: attributeDict["optional"] == "1",
transferOwnership: TransferOwnership(rawValue: attributeDict["transfer-ownership"] ?? "none") ?? .none,
isInstanceParameter: elementName == "instance-parameter",
direction: ParameterDirection(rawValue: attributeDict["direction"] ?? "in") ?? .in,
callerAllocates: attributeDict["caller-allocates"] == "1",
scope: attributeDict["scope"].flatMap(CallbackScope.init(rawValue:)),
closureIndex: attributeDict["closure"].flatMap(Int.init),
destroyIndex: attributeDict["destroy"].flatMap(Int.init)
)))
case "return-value":
stack.append(.returnValue(ReturnValue(
type: .void,
isNullable: attributeDict["nullable"] == "1",
transferOwnership: TransferOwnership(rawValue: attributeDict["transfer-ownership"] ?? "none") ?? .none
)))
case "type":
stack.append(.type(TypeBuilder(
name: attributeDict["name"] ?? "none",
cType: attributeDict["c:type"] ?? ""
)))
case "array":
stack.append(.array(ArrayBuilder(
name: attributeDict["name"],
info: ArrayInfo(
lengthParameterIndex: attributeDict["length"].flatMap(Int.init),
fixedSize: attributeDict["fixed-size"].flatMap(Int.init),
isZeroTerminated: attributeDict["zero-terminated"].map { $0 == "1" }
?? (attributeDict["length"] == nil && attributeDict["fixed-size"] == nil),
cType: attributeDict["c:type"] ?? ""
)
)))
case "constant":
guard let name = requireAttribute("name", from: attributeDict, for: elementName, parser: parser),
let value = requireAttribute("value", from: attributeDict, for: elementName, parser: parser) else { return }
stack.append(.constant(Constant(name: name, value: value, type: .void)))
case "alias":
guard let name = requireAttribute("name", from: attributeDict, for: elementName, parser: parser),
let cType = requireAttribute("c:type", from: attributeDict, for: elementName, parser: parser) else { return }
stack.append(.alias(Alias(name: name, cType: cType, target: .void)))
case "implements":
if let name = attributeDict["name"] {
mutateTop { if case .klass(var cls) = $0 { cls.implements.append(name); $0 = .klass(cls) } }
}
stack.append(.ignored(elementName))
case "field":
guard let name = requireAttribute("name", from: attributeDict, for: elementName, parser: parser) else { return }
stack.append(.field(Field(
name: name, type: .void,
isReadable: attributeDict["readable"] != "0",
isWritable: attributeDict["writable"] == "1"
)))
case "doc":
currentText = ""
stack.append(.doc)
default:
stack.append(.ignored(elementName))
}
}
// MARK: Element end
func parser(_ parser: XMLParser, didEndElement elementName: String,
namespaceURI: String?, qualifiedName: String?) {
guard let frame = stack.popLast() else { return }
switch frame {
case .namespace(let ns):
repository.namespaces.append(ns)
case .klass(let cls):
mutateTop { if case .namespace(var ns) = $0 { ns.classes.append(cls); $0 = .namespace(ns) } }
case .interface(let iface):
mutateTop { if case .namespace(var ns) = $0 { ns.interfaces.append(iface); $0 = .namespace(ns) } }
case .record(let record):
mutateTop { if case .namespace(var ns) = $0 { ns.records.append(record); $0 = .namespace(ns) } }
case .enumeration(let e):
mutateTop { if case .namespace(var ns) = $0 { ns.enumerations.append(e); $0 = .namespace(ns) } }
case .bitfield(let b):
mutateTop { if case .namespace(var ns) = $0 { ns.bitfields.append(b); $0 = .namespace(ns) } }
case .callback(let cb):
// Namespace-level callbacks are bindable types in their own right.
// A callback nested in a <field> is a function-pointer member of a
// C struct (vtables such as GIOFuncs): at the ABI level that field
// is exactly an opaque pointer, which is what it is recorded as.
// Callbacks nested anywhere else describe a signature in place and
// are not separate declarations.
mutateTop {
switch $0 {
case .namespace(var ns): ns.callbacks.append(cb); $0 = .namespace(ns)
case .field(var f):
f = Field(name: f.name, type: .pointer, isReadable: f.isReadable,
isWritable: f.isWritable, doc: f.doc)
$0 = .field(f)
default: break
}
}
case .constructor(let ctor):
mutateTop {
switch $0 {
case .klass(var cls): cls.constructors.append(ctor); $0 = .klass(cls)
case .record(var rec): rec.constructors.append(ctor); $0 = .record(rec)
default: break
}
}
case .method(let method):
mutateTop {
switch $0 {
case .klass(var cls): cls.methods.append(method); $0 = .klass(cls)
case .interface(var iface): iface.methods.append(method); $0 = .interface(iface)
case .record(var rec): rec.methods.append(method); $0 = .record(rec)
default: break
}
}
case .function(let fn):
mutateTop {
switch $0 {
case .klass(var cls): cls.functions.append(fn); $0 = .klass(cls)
case .interface(var iface): iface.functions.append(fn); $0 = .interface(iface)
case .record(var rec): rec.functions.append(fn); $0 = .record(rec)
case .namespace(var ns): ns.functions.append(fn); $0 = .namespace(ns)
default: break
}
}
case .signal(let sig):
mutateTop {
switch $0 {
case .klass(var cls): cls.signals.append(sig); $0 = .klass(cls)
case .interface(var iface): iface.signals.append(sig); $0 = .interface(iface)
default: break
}
}
case .property(let prop):
mutateTop {
switch $0 {
case .klass(var cls): cls.properties.append(prop); $0 = .klass(cls)
case .interface(var iface): iface.properties.append(prop); $0 = .interface(iface)
default: break
}
}
case .parameterList(let params):
mutateTop {
switch $0 {
case .method(var m): m.parameters = params; $0 = .method(m)
case .constructor(var c): c.parameters = params; $0 = .constructor(c)
case .function(var f): f.parameters = params; $0 = .function(f)
case .signal(var s): s.parameters = params; $0 = .signal(s)
case .callback(var cb): cb.parameters = params; $0 = .callback(cb)
default: break
}
}
case .parameter(let param):
mutateTop { if case .parameterList(var list) = $0 { list.append(param); $0 = .parameterList(list) } }
case .returnValue(let rv):
mutateTop {
switch $0 {
case .method(var m): m.returnValue = rv; $0 = .method(m)
case .constructor(var c): c.returnValue = rv; $0 = .constructor(c)
case .function(var f): f.returnValue = rv; $0 = .function(f)
case .signal(var s): s.returnValue = rv; $0 = .signal(s)
case .callback(var cb): cb.returnValue = rv; $0 = .callback(cb)
default: break
}
}
case .field(let field):
mutateTop { if case .record(var rec) = $0 { rec.fields.append(field); $0 = .record(rec) } }
case .constant(let c):
mutateTop { if case .namespace(var ns) = $0 { ns.constants.append(c); $0 = .namespace(ns) } }
case .alias(let a):
mutateTop { if case .namespace(var ns) = $0 { ns.aliases.append(a); $0 = .namespace(ns) } }
case .type(let builder):
assignType(Self.buildType(from: builder), cType: builder.cType)
case .array(let builder):
let element = builder.children.first ?? .pointer
if let name = builder.name, let kind = Self.containerKind(forTypeName: name) {
assignType(.container(kind, elements: builder.children), cType: builder.info.cType)
} else {
assignType(.cArray(element, builder.info), cType: builder.info.cType)
}
case .doc:
assignDoc(currentText.trimmingCharacters(in: .whitespacesAndNewlines))
currentText = ""
case .ignored:
break
}
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
currentText += string
}
// MARK: - Attachment helpers
/// Mutates the innermost open frame in place.
///
/// - Parameter body: A closure receiving the top frame for mutation. Not
/// called when the stack is empty.
private func mutateTop(_ body: (inout Frame) -> Void) {
guard !stack.isEmpty else { return }
body(&stack[stack.count - 1])
}
/// Assigns a resolved type to whichever construct encloses it.
///
/// Handles every element that has a `<type>` or `<array>` child: parameters,
/// return values, properties, constants, aliases, record fields, and nested
/// container/array element types.
///
/// - Parameters:
/// - type: The resolved GIR type.
/// - cType: The `c:type` spelling, applied to parameters when non-empty.
private func assignType(_ type: GIRType, cType: String) {
mutateTop {
switch $0 {
case .parameter(var p):
p.type = type
if !cType.isEmpty { p.cType = cType }
$0 = .parameter(p)
case .returnValue(var rv):
rv.type = type
if !cType.isEmpty { rv.cType = cType }
$0 = .returnValue(rv)
case .property(var prop):
prop.type = type
$0 = .property(prop)
case .constant(var c):
c.type = type
$0 = .constant(c)
case .alias(var a):
a.target = type
$0 = .alias(a)
case .field(var f):
// Field is immutable in the IR, so rebuild it with the type
// that only becomes known when the child <type> closes.
f = Field(name: f.name, type: type, isReadable: f.isReadable,
isWritable: f.isWritable, doc: f.doc)
$0 = .field(f)
case .type(var builder):
builder.children.append(type)
$0 = .type(builder)
case .array(var builder):
builder.children.append(type)
$0 = .array(builder)
default:
break
}
}
}
/// Attaches documentation text to whichever construct encloses the `<doc>`.
///
/// - Parameter text: The trimmed documentation text; ignored when empty.
private func assignDoc(_ text: String) {
guard !text.isEmpty else { return }
mutateTop {
switch $0 {
case .klass(var x): x.doc = text; $0 = .klass(x)
case .interface(var x): x.doc = text; $0 = .interface(x)
case .record(var x): x.doc = text; $0 = .record(x)
case .enumeration(var x): x.doc = text; $0 = .enumeration(x)
case .bitfield(var x): x.doc = text; $0 = .bitfield(x)
case .callback(var x): x.doc = text; $0 = .callback(x)
case .constructor(var x): x.doc = text; $0 = .constructor(x)
case .method(var x): x.doc = text; $0 = .method(x)
case .function(var x): x.doc = text; $0 = .function(x)
case .signal(var x): x.doc = text; $0 = .signal(x)
case .property(var x): x.doc = text; $0 = .property(x)
case .parameter(var x): x.doc = text; $0 = .parameter(x)
case .returnValue(var x): x.doc = text; $0 = .returnValue(x)
case .constant(var x): x.doc = text; $0 = .constant(x)
case .alias(var x): x.doc = text; $0 = .alias(x)
case .field(var x):
x = Field(name: x.name, type: x.type, isReadable: x.isReadable,
isWritable: x.isWritable, doc: text)
$0 = .field(x)
default: break
}
}
}
/// Requires that an attribute exists, or aborts parsing with an error.
/// - Parameters:
/// - key: The attribute name to look up.
/// - dict: The attribute dictionary from the current XML element.
/// - element: The name of the XML element (for error messages).
/// - parser: The XML parser to abort on failure.
/// - Returns: The attribute value, or `nil` if the attribute is missing.
private func requireAttribute(_ key: String, from dict: [String: String], for element: String, parser: XMLParser) -> String? {
guard let value = dict[key] else {
parseError = .missingAttribute("Missing '\(key)' on <\(element)>")
parser.abortParsing()
return nil
}
return value
}
// MARK: - Type mapping
/// Extracts the binding-relevant GIR metadata common to all symbols.
///
/// - Parameter dict: The element's attribute dictionary.
/// - Returns: The parsed ``SymbolInfo``.
static func symbolInfo(from dict: [String: String]) -> SymbolInfo {
SymbolInfo(
isIntrospectable: dict["introspectable"] != "0",
isDeprecated: dict["deprecated"] == "1",
deprecatedVersion: dict["deprecated-version"],
shadowedBy: dict["shadowed-by"],
shadows: dict["shadows"],
movedTo: dict["moved-to"]
)
}
/// Builds a `GIRType` from a completed `<type>` element.
///
/// Container type names (`GLib.List`, `GLib.HashTable`, ) become
/// ``GIRType/container(_:elements:)`` carrying their nested element types;
/// everything else maps through ``girType(forName:)``.
///
/// - Parameter builder: The accumulated `<type>` element state.
/// - Returns: The resolved GIR type.
static func buildType(from builder: TypeBuilder) -> GIRType {
if let kind = containerKind(forTypeName: builder.name) {
return .container(kind, elements: builder.children)
}
return girType(forName: builder.name)
}
/// Maps a GIR container type name to its ``ContainerKind``.
///
/// - Parameter name: A GIR type name, e.g. `"GLib.List"`.
/// - Returns: The container kind, or `nil` if the name is not a container.
static func containerKind(forTypeName name: String) -> ContainerKind? {
switch name {
case "GLib.List": return .list
case "GLib.SList": return .slist
case "GLib.HashTable": return .hashTable
case "GLib.Array": return .array
case "GLib.PtrArray": return .ptrArray
case "GLib.ByteArray": return .byteArray
default: return nil
}
}
/// Maps a GIR type name string to the corresponding `GIRType` case.
///
/// Recognizes the full set of GLib primitive names and dotted
/// namespace-qualified references (e.g. `Gtk.Widget`). Unknown names become
/// an unqualified `.typeRef`, which the ``TypeRegistry`` later resolves
/// or reports as unresolvable, rather than fabricating a Swift type.
///
/// - Parameter name: The GIR type name (e.g. `"gint32"`, `"utf8"`, `"Gtk.Widget"`).
/// - Returns: The corresponding `GIRType` value.
static func girType(forName name: String) -> GIRType {
switch name {
case "none": return .void
case "gboolean": return .boolean
case "gint8": return .int8
case "gint16": return .int16
case "gint", "gint32": return .int32
case "gint64": return .int64
case "guint8": return .uint8
case "guint16": return .uint16
case "guint", "guint32": return .uint32
case "guint64": return .uint64
case "glong", "gintptr", "time_t": return .long
case "gulong", "guintptr": return .ulong
case "gsize": return .size
case "gssize", "goffset": return .ssize
case "gchar": return .char
case "gshort": return .int16
case "guchar": return .uchar
case "gushort": return .uint16
case "gunichar": return .unichar
case "gunichar2": return .uint16
case "GType": return .gtype
case "gfloat": return .float
case "gdouble": return .double
case "utf8": return .string
case "filename": return .filename
case "gpointer", "gconstpointer": return .pointer
case "va_list": return .vaList
default:
if let dotIndex = name.firstIndex(of: ".") {
let ns = String(name[..<dotIndex])
let type = String(name[name.index(after: dotIndex)...])
return .typeRef(type, namespace: ns)
}
return .typeRef(name)
}
}
}