1. # The Iterator role defines the API for an iterator and provides simple
  2. # fallback implementations for most of it, so any given iterator can pick
  3. # and choose what bits it can implement better for performance and/or
  4. # correctness reasons.
  5. my role Iterator {
  6. # Pulls one value from the iterator. If there's nothing more to pull,
  7. # returns the constant IterationEnd. If you don't override any other
  8. # methods in this role, they'll all end up falling back to using this.
  9. method pull-one() { ... }
  10. # Skip one value from the iterator. Should return a true-like value to
  11. # indicate the skip was successful. Override this method if you can
  12. # make an iterator that has significantly less to do when skipping a
  13. # generated value.
  14. method skip-one() {
  15. nqp::not_i(nqp::eqaddr(self.pull-one,IterationEnd))
  16. }
  17. # Has the iterator produce a certain number of values and push them into
  18. # the target. The only time the iterator may push less values than asked
  19. # for is when it reaches the end of the iteration. It may never push more
  20. # values than are requested. Iterators that can do something smarter than
  21. # the default implementation here should override this method. Should
  22. # return how many things were pushed. Note that if the iterator does any
  23. # side-effects as a result of producing values then up to $n of them will
  24. # occur; you must be sure this is desired. Returns the number of things
  25. # pushed, or IterationEnd if it reached the end of the iteration.
  26. method push-exactly($target, int $n) {
  27. nqp::stmts(
  28. (my int $i = -1),
  29. nqp::until( # doesn't sink
  30. nqp::isge_i($i = nqp::add_i($i,1),$n)
  31. || nqp::eqaddr((my $pulled := self.pull-one),IterationEnd),
  32. $target.push($pulled) # don't .sink $pulled here, it can be a Seq
  33. ),
  34. nqp::if(
  35. nqp::eqaddr($pulled,IterationEnd),
  36. IterationEnd,
  37. $i
  38. )
  39. )
  40. }
  41. # Has the iteration push at least a certain number of values into the
  42. # target buffer. For iterators that do side-effects, this should always
  43. # be the same as push-exactly. Those that know they can safely work ahead
  44. # to achieve better throughput may do so. Returns the number of things
  45. # pushed, or IterationEnd if it reached the end of the iteration.
  46. method push-at-least($target, int $n) {
  47. self.push-exactly($target, $n)
  48. }
  49. # Has the iterator produce all of its values into the target. Typically
  50. # called in .STORE if the iterator is non-lazy. Returns IterationEnd.
  51. method push-all($target --> IterationEnd) {
  52. nqp::until( # we may not .sink $pulled here, since it can be a Seq
  53. nqp::eqaddr((my $pulled := self.pull-one),IterationEnd),
  54. $target.push($pulled)
  55. )
  56. }
  57. # Pushes things until we hit a lazy iterator (one whose is-lazy method returns
  58. # True). The default works well for non-composite iterators (that is, those
  59. # that don't trigger the evaluation of other iterators): it looks at the
  60. # lazy property of itself, and if it's true, does nothing, otherwise it
  61. # calls push-all. If all values the iterator can produce are pushed, then
  62. # IterationEnd should be returned. Otherwise, return something else (Mu
  63. # will do fine).
  64. method push-until-lazy($target) {
  65. nqp::unless(
  66. self.is-lazy,
  67. self.push-all($target)
  68. )
  69. }
  70. # Skip the given number of values. Return true if succesful in
  71. # skipping that many values.
  72. method skip-at-least(int $toskip) {
  73. nqp::stmts(
  74. (my int $left = $toskip),
  75. nqp::while(
  76. nqp::isge_i(($left = nqp::sub_i($left,1)),0) && self.skip-one,
  77. nqp::null
  78. ),
  79. nqp::islt_i($left,0)
  80. )
  81. }
  82. # Skip the given number of values produced before returning the next
  83. # pulled value. Given 0 it is an expensive way to do .pull-one
  84. method skip-at-least-pull-one(int $toskip) {
  85. nqp::if(
  86. self.skip-at-least($toskip),
  87. self.pull-one,
  88. IterationEnd
  89. )
  90. }
  91. # The optional "count-only" method in an Iterator class returns the number
  92. # of elements that the iterator would be return when generating values,
  93. # but *without* actually generating any values. This can e.g. be the case
  94. # when an iterator is created for a hash, or for all the characters in a
  95. # string, of which the number elements is already known.
  96. # method count-only(--> Int:D) { ... }
  97. # The optional "bool-only" method in an Iterator class returns a Bool
  98. # to indicate whether the generator is able to generate *any* value,
  99. # *without* actually generating any value. This can e.g. be the case
  100. # when an iterator is created for a hash.
  101. # method bool-only(--> Bool:D) { ... }
  102. # Consumes all of the values in the iterator for their side-effects only.
  103. # May be overridden by iterators to either warn about use of things in
  104. # sink context that should not be used that way, or to process things in
  105. # a more efficient way when we know we don't need the results.
  106. method sink-all(--> IterationEnd) {
  107. nqp::until(
  108. nqp::eqaddr(self.pull-one,IterationEnd),
  109. nqp::null
  110. )
  111. }
  112. # Whether the iterator is lazy (True if yes, False if no).
  113. # If True, the iterator must *never* try to evaluate more than the
  114. # user absolutely asks for. This has e.g. effect on the behaviour
  115. # on .STORE: a lazy iterator would not reify, a non-lazy would.
  116. method is-lazy(--> False) { }
  117. }