Expression.swift 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import Foundation
  2. // Memoizes the given closure, only calling the passed
  3. // closure once; even if repeat calls to the returned closure
  4. internal func memoizedClosure<T>(_ closure: @escaping () throws -> T) -> (Bool) throws -> T {
  5. var cache: T?
  6. return { withoutCaching in
  7. if withoutCaching || cache == nil {
  8. cache = try closure()
  9. }
  10. return cache!
  11. }
  12. }
  13. /// Expression represents the closure of the value inside expect(...).
  14. /// Expressions are memoized by default. This makes them safe to call
  15. /// evaluate() multiple times without causing a re-evaluation of the underlying
  16. /// closure.
  17. ///
  18. /// @warning Since the closure can be any code, Objective-C code may choose
  19. /// to raise an exception. Currently, Expression does not memoize
  20. /// exception raising.
  21. ///
  22. /// This provides a common consumable API for matchers to utilize to allow
  23. /// Nimble to change internals to how the captured closure is managed.
  24. public struct Expression<T> {
  25. // swiftlint:disable identifier_name
  26. internal let _expression: (Bool) throws -> T?
  27. internal let _withoutCaching: Bool
  28. // swiftlint:enable identifier_name
  29. public let location: SourceLocation
  30. public let isClosure: Bool
  31. /// Creates a new expression struct. Normally, expect(...) will manage this
  32. /// creation process. The expression is memoized.
  33. ///
  34. /// @param expression The closure that produces a given value.
  35. /// @param location The source location that this closure originates from.
  36. /// @param isClosure A bool indicating if the captured expression is a
  37. /// closure or internally produced closure. Some matchers
  38. /// may require closures. For example, toEventually()
  39. /// requires an explicit closure. This gives Nimble
  40. /// flexibility if @autoclosure behavior changes between
  41. /// Swift versions. Nimble internals always sets this true.
  42. public init(expression: @escaping () throws -> T?, location: SourceLocation, isClosure: Bool = true) {
  43. self._expression = memoizedClosure(expression)
  44. self.location = location
  45. self._withoutCaching = false
  46. self.isClosure = isClosure
  47. }
  48. /// Creates a new expression struct. Normally, expect(...) will manage this
  49. /// creation process.
  50. ///
  51. /// @param expression The closure that produces a given value.
  52. /// @param location The source location that this closure originates from.
  53. /// @param withoutCaching Indicates if the struct should memoize the given
  54. /// closure's result. Subsequent evaluate() calls will
  55. /// not call the given closure if this is true.
  56. /// @param isClosure A bool indicating if the captured expression is a
  57. /// closure or internally produced closure. Some matchers
  58. /// may require closures. For example, toEventually()
  59. /// requires an explicit closure. This gives Nimble
  60. /// flexibility if @autoclosure behavior changes between
  61. /// Swift versions. Nimble internals always sets this true.
  62. public init(memoizedExpression: @escaping (Bool) throws -> T?, location: SourceLocation, withoutCaching: Bool, isClosure: Bool = true) {
  63. self._expression = memoizedExpression
  64. self.location = location
  65. self._withoutCaching = withoutCaching
  66. self.isClosure = isClosure
  67. }
  68. /// Returns a new Expression from the given expression. Identical to a map()
  69. /// on this type. This should be used only to typecast the Expression's
  70. /// closure value.
  71. ///
  72. /// The returned expression will preserve location and isClosure.
  73. ///
  74. /// @param block The block that can cast the current Expression value to a
  75. /// new type.
  76. public func cast<U>(_ block: @escaping (T?) throws -> U?) -> Expression<U> {
  77. return Expression<U>(
  78. expression: ({ try block(self.evaluate()) }),
  79. location: self.location,
  80. isClosure: self.isClosure
  81. )
  82. }
  83. public func evaluate() throws -> T? {
  84. return try self._expression(_withoutCaching)
  85. }
  86. public func withoutCaching() -> Expression<T> {
  87. return Expression(
  88. memoizedExpression: self._expression,
  89. location: location,
  90. withoutCaching: true,
  91. isClosure: isClosure
  92. )
  93. }
  94. }