1
1
mirror of https://github.com/github/semantic.git synced 2025-01-04 21:47:07 +03:00

Merge pull request #125 from github/parse-json

Add JSONParser
This commit is contained in:
Rob Rix 2015-10-21 11:37:30 -04:00
commit 3ba2585165
7 changed files with 230 additions and 101 deletions

3
.gitmodules vendored
View File

@ -4,6 +4,9 @@
[submodule "prototype/External/Stream"]
path = prototype/External/Stream
url = https://github.com/antitypical/Stream.git
[submodule "prototype/External/Madness"]
path = prototype/External/Madness
url = https://github.com/robrix/Madness
[submodule "prototype/External/SwiftCheck"]
path = prototype/External/SwiftCheck
url = https://github.com/typelift/SwiftCheck.git

View File

@ -7,6 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
D18FF8821BD0404700D18F2D /* Madness.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D18FF8811BD0404700D18F2D /* Madness.framework */; };
D1A0934E1BD18969005A6326 /* JSONLeaf.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A0934C1BD188F5005A6326 /* JSONLeaf.swift */; settings = {ASSET_TAGS = (); }; };
D1A0934F1BD189B4005A6326 /* JSONParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A0934A1BD188CA005A6326 /* JSONParser.swift */; settings = {ASSET_TAGS = (); }; };
D40B89C41BC319070078E098 /* Matrix.swift in Sources */ = {isa = PBXBuildFile; fileRef = D40B89C31BC319070078E098 /* Matrix.swift */; settings = {ASSET_TAGS = (); }; };
D40B89C81BC439000078E098 /* Assertions.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D40B89C71BC439000078E098 /* Assertions.framework */; };
D40D72541BCEEB1F001B7A9E /* InterpreterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D40D72531BCEEB1F001B7A9E /* InterpreterTests.swift */; settings = {ASSET_TAGS = (); }; };
@ -62,6 +65,9 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
D18FF8811BD0404700D18F2D /* Madness.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Madness.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Doubt-gibmbsxjgxflybarnuoisglmgdll/Build/Products/Debug/Madness.framework"; sourceTree = "<group>"; };
D1A0934A1BD188CA005A6326 /* JSONParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONParser.swift; sourceTree = "<group>"; };
D1A0934C1BD188F5005A6326 /* JSONLeaf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONLeaf.swift; sourceTree = "<group>"; };
D40B89C31BC319070078E098 /* Matrix.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Matrix.swift; sourceTree = "<group>"; };
D40B89C71BC439000078E098 /* Assertions.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Assertions.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D40D72531BCEEB1F001B7A9E /* InterpreterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InterpreterTests.swift; sourceTree = "<group>"; };
@ -107,6 +113,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D18FF8821BD0404700D18F2D /* Madness.framework in Frameworks */,
D42F096B1BCCC41600B95610 /* Either.framework in Frameworks */,
D42F096C1BCCC41600B95610 /* Memo.framework in Frameworks */,
D42F096D1BCCC41600B95610 /* Prelude.framework in Frameworks */,
@ -183,6 +190,8 @@
D4DF96EC1BC46B630040F41F /* SES.swift */,
D435B7521BB31BBC000902F6 /* BoundsCheckedArray.swift */,
D42F097F1BCECB7900B95610 /* TermType.swift */,
D1A0934A1BD188CA005A6326 /* JSONParser.swift */,
D1A0934C1BD188F5005A6326 /* JSONLeaf.swift */,
D40D72551BCFF360001B7A9E /* Interpreter.swift */,
D4AAE5001B5AE22E004E581F /* Supporting Files */,
);
@ -196,6 +205,7 @@
D4DF96F01BC54C970040F41F /* Doubt.modulemap */,
D42F09671BCCC41600B95610 /* Either.framework */,
D42F09681BCCC41600B95610 /* Memo.framework */,
D18FF8811BD0404700D18F2D /* Madness.framework */,
D42F09691BCCC41600B95610 /* Prelude.framework */,
D42F096A1BCCC41600B95610 /* Stream.framework */,
);
@ -403,6 +413,8 @@
buildActionMask = 2147483647;
files = (
D4DF970A1BC5DF800040F41F /* main.swift in Sources */,
D1A0934E1BD18969005A6326 /* JSONLeaf.swift in Sources */,
D1A0934F1BD189B4005A6326 /* JSONParser.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -4,6 +4,9 @@
<FileRef
location = "group:Doubt.xcodeproj">
</FileRef>
<FileRef
location = "group:External/Madness/Madness.xcodeproj">
</FileRef>
<FileRef
location = "group:External/Assertions/Assertions.xcodeproj">
</FileRef>

View File

@ -0,0 +1,67 @@
//
// JSONLeaf.swift
// Doubt
//
// Created by Josh Vera on 10/16/15.
// Copyright © 2015 GitHub. All rights reserved.
//
import Foundation
import Doubt
typealias Term = Cofree<JSONLeaf, Range<String.Index>>
typealias Diff = Free<JSONLeaf, Patch<Term>>
enum JSONLeaf: CustomJSONConvertible, CustomStringConvertible, Equatable {
case Number(Double)
case Boolean(Bool)
case String(Swift.String)
case Null
// MARK: CustomJSONConvertible
var JSON: Doubt.JSON {
switch self {
case let .Number(n):
return .Number(n)
case let .Boolean(b):
return .Boolean(b)
case let .String(s):
return .String(s)
case .Null:
return .Null
}
}
// MARK: CustomStringConvertible
var description: Swift.String {
switch self {
case let .Number(n):
return Swift.String(n)
case let .Boolean(b):
return Swift.String(b)
case let .String(s):
return Swift.String(reflecting: s)
case .Null:
return "null"
}
}
}
func == (left: JSONLeaf, right: JSONLeaf) -> Bool {
switch (left, right) {
case let (.Number(a), .Number(b)):
return a == b
case let (.Boolean(a), .Boolean(b)):
return a == b
case let (.String(a), .String(b)):
return a == b
case (.Null, .Null):
return true
default:
return false
}
}

View File

@ -0,0 +1,116 @@
//
// JSONParser.swift
// Doubt
//
// Created by Josh Vera on 10/16/15.
// Copyright © 2015 GitHub. All rights reserved.
//
import Foundation
import Madness
import Either
import Prelude
import Doubt
typealias CofreeJSON = Cofree<JSONLeaf, Range<String.CharacterView.Index>>
typealias JSONParser = Parser<String, CofreeJSON>.Function
extension String: CollectionType {
public var count: Index.Distance {
return characters.count
}
public static func lift<A>(parser: Parser<String.CharacterView, A>.Function) -> Parser<String, A>.Function {
return {
parser($0.characters, $1)
}
}
}
typealias StringParser = Parser<String, String>.Function
typealias CharacterParser = Parser<String, [Character]>.Function
// Inlined for performance reasons
let whitespaceChars: [Character] = [" ", "\n", "\t", "\r"]
let whitespace: CharacterParser = String.lift(satisfy({ whitespaceChars.contains($0) })*)
// Quoted strings parser
// TODO: Improve string parsing
let stringBody: StringParser = { $0.map({ String($0) }).joinWithSeparator("") } <^>
String.lift(noneOf("\"")*)
let quoted = %"\"" *> stringBody <* %"\"" <* whitespace
typealias MembersParser = Parser<String, [(String, CofreeJSON)]>.Function;
// Parses an array of (String, CofreeJSON) object members
func members(json: JSONParser) -> MembersParser {
let pairs: Parser<String, (String, CofreeJSON)>.Function = (curry(pair) <^>
quoted
<* whitespace
<* %":"
<* whitespace
<*> json)
let separatedPairs: MembersParser = (%"," *> whitespace *> pairs <* whitespace)*
let oneOrMore: MembersParser = curry { [$0] + $1 } <^>
pairs
<* whitespace
<*> separatedPairs
return oneOrMore <|> pure([])
}
typealias ValuesParser = Parser<String, [CofreeJSON]>.Function;
// Parses an array of CofreeJSON array values
func elements(json: JSONParser) -> ValuesParser {
let value: Parser<String, CofreeJSON>.Function = whitespace *> json <* whitespace
return sepBy(value, %",")
}
let json: JSONParser = fix { json in
// TODO: Parse backslashed escape characters
let string: JSONParser = quoted --> {
Cofree($1, .Leaf(.String($2)))
} <?> "string"
let array: JSONParser = %"["
<* whitespace
*> elements(json)
<* %"]"
<* whitespace --> {
Cofree($1, .Indexed($2))
} <?> "array"
let object: JSONParser = %"{"
*> whitespace
*> members(json)
<* whitespace
<* %"}"
<* whitespace
--> { (_, range, values) in
Cofree(range, .Keyed(Dictionary(elements: values)))
} <?> "object"
let doubleParser: DoubleParser = number
let numberParser: JSONParser = String.lift(doubleParser --> { _, range, value in
let num = JSONLeaf.Number(value)
return Cofree(range, .Leaf(num))
}) <?> "number"
let null: JSONParser = %"null" --> { (_, range, value) in
return Cofree(range, .Leaf(.Null))
} <?> "null"
let boolean: JSONParser = %"false" <|> %"true" --> { (_, range, value) in
let boolean = value == "true"
return Cofree(range, .Leaf(.Boolean(boolean)))
} <?> "boolean"
// TODO: This should be JSON = dict <|> array and
// Value = dict | array | string | number | null | bool
return object <|> array <|> string <|> numberParser <|> boolean <|> null
}

1
prototype/External/Madness vendored Submodule

@ -0,0 +1 @@
Subproject commit 68fcabcdecd9219f3b3de0f423ff5e08aaaffae8

View File

@ -1,108 +1,35 @@
import Cocoa
import Doubt
import Either
import Prelude
import Madness
typealias Term = Cofree<JSONLeaf, Int>
typealias Diff = Free<JSONLeaf, Patch<Term>>
enum JSONLeaf: CustomJSONConvertible, CustomStringConvertible, Equatable {
case Number(Double)
case Boolean(Bool)
case String(Swift.String)
case Null
// MARK: CustomJSONConvertible
var JSON: Doubt.JSON {
switch self {
case let .Number(n):
return .Number(n)
case let .Boolean(b):
return .Boolean(b)
case let .String(s):
return .String(s)
case .Null:
return .Null
}
}
// MARK: CustomStringConvertible
var description: Swift.String {
switch self {
case let .Number(n):
return Swift.String(n)
case let .Boolean(b):
return Swift.String(b)
case let .String(s):
return Swift.String(reflecting: s)
case .Null:
return "null"
}
}
}
func == (left: JSONLeaf, right: JSONLeaf) -> Bool {
switch (left, right) {
case let (.Number(a), .Number(b)):
return a == b
case let (.Boolean(a), .Boolean(b)):
return a == b
case let (.String(a), .String(b)):
return a == b
case (.Null, .Null):
return true
default:
return false
}
}
extension JSON {
init?(path: Swift.String) {
guard let data = try? NSData(contentsOfFile: path, options: []) else { return nil }
guard let object = try? NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) else { return nil }
self.init(object: object)
}
var term: Term {
func annotate(json: Syntax<Term, JSONLeaf>) -> Term {
return Cofree(size(json), json)
}
func size(syntax: Syntax<Term, JSONLeaf>) -> Int {
switch syntax {
case .Leaf:
return 1
case let .Indexed(i):
return 1 + i.map { size($0.unwrap) }.reduce(0, combine: +)
case let .Keyed(i):
return 1 + i.values.map { size($0.unwrap) }.reduce(0, combine: +)
}
}
switch self {
case let .Array(a):
return annotate(.Indexed(a.map { $0.term }))
case let .Dictionary(d):
return annotate(.Keyed(Swift.Dictionary(elements: d.map { ($0, $1.term) })))
case let .Number(n):
return annotate(.Leaf(.Number(n)))
case let .Boolean(b):
return annotate(.Leaf(.Boolean(b)))
case let .String(s):
return annotate(.Leaf(.String(s)))
case .Null:
return annotate(.Leaf(.Null))
}
}
}
let arguments = BoundsCheckedArray(array: Process.arguments)
if let a = arguments[1].flatMap(JSON.init), b = arguments[2].flatMap(JSON.init) {
let diff = Interpreter(comparable: const(true), cost: Diff.sum(Patch.difference)).run(a.term, b.term)
if let JSON = NSString(data: diff.JSON(ifPure: { $0.JSON { $0.JSON } }, ifLeaf: { $0.JSON }).serialize(), encoding: NSUTF8StringEncoding) {
print(JSON)
}
let empty = "{}\n"
print(parse(json, input: empty))
let dict = "{\"hello\":\"world\"}"
print(parse(json, input: dict))
let dictWithSpaces = "{ \"hello\" : \"world\" }"
print(parse(json, input: dictWithSpaces))
let dictWithMembers = "{\"hello\":\"world\",\"sup\":\"cat\"}"
print(parse(json, input: dictWithMembers))
let dictWithArray = "{\"hello\": [\"world\"],\"sup\": [\"cat\", \"dog\", \"keith\"] }"
print(parse(json, input: dictWithArray))
let readFile = { (path: String) -> String? in
guard let data = try? NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding) else { return nil }
return data as String?
}
if let a = arguments[1].flatMap(readFile).flatMap({
curry(parse)(json)($0).right
}), b = arguments[2].flatMap(readFile).flatMap({
curry(parse)(json)($0).right
}) {
let diff = Interpreter(comparable: const(true), cost: Diff.sum(Patch.difference)).run(a, b)
print(diff)
}