Category talk:Wren-iterate

From Rosetta Code

Source code

/* Module "iterate.wren" */

/* Stepped wraps a Sequence so it can be iterated by steps other than 1. */
class Stepped is Sequence {
    // Constructs a new stepped sequence.
    construct new(seq, step) {
        if (!(seq is Sequence)) Fiber.abort("First argument must be a sequence.")
        if (!((step is Num) && step.isInteger && step > 0)) {
            Fiber.abort("Second argument must be a positive integer.")
        }
        _seq = seq
        _step = step
    }

    // Ensures a range is ascending before passing it to the constructor.
    // It it isn't, returns an empty range. Useful when bounds are variable.
    static ascend(range, step) {
        if (!(range is Range)) Fiber.abort("First argument must be a range.")
        return (range.from <= range.to) ? new(range, step) : 0...0
    }

    // Ensures a range is descending before passing it to the constructor.
    // It it isn't, returns an empty range. Useful when bounds are variable.
    static descend(range, step) {
        if (!(range is Range)) Fiber.abort("First argument must be a range.")
        return (range.from >= range.to) ? new(range, step) : 0...0
    }

    // Convenience versions of the above methods which call them with a step of 1.
    static ascend(range)  { ascend(range,  1) }
    static descend(range) { descend(range, 1) }

    // Iterator protocol methods.
    iterate(iterator) {
        if (!iterator || _step == 1) {
            return _seq.iterate(iterator)
        } else {
            var count = _step
            while (count > 0 && iterator) {
               iterator = _seq.iterate(iterator)
               count = count - 1
            }
            return iterator
        }
    }

    iteratorValue(iterator) { _seq.iteratorValue(iterator) }
}

/*
    Reversed wraps a Sequence so it can be iterated in reverse
    and by steps other than 1. To ensure this always works, non-lists
    are converted internally to lists.
*/
class Reversed is Sequence {
    // Constructs a new reversed sequence.
    construct new(seq, step) {
        if (!(seq is Sequence)) Fiber.abort("First argument must be a sequence.")
        if (!((step is Num) && step.isInteger && step > 0)) {
            Fiber.abort("Second argument must be a positive integer.")
        }
        _seq = (seq is List) ? seq : seq.toList
        _step = step
    }

    // Convenience method which calls the constructor with a step of 1.
    static new(seq) { Reversed.new(seq, 1) }

    // Iterator protocol methods.
    iterate(iterator) {
        var it = _seq.iterate(iterator)
        if (it == null || it == 0) {
            it = _seq.count - 1
        } else if (it == false) {
            it = _seq.count - 1 - _step
        } else {
            it = it - 1 - _step
        }
        return (it >= 0) ? it : false
    }

    iteratorValue(iterator) { _seq.iteratorValue(iterator) }
}

/* SeqEntry represents an (index, value) pair for use with the Indexed class. */
class SeqEntry {
    // Constructs a new SeqEntry object.
    construct new(index, value) {
        _index = index
        _value = value
    }

    // Properties.
    index { _index }
    value { _value }

    // Returns the current instance's string representation.
    toString { "%(_index):%(_value)" }
}

/*
    Indexed wraps a Sequence so its elements can be iterated over
    together with their zero-based indices. To ensure this always works, non-lists
    are converted internally to lists.
*/
class Indexed is Sequence {
    // Constructs a new indexed sequence with a step of 'step' and optionally reversed.
    construct new(seq, step, reversed) {
        if (!(reversed is Bool)) Fiber.abort("Third argument must be true or false.")
        _seq = (seq is List) ? seq : seq.toList
        _seq = !reversed ? Stepped.new(_seq, step) : Reversed.new(_seq, step)
    }

    // Constructs a new indexed sequence with a step of 'step' and 'reversed' set to false.
    static new(seq, step) { new(seq, step, false) }

    // Constructs a new indexed sequence with a step of 1 and reversed set to false.
    static new(seq) { new(seq, 1, false) }

    // Iterator protocol methods.
    iterate(iterator) { _seq.iterate(iterator) }

    iteratorValue(iterator) {
        return SeqEntry.new(iterator, _seq.iteratorValue(iterator))
    }
}

/*
    Loop enables a parameterless function to be called repeatedly either for a fixed number of times or
    until some condition is met. The function is always called at least once.
*/
class Loop {
    // Calls a parameterless function,'fn', in a loop 'n' times.
    // If 'fn' returns a value it is ignored.
    static times(n, fn) {
        if (!(n is Num) || !n.isInteger || n < 1) {
            Fiber.abort("n must be a positive integer.")
        }
        if (!(fn is Fn) || fn.arity > 0) {
            Fiber.abort("fn must be a function which takes no arguments.")
        }
        for (i in 1..n) fn.call()
    }

