diff --git a/Package.swift b/Package.swift index ef56714..72c6418 100644 --- a/Package.swift +++ b/Package.swift @@ -27,7 +27,11 @@ let package = Package( ], targets: [ .target( - name: "Coder"), + name: "Coder", + dependencies: [ + .product(name: "Binary", package: "swift-binary") + ] + ), .testTarget( name: "CoderTests", dependencies: ["Coder"]), diff --git a/Sources/Coder/Coder.swift b/Sources/Coder/Coder.swift index 8b13789..b42dce5 100644 --- a/Sources/Coder/Coder.swift +++ b/Sources/Coder/Coder.swift @@ -1 +1 @@ - +@_exported import Binary diff --git a/Sources/StringDecoder/Combinators/StringDecoder+Chain.swift b/Sources/StringDecoder/Combinators/StringDecoder+Chain.swift index e1aa325..9c65801 100644 --- a/Sources/StringDecoder/Combinators/StringDecoder+Chain.swift +++ b/Sources/StringDecoder/Combinators/StringDecoder+Chain.swift @@ -12,4 +12,16 @@ public extension StringDecoder { return flatMap(rest) } + + func chainRight(_ op: StringDecoder<(Element, Element) -> Element, Failure>) -> Self { + func rest(_ x: Element) -> Self { + op.flatMap { f in + self.chainRight(op).map { y in + f(x, y) + } + }.or(pure(x)) + } + + return self.flatMap(rest) + } } diff --git a/Sources/StringDecoder/Combinators/StringDecoder+LookAhead.swift b/Sources/StringDecoder/Combinators/StringDecoder+LookAhead.swift index 91f175b..0442e95 100644 --- a/Sources/StringDecoder/Combinators/StringDecoder+LookAhead.swift +++ b/Sources/StringDecoder/Combinators/StringDecoder+LookAhead.swift @@ -7,7 +7,7 @@ public extension StringDecoder { case .success: return self.decode(input) case .failure: - return self.decode(State(string: "", offset: input.offset)) + return self.decode(State(string: "", offset: input.offset, line: input.line, column: input.column)) } } } diff --git a/Sources/StringDecoder/Combinators/StringDecoder+TryAhead.swift b/Sources/StringDecoder/Combinators/StringDecoder+TryAhead.swift index 47723da..7e5e584 100644 --- a/Sources/StringDecoder/Combinators/StringDecoder+TryAhead.swift +++ b/Sources/StringDecoder/Combinators/StringDecoder+TryAhead.swift @@ -5,7 +5,7 @@ public extension StringDecoder { StringDecoder { input in switch forwardEncoder(input) { case .success: - return self.decode(State(string: "", offset: input.offset)) + return self.decode(State(string: "", offset: input.offset, line: input.line, column: input.column)) case .failure: return self.decode(input) } diff --git a/Sources/StringDecoder/Decoders/StringDecoder+Indent.swift b/Sources/StringDecoder/Decoders/StringDecoder+Indent.swift deleted file mode 100644 index 0f7b9fb..0000000 --- a/Sources/StringDecoder/Decoders/StringDecoder+Indent.swift +++ /dev/null @@ -1,24 +0,0 @@ -import Foundation - - - -public func indent(by count: Int = 1) -> StringDecoder, StringDecoderFailure> { - StringDecoder { input in - print("Indent:", input) - return space().count(4 * (input.indentation + count)).decode(input.indent(by: count)) - } -} - -public func dedent(by count: Int = 1) -> StringDecoder, StringDecoderFailure> { - StringDecoder { input in - print("Indent:", input) - return space().count(4 * (input.indentation - count)).decode(input.indent(by: -count)) - } -} - -public var indentation: StringDecoder, StringDecoderFailure> { - StringDecoder { input in - print("Indent:", input) - return space().count(4 * (input.indentation)).decode(input) - } -} diff --git a/Sources/StringDecoder/Decoders/StringDecoder+Parentheses.swift b/Sources/StringDecoder/Decoders/StringDecoder+Parentheses.swift index 79e8c93..7ae98ea 100644 --- a/Sources/StringDecoder/Decoders/StringDecoder+Parentheses.swift +++ b/Sources/StringDecoder/Decoders/StringDecoder+Parentheses.swift @@ -3,3 +3,16 @@ import Foundation public let leftParentheses: StringDecoder = match(Character("(")) public let rightParentheses: StringDecoder = match(Character(")")) + + +public let leftCurlyBracket: StringDecoder = match(Character("{")) +public let rightCurlyBracket: StringDecoder = match(Character("}")) + +public extension StringDecoder where Failure == StringDecoderFailure { + var insideCurlyBrackets: Self { + self.between( + open: leftCurlyBracket.discard(whitespace()), + close: whitespace().discardThen(rightCurlyBracket) + ) + } +} diff --git a/Sources/StringDecoder/Decoders/StringDecoder+Sigil.swift b/Sources/StringDecoder/Decoders/StringDecoder+Sigil.swift new file mode 100644 index 0000000..08f69e4 --- /dev/null +++ b/Sources/StringDecoder/Decoders/StringDecoder+Sigil.swift @@ -0,0 +1,5 @@ +import Foundation + +public func isSigil(_ c: Character) -> Bool { + c == "$" +} diff --git a/Sources/StringDecoder/Decoders/StringDecoder+Whitespace.swift b/Sources/StringDecoder/Decoders/StringDecoder+Whitespace.swift index 86705f4..64cc04d 100644 --- a/Sources/StringDecoder/Decoders/StringDecoder+Whitespace.swift +++ b/Sources/StringDecoder/Decoders/StringDecoder+Whitespace.swift @@ -15,3 +15,11 @@ public func space() -> StringDecoder { public func newline() -> StringDecoder { match("\n") } + +public let horizontalWhitespace: StringDecoder = tab().or(space()).many.map { String($0.flatMap { $0 }) } + +public extension StringDecoder where Failure == StringDecoderFailure { + var insideHorizontalWhitespace: Self { + self.between(horizontalWhitespace) + } +} diff --git a/Sources/StringDecoder/State.swift b/Sources/StringDecoder/State.swift index 31c8240..e6753ce 100644 --- a/Sources/StringDecoder/State.swift +++ b/Sources/StringDecoder/State.swift @@ -3,34 +3,66 @@ import Foundation public struct State: Equatable { public let string: String public var offset: UInt64 - public internal(set) var indentation: Int - - public init(string: String, offset: UInt64, indentation: Int = 0) { + public var line: UInt + public var column: UInt + + public init(string: String, offset: UInt64, line: UInt, column: UInt) { self.string = string self.offset = offset - self.indentation = indentation + self.line = line + self.column = column } - + public var head: Character? { guard string.characters.indices.contains(Int(offset)) else { return nil } return string.characters[Int(offset)] } - + public var next: State { advanced(by: 1) } - + public func advanced(by count: Int) -> State { - State(string: string, offset: offset + UInt64(count), indentation: indentation) + var newLine = line + var newColumn = column + var newOffset = offset + + for _ in 0.. State { - State(string: string, offset: offset, indentation: indentation + count) + + public var positionMetadata: PositionMetadata { + PositionMetadata(offset: offset, line: line, column: column) } } extension State: ExpressibleByArrayLiteral { public init(arrayLiteral elements: Character...) { - self = State(string: String(elements), offset: 0) + self = State(string: String(elements), offset: 0, line: 1, column: 1) + } +} + +public struct PositionMetadata: Equatable { + public let offset: UInt64 + public let line: UInt + public let column: UInt + + public init(offset: UInt64, line: UInt, column: UInt) { + self.offset = offset + self.line = line + self.column = column } } diff --git a/Sources/StringDecoder/StringDecoder.swift b/Sources/StringDecoder/StringDecoder.swift index dd3b8c9..36cc5a6 100644 --- a/Sources/StringDecoder/StringDecoder.swift +++ b/Sources/StringDecoder/StringDecoder.swift @@ -1,5 +1,6 @@ import Foundation @_exported import Coder +@_exported import Binary public struct StringDecoder { public typealias Input = State @@ -19,7 +20,7 @@ public struct StringDecoder { public extension StringDecoder { func callAsFunction(_ string: String) -> Output { - decode(State(string: string, offset: 0)) + decode(State(string: string, offset: 0, line: 1, column: 1)) } } diff --git a/Tests/StringDecoderTests/IndentTests.swift b/Tests/StringDecoderTests/IndentTests.swift deleted file mode 100644 index 02b9d6e..0000000 --- a/Tests/StringDecoderTests/IndentTests.swift +++ /dev/null @@ -1,53 +0,0 @@ -import XCTest -import StringDecoder - -final class IndentTests: XCTestCase { - func testIndent() { - let string = """ - this - that - other - - this - that - other - - """ - - struct Some: Equatable { - let this: String - let other: Other - } - - struct Other: Equatable { - let that: String - let other: String - } - - let element = StringDecoder(Some.init) { - match("this").discard(newline()) - StringDecoder(Other.init) { - match("that").discard(newline()) - indentation.discardThen("other").discard(newline()) - } - .between(open: indent(), close: dedent()) - //indent().discardThen(match("that")).discard(newline()).discard(dedent()) - }//.between(open: indent(), close: dedent()) - - let elements = element.many(separatedBy: newline()) - - switch elements(string) { - case .success((let element, let state)): - //XCTAssertEqual(element, Some(this: "this", other: Other(that: "that", other: "other"))) - XCTAssertEqual(element, [ - Some(this: "this", other: Other(that: "that", other: "other")), - Some(this: "this", other: Other(that: "that", other: "other")) - ]) - XCTAssertEqual(state.indentation, 0) - case .failure(let error): - XCTFail(error.localizedDescription) - } - - - } -} diff --git a/Tests/StringDecoderTests/StringDecoderCombinatorsTests.swift b/Tests/StringDecoderTests/StringDecoderCombinatorsTests.swift index 76b6b79..d93faf3 100644 --- a/Tests/StringDecoderTests/StringDecoderCombinatorsTests.swift +++ b/Tests/StringDecoderTests/StringDecoderCombinatorsTests.swift @@ -8,7 +8,7 @@ final class StringDecoderCombinatorsTests: XCTestCase { switch match(Character("c")).decode(["c"]) { case .success((let element, let state)): XCTAssertEqual(element, "c") - XCTAssertEqual(state, State(string: "c", offset: 1)) + XCTAssertEqual(state, State(string: "c", offset: 1, line: 1, column: 2)) case .failure(_): XCTFail() } @@ -19,56 +19,56 @@ final class StringDecoderCombinatorsTests: XCTestCase { let number: StringDecoder = satisfy({ $0.isNumber }) let alphanumeric: StringDecoder = letter.or(number).many.map { String($0) } - switch alphanumeric.decode(State(string: "ccc11!", offset: 0)) { + switch alphanumeric.decode(State(string: "ccc11!", offset: 0, line: 1, column: 2)) { case .success((let element, let state)): XCTAssertEqual(element, "ccc11") - XCTAssertEqual(state, State(string: "ccc11!", offset: 5)) + XCTAssertEqual(state, State(string: "ccc11!", offset: 5, line: 1, column: 7)) case .failure(_): XCTFail() } - switch satisfy({ $0.isLetter }).many.map({ String($0) }).decode(State(string: "ccc11", offset: 0)) { + switch satisfy({ $0.isLetter }).many.map({ String($0) }).decode(State(string: "ccc11", offset: 0, line: 1, column: 2)) { case .success((let element, let state)): XCTAssertEqual(element, "ccc") - XCTAssertEqual(state, State(string: "ccc11", offset: 3)) + XCTAssertEqual(state, State(string: "ccc11", offset: 3, line: 1, column: 5)) case .failure(_): XCTFail() } - switch satisfy({ $0.isLetter }).many.map({ String($0) }).decode(State(string: "1", offset: 0)) { + switch satisfy({ $0.isLetter }).many.map({ String($0) }).decode(State(string: "1", offset: 0, line: 1, column: 2)) { case .success((let element, let state)): XCTAssertEqual(element, "") - XCTAssertEqual(state, State(string: "1", offset: 0)) + XCTAssertEqual(state, State(string: "1", offset: 0, line: 1, column: 2)) case .failure(_): XCTFail() } - switch satisfy({ $0.isLetter }).many.map({ String($0) }).decode(State(string: "", offset: 0)) { + switch satisfy({ $0.isLetter }).many.map({ String($0) }).decode(State(string: "", offset: 0, line: 1, column: 2)) { case .success((let element, let state)): XCTAssertEqual(element, "") - XCTAssertEqual(state, State(string: "", offset: 0)) + XCTAssertEqual(state, State(string: "", offset: 0, line: 1, column: 2)) case .failure(_): XCTFail() } } func testSomeCombinator() { - switch satisfy({ $0.isLetter }).some.decode(State(string: "ccc11", offset: 0)) { + switch satisfy({ $0.isLetter }).some.decode(State(string: "ccc11", offset: 0, line: 1, column: 2)) { case .success((let element, let state)): XCTAssertEqual(element, ["c", "c", "c"]) - XCTAssertEqual(state, State(string: "ccc11", offset: 3)) + XCTAssertEqual(state, State(string: "ccc11", offset: 3, line: 1, column: 5)) case .failure(_): XCTFail() } - switch satisfy({ $0.isLetter }).some.decode(State(string: "1", offset: 0)) { + switch satisfy({ $0.isLetter }).some.decode(State(string: "1", offset: 0, line: 1, column: 2)) { case .success: XCTFail() case .failure(let failure): XCTAssertEqual(failure, .mismatchedPrimitive(0)) } - switch satisfy({ $0.isLetter }).some.decode(State(string: "", offset: 0)) { + switch satisfy({ $0.isLetter }).some.decode(State(string: "", offset: 0, line: 1, column: 2)) { case .success: XCTFail() case .failure(let failure): @@ -80,15 +80,15 @@ final class StringDecoderCombinatorsTests: XCTestCase { let letter: StringDecoder = satisfy({ $0.isLetter }) let number: StringDecoder = satisfy({ $0.isNumber }) - switch letter.some(separatedBy: number).decode(State(string: "a1b2c3!!", offset: 0)) { + switch letter.some(separatedBy: number).decode(State(string: "a1b2c3!!", offset: 0, line: 1, column: 2)) { case .success((let element, let state)): XCTAssertEqual(element, ["a", "b", "c"]) - XCTAssertEqual(state, State(string: "a1b2c3!!", offset: 5)) + XCTAssertEqual(state, State(string: "a1b2c3!!", offset: 5, line: 1, column: 7)) case .failure(_): XCTFail() } - switch letter.some(separatedBy: number).decode(State(string: "1", offset: 0)) { + switch letter.some(separatedBy: number).decode(State(string: "1", offset: 0, line: 1, column: 2)) { case .success: XCTFail() case .failure(let failure): @@ -101,10 +101,10 @@ final class StringDecoderCombinatorsTests: XCTestCase { let number: StringDecoder = satisfy({ $0.isNumber }) let space: StringDecoder = match(" ") - switch letter.many(separatedBy: number).decode(State(string: "a1b2c3!!", offset: 0)) { + switch letter.many(separatedBy: number).decode(State(string: "a1b2c3!!", offset: 0, line: 1, column: 2)) { case .success((let element, let state)): XCTAssertEqual(element, ["a", "b", "c"]) - XCTAssertEqual(state, State(string: "a1b2c3!!", offset: 5)) + XCTAssertEqual(state, State(string: "a1b2c3!!", offset: 5, line: 1, column: 7)) case .failure: XCTFail() } @@ -125,15 +125,15 @@ final class StringDecoderCombinatorsTests: XCTestCase { switch thisDecoder("call(AA BB CC)") { case .success((let element, let state)): XCTAssertEqual(element, This(name: "call", argumants: ["AA", "BB", "CC"])) - XCTAssertEqual(state, State(string: "call(AA BB CC)", offset: 14)) + XCTAssertEqual(state, State(string: "call(AA BB CC)", offset: 14, line: 1, column: 15)) case .failure: XCTFail() } - switch letter.many(separatedBy: number).decode(State(string: "1", offset: 0)) { + switch letter.many(separatedBy: number).decode(State(string: "1", offset: 0, line: 1, column: 2)) { case .success((let element, let state)): XCTAssertEqual(element, []) - XCTAssertEqual(state, State(string: "1", offset: 0)) + XCTAssertEqual(state, State(string: "1", offset: 0, line: 1, column: 2)) case .failure: XCTFail() } @@ -143,11 +143,11 @@ final class StringDecoderCombinatorsTests: XCTestCase { switch satisfy({ $0.isLetter }) .tryAhead(match("nn")) .many - .decode(State(string: "cadnntest", offset: 0)) + .decode(State(string: "cadnntest", offset: 0, line: 1, column: 2)) { case .success((let element, let state)): XCTAssertEqual(element, ["c", "a", "d"]) - XCTAssertEqual(state, State(string: "cadnntest", offset: 3)) + XCTAssertEqual(state, State(string: "cadnntest", offset: 3, line: 1, column: 5)) case .failure(_): XCTFail() } @@ -156,18 +156,18 @@ final class StringDecoderCombinatorsTests: XCTestCase { func testLookAheadCombinator() { switch satisfy({ $0.isLetter }) .lookAhead(match(Character("c"))) - .decode(State(string: "cca", offset: 0)) + .decode(State(string: "cca", offset: 0, line: 1, column: 2)) { case .success((let element, let state)): XCTAssertEqual(element, Character("c")) - XCTAssertEqual(state, State(string: "cca", offset: 1)) + XCTAssertEqual(state, State(string: "cca", offset: 1, line: 1, column: 3)) case .failure(_): XCTFail() } switch satisfy({ $0.isLetter }) .lookAhead(match(Character("c"))) - .decode(State(string: "j1a", offset: 0)) + .decode(State(string: "j1a", offset: 0, line: 1, column: 2)) { case .success: XCTFail()