249 lines
6.3 KiB
Swift
249 lines
6.3 KiB
Swift
import Foundation
|
|
|
|
|
|
struct UBJsonReader
|
|
{
|
|
private var file: FileHandle
|
|
|
|
init(file: FileHandle)
|
|
{
|
|
self.file = file
|
|
}
|
|
|
|
func read() throws -> UBJsonToken
|
|
{
|
|
guard try readCharacter() == "{"
|
|
else { throw UBReaderError.badFormat("Stream does not start with an object") }
|
|
return try readObject()
|
|
}
|
|
|
|
private func parse(type: Character) throws -> UBJsonToken
|
|
{
|
|
let oldFormat = true
|
|
return switch type
|
|
{
|
|
case "{": try readObject()
|
|
case "[": try readArray()
|
|
case "Z": .null
|
|
case "N": .noop
|
|
case "T": .bool(true)
|
|
case "F": .bool(false)
|
|
case "i": oldFormat
|
|
? .int16(try file.read(as: Int16.self).bigEndian)
|
|
: .int8(try file.read(as: Int8.self))
|
|
case "U": .uint8(try file.read(as: UInt8.self))
|
|
case "I": oldFormat
|
|
? .int32(try file.read(as: Int32.self).bigEndian)
|
|
: .int16(try file.read(as: Int16.self).bigEndian)
|
|
case "l": .int32(try file.read(as: Int32.self).bigEndian)
|
|
case "L": .int64(try file.read(as: Int64.self).bigEndian)
|
|
case "d": .float32(try readBeFloatPiss())
|
|
case "D": .float64(try readBeFloat())
|
|
case "H": throw UBReaderError.badFormat("High-precision numbers are unsupported")
|
|
case "C": .char(try readCharacter())
|
|
case "S": .string(try readString())
|
|
default: throw UBReaderError.badToken("Unexpected token \"\(type)\"")
|
|
}
|
|
}
|
|
|
|
private func readObject() throws -> UBJsonToken
|
|
{
|
|
var items = Dictionary<String, UBJsonToken>()
|
|
while true
|
|
{
|
|
let type = try readCharacter()
|
|
var name: String
|
|
switch type
|
|
{
|
|
case "S", "i":
|
|
name = try readString(type)
|
|
case "}":
|
|
return .object(items)
|
|
default:
|
|
throw UBReaderError.badToken("Unexpected token while reading object field key")
|
|
}
|
|
if items.keys.contains(name) { throw UBReaderError.badFormat("Object contains overlapping keys") }
|
|
items[name] = try parse(type: try readCharacter())
|
|
}
|
|
}
|
|
|
|
private func readArray() throws -> UBJsonToken
|
|
{
|
|
var array = [UBJsonToken]()
|
|
while true
|
|
{
|
|
let type = try readCharacter()
|
|
switch type
|
|
{
|
|
case "]":
|
|
return .array(array)
|
|
default:
|
|
array.append(try parse(type: type))
|
|
}
|
|
}
|
|
}
|
|
|
|
private func readBeFloatPiss() throws -> Float
|
|
{
|
|
guard var bytes = try? file.read(upToCount: 4)
|
|
else { throw UBReaderError.readError("Read failure while reading float data") }
|
|
return Float(bitPattern: bytes.withUnsafeBytes { $0.load(as: UInt32.self) }.bigEndian)
|
|
}
|
|
|
|
private func readBeFloat<T: BinaryFloatingPoint, U: UnsignedInteger>() throws -> T where T.RawSignificand == U
|
|
{
|
|
guard var bytes = try? file.read(upToCount: MemoryLayout<U>.size)
|
|
else { throw UBReaderError.readError("Read failure while reading float data") }
|
|
bytes.reverse()
|
|
return T(bytes.withUnsafeBytes { $0.load(as: U.self) })
|
|
}
|
|
|
|
private func readCharacter() throws -> Character
|
|
{
|
|
guard let raw = try? file.read(as: UInt8.self),
|
|
let uni = UnicodeScalar(Int(raw))
|
|
else { throw UBReaderError.readError("Read failure while reading character") }
|
|
return Character(uni)
|
|
}
|
|
|
|
private func readString(_ optType: Character? = nil) throws -> String
|
|
{
|
|
let type = optType == nil ? try readCharacter() : optType!
|
|
var length: Int
|
|
switch type
|
|
{
|
|
case "S":
|
|
guard try readCharacter() == "i"
|
|
else { throw UBReaderError.badToken("Malformed string") }
|
|
fallthrough
|
|
case "i":
|
|
length = Int(try file.read(as: Int8.self))
|
|
case "s":
|
|
length = Int(try file.read(as: UInt8.self))
|
|
default: throw UBReaderError.badToken("Unexpected token while reading string")
|
|
}
|
|
if length < 0 { throw UBReaderError.badToken("Negative string length") }
|
|
if length == 0 { return "" }
|
|
guard let data = try file.read(upToCount: length)
|
|
else { throw UBReaderError.readError("Error reading string") }
|
|
return String(decoding: data, as: UTF8.self)
|
|
}
|
|
}
|
|
|
|
enum UBReaderError: Error
|
|
{
|
|
case badFormat(_ message: String)
|
|
case badToken(_ message: String)
|
|
case readError(_ message: String)
|
|
case valueError(_ message: String)
|
|
}
|
|
|
|
enum UBJsonToken
|
|
{
|
|
case object(_ fields: Dictionary<String, UBJsonToken>)
|
|
case array(_ items: [UBJsonToken])
|
|
case null
|
|
case noop
|
|
case bool(_ value: Bool)
|
|
case int8(_ value: Int8)
|
|
case uint8(_ value: UInt8)
|
|
case int16(_ value: Int16)
|
|
case int32(_ value: Int32)
|
|
case int64(_ value: Int64)
|
|
case highPrecision
|
|
case float32(_ value: Float32)
|
|
case float64(_ value: Float64)
|
|
case char(_ value: Character)
|
|
case string(_ value: String)
|
|
}
|
|
|
|
extension UBJsonToken
|
|
{
|
|
var array: [UBJsonToken]
|
|
{
|
|
get throws
|
|
{
|
|
if case .array(let items) = self { return items }
|
|
throw UBReaderError.valueError("Not an array")
|
|
}
|
|
}
|
|
|
|
func getArray(key: String) throws -> [UBJsonToken]
|
|
{
|
|
if case .object(let fields) = self
|
|
{
|
|
guard let child = fields[key]
|
|
else { throw UBReaderError.valueError("Field \"\(key)\" not found") }
|
|
return try child.array
|
|
}
|
|
throw UBReaderError.valueError("Not an object")
|
|
}
|
|
|
|
var int16: Int16
|
|
{
|
|
get throws
|
|
{
|
|
if case .int16(let value) = self { return value }
|
|
throw UBReaderError.valueError("Not an int16")
|
|
}
|
|
}
|
|
|
|
func getInt16Array(key: String) throws -> [Int16]
|
|
{
|
|
try getArray(key: key).map(
|
|
{ i in
|
|
if case .int8(let value) = i { return Int16(value) }
|
|
else if case .uint8(let value) = i { return Int16(value) }
|
|
else if case .int16(let value) = i { return value }
|
|
else if case .int32(let value) = i
|
|
{
|
|
if value < Int16.min || value > Int16.max { throw UBReaderError.valueError("Value out of range") }
|
|
return Int16(truncatingIfNeeded: value)
|
|
}
|
|
else if case .int64(let value) = i
|
|
{
|
|
if value < Int16.min || value > Int16.max { throw UBReaderError.valueError("Value out of range") }
|
|
return Int16(truncatingIfNeeded: value)
|
|
}
|
|
else { throw UBReaderError.valueError("Can't read array as int16s") }
|
|
})
|
|
}
|
|
|
|
var float: Float
|
|
{
|
|
get throws
|
|
{
|
|
if case .float32(let value) = self { return value }
|
|
throw UBReaderError.valueError("Not a float32")
|
|
}
|
|
}
|
|
|
|
func getFloatArray(key: String) throws -> [Float]
|
|
{
|
|
try getArray(key: key).map({ try $0.float })
|
|
}
|
|
|
|
var string: String
|
|
{
|
|
get throws
|
|
{
|
|
if case .string(let value) = self { return value }
|
|
throw UBReaderError.valueError("Not a string")
|
|
}
|
|
}
|
|
|
|
func getString(key: String, default defStr: String? = nil) throws -> String
|
|
{
|
|
if case .object(let fields) = self
|
|
{
|
|
guard let child = fields[key] else
|
|
{
|
|
if defStr == nil { throw UBReaderError.valueError("Field \"\(key)\" not found") }
|
|
return defStr!
|
|
}
|
|
return try child.string
|
|
}
|
|
throw UBReaderError.valueError("Not an object")
|
|
}
|
|
}
|