diff --git a/Array.js b/Array.js index 9524b18..dd94f9f 100644 --- a/Array.js +++ b/Array.js @@ -21,6 +21,7 @@ var generator = require('./generator') // NOTE: this is used in a similar fashion to Python's StopIteration... var STOP = +module.STOP = object.STOP @@ -42,7 +43,7 @@ var wrapIterFunc = function(iter){ } catch(err){ if(err === STOP){ return - } else if( err instanceof STOP){ + } else if(err instanceof STOP){ return err.value } throw err } } } diff --git a/README.md b/README.md index 8d8ba6c..f7aa64b 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ A library of JavaScript type extensions, types and type utilities. - [The basics](#the-basics) - [`generator.Generator`](#generatorgenerator) - [`generator.iter(..)`](#generatoriter) + - [`generator.STOP`](#generatorstop) - [Generator instance iteration](#generator-instance-iteration) - [`.map(..)` / `.filter(..)` / `.reduce(..)`](#generatormap--generatorfilter--generatorreduce) - [`.slice(..)`](#generatorslice) @@ -90,6 +91,15 @@ A library of JavaScript type extensions, types and type utilities. - [`.map(..)` / `.filter(..)` / `.reduce(..)` / `.flat()`](#generatormap--generatorfilter--generatorreduce--generatorflat) - [`.toArray()`](#generatortoarray-1) - [`.then(..)` / `.catch(..)` / `.finally(..)`](#generatorthen--generatorcatch--generatorfinally-1) + - [Generator combinators](#generator-combinators) + - [`.chain(..)` / `.chain(..)`](#generatorchain--generatorchain) + - [`.concat(..)` / `.concat(..)`](#generatorconcat--generatorconcat) + - [Generator library](#generator-library) + - [`generator.range(..)`](#generatorrange) + - [`generator.repeat(..)`](#generatorrepeat) + - [`generator.produce(..)`](#generatorproduce) + - [Generator helpers](#generator-helpers) + - [`generator.stoppable(..)`](#generatorstoppable) - [Containers](#containers) - [`containers.UniqueKeyMap()` (`Map`)](#containersuniquekeymap-map) - [`.set(..)`](#unique-key-mapset) @@ -1625,6 +1635,12 @@ But `Generator()` takes no arguments and thus can not be used as a wrapper while `.iter(..)` is designed to accept an iterable value like an array object. +#### `generator.STOP` + + + + + ### Generator instance iteration This is a set of `Array`-like iterator methods that enable chaining of @@ -1651,6 +1667,22 @@ var L = [1,2,3] .toArray() ``` +Throwing `STOP` form within the handler will stop generation, throwing +`STOP()` will yield the `` then stop. +```javascript +var stopAt = function(n){ + return function(e){ + if(e == n){ + // stop iteration yielding the value we are stopping at... + throw generator.STOP(e) } + return e } } + +var L = [1,2,3,4,5] + .iter() + .map(stopAt(3)) + .toArray() +``` + + +#### `.chain(..)` / `.chain(..)` + +```bnf +.chain(, ..) + -> + +.chain(, ..) + -> +``` + + +```javascript +// double each element... +var x2 = generator.iter + .map(function(e){ return e * 2 }) + +generator.range(0, 100).chain(x2) +``` + +#### `.concat(..)` / `.concat(..)` + +Concatenate the results from generators +```bnf +.concat(, ..) + -> + +.concat(, ..) + -> +``` + + + + + + + + + + + +### Generator library + +#### `generator.range(..)` + +Create a generator yielding a range of numbers +```bnf +range() +range() +range(, ) +range(, , ) + -> +``` + + + + +#### `generator.repeat(..)` + +Create a generator repeatedly yielding `` +```bnf +repeat() +repeat() +repeat(, ) + -> + +() + -> +``` + +If no value is given `true` is yielded by default. + +`` if given will be called with each `` before it is yielded and +if it returns `false` the iteration is stopped. + + + + +#### `generator.produce(..)` + +Create a generator calling a function to produce yielded values +```bnf +produce() +produce() + -> + +() + -> +``` + +`(..)` can `throw` `STOP` or `STOP()` to stop production at any time. + + + + + +### Generator helpers + +#### `generator.stoppable(..)` + +Wrap function/generator adding support for stopping mid-iteration by throwing `STOP`. + +```bnf +stoppable() + -> +``` + + + + + ## Containers ```javascript diff --git a/generator.js b/generator.js index c351a52..406f5cc 100644 --- a/generator.js +++ b/generator.js @@ -12,7 +12,15 @@ var object = require('ig-object') /*********************************************************************/ -// The generator hirearchy in JS is a bit complicated. + +// NOTE: this is used in a similar fashion to Python's StopIteration... +var STOP = +module.STOP = + object.STOP + + +//--------------------------------------------------------------------- +// The generator hierarchy in JS is a bit complicated. // // Consider the following: // @@ -93,29 +101,42 @@ Generator.iter = // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Helpers... +// +// makeGenerator() +// -> +// +// makeGenerator(, ) +// -> +// +// +// (...args) +// -> +// +// (...inputs) +// -> +// +// (args, ...inputs) +// -> args +// +// // XXX this needs to be of the correct type... (???) // XXX need to accept generators as handlers... -var makeGenerator = function(name){ +var makeGenerator = function(name, pre){ return function(...args){ var that = this return Object.assign( function*(){ - // XXX need to accept generator as handler (i.e. args[0] instanceof Generator)... - /*/ XXX EXPERIMENTAL... - if(args[0] instanceof Generator){ - yield* that(...arguments)[name](...args) - .map(function(g){ - return [...g] }) - .flat() - return } - //*/ - yield* that(...arguments)[name](...args) }, + var a = pre ? + pre.call(this, args, ...arguments) + : args + yield* that(...arguments)[name](...a) }, { toString: function(){ return [ that.toString(), // XXX need to normalize args better... `.${ name }(${ args.join(', ') })`, ].join('\n ') }, }) } } + // XXX do a better doc... var makePromise = function(name){ return function(...args){ @@ -124,6 +145,34 @@ var makePromise = function(name){ return that(...arguments)[name](func) } } } +var stoppable = +module.stoppable = +function(func){ + return Object.assign( + func instanceof Generator ? + function*(){ + try{ + yield* func.call(this, ...arguments) + } catch(err){ + if(err === STOP){ + return + } else if(err instanceof STOP){ + yield err.value + return } + throw err } } + : function(){ + try{ + return func.call(this, ...arguments) + } catch(err){ + if(err === STOP){ + return + } else if(err instanceof STOP){ + return err.value } + throw err } }, + { toString: function(){ + return func.toString() }, }) } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - var GeneratorMixin = @@ -183,6 +232,20 @@ object.Mixin('GeneratorMixin', 'soft', { then: makePromise('then'), catch: makePromise('catch'), finally: makePromise('finally'), + + // combinators... + // + chain: makeGenerator('chain'), + concat: makeGenerator('concat', + // initialize arguments... + function(next, ...args){ + return next + .map(function(e){ + return (e instanceof Generator + || typeof(e) == 'function') ? + e(...args) + : e }) }), + //zip: makeGenerator('zip'), }) @@ -242,24 +305,24 @@ object.Mixin('GeneratorProtoMixin', 'soft', { // will be expanded... // NOTE: there is no point to add generator-handler support to either // .filter(..) or .reduce(..) - map: function*(func){ + map: stoppable(function*(func){ var i = 0 if(func instanceof Generator){ for(var e of this){ yield* func(e, i++, this) } } else { for(var e of this){ - yield func(e, i++, this) } } }, - filter: function*(func){ + yield func(e, i++, this) } } }), + filter: stoppable(function*(func){ var i = 0 for(var e of this){ if(func(e, i++, this)){ - yield e } } }, - reduce: function*(func, res){ + yield e } } }), + reduce: stoppable(function*(func, res){ var i = 0 for(var e of this){ res = func(res, e, i++, this) } - yield res }, + yield res }), pop: function(){ return [...this].pop() }, @@ -291,8 +354,27 @@ object.Mixin('GeneratorProtoMixin', 'soft', { finally: function(func){ return this.promise().finally(func) }, + // combinators... + // + chain: function*(...next){ + yield* next + .reduce(function(cur, next){ + return next(cur) }, this) }, + concat: function*(...next){ + yield* this + for(var e of next){ + yield* e } }, // XXX EXPERIMENTAL... + /* XXX not sure how to do this yet... + tee: function*(...next){ + // XXX take the output of the current generator and feed it into + // each of the next generators... (???) + }, + zip: function*(...items){ + // XXX + }, + //*/ }) @@ -300,6 +382,38 @@ GeneratorMixin(GeneratorPrototype) GeneratorProtoMixin(GeneratorPrototype.prototype) +//--------------------------------------------------------------------- +// Generators... + +// NOTE: step can be 0, this will repeat the first element infinitely... +var range = +module.range = +function*(from, to, step){ + if(to == null){ + to = from + from = 0 } + step = step ?? (from > to ? -1 : 1) + while(step > 0 ? + from < to + : from > to){ + yield from + from += step } } + + +var repeat = +module.repeat = +function*(value=true, stop){ + while( typeof(stop) == 'function' && stop(value) ){ + yield value } } + + +var produce = +module.produce = +stoppable(function*(func){ + while(true){ + yield func() } }) + + /********************************************************************** diff --git a/main.js b/main.js index 699ed96..c6f5fd7 100644 --- a/main.js +++ b/main.js @@ -7,6 +7,8 @@ (function(require){ var module={} // make module AMD/node compatible... /*********************************************************************/ +var object = require('ig-object') + // Extend built-in types... require('./Object') require('./Array') @@ -26,6 +28,7 @@ module.runner = require('./runner') // Shorthands... +module.STOP = object.STOP // NOTE: this is a generic enough type to be accessible at the root... module.Generator = module.generator.Generator diff --git a/package.json b/package.json index 4771b1b..fd65a49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ig-types", - "version": "6.2.1", + "version": "6.3.0", "description": "Generic JavaScript types and type extensions...", "main": "main.js", "scripts": {