Skip to content

Instantly share code, notes, and snippets.

@nerdsupremacist
Last active August 13, 2019 19:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nerdsupremacist/8558223183db9d9a71fbfe790fdadf0a to your computer and use it in GitHub Desktop.
Save nerdsupremacist/8558223183db9d9a71fbfe790fdadf0a to your computer and use it in GitHub Desktop.
JSON Parser built using Ogma
import Ogma
public enum JSON {
case dictionary([String : JSON])
case array([JSON])
case int(Int)
case double(Double)
case string(String)
case bool(Bool)
case null
}
extension JSON {
public enum Token: TokenProtocol {
case openCurlyBracket
case closeCurlyBracket
case openSquareBracket
case closeSquareBracket
case comma
case colon
case `true`
case `false`
case string(String)
case double(Double)
case int(Int)
case null
}
}
extension JSON.Token {
var string: String? {
guard case .string(let string) = self else { return nil }
return string
}
var int: Int? {
guard case .int(let int) = self else { return nil }
return int
}
var double: Double? {
guard case .double(let double) = self else { return nil }
return double
}
}
extension JSON {
enum Lexer: GeneratorLexer {
typealias Token = JSON.Token
static let generators: Generators = [
RegexTokenGenerator(pattern: "\\{").map(to: .openCurlyBracket),
RegexTokenGenerator(pattern: "\\}").map(to: .closeCurlyBracket),
RegexTokenGenerator(pattern: "\\[").map(to: .openSquareBracket),
RegexTokenGenerator(pattern: "\\]").map(to: .closeSquareBracket),
RegexTokenGenerator(pattern: ",").map(to: .comma),
RegexTokenGenerator(pattern: ":").map(to: .colon),
RegexTokenGenerator(pattern: "true\\b").map(to: .true),
RegexTokenGenerator(pattern: "false\\b").map(to: .false),
RegexTokenGenerator(pattern: "null\\b").map(to: .null),
StringLiteralTokenGenerator().map(Token.string),
DoubleLiteralTokenGenerator().map(Token.double),
IntLiteralTokenGenerator().map(Token.int),
WhiteSpaceTokenGenerator().ignore(),
]
}
}
extension Bool: Parsable {
public static let parser: AnyParser<JSON.Token, Bool> = Token.true.map { true } ||
Token.false.map { false }
}
extension String: Parsable {
public static let parser: AnyParser<JSON.Token, String> = .consuming(keyPath: \.string)
}
extension Int: Parsable {
public static let parser: AnyParser<JSON.Token, Int> = .consuming(keyPath: \.int)
}
extension Double: Parsable {
public static let parser: AnyParser<JSON.Token, Double> = .consuming(keyPath: \.double)
}
extension Array: Parsable where Element: Parsable, Element.Token == JSON.Token {
public typealias Token = JSON.Token
public static let parser: AnyParser<JSON.Token, Array<JSON>> = Element
.separated(by: .comma, allowsEmpty: true)
.wrapped(by: .openSquareBracket, and: .closeSquareBracket)
}
extension Dictionary: Parsable where Key: Parsable, Key.Token == JSON.Token, Value: Parsable, Value.Token == JSON.Token {
public typealias Token = JSON.Token
public static var parser: AnyParser<JSON.Token, Dictionary<Key, Value>> {
let element = Key.self && .colon && Value.self
return element
.separated(by: .comma, allowsTrailingSeparator: true, allowsEmpty: true)
.map { Dictionary($0, uniquingKeysWith: { $1 }) }
.wrapped(by: .openCurlyBracket, and: .closeCurlyBracket)
}
}
extension JSON: Parsable {
public static let parser: AnyParser<Token, JSON> = Bool.map(JSON.bool) ||
Int.map(JSON.int) ||
Double.map(JSON.double) ||
String.map(JSON.string) ||
Token.null.map { .null } ||
Array<JSON>.map(JSON.array) ||
Dictionary<String, JSON>.map(JSON.dictionary)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment