import Foundation public struct ModuleSymbolBundle: Sendable { public var exports: [Export] public var imports: [Import] public var symbols: [Symbol] public init(exports: [Export] = [], imports: [Import] = [], symbols: [Symbol] = []) { self.exports = exports self.imports = imports self.symbols = symbols } public enum SymbolKind: String, Sendable, Hashable { case function case variable } public struct Export: Sendable, Hashable, Identifiable { public var id: String { "\(name)@0x\(String(address, radix: 16))" } public let kind: SymbolKind public let name: String public let address: UInt64 public init(kind: SymbolKind, name: String, address: UInt64) { self.kind = kind self.name = name self.address = address } } public struct Import: Sendable, Hashable, Identifiable { public var id: String { "\(module ?? "?")!\(name)@0x\(String(address ?? 0, radix: 16))" } public let kind: SymbolKind? public let name: String public let module: String? public let address: UInt64? public let slot: UInt64? public init(kind: SymbolKind?, name: String, module: String?, address: UInt64?, slot: UInt64?) { self.kind = kind self.name = name self.module = module self.address = address self.slot = slot } } public struct Symbol: Sendable, Hashable, Identifiable { public var id: String { "\(name)@0x\(String(address, radix: 16))" } public let name: String public let type: String public let address: UInt64 public let isGlobal: Bool public let size: Int? public let sectionID: String? public let sectionProtection: String? public init( name: String, type: String, address: UInt64, isGlobal: Bool, size: Int?, sectionID: String?, sectionProtection: String? ) { self.name = name self.type = type self.address = address self.isGlobal = isGlobal self.size = size self.sectionID = sectionID self.sectionProtection = sectionProtection } public var isCode: Bool { if type == "undefined" { return false } if type == "function" { return true } if let prot = sectionProtection, prot.contains("x") { return true } return false } public var isData: Bool { type == "object" || type == "common" || type == "tls" } } } extension ModuleSymbolBundle { static func parseAddress(_ s: String?) -> UInt64? { guard let s else { return nil } let trimmed = s.hasPrefix("0x") ? String(s.dropFirst(2)) : s return UInt64(trimmed, radix: 16) } static func decodeExport(_ dict: [String: Any]) -> Export? { guard let typeRaw = dict["type"] as? String, let kind = SymbolKind(rawValue: typeRaw), let name = dict["name"] as? String, let address = parseAddress(dict["address"] as? String) else { return nil } return Export(kind: kind, name: name, address: address) } static func decodeImport(_ dict: [String: Any]) -> Import? { guard let name = dict["name"] as? String else { return nil } let kind = (dict["type"] as? String).flatMap(SymbolKind.init(rawValue:)) return Import( kind: kind, name: name, module: dict["module"] as? String, address: parseAddress(dict["address"] as? String), slot: parseAddress(dict["slot"] as? String) ) } static func decodeSymbol(_ dict: [String: Any]) -> Symbol? { guard let name = dict["name"] as? String, let type = dict["type"] as? String, let address = parseAddress(dict["address"] as? String), let isGlobal = dict["isGlobal"] as? Bool else { return nil } return Symbol( name: name, type: type, address: address, isGlobal: isGlobal, size: dict["size"] as? Int, sectionID: dict["sectionID"] as? String, sectionProtection: dict["sectionProtection"] as? String ) } public static func fromJSON(_ obj: [String: Any]) -> ModuleSymbolBundle { let exportDicts = (obj["exports"] as? [[String: Any]]) ?? [] let importDicts = (obj["imports"] as? [[String: Any]]) ?? [] let symbolDicts = (obj["symbols"] as? [[String: Any]]) ?? [] return ModuleSymbolBundle( exports: exportDicts.compactMap(decodeExport), imports: importDicts.compactMap(decodeImport), symbols: symbolDicts.compactMap(decodeSymbol) ) } } public enum ModuleSymbolCategory: String, Sendable { case exports case imports case symbols } public struct ModuleSymbolPage: Sendable { public static let pageSize = 1000 public let rows: Rows public let matched: Int public let offset: Int public let counts: Counts public var count: Int { switch rows { case .exports(let r): return r.count case .imports(let r): return r.count case .symbols(let r): return r.count } } public var hasPrevious: Bool { offset > 0 } public var hasNext: Bool { offset + count < matched } public enum Rows: Sendable { case exports([ModuleSymbolBundle.Export]) case imports([ModuleSymbolBundle.Import]) case symbols([ModuleSymbolBundle.Symbol]) } public struct Counts: Sendable { public let exports: Int public let imports: Int public let symbols: Int public subscript(_ category: ModuleSymbolCategory) -> Int { switch category { case .exports: return exports case .imports: return imports case .symbols: return symbols } } } public static func fromJSON(_ obj: [String: Any], category: ModuleSymbolCategory) -> ModuleSymbolPage { let rowDicts = (obj["rows"] as? [[String: Any]]) ?? [] let rows: Rows switch category { case .exports: rows = .exports(rowDicts.compactMap(ModuleSymbolBundle.decodeExport)) case .imports: rows = .imports(rowDicts.compactMap(ModuleSymbolBundle.decodeImport)) case .symbols: rows = .symbols(rowDicts.compactMap(ModuleSymbolBundle.decodeSymbol)) } let countsObj = (obj["counts"] as? [String: Any]) ?? [:] let counts = Counts( exports: countsObj["exports"] as? Int ?? 0, imports: countsObj["imports"] as? Int ?? 0, symbols: countsObj["symbols"] as? Int ?? 0 ) return ModuleSymbolPage( rows: rows, matched: obj["matched"] as? Int ?? rowDicts.count, offset: obj["offset"] as? Int ?? 0, counts: counts ) } }