/* 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] }
}