From 2b58903675a0afd791055df9410d1c68ffb3b5ad Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Wed, 3 Aug 2022 01:57:57 +0300 Subject: [PATCH] npm update + tweaks... Signed-off-by: Alex A. Naanou --- Makefile | 2 + lib/doc.js | 137 +++++++++++++ lib/object.js | 504 +++++++++++++++++++---------------------------- lib/stoppable.js | 157 +++++++++++++++ 4 files changed, 495 insertions(+), 305 deletions(-) create mode 100755 lib/doc.js create mode 100755 lib/stoppable.js diff --git a/Makefile b/Makefile index a01b31d..9e13f71 100755 --- a/Makefile +++ b/Makefile @@ -7,6 +7,8 @@ BOOTSTRAP_FILES := \ README.md LOCAL_MODULES := \ + node_modules/ig-doc/doc.js \ + node_modules/ig-stoppable/stoppable.js \ node_modules/ig-object/object.js \ node_modules/ig-actions/actions.js \ node_modules/ig-features/features.js diff --git a/lib/doc.js b/lib/doc.js new file mode 100755 index 0000000..009c268 --- /dev/null +++ b/lib/doc.js @@ -0,0 +1,137 @@ +/********************************************************************** +* +* doc.js +* +* Basic JavaScript self-documentation utils +* +***********************************************/ /* c8 ignore next 2 */ +((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) +(function(require){ var module={} // make module AMD/node compatible... +/*********************************************************************/ + +module.TAB_SIZE = 4 + +module.LEADING_TABS = 1 + + +// Normalize code indent... +// +// normalizeIndent(text) +// -> text +// +// +// This will remove common indent from each line of text, this is useful +// for printing function code of functions that were defined at deep +// levels of indent. +// +// This will ignore the indent of the first line. +// +// If the last line is indented higher or equal to the rest of the text +// we will use leading_tabs (defaults to LEADING_TABS) to indent the +// rest of the text. +// This will indent the following styles correctnly: +// +// |function(a, b){ |function(a, b){ +// | return a + b } | return a + b +// | |} +// +// +// NOTE: this will trim out both leading and trailing white-space. +// NOTE: this is generally code-agnostic with one sigificant +// exception -- normalizeIndent(..) will break code written +// in Whitespace. +// +// XXX BUG? +// `a `a `a +// | b -> |b expected? | b +// | c` | c` | c` +// while: +// `a `a +// | b -> | b as expected. +// | c` | c` +// this leads to functions like the following to get messed up: +// |function(a){ +// | return a +// | || 'moo' } +// +// XXX is this the right place for this??? +// ...when moving take care that ImageGrid's core.doc uses this... +var normalizeIndent = +module.normalizeIndent = +function(text, {tab_size=module.TAB_SIZE, leading_tabs=module.LEADING_TABS, pad_tabs=0}={}){ + leading_tabs *= tab_size + var padding = ' '.repeat(pad_tabs*tab_size) + // prepare text... + var tab = ' '.repeat(tab_size || 0) + text = tab != '' ? + text.replace(/\t/g, tab) + : text + // trim the tail and remove leading blank lines... + var lines = text.trimEnd().split(/\n/) + while(lines.length > 0 + && lines[0].trim() == ''){ + // XXX we have two options here: + // - indent everyline including the first non-blank + // - do not indent anything (current) + // ...not sure which is best... + leading_tabs = 0 + lines.shift() } + // count common indent... + var l = lines + .reduce(function(l, e, i){ + var indent = e.length - e.trimLeft().length + return e.trim().length == 0 + // ignore 0 indent of first line... + || (i == 0 && indent == 0) ? + l + // last line... + : i == lines.length-1 + && indent >= l ? + // XXX feels a bit overcomplicated... + (l < 0 ? + // last of two with 0 indent on first -> indent... + Math.max(indent - leading_tabs, 0) + // ignore leading_tabs if lower indent... + : Math.min(l, Math.max(indent - leading_tabs, 0))) + // initial state... + : l < 0 ? + indent + // min... + : Math.min(l, indent) }, -1) || 0 + // normalize... + return padding + +lines + .map(function(line, i){ + return i == 0 ? + line + : line.slice(l) }) + .join('\n'+ padding) + .trim() } + + +// shorthand more suted for text... +var normalizeTextIndent = +module.normalizeTextIndent = +function(text, opts={leading_tabs: 0}){ + return module.normalizeIndent(text, opts) } + + +// template string tag versions of the above... +var doc = +module.doc = +function(strings, ...values){ + return normalizeIndent(strings + .map(function(s, i){ return s + (values[i] || '') }) + .join('')) } + +var text = +module.text = +function(strings, ...values){ + return normalizeTextIndent(strings + .map(function(s, i){ return s + (values[i] || '') }) + .join('')) } + + + +/********************************************************************** +* vim:set ts=4 sw=4 nowrap : */ return module }) diff --git a/lib/object.js b/lib/object.js index 70e6cde..1806716 100755 --- a/lib/object.js +++ b/lib/object.js @@ -21,6 +21,19 @@ (function(require){ var module={} // make module AMD/node compatible... /*********************************************************************/ +var doc = require('ig-doc') + +module.doc = doc.doc +module.text = doc.text +module.normalizeIndent = doc.normalizeIndent +module.normalizeTextIndent = doc.normalizeTextIndent + +var STOP = +module.STOP = + require('ig-stoppable').STOP + + +//--------------------------------------------------------------------- // Function methods to link into a constructor producing a callable // defined via .__call__(..) @@ -49,7 +62,9 @@ module.LINK_FUNCTION_METHODS = [ // var BOOTSTRAP = function(func){ - var b = BOOTSTRAP.__delayed = BOOTSTRAP.__delayed || [] + var b = BOOTSTRAP.__delayed = + BOOTSTRAP.__delayed + || [] func ? b.push(func) : b.map(function(f){ f() }) } @@ -58,144 +73,36 @@ function(func){ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -module.TAB_SIZE = 4 - -module.LEADING_TABS = 1 - - -// Normalize code indent... -// -// normalizeIndent(text) -// -> text -// -// -// This will remove common indent from each line of text, this is useful -// for printing function code of functions that were defined at deep -// levels of indent. -// -// This will ignore the indent of the first line. -// -// If the last line is indented higher or equal to the rest of the text -// we will use leading_tabs (defaults to LEADING_TABS) to indent the -// rest of the text. -// This will indent the following styles correctnly: -// -// |function(a, b){ |function(a, b){ -// | return a + b } | return a + b -// | |} -// -// -// NOTE: this will trim out both leading and trailing white-space. -// NOTE: this is generally code-agnostic with one sigificant -// exception -- normalizeIndent(..) will break code written -// in Whitespace. -// -// XXX BUG? -// `a `a `a -// | b -> |b expected? | b -// | c` | c` | c` -// while: -// `a `a -// | b -> | b as expected. -// | c` | c` -// this leads to functions like the following to get messed up: -// |function(a){ -// | return a -// | || 'moo' } -// -// XXX is this the right place for this??? -// ...when moving take care that ImageGrid's core.doc uses this... -var normalizeIndent = -module.normalizeIndent = -function(text, {tab_size=module.TAB_SIZE, leading_tabs=module.LEADING_TABS, pad_tabs=0}={}){ - leading_tabs *= tab_size - var padding = ' '.repeat(pad_tabs*tab_size) - // prepare text... - var tab = ' '.repeat(tab_size || 0) - text = tab != '' ? - text.replace(/\t/g, tab) - : text - // trim the tail and remove leading blank lines... - var lines = text.trimEnd().split(/\n/) - while(lines.length > 0 - && lines[0].trim() == ''){ - // XXX we have two options here: - // - indent everyline including the first non-blank - // - do not indent anything (current) - // ...not sure which is best... - leading_tabs = 0 - lines.shift() } - // count common indent... - var l = lines - .reduce(function(l, e, i){ - var indent = e.length - e.trimLeft().length - return e.trim().length == 0 - // ignore 0 indent of first line... - || (i == 0 && indent == 0) ? - l - // last line... - : i == lines.length-1 - && indent >= l ? - // XXX feels a bit overcomplicated... - (l < 0 ? - // last of two with 0 indent on first -> indent... - Math.max(indent - leading_tabs, 0) - // ignore leading_tabs if lower indent... - : Math.min(l, Math.max(indent - leading_tabs, 0))) - // initial state... - : l < 0 ? - indent - // min... - : Math.min(l, indent) }, -1) || 0 - // normalize... - return padding - +lines - .map(function(line, i){ - return i == 0 ? - line - : line.slice(l) }) - .join('\n'+ padding) - .trim() } - - -// shorthand more suted for text... -var normalizeTextIndent = -module.normalizeTextIndent = -function(text, opts={leading_tabs: 0}){ - return module.normalizeIndent(text, opts) } - - -// template string tag versions of the above... -var doc = -module.doc = -function(strings, ...values){ - return normalizeIndent(strings - .map(function(s, i){ return s + (values[i] || '') }) - .join('')) } - -var text = -module.text = -function(strings, ...values){ - return normalizeTextIndent(strings - .map(function(s, i){ return s + (values[i] || '') }) - .join('')) } - - // Get keys from prototype chain... // -// deepKeys(obj) -// deepKeys(obj, stop) +// deepKeys(obj[, all]) +// deepKeys(obj, stop[, all]) +// -> keys +// +// List all keys incuding non-enumerable... +// deepKeys(obj, true) +// deepKeys(obj, stop, true) // -> keys // // // NOTE: this is like Object.keys(..) but will get keys for all levels // till stop if given... +// NOTE: by default this lists only enumerable keys... var deepKeys = module.deepKeys = -function(obj, stop){ +function(obj, stop, all){ + all = arguments[arguments.length-1] + all = (all === true || all === false) ? + all + : false + stop = (stop === true || stop === false) ? + undefined + : stop var res = [] while(obj != null){ - res.push(Object.keys(obj)) + res.push(Object.keys(all ? + Object.getOwnPropertyDescriptors(obj) + : obj)) if(obj === stop){ break } obj = obj.__proto__ } @@ -218,8 +125,8 @@ function(obj, stop){ // Non-strict match... // match(a, b, true) // -// This is similar to the default case but uses equality rather than -// identity to match values. +// This is similar to the default case but uses equality rather than +// identity to match values. // // // NOTE: this will do a shallow test using Object.keys(..) thus .__proto__ @@ -235,7 +142,8 @@ function(base, obj, non_strict){ return false } // attr count... var o = Object.keys(Object.getOwnPropertyDescriptors(obj)) - if(Object.keys(Object.getOwnPropertyDescriptors(base)).length != o.length){ + if(Object.keys(Object.getOwnPropertyDescriptors(base)).length + != o.length){ return false } // names and values... o = o.map(function(k){ @@ -285,13 +193,16 @@ module.create = function(obj){ // name given... var name = '' - if(typeof(obj) == 'string' && arguments.length > 1){ + if(typeof(obj) == 'string' + && arguments.length > 1){ ;[name, obj] = arguments // sanity check... if(!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name.trim())){ throw new Error(`create(..): invalid name: "${name}"`) } } // calable... if(typeof(obj) == 'function'){ + // NOTE: this is ignored by c8 as we will never run this directly + // as it will immediately get eval(..)'ed... /* c8 ignore next 9 */ var func = function(){ return '__call__' in func ? @@ -306,12 +217,15 @@ function(obj){ // NOTE: we just created func(..) so no need to sanitize it, the // only potential vector of atack (AFAIK) here is name and // that is checked above... - func.name = name + Object.defineProperty(func, 'name', {value: name}) + /* XXX NAME... + //func.name = name func.name != name && (func = eval('('+ func .toString() .replace(/function\(/, `function ${name}(`) +')')) + //*/ func.__proto__ = obj __toStringProxy(func) return func } @@ -329,82 +243,43 @@ BOOTSTRAP(function(){ // // XXX EXPERIMENTAL module.Error = - Constructor('Error', Error, { - get name(){ - return this.constructor.name }, + Constructor('Error', Error, { + get name(){ + return this.constructor.name }, - // XXX BUG? is this an error that with this everything seems to work - // while without this instances of this work fine while instances - // of "sub-classes" do not set the .stack correctly??? - // ...is this a JS quirk or am I missing something??? - __new__: function(context, ...args){ - return Reflect.construct(module.Error.__proto__, args, this.constructor) }, - //return Reflect.construct(Error, args, this.constructor) }, - }) + // XXX BUG? is this an error that with this everything seems + // to work while without this instances of this work + // fine while instances of "sub-classes" do not set + // the .stack correctly??? + // ...is this a JS quirk or am I missing something??? + __new__: function(context, ...args){ + return Reflect.construct( + module.Error.__proto__, args, this.constructor) }, + //return Reflect.construct(Error, args, this.constructor) }, + }) }) + //--------------------------------------------------------------------- // Prototype chain content access... -BOOTSTRAP(function(){ - - // Value trigger iteration stop and to carry results... - // - module.STOP = - Constructor('STOP', { - doc: 'stop iteration.', - __init__: function(value){ - this.value = value }, - }) - -}) - - - -// Get a list of source objects for a prop/attr name... +// Get source objects for a prop/attr name... // // sources(obj, name) -// sources(obj, name, callback) -// -> list -// -> [] -// -// Get callables or objects defining .__call__ (special-case) -// sources(obj, '__call__') -// sources(obj, '__call__', callback) -// -> list -// -> [] +// -> iterator // // Get full chain... // sources(obj) -// sources(obj, callback) -// -> list +// -> iterator // -// -// callback(obj, i) -// -> STOP -// -> STOP(value) -// -> .. -// -// -// The callback(..) is called with each matching object. -// -// callback(..) return values: -// - STOP - stop the search and return the match list terminated -// with the object triggering the stop. -// - STOP(value) - stop the search and return the match list terminated -// with the value passed to STOP(..) -// - undefined - return the triggering object as-is -// NOTE: this is the same as returning [obj] -// - array - merge array content into the result insteaad of -// the triggering value. -// NOTE: an ampty array will effectively omit the -// triggering object from the results. -// - other - return a value instead of the triggering object. +// Get callables or objects defining .__call__ (special-case) +// sources(obj, '__call__') +// -> iterator // // -// NOTE: this gos up the prototype chain, not caring about any role ( +// NOTE: this goes up the prototype chain, not caring about any role ( // instance/class or instance/prototype) bounderies and depends // only on the object given as the starting point. // It is possible to start the search from this, thus checking @@ -413,96 +288,64 @@ BOOTSTRAP(function(){ // NOTE: this will not trigger any props... var sources = module.sources = -function(obj, name, callback){ - // get full chain... - if(typeof(name) == 'function'){ - callback = name - name = undefined - } - var i = 0 - var o - var res = [] +function*(obj, name=undefined){ while(obj != null){ - //if(obj.hasOwnProperty(name)){ if(name === undefined - || obj.hasOwnProperty(name) - || (name == '__call__' && typeof(obj) == 'function')){ - // handle callback... - o = callback - && callback(obj, i++) - // manage results... - res.push( - (o === undefined || o === module.STOP) ? - [obj] - : o instanceof module.STOP ? - o.value - : o ) - // stop... - if(o === module.STOP - || o instanceof module.STOP){ - return res.flat() } } - obj = obj.__proto__ } - return res.flat() } + || obj.hasOwnProperty(name) + || (name == '__call__' + && typeof(obj) == 'function')){ + yield obj } + obj = obj.__proto__ } } -// Get a list of values/props set in source objects for a prop/attr name... +// Get object-value/prop pairs set in source objects for a prop/attr name... +// +// entries(obj, name) +// -> iterator // -// Get values... -// values(obj, name) -// values(obj, name, callback) -// -> list -// -> [] -// // Get propery descriptors... -// values(obj, name, true) -// values(obj, name, callback, true) -// -> list -// -> [] -// -// callback(value/prop, obj) -// -> STOP -// -> STOP(value) -// -> .. -// +// entries(obj, name, true) +// -> iterator +// +// +// Item format: +// [ +// source, +// value, +// ] +// // // Special case: name is given as '__call__' // This will return either the value the object if it is callable // or the value of .__call__ attribute... // // -// NOTE: for more docs on the callback(..) see sources(..) -var values = -module.values = -function(obj, name, callback, props){ - props = callback === true ? - callback - : props - var _get = function(obj, name){ - return props ? +// NOTE: for more docs see sources(..) +var entries = +module.entries = +function*(obj, name, props=false){ + for(var obj of sources(obj, name)){ + yield [ + obj, + props ? Object.getOwnPropertyDescriptor(obj, name) // handle callable instance... : !(name in obj) && name == '__call__' && typeof(obj) == 'function' ? obj - // normal attr... - : obj[name] } - // wrap the callback if given... - var c = typeof(callback) == 'function' - && function(obj, i){ - var val = _get(obj, name) - var res = callback(val, obj, i) - return res === module.STOP ? - // wrap the expected stop result if the user did not do it... - module.STOP(val) - : res } - return c ? - // NOTE: we do not need to handle the callback return values as - // this is fully done by c(..) in sources(..) - sources(obj, name, c) - : sources(obj, name) - .map(function(obj){ - return _get(obj, name) }) } + : obj[name], + ] }} + + +// Get values/props set in source objects for a prop/attr name... +// +// NOTE: this is specialization of entries(..), see that for more info. +var values = +module.values = +function*(obj, name, props=false){ + for(var [_, value] of entries(...arguments)){ + yield value }} // Find the next parent attribute in the prototype chain. @@ -588,24 +431,22 @@ function(proto, name){ if(name == ''){ throw new Error('parent(..): need a method with non-empty .name') } // get first matching source... - proto = sources(that, name, - function(obj, i){ - // NOTE: the .hasOwnProperty(..) test is here so as - // to skip the base callable when searching for - // .__call__ that is returned as a special case - // by sourcei(..) and this should have no effect - // or other cases... - // NOTE: this will only skip the root callable... - return (i > 0 || obj.hasOwnProperty(name)) - && obj[name] === proto - && module.STOP }) - .pop() } + var i = 0 + for(var obj of sources(that, name)){ + // NOTE: the .hasOwnProperty(..) test is here so as + // to skip the base callable when searching for + // .__call__ that is returned as a special case + // by source(..) and this should have no effect + // or other cases... + // NOTE: this will only skip the root callable... + if((i++ > 0 || obj.hasOwnProperty(name)) + && obj[name] === proto){ + proto = obj + break }}} // get first source... - var res = sources(proto, name, - function(obj, i){ - return i == 1 - && module.STOP }) - .pop() + var res = sources(proto, name) + res.next() + res = res.next().value return !res ? undefined :(!(name in res) && typeof(res) == 'function') ? @@ -624,11 +465,9 @@ var parentProperty = module.parentProperty = function(proto, name){ // get second source... - var res = sources(proto, name, - function(obj, i){ - return i == 1 - && module.STOP }) - .pop() + var res = sources(proto, name) + res.next() + res = res.next().value return res ? // get next value... Object.getOwnPropertyDescriptor(res, name) @@ -674,7 +513,7 @@ function(proto, name, that, ...args){ var parentOf = module.parentOf = function(parent, child){ - return new Set(sources(child)).has(parent) } + return new Set([...sources(child)]).has(parent) } // Reverse of parentOf(..) var childOf = @@ -711,7 +550,7 @@ function(func){ : '__call__' in this ? this.__call__ : this.__proto__) - return module.normalizeIndent(f.toString(...args)) }, + return doc.normalizeIndent(f.toString(...args)) }, enumerable: false, }) return func } @@ -955,28 +794,78 @@ function(context, constructor, ...args){ // to be rooted in Function. // though the typeof(..) == 'function' will always work. // NOTE: this will fail with non-identifier names... -// XXX is this a bug or a feature??? =) +// NOTE: a bit more obvious syntax could be something like: +// var A = Constructor('A', { +// __proto__: B, +// +// // constructor stuff... +// +// prototype: { +// // instance stuff... +// } +// }) +// vs: +// var A = Constructor('A', B, { +// // constructor stuff... +// +// }, { +// // instance stuff... +// }) +// this though a bit more obvious is in the general case can be +// more verbose, i.e. on the top level we define the constructor +// stuff which in the general case secondary while .prototype is a +// level below that while in use it's the main functionality... so, +// this is a question of ease of use vs. mirroring the JS structure, +// the answer chosen here is to prioritize simplicity and conciseness +// over verbose mirroring... +// XXX needs revision from time to time... +// XXX might be a good idea to implement the above syntax and test +// it out... +// Constructor([, ], ) +// Constructor([, ], ) +// Constructor([, ], , ) +// +// ::= +// { +// ... +// +// prototype: { +// ... +// }, +// } +// The only questions here is weather a set .prototype is a good +// enough indicator and should we use .__proto__? var Constructor = module.Constructor = // shorthand... module.C = function Constructor(name, a, b, c){ - var args = [...arguments].slice(1, 4) - // sanity check... if(!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name.trim())){ throw new Error(`Constructor(..): invalid name: "${name}"`) } // parse args... + var args = [...arguments].slice(1, 4) + // Constructor(name[[, constructor[, mixin]], proto]) var proto = args.pop() || {} - var constructor_proto = typeof(args[0]) == 'function' ? - args.shift() - : undefined + var constructor_proto = + typeof(args[0]) == 'function' ? + args.shift() + : undefined var constructor_mixin = args.pop() - // handle: - // Constructor(name, constructor, ..) + /* XXX EXPERIMENTAL... + // handle: Constructor(name[, constructor], { prototype: { .. }, .. }) + // + if(proto.hasOwnProperty('prototype') + && proto.prototype){ + console.log('>>>>', name) + constructor_mixin = proto + proto = proto.prototype } + //*/ + + // handle: Constructor(name, constructor, ..) // // NOTE: this is a bit too functional in style by an if-tree would // be more bulky and less readable... @@ -1015,6 +904,8 @@ function Constructor(name, a, b, c){ && (proto.__proto__ = constructor_proto.prototype) } // the constructor base... + // NOTE: this is ignored by c8 as we will never run this directly as + // it will immediately get eval(..)'ed... /* c8 ignore next 9 */ var _constructor = function Constructor(){ // create raw instance... @@ -1031,7 +922,9 @@ function Constructor(name, a, b, c){ // Object.defineProperty(_constructor, 'name', { value: name }) // because this does not affect the name displayed by the Chrome // DevTools. FF does not seem to care about either version of code... - _constructor.name = name + Object.defineProperty(_constructor, 'name', {value: name}) + /* XXX NAME... + //_constructor.name = name // just in case the browser/node refuses to change the name, we'll make // them a different offer ;) // NOTE: it is not possible to abstract this eval(..) into something @@ -1044,6 +937,7 @@ function Constructor(name, a, b, c){ _constructor .toString() .replace(/Constructor/g, name) +')')) + //*/ // set .toString(..)... // NOTE: this test is here to enable mixinFlat(..) to overwrite // .toString(..) below... @@ -1065,7 +959,7 @@ function Constructor(name, a, b, c){ .toString() .replace(/[^{]*{/, '{') : '{ .. }' - return `${this.name}(${args})${module.normalizeIndent(code)}` }, + return `${this.name}(${args})${doc.normalizeIndent(code)}` }, enumerable: false, }) // set generic raw instance constructor... @@ -1228,6 +1122,8 @@ function(base, ...objects){ // NOTE: this will also match base... // NOTE: if base matches directly callback(..) will get undefined as parent // NOTE: for more docs on the callback(..) see sources(..) +// +// XXX should this be a generator??? var mixins = module.mixins = function(base, object, callback){ @@ -1275,11 +1171,9 @@ function(base, object){ mixins(base, object, function(){ return module.STOP }) .length > 0 // flat mixin search... - || sources(base, function(p){ - return matchPartial(p, object) ? - module.STOP - : [] }) - .length > 0 )} + || [...sources(base)] + .some(function(p){ + return matchPartial(p, object) }) )} // Mix-out sets of methods/props/attrs out of an object prototype chain... @@ -1435,4 +1329,4 @@ BOOTSTRAP() /********************************************************************** -* vim:set ts=4 sw=4 : */ return module }) +* vim:set ts=4 sw=4 nowrap : */ return module }) diff --git a/lib/stoppable.js b/lib/stoppable.js new file mode 100755 index 0000000..4e5abec --- /dev/null +++ b/lib/stoppable.js @@ -0,0 +1,157 @@ +/********************************************************************** +* +* stoppable.js +* +* Utility library implementing tooling to make stoppable functions... +* +* +* Repo and docs: +* https://github.com/flynx/stoppable.js +* +* +***********************************************/ /* c8 ignore next 2 */ +((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) +(function(require){ var module={} // make module AMD/node compatible... +/*********************************************************************/ + + +//--------------------------------------------------------------------- +// helpers... + +var AsyncFunction = + (async function(){}).constructor + +var Generator = + (function*(){}).constructor + +var AsyncGenerator = + (async function*(){}).constructor + + + +//--------------------------------------------------------------------- + +// Wrap a callable in a STOP handler +// +// stoppable(func) +// -> func +// +// stoppable(gen) +// -> gen +// +// stoppable(asyncgen) +// -> asyncgen +// +// +// The client callable can be one of: +// - function +// - generator +// - async generator +// +// The returned callable will be of the same type as the input callable. +// +// The wrapper handles STOP slightly differently if the client is a +// function or if it is a generator / async generator: +// - function +// STOP returned / thrown +// -> return undefined +// STOP(value) returned / thrown +// -> return value +// - generator / async generator +// STOP yielded / thrown +// -> iteration stops +// STOP(value) yielded / thrown +// -> value yielded and iteration stops +// +// +// NOTE: this repeats the same code at lest twice, not sure yet how to avoid +// this... +module = +function(func){ + return Object.assign( + // NOTE: the below implementations are almost the same, the main + // differences being the respective generator/async mechanics... + func instanceof Generator ? + function*(){ + try{ + for(var res of func.call(this, ...arguments)){ + if(res === module.STOP){ + return } + if(res instanceof module.STOP){ + yield res.value + return } + yield res } + } catch(err){ + if(err === module.STOP){ + return + } else if(err instanceof module.STOP){ + yield err.value + return } + throw err } } + : func instanceof AsyncGenerator ? + async function*(){ + try{ + for await(var res of func.call(this, ...arguments)){ + if(res === module.STOP){ + return } + if(res instanceof module.STOP){ + yield res.value + return } + yield res } + } catch(err){ + if(err === module.STOP){ + return + } else if(err instanceof module.STOP){ + yield err.value + return } + throw err } } + : func instanceof AsyncFunction ? + async function(){ + try{ + var res = await func.call(this, ...arguments) + // NOTE: this is here for uniformity... + if(res === module.STOP){ + return } + if(res instanceof module.STOP){ + return res.value } + return res + } catch(err){ + if(err === module.STOP){ + return + } else if(err instanceof module.STOP){ + return err.value } + throw err } } + : function(){ + try{ + var res = func.call(this, ...arguments) + // NOTE: this is here for uniformity... + if(res === module.STOP){ + return } + if(res instanceof module.STOP){ + return res.value } + return res + } catch(err){ + if(err === module.STOP){ + return + } else if(err instanceof module.STOP){ + return err.value } + throw err } }, + { toString: function(){ + return func.toString() }, }) } + + + +//--------------------------------------------------------------------- + +module.STOP = +function(value){ + return { + __proto__: module.STOP.prototype, + doc: 'stop iteration.', + value, + } } + + + +/********************************************************************** +* vim:set ts=4 sw=4 nowrap : */ return module })