spb/metrika Public
Stata-class statistics, GPU-accelerated by Apple Silicon. Native Swift — no Electron, no Python runtime, no compromises.
Swift 92.4%
HTML 3.3%
R 3%
Shell 1.3%
1//2// DTAFormat.swift3// Metrika4//5// Author: Simon-Pierre Boucher6// Contact: contact@spboucher.ai7// Copyright © 2026 Simon-Pierre Boucher. All rights reserved.8//910import Foundation1112/// Shared constants and byte-cursor plumbing for the native Stata .dta13/// reader/writer (formats 117–119, CLAUDE.md §6).14enum DTAFormat {15 /// Variable type codes (dta 117+).16 static let typeStrL: UInt16 = 3276817 static let typeDouble: UInt16 = 6552618 static let typeFloat: UInt16 = 6552719 static let typeLong: UInt16 = 6552820 static let typeInt: UInt16 = 6552921 static let typeByte: UInt16 = 6553022 static let maxFixedStringLength = 20452324 /// Smallest value treated as missing, per numeric storage type.25 /// Stata reserves the top of each range for `.` and `.a`–`.z`; the26 /// distinctions collapse into one missing state here.27 static let doubleMissingThreshold = 8.988465674311579e30728 static let floatMissingThreshold: Float = 1.701e3829 static let longMissingThreshold: Int32 = 2_147_483_62130 static let intMissingThreshold: Int16 = 3274131 static let byteMissingThreshold: Int8 = 1013233 /// Canonical `.` (system missing) for doubles: +2¹⁰²³.34 static let doubleMissingValue = Double(bitPattern: 0x7FE0_0000_0000_0000)3536 /// Per-variable field widths that differ across format versions.37 struct Layout {38 let release: Int39 let variableCountBytes: Int // K40 let observationCountBytes: Int// N41 let nameBytes: Int42 let formatBytes: Int43 let labelNameBytes: Int44 let variableLabelBytes: Int45 let sortEntryBytes: Int46 let datasetLabelLengthBytes: Int47 let strLVarBytes: Int // v in a data-section strL cell48 let strLObsBytes: Int // o in a data-section strL cell49 let gsoObsBytes: Int // o in a GSO record5051 init?(release: Int) {52 self.release = release53 switch release {54 case 117:55 variableCountBytes = 256 observationCountBytes = 457 nameBytes = 3358 formatBytes = 4959 labelNameBytes = 3360 variableLabelBytes = 8161 sortEntryBytes = 262 datasetLabelLengthBytes = 163 strLVarBytes = 464 strLObsBytes = 465 gsoObsBytes = 466 case 118:67 variableCountBytes = 268 observationCountBytes = 869 nameBytes = 12970 formatBytes = 5771 labelNameBytes = 12972 variableLabelBytes = 32173 sortEntryBytes = 274 datasetLabelLengthBytes = 275 strLVarBytes = 276 strLObsBytes = 677 gsoObsBytes = 878 case 119:79 variableCountBytes = 480 observationCountBytes = 881 nameBytes = 12982 formatBytes = 5783 labelNameBytes = 12984 variableLabelBytes = 32185 sortEntryBytes = 486 datasetLabelLengthBytes = 287 strLVarBytes = 488 strLObsBytes = 689 gsoObsBytes = 890 default:91 return nil92 }93 }94 }95}9697/// Sequential binary cursor with endian-aware integer reads.98struct DTACursor {99 let data: Data100 var offset: Int = 0101 var bigEndian = false102103 init(data: Data) { self.data = data }104105 var remaining: Int { data.count - offset }106107 mutating func readBytes(_ count: Int) throws(ZQDataError) -> Data {108 guard count >= 0, offset + count <= data.count else {109 throw ZQDataError("corrupt .dta: unexpected end of file at offset \(offset)")110 }111 let slice = data.subdata(in: offset..<(offset + count))112 offset += count113 return slice114 }115116 /// Consumes an exact ASCII tag or fails with its name.117 mutating func expect(_ tag: String) throws(ZQDataError) {118 let expected = Data(tag.utf8)119 let actual = try readBytes(expected.count)120 guard actual == expected else {121 throw ZQDataError(122 "corrupt .dta: expected '\(tag)' at offset \(offset - expected.count)"123 )124 }125 }126127 mutating func readUInt(_ byteCount: Int) throws(ZQDataError) -> UInt64 {128 let bytes = try readBytes(byteCount)129 var value: UInt64 = 0130 if bigEndian {131 for byte in bytes { value = value << 8 | UInt64(byte) }132 } else {133 for byte in bytes.reversed() { value = value << 8 | UInt64(byte) }134 }135 return value136 }137138 mutating func readDouble() throws(ZQDataError) -> Double {139 Double(bitPattern: try readUInt(8))140 }141142 mutating func readFloat() throws(ZQDataError) -> Float {143 Float(bitPattern: UInt32(truncatingIfNeeded: try readUInt(4)))144 }145146 /// Fixed-width, null-padded text field.147 mutating func readPaddedString(148 _ width: Int, release: Int149 ) throws(ZQDataError) -> String {150 let bytes = try readBytes(width)151 let trimmed = bytes.prefix { $0 != 0 }152 if release >= 118 {153 return String(data: trimmed, encoding: .utf8) ?? ""154 }155 return String(data: trimmed, encoding: .isoLatin1) ?? ""156 }157}158