Revolucent icon

JSON parser combinators using Parsimonious

Revolucent | PRO | 04/13/19 09:07:22 AM UTC | 0 ⭐ | 1079 👁️ | Never ⏰ | []
Swift |

2.15 KB

|

None

|

0 👍

/

0 👎

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