Files
CavesOfSwift/Sources/JolkEngine/Util/UBJsonReader.swift
2024-05-05 17:01:56 +10:00

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")
}
}