From bfef503f5abf49fc4824b23b36565d329eb2638f Mon Sep 17 00:00:00 2001 From: "deepsource-dev-autofix[bot]" <61578317+deepsource-dev-autofix[bot]@users.noreply.github.com> Date: Tue, 8 Aug 2023 04:30:19 +0000 Subject: [PATCH] style: format code with swift-format Format code with swift-format This commit fixes the style issues introduced in b5e80fc according to the output from swift-format. Details: None --- Package.swift | 12 +- Sources/JSONLib/Error.swift | 58 +- Sources/JSONLib/Functional.swift | 33 +- Sources/JSONLib/JSValue.Accessors.swift | 240 +- Sources/JSONLib/JSValue.Encodings.swift | 8 +- Sources/JSONLib/JSValue.ErrorHandling.swift | 92 +- Sources/JSONLib/JSValue.Indexers.swift | 126 +- Sources/JSONLib/JSValue.Literals.swift | 88 +- Sources/JSONLib/JSValue.Parsing.swift | 2060 ++++++++++------- Sources/JSONLib/JSValue.swift | 423 ++-- Sources/JSONLib/ReplayableGenerator.swift | 102 +- Sources/ParserPerfTestHarness/main.swift | 207 +- Sources/ParserTestHarness/main.swift | 110 +- .../JSONLibTests/JSValueEquatableTests.swift | 197 +- .../JSONLibTests/JSValueTests.Indexers.swift | 131 +- .../JSONLibTests/JSValueTests.Literals.swift | 207 +- Tests/JSONLibTests/JSValueTests.Parsing.swift | 1483 ++++++------ Tests/JSONLibTests/JSValueTests.Usage.swift | 529 ++--- Tests/LinuxMain.swift | 11 +- 19 files changed, 3322 insertions(+), 2795 deletions(-) diff --git a/Package.swift b/Package.swift index 8593b38..1d768e8 100644 --- a/Package.swift +++ b/Package.swift @@ -3,10 +3,10 @@ import PackageDescription let package = Package( - name: "json-swift", - targets: [ - Target(name: "JSONLib", dependencies: []), - Target(name: "ParserTestHarness", dependencies: ["JSONLib"]), - Target(name: "ParserPerfTestHarness", dependencies: ["JSONLib"]) - ] + name: "json-swift", + targets: [ + Target(name: "JSONLib", dependencies: []), + Target(name: "ParserTestHarness", dependencies: ["JSONLib"]), + Target(name: "ParserPerfTestHarness", dependencies: ["JSONLib"]), + ] ) diff --git a/Sources/JSONLib/Error.swift b/Sources/JSONLib/Error.swift index 80a81d8..d2f33bb 100644 --- a/Sources/JSONLib/Error.swift +++ b/Sources/JSONLib/Error.swift @@ -5,44 +5,44 @@ /// Represents error information for JSON parsing issues. public final class JsonParserError: Swift.Error { - public typealias ErrorInfoDictionary = [String:String] + public typealias ErrorInfoDictionary = [String: String] - /// The error code used to differentiate between various error states. - public let code: Int + /// The error code used to differentiate between various error states. + public let code: Int - /// A string that is used to group errors into related error buckets. - public let domain: String + /// A string that is used to group errors into related error buckets. + public let domain: String - /// A place to store any custom information that needs to be passed along with the error instance. - public let userInfo: ErrorInfoDictionary? + /// A place to store any custom information that needs to be passed along with the error instance. + public let userInfo: ErrorInfoDictionary? - - /// Initializes a new `Error` instance. - public init(code: Int, domain: String, userInfo: ErrorInfoDictionary?) { - self.code = code - self.domain = domain - self.userInfo = userInfo - } + /// Initializes a new `Error` instance. + public init(code: Int, domain: String, userInfo: ErrorInfoDictionary?) { + self.code = code + self.domain = domain + self.userInfo = userInfo + } } /// The standard keys used in `Error` and `userInfo`. public struct ErrorKeys { - private init() {} - - public static let LocalizedDescription = "NSLocalizedDescription" - public static let LocalizedFailureReason = "NSLocalizedFailureReason" - public static let LocalizedRecoverySuggestion = "NSLocalizedRecoverySuggestion" - public static let LocalizedRecoveryOptions = "NSLocalizedRecoveryOptions" - public static let RecoveryAttempter = "NSRecoveryAttempter" - public static let HelpAnchor = "NSHelpAnchor" - - public static let StringEncoding = "NSStringEncoding" - public static let URL = "NSURL" - public static let FilePath = "NSFilePath" + private init() {} + + public static let LocalizedDescription = "NSLocalizedDescription" + public static let LocalizedFailureReason = "NSLocalizedFailureReason" + public static let LocalizedRecoverySuggestion = "NSLocalizedRecoverySuggestion" + public static let LocalizedRecoveryOptions = "NSLocalizedRecoveryOptions" + public static let RecoveryAttempter = "NSRecoveryAttempter" + public static let HelpAnchor = "NSHelpAnchor" + + public static let StringEncoding = "NSStringEncoding" + public static let URL = "NSURL" + public static let FilePath = "NSFilePath" } extension JsonParserError: CustomStringConvertible { - public var description: String { - return "Error code: \(self.code), domain: \(self.domain)\ninfo: \(String(describing: self.userInfo))" - } + public var description: String { + return + "Error code: \(self.code), domain: \(self.domain)\ninfo: \(String(describing: self.userInfo))" + } } diff --git a/Sources/JSONLib/Functional.swift b/Sources/JSONLib/Functional.swift index 910b876..61cc898 100644 --- a/Sources/JSONLib/Functional.swift +++ b/Sources/JSONLib/Functional.swift @@ -4,8 +4,8 @@ * ------------------------------------------------------------------------------------------ */ precedencegroup FunctionalPrecedence { - associativity: left - higherThan: MultiplicationPrecedence + associativity: left + higherThan: MultiplicationPrecedence } infix operator ⇒ : FunctionalPrecedence @@ -16,13 +16,13 @@ infix operator ⇒ : FunctionalPrecedence /// - parameter rhs: The value to apply to the function /// - returns: The transformation of `rhs` using `lhs`. public func ⇒ (lhs: ((A) -> B)?, rhs: A?) -> B? { - if let lhs = lhs { - if let rhs = rhs { - return lhs(rhs) - } - } - - return nil + if let lhs = lhs { + if let rhs = rhs { + return lhs(rhs) + } + } + + return nil } /// Allows for a value to be transformed by a function, allowing for optionals. @@ -31,13 +31,13 @@ public func ⇒ (lhs: ((A) -> B)?, rhs: A?) -> B? { /// - parameter rhs: The transformative function /// - returns: The transformation of `lhs` using `rhs`. public func ⇒ (lhs: A?, rhs: ((A) -> B)?) -> B? { - if let lhs = lhs { - if let rhs = rhs { - return rhs(lhs) - } - } - - return nil + if let lhs = lhs { + if let rhs = rhs { + return rhs(lhs) + } + } + + return nil } /// Allows for a transformative function to be applied to a value. @@ -47,7 +47,6 @@ public func ⇒ (lhs: A?, rhs: ((A) -> B)?) -> B? { /// - returns: The transformation of `rhs` using `lhs`. public func ⇒ (lhs: (A) -> B, rhs: A) -> B { return lhs(rhs) } - /// Allows for a value to be transformed by a function. /// /// - parameter lhs: The value to apply to the function diff --git a/Sources/JSONLib/JSValue.Accessors.swift b/Sources/JSONLib/JSValue.Accessors.swift index 50da339..27712b8 100644 --- a/Sources/JSONLib/JSValue.Accessors.swift +++ b/Sources/JSONLib/JSValue.Accessors.swift @@ -8,130 +8,128 @@ */ extension JSValue { - - /// Attempts to retrieve a `String` out of the `JSValue`. - /// - /// - returns: If the `JSValue` is a `String`, then the stored `String` value is returned, otherwise `nil`. - public var string: String? { - switch self { - case .string(let value): return value - default: return nil - } - } - - /// Attempts to retrieve a `Double` out of the `JSValue`. - /// - /// - returns: If the `JSValue` is a `JSNumber`, then the stored `Double` value is returned, otherwise `nil`. - public var number: Double? { - switch self { - case .number(let value): return value - default: return nil - } - } - - /// Attempts to retrieve an `Int` out of the `JSValue`. - /// - /// - returns: If the `JSValue` is a `Double`, then the stored `Double` value is returned, otherwise `nil`. - public var integer: Int? { - switch self { - case .number(let value): return Int(value) - default: return nil - } - } - - - /// Attempts to retrieve a `Bool` out of the `JSValue`. - /// - /// - returns: If the `JSValue` is a `Bool`, then the stored `Bool` value is returned, otherwise `nil`. - public var bool: Bool? { - switch self { - case .bool(let value): return value - default: return nil - } - } - - /// Attempts to retrieve a `[String:JSValue]` out of the `JSValue`. - /// - /// - returns: If the `JSValue` is a `[String:JSValue]`, then the stored `[String:JSValue]` value is returned, otherwise `nil`. - public var object: [String:JSValue]? { - switch self { - case .object(let value): return value - default: return nil - } - } - - /// Attempts to retrieve a `[JSValue]` out of the `JSValue`. - /// - /// - returns: If the `JSValue` is a `JSArray`, then the stored `[JSValue]` value is returned, otherwise `nil`. - public var array: [JSValue]? { - switch self { - case .array(let value): return value - default: return nil - } - } - - /// Used to determine if a `nil` value is stored within `JSValue`. There is no intrinsic type for this value. - /// - /// - returns: If the `JSValue` is a `JSNull`, then the `true` is returned, otherwise `false`. - public var null: Bool { - switch self { - case .null: return true - default: return false - } - } + + /// Attempts to retrieve a `String` out of the `JSValue`. + /// + /// - returns: If the `JSValue` is a `String`, then the stored `String` value is returned, otherwise `nil`. + public var string: String? { + switch self { + case .string(let value): return value + default: return nil + } + } + + /// Attempts to retrieve a `Double` out of the `JSValue`. + /// + /// - returns: If the `JSValue` is a `JSNumber`, then the stored `Double` value is returned, otherwise `nil`. + public var number: Double? { + switch self { + case .number(let value): return value + default: return nil + } + } + + /// Attempts to retrieve an `Int` out of the `JSValue`. + /// + /// - returns: If the `JSValue` is a `Double`, then the stored `Double` value is returned, otherwise `nil`. + public var integer: Int? { + switch self { + case .number(let value): return Int(value) + default: return nil + } + } + + /// Attempts to retrieve a `Bool` out of the `JSValue`. + /// + /// - returns: If the `JSValue` is a `Bool`, then the stored `Bool` value is returned, otherwise `nil`. + public var bool: Bool? { + switch self { + case .bool(let value): return value + default: return nil + } + } + + /// Attempts to retrieve a `[String:JSValue]` out of the `JSValue`. + /// + /// - returns: If the `JSValue` is a `[String:JSValue]`, then the stored `[String:JSValue]` value is returned, otherwise `nil`. + public var object: [String: JSValue]? { + switch self { + case .object(let value): return value + default: return nil + } + } + + /// Attempts to retrieve a `[JSValue]` out of the `JSValue`. + /// + /// - returns: If the `JSValue` is a `JSArray`, then the stored `[JSValue]` value is returned, otherwise `nil`. + public var array: [JSValue]? { + switch self { + case .array(let value): return value + default: return nil + } + } + + /// Used to determine if a `nil` value is stored within `JSValue`. There is no intrinsic type for this value. + /// + /// - returns: If the `JSValue` is a `JSNull`, then the `true` is returned, otherwise `false`. + public var null: Bool { + switch self { + case .null: return true + default: return false + } + } } /// Provide a usability extension to allow chaining index accessors without having to /// marked it as optional. /// e.g. foo["hi"]?["this"]?["sucks"]?.string vs. foo["so"]["much"]["better"].string extension Optional where Wrapped == JSValue { - /// Attempts to retrieve a `String` out of the `JSValue`. - /// - /// - returns: If the `JSValue` is a `String`, then the stored `String` value is returned, otherwise `nil`. - public var string: String? { - return self?.string - } - - /// Attempts to retrieve a `Double` out of the `JSValue`. - /// - /// - returns: If the `JSValue` is a `JSNumber`, then the stored `Double` value is returned, otherwise `nil`. - public var number: Double? { - return self?.number - } - - /// Attempts to retrieve an `Int` out of the `JSValue`. - /// - /// - returns: If the `JSValue` is a `Double`, then the stored `Double` value is returned, otherwise `nil`. - public var integer: Int? { - return self?.integer - } - - - /// Attempts to retrieve a `Bool` out of the `JSValue`. - /// - /// - returns: If the `JSValue` is a `Bool`, then the stored `Bool` value is returned, otherwise `nil`. - public var bool: Bool? { - return self?.bool - } - - /// Attempts to retrieve a `[String:JSValue]` out of the `JSValue`. - /// - /// - returns: If the `JSValue` is a `[String:JSValue]`, then the stored `[String:JSValue]` value is returned, otherwise `nil`. - public var object: [String:JSValue]? { - return self?.object - } - - /// Attempts to retrieve a `[JSValue]` out of the `JSValue`. - /// - /// - returns: If the `JSValue` is a `JSArray`, then the stored `[JSValue]` value is returned, otherwise `nil`. - public var array: [JSValue]? { - return self?.array - } - - /// Used to determine if a `nil` value is stored within `JSValue`. There is no intrinsic type for this value. - /// - /// - returns: If the `JSValue` is a `JSNull`, then the `true` is returned, otherwise `false`. - public var null: Bool { - return self?.null ?? false - } -} \ No newline at end of file + /// Attempts to retrieve a `String` out of the `JSValue`. + /// + /// - returns: If the `JSValue` is a `String`, then the stored `String` value is returned, otherwise `nil`. + public var string: String? { + return self?.string + } + + /// Attempts to retrieve a `Double` out of the `JSValue`. + /// + /// - returns: If the `JSValue` is a `JSNumber`, then the stored `Double` value is returned, otherwise `nil`. + public var number: Double? { + return self?.number + } + + /// Attempts to retrieve an `Int` out of the `JSValue`. + /// + /// - returns: If the `JSValue` is a `Double`, then the stored `Double` value is returned, otherwise `nil`. + public var integer: Int? { + return self?.integer + } + + /// Attempts to retrieve a `Bool` out of the `JSValue`. + /// + /// - returns: If the `JSValue` is a `Bool`, then the stored `Bool` value is returned, otherwise `nil`. + public var bool: Bool? { + return self?.bool + } + + /// Attempts to retrieve a `[String:JSValue]` out of the `JSValue`. + /// + /// - returns: If the `JSValue` is a `[String:JSValue]`, then the stored `[String:JSValue]` value is returned, otherwise `nil`. + public var object: [String: JSValue]? { + return self?.object + } + + /// Attempts to retrieve a `[JSValue]` out of the `JSValue`. + /// + /// - returns: If the `JSValue` is a `JSArray`, then the stored `[JSValue]` value is returned, otherwise `nil`. + public var array: [JSValue]? { + return self?.array + } + + /// Used to determine if a `nil` value is stored within `JSValue`. There is no intrinsic type for this value. + /// + /// - returns: If the `JSValue` is a `JSNull`, then the `true` is returned, otherwise `false`. + public var null: Bool { + return self?.null ?? false + } +} diff --git a/Sources/JSONLib/JSValue.Encodings.swift b/Sources/JSONLib/JSValue.Encodings.swift index 18749b8..5b4f0d0 100644 --- a/Sources/JSONLib/JSValue.Encodings.swift +++ b/Sources/JSONLib/JSValue.Encodings.swift @@ -11,7 +11,7 @@ // /// within the contents of a string value. // public struct Encodings { // private init() {} -// +// // /// The encoding prefix for all base64 encoded values. // public static let base64 = "data:text/plain;base64," // } @@ -29,11 +29,11 @@ // // return [Byte](bytes) // // } // fatalError("nyi") -// +// // default: // return nil // } -// +// // return nil // } -//} \ No newline at end of file +//} diff --git a/Sources/JSONLib/JSValue.ErrorHandling.swift b/Sources/JSONLib/JSValue.ErrorHandling.swift index cc0f4b6..392cb0b 100644 --- a/Sources/JSONLib/JSValue.ErrorHandling.swift +++ b/Sources/JSONLib/JSValue.ErrorHandling.swift @@ -4,47 +4,53 @@ * ------------------------------------------------------------------------------------------ */ extension JSValue { - - /// A type that holds the error code and standard error message for the various types of failures - /// a `JSValue` can have. - public struct ErrorMessage { - /// The numeric value of the error number. - public let code: Int - - /// The default message describing the error. - public let message: String - } - - /// All of the error codes and standard error messages when parsing JSON. - public struct ErrorCode { - private init() {} - - /// A integer that is outside of the safe range was attempted to be set. - public static let InvalidIntegerValue = ErrorMessage( - code:1, - message: "The specified number is not valid. Valid numbers are within the range: [\(JSValue.MinimumSafeInt), \(JSValue.MaximumSafeInt)]") - - /// Error when attempting to access an element from a `JSValue` backed by a dictionary and there is no - /// value stored at the specified key. - public static let KeyNotFound = ErrorMessage( - code: 2, - message: "The specified key cannot be found.") - - /// Error when attempting to index into a `JSValue` that is not backed by a dictionary or array. - public static let IndexingIntoUnsupportedType = ErrorMessage( - code: 3, - message: "Indexing is only supported on arrays and dictionaries." - ) - - /// Error when attempting to access an element from a `JSValue` backed by an array and the index is - /// out of range. - public static let IndexOutOfRange = ErrorMessage( - code: 4, - message: "The specified index is out of range of the bounds for the array.") - - /// Error when a parsing error occurs. - public static let ParsingError = ErrorMessage( - code: 5, - message: "The JSON string being parsed was invalid.") - } + + /// A type that holds the error code and standard error message for the various types of failures + /// a `JSValue` can have. + public struct ErrorMessage { + /// The numeric value of the error number. + public let code: Int + + /// The default message describing the error. + public let message: String + } + + /// All of the error codes and standard error messages when parsing JSON. + public struct ErrorCode { + private init() {} + + /// A integer that is outside of the safe range was attempted to be set. + public static let InvalidIntegerValue = ErrorMessage( + code: 1, + message: + "The specified number is not valid. Valid numbers are within the range: [\(JSValue.MinimumSafeInt), \(JSValue.MaximumSafeInt)]" + ) + + /// Error when attempting to access an element from a `JSValue` backed by a dictionary and there is no + /// value stored at the specified key. + public static let KeyNotFound = ErrorMessage( + code: 2, + message: "The specified key cannot be found." + ) + + /// Error when attempting to index into a `JSValue` that is not backed by a dictionary or array. + public static let IndexingIntoUnsupportedType = ErrorMessage( + code: 3, + message: "Indexing is only supported on arrays and dictionaries." + ) + + /// Error when attempting to access an element from a `JSValue` backed by an array and the index is + /// out of range. + public static let IndexOutOfRange = ErrorMessage( + code: 4, + message: + "The specified index is out of range of the bounds for the array." + ) + + /// Error when a parsing error occurs. + public static let ParsingError = ErrorMessage( + code: 5, + message: "The JSON string being parsed was invalid." + ) + } } diff --git a/Sources/JSONLib/JSValue.Indexers.swift b/Sources/JSONLib/JSValue.Indexers.swift index 80b7d08..373b195 100644 --- a/Sources/JSONLib/JSValue.Indexers.swift +++ b/Sources/JSONLib/JSValue.Indexers.swift @@ -4,73 +4,73 @@ * ------------------------------------------------------------------------------------------ */ extension JSValue { - /// Attempts to treat the `JSValue` as a `JSObject` and perform the lookup. - /// - /// - returns: A `JSValue` that represents the value found at `key` - public subscript(key: String) -> JSValue? { - get { - if let dict = self.object { - if let value = dict[key] { - return value - } - } + /// Attempts to treat the `JSValue` as a `JSObject` and perform the lookup. + /// + /// - returns: A `JSValue` that represents the value found at `key` + public subscript(key: String) -> JSValue? { + get { + if let dict = self.object { + if let value = dict[key] { + return value + } + } - return nil - } - set { - if var dict = self.object { - dict[key] = newValue - self = JSValue(dict) - } - } - } - - /// Attempts to treat the `JSValue` as an array and return the item at the index. - public subscript(index: Int) -> JSValue? { - get { - if let array = self.array { - if index >= 0 && index < array.count { - return array[index] - } - } - - return nil - } - set { - if var array = self.array { - array[index] = newValue ?? .null - self = JSValue(array) - } - } - } + return nil + } + set { + if var dict = self.object { + dict[key] = newValue + self = JSValue(dict) + } + } + } + + /// Attempts to treat the `JSValue` as an array and return the item at the index. + public subscript(index: Int) -> JSValue? { + get { + if let array = self.array { + if index >= 0 && index < array.count { + return array[index] + } + } + + return nil + } + set { + if var array = self.array { + array[index] = newValue ?? .null + self = JSValue(array) + } + } + } } /// Provide a usability extension to allow chaining index accessors without having to /// marked it as optional. /// e.g. foo["hi"]?["this"]?["sucks"] vs. foo["so"]["much"]["better"] extension Optional where Wrapped == JSValue { - public subscript(key: String) -> JSValue? { - get { - return self?.object?[key] - } - set { - if var dict = self?.object { - dict[key] = newValue - self = JSValue(dict) - } - } - } - - /// Attempts to treat the `JSValue` as an array and return the item at the index. - public subscript(index: Int) -> JSValue? { - get { - return self?.array?[index] - } - set { - if var array = self?.array { - array[index] = newValue ?? .null - self = JSValue(array) - } - } - } -} \ No newline at end of file + public subscript(key: String) -> JSValue? { + get { + return self?.object?[key] + } + set { + if var dict = self?.object { + dict[key] = newValue + self = JSValue(dict) + } + } + } + + /// Attempts to treat the `JSValue` as an array and return the item at the index. + public subscript(index: Int) -> JSValue? { + get { + return self?.array?[index] + } + set { + if var array = self?.array { + array[index] = newValue ?? .null + self = JSValue(array) + } + } + } +} diff --git a/Sources/JSONLib/JSValue.Literals.swift b/Sources/JSONLib/JSValue.Literals.swift index 5b30052..55572b6 100644 --- a/Sources/JSONLib/JSValue.Literals.swift +++ b/Sources/JSONLib/JSValue.Literals.swift @@ -3,61 +3,61 @@ * Licensed under the MIT License. See License in the project root for license information. * ------------------------------------------------------------------------------------------ */ -extension JSValue : ExpressibleByIntegerLiteral { - private static func convert(_ value: Int64) -> JSValue { - return JSValue(Double(value)) - } - - public init(integerLiteral value: Int64) { - self = JSValue.convert(value) - } +extension JSValue: ExpressibleByIntegerLiteral { + private static func convert(_ value: Int64) -> JSValue { + return JSValue(Double(value)) + } + + public init(integerLiteral value: Int64) { + self = JSValue.convert(value) + } } -extension JSValue : ExpressibleByFloatLiteral { - public init(floatLiteral value: Double) { - self = JSValue(value) - } +extension JSValue: ExpressibleByFloatLiteral { + public init(floatLiteral value: Double) { + self = JSValue(value) + } } -extension JSValue : ExpressibleByStringLiteral { - public init(stringLiteral value: String) { - self = JSValue(value) - } - - public init(extendedGraphemeClusterLiteral value: String) { - self = JSValue(value) - } - - public init(unicodeScalarLiteral value: String) { - self = JSValue(value) - } +extension JSValue: ExpressibleByStringLiteral { + public init(stringLiteral value: String) { + self = JSValue(value) + } + + public init(extendedGraphemeClusterLiteral value: String) { + self = JSValue(value) + } + + public init(unicodeScalarLiteral value: String) { + self = JSValue(value) + } } -extension JSValue : ExpressibleByArrayLiteral { - public init(arrayLiteral elements: JSValue...) { - self = JSValue(elements) - } +extension JSValue: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: JSValue...) { + self = JSValue(elements) + } } -extension JSValue : ExpressibleByDictionaryLiteral { - public init(dictionaryLiteral elements: (String, JSValue)...) { - var dict = [String:JSValue]() - for (k, v) in elements { - dict[k] = v - } - - self = JSValue(dict) - } +extension JSValue: ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (String, JSValue)...) { + var dict = [String: JSValue]() + for (k, v) in elements { + dict[k] = v + } + + self = JSValue(dict) + } } -extension JSValue : ExpressibleByNilLiteral { - public init(nilLiteral: ()) { - self = .null - } +extension JSValue: ExpressibleByNilLiteral { + public init(nilLiteral: ()) { + self = .null + } } extension JSValue: ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { - self = JSValue(value) - } + public init(booleanLiteral value: Bool) { + self = JSValue(value) + } } diff --git a/Sources/JSONLib/JSValue.Parsing.swift b/Sources/JSONLib/JSValue.Parsing.swift index 9c3dd4f..6b628d2 100644 --- a/Sources/JSONLib/JSValue.Parsing.swift +++ b/Sources/JSONLib/JSValue.Parsing.swift @@ -6,851 +6,1265 @@ import Foundation extension JSValue { - public static func parse(_ string: String) throws -> JSValue { - let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false)! - return try data.withUnsafeBytes { (ptr: UnsafePointer) -> JSValue in - let buffer = UnsafeBufferPointer(start: ptr, count: data.count) - let generator = ReplayableGenerator(buffer) - - let value = try parse(generator) - try validateRemainingContent(generator) - return value - } - } - - public static func parse(_ data: Data) throws -> JSValue { - return try data.withUnsafeBytes { (ptr: UnsafePointer) -> JSValue in - let buffer = UnsafeBufferPointer(start: ptr, count: data.count) - let generator = ReplayableGenerator(buffer) - - let value = try parse(generator) - try validateRemainingContent(generator) - return value - } - } - - /// Parses the given sequence of UTF8 code points and attempts to return a `JSValue` from it. - /// - parameter seq: The sequence of UTF8 code points. - /// - returns: A `JSParsingResult` containing the parsed `JSValue` or error information. - public static func parse(_ bytes: [UInt8]) throws -> JSValue { - return try bytes.withUnsafeBufferPointer { ptr in - let generator = ReplayableGenerator(ptr) - - let value = try parse(generator) - try validateRemainingContent(generator) - return value - } - } - - static func validateRemainingContent(_ generator: ReplayableGenerator) throws { - for codeunit in generator { - if codeunit.isWhitespace() { continue } - else { - let remainingText = substring(generator) - - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Invalid characters after the last item in the JSON: \(remainingText)"] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - } - } - - static let maximumDepth = 1000 - static var depthGuard: Int = 0 - static func parse(_ generator: ReplayableGenerator) throws -> JSValue { - for codeunit in generator { - if codeunit.isWhitespace() { continue } - - if codeunit == Token.LeftCurly { - depthGuard += 1 - defer { depthGuard -= 1 } - - if depthGuard > maximumDepth { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unable to process JSON file with a nested depth greater than \(maximumDepth)."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - return try JSValue.parseObject(generator) - } - else if codeunit == Token.LeftBracket { - depthGuard += 1 - defer { depthGuard -= 1 } - - if depthGuard > maximumDepth { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unable to process JSON file with a nested depth greater than \(maximumDepth)."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - return try JSValue.parseArray(generator) - } - else if codeunit.isDigit() || codeunit == Token.Minus || codeunit == Token.Plus || codeunit == Token.Period { - return try JSValue.parseNumber(generator) - } - else if codeunit == Token.t { - return try JSValue.parseTrue(generator) - } - else if codeunit == Token.f { - return try JSValue.parseFalse(generator) - } - else if codeunit == Token.n { - return try JSValue.parseNull(generator) - } - else if codeunit == Token.DoubleQuote { - return try JSValue.parseString(generator) - } - else { - break - } - } - - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "No valid JSON value was found to parse in string."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - enum ObjectParsingState { - case initial - case key - case value - } - - static func parseObject(_ generator: ReplayableGenerator) throws -> JSValue { - var state = ObjectParsingState.initial - - var key = "" - var dict = [String:JSValue]() - var needsComma = false - var lastParsedComma = false - - for (idx, codeunit) in generator.enumerated() { - switch (idx, codeunit) { - case (0, Token.LeftCurly): continue - case (_, Token.RightCurly): - if lastParsedComma { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "A trailing `,` is not supported. Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - switch state { - case .initial: fallthrough - case .value: - let _ = generator.next() // eat the '}' - return JSValue(dict) - - default: - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Expected token '}' at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - case (_, Token.DoubleQuote): - if needsComma { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unable to parse object. Expected `,` to separate values. Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } + public static func parse(_ string: String) throws -> JSValue { + let data = string.data( + using: String.Encoding.utf8, + allowLossyConversion: false + )! + return try data.withUnsafeBytes { (ptr: UnsafePointer) -> JSValue in + let buffer = UnsafeBufferPointer(start: ptr, count: data.count) + let generator = ReplayableGenerator(buffer) + + let value = try parse(generator) + try validateRemainingContent(generator) + return value + } + } - switch state { - case .initial: - state = .key + public static func parse(_ data: Data) throws -> JSValue { + return try data.withUnsafeBytes { (ptr: UnsafePointer) -> JSValue in + let buffer = UnsafeBufferPointer(start: ptr, count: data.count) + let generator = ReplayableGenerator(buffer) - key = try parseString(generator).string! - generator.replay() + let value = try parse(generator) + try validateRemainingContent(generator) + return value + } + } + + /// Parses the given sequence of UTF8 code points and attempts to return a `JSValue` from it. + /// - parameter seq: The sequence of UTF8 code points. + /// - returns: A `JSParsingResult` containing the parsed `JSValue` or error information. + public static func parse(_ bytes: [UInt8]) throws -> JSValue { + return try bytes.withUnsafeBufferPointer { ptr in + let generator = ReplayableGenerator(ptr) + + let value = try parse(generator) + try validateRemainingContent(generator) + return value + } + } + + static func validateRemainingContent(_ generator: ReplayableGenerator) throws { + for codeunit in generator { + if codeunit.isWhitespace() { + continue + } + else { + let remainingText = substring(generator) + + let info = [ + ErrorKeys.LocalizedDescription: ErrorCode + .ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "Invalid characters after the last item in the JSON: \(remainingText)", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + } + } + + static let maximumDepth = 1000 + static var depthGuard: Int = 0 + static func parse(_ generator: ReplayableGenerator) throws -> JSValue { + for codeunit in generator { + if codeunit.isWhitespace() { continue } + + if codeunit == Token.LeftCurly { + depthGuard += 1 + defer { depthGuard -= 1 } + + if depthGuard > maximumDepth { + let info = [ + ErrorKeys.LocalizedDescription: + ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "Unable to process JSON file with a nested depth greater than \(maximumDepth).", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + return try JSValue.parseObject(generator) + } + else if codeunit == Token.LeftBracket { + depthGuard += 1 + defer { depthGuard -= 1 } + + if depthGuard > maximumDepth { + let info = [ + ErrorKeys.LocalizedDescription: + ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "Unable to process JSON file with a nested depth greater than \(maximumDepth).", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + return try JSValue.parseArray(generator) + } + else if codeunit.isDigit() || codeunit == Token.Minus + || codeunit == Token.Plus || codeunit == Token.Period + { + return try JSValue.parseNumber(generator) + } + else if codeunit == Token.t { + return try JSValue.parseTrue(generator) + } + else if codeunit == Token.f { + return try JSValue.parseFalse(generator) + } + else if codeunit == Token.n { + return try JSValue.parseNull(generator) + } + else if codeunit == Token.DoubleQuote { + return try JSValue.parseString(generator) + } + else { + break + } + } - default: - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Expected token ''' (single quote) or '\"' at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - case (_, Token.Colon): - switch state { - case .key: - state = .value - - let _ = generator.next() // eat the ':' - let value = try parse(generator) - dict[key] = value - generator.replay() - needsComma = true - lastParsedComma = false - - default: let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Expected token ':' at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } + ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "No valid JSON value was found to parse in string.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + enum ObjectParsingState { + case initial + case key + case value + } + + static func parseObject(_ generator: ReplayableGenerator) throws -> JSValue { + var state = ObjectParsingState.initial + + var key = "" + var dict = [String: JSValue]() + var needsComma = false + var lastParsedComma = false + + for (idx, codeunit) in generator.enumerated() { + switch (idx, codeunit) { + case (0, Token.LeftCurly): continue + case (_, Token.RightCurly): + if lastParsedComma { + let info = [ + ErrorKeys.LocalizedDescription: + ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "A trailing `,` is not supported. Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + switch state { + case .initial, .value: + let _ = generator.next() // eat the '}' + return JSValue(dict) + + default: + let info = [ + ErrorKeys.LocalizedDescription: + ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "Expected token '}' at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + case (_, Token.DoubleQuote): + if needsComma { + let info = [ + ErrorKeys.LocalizedDescription: + ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "Unable to parse object. Expected `,` to separate values. Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + switch state { + case .initial: + state = .key + + key = try parseString(generator).string! + generator.replay() + + default: + let info = [ + ErrorKeys.LocalizedDescription: + ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "Expected token ''' (single quote) or '\"' at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + case (_, Token.Colon): + switch state { + case .key: + state = .value + + let _ = generator.next() // eat the ':' + let value = try parse(generator) + dict[key] = value + generator.replay() + needsComma = true + lastParsedComma = false + + default: + let info = [ + ErrorKeys.LocalizedDescription: + ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "Expected token ':' at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + case (_, Token.Comma): + if !needsComma { + let info = [ + ErrorKeys.LocalizedDescription: + ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "A `,` can only separate values. Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + needsComma = false + lastParsedComma = true + + switch state { + case .value: + state = .initial + key = "" + + default: + let info = [ + ErrorKeys.LocalizedDescription: + ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "Expected token ',' at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + default: + if codeunit.isWhitespace() { + continue + } + else { + let info = [ + ErrorKeys.LocalizedDescription: + ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "Unexpected token at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + } + } - case (_, Token.Comma): - if !needsComma { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "A `,` can only separate values. Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - needsComma = false - lastParsedComma = true - - switch state { - case .value: - state = .initial - key = "" - - default: let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Expected token ',' at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - default: - if codeunit.isWhitespace() { continue } - else { + ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "Unable to parse object. Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + static func parseArray(_ generator: ReplayableGenerator) throws -> JSValue { + var values = [JSValue]() + var needsComma = false + var lastParsedComma = false + + for (idx, codeunit) in generator.enumerated() { + switch (idx, codeunit) { + case (0, Token.LeftBracket): continue + case (_, Token.RightBracket): + if lastParsedComma { + let info = [ + ErrorKeys.LocalizedDescription: + ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "A trailing `,` is not supported. Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + let _ = generator.next() // eat the ']' + return JSValue(values) + case (_, Token.Comma): + if !needsComma { + let info = [ + ErrorKeys.LocalizedDescription: + ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "A `,` can only separate values. Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + needsComma = false + lastParsedComma = true + + default: + if codeunit.isWhitespace() { + continue + } + else { + if needsComma { + let info = [ + ErrorKeys + .LocalizedDescription: + ErrorCode + .ParsingError + .message, + ErrorKeys + .LocalizedFailureReason: + "Unable to parse array. Expected `,` to separate values. Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError + .code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + let value = try parse(generator) + values.append(value) + generator.replay() + needsComma = true + lastParsedComma = false + } + } + } + let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unexpected token at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - } - } - - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unable to parse object. Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - static func parseArray(_ generator: ReplayableGenerator) throws -> JSValue { - var values = [JSValue]() - var needsComma = false - var lastParsedComma = false - - for (idx, codeunit) in generator.enumerated() { - switch (idx, codeunit) { - case (0, Token.LeftBracket): continue - case (_, Token.RightBracket): - if lastParsedComma { + ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "Unable to parse array. Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + enum NumberParsingState { + case initial + case leadingZero + case leadingNegativeSign + case integer + case decimal + case fractional + case exponent + case exponentSign + case exponentDigits + } + + static func parseNumber(_ generator: ReplayableGenerator) throws -> JSValue { + // The https://tools.ietf.org/html/rfc7159 spec for numbers. + // number = [ minus ] int [ frac ] [ exp ] + // decimal-point = %x2E ; . + // digit1-9 = %x31-39 ; 1-9 + // e = %x65 / %x45 ; e E + // exp = e [ minus / plus ] 1*DIGIT + // frac = decimal-point 1*DIGIT + // int = zero / ( digit1-9 *DIGIT ) + // minus = %x2D ; - + // plus = %x2B ; + + // zero = %x30 ; 0 + + var sign: Double = 1 + var value: Double = 0 + var fraction: Double = 0 + var exponent: Double = 0 + var exponentSign: Double = 1 + var fractionMultiplier: Double = 10 + + var state: NumberParsingState = .initial + var valid = false + + loop: for (idx, codeunit) in generator.enumerated() { + switch (idx, codeunit, state) { + + case (_, Token.Minus, .initial): + sign = -1 + state = .leadingNegativeSign + + case (_, Token.Plus, .initial): + let info = [ + ErrorKeys.LocalizedDescription: ErrorCode + .ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "Leading plus is not supported. Index: \(idx). Token: \(codeunit). State: \(state). Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + + case (_, Token.Zero, .initial): + state = .leadingZero + + case (_, Token.Zero, .leadingNegativeSign): + state = .leadingZero + + case (_, Token.Zero...Token.Nine, .leadingZero): + let info = [ + ErrorKeys.LocalizedDescription: ErrorCode + .ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "Leading zeros are not allowed.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + case (_, Token.One...Token.Nine, .leadingNegativeSign), + (_, Token.One...Token.Nine, .initial): + value = value * 10 + Double(codeunit - Token.Zero) + state = .integer + + case (_, Token.Zero...Token.Nine, .decimal): + fraction = + Double(codeunit - Token.Zero) / fractionMultiplier + fractionMultiplier *= 10 + state = .fractional + + case (_, Token.Zero...Token.Nine, .integer): + value = value * 10 + Double(codeunit - Token.Zero) + + case (_, Token.Zero...Token.Nine, .fractional): + fraction = + fraction + + (Double(codeunit - Token.Zero) + / fractionMultiplier) + fractionMultiplier *= 10 + case (_, Token.e, .leadingZero), (_, Token.E, .leadingZero), + (_, Token.e, .integer), (_, Token.E, .integer), + (_, Token.e, .fractional), (_, Token.E, .fractional): + state = .exponent + + case (_, Token.Minus, .exponent): + exponentSign = -1 + state = .exponentSign + + case (_, Token.Plus, .exponent): + state = .exponentSign + + case (_, Token.Zero...Token.Nine, .exponent), + (_, Token.Zero...Token.Nine, .exponentSign), + (_, Token.Zero...Token.Nine, .exponentDigits): + exponent = exponent * 10 + Double(codeunit - Token.Zero) + state = .exponentDigits + case (_, Token.Period, .leadingZero), (_, Token.Period, .integer): + state = .decimal + + default: + if codeunit.isValidTerminator() { + generator.replay() + valid = true + break loop + } + else { + let info = [ + ErrorKeys.LocalizedDescription: + ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "Unexpected token at index: \(idx). Token: \(codeunit). State: \(state). Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + } + } + + if generator.atEnd() || valid { + if state == .decimal { + let info = [ + ErrorKeys.LocalizedDescription: ErrorCode + .ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "A trailing period is not allowed.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + if state == .exponent { + let info = [ + ErrorKeys.LocalizedDescription: ErrorCode + .ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "A trailing 'e' or 'E' is not allowed.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + if state == .exponentSign { + let info = [ + ErrorKeys.LocalizedDescription: ErrorCode + .ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "A exponent sign is not allowed.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + if state == .leadingNegativeSign { + let info = [ + ErrorKeys.LocalizedDescription: ErrorCode + .ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "A number cannot just be a leading negative sign.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + guard let exponentValue = Int(exactly: exponentSign * exponent) else { + let info = [ + ErrorKeys.LocalizedDescription: ErrorCode + .ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "The exponent is too large to process.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + return JSValue(sign * exp((value + fraction), exponentValue)) + } + let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "A trailing `,` is not supported. Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - let _ = generator.next() // eat the ']' - return JSValue(values) - case (_, Token.Comma): - if !needsComma { + ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "Unable to parse array. Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + static func parseTrue(_ generator: ReplayableGenerator) throws -> JSValue { + for (idx, codeunit) in generator.enumerated() { + switch (idx, codeunit) { + case (0, Token.t): continue + case (1, Token.r): continue + case (2, Token.u): continue + case (3, Token.e): continue + case (4, _): + if codeunit.isValidTerminator() { return JSValue(true) } + fallthrough + + default: + let info = [ + ErrorKeys.LocalizedDescription: ErrorCode + .ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "Unexpected token at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + } + + if generator.atEnd() { return JSValue(true) } + let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "A `,` can only separate values. Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - needsComma = false - lastParsedComma = true - - default: - if codeunit.isWhitespace() { continue } - else { - if needsComma { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unable to parse array. Expected `,` to separate values. Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) + ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "Unable to parse 'true' literal. Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + static func parseFalse(_ generator: ReplayableGenerator) throws -> JSValue { + for (idx, codeunit) in generator.enumerated() { + switch (idx, codeunit) { + case (0, Token.f): continue + case (1, Token.a): continue + case (2, Token.l): continue + case (3, Token.s): continue + case (4, Token.e): continue + case (5, _): + if codeunit.isValidTerminator() { return JSValue(false) } + fallthrough + + default: + let info = [ + ErrorKeys.LocalizedDescription: ErrorCode + .ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "Unexpected token at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } } - let value = try parse(generator) - values.append(value) - generator.replay() - needsComma = true - lastParsedComma = false - } - } - } - - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unable to parse array. Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - enum NumberParsingState { - case initial - case leadingZero - case leadingNegativeSign - case integer - case decimal - case fractional - case exponent - case exponentSign - case exponentDigits - } - - static func parseNumber(_ generator: ReplayableGenerator) throws -> JSValue { - // The https://tools.ietf.org/html/rfc7159 spec for numbers. - // number = [ minus ] int [ frac ] [ exp ] - // decimal-point = %x2E ; . - // digit1-9 = %x31-39 ; 1-9 - // e = %x65 / %x45 ; e E - // exp = e [ minus / plus ] 1*DIGIT - // frac = decimal-point 1*DIGIT - // int = zero / ( digit1-9 *DIGIT ) - // minus = %x2D ; - - // plus = %x2B ; + - // zero = %x30 ; 0 - - - var sign: Double = 1 - var value: Double = 0 - var fraction: Double = 0 - var exponent: Double = 0 - var exponentSign: Double = 1 - var fractionMultiplier: Double = 10 - - var state: NumberParsingState = .initial - var valid = false - - loop: for (idx, codeunit) in generator.enumerated() { - switch (idx, codeunit, state) { - - case (_, Token.Minus, .initial): - sign = -1 - state = .leadingNegativeSign - - case (_, Token.Plus, .initial): - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Leading plus is not supported. Index: \(idx). Token: \(codeunit). State: \(state). Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - - case (_, Token.Zero, .initial): - state = .leadingZero - - case (_, Token.Zero, .leadingNegativeSign): - state = .leadingZero - - case (_, Token.Zero...Token.Nine, .leadingZero): - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Leading zeros are not allowed."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - - case (_, Token.One...Token.Nine, .leadingNegativeSign): fallthrough - case (_, Token.One...Token.Nine, .initial): - value = value * 10 + Double(codeunit - Token.Zero) - state = .integer - - case (_, Token.Zero...Token.Nine, .decimal): - fraction = Double(codeunit - Token.Zero) / fractionMultiplier - fractionMultiplier *= 10 - state = .fractional - - case (_, Token.Zero...Token.Nine, .integer): - value = value * 10 + Double(codeunit - Token.Zero) - - case (_, Token.Zero...Token.Nine, .fractional): - fraction = fraction + (Double(codeunit - Token.Zero) / fractionMultiplier) - fractionMultiplier *= 10 - - case (_, Token.e, .leadingZero): fallthrough - case (_, Token.E, .leadingZero): fallthrough - case (_, Token.e, .integer): fallthrough - case (_, Token.E, .integer): fallthrough - case (_, Token.e, .fractional): fallthrough - case (_, Token.E, .fractional): - state = .exponent - - case (_, Token.Minus, .exponent): - exponentSign = -1 - state = .exponentSign - - case (_, Token.Plus, .exponent): - state = .exponentSign - - case (_, Token.Zero...Token.Nine, .exponent): fallthrough - case (_, Token.Zero...Token.Nine, .exponentSign): fallthrough - case (_, Token.Zero...Token.Nine, .exponentDigits): - exponent = exponent * 10 + Double(codeunit - Token.Zero) - state = .exponentDigits - - case (_, Token.Period, .leadingZero): fallthrough - case (_, Token.Period, .integer): - state = .decimal - - default: - if codeunit.isValidTerminator() { generator.replay(); valid = true; break loop } - else { + + if generator.atEnd() { return JSValue(false) } + let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unexpected token at index: \(idx). Token: \(codeunit). State: \(state). Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - } - } - - if generator.atEnd() || valid { - if state == .decimal { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "A trailing period is not allowed."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - if state == .exponent { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "A trailing 'e' or 'E' is not allowed."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - if state == .exponentSign { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "A exponent sign is not allowed."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - if state == .leadingNegativeSign { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "A number cannot just be a leading negative sign."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - guard let exponentValue = Int(exactly: exponentSign * exponent) else { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "The exponent is too large to process."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - return JSValue(sign * exp((value + fraction), exponentValue)) - } - - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unable to parse array. Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - static func parseTrue(_ generator: ReplayableGenerator) throws -> JSValue { - for (idx, codeunit) in generator.enumerated() { - switch (idx, codeunit) { - case (0, Token.t): continue - case (1, Token.r): continue - case (2, Token.u): continue - case (3, Token.e): continue - case (4, _): - if codeunit.isValidTerminator() { return JSValue(true) } - fallthrough - - default: - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unexpected token at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - } - - if generator.atEnd() { return JSValue(true) } - - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unable to parse 'true' literal. Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - static func parseFalse(_ generator: ReplayableGenerator) throws -> JSValue { - for (idx, codeunit) in generator.enumerated() { - switch (idx, codeunit) { - case (0, Token.f): continue - case (1, Token.a): continue - case (2, Token.l): continue - case (3, Token.s): continue - case (4, Token.e): continue - case (5, _): - if codeunit.isValidTerminator() { return JSValue(false) } - fallthrough - - default: - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unexpected token at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - } - - if generator.atEnd() { return JSValue(false) } - - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unable to parse 'false' literal. Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - static func parseNull(_ generator: ReplayableGenerator) throws -> JSValue { - for (idx, codeunit) in generator.enumerated() { - switch (idx, codeunit) { - case (0, Token.n): continue - case (1, Token.u): continue - case (2, Token.l): continue - case (3, Token.l): continue - case (4, _): - if codeunit.isValidTerminator() { return .null } - fallthrough - - default: - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unexpected token at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - } - - if generator.atEnd() { return .null } - - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unable to parse 'null' literal. Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - fileprivate static func parseHexDigit(_ digit: UInt8) -> Int? { - if Token.Zero <= digit && digit <= Token.Nine { - return Int(digit) - Int(Token.Zero) - } else if Token.a <= digit && digit <= Token.f { - return 10 + Int(digit) - Int(Token.a) - } else if Token.A <= digit && digit <= Token.F { - return 10 + Int(digit) - Int(Token.A) - } else { - return nil - } - } - - // Implementation Note: This is move outside here to avoid the re-allocation of this buffer each time. - // This has a significant impact on performance. - static var stringStorage: [UInt8] = [] - static func parseString(_ generator: ReplayableGenerator) throws -> JSValue { - stringStorage.removeAll(keepingCapacity: true) - - for (idx, codeunit) in generator.enumerated() { - switch (idx, codeunit) { - case (0, Token.DoubleQuote): continue - case (_, Token.DoubleQuote): - let _ = generator.next() // eat the quote - - if let string = String(bytes: stringStorage, encoding: .utf8) { - return JSValue(string) - } - else { + ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "Unable to parse 'false' literal. Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + static func parseNull(_ generator: ReplayableGenerator) throws -> JSValue { + for (idx, codeunit) in generator.enumerated() { + switch (idx, codeunit) { + case (0, Token.n): continue + case (1, Token.u): continue + case (2, Token.l): continue + case (3, Token.l): continue + case (4, _): + if codeunit.isValidTerminator() { return .null } + fallthrough + + default: + let info = [ + ErrorKeys.LocalizedDescription: ErrorCode + .ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "Unexpected token at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + } + + if generator.atEnd() { return .null } + let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unable to convert the parsed bytes into a string. Bytes: \(stringStorage)'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - case (_, Token.Null...Token.ShiftIn): + ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "Unable to parse 'null' literal. Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + fileprivate static func parseHexDigit(_ digit: UInt8) -> Int? { + if Token.Zero <= digit && digit <= Token.Nine { + return Int(digit) - Int(Token.Zero) + } + else if Token.a <= digit && digit <= Token.f { + return 10 + Int(digit) - Int(Token.a) + } + else if Token.A <= digit && digit <= Token.F { + return 10 + Int(digit) - Int(Token.A) + } + else { + return nil + } + } + + // Implementation Note: This is move outside here to avoid the re-allocation of this buffer each time. + // This has a significant impact on performance. + static var stringStorage: [UInt8] = [] + static func parseString(_ generator: ReplayableGenerator) throws -> JSValue { + stringStorage.removeAll(keepingCapacity: true) + + for (idx, codeunit) in generator.enumerated() { + switch (idx, codeunit) { + case (0, Token.DoubleQuote): continue + case (_, Token.DoubleQuote): + let _ = generator.next() // eat the quote + + if let string = String( + bytes: stringStorage, + encoding: .utf8 + ) { + return JSValue(string) + } + else { + let info = [ + ErrorKeys.LocalizedDescription: + ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "Unable to convert the parsed bytes into a string. Bytes: \(stringStorage)'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + case (_, Token.Null...Token.ShiftIn): + let info = [ + ErrorKeys.LocalizedDescription: ErrorCode + .ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "An unescaped control character is not allowed in a string.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + + case (_, Token.Backslash): + let next = generator.next() + + if let next = next { + switch next { + + case Token.Backslash: + stringStorage.append(Token.Backslash) + + case Token.Forwardslash: + stringStorage.append(Token.Forwardslash) + + case Token.DoubleQuote: + stringStorage.append(Token.DoubleQuote) + + case Token.n: + stringStorage.append(Token.Linefeed) + + case Token.b: + stringStorage.append(Token.Backspace) + + case Token.f: + stringStorage.append(Token.Formfeed) + + case Token.r: + stringStorage.append( + Token.CarriageReturn + ) + + case Token.t: + stringStorage.append( + Token.HorizontalTab + ) + + case Token.u: + let codeunits: [UInt16] + + let codeunit = try parseCodeUnit( + generator + ) + if UTF16.isLeadSurrogate(codeunit) { + guard + let next = + generator + .next() + else { + let info = [ + ErrorKeys + .LocalizedDescription: + ErrorCode + .ParsingError + .message, + ErrorKeys + .LocalizedFailureReason: + "Expected to find the second half of the surrogate pair.", + ] + throw + JsonParserError( + code: + ErrorCode + .ParsingError + .code, + domain: + JSValueErrorDomain, + userInfo: + info + ) + } + + if next != Token.Backslash { + let info = [ + ErrorKeys + .LocalizedDescription: + ErrorCode + .ParsingError + .message, + ErrorKeys + .LocalizedFailureReason: + "Invalid unicode scalar, expected \\.", + ] + throw + JsonParserError( + code: + ErrorCode + .ParsingError + .code, + domain: + JSValueErrorDomain, + userInfo: + info + ) + } + + guard + let next2 = + generator + .next() + else { + let info = [ + ErrorKeys + .LocalizedDescription: + ErrorCode + .ParsingError + .message, + ErrorKeys + .LocalizedFailureReason: + "Expected to find the second half of the surrogate pair.", + ] + throw + JsonParserError( + code: + ErrorCode + .ParsingError + .code, + domain: + JSValueErrorDomain, + userInfo: + info + ) + } + + if next2 != Token.u { + let info = [ + ErrorKeys + .LocalizedDescription: + ErrorCode + .ParsingError + .message, + ErrorKeys + .LocalizedFailureReason: + "Invalid unicode scalar, expected u.", + ] + throw + JsonParserError( + code: + ErrorCode + .ParsingError + .code, + domain: + JSValueErrorDomain, + userInfo: + info + ) + } + + let codeunit2 = + try parseCodeUnit( + generator + ) + codeunits = [ + codeunit, codeunit2, + ] + } + else { + codeunits = [codeunit] + } + + let transcodingError = transcode( + codeunits.makeIterator(), + from: UTF16.self, + to: UTF8.self, + stoppingOnError: true + ) { stringStorage.append($0) } + + if transcodingError { + let info = [ + ErrorKeys + .LocalizedDescription: + ErrorCode + .ParsingError + .message, + ErrorKeys + .LocalizedFailureReason: + "Invalid unicode scalar", + ] + throw JsonParserError( + code: ErrorCode + .ParsingError + .code, + domain: + JSValueErrorDomain, + userInfo: info + ) + } + + default: + let info = [ + ErrorKeys + .LocalizedDescription: + ErrorCode + .ParsingError + .message, + ErrorKeys + .LocalizedFailureReason: + "Unexpected token at index: \(idx + 1). Token: \(next). Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError + .code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + } + else { + let info = [ + ErrorKeys.LocalizedDescription: + ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "Unexpected token at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + default: + stringStorage.append(codeunit) + } + } + let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "An unescaped control character is not allowed in a string."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - - case (_, Token.Backslash): - let next = generator.next() - - if let next = next { - switch next { - - case Token.Backslash: - stringStorage.append(Token.Backslash) - - case Token.Forwardslash: - stringStorage.append(Token.Forwardslash) - - case Token.DoubleQuote: - stringStorage.append(Token.DoubleQuote) - - case Token.n: - stringStorage.append(Token.Linefeed) - - case Token.b: - stringStorage.append(Token.Backspace) - - case Token.f: - stringStorage.append(Token.Formfeed) - - case Token.r: - stringStorage.append(Token.CarriageReturn) - - case Token.t: - stringStorage.append(Token.HorizontalTab) - - case Token.u: - let codeunits: [UInt16] - - let codeunit = try parseCodeUnit(generator) - if UTF16.isLeadSurrogate(codeunit) { - guard let next = generator.next() else { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Expected to find the second half of the surrogate pair."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - if next != Token.Backslash { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Invalid unicode scalar, expected \\."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - guard let next2 = generator.next() else { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Expected to find the second half of the surrogate pair."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - if next2 != Token.u { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Invalid unicode scalar, expected u."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - let codeunit2 = try parseCodeUnit(generator) - codeunits = [codeunit, codeunit2] - } - else { - codeunits = [codeunit] - } - - let transcodingError = transcode(codeunits.makeIterator(), - from: UTF16.self, - to: UTF8.self, - stoppingOnError: true) { stringStorage.append($0) } - - if transcodingError { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Invalid unicode scalar"] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } + ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "Unable to parse string. Context: '\(contextualString(generator))'.", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + static func parseCodeUnit(_ generator: ReplayableGenerator) throws -> UInt16 { + let c1 = generator.next() + let c2 = generator.next() + let c3 = generator.next() + let c4 = generator.next() + + switch (c1, c2, c3, c4) { + case let (.some(c1), .some(c2), .some(c3), .some(c4)): + let value1 = parseHexDigit(c1) + let value2 = parseHexDigit(c2) + let value3 = parseHexDigit(c3) + let value4 = parseHexDigit(c4) + + if value1 == nil || value2 == nil || value3 == nil || value4 == nil { + let info = [ + ErrorKeys.LocalizedDescription: ErrorCode + .ParsingError.message, + ErrorKeys.LocalizedFailureReason: + "Invalid unicode escape sequence", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) + } + + return UInt16( + (value1! << 12) | (value2! << 8) | (value3! << 4) | value4! + ) default: - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unexpected token at index: \(idx + 1). Token: \(next). Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) + let info = [ + ErrorKeys.LocalizedDescription: ErrorCode.ParsingError + .message, + ErrorKeys.LocalizedFailureReason: + "Invalid unicode escape sequence", + ] + throw JsonParserError( + code: ErrorCode.ParsingError.code, + domain: JSValueErrorDomain, + userInfo: info + ) } - } - else { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unexpected token at index: \(idx). Token: \(codeunit). Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - default: - stringStorage.append(codeunit) - } - } - - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Unable to parse string. Context: '\(contextualString(generator))'."] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - static func parseCodeUnit(_ generator: ReplayableGenerator) throws -> UInt16 { - let c1 = generator.next() - let c2 = generator.next() - let c3 = generator.next() - let c4 = generator.next() - - switch (c1, c2, c3, c4) { - case let (.some(c1), .some(c2), .some(c3), .some(c4)): - let value1 = parseHexDigit(c1) - let value2 = parseHexDigit(c2) - let value3 = parseHexDigit(c3) - let value4 = parseHexDigit(c4) - - if value1 == nil || value2 == nil || value3 == nil || value4 == nil { - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Invalid unicode escape sequence"] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - - return UInt16((value1! << 12) | (value2! << 8) | (value3! << 4) | value4!) - - default: - let info = [ - ErrorKeys.LocalizedDescription: ErrorCode.ParsingError.message, - ErrorKeys.LocalizedFailureReason: "Invalid unicode escape sequence"] - throw JsonParserError(code: ErrorCode.ParsingError.code, domain: JSValueErrorDomain, userInfo: info) - } - } - - // MARK: Helper functions - - static func substring(_ generator: ReplayableGenerator) -> String { - var string = "" - - for codeunit in generator { - string += String(codeunit) - } - - return string - } - - - static func contextualString(_ generator: ReplayableGenerator, left: Int = 5, right: Int = 10) -> String { - var string = "" - - for _ in 0.. Double { - return exp < 0 ? - (0 ..< abs(exp)).reduce(number, { x, _ in x / 10 }) : - (0 ..< exp).reduce(number, { x, _ in x * 10 }) - } + } + + // MARK: Helper functions + + static func substring(_ generator: ReplayableGenerator) -> String { + var string = "" + + for codeunit in generator { + string += String(codeunit) + } + + return string + } + + static func contextualString( + _ generator: ReplayableGenerator, + left: Int = 5, + right: Int = 10 + ) -> String { + var string = "" + + for _ in 0.. Double { + return exp < 0 + ? (0.. Bool { - switch self { - case 0x09: return true /* horizontal tab */ - case 0x0A: return true /* line feed or new line */ - case 0x20: return true /* space */ - case 0x0D: return true /* carriage return */ - default: return false - } - } - - /// Determines if the `UnicodeScalar` represents a numeric digit. - /// - /// :return: `true` if the scalar is a Unicode numeric character; `false` otherwise. - func isDigit() -> Bool { - return self >= Token.Zero && self <= Token.Nine - } - - /// Determines if the `UnicodeScalar` represents a valid terminating character. - /// :return: `true` if the scalar is a valid terminator, `false` otherwise. - func isValidTerminator() -> Bool { - if self == Token.Comma { return true } - if self == Token.RightBracket { return true } - if self == Token.RightCurly { return true } - if self.isWhitespace() { return true } - - return false - } - - // /// Stores the `UInt8` bytes that make up the UTF8 code points for the scalar. - // /// - // /// :param: buffer the buffer to write the UTF8 code points into. - // func utf8(inout buffer: [UInt8]) { - // /* - // * This implementation should probably be replaced by the function below. However, - // * I am not quite sure how to properly use `SinkType` yet... - // * - // * UTF8.encode(input: UnicodeScalar, output: &S) - // */ - // - // if value <= 0x007F { - // buffer.append(UInt8(value)) - // } - // else if 0x0080 <= value && value <= 0x07FF { - // buffer.append(UInt8(value &/ 64) &+ 192) - // buffer.append(UInt8(value &% 64) &+ 128) - // } - // else if (0x0800 <= value && value <= 0xD7FF) || (0xE000 <= value && value <= 0xFFFF) { - // buffer.append(UInt8(value &/ 4096) &+ 224) - // buffer.append(UInt8((value &% 4096) &/ 64) &+ 128) - // buffer.append(UInt8(value &% 64 &+ 128)) - // } - // else { - // buffer.append(UInt8(value &/ 262144) &+ 240) - // buffer.append(UInt8((value &% 262144) &/ 4096) &+ 128) - // buffer.append(UInt8((value &% 4096) &/ 64) &+ 128) - // buffer.append(UInt8(value &% 64) &+ 128) - // } - // } + /// Determines if the `UnicodeScalar` represents one of the standard Unicode whitespace characters. + /// + /// :return: `true` if the scalar is a valid JSON whitespace character; `false` otherwise. + func isWhitespace() -> Bool { + switch self { + case 0x09: return true /* horizontal tab */ + case 0x0A: return true /* line feed or new line */ + case 0x20: return true /* space */ + case 0x0D: return true /* carriage return */ + default: return false + } + } + + /// Determines if the `UnicodeScalar` represents a numeric digit. + /// + /// :return: `true` if the scalar is a Unicode numeric character; `false` otherwise. + func isDigit() -> Bool { + return self >= Token.Zero && self <= Token.Nine + } + + /// Determines if the `UnicodeScalar` represents a valid terminating character. + /// :return: `true` if the scalar is a valid terminator, `false` otherwise. + func isValidTerminator() -> Bool { + if self == Token.Comma { return true } + if self == Token.RightBracket { return true } + if self == Token.RightCurly { return true } + if self.isWhitespace() { return true } + + return false + } + + // /// Stores the `UInt8` bytes that make up the UTF8 code points for the scalar. + // /// + // /// :param: buffer the buffer to write the UTF8 code points into. + // func utf8(inout buffer: [UInt8]) { + // /* + // * This implementation should probably be replaced by the function below. However, + // * I am not quite sure how to properly use `SinkType` yet... + // * + // * UTF8.encode(input: UnicodeScalar, output: &S) + // */ + // + // if value <= 0x007F { + // buffer.append(UInt8(value)) + // } + // else if 0x0080 <= value && value <= 0x07FF { + // buffer.append(UInt8(value &/ 64) &+ 192) + // buffer.append(UInt8(value &% 64) &+ 128) + // } + // else if (0x0800 <= value && value <= 0xD7FF) || (0xE000 <= value && value <= 0xFFFF) { + // buffer.append(UInt8(value &/ 4096) &+ 224) + // buffer.append(UInt8((value &% 4096) &/ 64) &+ 128) + // buffer.append(UInt8(value &% 64 &+ 128)) + // } + // else { + // buffer.append(UInt8(value &/ 262144) &+ 240) + // buffer.append(UInt8((value &% 262144) &/ 4096) &+ 128) + // buffer.append(UInt8((value &% 4096) &/ 64) &+ 128) + // buffer.append(UInt8(value &% 64) &+ 128) + // } + // } } /// The code unit value for all of the token characters used. struct Token { - fileprivate init() {} - - // Control Codes - static let Null = UInt8(0) - static let Backspace = UInt8(8) - static let HorizontalTab = UInt8(9) - static let Linefeed = UInt8(10) - static let Formfeed = UInt8(12) - static let CarriageReturn = UInt8(13) - static let ShiftIn = UInt8(15) - - // Tokens for JSON - static let LeftBracket = UInt8(91) - static let RightBracket = UInt8(93) - static let LeftCurly = UInt8(123) - static let RightCurly = UInt8(125) - static let Comma = UInt8(44) - static let SingleQuote = UInt8(39) - static let DoubleQuote = UInt8(34) - static let Minus = UInt8(45) - static let Plus = UInt8(43) - static let Backslash = UInt8(92) - static let Forwardslash = UInt8(47) - static let Colon = UInt8(58) - static let Period = UInt8(46) - - // Numbers - static let Zero = UInt8(48) - static let One = UInt8(49) - static let Two = UInt8(50) - static let Three = UInt8(51) - static let Four = UInt8(52) - static let Five = UInt8(53) - static let Six = UInt8(54) - static let Seven = UInt8(55) - static let Eight = UInt8(56) - static let Nine = UInt8(57) - - // Character tokens for JSON - static let A = UInt8(65) - static let E = UInt8(69) - static let F = UInt8(70) - static let a = UInt8(97) - static let b = UInt8(98) - static let e = UInt8(101) - static let f = UInt8(102) - static let l = UInt8(108) - static let n = UInt8(110) - static let r = UInt8(114) - static let s = UInt8(115) - static let t = UInt8(116) - static let u = UInt8(117) + fileprivate init() {} + + // Control Codes + static let Null = UInt8(0) + static let Backspace = UInt8(8) + static let HorizontalTab = UInt8(9) + static let Linefeed = UInt8(10) + static let Formfeed = UInt8(12) + static let CarriageReturn = UInt8(13) + static let ShiftIn = UInt8(15) + + // Tokens for JSON + static let LeftBracket = UInt8(91) + static let RightBracket = UInt8(93) + static let LeftCurly = UInt8(123) + static let RightCurly = UInt8(125) + static let Comma = UInt8(44) + static let SingleQuote = UInt8(39) + static let DoubleQuote = UInt8(34) + static let Minus = UInt8(45) + static let Plus = UInt8(43) + static let Backslash = UInt8(92) + static let Forwardslash = UInt8(47) + static let Colon = UInt8(58) + static let Period = UInt8(46) + + // Numbers + static let Zero = UInt8(48) + static let One = UInt8(49) + static let Two = UInt8(50) + static let Three = UInt8(51) + static let Four = UInt8(52) + static let Five = UInt8(53) + static let Six = UInt8(54) + static let Seven = UInt8(55) + static let Eight = UInt8(56) + static let Nine = UInt8(57) + + // Character tokens for JSON + static let A = UInt8(65) + static let E = UInt8(69) + static let F = UInt8(70) + static let a = UInt8(97) + static let b = UInt8(98) + static let e = UInt8(101) + static let f = UInt8(102) + static let l = UInt8(108) + static let n = UInt8(110) + static let r = UInt8(114) + static let s = UInt8(115) + static let t = UInt8(116) + static let u = UInt8(117) } diff --git a/Sources/JSONLib/JSValue.swift b/Sources/JSONLib/JSValue.swift index da7e802..e5081e0 100644 --- a/Sources/JSONLib/JSValue.swift +++ b/Sources/JSONLib/JSValue.swift @@ -7,233 +7,240 @@ public typealias JSON = JSValue /// The error domain for all `JSValue` related errors. -public let JSValueErrorDomain = "com.kiadstudios.json.error" +public let JSValueErrorDomain = "com.kiadstudios.json.error" /// A representative type for all possible JSON values. /// /// See http://json.org for a full description. -public enum JSValue : Equatable { - - /// The maximum integer that is safely representable in JavaScript. - public static let MaximumSafeInt: Int64 = 9007199254740991 - - /// The minimum integer that is safely representable in JavaScript. - public static let MinimumSafeInt: Int64 = -9007199254740991 - - /// Holds an array of JavaScript values that conform to valid `JSValue` types. - case array([JSValue]) - - /// Holds an unordered set of key/value pairs conforming to valid `JSValue` types. - case object([String : JSValue]) - - /// Holds the value conforming to JavaScript's String object. - case string(String) - - /// Holds the value conforming to JavaScript's Number object. - case number(Double) - - /// Holds the value conforming to JavaScript's Boolean wrapper. - case bool(Bool) - - /// Holds the value that corresponds to `null`. - case null - - /// Initializes a new `JSValue` with a `[JSValue]` value. - public init(_ value: [JSValue]) { - self = .array(value) - } - - /// Initializes a new `JSValue` with a `[String:JSValue]` value. - public init(_ value: [String:JSValue]) { - self = .object(value) - } - - /// Initializes a new `JSValue` with a `String` value. - public init(_ value: String) { - self = .string(value) - } - - /// Initializes a new `JSValue` with a `Double` value. - public init(_ value: Double) { - self = .number(value) - } - - /// Initializes a new `JSValue` with a `Bool` value. - public init(_ value: Bool) { - self = .bool(value) - } +public enum JSValue: Equatable { + + /// The maximum integer that is safely representable in JavaScript. + public static let MaximumSafeInt: Int64 = 9_007_199_254_740_991 + + /// The minimum integer that is safely representable in JavaScript. + public static let MinimumSafeInt: Int64 = -9_007_199_254_740_991 + + /// Holds an array of JavaScript values that conform to valid `JSValue` types. + case array([JSValue]) + + /// Holds an unordered set of key/value pairs conforming to valid `JSValue` types. + case object([String: JSValue]) + + /// Holds the value conforming to JavaScript's String object. + case string(String) + + /// Holds the value conforming to JavaScript's Number object. + case number(Double) + + /// Holds the value conforming to JavaScript's Boolean wrapper. + case bool(Bool) + + /// Holds the value that corresponds to `null`. + case null + + /// Initializes a new `JSValue` with a `[JSValue]` value. + public init(_ value: [JSValue]) { + self = .array(value) + } + + /// Initializes a new `JSValue` with a `[String:JSValue]` value. + public init(_ value: [String: JSValue]) { + self = .object(value) + } + + /// Initializes a new `JSValue` with a `String` value. + public init(_ value: String) { + self = .string(value) + } + + /// Initializes a new `JSValue` with a `Double` value. + public init(_ value: Double) { + self = .number(value) + } + + /// Initializes a new `JSValue` with a `Bool` value. + public init(_ value: Bool) { + self = .bool(value) + } } // All of the stupid number-type initializers because of the lack of type conversion. // Grr... convenience initializers not allowed in this context... // Also... without the labels, Swift cannot seem to actually get the type inference correct (6.1b3) extension JSValue { - /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. - public init(int8 value: Int8) { - self = .number(Double(value)) - } - - /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. - public init(in16 value: Int16) { - self = .number(Double(value)) - } - - /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. - public init(int32 value: Int32) { - self = .number(Double(value)) - } - - /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. - public init(int64 value: Int64) { - self = .number(Double(value)) - } - - /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. - public init(uint8 value: UInt8) { - self = .number(Double(value)) - } - - /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. - public init(uint16 value: UInt16) { - self = .number(Double(value)) - } - - /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. - public init(uint32 value: UInt32) { - self = .number(Double(value)) - } - - /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. - public init(uint64 value: UInt64) { - self = .number(Double(value)) - } - - /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. - public init(int value: Int) { - self = .number(Double(value)) - } - - /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. - public init(uint value: UInt) { - self = .number(Double(value)) - } - - /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. - public init(float value: Float) { - self = .number(Double(value)) - } + /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. + public init(int8 value: Int8) { + self = .number(Double(value)) + } + + /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. + public init(in16 value: Int16) { + self = .number(Double(value)) + } + + /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. + public init(int32 value: Int32) { + self = .number(Double(value)) + } + + /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. + public init(int64 value: Int64) { + self = .number(Double(value)) + } + + /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. + public init(uint8 value: UInt8) { + self = .number(Double(value)) + } + + /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. + public init(uint16 value: UInt16) { + self = .number(Double(value)) + } + + /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. + public init(uint32 value: UInt32) { + self = .number(Double(value)) + } + + /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. + public init(uint64 value: UInt64) { + self = .number(Double(value)) + } + + /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. + public init(int value: Int) { + self = .number(Double(value)) + } + + /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. + public init(uint value: UInt) { + self = .number(Double(value)) + } + + /// Convenience initializer for a `JSValue` with a non-standard `JSNumberType` value. + public init(float value: Float) { + self = .number(Double(value)) + } } extension JSValue: CustomStringConvertible { - - /// Attempts to convert the `JSValue` into its string representation. - /// - /// - parameter indent: the indent string to use; defaults to " ". If `nil` is - /// given, then the JSON is flattened. - /// - /// - returns: A `FailableOf` that will contain the `String` value if successful, - /// otherwise, the `Error` information for the conversion. - public func stringify(_ indent: String? = " ") -> String { - return prettyPrint(indent, 0) - } - - /// Attempts to convert the `JSValue` into its string representation. - /// - /// - parameter indent: the number of spaces to include. - /// - /// - returns: A `FailableOf` that will contain the `String` value if successful, - /// otherwise, the `Error` information for the conversion. - public func stringify(_ indent: Int) -> String { - let padding = (0..` that will contain the `String` value if successful, + /// otherwise, the `Error` information for the conversion. + public func stringify(_ indent: String? = " ") -> String { + return prettyPrint(indent, 0) + } + + /// Attempts to convert the `JSValue` into its string representation. + /// + /// - parameter indent: the number of spaces to include. + /// + /// - returns: A `FailableOf` that will contain the `String` value if successful, + /// otherwise, the `Error` information for the conversion. + public func stringify(_ indent: Int) -> String { + let padding = (0.. Bool { - switch (lhs, rhs) { - case (.null, .null): - return true - - case let (.bool(lhsValue), .bool(rhsValue)): - return lhsValue == rhsValue - - case let (.string(lhsValue), .string(rhsValue)): - return lhsValue == rhsValue - - case let (.number(lhsValue), .number(rhsValue)): - return lhsValue == rhsValue - - case let (.array(lhsValue), .array(rhsValue)): - return lhsValue == rhsValue - - case let (.object(lhsValue), .object(rhsValue)): - return lhsValue == rhsValue - - default: - return false - } +public func == (lhs: JSValue, rhs: JSValue) -> Bool { + switch (lhs, rhs) { + case (.null, .null): + return true + + case let (.bool(lhsValue), .bool(rhsValue)): + return lhsValue == rhsValue + + case let (.string(lhsValue), .string(rhsValue)): + return lhsValue == rhsValue + + case let (.number(lhsValue), .number(rhsValue)): + return lhsValue == rhsValue + + case let (.array(lhsValue), .array(rhsValue)): + return lhsValue == rhsValue + + case let (.object(lhsValue), .object(rhsValue)): + return lhsValue == rhsValue + + default: + return false + } } extension JSValue { - func prettyPrint(_ indent: String?, _ level: Int) -> String { - func escape(_ value: String) -> String { - return value - .replacingOccurrences(of: "\"", with: "\\\"") - .replacingOccurrences(of: "\t", with: "\\t") - .replacingOccurrences(of: "\n", with: "\\n") - .replacingOccurrences(of: "\r", with: "\\r") - } - - var currentIndent = "" - let nextIndentLevel = level + (indent == nil ? 0 : 1) - - for _ in (0.. String { + func escape(_ value: String) -> String { + return + value + .replacingOccurrences(of: "\"", with: "\\\"") + .replacingOccurrences(of: "\t", with: "\\t") + .replacingOccurrences(of: "\n", with: "\\n") + .replacingOccurrences(of: "\r", with: "\\r") + } + + var currentIndent = "" + let nextIndentLevel = level + (indent == nil ? 0 : 1) + + for _ in (0.. - var offset = 0 + var firstRun = true + var usePrevious = false + var previousElement: UInt8? = nil + var buffer: UnsafeBufferPointer + var offset = 0 - /// Initializes a new `ReplayableGenerator` with an underlying `UnsafeBufferPointer`. - /// - parameter sequence: the sequence that will be used to traverse the content. - public init(_ buffer: UnsafeBufferPointer) { - self.buffer = buffer - } + /// Initializes a new `ReplayableGenerator` with an underlying `UnsafeBufferPointer`. + /// - parameter sequence: the sequence that will be used to traverse the content. + public init(_ buffer: UnsafeBufferPointer) { + self.buffer = buffer + } - /// Moves the current element to the next element in the collection, if one exists. - /// :return: The `current` element or `nil` if the element does not exist. - public func next() -> Element? { - if usePrevious { - usePrevious = false - } - else { - previousElement = offset >= buffer.count ? nil : buffer[offset] - offset += 1 - } + /// Moves the current element to the next element in the collection, if one exists. + /// :return: The `current` element or `nil` if the element does not exist. + public func next() -> Element? { + if usePrevious { + usePrevious = false + } + else { + previousElement = offset >= buffer.count ? nil : buffer[offset] + offset += 1 + } - return previousElement - } + return previousElement + } - /// Ensures that the next call to `next()` will use the current value. - public func replay() { - usePrevious = true - } + /// Ensures that the next call to `next()` will use the current value. + public func replay() { + usePrevious = true + } - /// Creates a generator that can be used to traversed the content. Each call to - /// `generate` will call `replay()`. - /// - /// :return: A iterable collection backing the content. - public func makeIterator() -> ReplayableGenerator { - if firstRun { - firstRun = false - offset = 0 - usePrevious = false - previousElement = nil - } - else { - self.replay() - } + /// Creates a generator that can be used to traversed the content. Each call to + /// `generate` will call `replay()`. + /// + /// :return: A iterable collection backing the content. + public func makeIterator() -> ReplayableGenerator { + if firstRun { + firstRun = false + offset = 0 + usePrevious = false + previousElement = nil + } + else { + self.replay() + } - return self - } + return self + } - /// Determines if the generator is at the end of the collection's content. - /// - /// :return: `true` if there more content in the collection to view, `false` otherwise. - public func atEnd() -> Bool { - let element = next() - replay() + /// Determines if the generator is at the end of the collection's content. + /// + /// :return: `true` if there more content in the collection to view, `false` otherwise. + public func atEnd() -> Bool { + let element = next() + replay() - return element == nil - } + return element == nil + } } diff --git a/Sources/ParserPerfTestHarness/main.swift b/Sources/ParserPerfTestHarness/main.swift index afa9521..b723b2a 100644 --- a/Sources/ParserPerfTestHarness/main.swift +++ b/Sources/ParserPerfTestHarness/main.swift @@ -4,105 +4,120 @@ */ #if os(macOS) -import Foundation -import JSONLib -import QuartzCore - -// Must load locally. -// import Freddy - -func printUsage() -> Never { - print("usage: ParserPerfTestHarness [test file]") - exit(-1) -} - -struct Results: CustomStringConvertible { - let minimum: T - let maximum: T - let average: T - - var description: String { - return "min: \(minimum), max: \(maximum), avg: \(average)" - } -} - -func memory(iterations: Int = 10, fn: @escaping () throws -> Void) throws -> Results { - var min: Int = Int.max - var max: Int = Int.min - var counter: Int = 0 - - for _ in 0.. max { max = used } - counter += used - } - - return Results(minimum: min, maximum: max, average: Int(counter / iterations)) -} - -func performance(iterations: Int = 10, fn: () throws -> Void) throws -> Results { - var min: CFTimeInterval = 999999999999 - var max: CFTimeInterval = 0 - var counter: CFTimeInterval = 0 - - for _ in 0.. max { max = used } - counter += used - } - - return Results(minimum: min, maximum: max, average: counter / Double(iterations)) -} - -func diff(_ x: Int, _ y: Int) -> Int { - return -100 * (x - y) / y -} - -func diff(_ x: Double, _ y: Double) -> Double { - return -100 * (x - y) / y -} - - -if CommandLine.arguments.count != 2 { printUsage() } -let testFile = CommandLine.arguments[1] -if FileManager.default.fileExists(atPath: testFile) == false { - print("** file not found: \(testFile)") - exit(-2) -} - -guard let _contents = try? NSString(contentsOfFile: testFile, encoding: String.Encoding.utf8.rawValue) else { - print("** unable to load the file at: \(testFile)") - exit(-3) -} - -let contents = _contents as String -let data = contents.data(using: .utf8)! -let bytes = [UInt8](data) - -let filename = testFile.components(separatedBy: "/").last! -let shouldParse = filename.hasPrefix("y_") - - -print("NSJONSerialization:") -let nsjsonSerializationPerfResults = try performance { let _ = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) } -print("performance results: \(nsjsonSerializationPerfResults)") - -print("\nJSONLib:") -let jsonlibPerfResults = try performance { let _ = try JSON.parse(data) } -print("performance results: \(jsonlibPerfResults)") + import Foundation + import JSONLib + import QuartzCore + + // Must load locally. + // import Freddy + + func printUsage() -> Never { + print("usage: ParserPerfTestHarness [test file]") + exit(-1) + } + + struct Results: CustomStringConvertible { + let minimum: T + let maximum: T + let average: T + + var description: String { + return "min: \(minimum), max: \(maximum), avg: \(average)" + } + } + + func memory(iterations: Int = 10, fn: @escaping () throws -> Void) throws -> Results + { + var min: Int = Int.max + var max: Int = Int.min + var counter: Int = 0 + + for _ in 0.. max { max = used } + counter += used + } + + return Results(minimum: min, maximum: max, average: Int(counter / iterations)) + } + + func performance(iterations: Int = 10, fn: () throws -> Void) throws -> Results< + CFTimeInterval + > { + var min: CFTimeInterval = 999_999_999_999 + var max: CFTimeInterval = 0 + var counter: CFTimeInterval = 0 + + for _ in 0.. max { max = used } + counter += used + } + + return Results( + minimum: min, + maximum: max, + average: counter / Double(iterations) + ) + } + + func diff(_ x: Int, _ y: Int) -> Int { + return -100 * (x - y) / y + } + + func diff(_ x: Double, _ y: Double) -> Double { + return -100 * (x - y) / y + } + + if CommandLine.arguments.count != 2 { printUsage() } + let testFile = CommandLine.arguments[1] + if FileManager.default.fileExists(atPath: testFile) == false { + print("** file not found: \(testFile)") + exit(-2) + } + + guard + let _contents = try? NSString( + contentsOfFile: testFile, + encoding: String.Encoding.utf8.rawValue + ) + else { + print("** unable to load the file at: \(testFile)") + exit(-3) + } + + let contents = _contents as String + let data = contents.data(using: .utf8)! + let bytes = [UInt8](data) + + let filename = testFile.components(separatedBy: "/").last! + let shouldParse = filename.hasPrefix("y_") + + print("NSJONSerialization:") + let nsjsonSerializationPerfResults = try performance { + let _ = try? JSONSerialization.jsonObject( + with: data, + options: JSONSerialization.ReadingOptions.allowFragments + ) + } + print("performance results: \(nsjsonSerializationPerfResults)") + + print("\nJSONLib:") + let jsonlibPerfResults = try performance { let _ = try JSON.parse(data) } + print("performance results: \(jsonlibPerfResults)") // Must enable locally. // print("\nFreddy Results:") // let freddyPerfResults = try performance { let _ = try? JSON(data: data) } // print("performance results: \(freddyPerfResults.description)") #else -print("Sadly, only macOS is supported for the perf infrastructure at this time.") -#endif \ No newline at end of file + print("Sadly, only macOS is supported for the perf infrastructure at this time.") +#endif diff --git a/Sources/ParserTestHarness/main.swift b/Sources/ParserTestHarness/main.swift index 56c3379..abf2ef6 100644 --- a/Sources/ParserTestHarness/main.swift +++ b/Sources/ParserTestHarness/main.swift @@ -13,73 +13,73 @@ import JSONLib // import Freddy func printUsage() -> Never { - print("usage: ParserTestHarness [-freddy] -file:[test file]") - exit(-1) + print("usage: ParserTestHarness [-freddy] -file:[test file]") + exit(-1) } var useFreddy = false var file: String? = nil for arg in CommandLine.arguments { - if arg == "-freddy" { - useFreddy = true - } - else if arg.hasPrefix("-file") { - file = arg.replacingOccurrences(of: "-file:", with: "") - } + if arg == "-freddy" { + useFreddy = true + } + else if arg.hasPrefix("-file") { + file = arg.replacingOccurrences(of: "-file:", with: "") + } } if let testFile = file { - if FileManager.default.fileExists(atPath: testFile) == false { - print("** file not found: \(testFile)") - exit(-2) - } + if FileManager.default.fileExists(atPath: testFile) == false { + print("** file not found: \(testFile)") + exit(-2) + } - let filename = testFile.components(separatedBy: "/").last! - let shouldParse = filename.hasPrefix("y_") + let filename = testFile.components(separatedBy: "/").last! + let shouldParse = filename.hasPrefix("y_") - guard let data = try? Data(contentsOf: URL(fileURLWithPath: testFile)) else { - if shouldParse { - print("** unable to load the file at: \(testFile)") - exit(-3) - } - else { - print("expected failing parsing file: \(testFile)") - exit(0) - } - } + guard let data = try? Data(contentsOf: URL(fileURLWithPath: testFile)) else { + if shouldParse { + print("** unable to load the file at: \(testFile)") + exit(-3) + } + else { + print("expected failing parsing file: \(testFile)") + exit(0) + } + } - do { - if useFreddy { - // Must do this locally - // let _ = try JSON(data: data) - print("Must load Freddy locally") - exit(-101) - } - else { - let _ = try JSON.parse(data) - } - if shouldParse { - print("success parsing file: \(testFile)") - } - else { - print("** expected error while parsing file: \(testFile)") - exit(-5) - } - } - catch { - if shouldParse { - print("** error parsing file: \(testFile)") - print("--- ERROR INFO ---") - print("\(error)") - print("------------------") - exit(-4) - } - else { - print("expected failing parsing file: \(testFile)") - } - } + do { + if useFreddy { + // Must do this locally + // let _ = try JSON(data: data) + print("Must load Freddy locally") + exit(-101) + } + else { + let _ = try JSON.parse(data) + } + if shouldParse { + print("success parsing file: \(testFile)") + } + else { + print("** expected error while parsing file: \(testFile)") + exit(-5) + } + } + catch { + if shouldParse { + print("** error parsing file: \(testFile)") + print("--- ERROR INFO ---") + print("\(error)") + print("------------------") + exit(-4) + } + else { + print("expected failing parsing file: \(testFile)") + } + } } else { - printUsage() + printUsage() } diff --git a/Tests/JSONLibTests/JSValueEquatableTests.swift b/Tests/JSONLibTests/JSValueEquatableTests.swift index 801ebec..7779087 100644 --- a/Tests/JSONLibTests/JSValueEquatableTests.swift +++ b/Tests/JSONLibTests/JSValueEquatableTests.swift @@ -4,106 +4,107 @@ * ------------------------------------------------------------------------------------------ */ import XCTest + @testable import JSONLib class JSValueEquatableTests: XCTestCase { - func testEquatableNullTrue() { - let lhs: JSON = nil - let rhs: JSON = nil - - XCTAssertEqual(lhs, rhs) - } - - func testEquatableBoolTrue() { - let lhs: JSValue = true - let rhs: JSValue = true - - XCTAssertEqual(lhs, rhs) - } - - func testEquatableBoolFalse() { - let lhs: JSValue = true - let rhs: JSValue = false - - XCTAssertNotEqual(lhs, rhs) - } - - func testEquatableStringTrue() { - let lhs: JSValue = "hello" - let rhs: JSValue = "hello" - - XCTAssertEqual(lhs, rhs) - } - - func testEquatableStringFalse() { - let lhs: JSValue = "hello" - let rhs: JSValue = "bob" - - XCTAssertNotEqual(lhs, rhs) - } - - func testEquatableNumberTrue() { - let lhs: JSValue = 1234 - let rhs: JSValue = 1234 - - XCTAssertEqual(lhs, rhs) - } - - func testEquatableNumberFalse() { - let lhs: JSValue = 1234 - let rhs: JSValue = 4321 - - XCTAssertNotEqual(lhs, rhs) - } - - func testEquatableArrayTrue() { - let lhs: JSValue = [1, 3, 5] - let rhs: JSValue = [1, 3, 5] - - XCTAssertEqual(lhs, rhs) - } - - func testEquatableArrayFalse() { - let lhs: JSValue = [1, 3, 5] - let rhs: JSValue = [1, 3, 7] - - XCTAssertNotEqual(lhs, rhs) - } - - func testEquatableObjectTrue() { - let lhs: JSValue = ["key1": 1, "key2": 3, "key3": 5] - let rhs: JSValue = ["key1": 1, "key2": 3, "key3": 5] - - XCTAssertEqual(lhs, rhs) - } - - func testEquatableObjectFalse() { - let lhs: JSValue = ["key1": 1, "key2": 3, "key3": 5] - let rhs: JSValue = ["key0": 1, "key1": 3, "key2": 5] - - XCTAssertNotEqual(lhs, rhs) - } - - func testEquatableTypeMismatch() { - let lhs: JSValue = ["key1": 1, "key2": 3, "key3": 5] - let rhs: JSValue = [1, 3, 5] - - XCTAssertNotEqual(lhs, rhs) - } - - static let allTests = [ - ("testEquatableNullTrue", testEquatableNullTrue), - ("testEquatableBoolTrue", testEquatableBoolTrue), - ("testEquatableBoolFalse", testEquatableBoolFalse), - ("testEquatableStringTrue", testEquatableStringTrue), - ("testEquatableStringFalse", testEquatableStringFalse), - ("testEquatableNumberTrue", testEquatableNumberTrue), - ("testEquatableNumberFalse", testEquatableNumberFalse), - ("testEquatableArrayTrue", testEquatableArrayTrue), - ("testEquatableArrayFalse", testEquatableArrayFalse), - ("testEquatableObjectTrue", testEquatableObjectTrue), - ("testEquatableObjectFalse", testEquatableObjectFalse), - ("testEquatableTypeMismatch", testEquatableTypeMismatch), - ] + func testEquatableNullTrue() { + let lhs: JSON = nil + let rhs: JSON = nil + + XCTAssertEqual(lhs, rhs) + } + + func testEquatableBoolTrue() { + let lhs: JSValue = true + let rhs: JSValue = true + + XCTAssertEqual(lhs, rhs) + } + + func testEquatableBoolFalse() { + let lhs: JSValue = true + let rhs: JSValue = false + + XCTAssertNotEqual(lhs, rhs) + } + + func testEquatableStringTrue() { + let lhs: JSValue = "hello" + let rhs: JSValue = "hello" + + XCTAssertEqual(lhs, rhs) + } + + func testEquatableStringFalse() { + let lhs: JSValue = "hello" + let rhs: JSValue = "bob" + + XCTAssertNotEqual(lhs, rhs) + } + + func testEquatableNumberTrue() { + let lhs: JSValue = 1234 + let rhs: JSValue = 1234 + + XCTAssertEqual(lhs, rhs) + } + + func testEquatableNumberFalse() { + let lhs: JSValue = 1234 + let rhs: JSValue = 4321 + + XCTAssertNotEqual(lhs, rhs) + } + + func testEquatableArrayTrue() { + let lhs: JSValue = [1, 3, 5] + let rhs: JSValue = [1, 3, 5] + + XCTAssertEqual(lhs, rhs) + } + + func testEquatableArrayFalse() { + let lhs: JSValue = [1, 3, 5] + let rhs: JSValue = [1, 3, 7] + + XCTAssertNotEqual(lhs, rhs) + } + + func testEquatableObjectTrue() { + let lhs: JSValue = ["key1": 1, "key2": 3, "key3": 5] + let rhs: JSValue = ["key1": 1, "key2": 3, "key3": 5] + + XCTAssertEqual(lhs, rhs) + } + + func testEquatableObjectFalse() { + let lhs: JSValue = ["key1": 1, "key2": 3, "key3": 5] + let rhs: JSValue = ["key0": 1, "key1": 3, "key2": 5] + + XCTAssertNotEqual(lhs, rhs) + } + + func testEquatableTypeMismatch() { + let lhs: JSValue = ["key1": 1, "key2": 3, "key3": 5] + let rhs: JSValue = [1, 3, 5] + + XCTAssertNotEqual(lhs, rhs) + } + + static let allTests = [ + ("testEquatableNullTrue", testEquatableNullTrue), + ("testEquatableBoolTrue", testEquatableBoolTrue), + ("testEquatableBoolFalse", testEquatableBoolFalse), + ("testEquatableStringTrue", testEquatableStringTrue), + ("testEquatableStringFalse", testEquatableStringFalse), + ("testEquatableNumberTrue", testEquatableNumberTrue), + ("testEquatableNumberFalse", testEquatableNumberFalse), + ("testEquatableArrayTrue", testEquatableArrayTrue), + ("testEquatableArrayFalse", testEquatableArrayFalse), + ("testEquatableObjectTrue", testEquatableObjectTrue), + ("testEquatableObjectFalse", testEquatableObjectFalse), + ("testEquatableTypeMismatch", testEquatableTypeMismatch), + ] } diff --git a/Tests/JSONLibTests/JSValueTests.Indexers.swift b/Tests/JSONLibTests/JSValueTests.Indexers.swift index 9d55ec0..5eefdc7 100644 --- a/Tests/JSONLibTests/JSValueTests.Indexers.swift +++ b/Tests/JSONLibTests/JSValueTests.Indexers.swift @@ -3,77 +3,78 @@ * Licensed under the MIT License. See License in the project root for license information. * ------------------------------------------------------------------------------------------ */ -import XCTest import JSONLib +import XCTest + +class JSValueIndexersTests: XCTestCase { + + func testBasicArrayMutability() { + var value: JSON = [1, "Dog", 3.412, true] + XCTAssertTrue(value[0].number == 1) + XCTAssertTrue(value[1].string == "Dog") + XCTAssertTrue(value[2].number == 3.412) + XCTAssertTrue(value[3].bool == true) -class JSValueIndexersTests : XCTestCase { + let newValue: JSValue = "Cat" + value[1] = newValue + XCTAssertTrue(value[0].number == 1) + XCTAssertTrue(value[1].string == "Cat") + XCTAssertTrue(value[2].number == 3.412) + XCTAssertTrue(value[3].bool == true) - func testBasicArrayMutability() { - var value: JSON = [1, "Dog", 3.412, true] - XCTAssertTrue(value[0].number == 1) - XCTAssertTrue(value[1].string == "Dog") - XCTAssertTrue(value[2].number == 3.412) - XCTAssertTrue(value[3].bool == true) - - let newValue: JSValue = "Cat" - value[1] = newValue - XCTAssertTrue(value[0].number == 1) - XCTAssertTrue(value[1].string == "Cat") - XCTAssertTrue(value[2].number == 3.412) - XCTAssertTrue(value[3].bool == true) + value[1] = "Mouse" + XCTAssertTrue(value[0].number == 1) + XCTAssertTrue(value[1].string == "Mouse") + XCTAssertTrue(value[2].number == 3.412) + XCTAssertTrue(value[3].bool == true) + } - value[1] = "Mouse" - XCTAssertTrue(value[0].number == 1) - XCTAssertTrue(value[1].string == "Mouse") - XCTAssertTrue(value[2].number == 3.412) - XCTAssertTrue(value[3].bool == true) - } - - func testNestedArray() { - var value: JSON = [1, "Dog", [3.412, true]] - XCTAssertTrue(value[0].number == 1) - XCTAssertTrue(value[1].string == "Dog") + func testNestedArray() { + var value: JSON = [1, "Dog", [3.412, true]] + XCTAssertTrue(value[0].number == 1) + XCTAssertTrue(value[1].string == "Dog") - value[2][0] = 1.123 - XCTAssertTrue(value[2][0].number == 1.123) - XCTAssertTrue(value[2][1].bool == true) - } - - func testNestedMixedTypes() { - var json: JSON = [ - "stat": "ok", - "blogs": [ - "blog": [ - [ - "id": 73, - "name": "Bloxus test", - "needspassword": true, - "url": "http://remote.bloxus.com/" - ], - [ - "id": 74, - "name": "Manila Test", - "needspassword": false, - "url": "http://flickrtest1.userland.com/" + value[2][0] = 1.123 + XCTAssertTrue(value[2][0].number == 1.123) + XCTAssertTrue(value[2][1].bool == true) + } + + func testNestedMixedTypes() { + var json: JSON = [ + "stat": "ok", + "blogs": [ + "blog": [ + [ + "id": 73, + "name": "Bloxus test", + "needspassword": true, + "url": "http://remote.bloxus.com/", + ], + [ + "id": 74, + "name": "Manila Test", + "needspassword": false, + "url": + "http://flickrtest1.userland.com/", + ], + ] + ], ] - ] - ] - ] - - XCTAssertTrue(json["stat"].string == "ok") - XCTAssertTrue(json["blogs"]["blog"][0]["id"].number == 73) - XCTAssertTrue(json["blogs"]["blog"][0]["needspassword"].bool == true) - - json["blogs"]["blog"][0]["needspassword"] = false - XCTAssertTrue(json["stat"].string == "ok") - XCTAssertTrue(json["blogs"]["blog"][0]["id"].number == 73) - XCTAssertTrue(json["blogs"]["blog"][0]["needspassword"].bool == false) - } - static let allTests = [ - ("testBasicArrayMutability", testBasicArrayMutability), - ("testNestedArray", testNestedArray), - ("testNestedMixedTypes", testNestedMixedTypes), - ] + XCTAssertTrue(json["stat"].string == "ok") + XCTAssertTrue(json["blogs"]["blog"][0]["id"].number == 73) + XCTAssertTrue(json["blogs"]["blog"][0]["needspassword"].bool == true) + + json["blogs"]["blog"][0]["needspassword"] = false + XCTAssertTrue(json["stat"].string == "ok") + XCTAssertTrue(json["blogs"]["blog"][0]["id"].number == 73) + XCTAssertTrue(json["blogs"]["blog"][0]["needspassword"].bool == false) + } + + static let allTests = [ + ("testBasicArrayMutability", testBasicArrayMutability), + ("testNestedArray", testNestedArray), + ("testNestedMixedTypes", testNestedMixedTypes), + ] } diff --git a/Tests/JSONLibTests/JSValueTests.Literals.swift b/Tests/JSONLibTests/JSValueTests.Literals.swift index e7ee481..678396f 100644 --- a/Tests/JSONLibTests/JSValueTests.Literals.swift +++ b/Tests/JSONLibTests/JSValueTests.Literals.swift @@ -3,109 +3,112 @@ * Licensed under the MIT License. See License in the project root for license information. * ------------------------------------------------------------------------------------------ */ -import XCTest import JSONLib +import XCTest + +class JSValueLiteralsTests: XCTestCase { + + func testStringValue() { + let value: JSValue = "hello world" + XCTAssertTrue( + value.string?.compare("hello world") == ComparisonResult.orderedSame + ) + } + + func testCompareStringToNonStringValue() { + let value: JSValue = 1234 + XCTAssertFalse(value.string?.compare("1234") == ComparisonResult.orderedSame) + } + + func testIntegerValue() { + let value: JSValue = 123 + XCTAssertTrue(value.number == 123) + } + + func testDoubleValue() { + let value: JSValue = 3.1245123123 + XCTAssertTrue(value.number == 3.1245123123) + } + + func testBoolTrueValue() { + let value: JSValue = true + XCTAssertTrue(value.bool == true) + } -class JSValueLiteralsTests : XCTestCase { - - func testStringValue() { - let value: JSValue = "hello world" - XCTAssertTrue(value.string?.compare("hello world") == ComparisonResult.orderedSame) - } - - func testCompareStringToNonStringValue() { - let value: JSValue = 1234 - XCTAssertFalse(value.string?.compare("1234") == ComparisonResult.orderedSame) - } - - func testIntegerValue() { - let value: JSValue = 123 - XCTAssertTrue(value.number == 123) - } - - func testDoubleValue() { - let value: JSValue = 3.1245123123 - XCTAssertTrue(value.number == 3.1245123123) - } - - func testBoolTrueValue() { - let value: JSValue = true - XCTAssertTrue(value.bool == true) - } - - func testBoolFalseValue() { - let value: JSValue = false - XCTAssertTrue(value.bool == false) - } - - func testNilValue() { - let value: JSValue = nil - XCTAssertTrue(value.null) - } - - func testBasicArray() { - let value: JSON = [1, "Dog", 3.412, true] - XCTAssertTrue(value[0].number == 1) - XCTAssertTrue(value[1].string == "Dog") - XCTAssertTrue(value[2].number == 3.412) - XCTAssertTrue(value[3].bool == true) - } - - func testNestedArray() { - let value: JSON = [1, "Dog", [3.412, true]] - XCTAssertTrue(value[0].number == 1) - XCTAssertTrue(value[1].string == "Dog") - - // Usage #1 - if let array = value[2].array { - XCTAssertTrue(array[0].number == 3.412) - XCTAssertTrue(array[1].bool == true) - } - else { - XCTFail() - } - - // Usage #2 - XCTAssertTrue(value[2][0].number == 3.412) - XCTAssertTrue(value[2][1].bool == true) - } - - func testFlickrResult() { - var json: JSON = [ - "stat": "ok", - "blogs": [ - "blog": [ - [ - "id": 73, - "name": "Bloxus test", - "needspassword": true, - "url": "http://remote.bloxus.com/" - ], - [ - "id": 74, - "name": "Manila Test", - "needspassword": false, - "url": "http://flickrtest1.userland.com/" + func testBoolFalseValue() { + let value: JSValue = false + XCTAssertTrue(value.bool == false) + } + + func testNilValue() { + let value: JSValue = nil + XCTAssertTrue(value.null) + } + + func testBasicArray() { + let value: JSON = [1, "Dog", 3.412, true] + XCTAssertTrue(value[0].number == 1) + XCTAssertTrue(value[1].string == "Dog") + XCTAssertTrue(value[2].number == 3.412) + XCTAssertTrue(value[3].bool == true) + } + + func testNestedArray() { + let value: JSON = [1, "Dog", [3.412, true]] + XCTAssertTrue(value[0].number == 1) + XCTAssertTrue(value[1].string == "Dog") + + // Usage #1 + if let array = value[2].array { + XCTAssertTrue(array[0].number == 3.412) + XCTAssertTrue(array[1].bool == true) + } + else { + XCTFail() + } + + // Usage #2 + XCTAssertTrue(value[2][0].number == 3.412) + XCTAssertTrue(value[2][1].bool == true) + } + + func testFlickrResult() { + var json: JSON = [ + "stat": "ok", + "blogs": [ + "blog": [ + [ + "id": 73, + "name": "Bloxus test", + "needspassword": true, + "url": "http://remote.bloxus.com/", + ], + [ + "id": 74, + "name": "Manila Test", + "needspassword": false, + "url": + "http://flickrtest1.userland.com/", + ], + ] + ], ] - ] - ] - ] - - XCTAssertTrue(json["stat"].string == "ok") - XCTAssertTrue(json["blogs"]["blog"][0]["id"].number == 73) - XCTAssertTrue(json["blogs"]["blog"][0]["needspassword"].bool == true) - } - - static let allTests = [ - ("testStringValue", testStringValue), - ("testCompareStringToNonStringValue", testCompareStringToNonStringValue), - ("testIntegerValue", testIntegerValue), - ("testDoubleValue", testDoubleValue), - ("testBoolTrueValue", testBoolTrueValue), - ("testBoolFalseValue", testBoolFalseValue), - ("testNilValue", testNilValue), - ("testBasicArray", testBasicArray), - ("testNestedArray", testNestedArray), - ("testFlickrResult", testFlickrResult), - ] + + XCTAssertTrue(json["stat"].string == "ok") + XCTAssertTrue(json["blogs"]["blog"][0]["id"].number == 73) + XCTAssertTrue(json["blogs"]["blog"][0]["needspassword"].bool == true) + } + + static let allTests = [ + ("testStringValue", testStringValue), + ("testCompareStringToNonStringValue", testCompareStringToNonStringValue), + ("testIntegerValue", testIntegerValue), + ("testDoubleValue", testDoubleValue), + ("testBoolTrueValue", testBoolTrueValue), + ("testBoolFalseValue", testBoolFalseValue), + ("testNilValue", testNilValue), + ("testBasicArray", testBasicArray), + ("testNestedArray", testNestedArray), + ("testFlickrResult", testFlickrResult), + ] } diff --git a/Tests/JSONLibTests/JSValueTests.Parsing.swift b/Tests/JSONLibTests/JSValueTests.Parsing.swift index cac00b2..f8d73e0 100644 --- a/Tests/JSONLibTests/JSValueTests.Parsing.swift +++ b/Tests/JSONLibTests/JSValueTests.Parsing.swift @@ -3,711 +3,784 @@ * Licensed under the MIT License. See License in the project root for license information. * ------------------------------------------------------------------------------------------ */ -import XCTest import JSONLib +import XCTest + +class JSValueParsingTests: XCTestCase { + + override func setUp() { + self.continueAfterFailure = false + } + + func testParseNull() { + let string = "null" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertTrue(jsvalue.null == true) + } + + func testParseNullWithWhitespace() { + let string = " \r\n\n\r\t\t\t\tnull\t\t\t\t\t\t\t \n\r\r\n\n\n" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertTrue(jsvalue.null == true) + } + + func testParseNullInvalidJSON() { + let string = "null," + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue == nil) + // TODO: Validate the error information + } + + func testParseTrue() { + let string = "true" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertTrue(jsvalue.bool == true) + } + + func testParseTrueWithWhitespace() { + let string = " \r\n\n\r\t\t\t\ttrue\t\t\t\t\t\t\t \n\r\r\n\n\n" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertTrue(jsvalue.bool == true) + } + + func testParseTrueInvalidJSON() { + let string = "true#" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue == nil) + // TODO: Validate the error information + } + + func testParseFalse() { + let string = "false" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertTrue(jsvalue.bool == false) + } + + func testParseFalseWithWhitespace() { + let string = " \r\n\n\r\t\t\t\tfalse\t\t\t\t\t\t\t \n\r\r\n\n\n" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertTrue(jsvalue.bool == false) + } + + func testParseFalseInvalidJSON() { + let string = "false-" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue == nil) + // TODO: Validate the error information + } + + func testParseStringWithDoubleQuote() { + let string = "\"Bob\"" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertEqual(jsvalue.string, "Bob") + } + + func testParseStringWithSingleQuote() { + let string = "'Bob'" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue == nil) + } + + func testParseStringWithEscapedQuote() { + let string = "'Bob \"the man \" Roberts'" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue == nil) + } -class JSValueParsingTests : XCTestCase { - - override func setUp() { - self.continueAfterFailure = false - } - - func testParseNull() { - let string = "null" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertTrue(jsvalue.null == true) - } - - func testParseNullWithWhitespace() { - let string = " \r\n\n\r\t\t\t\tnull\t\t\t\t\t\t\t \n\r\r\n\n\n" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertTrue(jsvalue.null == true) - } - - func testParseNullInvalidJSON() { - let string = "null," - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue == nil) - // TODO: Validate the error information - } - - func testParseTrue() { - let string = "true" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertTrue(jsvalue.bool == true) - } - - func testParseTrueWithWhitespace() { - let string = " \r\n\n\r\t\t\t\ttrue\t\t\t\t\t\t\t \n\r\r\n\n\n" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertTrue(jsvalue.bool == true) - } - - func testParseTrueInvalidJSON() { - let string = "true#" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue == nil) - // TODO: Validate the error information - } - - func testParseFalse() { - let string = "false" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertTrue(jsvalue.bool == false) - } - - func testParseFalseWithWhitespace() { - let string = " \r\n\n\r\t\t\t\tfalse\t\t\t\t\t\t\t \n\r\r\n\n\n" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertTrue(jsvalue.bool == false) - } - - func testParseFalseInvalidJSON() { - let string = "false-" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue == nil) - // TODO: Validate the error information - } - - func testParseStringWithDoubleQuote() { - let string = "\"Bob\"" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertEqual(jsvalue.string, "Bob") - } - - func testParseStringWithSingleQuote() { - let string = "'Bob'" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue == nil) - } - - func testParseStringWithEscapedQuote() { - let string = "'Bob \"the man \" Roberts'" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue == nil) - } - - func testParseStringWithEscapedQuoteMatchingEndQuotes() { - let string = "\"Bob \\\"the man\\\" Roberts\"" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertEqual(jsvalue.string, "Bob \"the man\" Roberts") - } - - func testParseStringWithMultipleEscapes() { - let string = "\"e&\\\\첊xz坍崦ݻ鍴\\\"嵥B3\\u000b㢊\\u0015L臯.샥\"" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertEqual(jsvalue.string, "e&\\첊xz坍崦ݻ鍴\"嵥B3\u{000b}㢊\u{0015}L臯.샥") - } - - func testParseStringWithMultipleUnicodeTypes() { - let string = "\"(\u{20da}g8큽튣>^Y{뤋.袊䂓;_g]S\\u202a꽬L;^'#땏bႌ?C緡<䝲䲝断ꏏ6\\u001asD7IK5Wxo8\\u0006p弊⼂ꯍ扵\\u0003`뵂픋%ꄰ⫙됶l囏尛+䗅E쟇\\\\\"" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertEqual(jsvalue.string, "(\u{20da}g8큽튣>^Y{뤋.袊䂓;_g]S\u{202a}꽬L;^'#땏bႌ?C緡<䝲䲝断ꏏ6\u{001a}sD7IK5Wxo8\u{0006}p弊⼂ꯍ扵\u{0003}`뵂픋%ꄰ⫙됶l囏尛+䗅E쟇\\") - } - - func testParseStringWithTrailingEscapedQuotes() { - let string = "\"\\\"䬰ỐwD捾V`邀⠕VD㺝sH6[칑.:醥葹*뻵倻aD\\\"\"" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertEqual(jsvalue.string, "\"䬰ỐwD捾V`邀⠕VD㺝sH6[칑.:醥葹*뻵倻aD\"") - } - - func testParseInteger() { - let string = "101" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertTrue(jsvalue.number == 101) - } - - func testParseNegativeInteger() { - let string = "-109234" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertTrue(jsvalue.number == -109234) - } - - func testParseDouble() { - let string = "12.345678" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertEqualWithAccuracy(jsvalue!.number!, 12.345678, accuracy: 0.01) - } - - func testParseNegativeDouble() { - let string = "-123.949" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertEqualWithAccuracy(jsvalue!.number!, -123.949, accuracy: 0.01) - } - - func testParseExponent() { - let string = "12.345e2" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertEqualWithAccuracy(jsvalue!.number!, 12.345e2, accuracy: 0.01) - } - - func testParsePositiveExponent() { - let string = "12.345e+2" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertEqualWithAccuracy(jsvalue!.number!, 12.345e+2, accuracy: 0.01) - } - - func testParseNegativeExponent() { - let string = "-123.9492e-5" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertEqualWithAccuracy(jsvalue!.number!, -123.9492e-5, accuracy: 0.01) - } - - func testParseEmptyArray() { - let string = "[]" - let json = try? JSON.parse(string) - - XCTAssertTrue(json != nil) - if let json = json { - XCTAssertTrue(json.array != nil) - } - } - - func testSingleElementArray() { - let string = "[101]" - let json = try? JSON.parse(string) - - XCTAssertTrue(json != nil) - if let json = json { - XCTAssertTrue(json.array != nil) - XCTAssertTrue(json.array?.count == 1) - XCTAssertTrue(json[0] == 101) - } - } - - func testMultipleElementArray() { - let string = "[101, 202, 303]" - let json = try? JSON.parse(string) - - XCTAssertTrue(json != nil) - if let json = json { - XCTAssertTrue(json.array != nil) - XCTAssertTrue(json.array?.count == 3) - XCTAssertTrue(json[0] == 101) - XCTAssertTrue(json[1] == 202) - XCTAssertTrue(json[2] == 303) - } - } - - func testParseEmptyDictionary() { - let string = "{}" - let json = try? JSON.parse(string) - - XCTAssertTrue(json != nil) - if let json = json { - XCTAssertTrue(json.object != nil) - } - } - - func testParseEmptyDictionaryWithExtraWhitespace() { - let string = " {\r\n\n\n\n \t \t} \t \t" - let json = try? JSON.parse(string) - - XCTAssertTrue(json != nil) - if let json = json { - XCTAssertTrue(json.object != nil) - } - } - - func testParseDictionaryWithSingleKeyValuePair() { - let string = "{ \"key\": 101 }" - let json = try? JSON.parse(string) - - XCTAssertTrue(json != nil) - if let json = json { - XCTAssertTrue(json.object != nil) - XCTAssertEqual(json["key"].number!, 101) - } - } - - func testParseDictionaryWithMultipleKeyValuePairs() { - let string = "{ \"key1\": 101, \"key2\" : 202,\"key3\":303}" - let json = try? JSON.parse(string) - - XCTAssertTrue(json != nil) - if let json = json { - XCTAssertTrue(json.object != nil) - XCTAssertEqual(json["key1"].number!, 101) - XCTAssertEqual(json["key2"].number!, 202) - XCTAssertEqual(json["key3"].number!, 303) - } - } - - func testParseMixedArray() { - let string = "[1, -12, \"Bob\", true, false, null, -2.11234123]" - let json = try? JSON.parse(string) - - XCTAssertTrue(json != nil) - if let json = json { - XCTAssertTrue(json.array != nil) - XCTAssertEqual(json.array!.count, 7) - XCTAssertEqual(json[0].number!, 1) - XCTAssertEqual(json[1].number!, -12) - XCTAssertEqual(json[2].string!, "Bob") - XCTAssertEqual(json[3].bool!, true) - XCTAssertEqual(json[4].bool!, false) - XCTAssertEqual(json[5].null, true) - XCTAssertEqualWithAccuracy(json[6].number!, -2.11234123, accuracy: 0.01) - } - } - - func testParseMixedDictionary() { - let string = "{\"key1\": 1, \"key2\": -12, \"key3\": \"Bob\", \"key4\": true, \"key5\": false, \"key6\": null, \"key7\": -2.11234123}" - let json = try? JSON.parse(string) - - XCTAssertTrue(json != nil) - if let json = json { - XCTAssertTrue(json.object != nil) - XCTAssertEqual(json.object!.count, 7) - XCTAssertEqual(json["key1"].number!, 1) - XCTAssertEqual(json["key2"].number!, -12) - XCTAssertEqual(json["key3"].string!, "Bob") - XCTAssertEqual(json["key4"].bool!, true) - XCTAssertEqual(json["key5"].bool!, false) - XCTAssertEqual(json["key6"].null, true) - XCTAssertEqualWithAccuracy(json["key7"].number!, -2.11234123, accuracy: 0.01) } - } - - func testParseNestedMixedTypes() { - let string = "{\"key1\": 1, \"key2\": [ -12 , 12 ], \"key3\": \"Bob\", \"\\n鱿aK㝡␒㼙2촹f\": { \"foo\": \"bar\" }, \"key5\": false, \"key6\": null, \"key\\\"7\": -2.11234123}" - let json = try? JSON.parse(string) - - XCTAssertTrue(json != nil) - if let json = json { - XCTAssertTrue(json.object != nil) - XCTAssertEqual(json.object!.count, 7) - XCTAssertEqual(json["key1"].number!, 1) - XCTAssertTrue(json["key2"].array != nil) - XCTAssertEqual(json["key2"].array!.count, 2) - XCTAssertEqual(json["key2"][0].number!, -12) - XCTAssertEqual(json["key2"][1].number!, 12) - XCTAssertEqual(json["key3"].string!, "Bob") - XCTAssertTrue(json["\n鱿aK㝡␒㼙2촹f"].object != nil) - XCTAssertEqual(json["\n鱿aK㝡␒㼙2촹f"]["foo"].string!, "bar") - XCTAssertEqual(json["key5"].bool!, false) - XCTAssertEqual(json["key6"].null, true) - XCTAssertEqualWithAccuracy(json["key\"7"].number!, -2.11234123, accuracy: 0.01) - } - } - - func testParsePrettyPrintedNestedMixedTypes() { - let string = "{\"key1\": 1, \"key2\": [ -12 , 12 ], \"key3\": \"Bob\", \"\\n鱿aK㝡␒㼙2촹f\": { \"foo\": \"bar\" }, \"key5\": false, \"key6\": null, \"key\\\"7\": -2.11234123}" - let json1 = try? JSON.parse(string) - - XCTAssertTrue(json1 != nil) - - let prettyPrinted = json1?.stringify() ?? "" - let json2 = try? JSON.parse(prettyPrinted) - XCTAssertEqual(json1, json2) - } - - func testPrettyPrintedNestedObjectType() { - let string = "{\"key\": { \"foo\": \"bar\" }}" - let json1 = try? JSON.parse(string) - - XCTAssertTrue(json1 != nil) - - let prettyPrinted = json1?.stringify() ?? "" - XCTAssertEqual(prettyPrinted, "{\n \"key\": {\n \"foo\": \"bar\"\n }\n}") - } - - func testPrettyPrintedNestedArrayType() { - let string = "{\"key\": [ \"foo\", \"bar\" ]}" - let json1 = try? JSON.parse(string) - - XCTAssertTrue(json1 != nil) - - let prettyPrinted = json1?.stringify() ?? "" - XCTAssertEqual(prettyPrinted, "{\n \"key\": [\n \"foo\",\n \"bar\"\n ]\n}") - } - - func testMutipleNestedArrayDictionaryTypes() { - let string = "[[[[{},{},{\"ꫯ\":\"ꫯ\"}]]],[],[],[{}]]" - let json = try? JSON.parse(string) - - XCTAssertTrue(json != nil) - } - - func testParseStringWithSingleEscapedControlCharacters() { - let string = "\"\\n\"" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertEqual(jsvalue.string, "\n") - - let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false) - let json: Any! - do { - json = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments) - } catch _ { - json = nil - }; - let jsonString = json as! String - XCTAssertEqual("\n", jsonString) - } - - func testParseStringWithEscapedControlCharacters() { - let string = "\"\\\\\\/\\n\\r\\t\"" // "\\\/\n\r\t" => "\/\n\r\t" - let jsvalue = try? JSValue.parse(string) - - XCTAssertTrue(jsvalue != nil) - XCTAssertEqual(jsvalue.string, "\\/\n\r\t") - - let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false) - let json: Any! - do { - json = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments) - } catch _ { - json = nil - }; - let jsonString = json as! String - XCTAssertEqual(jsvalue.string, jsonString) - } - - func testParseStringWithUnicodeEscapes() { - let string = "\"value=\\u0026\\u03c6\\u00DF\"" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue != nil) - XCTAssertEqual(jsvalue.string, "value=&\u{03C6}ß") - } - - func testParseStringWithInvalidUnicodeEscapes() { - let string = "\"value=\\uxyz2\"" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - // TODO: Validate the error information - } - - func testParseStringWithSurrogatePairs() { - let string = "\"\\uD834\\uDD1E\"" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue != nil) - } - - func testParseInvalidArrayMissingComma() { - let string = "[1 true]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseInvalidArrayEmptyComma() { - let string = "[1,,true]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseInvalidArrayTrailingComma() { - let string = "[1,true,]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseStringUnescapedNewline() { - let string = "[\"new\nline\"]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseStringEscapedNewline() { - let string = "[\"new\\nline\"]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue != nil) - } - - func testParseStringUnescapedTab() { - let string = "[\"new\tline\"]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseStringEscapedTab() { - let string = "[\"new\\tline\"]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue != nil) - } - - func testParseNumberNetIntStartingWithZero() { - let string = "[-012]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumber0ePlus1() { - let string = "[0e+1]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue != nil) - } - - func testParseNumber0e1() { - let string = "[0e1]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue != nil) - } - - func testParseNumberCloseToZero() { - let string = "[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue != nil) - } - - func testParseNumberNegativeReal() { - let string = "-0.1" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue != nil) - } - - func testParseNumberPlusPlus() { - let string = "[++1234]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberPlus() { - let string = "[+1]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberTwoDot() { - let string = "[-2.]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberTwoDotE3() { - let string = "[2.e3]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberTwoDotEMinus3() { - let string = "[2.e-3]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberTwoDotEPlus3() { - let string = "[2.e+3]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberOneDot() { - let string = "[1.]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseEmpty() { - let string = "" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberZeroDotE1() { - let string = "[0.e1]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberLeadingDot() { - let string = "[.123]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberMinus() { - let string = "-" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberZeroDot3EPlus() { - let string = "0.3e+" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberZero3e() { - let string = "0.3e" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberZero3eMinus() { - let string = "0.e3-" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberOneDot0EPlus() { - let string = "1.0e+" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberOneDot0EMinus() { - let string = "1.0e-" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberOneDot0e() { - let string = "1.0e" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberOneDot0E() { - let string = "1.0E" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberZeroEPlus() { - let string = "0E+" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberZeroeMinus() { - let string = "0e-" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberZeroE() { - let string = "0E" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseObjectTrailingComma() { - let string = "{\"id\":0,}" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - - func testParseNumberHugeExp() { - let string = "[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006]" - let jsvalue = try? JSValue.parse(string) - XCTAssertTrue(jsvalue == nil) - } - -// TODO(owensd): This should be redone to support Linux as well. -#if os(macOS) - func testParsingSampleJSON() { - // SwiftBug(SR-4725) - Support test collateral properly - let path = NSString.path(withComponents: [Bundle(for: JSValueParsingTests.self).bundlePath, "..", "..", "..", "TestCollateral", "sample.json"]) - XCTAssertNotNil(path) - - let string: NSString? - do { - string = try NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue) - } catch _ { - string = nil - } - XCTAssertNotNil(string) - - let json = try? JSON.parse(string! as String) - XCTAssertTrue(json != nil) - } -#endif - - func testStringifyEscaping() { - let json: JSON = [ - "url" : "should escape double quotes \"" - ] - - let str = json.stringify(0) - let expected = "{\"url\":\"should escape double quotes \\\"\"}" - XCTAssertEqual(str, expected) - } - - static let allTests = [ - ("testParseNull", testParseNull), - ("testParseNullWithWhitespace", testParseNullWithWhitespace), - ("testParseNullInvalidJSON", testParseNullInvalidJSON), - ("testParseTrue", testParseTrue), - ("testParseTrueWithWhitespace", testParseTrueWithWhitespace), - ("testParseTrueInvalidJSON", testParseTrueInvalidJSON), - ("testParseFalse", testParseFalse), - ("testParseFalseWithWhitespace", testParseFalseWithWhitespace), - ("testParseFalseInvalidJSON", testParseFalseInvalidJSON), - ("testParseStringWithDoubleQuote", testParseStringWithDoubleQuote), - ("testParseStringWithSingleQuote", testParseStringWithSingleQuote), - ("testParseStringWithEscapedQuote", testParseStringWithEscapedQuote), - ("testParseStringWithEscapedQuoteMatchingEndQuotes", testParseStringWithEscapedQuoteMatchingEndQuotes), - ("testParseStringWithMultipleEscapes", testParseStringWithMultipleEscapes), - ("testParseStringWithMultipleUnicodeTypes", testParseStringWithMultipleUnicodeTypes), - ("testParseStringWithTrailingEscapedQuotes", testParseStringWithTrailingEscapedQuotes), - ("testParseInteger", testParseInteger), - ("testParseNegativeInteger", testParseNegativeInteger), - ("testParseDouble", testParseDouble), - ("testParseNegativeDouble", testParseNegativeDouble), - ("testParseExponent", testParseExponent), - ("testParsePositiveExponent", testParsePositiveExponent), - ("testParseNegativeExponent", testParseNegativeExponent), - ("testParseEmptyArray", testParseEmptyArray), - ("testSingleElementArray", testSingleElementArray), - ("testMultipleElementArray", testMultipleElementArray), - ("testParseEmptyDictionary", testParseEmptyDictionary), - ("testParseEmptyDictionaryWithExtraWhitespace", testParseEmptyDictionaryWithExtraWhitespace), - ("testParseDictionaryWithSingleKeyValuePair", testParseDictionaryWithSingleKeyValuePair), - ("testParseDictionaryWithMultipleKeyValuePairs", testParseDictionaryWithMultipleKeyValuePairs), - ("testParseMixedArray", testParseMixedArray), - ("testParseMixedDictionary", testParseMixedDictionary), - ("testParseNestedMixedTypes", testParseNestedMixedTypes), - ("testParsePrettyPrintedNestedMixedTypes", testParsePrettyPrintedNestedMixedTypes), - ("testPrettyPrintedNestedObjectType", testPrettyPrintedNestedObjectType), - ("testPrettyPrintedNestedArrayType", testPrettyPrintedNestedArrayType), - ("testMutipleNestedArrayDictionaryTypes", testMutipleNestedArrayDictionaryTypes), - ("testParseStringWithSingleEscapedControlCharacters", testParseStringWithSingleEscapedControlCharacters), - ("testParseStringWithEscapedControlCharacters", testParseStringWithEscapedControlCharacters), - ("testParseStringWithUnicodeEscapes", testParseStringWithUnicodeEscapes), - ("testParseStringWithInvalidUnicodeEscapes", testParseStringWithInvalidUnicodeEscapes), - ("testStringifyEscaping", testStringifyEscaping), - ("testParseStringWithSurrogatePairs", testParseStringWithSurrogatePairs) - ] + func testParseStringWithEscapedQuoteMatchingEndQuotes() { + let string = "\"Bob \\\"the man\\\" Roberts\"" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertEqual(jsvalue.string, "Bob \"the man\" Roberts") + } + + func testParseStringWithMultipleEscapes() { + let string = "\"e&\\\\첊xz坍崦ݻ鍴\\\"嵥B3\\u000b㢊\\u0015L臯.샥\"" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertEqual(jsvalue.string, "e&\\첊xz坍崦ݻ鍴\"嵥B3\u{000b}㢊\u{0015}L臯.샥") + } + + func testParseStringWithMultipleUnicodeTypes() { + let string = + "\"(\u{20da}g8큽튣>^Y{뤋.袊䂓;_g]S\\u202a꽬L;^'#땏bႌ?C緡<䝲䲝断ꏏ6\\u001asD7IK5Wxo8\\u0006p弊⼂ꯍ扵\\u0003`뵂픋%ꄰ⫙됶l囏尛+䗅E쟇\\\\\"" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertEqual( + jsvalue.string, + "(\u{20da}g8큽튣>^Y{뤋.袊䂓;_g]S\u{202a}꽬L;^'#땏bႌ?C緡<䝲䲝断ꏏ6\u{001a}sD7IK5Wxo8\u{0006}p弊⼂ꯍ扵\u{0003}`뵂픋%ꄰ⫙됶l囏尛+䗅E쟇\\" + ) + } + + func testParseStringWithTrailingEscapedQuotes() { + let string = "\"\\\"䬰ỐwD捾V`邀⠕VD㺝sH6[칑.:醥葹*뻵倻aD\\\"\"" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertEqual(jsvalue.string, "\"䬰ỐwD捾V`邀⠕VD㺝sH6[칑.:醥葹*뻵倻aD\"") + } + + func testParseInteger() { + let string = "101" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertTrue(jsvalue.number == 101) + } + + func testParseNegativeInteger() { + let string = "-109234" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertTrue(jsvalue.number == -109234) + } + + func testParseDouble() { + let string = "12.345678" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertEqualWithAccuracy(jsvalue!.number!, 12.345678, accuracy: 0.01) + } + + func testParseNegativeDouble() { + let string = "-123.949" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertEqualWithAccuracy(jsvalue!.number!, -123.949, accuracy: 0.01) + } + + func testParseExponent() { + let string = "12.345e2" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertEqualWithAccuracy(jsvalue!.number!, 12.345e2, accuracy: 0.01) + } + + func testParsePositiveExponent() { + let string = "12.345e+2" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertEqualWithAccuracy(jsvalue!.number!, 12.345e+2, accuracy: 0.01) + } + + func testParseNegativeExponent() { + let string = "-123.9492e-5" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertEqualWithAccuracy(jsvalue!.number!, -123.9492e-5, accuracy: 0.01) + } + + func testParseEmptyArray() { + let string = "[]" + let json = try? JSON.parse(string) + + XCTAssertTrue(json != nil) + if let json = json { + XCTAssertTrue(json.array != nil) + } + } + + func testSingleElementArray() { + let string = "[101]" + let json = try? JSON.parse(string) + + XCTAssertTrue(json != nil) + if let json = json { + XCTAssertTrue(json.array != nil) + XCTAssertTrue(json.array?.count == 1) + XCTAssertTrue(json[0] == 101) + } + } + + func testMultipleElementArray() { + let string = "[101, 202, 303]" + let json = try? JSON.parse(string) + + XCTAssertTrue(json != nil) + if let json = json { + XCTAssertTrue(json.array != nil) + XCTAssertTrue(json.array?.count == 3) + XCTAssertTrue(json[0] == 101) + XCTAssertTrue(json[1] == 202) + XCTAssertTrue(json[2] == 303) + } + } + + func testParseEmptyDictionary() { + let string = "{}" + let json = try? JSON.parse(string) + + XCTAssertTrue(json != nil) + if let json = json { + XCTAssertTrue(json.object != nil) + } + } + + func testParseEmptyDictionaryWithExtraWhitespace() { + let string = " {\r\n\n\n\n \t \t} \t \t" + let json = try? JSON.parse(string) + + XCTAssertTrue(json != nil) + if let json = json { + XCTAssertTrue(json.object != nil) + } + } + + func testParseDictionaryWithSingleKeyValuePair() { + let string = "{ \"key\": 101 }" + let json = try? JSON.parse(string) + + XCTAssertTrue(json != nil) + if let json = json { + XCTAssertTrue(json.object != nil) + XCTAssertEqual(json["key"].number!, 101) + } + } + + func testParseDictionaryWithMultipleKeyValuePairs() { + let string = "{ \"key1\": 101, \"key2\" : 202,\"key3\":303}" + let json = try? JSON.parse(string) + + XCTAssertTrue(json != nil) + if let json = json { + XCTAssertTrue(json.object != nil) + XCTAssertEqual(json["key1"].number!, 101) + XCTAssertEqual(json["key2"].number!, 202) + XCTAssertEqual(json["key3"].number!, 303) + } + } + + func testParseMixedArray() { + let string = "[1, -12, \"Bob\", true, false, null, -2.11234123]" + let json = try? JSON.parse(string) + + XCTAssertTrue(json != nil) + if let json = json { + XCTAssertTrue(json.array != nil) + XCTAssertEqual(json.array!.count, 7) + XCTAssertEqual(json[0].number!, 1) + XCTAssertEqual(json[1].number!, -12) + XCTAssertEqual(json[2].string!, "Bob") + XCTAssertEqual(json[3].bool!, true) + XCTAssertEqual(json[4].bool!, false) + XCTAssertEqual(json[5].null, true) + XCTAssertEqualWithAccuracy( + json[6].number!, + -2.11234123, + accuracy: 0.01 + ) + } + } + + func testParseMixedDictionary() { + let string = + "{\"key1\": 1, \"key2\": -12, \"key3\": \"Bob\", \"key4\": true, \"key5\": false, \"key6\": null, \"key7\": -2.11234123}" + let json = try? JSON.parse(string) + + XCTAssertTrue(json != nil) + if let json = json { + XCTAssertTrue(json.object != nil) + XCTAssertEqual(json.object!.count, 7) + XCTAssertEqual(json["key1"].number!, 1) + XCTAssertEqual(json["key2"].number!, -12) + XCTAssertEqual(json["key3"].string!, "Bob") + XCTAssertEqual(json["key4"].bool!, true) + XCTAssertEqual(json["key5"].bool!, false) + XCTAssertEqual(json["key6"].null, true) + XCTAssertEqualWithAccuracy( + json["key7"].number!, + -2.11234123, + accuracy: 0.01 + ) + } + } + + func testParseNestedMixedTypes() { + let string = + "{\"key1\": 1, \"key2\": [ -12 , 12 ], \"key3\": \"Bob\", \"\\n鱿aK㝡␒㼙2촹f\": { \"foo\": \"bar\" }, \"key5\": false, \"key6\": null, \"key\\\"7\": -2.11234123}" + let json = try? JSON.parse(string) + + XCTAssertTrue(json != nil) + if let json = json { + XCTAssertTrue(json.object != nil) + XCTAssertEqual(json.object!.count, 7) + XCTAssertEqual(json["key1"].number!, 1) + XCTAssertTrue(json["key2"].array != nil) + XCTAssertEqual(json["key2"].array!.count, 2) + XCTAssertEqual(json["key2"][0].number!, -12) + XCTAssertEqual(json["key2"][1].number!, 12) + XCTAssertEqual(json["key3"].string!, "Bob") + XCTAssertTrue(json["\n鱿aK㝡␒㼙2촹f"].object != nil) + XCTAssertEqual(json["\n鱿aK㝡␒㼙2촹f"]["foo"].string!, "bar") + XCTAssertEqual(json["key5"].bool!, false) + XCTAssertEqual(json["key6"].null, true) + XCTAssertEqualWithAccuracy( + json["key\"7"].number!, + -2.11234123, + accuracy: 0.01 + ) + } + } + + func testParsePrettyPrintedNestedMixedTypes() { + let string = + "{\"key1\": 1, \"key2\": [ -12 , 12 ], \"key3\": \"Bob\", \"\\n鱿aK㝡␒㼙2촹f\": { \"foo\": \"bar\" }, \"key5\": false, \"key6\": null, \"key\\\"7\": -2.11234123}" + let json1 = try? JSON.parse(string) + + XCTAssertTrue(json1 != nil) + + let prettyPrinted = json1?.stringify() ?? "" + let json2 = try? JSON.parse(prettyPrinted) + XCTAssertEqual(json1, json2) + } + + func testPrettyPrintedNestedObjectType() { + let string = "{\"key\": { \"foo\": \"bar\" }}" + let json1 = try? JSON.parse(string) + + XCTAssertTrue(json1 != nil) + + let prettyPrinted = json1?.stringify() ?? "" + XCTAssertEqual(prettyPrinted, "{\n \"key\": {\n \"foo\": \"bar\"\n }\n}") + } + + func testPrettyPrintedNestedArrayType() { + let string = "{\"key\": [ \"foo\", \"bar\" ]}" + let json1 = try? JSON.parse(string) + + XCTAssertTrue(json1 != nil) + + let prettyPrinted = json1?.stringify() ?? "" + XCTAssertEqual( + prettyPrinted, + "{\n \"key\": [\n \"foo\",\n \"bar\"\n ]\n}" + ) + } + + func testMutipleNestedArrayDictionaryTypes() { + let string = "[[[[{},{},{\"ꫯ\":\"ꫯ\"}]]],[],[],[{}]]" + let json = try? JSON.parse(string) + + XCTAssertTrue(json != nil) + } + + func testParseStringWithSingleEscapedControlCharacters() { + let string = "\"\\n\"" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertEqual(jsvalue.string, "\n") + + let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false) + let json: Any! + do { + json = try JSONSerialization.jsonObject( + with: data!, + options: JSONSerialization.ReadingOptions.allowFragments + ) + } + catch _ { + json = nil + } + let jsonString = json as! String + XCTAssertEqual("\n", jsonString) + } + + func testParseStringWithEscapedControlCharacters() { + let string = "\"\\\\\\/\\n\\r\\t\"" // "\\\/\n\r\t" => "\/\n\r\t" + let jsvalue = try? JSValue.parse(string) + + XCTAssertTrue(jsvalue != nil) + XCTAssertEqual(jsvalue.string, "\\/\n\r\t") + + let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false) + let json: Any! + do { + json = try JSONSerialization.jsonObject( + with: data!, + options: JSONSerialization.ReadingOptions.allowFragments + ) + } + catch _ { + json = nil + } + let jsonString = json as! String + XCTAssertEqual(jsvalue.string, jsonString) + } + + func testParseStringWithUnicodeEscapes() { + let string = "\"value=\\u0026\\u03c6\\u00DF\"" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue != nil) + XCTAssertEqual(jsvalue.string, "value=&\u{03C6}ß") + } + + func testParseStringWithInvalidUnicodeEscapes() { + let string = "\"value=\\uxyz2\"" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + // TODO: Validate the error information + } + + func testParseStringWithSurrogatePairs() { + let string = "\"\\uD834\\uDD1E\"" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue != nil) + } + + func testParseInvalidArrayMissingComma() { + let string = "[1 true]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseInvalidArrayEmptyComma() { + let string = "[1,,true]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseInvalidArrayTrailingComma() { + let string = "[1,true,]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseStringUnescapedNewline() { + let string = "[\"new\nline\"]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseStringEscapedNewline() { + let string = "[\"new\\nline\"]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue != nil) + } + + func testParseStringUnescapedTab() { + let string = "[\"new\tline\"]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseStringEscapedTab() { + let string = "[\"new\\tline\"]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue != nil) + } + + func testParseNumberNetIntStartingWithZero() { + let string = "[-012]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumber0ePlus1() { + let string = "[0e+1]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue != nil) + } + + func testParseNumber0e1() { + let string = "[0e1]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue != nil) + } + + func testParseNumberCloseToZero() { + let string = + "[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue != nil) + } + + func testParseNumberNegativeReal() { + let string = "-0.1" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue != nil) + } + + func testParseNumberPlusPlus() { + let string = "[++1234]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberPlus() { + let string = "[+1]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberTwoDot() { + let string = "[-2.]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberTwoDotE3() { + let string = "[2.e3]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberTwoDotEMinus3() { + let string = "[2.e-3]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberTwoDotEPlus3() { + let string = "[2.e+3]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberOneDot() { + let string = "[1.]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseEmpty() { + let string = "" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberZeroDotE1() { + let string = "[0.e1]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberLeadingDot() { + let string = "[.123]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberMinus() { + let string = "-" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberZeroDot3EPlus() { + let string = "0.3e+" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberZero3e() { + let string = "0.3e" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberZero3eMinus() { + let string = "0.e3-" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberOneDot0EPlus() { + let string = "1.0e+" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberOneDot0EMinus() { + let string = "1.0e-" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberOneDot0e() { + let string = "1.0e" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberOneDot0E() { + let string = "1.0E" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberZeroEPlus() { + let string = "0E+" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberZeroeMinus() { + let string = "0e-" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberZeroE() { + let string = "0E" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseObjectTrailingComma() { + let string = "{\"id\":0,}" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + func testParseNumberHugeExp() { + let string = + "[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006]" + let jsvalue = try? JSValue.parse(string) + XCTAssertTrue(jsvalue == nil) + } + + // TODO(owensd): This should be redone to support Linux as well. + #if os(macOS) + func testParsingSampleJSON() { + // SwiftBug(SR-4725) - Support test collateral properly + let path = NSString.path(withComponents: [ + Bundle(for: JSValueParsingTests.self).bundlePath, "..", + "..", "..", "TestCollateral", "sample.json", + ]) + XCTAssertNotNil(path) + + let string: NSString? + do { + string = try NSString( + contentsOfFile: path, + encoding: String.Encoding.utf8.rawValue + ) + } + catch _ { + string = nil + } + XCTAssertNotNil(string) + + let json = try? JSON.parse(string! as String) + XCTAssertTrue(json != nil) + } + #endif + + func testStringifyEscaping() { + let json: JSON = [ + "url": "should escape double quotes \"" + ] + + let str = json.stringify(0) + let expected = "{\"url\":\"should escape double quotes \\\"\"}" + XCTAssertEqual(str, expected) + } + + static let allTests = [ + ("testParseNull", testParseNull), + ("testParseNullWithWhitespace", testParseNullWithWhitespace), + ("testParseNullInvalidJSON", testParseNullInvalidJSON), + ("testParseTrue", testParseTrue), + ("testParseTrueWithWhitespace", testParseTrueWithWhitespace), + ("testParseTrueInvalidJSON", testParseTrueInvalidJSON), + ("testParseFalse", testParseFalse), + ("testParseFalseWithWhitespace", testParseFalseWithWhitespace), + ("testParseFalseInvalidJSON", testParseFalseInvalidJSON), + ("testParseStringWithDoubleQuote", testParseStringWithDoubleQuote), + ("testParseStringWithSingleQuote", testParseStringWithSingleQuote), + ("testParseStringWithEscapedQuote", testParseStringWithEscapedQuote), + ( + "testParseStringWithEscapedQuoteMatchingEndQuotes", + testParseStringWithEscapedQuoteMatchingEndQuotes + ), + ("testParseStringWithMultipleEscapes", testParseStringWithMultipleEscapes), + ( + "testParseStringWithMultipleUnicodeTypes", + testParseStringWithMultipleUnicodeTypes + ), + ( + "testParseStringWithTrailingEscapedQuotes", + testParseStringWithTrailingEscapedQuotes + ), + ("testParseInteger", testParseInteger), + ("testParseNegativeInteger", testParseNegativeInteger), + ("testParseDouble", testParseDouble), + ("testParseNegativeDouble", testParseNegativeDouble), + ("testParseExponent", testParseExponent), + ("testParsePositiveExponent", testParsePositiveExponent), + ("testParseNegativeExponent", testParseNegativeExponent), + ("testParseEmptyArray", testParseEmptyArray), + ("testSingleElementArray", testSingleElementArray), + ("testMultipleElementArray", testMultipleElementArray), + ("testParseEmptyDictionary", testParseEmptyDictionary), + ( + "testParseEmptyDictionaryWithExtraWhitespace", + testParseEmptyDictionaryWithExtraWhitespace + ), + ( + "testParseDictionaryWithSingleKeyValuePair", + testParseDictionaryWithSingleKeyValuePair + ), + ( + "testParseDictionaryWithMultipleKeyValuePairs", + testParseDictionaryWithMultipleKeyValuePairs + ), + ("testParseMixedArray", testParseMixedArray), + ("testParseMixedDictionary", testParseMixedDictionary), + ("testParseNestedMixedTypes", testParseNestedMixedTypes), + ( + "testParsePrettyPrintedNestedMixedTypes", + testParsePrettyPrintedNestedMixedTypes + ), + ("testPrettyPrintedNestedObjectType", testPrettyPrintedNestedObjectType), + ("testPrettyPrintedNestedArrayType", testPrettyPrintedNestedArrayType), + ( + "testMutipleNestedArrayDictionaryTypes", + testMutipleNestedArrayDictionaryTypes + ), + ( + "testParseStringWithSingleEscapedControlCharacters", + testParseStringWithSingleEscapedControlCharacters + ), + ( + "testParseStringWithEscapedControlCharacters", + testParseStringWithEscapedControlCharacters + ), + ("testParseStringWithUnicodeEscapes", testParseStringWithUnicodeEscapes), + ( + "testParseStringWithInvalidUnicodeEscapes", + testParseStringWithInvalidUnicodeEscapes + ), + ("testStringifyEscaping", testStringifyEscaping), + ("testParseStringWithSurrogatePairs", testParseStringWithSurrogatePairs), + ] } diff --git a/Tests/JSONLibTests/JSValueTests.Usage.swift b/Tests/JSONLibTests/JSValueTests.Usage.swift index 4faee8c..6d5f32b 100644 --- a/Tests/JSONLibTests/JSValueTests.Usage.swift +++ b/Tests/JSONLibTests/JSValueTests.Usage.swift @@ -3,106 +3,105 @@ * Licensed under the MIT License. See License in the project root for license information. * ------------------------------------------------------------------------------------------ */ -import XCTest import JSONLib +import XCTest /* * The purpose of these tests are to ensure the desired usage is maintained throughout all of the * refactoring that is taking place and will take place in the future. - * + * * Any breaks in these tests mean that usability of the API has been compromised. */ -class JSValueUsageTests : XCTestCase { +class JSValueUsageTests: XCTestCase { - func testValidateSingleValueStringUsagePatternIfLet() { - let json: JSValue = "Hello" - if let value = json.string { - XCTAssertEqual(value, "Hello") - } - else { - XCTFail() - } - } - - func testValidateSingleValueStringUsagePatternOptionalChaining() { - let json: JSValue = "Hello" - - let value = json.string?.uppercased() ?? "" - XCTAssertEqual(value, "HELLO") - } - - func testValidateSingleValueNumberUsagePatternIfLet() { - let json: JSValue = 123 - if let value = json.number { - XCTAssertEqual(value, 123) - } - else { - XCTFail() - } - } - - func testValidateSingleValueNumberUsagePatternOptionalChaining() { - let json: JSValue = 123 - - let value = json.number?.distance(to: 100) ?? 0 - XCTAssertEqual(value, -23) - } - - func testValidateSingleValueBoolUsagePatternIfLet() { - let json: JSValue = false - if let value = json.bool { - XCTAssertEqual(value, false) - } - else { - XCTFail() - } - } - - func testValidateSingleValueBoolUsagePatternOptionalChaining() { - let json: JSValue = true - - let value = json.bool ?? false - XCTAssertEqual(value, true) - } - - - func testValidateSingleLevelAccessInDictionaryUsage() { - var json: JSValue = ["status": "ok"] - - XCTAssertEqual(json["status"].string, "ok") - } - - func testValidateMultipleLevelAccessInDictionaryUsage() { - var json: JSValue = ["item": ["info": ["name": "Item #1"]]] - - XCTAssertEqual(json["item"]["info"]["name"].string, "Item #1") - } - - func testValidateMultipleLevelAccessInDictionaryUsageNonLiterals() { - let item1 = "Item #1" - - var json: JSValue = ["item": ["info": ["name": JSValue(item1) ]]] - - XCTAssertEqual(json["item"]["info"]["name"].string, "Item #1") - } - - func testValidateSingleLevelAccessInDictionaryUsageWithMissingKey() { - var json: JSValue = ["status": "ok"] - - XCTAssertTrue(json["stat"].string == nil) - } - - func testValidateArrayUsageNonLiterals() { - var array = [JSValue]() - array.append("Item #1") - - var json = JSValue(array) - - XCTAssertEqual(json[0].string, "Item #1") - } - - /* + func testValidateSingleValueStringUsagePatternIfLet() { + let json: JSValue = "Hello" + if let value = json.string { + XCTAssertEqual(value, "Hello") + } + else { + XCTFail() + } + } + + func testValidateSingleValueStringUsagePatternOptionalChaining() { + let json: JSValue = "Hello" + + let value = json.string?.uppercased() ?? "" + XCTAssertEqual(value, "HELLO") + } + + func testValidateSingleValueNumberUsagePatternIfLet() { + let json: JSValue = 123 + if let value = json.number { + XCTAssertEqual(value, 123) + } + else { + XCTFail() + } + } + + func testValidateSingleValueNumberUsagePatternOptionalChaining() { + let json: JSValue = 123 + + let value = json.number?.distance(to: 100) ?? 0 + XCTAssertEqual(value, -23) + } + + func testValidateSingleValueBoolUsagePatternIfLet() { + let json: JSValue = false + if let value = json.bool { + XCTAssertEqual(value, false) + } + else { + XCTFail() + } + } + + func testValidateSingleValueBoolUsagePatternOptionalChaining() { + let json: JSValue = true + + let value = json.bool ?? false + XCTAssertEqual(value, true) + } + + func testValidateSingleLevelAccessInDictionaryUsage() { + var json: JSValue = ["status": "ok"] + + XCTAssertEqual(json["status"].string, "ok") + } + + func testValidateMultipleLevelAccessInDictionaryUsage() { + var json: JSValue = ["item": ["info": ["name": "Item #1"]]] + + XCTAssertEqual(json["item"]["info"]["name"].string, "Item #1") + } + + func testValidateMultipleLevelAccessInDictionaryUsageNonLiterals() { + let item1 = "Item #1" + + var json: JSValue = ["item": ["info": ["name": JSValue(item1)]]] + + XCTAssertEqual(json["item"]["info"]["name"].string, "Item #1") + } + + func testValidateSingleLevelAccessInDictionaryUsageWithMissingKey() { + var json: JSValue = ["status": "ok"] + + XCTAssertTrue(json["stat"].string == nil) + } + + func testValidateArrayUsageNonLiterals() { + var array = [JSValue]() + array.append("Item #1") + + var json = JSValue(array) + + XCTAssertEqual(json[0].string, "Item #1") + } + + /* func testFunctionalParsingToStruct() { var json: JSON = [ "id" : 73, @@ -110,15 +109,15 @@ class JSValueUsageTests : XCTestCase { "needspassword" : true, "url" : "http://remote.bloxus.com/" ] - + let blog = make ⇒ (json["id"].number ⇒ toInt) ⇒ json["name"].string ⇒ json["needspassword"].bool ⇒ (json["url"].string ⇒ toURL) - + XCTAssertTrue(blog != nil) - + if let blog = blog { XCTAssertEqual(blog.id, 73) XCTAssertEqual(blog.name, "Bloxus test") @@ -126,201 +125,211 @@ class JSValueUsageTests : XCTestCase { XCTAssertEqual(blog.url, URL(string: "http://remote.bloxus.com/")!) } }*/ - - func testFunctionalParsingToStructIncorrectKey() { - var json: JSON = [ - "id" : 73, - "name" : "Bloxus test", - "password" : true, - "url" : "http://remote.bloxus.com/" - ] - - let id = json["id"] ⇒ toInt - let name = json["name"] ⇒ toString - let password = json["needspassword"] ⇒ toBool - let url = json["url"] ⇒ toURL - - _ = makeFailable ⇒ id ⇒ name ⇒ password ⇒ url - -// XCTAssertTrue(blog.1 != nil) -// if let error = blog.1 { -// XCTAssertEqual(error.code, JSValue.ErrorCode.KeyNotFound.code) -// } - } - - func testEnhancementRequest18() { - var object: JSON = [:] - object["one"] = 1 - - var array: [JSON] = [] - for index in 1...10 { - // nope... my stupid bug... - array.append(JSON(int: index)) - } - - object["array"] = JSON(array) - XCTAssertEqual(array.count, 10) - - var root: JSON = [] - root["object"] = object - } - -// Disabling these tests for now as they are not order deterministic. -// -// func testStringifyWithDefaultIndent() { -// var json: JSON = [ -// "id" : 73, -// "name" : "Bloxus test", -// "password" : true, -// "url" : "http://remote.bloxus.com/" -// ] -// -// let str = json.stringify() -// let expected = "{\n \"id\": 73.0,\n \"password\": true,\n \"name\": \"Bloxus test\",\n \"url\": \"http://remote.bloxus.com/\"\n}" -// XCTAssertEqual(str, expected) -// } -// -// func testStringifyWithNoIndent() { -// var json: JSON = [ -// "id" : 73, -// "name" : "Bloxus test", -// "password" : true, -// "url" : "http://remote.bloxus.com/" -// ] -// -// let str = json.stringify(0) -// let expected = "{\"id\":73.0,\"password\":true,\"name\":\"Bloxus test\",\"url\":\"http://remote.bloxus.com/\"}" -// XCTAssertEqual(str, expected) -// } - - func testStringifyWithFlatPrintOut() { - let json: JSON = [ - "id" : [ - "nested": [ - "value": "hi" - ] - ] - ] - - let str = json.stringify(nil) - let expected = "{\"id\": {\"nested\": {\"value\": \"hi\"}}}" - XCTAssertEqual(str, expected) - } - - func testStringifyWithIntegerValue() { - let json: JSON = [ - "id": 12 - ] - - let str = json.stringify(nil) - let expected = "{\"id\": 12}" - XCTAssertEqual(str, expected) - } - - func testStringifyWithDoubleValue() { - let json: JSON = [ - "id": 12.01 - ] - - let str = json.stringify(nil) - let expected = "{\"id\": 12.01}" - XCTAssertEqual(str, expected) - } + + func testFunctionalParsingToStructIncorrectKey() { + var json: JSON = [ + "id": 73, + "name": "Bloxus test", + "password": true, + "url": "http://remote.bloxus.com/", + ] + + let id = json["id"] ⇒ toInt + let name = json["name"] ⇒ toString + let password = json["needspassword"] ⇒ toBool + let url = json["url"] ⇒ toURL + + _ = makeFailable ⇒ id ⇒ name ⇒ password ⇒ url + + // XCTAssertTrue(blog.1 != nil) + // if let error = blog.1 { + // XCTAssertEqual(error.code, JSValue.ErrorCode.KeyNotFound.code) + // } + } + + func testEnhancementRequest18() { + var object: JSON = [:] + object["one"] = 1 + + var array: [JSON] = [] + for index in 1...10 { + // nope... my stupid bug... + array.append(JSON(int: index)) + } + + object["array"] = JSON(array) + XCTAssertEqual(array.count, 10) + + var root: JSON = [] + root["object"] = object + } + + // Disabling these tests for now as they are not order deterministic. + // + // func testStringifyWithDefaultIndent() { + // var json: JSON = [ + // "id" : 73, + // "name" : "Bloxus test", + // "password" : true, + // "url" : "http://remote.bloxus.com/" + // ] + // + // let str = json.stringify() + // let expected = "{\n \"id\": 73.0,\n \"password\": true,\n \"name\": \"Bloxus test\",\n \"url\": \"http://remote.bloxus.com/\"\n}" + // XCTAssertEqual(str, expected) + // } + // + // func testStringifyWithNoIndent() { + // var json: JSON = [ + // "id" : 73, + // "name" : "Bloxus test", + // "password" : true, + // "url" : "http://remote.bloxus.com/" + // ] + // + // let str = json.stringify(0) + // let expected = "{\"id\":73.0,\"password\":true,\"name\":\"Bloxus test\",\"url\":\"http://remote.bloxus.com/\"}" + // XCTAssertEqual(str, expected) + // } + + func testStringifyWithFlatPrintOut() { + let json: JSON = [ + "id": [ + "nested": [ + "value": "hi" + ] + ] + ] + + let str = json.stringify(nil) + let expected = "{\"id\": {\"nested\": {\"value\": \"hi\"}}}" + XCTAssertEqual(str, expected) + } + + func testStringifyWithIntegerValue() { + let json: JSON = [ + "id": 12 + ] + + let str = json.stringify(nil) + let expected = "{\"id\": 12}" + XCTAssertEqual(str, expected) + } + + func testStringifyWithDoubleValue() { + let json: JSON = [ + "id": 12.01 + ] + + let str = json.stringify(nil) + let expected = "{\"id\": 12.01}" + XCTAssertEqual(str, expected) + } } // MARK: Test Helpers struct Blog { - let id: Int - let name: String - let needsPassword : Bool - let url: URL + let id: Int + let name: String + let needsPassword: Bool + let url: URL } func toInt(_ number: Double?) -> Int? { - if let value = number { - return Int(value) - } - - return nil + if let value = number { + return Int(value) + } + + return nil } func toURL(_ string: String?) -> URL? { - if let url = string { - return URL(string: url) - } - - return nil + if let url = string { + return URL(string: url) + } + + return nil } func make(_ id: Int?) - -> (String?) - -> (Bool?) - -> (URL?) - -> Blog? + -> (String?) + -> (Bool?) + -> (URL?) + -> Blog? { - return { name in - return { needsPassword in - return { url in - if id == nil { return nil } - if name == nil { return nil } - if needsPassword == nil { return nil } - if url == nil { return nil } - - return Blog(id: id!, name: name!, needsPassword: needsPassword!, url: url!) - } - } - } + return { name in + return { needsPassword in + return { url in + if id == nil { return nil } + if name == nil { return nil } + if needsPassword == nil { return nil } + if url == nil { return nil } + + return Blog( + id: id!, + name: name!, + needsPassword: needsPassword!, + url: url! + ) + } + } + } } func makeFailable(_ id: Int?) - -> (String?) - -> (Bool?) - -> (URL?) - -> Blog? + -> (String?) + -> (Bool?) + -> (URL?) + -> Blog? { - return { name in - return { needsPassword in - return { url in - guard let id = id else { return nil } - guard let name = name else { return nil } - guard let needsPassword = needsPassword else { return nil } - guard let url = url else { return nil } - - return Blog(id: id, name: name, needsPassword: needsPassword, url: url) - } - } - } + return { name in + return { needsPassword in + return { url in + guard let id = id else { return nil } + guard let name = name else { return nil } + guard let needsPassword = needsPassword else { return nil } + guard let url = url else { return nil } + + return Blog( + id: id, + name: name, + needsPassword: needsPassword, + url: url + ) + } + } + } } func toInt(_ value: JSValue) -> Int? { - if let value = value.number { - return Int(value) - } - - return nil + if let value = value.number { + return Int(value) + } + + return nil } func toURL(_ value: JSValue) -> URL? { - if let url = value.string { - return URL(string: url) - } - - return nil + if let url = value.string { + return URL(string: url) + } + + return nil } func toBool(_ value: JSValue) -> Bool? { - if let bool = value.bool { - return bool - } - - return nil + if let bool = value.bool { + return bool + } + + return nil } func toString(_ value: JSValue) -> String? { - if let string = value.string { - return string - } - - return nil + if let string = value.string { + return string + } + + return nil } diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index ccca867..c21ba85 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -4,11 +4,12 @@ * ------------------------------------------------------------------------------------------ */ import XCTest + @testable import JSONLibTests XCTMain([ - testCase(JSValueEquatableTests.allTests), - testCase(JSValueIndexersTests.allTests), - testCase(JSValueLiteralsTests.allTests), - testCase(JSValueParsingTests.allTests), -]) \ No newline at end of file + testCase(JSValueEquatableTests.allTests), + testCase(JSValueIndexersTests.allTests), + testCase(JSValueLiteralsTests.allTests), + testCase(JSValueParsingTests.allTests), +])