Fuzion Logo
flang.dev — The Fuzion Language Portal
JavaScript seems to be disabled. Functionality is limited.

stream.fz


# This file is part of the Fuzion language implementation.
#
# The Fuzion language implementation is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation, version 3 of the License.
#
# The Fuzion language implementation is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License along with The
# Fuzion language implementation.  If not, see <https://www.gnu.org/licenses/>.


# -----------------------------------------------------------------------
#
#  Tokiwa Software GmbH, Germany
#
#  Source code of Fuzion standard library feature stream
#
#  Author: Fridtjof Siebert (siebert@tokiwa.software)
#
# -----------------------------------------------------------------------

# stream -- a stream of values
#
# NYI: Check if stream should be replaced by a lazy list, which is a choice
# of either nil or a tuple (head, tail). This should avoid the need to store
# mutable state.
stream(redef T type) ref : Sequence T is

  # create a list from this stream
  #
  asList0 option (some (list T)) := nil
  redef asList list T is
    if !asList0.exists
      set asList0 := some fromStream
    asList0.get.val

  # this must not be called more than once!
  private fromStream list T is
    if hasNext
      h := next
      ref : Cons T (list T)
        memoizedTail option (some (list T)) := nil
        head => h
        tail =>
          if !memoizedTail.exists
            set memoizedTail := some fromStream
          memoizedTail.get.val
    else
      nil


  # take n items from stream, less if stream has fewer than n items
  #
  redef take (n i32) =>
    for
      a := marray 0 (fuzion.sys.array T 0), a.add x
      i in (1..n)
    while hasNext
      x := next
    else
      (a.take i).asList


  # create a stream of T.
  #
  # A stream contains mutable state, so it cannot be reused or shared
  # between threads.
  #
  # Default implementation uses asList.  Heirs must redefine at least
  # one of asList or asStream.
  #
  redef asStream stream T is stream.this


  # apply f to all elements in this stream
  #
  redef forAll(f T -> unit) unit is while hasNext do f(next)


  # apply f to all elements in this stream, infix operator synonyme of forAll.
  #
  redef infix | (f T -> unit) unit is while hasNext do f(next)


  # apply 'f' to each element 'e' as long as 'f e'
  #
  redef forWhile(f T -> bool) unit is while hasNext && f(next)


  # create a new stream that contains the first elements of this stream for
  # which 'f e' is false
  #
  redef before(f T -> bool) stream T is before0 f
  before0(f T -> bool) ref : stream T is
    nextCache := stream.this.next  # NYI should be option T and set to None if !stream.this.hasNext
    redef hasNext => !f(nextCache)
    redef next T is
      res := nextCache
      set nextCache := stream.this.next
      res


  # create new stream from all elements for which predicate f is true
  #
  redef filter(f T -> bool) Sequence T is filter0 f
  filter0(f T -> bool) ref : stream T is
    cache T := ?  # NYI: should be option T
    cacheOk := false
    next2
    redef hasNext => cacheOk
    redef next T is
      res := cache
      set cacheOk := false
      next2
      res
    next2 unit is
      while !cacheOk && stream.this.hasNext
        set cache := stream.this.next
        set cacheOk := f(cache)


  # check if predicate f holds for all elements produced by this stream
  #
  redef infix ∀ (f T -> bool) bool is
    while hasNext: f next   # hasNext implies f next
    until !hasNext


  # check if predicate f holds for at least one element produced by this stream
  #
  redef infix ∃ (f T -> bool) bool is
    while hasNext
    until f next


  # does this stream have one more element?
  #
  hasNext bool is abstract


  # the next element in this stream
  #
  next T
  /* NYI: C backend creates broken code for calling precondition of abstract feature
    pre
      hasNext
   */
  is abstract


  # get the next element or nil if !hasNext
  #
  nextIfExists option T is
    if hasNext
      next
    else
      nil


  # print the elements of this stream
  #
  print unit is
    forAll (x ->
      yak x
      if hasNext then yak ", ")


  # count the elements of this stream
  #
  redef count i32 is
    # NYI: check if this works: (map i32 x->1).fold i32.sum
    asList.map i32 x->1
          .fold i32.sum


  # collect all items from this stream into an array
  #
  redef asArray array T is
    for
      a := marray 0 (fuzion.sys.array T 0), a.add x
    while hasNext
      x := next
    else
      a.as_array


  # create a stream that consists of all be the elements if this stream followed
  # by all the elements of s
  #
  concatStreams (s stream T) ref : stream T is
    hasNext => stream.this.hasNext || s.hasNext
    next => if (stream.this.hasNext) stream.this.next else s.next


  # create a string from the elements of this stream
  #
  redef asString ref string is
    (map string (x -> x.asString))
      .fold (strings.concat ", ")


  # create a string representation of this list including all the string
  # representations of its contents, separated by 'sep'.
  #
  redef asString (sep string) => asList.asString sep


  # map the stream to a new stream applying function f to all elements
  #
  # This performs a lazy mapping, f is called only when the elements
  # are taken from the stream.
  #
  map(B type, f T -> B) stream B is
    ref : stream B
      hasNext => stream.this.hasNext
      next B is f stream.this.next


  # NYI: This currently does not work with the C backend since generics resolution
  # for stream.this.next confuses the type from the outer feature (T) with the type
  # from the inherited feature (B).
  map_broken(B type, f T -> B) : stream B is
    hasNext => stream.this.hasNext
    next B is f stream.this.next


  # fold the elements of this stream using the given monoid.
  #
  # e.g., to sum the elements of a stream of i32, use s.fold i32.sum
  #
  redef fold (m Monoid T) => fold m.e m


  # fold the elements of this stream using the given monoid m and initial value s.
  #
  # e.g., to sum the elements of a stream of i32, use s.fold i32.sum
  #
  fold (s T, m Monoid T) =>
    for
      r := s, m.op r next
    while hasNext
    else
      r


# streams -- unit type defining features related
# to streams but not requiring an instance
#
streams is

  # creates a - potentially infinite - stream by using given supplier
  # end of stream is reached once supplier returns nil
  # note that the supplier is called only once for each hasNext/next cycle
  #
  generate(T type, supplier () -> option T) stream T is

    not_initialized is
    depleted is
    val := mut (choice T not_initialized depleted) not_initialized

    read =>
      match val.get
        not_initialized =>
          match supplier()
            t T => val <- t
            nil => val <- depleted
        * =>
      val.get

    reset =>
      val <- not_initialized

    ref: stream T
      redef hasNext bool is
        match read
          v T => true
          * => false
      redef next T is
        match read
          v T => reset; v
          * => fuzion.std.panic "there is no next value available"