diff --git a/prototype/Doubt.xcodeproj/project.pbxproj b/prototype/Doubt.xcodeproj/project.pbxproj index dd9907062..719266616 100644 --- a/prototype/Doubt.xcodeproj/project.pbxproj +++ b/prototype/Doubt.xcodeproj/project.pbxproj @@ -8,8 +8,9 @@ /* 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 */; }; - D1A0934F1BD189B4005A6326 /* JSONParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A0934A1BD188CA005A6326 /* JSONParser.swift */; }; + D1F5FE201BDE891F0048BAE4 /* JSONParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1F5FE1F1BDE891F0048BAE4 /* JSONParserTests.swift */; }; + D1F5FE211BDE9C450048BAE4 /* JSONLeaf.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A0934C1BD188F5005A6326 /* JSONLeaf.swift */; }; + D1F5FE221BDE9CC10048BAE4 /* JSONParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A0934A1BD188CA005A6326 /* JSONParser.swift */; }; D40B89C41BC319070078E098 /* Matrix.swift in Sources */ = {isa = PBXBuildFile; fileRef = D40B89C31BC319070078E098 /* Matrix.swift */; }; D40B89C81BC439000078E098 /* Assertions.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D40B89C71BC439000078E098 /* Assertions.framework */; }; D40D72541BCEEB1F001B7A9E /* InterpreterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D40D72531BCEEB1F001B7A9E /* InterpreterTests.swift */; }; @@ -83,6 +84,7 @@ D18FF8811BD0404700D18F2D /* Madness.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Madness.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Doubt-gibmbsxjgxflybarnuoisglmgdll/Build/Products/Debug/Madness.framework"; sourceTree = ""; }; D1A0934A1BD188CA005A6326 /* JSONParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONParser.swift; sourceTree = ""; }; D1A0934C1BD188F5005A6326 /* JSONLeaf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONLeaf.swift; sourceTree = ""; }; + D1F5FE1F1BDE891F0048BAE4 /* JSONParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONParserTests.swift; sourceTree = ""; }; D40B89C31BC319070078E098 /* Matrix.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Matrix.swift; sourceTree = ""; }; 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 = ""; }; @@ -250,6 +252,7 @@ D4AAE5111B5AE22E004E581F /* DoubtTests */ = { isa = PBXGroup; children = ( + D1F5FE1F1BDE891F0048BAE4 /* JSONParserTests.swift */, D432D4701BA9AC0B00F3FABC /* SESTests.swift */, D40D72531BCEEB1F001B7A9E /* InterpreterTests.swift */, D40D725B1BD15417001B7A9E /* RangedTerm.swift */, @@ -459,6 +462,7 @@ D42F097E1BCEAEDA00B95610 /* Operation.swift in Sources */, D45A36C91BBC667D00BE3DDE /* Categorizable.swift in Sources */, D4DF96ED1BC46B630040F41F /* SES.swift in Sources */, + D1F5FE221BDE9CC10048BAE4 /* JSONParser.swift in Sources */, D42F09801BCECB7900B95610 /* TermType.swift in Sources */, D4AAE5401B5AE2D0004E581F /* RangeReplaceableCollectionType.swift in Sources */, D49FCBC41BBEF98E00C5E9C3 /* Free.swift in Sources */, @@ -470,6 +474,7 @@ D40B89C41BC319070078E098 /* Matrix.swift in Sources */, D4AAE5491B5AE2D0004E581F /* StringLiteralConvertible.swift in Sources */, D4413FF11BB08FDC00E3C3C1 /* JSON.swift in Sources */, + D1F5FE211BDE9C450048BAE4 /* JSONLeaf.swift in Sources */, D4AAE5451B5AE2D0004E581F /* Operators.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -480,6 +485,7 @@ files = ( D40D72641BD1A0C1001B7A9E /* RangedDiff.swift in Sources */, D40D725E1BD1826C001B7A9E /* TermTests.swift in Sources */, + D1F5FE201BDE891F0048BAE4 /* JSONParserTests.swift in Sources */, D40D72601BD19E69001B7A9E /* DiffTests.swift in Sources */, D432D4711BA9AC0B00F3FABC /* SESTests.swift in Sources */, D40D72621BD1A07E001B7A9E /* UnannotatedTerm.swift in Sources */, @@ -493,8 +499,6 @@ buildActionMask = 2147483647; files = ( D4DF970A1BC5DF800040F41F /* main.swift in Sources */, - D1A0934E1BD18969005A6326 /* JSONLeaf.swift in Sources */, - D1A0934F1BD189B4005A6326 /* JSONParser.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/prototype/Doubt/JSONLeaf.swift b/prototype/Doubt/JSONLeaf.swift index aa89e6961..d1afc4639 100644 --- a/prototype/Doubt/JSONLeaf.swift +++ b/prototype/Doubt/JSONLeaf.swift @@ -7,12 +7,11 @@ // import Foundation -import Doubt typealias Term = Cofree> typealias Diff = Free> -enum JSONLeaf: CustomJSONConvertible, CustomStringConvertible, Equatable { +public enum JSONLeaf: CustomJSONConvertible, CustomStringConvertible, Equatable { case Number(Double) case Boolean(Bool) case String(Swift.String) @@ -21,7 +20,7 @@ enum JSONLeaf: CustomJSONConvertible, CustomStringConvertible, Equatable { // MARK: CustomJSONConvertible - var JSON: Doubt.JSON { + public var JSON: Doubt.JSON { switch self { case let .Number(n): return .Number(n) @@ -37,7 +36,7 @@ enum JSONLeaf: CustomJSONConvertible, CustomStringConvertible, Equatable { // MARK: CustomStringConvertible - var description: Swift.String { + public var description: Swift.String { switch self { case let .Number(n): return Swift.String(n) @@ -51,7 +50,7 @@ enum JSONLeaf: CustomJSONConvertible, CustomStringConvertible, Equatable { } } -func == (left: JSONLeaf, right: JSONLeaf) -> Bool { +public func == (left: JSONLeaf, right: JSONLeaf) -> Bool { switch (left, right) { case let (.Number(a), .Number(b)): return a == b diff --git a/prototype/Doubt/JSONParser.swift b/prototype/Doubt/JSONParser.swift index 90b503f11..fe8f7b9c6 100644 --- a/prototype/Doubt/JSONParser.swift +++ b/prototype/Doubt/JSONParser.swift @@ -8,80 +8,53 @@ import Foundation import Madness -import Either import Prelude -import Doubt -typealias CofreeJSON = Cofree> -typealias JSONParser = Parser.Function - -extension String: CollectionType { - public var count: Index.Distance { - return characters.count - } - - public static func lift(parser: Parser.Function) -> Parser.Function { - return { - parser($0.characters, $1) - } - } -} - -typealias StringParser = Parser.Function -typealias CharacterParser = Parser.Function +public typealias CofreeJSON = Cofree> +public typealias JSONParser = Parser.Function // Inlined for performance reasons -let whitespaceChars: [Character] = [" ", "\n", "\t", "\r"] -let whitespace: CharacterParser = String.lift(satisfy({ whitespaceChars.contains($0) })*) +let whitespace = oneOf(" \n\r\t")* + +// TODO: Parse unicode escape sequence +let escapeChar: StringParser = curry(+) <^> %"\\" <*> ({ String($0) } <^> oneOf("\\\"bfnrt")) +let otherChar: StringParser = { String($0) } <^> noneOf("\"\\") // Quoted strings parser // TODO: Improve string parsing -let stringBody: StringParser = { $0.map({ String($0) }).joinWithSeparator("") } <^> - String.lift(noneOf("\"")*) -let quoted = %"\"" *> stringBody <* %"\"" <* whitespace +let stringBody: StringParser = { $0.joinWithSeparator("") } <^> many(escapeChar <|> otherChar) +let quoted = %"\"" *> stringBody <* %"\"" -typealias MembersParser = Parser.Function; +typealias MembersParser = Parser.Function; // Parses an array of (String, CofreeJSON) object members func members(json: JSONParser) -> MembersParser { - let pairs: Parser.Function = (curry(pair) <^> - quoted + let keyAndKeyTerm: Parser.Function = quoted --> { (_, range, key) in + (key, Cofree(range, .Leaf(.String(key)))) + } + let pairs: Parser.Function = (curry(pair) <^> + keyAndKeyTerm <* whitespace <* %":" <* whitespace - <*> json) + <*> json) --> { (_, range, values) in + (values.0.0, Cofree(range, .Fixed([values.0.1, values.1]))) + } - let separatedPairs: MembersParser = (%"," *> whitespace *> pairs <* whitespace)* - - let oneOrMore: MembersParser = curry { [$0] + $1 } <^> - pairs - <* whitespace - <*> separatedPairs - - return oneOrMore <|> pure([]) + return sepBy(pairs, whitespace <* %"," <* whitespace) } -typealias ValuesParser = Parser.Function; - -// Parses an array of CofreeJSON array values -func elements(json: JSONParser) -> ValuesParser { - let value: Parser.Function = whitespace *> json <* whitespace - - return sepBy(value, %",") -} - -let json: JSONParser = fix { json in - // TODO: Parse backslashed escape characters - +public let json: JSONParser = fix { json in let string: JSONParser = quoted --> { Cofree($1, .Leaf(.String($2))) } "string" let array: JSONParser = %"[" <* whitespace - *> elements(json) + *> sepBy(whitespace *> json, whitespace <* %"," <* whitespace) + <* whitespace <* %"]" - <* whitespace --> { + --> { Cofree($1, .Indexed($2)) } "array" @@ -90,15 +63,12 @@ let json: JSONParser = fix { json in *> members(json) <* whitespace <* %"}" - <* whitespace - --> { (_, range, values) in + --> { (_, range, values: [(String, CofreeJSON)]) 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)) + let numberParser: JSONParser = (number --> { _, range, value in + Cofree(range, .Leaf(JSONLeaf.Number(value))) }) "number" let null: JSONParser = %"null" --> { (_, range, value) in @@ -112,5 +82,5 @@ let json: JSONParser = fix { json in // TODO: This should be JSON = dict <|> array and // Value = dict | array | string | number | null | bool - return object <|> array <|> string <|> numberParser <|> boolean <|> null + return (object <|> array <|> string <|> numberParser <|> boolean <|> null) <* whitespace } diff --git a/prototype/Doubt/Syntax.swift b/prototype/Doubt/Syntax.swift index 191232e64..0db5f3f37 100644 --- a/prototype/Doubt/Syntax.swift +++ b/prototype/Doubt/Syntax.swift @@ -91,6 +91,8 @@ extension Syntax { return leaf(l1, l2) case let (.Indexed(v1), .Indexed(v2)): return v1.count == v2.count && zip(v1, v2).lazy.map(recur).reduce(true) { $0 && $1 } + case let (.Fixed(v1), .Fixed(v2)): + return v1.count == v2.count && zip(v1, v2).lazy.map(recur).reduce(true) { $0 && $1 } case let (.Keyed(d1), .Keyed(d2)): return Set(d1.keys) == Set(d2.keys) && d1.keys.map { recur(d1[$0]!, d2[$0]!) }.reduce(true) { $0 && $1 } default: diff --git a/prototype/DoubtTests/JSONParserTests.swift b/prototype/DoubtTests/JSONParserTests.swift new file mode 100644 index 000000000..84a5d4bc0 --- /dev/null +++ b/prototype/DoubtTests/JSONParserTests.swift @@ -0,0 +1,28 @@ +import Assertions +import Doubt +import Prelude +import Madness +import XCTest + +final class JSONParserTests: XCTestCase { + func testCapturesKeysInKeyedElements() { + let dictWithArray = "{ \"hello\":\n [\"world\",\n \"sailor\"\n ]}" + let array: Cofree> = Cofree(15..<41, .Indexed([ + Cofree(16..<23, .Leaf(.String("world"))), + Cofree(29..<37, .Leaf(.String("sailor"))) + ])) + + let fixedPairs: Cofree> = Cofree(2..<41, .Fixed([Cofree(2..<9, .Leaf(.String("hello"))), array])) + + let expected: Cofree> = Cofree(0..<42, .Keyed(["hello": fixedPairs])) + let actual = Madness.parse(json, input: dictWithArray).right! + let firstIndex = actual.extract + let new: Cofree> = actual.map({ range in + let startI: Int = firstIndex.startIndex.distanceTo(range.startIndex) + let endI: Int = firstIndex.startIndex.distanceTo(range.endIndex) + return Range(start: startI, end: endI) + }) + + assert(new, == , expected) + } +}