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<String>) 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<String>) throws -> JSON {
return try context <- JSON.array <*> char("[") *> ws *> many(json <* ws, sepBy: ws + char(",") + ws) <* char("]")
}
func jpair(_ context: Context<String>) 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<String>) 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))
}
}
Comments