/*
* Copyright (c) 2017-2018 Thomas Otterson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* A set of channel utilities for converting channels into other kinds of data, and vice versa.
*
* @module cispy/utils/conversion
* @private
*/
const { chan, close, CLOSED } = require('../channel');
const { put, take, putAsync } = require('../ops');
/**
* **Creates a single value from a channel by running its values through a reducing function.**
*
* For every value put onto the input channel, the reducing function is called with two parameters: the accumulator that
* holds the result of the reduction so far, and the new input value. The initial value of the accumulator is the third
* parameter to `reduce`. The reduction is not complete until the input channel closes.
*
* This function returns a channel. When the final reduced value is produced, it is put onto this channel, and when that
* value is taken from it, the channel is closed.
*
* ```
* const { go, chan, put, take, close, utils } = cispy;
* const { reduce } = utils;
*
* const input = chan();
* const output = reduce((acc, value) => acc + value, input, 0);
*
* go(async () => {
* await put(input, 1);
* await put(input, 2);
* await put(input, 3);
* close(input);
* });
*
* go(async () => {
* const result = await take(output);
* console.log(output); // -> 6
* });
*
* ```
*
* Note that the input channel *must* be closed at some point, or no value will ever appear on the output channel. The
* closing of the channel is what signifies that the reduction should be completed.
*
* @memberOf module:cispy/utils~CispyUtils
* @param {module:cispy/utils~reducer} fn The reducer function responsible for turning the series of channel
* values into a single output value.
* @param {module:cispy/channel~Channel} ch The channel whose values are being reduced into a single output value.
* @param {*} init The initial value to feed into the reducer function for the first reduction step.
* @return {module:cispy/channel~Channel} A channel that will, when the input channel closes, have the reduced
* value put into it. When this value is taken, the channel will automatically close.
*/
function reduce(fn, ch, init) {
const output = chan();
async function loop() {
let acc = init;
for (;;) {
const value = await take(ch);
if (value === CLOSED) {
putAsync(output, acc, () => close(output));
return;
}
acc = fn(acc, value);
}
}
loop();
return output;
}
/**
* **Puts all values from an array onto the supplied channel.**
*
* If no channel is passed to this function, a new channel is created. In effect, this directly converts an array into a
* channel with the same values on it.
*
* The channel is closed after the final array value is put onto it.
*
* ```
* const { go, chan, take, utils } = cispy;
* const { onto } = utils;
*
* const input = [1, 2, 3];
* const output = onto(input);
*
* go(async () => {
* console.log(await take(output)); // -> 1
* console.log(await take(output)); // -> 2
* console.log(await take(output)); // -> 3
* console.log(output.closed); // -> true
* });
* ```
*
* @memberOf module:cispy/util~CispyUtils
* @param {module:cispy/channel~Channel} [ch] The channel onto which to put all of the array elements. If this is
* not present, a new channel will be created.
* @param {Array} array The array of values to be put onto the channel.
* @return {module:cispy/channel~Channel} the channel onto which the array elements are put. This is the same as
* the input channel, but if no input channel is specified, this will be a new channel. It will close when the final
* value is taken from it.
*/
function onto(ch, array) {
const [chnl, arr] = Array.isArray(ch) ? [chan(ch.length), ch] : [ch, array];
async function loop() {
for (const item of arr) {
await put(chnl, item);
}
close(chnl);
}
loop();
return chnl;
}
/**
* **Takes all of the values from a channel and pushes them into an array.**
*
* If no array is passed to this function, a new (empty) one is created. In effect, this directly converts a channel
* into an array with the same values. Either way, this operation cannot complete until the input channel is closed.
*
* This function returns a channel. When the final array is produced, it is put onto this channel, and when that value
* is taken from it, the channel is closed.
*
* ```
* const { go, chan, put, take, close, utils } = cispy;
* const { into } = utils;
*
* const input = chan();
* const output = into(input);
*
* go(async () => {
* await put(input, 1);
* await put(input, 2);
* await put(input, 3);
* close(input);
* });
*
* go(async () => {
* const result = await take(output);
* console.log(result); // -> [1, 2, 3]
* });
* ```
*
* Note that the input channel *must* be closed at some point, or no value will ever appear on the output channel. The
* closing of the channel is what signifies that all of the values needed to make the array are now available.
*
* @memberOf module:cispy/util~CispyUtils
* @param {Array} [array] The array to put the channel values into. If this is not present, a new, empty array will be
* created.
* @param {module:cispy/channel~Channel} ch The channel from which values are taken to put into the array.
* @return {module:cispy/channel~Channel} A channel that will, when the input channel closes, have the array of
* channel values put onto it. When this array is taken, the channel will automatically close.
*/
function into(array, ch) {
const [arr, chnl] = Array.isArray(array) ? [array, ch] : [[], array];
const init = arr.slice();
return reduce(
(acc, input) => {
acc.push(input);
return acc;
},
chnl,
init
);
}
module.exports = {
reduce,
onto,
into
};