Files
OpenEmuKit/Source/ShaderPresetTextEncoding.swift

165 lines
5.8 KiB
Swift

// Copyright (c) 2022, OpenEmu Team
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the OpenEmu Team nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY OpenEmu Team ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import Foundation
import OpenEmuKitPrivate
public enum ShaderPresetWriteError: Error {
case invalidCharacters
case missingCreatedAt
}
@frozen public enum ShaderPresetTextWriter {
@frozen public struct Options: OptionSet {
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
public static let name = Self(rawValue: 1 << 0)
public static let shader = Self(rawValue: 1 << 1)
public static let createdAt = Self(rawValue: 1 << 2)
public static let all: Self = [.name, .shader]
}
static var formatter: NumberFormatter {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 4
formatter.decimalSeparator = "."
formatter.groupingSeparator = ""
return formatter
}
static let invalidCharacters = #"#""#
static let invalidCharacterSet = CharacterSet(charactersIn: invalidCharacters)
/// A boolean value to determine if a string is a valid identifier.
/// - Parameter s: The string to be validated.
/// - Returns: `true` if s is a valid identifier.
public static func isValidIdentifier(_ s: String) -> Bool {
s.rangeOfCharacter(from: invalidCharacterSet) == nil
}
public static func write(preset c: ShaderPresetData, options: Options = [.shader]) throws -> String {
var s = ""
var first = true
if options.contains(.name) {
guard isValidIdentifier(c.name) else { throw ShaderPresetWriteError.invalidCharacters }
first = false
s.append("$name=\"\(c.name)\"")
}
if options.contains(.shader) {
guard isValidIdentifier(c.shader) else { throw ShaderPresetWriteError.invalidCharacters }
if !first {
s.append(";")
} else {
first = false
}
s.append("$shader=\"\(c.shader)\"")
}
if options.contains(.createdAt) {
guard let createdAt = c.createdAt else { throw ShaderPresetWriteError.missingCreatedAt }
if !first {
s.append(";")
} else {
first = false
}
s.append("$createdAt=\"\(UInt64(createdAt))\"")
}
// Sort the keys for a consistent output
for key in c.parameters.keys.sorted() {
if !first {
s.append(";")
}
s.append("\(key)=\(formatter.string(from: c.parameters[key]! as NSNumber)!)")
first = false
}
return s
}
}
public enum ShaderPresetReadError: Error {
/// Preset is malformed
case malformed
}
@frozen public enum ShaderPresetTextReader {
enum State {
case key, value
}
public static func read(text: String, id: String? = nil) throws -> ShaderPresetData {
guard let tokens = try? PKVScanner.parse(text: text)
else { throw ShaderPresetReadError.malformed }
var name = "Unnamed shader preset"
var shader = ""
var createdAt: TimeInterval?
var params = [String: Double]()
var iter = tokens.makePeekableIterator()
while let (tok, key) = iter.peek(), tok == .identifier || tok == .systemIdentifier {
iter.next()
if tok == .identifier {
guard
let (tok, val) = iter.peek(),
tok == .float
else { break }
iter.next()
params[key] = Double(val)
continue
}
// tok == .reserveIdentifier
guard
let (tok, val) = iter.peek(),
tok == .string
else { break }
iter.next()
switch key {
case "$name":
name = val
case "$shader":
shader = val
case "$createdAt":
createdAt = TimeInterval(val)
default:
break
}
}
return ShaderPresetData(name: name, shader: shader, parameters: params, id: id, createdAt: createdAt)
}
}