import XCTest @testable import Parsimonious indirect enum JSON { case string(String) case number(NSNumber) case boolean(Bool) case object([String: JSON]) case array([JSON]) } let escape = "\\" let quote = "\"" let quotation = char(quote) *> manyS(char(all: !escape, !quote) | (char(escape) *> char(any: escape, quote))) <* char(quote) let jstring = JSON.string <*> quotation func jnumber(_ context: Context) throws -> JSON { let digits = many1S("0123456789") let num = digits + optionalS(char(".") + digits) let formatter = NumberFormatter() formatter.numberStyle = .decimal let ns = try context <- num guard let n = formatter.number(from: ns) else { throw ParseError(message: "Expected a number, but got \(ns).", context: context) } return JSON.number(n) } let jbool = {s in JSON.boolean(s == "true")} <*> string("true") | string("false") | fail("Expected a boolean value.") let ws = manyS(\Character.isWhitespace) func jarray(_ context: Context) throws -> JSON { return try context <- JSON.array <*> char("[") *> ws *> many(json <* ws, sepBy: ws + char(",") + ws) <* char("]") } func jpair(_ context: Context) throws -> (key: String, value: JSON) { return try context.transact { let key = try context <- quotation try context <- ws + char(":") + ws let value = try context <- json return (key, value) } } func jobject(_ context: Context) throws -> JSON { return try context.transact { try context <- char("{") <* ws let pairs = try context <- many(jpair <* ws, sepBy: ws + char(",") + ws) try context <- char("}") var object: [String: JSON] = [:] for pair in pairs { object[pair.key] = pair.value } return JSON.object(object) } } let json = jstring | jnumber | jbool | jarray | jobject class ParsimoniousTests: XCTestCase { func testParser() { let s = """ { "foo": "bar", "x": [7, 9, 11.14, {"yes": true}] } """ try XCTAssertNoThrow(parse(s, with: ws *> json <* ws)) } }