Skip to content

Commit

Permalink
Add a monadic bind operator
Browse files Browse the repository at this point in the history
  • Loading branch information
kyouko-taiga committed Nov 9, 2022
1 parent 36ac47e commit 3d802c0
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 3 deletions.
4 changes: 2 additions & 2 deletions Sources/Durian/AnyCombinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ public struct AnyCombinator<Context, Element>: Combinator {

private var _parse: (inout Context) throws -> Element?

/// Creates a new combinator that forwards operations to `base`.
/// Creates a type-erased combinator that forwards operations to `base`.
public init<C: Combinator>(_ base: C) where C.Context == Context, C.Element == Element {
self._parse = base.parse(_:)
}

/// Creates a new combinator that forwards operations to `parse`.
/// Creates a type-erased combinator that forwards operations to `parse`.
public init(parse: @escaping (inout Context) throws -> Element?) {
self._parse = parse
}
Expand Down
31 changes: 31 additions & 0 deletions Sources/Durian/Bind.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/// A combinator that applies a combinator and then, if successful, passes its result to a
/// function returning the next combinator to apply.
public struct Bind<Base: Combinator, Next: Combinator>: Combinator
where Base.Context == Next.Context
{

public typealias Context = Base.Context

public typealias Element = Next.Element

/// The base combinator.
public let base: Base

/// A function that accepts the result of `base` and returns a new combinator.
public let makeNext: (Base.Element) throws -> Next

/// Creates a combinator that applies `base` and then the combinator returned by `makeNext`.
public init(_ base: Base, and makeNext: @escaping (Base.Element) throws -> Next) {
self.base = base
self.makeNext = makeNext
}

public func parse(_ context: inout Context) throws -> Next.Element? {
if let a = try base.parse(&context) {
return try makeNext(a).parse(&context)
} else {
return nil
}
}

}
9 changes: 8 additions & 1 deletion Sources/Durian/Combinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ extension Combinator {
Combine(self, and: other, else: makeHardFailure)
}

/// Creates a combinator that applies `self` and then the result of `makeNext`.
public func bind<Next: Combinator>(
_ makeNext: @escaping (Element) throws -> Next
) -> Bind<Self, Next> {
Bind(self, and: makeNext)
}

/// Creates a combinator that applies `self`, or backtracks and applies `other` when `self`
/// return a soft failure.
public func or<Other: Combinator>(_ other: Other) -> Choose<Self, Other> {
Expand All @@ -48,7 +55,7 @@ extension Combinator {
TryCatch(trying: self, orCatchingAndApplying: other)
}

/// Creates a combinators that transforms the result of `self`.
/// Creates a combinator that transforms the result of `self`.
public func map<T>(
_ transform: @escaping (inout Context, Element) throws -> T
) -> Transform<Self, T> {
Expand Down
10 changes: 10 additions & 0 deletions Tests/DurianTests/DurianTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ final class DurianTests: XCTestCase {
XCTAssertEqual(context, "bc")
}

func testBind() throws {
let a = PopFirst<Substring>(if: { $0 == "a" })
let aAndA = a.bind({ (ch) in PopFirst(if: { $0 == ch }) })

let input = "aab"
var context = input.suffix(from: input.startIndex)
XCTAssertEqual(try aAndA.parse(&context), "a")
XCTAssertEqual(context, "b")
}

func testChooseFirst() {
let a = PopFirst<Substring>(if: { $0 == "a" })
let b = PopFirst<Substring>(if: { $0 == "b" })
Expand Down

0 comments on commit 3d802c0

Please sign in to comment.