    // Calls a parameterless function,'fn', in a loop, at least once
    // until it returns 'value'. Other return values are ignored.
    static until(value, fn) {
        if (!(fn is Fn) || fn.arity > 0) {
            Fiber.abort("fn must be a function which takes no arguments.")
        }
        while (true) {
            var ret = fn.call()
            if (ret == value) return
        }
    }

    // Calls a parameterless function,'fn', in a loop, at least once
    // whilst the value it returns satisfies a predicate function 'pred'.
    static whilst(pred, fn) {
        if (!(pred is Fn) || pred.arity != 1) {
            Fiber.abort("pred must be a function which takes a single argument.")
        }
        if (!(fn is Fn) || fn.arity > 0) {
            Fiber.abort("fn must be a function will takes no arguments.")
        }
        while (true) {
            var ret = fn.call()
            if (!pred.call(ret)) return
        }
    }
}

/*
    Pipe iterates through a list of functions or fibers passing the output of each element
    to the next element in the list. Where the output is a list, it can either be passed as a
    single argument or spread into individual arguments. An initial argument must be passed to the 
    first function or fiber and additional arguments may be inserted anywhere in the pipeline.
*/
class Pipe {
    // Private method which calls the function or fiber 'fn' with argument 'a' which, if a list,
    // may be 'spread' or passed as a single argument and returns the result of that call.
    // Excess arguments are ignored but it is an error to pass too few arguments.
    static call_(fn, a, spread) {
        var n = (fn is Fn) ? fn.arity : 1
        if (!(a is List) || !spread) {
            if (n == 0) return fn.call()
            if (n == 1) return fn.call(a)
            Fiber.abort("Too few arguments.")
        }
        if (a.count < n) Fiber.abort("Too few arguments.")
        if (n == 0)  return fn.call()
        if (n == 1)  return fn.call(a[0])
        if (n == 2)  return fn.call(a[0], a[1])
        if (n == 3)  return fn.call(a[0], a[1], a[2])
        if (n == 4)  return fn.call(a[0], a[1], a[2], a[3])
        if (n == 5)  return fn.call(a[0], a[1], a[2], a[3], a[4])
        if (n == 6)  return fn.call(a[0], a[1], a[2], a[3], a[4], a[5])
        if (n == 7)  return fn.call(a[0], a[1], a[2], a[3], a[4], a[5], a[6])
        if (n == 8)  return fn.call(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7])
        if (n == 9)  return fn.call(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8])
        if (n == 10) return fn.call(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9])
        if (n == 11) return fn.call(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10])
        if (n == 12) return fn.call(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11])
        if (n == 13) return fn.call(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12])
        if (n == 14) return fn.call(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13])
        if (n == 15) return fn.call(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14])
        if (n == 16) return fn.call(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15])
    }

    // Creates a pipeline of function or fiber calls.
    // 'arg' is an argument to be passed to the first function or fiber. If the latter takes no arguments, it will be ignored
    // so anything can be passed in this scenario ('null' recommended).
    // 'fns' is a list of functions or fibers to be called successively using the result of the previous call as its argument.
    // 'fns' may also include the following values:
    // 1. true - expands the previous result if its a list (only necessary if 'autoSpread' is false).
    // 2. false - does not expand the previous result (only necessary if 'autoSpread' is true).
    // 3. a list - adds the contents of the list to the previous result and spreads it.
    // 3. a map - takes each (index, value) pair and inserts 'value' at index 'index' into the previous result and spreads it.
    // 'autoSpread' is a boolean representing the default value for spreading lists. Passing true or false in 'fns'
    // overrides the default for the next function or fiber in the pipeline.
    // Returns the result of calling the final function or fiber which may be null.
    static [arg, fns, autoSpread] {
        if (!(autoSpread is Bool)) Fiber.abort("autoSpread must be a boolean.")
        if (!(fns is List)) fns = [fns]
        var spread = autoSpread
        for (fn in fns) {
            if (fn is Bool) {
                spread = fn
            } else if (fn is List) {
                if (!(arg is List)) arg = [arg]
                arg.addAll(fn)
                spread = true
            } else if (fn is Map) {
                if (!(arg is List)) arg = [arg]
                for (me in fn) arg.insert(me.key, me.value)
                spread = true
            } else if ((fn is Fn) || (fn is Fiber)) {
                arg = call_(fn, arg, spread)
                spread = autoSpread
            } else {
                Fiber.abort ("Invalid argument.")
            }
        }
        return arg
    }

    // Convenience version of the above method which sets 'autoSpread' to false.
    static [arg, fns] { this[arg, fns, false] }
}