From eb72d16bf61456b5c227f0447096c26c2e8f76ee Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Sun, 27 Apr 2014 12:11:06 +0200 Subject: [PATCH] Major reorganization and partial refactoring - Move out main modules from `lib` folder - Introduce `normalizer` based configurations, and convert primitive and regular handlers into thos normalizers (can be found in lib/normalizers folder). Custom normalizers can be provided at run time via `normaizer` option - Provide `plain` module which does not load any extensions or normalizers. Any extensions that have to be used should be required upfront and normalizers should be provided directly - Remove `method` option, instead `methods` and `methods-plan` modules are provided which generate descriptors for lazy created memoized methods - `profile` is no longer extension. To be used it should be required directly - Improve logic for `async` handling - Take out `max` extensionLRU logic into external `lru-queue` package - Remove `context` option - Remove possibility to access original arguments when resolvers are used - Assure expected length of memoized functions --- .lint | 8 +- .testignore | 1 + benchmark/fibonacci.js | 2 +- ext/async.js | 140 +++++++++++ ext/dispose.js | 27 +++ ext/max-age.js | 64 +++++ ext/max.js | 26 +++ ext/ref-counter.js | 39 ++++ index.js | 35 +++ lib/_base.js | 57 ----- lib/configure-map.js | 144 ++++++++++++ lib/d.js | 20 -- lib/ext/async.js | 111 --------- lib/ext/dispose.js | 33 --- lib/ext/max-age.js | 76 ------ lib/ext/max.js | 62 ----- lib/ext/method.js | 42 ---- lib/ext/ref-counter.js | 40 ---- lib/ext/resolvers.js | 42 ---- lib/index.js | 23 -- lib/methods.js | 15 ++ lib/normalizers/0.js | 5 + lib/normalizers/get-primitive-fixed.js | 12 + lib/normalizers/get-regular-1.js | 30 +++ lib/normalizers/get-regular-fixed.js | 72 ++++++ lib/normalizers/get-regular.js | 89 +++++++ lib/normalizers/primitive.js | 9 + lib/primitive.js | 90 ------- lib/registered-extensions.js | 1 + lib/regular.js | 246 -------------------- lib/resolve-length.js | 15 ++ methods-plain.js | 3 + methods.js | 3 + package.json | 30 +-- plain.js | 37 +++ lib/ext/profile.js => profile.js | 21 +- test/_base.js | 18 -- test/ext/async.js | 14 +- test/ext/dispose.js | 73 ++---- test/ext/max-age.js | 4 +- test/ext/max.js | 38 +-- test/ext/method.js | 34 --- test/ext/ref-counter.js | 54 ++--- test/ext/resolvers.js | 47 ---- test/index.js | 156 +++++-------- test/lib/configure-map.js | 77 ++++++ test/{d.js => lib/methods.js} | 16 +- test/lib/normalizers/0.js | 32 +++ test/lib/normalizers/get-primitive-fixed.js | 43 ++++ test/lib/normalizers/get-regular-1.js | 56 +++++ test/lib/normalizers/get-regular-fixed.js | 91 ++++++++ test/lib/normalizers/get-regular.js | 59 +++++ test/lib/normalizers/primitive.js | 18 ++ test/lib/registered-extensions.js | 6 + test/lib/resolve-length.js | 10 + test/methods-plain.js | 34 +++ test/methods.js | 31 +++ test/plain.js | 29 +++ test/primitive.js | 72 ------ test/{ext => }/profile.js | 0 test/regular.js | 223 ------------------ 61 files changed, 1424 insertions(+), 1481 deletions(-) create mode 100644 .testignore create mode 100644 ext/async.js create mode 100644 ext/dispose.js create mode 100644 ext/max-age.js create mode 100644 ext/max.js create mode 100644 ext/ref-counter.js create mode 100644 index.js delete mode 100644 lib/_base.js create mode 100644 lib/configure-map.js delete mode 100644 lib/d.js delete mode 100644 lib/ext/async.js delete mode 100644 lib/ext/dispose.js delete mode 100644 lib/ext/max-age.js delete mode 100644 lib/ext/max.js delete mode 100644 lib/ext/method.js delete mode 100644 lib/ext/ref-counter.js delete mode 100644 lib/ext/resolvers.js delete mode 100644 lib/index.js create mode 100644 lib/methods.js create mode 100644 lib/normalizers/0.js create mode 100644 lib/normalizers/get-primitive-fixed.js create mode 100644 lib/normalizers/get-regular-1.js create mode 100644 lib/normalizers/get-regular-fixed.js create mode 100644 lib/normalizers/get-regular.js create mode 100644 lib/normalizers/primitive.js delete mode 100644 lib/primitive.js create mode 100644 lib/registered-extensions.js delete mode 100644 lib/regular.js create mode 100644 lib/resolve-length.js create mode 100644 methods-plain.js create mode 100644 methods.js create mode 100644 plain.js rename lib/ext/profile.js => profile.js (80%) delete mode 100644 test/_base.js delete mode 100644 test/ext/method.js delete mode 100644 test/ext/resolvers.js create mode 100644 test/lib/configure-map.js rename test/{d.js => lib/methods.js} (68%) create mode 100644 test/lib/normalizers/0.js create mode 100644 test/lib/normalizers/get-primitive-fixed.js create mode 100644 test/lib/normalizers/get-regular-1.js create mode 100644 test/lib/normalizers/get-regular-fixed.js create mode 100644 test/lib/normalizers/get-regular.js create mode 100644 test/lib/normalizers/primitive.js create mode 100644 test/lib/registered-extensions.js create mode 100644 test/lib/resolve-length.js create mode 100644 test/methods-plain.js create mode 100644 test/methods.js create mode 100644 test/plain.js delete mode 100644 test/primitive.js rename test/{ext => }/profile.js (100%) delete mode 100644 test/regular.js diff --git a/.lint b/.lint index f909e38..1ddad3c 100644 --- a/.lint +++ b/.lint @@ -1,10 +1,9 @@ @root module -es5 indent 2 -maxlen 80 +maxlen 100 tabs ass @@ -14,8 +13,7 @@ nomen ./lib/_base.js bitwise -./lib/ext/max-age.js -bitwise +./ext/max-age.js predef+ setTimeout, clearTimeout ./lib/ext/max.js @@ -25,8 +23,6 @@ predef+ setTimeout, clearTimeout ./test/index.js -predef+ setTimeout - ./test/ext/max-age.js predef+ setTimeout diff --git a/.testignore b/.testignore new file mode 100644 index 0000000..f9c8c38 --- /dev/null +++ b/.testignore @@ -0,0 +1 @@ +/benchmark diff --git a/benchmark/fibonacci.js b/benchmark/fibonacci.js index 337decf..2f592fd 100644 --- a/benchmark/fibonacci.js +++ b/benchmark/fibonacci.js @@ -8,7 +8,7 @@ var forEach = require('es5-ext/object/for-each') , pad = require('es5-ext/string/#/pad') - , memoizee = require('../lib') + , memoizee = require('..') , underscore = require('underscore').memoize , lodash = require('lodash').memoize , lruCache = require('lru-cache') diff --git a/ext/async.js b/ext/async.js new file mode 100644 index 0000000..d8589a7 --- /dev/null +++ b/ext/async.js @@ -0,0 +1,140 @@ +// Support for asynchronous functions + +'use strict'; + +var aFrom = require('es5-ext/array/from') + , mixin = require('es5-ext/object/mixin') + , defineLength = require('es5-ext/function/_define-length') + , nextTick = require('next-tick') + + , slice = Array.prototype.slice + , apply = Function.prototype.apply, create = Object.create + , hasOwnProperty = Object.prototype.hasOwnProperty; + +require('../lib/registered-extensions').async = function (tbi, conf) { + var waiting = create(null), cache = create(null) + , base = conf.memoized, original = conf.original + , currentCallback, currentContext, currentArgs; + + // Initial + conf.memoized = defineLength(function (arg) { + var args = arguments, last = args[args.length - 1]; + if (typeof last === 'function') { + currentCallback = last; + args = slice.call(args, 0, -1); + } + return base.apply(currentContext = this, currentArgs = args); + }, base); + try { mixin(conf.memoized, base); } catch (ignore) {} + + // From cache (sync) + conf.on('get', function (id) { + var cb, context, args; + if (!currentCallback) return; + + // Unresolved + if (waiting[id]) { + if (typeof waiting[id] === 'function') { + waiting[id] = [waiting[id], currentCallback]; + } else { + waiting[id].push(currentCallback); + } + currentCallback = null; + return; + } + + // Resolved, assure next tick invocation + cb = currentCallback; + context = currentContext; + args = currentArgs; + currentCallback = currentContext = currentArgs = null; + nextTick(function () { + var data; + if (hasOwnProperty.call(cache, id)) { + data = cache[id]; + conf.emit('getasync', id); + apply.call(cb, data.context, data.args); + } else { + // Purged in a meantime, we shouldn't rely on cached value, recall + currentCallback = cb; + currentContext = context; + currentArgs = args; + base.apply(context, args); + } + }); + }); + + // Not from cache + conf.original = function () { + var args, cb, origCb, result; + if (!currentCallback) return apply.call(original, this, arguments); + args = aFrom(arguments); + cb = function self(err) { + var cb, args, id = self.id; + if (id == null) { + // Shouldn't happen, means async callback was called sync way + nextTick(apply.bind(self, this, arguments)); + return; + } + delete self.id; + cb = waiting[id]; + delete waiting[id]; + args = aFrom(arguments); + if (conf.has(id)) { + if (err) { + conf.delete(id); + } else { + cache[id] = { context: this, args: args }; + conf.emit('setasync', id, (typeof cb === 'function') ? 1 : cb.length); + } + } + if (typeof cb === 'function') { + result = apply.call(cb, this, args); + } else { + cb.forEach(function (cb) { + result = apply.call(cb, this, args); + }, this); + } + return result; + }; + origCb = currentCallback; + currentCallback = currentContext = currentArgs = null; + args.push(cb); + result = apply.call(original, this, args); + cb.cb = origCb; + currentCallback = cb; + return result; + }; + + // After not from cache call + conf.on('set', function (id) { + if (!currentCallback) { + conf.delete(id); + return; + } + waiting[id] = currentCallback.cb; + delete currentCallback.cb; + currentCallback.id = id; + currentCallback = null; + }); + + // On delete + conf.on('delete', function (id) { + var result; + // If false, we don't have value yet, so we assume that intention is not + // to memoize this call. After value is obtained we don't cache it but + // gracefully pass to callback + if (hasOwnProperty.call(waiting, id)) return; + if (!cache[id]) return; + result = cache[id]; + delete cache[id]; + conf.emit('deleteasync', id, result); + }); + + // On clear + conf.on('clear', function () { + var oldCache = cache; + cache = create(null); + conf.emit('clearasync', oldCache); + }); +}; diff --git a/ext/dispose.js b/ext/dispose.js new file mode 100644 index 0000000..4002b7f --- /dev/null +++ b/ext/dispose.js @@ -0,0 +1,27 @@ +// Call dispose callback on each cache purge + +'use strict'; + +var callable = require('es5-ext/object/valid-callable') + , forEach = require('es5-ext/object/for-each') + , extensions = require('../lib/registered-extensions') + + , slice = Array.prototype.slice, apply = Function.prototype.apply; + +extensions.dispose = function (dispose, conf, options) { + var del; + callable(dispose); + if (options.async && extensions.async) { + conf.on('deleteasync', del = function (id, result) { + apply.call(dispose, null, slice.call(result.args, 1)); + }); + conf.on('clearasync', function (cache) { + forEach(cache, function (result, id) { del(id, result); }); + }); + return; + } + conf.on('delete', del = function (id, result) { dispose(result); }); + conf.on('clear', function (cache) { + forEach(cache, function (result, id) { del(id, result); }); + }); +}; diff --git a/ext/max-age.js b/ext/max-age.js new file mode 100644 index 0000000..c49e9b2 --- /dev/null +++ b/ext/max-age.js @@ -0,0 +1,64 @@ +// Timeout cached values + +'use strict'; + +var forEach = require('es5-ext/object/for-each') + , timeout = require('timers-ext/valid-timeout') + , extensions = require('../lib/registered-extensions') + + , max = Math.max, min = Math.min, create = Object.create; + +extensions.maxAge = function (maxAge, conf, options) { + var timeouts, postfix, preFetchAge, preFetchTimeouts; + + maxAge = timeout(maxAge); + if (!maxAge) return; + + timeouts = create(null); + postfix = (options.async && extensions.async) ? 'async' : ''; + conf.on('set' + postfix, function (id) { + timeouts[id] = setTimeout(function () { conf.clear(id); }, maxAge); + if (!preFetchTimeouts) return; + if (preFetchTimeouts[id]) clearTimeout(preFetchTimeouts[id]); + preFetchTimeouts[id] = setTimeout(function () { + delete preFetchTimeouts[id]; + }, preFetchAge); + }); + conf.on('delete' + postfix, function (id) { + clearTimeout(timeouts[id]); + delete timeouts[id]; + if (!preFetchTimeouts) return; + clearTimeout(preFetchTimeouts[id]); + delete preFetchTimeouts[id]; + }); + + if (options.preFetch) { + if ((options.preFetch === true) || isNaN(options.preFetch)) { + preFetchAge = 0.333; + } else { + preFetchAge = max(min(Number(options.preFetch), 1), 0); + } + if (preFetchAge) { + preFetchTimeouts = {}; + preFetchAge = (1 - preFetchAge) * maxAge; + conf.on('get' + postfix, function (id, args, context) { + if (!preFetchTimeouts[id]) { + preFetchTimeouts[id] = setTimeout(function () { + delete preFetchTimeouts[id]; + conf.delete(id); + conf.memoized.apply(context, args); + }, 0); + } + }); + } + } + + conf.on('clear' + postfix, function () { + forEach(timeouts, function (id) { clearTimeout(id); }); + timeouts = {}; + if (preFetchTimeouts) { + forEach(preFetchTimeouts, function (id) { clearTimeout(id); }); + preFetchTimeouts = {}; + } + }); +}; diff --git a/ext/max.js b/ext/max.js new file mode 100644 index 0000000..e382c58 --- /dev/null +++ b/ext/max.js @@ -0,0 +1,26 @@ +// Limit cache size, LRU (least recently used) algorithm. + +'use strict'; + +var toPosInteger = require('es5-ext/number/to-pos-integer') + , lruQueue = require('lru-queue') + , extensions = require('../lib/registered-extensions'); + +extensions.max = function (max, conf, options) { + var postfix, queue, hit; + + max = toPosInteger(max); + if (!max) return; + + queue = lruQueue(max); + postfix = (options.async && extensions.async) ? 'async' : ''; + + conf.on('set' + postfix, hit = function (id) { + id = queue.hit(id); + if (id === undefined) return; + conf.delete(id); + }); + conf.on('get' + postfix, hit); + conf.on('delete' + postfix, queue.delete); + conf.on('clear' + postfix, queue.clear); +}; diff --git a/ext/ref-counter.js b/ext/ref-counter.js new file mode 100644 index 0000000..b990c94 --- /dev/null +++ b/ext/ref-counter.js @@ -0,0 +1,39 @@ +// Reference counter, useful for garbage collector like functionality + +'use strict'; + +var d = require('d') + , extensions = require('../lib/registered-extensions') + + , create = Object.create, defineProperties = Object.defineProperties; + +extensions.refCounter = function (ignore, conf, options) { + var cache, postfix; + + cache = create(null); + postfix = (options.async && extensions.async) ? 'async' : ''; + + conf.on('set' + postfix, function (id, length) { cache[id] = length || 1; }); + conf.on('get' + postfix, function (id) { ++cache[id]; }); + conf.on('delete' + postfix, function (id) { delete cache[id]; }); + conf.on('clear' + postfix, function () { cache = {}; }); + + defineProperties(conf.memoized, { + deleteRef: d(function () { + var id = conf.get(arguments); + if (id === null) return null; + if (!cache[id]) return null; + if (!--cache[id]) { + conf.delete(id); + return true; + } + return false; + }), + getRefCount: d(function () { + var id = conf.get(arguments); + if (id === null) return 0; + if (!cache[id]) return 0; + return cache[id]; + }) + }); +}; diff --git a/index.js b/index.js new file mode 100644 index 0000000..9237ba7 --- /dev/null +++ b/index.js @@ -0,0 +1,35 @@ +'use strict'; + +var normalizeOpts = require('es5-ext/object/normalize-options') + , resolveLength = require('./lib/resolve-length') + , plain = require('./plain'); + +module.exports = function (fn/*, options*/) { + var options = normalizeOpts(arguments[1]), length; + + if (!options.normalizer && !options.serialize) { + length = options.length = resolveLength(options.length, fn.length, options.async); + if (length === 0) { + options.normalizer = require('./lib/normalizers/0'); + } else if (options.primitive) { + if (length === false) { + options.normalizer = require('./lib/normalizers/primitive'); + } else if (length > 1) { + options.normalizer = require('./lib/normalizers/get-primitive-fixed')(length); + } + } else { + if (length === false) options.normalizer = require('./lib/normalizers/get-regular')(); + else if (length === 1) options.normalizer = require('./lib/normalizers/get-regular-1')(); + else options.normalizer = require('./lib/normalizers/get-regular-fixed')(length); + } + } + + // Assure extensions + if (options.async) require('./ext/async'); + if (options.dispose) require('./ext/dispose'); + if (options.maxAge) require('./ext/max-age'); + if (options.max) require('./ext/max'); + if (options.refCounter) require('./ext/ref-counter'); + + return plain(fn, options); +}; diff --git a/lib/_base.js b/lib/_base.js deleted file mode 100644 index 516128f..0000000 --- a/lib/_base.js +++ /dev/null @@ -1,57 +0,0 @@ -// To be used internally, memoize factory - -'use strict'; - -var callable = require('es5-ext/object/valid-callable') - , forEach = require('es5-ext/object/for-each') - , ee = require('event-emitter') - - , ext; - -module.exports = exports = function (core) { - return function self(fn/*, options */) { - var options, length, get, clear, conf; - - callable(fn); - options = Object(arguments[1]); - - // Do not memoize already memoized function - if (fn.memoized && !options.force) return fn; - - if (ext.method && (options.method != null)) { - return ext.method(options.method, options, fn, self); - } - - conf = ee({ memoize: self, fn: fn }); - - // Normalize length - if (isNaN(options.length)) { - length = fn.length; - // Special case - if (options.async && ext.async) --length; - } else { - length = (options.length === false) ? false : (options.length >>> 0); - } - - core(conf, length, options); - - forEach(ext, function (fn, name) { - if (fn.force) fn(conf, options); - else if (options[name]) fn(options[name], conf, options); - }); - - fn = conf.fn; - get = conf.get; - clear = conf.clear; - - conf.memoized.clear = function () { clear(get(arguments)); }; - conf.memoized.clearAll = function () { - conf.emit('purgeall'); - conf.clearAll(); - }; - conf.memoized.memoized = true; - conf.emit('ready'); - return conf.memoized; - }; -}; -ext = exports.ext = {}; diff --git a/lib/configure-map.js b/lib/configure-map.js new file mode 100644 index 0000000..c7b2949 --- /dev/null +++ b/lib/configure-map.js @@ -0,0 +1,144 @@ +'use strict'; + +var toArray = require('es5-ext/array/to-array') + , customError = require('es5-ext/error/custom') + , defineLength = require('es5-ext/function/_define-length') + , callable = require('es5-ext/object/valid-callable') + , d = require('d') + , ee = require('event-emitter').methods + + , slice = Array.prototype.slice + , apply = Function.prototype.apply, call = Function.prototype.call + , create = Object.create, hasOwnProperty = Object.prototype.hasOwnProperty + , defineProperties = Object.defineProperties + , on = ee.on, emit = ee.emit, resolveArgs; + +resolveArgs = function (args) { + return this.map(function (r, i) { + return r ? r(args[i]) : args[i]; + }).concat(slice.call(args, this.length)); +}; + +module.exports = function (original, length, options) { + var cache = create(null), conf, memLength, get, set, del, clear + , getListeners, setListeners, deleteListeners, memoized, resolve, resolvers; + if (length !== false) memLength = length; + else if (isNaN(original.length)) memLength = 1; + else memLength = original.length; + + if (options.normalizer) { + get = callable(options.normalizer.get); + if (options.normalizer.set !== undefined) { + set = callable(options.normalizer.set); + del = callable(options.normalizer.delete); + clear = callable(options.normalizer.clear); + } else { + set = get; + } + } else if (options.serialize) { + set = get = (function (serialize) { + return function (args) { return serialize.apply(null, args); }; + }(callable(options.serialize))); + } + + if (options.resolvers != null) { + resolvers = toArray(options.resolvers); + resolvers.forEach(function (r) { + if (r != null) callable(r); + }); + resolve = resolveArgs.bind(resolvers); + } + + if (get) { + memoized = defineLength(function (arg) { + var id, result, args = arguments; + if (resolve) args = resolve(args); + id = get(args); + if (id !== null) { + if (hasOwnProperty.call(cache, id)) { + if (getListeners) conf.emit('get', id, args, this); + return cache[id]; + } + } + if (args.length === 1) result = call.call(original, this, arg); + else result = apply.call(original, this, args); + if (id === null) { + id = get(args); + if (id !== null) throw customError("Circular invocation", 'CIRCULAR_INVOCATION'); + id = set(args); + } else if (hasOwnProperty.call(cache, id)) { + throw customError("Circular invocation", 'CIRCULAR_INVOCATION'); + } + cache[id] = result; + if (setListeners) conf.emit('set', id); + return result; + }, memLength); + } else { + memoized = function (arg) { + var result, args = arguments; + if (resolve) { + args = resolve(arguments); + arg = args[0]; + } + if (hasOwnProperty.call(cache, arg)) { + if (getListeners) conf.emit('get', arg, args, this); + return cache[arg]; + } + if (args.length === 1) result = call.call(original, this, arg); + else result = apply.call(original, this, args); + if (hasOwnProperty.call(cache, arg)) { + throw customError("Circular invocation", 'CIRCULAR_INVOCATION'); + } + cache[arg] = result; + if (setListeners) conf.emit('set', arg); + return result; + }; + } + conf = { + original: original, + memoized: memoized, + get: function (args) { + if (resolve) args = resolve(args); + if (get) return get(args); + return args[0]; + }, + has: function (id) { return hasOwnProperty.call(cache, id); }, + delete: function (id) { + var result; + if (!hasOwnProperty.call(cache, id)) return; + if (del) del(id); + result = cache[id]; + delete cache[id]; + if (deleteListeners) conf.emit('delete', id, result); + }, + clear: function () { + var oldCache = cache; + if (clear) clear(); + cache = create(null); + conf.emit('clear', oldCache); + }, + on: function (type, listener) { + if (type === 'get') getListeners = true; + else if (type === 'set') setListeners = true; + else if (type === 'delete') deleteListeners = true; + return on.call(this, type, listener); + }, + emit: emit, + updateEnv: function () { original = conf.original; } + }; + defineProperties(memoized, { + __memoized__: d(true), + delete: d(get ? defineLength(function (arg) { + var id, args = arguments; + if (resolve) args = resolve(args); + id = get(args); + if (id === null) return; + conf.delete(id); + }, memLength) : function (arg) { + if (resolve) arg = resolve(arguments)[0]; + return conf.delete(arg); + }), + clear: d(conf.clear) + }); + return conf; +}; diff --git a/lib/d.js b/lib/d.js deleted file mode 100644 index 5ea9737..0000000 --- a/lib/d.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -var forEach = require('es5-ext/object/for-each') - , callable = require('es5-ext/object/valid-callable') - , lazy = require('d/lazy') - - , a = [], b = []; - -module.exports = function (fn) { - var index = a.indexOf(callable(fn)); - if (index !== -1) return b[index]; - index = a.push(fn); - return (b[index - 1] = function (props) { - forEach(props, function (desc, name) { - var value = callable(desc.value); - desc.value = function (options) { return fn(value.bind(this), options); }; - }); - return lazy(props); - }); -}; diff --git a/lib/ext/async.js b/lib/ext/async.js deleted file mode 100644 index 80de76f..0000000 --- a/lib/ext/async.js +++ /dev/null @@ -1,111 +0,0 @@ -// Support for asynchronous functions - -'use strict'; - -var aFrom = require('es5-ext/array/from') - , last = require('es5-ext/array/#/last') - , isArguments = require('es5-ext/function/is-arguments') - , forEach = require('es5-ext/object/for-each') - , isCallable = require('es5-ext/object/is-callable') - , nextTick = require('next-tick') - - , isArray = Array.isArray, slice = Array.prototype.slice - , apply = Function.prototype.apply; - -require('../_base').ext.async = function (ignore, conf) { - var cache, purge; - - cache = conf.async = {}; - - (function (org) { - var value, cb, initContext, initArgs, fn, resolver; - - conf.on('init', function (id) { - value.id = id; - cache[id] = cb ? [cb] : []; - }); - - conf.on('hit', function (id, syncArgs, syncCtx) { - if (!cb) { - return; - } - - if (isArray(cache[id])) { - cache[id].push(cb); - } else { - nextTick(function (cb, id, ctx, args) { - if (cache[id]) { - conf.emit('hitasync', id, syncArgs, syncCtx); - apply.call(cb, this.context, this); - } else { - // Purged in a meantime, we shouldn't rely on cached value, recall - fn.apply(ctx, args); - } - }.bind(cache[id], cb, id, initContext, initArgs)); - initContext = initArgs = null; - } - }); - conf.fn = function () { - var args, asyncArgs; - args = arguments; - asyncArgs = aFrom(args); - asyncArgs.push(value = function self(err) { - var i, cb, waiting, res; - if (self.id == null) { - // Shouldn't happen, means async callback was called sync way - nextTick(apply.bind(self, this, arguments)); - return; - } - waiting = cache[self.id]; - if (conf.cache.hasOwnProperty(self.id)) { - if (err) { - delete cache[self.id]; - conf.clear(self.id); - } else { - arguments.context = this; - cache[self.id] = arguments; - conf.emit('initasync', self.id, waiting.length); - } - } else { - delete cache[self.id]; - } - for (i = 0; (cb = waiting[i]); ++i) { - res = apply.call(cb, this, arguments); - } - return res; - }); - return apply.call(org, this, asyncArgs); - }; - - fn = conf.memoized; - resolver = function (args) { - cb = last.call(args); - if (isCallable(cb)) return slice.call(args, 0, -1); - cb = null; - return args; - }; - conf.memoized = function () { - return fn.apply(initContext = this, initArgs = resolver(arguments)); - }; - forEach(fn, function (value, name) { - conf.memoized[name] = function () { - return fn[name].apply(this, resolver(arguments)); - }; - }); - - }(conf.fn)); - - conf.on('purge', purge = function (id) { - // If false, we don't have value yet, so we assume that intention is not - // to memoize this call. After value is obtained we don't cache it but - // gracefully pass to callback - if (isArguments(cache[id])) { - conf.emit('purgeasync', id); - delete cache[id]; - } - }); - - conf.on('purgeall', function () { - forEach(conf.async, function (value, id) { purge(id); }); - }); -}; diff --git a/lib/ext/dispose.js b/lib/ext/dispose.js deleted file mode 100644 index 669341c..0000000 --- a/lib/ext/dispose.js +++ /dev/null @@ -1,33 +0,0 @@ -// Call dispose callback on each cache purge - -'use strict'; - -var callable = require('es5-ext/object/valid-callable') - , forEach = require('es5-ext/object/for-each') - , ext = require('../_base').ext - - , slice = Array.prototype.slice; - -ext.dispose = function (dispose, conf, options) { - var clear, async, context; - - callable(dispose); - - async = (options.async && ext.async); - context = options.context; - conf.on('purge' + (async ? 'async' : ''), clear = async ? function (id) { - var value = conf.async[id]; - delete conf.cache[id]; - dispose.apply(context, slice.call(value, 1)); - } : function (id) { - var value = conf.cache[id]; - delete conf.cache[id]; - dispose.call(context, value); - }); - - if (!async) { - conf.on('purgeall', function () { - forEach(conf.cache, function (value, id) { clear(id); }); - }); - } -}; diff --git a/lib/ext/max-age.js b/lib/ext/max-age.js deleted file mode 100644 index 8d0fe05..0000000 --- a/lib/ext/max-age.js +++ /dev/null @@ -1,76 +0,0 @@ -// Timeout cached values - -'use strict'; - -var isNumber = require('es5-ext/number/is-number') - , forEach = require('es5-ext/object/for-each') - , nextTick = require('next-tick') - , ext = require('../_base').ext - - , max = Math.max, min = Math.min; - -ext.maxAge = function (maxAge, conf, options) { - var cache, async, preFetchAge, preFetchCache; - - maxAge = maxAge >>> 0; - if (!maxAge) { - return; - } - - cache = {}; - async = options.async && ext.async; - conf.on('init' + (async ? 'async' : ''), function (id) { - cache[id] = setTimeout(function () { conf.clear(id); }, maxAge); - if (preFetchCache) { - preFetchCache[id] = setTimeout(function () { delete preFetchCache[id]; }, - preFetchAge); - } - }); - conf.on('purge' + (async ? 'async' : ''), function (id) { - clearTimeout(cache[id]); - if (preFetchCache && preFetchCache[id]) { - clearTimeout(preFetchCache[id]); - delete preFetchCache[id]; - } - delete cache[id]; - }); - - if (options.preFetch) { - if (isNumber(options.preFetch)) { - preFetchAge = max(min(Number(options.preFetch), 1), 0); - } else { - preFetchAge = 0.333; - } - if (preFetchAge) { - preFetchCache = {}; - preFetchAge = (1 - preFetchAge) * maxAge; - conf.on('hit' + (async ? 'async' : ''), function (id, args, ctx) { - if (!preFetchCache[id]) { - preFetchCache[id] = true; - nextTick(function () { - if (preFetchCache[id] === true) { - delete preFetchCache[id]; - conf.clear(id); - conf.memoized.apply(ctx, args); - } - }); - } - }); - } - } - - if (!async) { - conf.on('purgeall', function () { - forEach(cache, function (id) { - clearTimeout(id); - }); - cache = {}; - if (preFetchCache) { - forEach(preFetchCache, function (id) { - clearTimeout(id); - }); - preFetchCache = {}; - } - }); - } -}; diff --git a/lib/ext/max.js b/lib/ext/max.js deleted file mode 100644 index 1d3f2f0..0000000 --- a/lib/ext/max.js +++ /dev/null @@ -1,62 +0,0 @@ -// Limit cache size, LRU (least recently used) algorithm. - -'use strict'; - -var ext = require('../_base').ext; - -ext.max = function (max, conf, options) { - var index, base, size, queue, map, async; - - max = max >>> 0; - if (!max) { - return; - } - - index = -1; - base = size = 0; - queue = {}; - map = {}; - async = options.async && ext.async; - - conf.on('init' + (async ? 'async' : ''), function (id) { - queue[++index] = id; - map[id] = index; - ++size; - if (size > max) { - conf.clear(queue[base]); - } - }); - - conf.on('hit' + (async ? 'async' : ''), function (id) { - var oldIndex = map[id]; - queue[++index] = id; - map[id] = index; - delete queue[oldIndex]; - if (base === oldIndex) { - while (!queue.hasOwnProperty(++base)) continue; //jslint: skip - } - }); - - conf.on('purge' + (async ? 'async' : ''), function (id) { - var oldIndex = map[id]; - delete queue[oldIndex]; - --size; - if (base === oldIndex) { - if (!size) { - index = -1; - base = 0; - } else { - while (!queue.hasOwnProperty(++base)) continue; //jslint: skip - } - } - }); - - if (!async) { - conf.on('purgeall', function () { - index = -1; - base = size = 0; - queue = {}; - map = {}; - }); - } -}; diff --git a/lib/ext/method.js b/lib/ext/method.js deleted file mode 100644 index 060f19e..0000000 --- a/lib/ext/method.js +++ /dev/null @@ -1,42 +0,0 @@ -// Memoized methods factory - -'use strict'; - -var copy = require('es5-ext/object/copy') - - , defineProperty = Object.defineProperty; - -require('../_base').ext.method = function (method, options, fn, configure) { - var name, descriptor, props, prop, selfName; - name = selfName = String(method); - descriptor = { - enumerable: (options.enumerable == null) ? false : - Boolean(options.enumerable), - configurable: (options.configurable == null) ? true : - Boolean(options.configurable), - writable: (options.writable == null) ? true : - Boolean(options.writable) - }; - props = {}; - prop = props[selfName] = copy(descriptor); - delete prop.writable; - if (options.protoDeep != null) { - if (typeof options.protoDeep === 'boolean') { - if (options.protoDeep) name = '_' + name + '_'; - } else { - name = String(options.protoDeep); - } - } - options = copy(options); - delete options.method; - delete options.protoDeep; - - prop.get = function () { - if ((name !== selfName) && this.hasOwnProperty(name)) return this[name]; - options.context = this; - descriptor.value = configure(fn.bind(this), options); - defineProperty(this, name, descriptor); - return this[name]; - }; - return props; -}; diff --git a/lib/ext/ref-counter.js b/lib/ext/ref-counter.js deleted file mode 100644 index ef6e343..0000000 --- a/lib/ext/ref-counter.js +++ /dev/null @@ -1,40 +0,0 @@ -// Reference counter, useful for garbage collector like functionality - -'use strict'; - -var ext = require('../_base').ext; - -ext.refCounter = function (ignore, conf, options) { - var cache, async; - - cache = {}; - async = options.async && ext.async; - - conf.on('init' + (async ? 'async' : ''), async ? function (id, length) { - cache[id] = length; - } : function (id) { cache[id] = 1; }); - conf.on('hit' + (async ? 'async' : ''), function (id) { ++cache[id]; }); - conf.on('purge' + (async ? 'async' : ''), function (id) { - delete cache[id]; - }); - if (!async) { - conf.on('purgeall', function () { cache = {}; }); - } - - conf.memoized.clearRef = function () { - var id = conf.get(arguments); - if (cache.hasOwnProperty(id)) { - if (!--cache[id]) { - conf.clear(id); - return true; - } - return false; - } - return null; - }; - conf.memoized.getRefCount = function () { - var id = conf.get(arguments); - if (!cache.hasOwnProperty(id)) return 0; - return cache[id]; - }; -}; diff --git a/lib/ext/resolvers.js b/lib/ext/resolvers.js deleted file mode 100644 index 03bce47..0000000 --- a/lib/ext/resolvers.js +++ /dev/null @@ -1,42 +0,0 @@ -// Normalize arguments before passing them to underlying function - -'use strict'; - -var toArray = require('es5-ext/array/to-array') - , forEach = require('es5-ext/object/for-each') - , callable = require('es5-ext/object/valid-callable') - - , slice = Array.prototype.slice - - , resolve; - -resolve = function (args) { - return this.map(function (r, i) { - return r ? r(args[i]) : args[i]; - }).concat(slice.call(args, this.length)); -}; - -require('../_base').ext.resolvers = function (resolvers, conf) { - var resolver; - - resolver = toArray(resolvers); - resolver.forEach(function (r) { - ((r == null) || callable(r)); - }); - resolver = resolve.bind(resolver); - - (function (fn) { - conf.memoized = function () { - var value; - conf.memoized.args = arguments; - value = fn.apply(this, resolver(arguments)); - delete conf.memoized.args; - return value; - }; - forEach(fn, function (value, name) { - conf.memoized[name] = function () { - return fn[name].apply(this, resolver(arguments)); - }; - }); - }(conf.memoized)); -}; diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index 157459e..0000000 --- a/lib/index.js +++ /dev/null @@ -1,23 +0,0 @@ -// Provides memoize with all options - -'use strict'; - -var regular = require('./regular') - , primitive = require('./primitive') - - , call = Function.prototype.call; - -// Order is significant! -require('./ext/dispose'); -require('./ext/resolvers'); -require('./ext/async'); -require('./ext/ref-counter'); -require('./ext/method'); -require('./ext/max-age'); -require('./ext/max'); - -module.exports = function (fn/* options */) { - var options = Object(arguments[1]); - return call.call((options.primitive || options.serialize) ? - primitive : regular, this, fn, options); -}; diff --git a/lib/methods.js b/lib/methods.js new file mode 100644 index 0000000..aebb728 --- /dev/null +++ b/lib/methods.js @@ -0,0 +1,15 @@ +'use strict'; + +var forEach = require('es5-ext/object/for-each') + , callable = require('es5-ext/object/valid-callable') + , lazy = require('d/lazy'); + +module.exports = function (memoize) { + return function (props) { + forEach(props, function (desc, name) { + var fn = callable(desc.value); + desc.value = function (options) { return memoize(fn.bind(this), options); }; + }); + return lazy(props); + }; +}; diff --git a/lib/normalizers/0.js b/lib/normalizers/0.js new file mode 100644 index 0000000..1476e87 --- /dev/null +++ b/lib/normalizers/0.js @@ -0,0 +1,5 @@ +'use strict'; + +var k = require('es5-ext/function/constant'); + +module.exports = { get: k('') }; diff --git a/lib/normalizers/get-primitive-fixed.js b/lib/normalizers/get-primitive-fixed.js new file mode 100644 index 0000000..4ff5c7a --- /dev/null +++ b/lib/normalizers/get-primitive-fixed.js @@ -0,0 +1,12 @@ +'use strict'; + +module.exports = function (length) { + if (!length) { + return { get: function () { return ''; } }; + } + return { get: function (args) { + var id = String(args[0]), i = 0, l = length; + while (--l) { id += '\u0001' + args[++i]; } + return id; + } }; +}; diff --git a/lib/normalizers/get-regular-1.js b/lib/normalizers/get-regular-1.js new file mode 100644 index 0000000..a7cbebd --- /dev/null +++ b/lib/normalizers/get-regular-1.js @@ -0,0 +1,30 @@ +'use strict'; + +var indexOf = require('es5-ext/array/#/e-index-of'); + +module.exports = function () { + var lastId = 0, argsMap = [], cache = []; + return { + get: function (args) { + var index = indexOf.call(argsMap, args[0]); + return (index === -1) ? null : cache[index]; + }, + set: function (args) { + argsMap.push(args[0]); + cache.push(++lastId); + return lastId; + }, + delete: function (id) { + var index = indexOf.call(cache, id); + if (index !== -1) { + argsMap.splice(index, 1); + cache.splice(index, 1); + } + }, + clear: function () { + argsMap = []; + cache = []; + lastId = 0; + } + }; +}; diff --git a/lib/normalizers/get-regular-fixed.js b/lib/normalizers/get-regular-fixed.js new file mode 100644 index 0000000..1556ef6 --- /dev/null +++ b/lib/normalizers/get-regular-fixed.js @@ -0,0 +1,72 @@ +'use strict'; + +var indexOf = require('es5-ext/array/#/e-index-of') + , create = Object.create; + +module.exports = function (length) { + var lastId = 0, map = [[], []], cache = create(null); + return { + get: function (args) { + var index = 0, set = map, i; + while (index < (length - 1)) { + i = indexOf.call(set[0], args[index]); + if (i === -1) return null; + set = set[1][i]; + ++index; + } + i = indexOf.call(set[0], args[index]); + if (i === -1) return null; + return set[1][i] || null; + }, + set: function (args) { + var index = 0, set = map, i; + while (index < (length - 1)) { + i = indexOf.call(set[0], args[index]); + if (i === -1) { + i = set[0].push(args[index]) - 1; + set[1].push([[], []]); + } + set = set[1][i]; + ++index; + } + i = indexOf.call(set[0], args[index]); + if (i === -1) { + i = set[0].push(args[index]) - 1; + } + set[1][i] = ++lastId; + cache[lastId] = args; + return lastId; + }, + delete: function (id) { + var index = 0, set = map, i, path = [], args = cache[id]; + while (index < (length - 1)) { + i = indexOf.call(set[0], args[index]); + if (i === -1) { + return; + } + path.push(set, i); + set = set[1][i]; + ++index; + } + i = indexOf.call(set[0], args[index]); + if (i === -1) { + return; + } + id = set[1][i]; + set[0].splice(i, 1); + set[1].splice(i, 1); + while (!set[0].length && path.length) { + i = path.pop(); + set = path.pop(); + set[0].splice(i, 1); + set[1].splice(i, 1); + } + delete cache[id]; + }, + clear: function () { + map = [[], []]; + cache = create(null); + lastId = 0; + } + }; +}; diff --git a/lib/normalizers/get-regular.js b/lib/normalizers/get-regular.js new file mode 100644 index 0000000..7cb8fa4 --- /dev/null +++ b/lib/normalizers/get-regular.js @@ -0,0 +1,89 @@ +'use strict'; + +var indexOf = require('es5-ext/array/#/e-index-of') + , create = Object.create; + +module.exports = function () { + var lastId = 0, map = [], cache = create(null); + return { + get: function (args) { + var index = 0, set = map, i, length = args.length; + if (length === 0) return set[length] || null; + if ((set = set[length])) { + while (index < (length - 1)) { + i = indexOf.call(set[0], args[index]); + if (i === -1) return null; + set = set[1][i]; + ++index; + } + i = indexOf.call(set[0], args[index]); + if (i === -1) return null; + return set[1][i] || null; + } + return null; + }, + set: function (args) { + var index = 0, set = map, i, length = args.length; + if (length === 0) { + set[length] = ++lastId; + } else { + if (!set[length]) { + set[length] = [[], []]; + } + set = set[length]; + while (index < (length - 1)) { + i = indexOf.call(set[0], args[index]); + if (i === -1) { + i = set[0].push(args[index]) - 1; + set[1].push([[], []]); + } + set = set[1][i]; + ++index; + } + i = indexOf.call(set[0], args[index]); + if (i === -1) { + i = set[0].push(args[index]) - 1; + } + set[1][i] = ++lastId; + } + cache[lastId] = args; + return lastId; + }, + delete: function (id) { + var index = 0, set = map, i, args = cache[id], length = args.length + , path = []; + if (length === 0) { + delete set[length]; + } else if ((set = set[length])) { + while (index < (length - 1)) { + i = indexOf.call(set[0], args[index]); + if (i === -1) { + return; + } + path.push(set, i); + set = set[1][i]; + ++index; + } + i = indexOf.call(set[0], args[index]); + if (i === -1) { + return; + } + id = set[1][i]; + set[0].splice(i, 1); + set[1].splice(i, 1); + while (!set[0].length && path.length) { + i = path.pop(); + set = path.pop(); + set[0].splice(i, 1); + set[1].splice(i, 1); + } + } + delete cache[id]; + }, + clear: function () { + map = []; + cache = create(null); + lastId = 0; + } + }; +}; diff --git a/lib/normalizers/primitive.js b/lib/normalizers/primitive.js new file mode 100644 index 0000000..53098f2 --- /dev/null +++ b/lib/normalizers/primitive.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = { get: function (args) { + var id, i, length = args.length; + if (!length) return '\u0002'; + id = String(args[i = 0]); + while (--length) id += '\u0001' + args[++i]; + return id; +} }; diff --git a/lib/primitive.js b/lib/primitive.js deleted file mode 100644 index 20befb2..0000000 --- a/lib/primitive.js +++ /dev/null @@ -1,90 +0,0 @@ -// Memoize working in primitive mode - -'use strict'; - -var customError = require('es5-ext/error/custom') - , callable = require('es5-ext/object/valid-callable') - , hasListeners = require('event-emitter/has-listeners') - - , serialize0 = function () { return ''; } - , serialize1 = function (args) { return args[0]; } - - , apply = Function.prototype.apply, call = Function.prototype.call - , serializeN; - -serializeN = function (args) { - var id = '', i, length = args.length; - if (length) { - id += args[i = 0]; - while (--length) id += '\u0001' + args[++i]; - } else { - id = '\u0002'; - } - return id; -}; - -module.exports = require('./_base')(function (conf, length, options) { - var get, cache = conf.cache = {}, fn - , hitListeners, initListeners, purgeListeners, serialize; - - if (options.serialize) { - serialize = callable(options.serialize); - get = conf.get = function (args) { return serialize.apply(this, args); }; - } else if (length === 1) { - get = conf.get = serialize1; - } else if (length === false) { - get = conf.get = serializeN; - } else if (length) { - get = conf.get = function (args) { - var id = String(args[0]), i = 0, l = length; - while (--l) { id += '\u0001' + args[++i]; } - return id; - }; - } else { - get = conf.get = serialize0; - } - - conf.memoized = (length === 1) ? function (id) { - var value; - if (cache.hasOwnProperty(id)) { - if (hitListeners) conf.emit('hit', id, arguments, this); - return cache[id]; - } - if (arguments.length === 1) value = call.call(fn, this, id); - else value = apply.call(fn, this, arguments); - if (cache.hasOwnProperty(id)) { - throw customError("Circular invocation", 'CIRCULAR_INVOCATION'); - } - cache[id] = value; - if (initListeners) conf.emit('init', id); - return value; - } : function () { - var id = get(arguments), value; - if (cache.hasOwnProperty(id)) { - if (hitListeners) conf.emit('hit', id, arguments, this); - return cache[id]; - } - value = apply.call(conf.fn, this, arguments); - if (cache.hasOwnProperty(id)) { - throw customError("Circular invocation", 'CIRCULAR_INVOCATION'); - } - cache[id] = value; - if (initListeners) conf.emit('init', id); - return value; - }; - - conf.clear = function (id) { - if (cache.hasOwnProperty(id)) { - if (purgeListeners) conf.emit('purge', id); - delete cache[id]; - } - }; - conf.clearAll = function () { cache = conf.cache = {}; }; - - conf.once('ready', function () { - fn = conf.fn; - hitListeners = hasListeners(conf, 'hit'); - initListeners = hasListeners(conf, 'init'); - purgeListeners = hasListeners(conf, 'purge'); - }); -}); diff --git a/lib/registered-extensions.js b/lib/registered-extensions.js new file mode 100644 index 0000000..ad9a93a --- /dev/null +++ b/lib/registered-extensions.js @@ -0,0 +1 @@ +'use strict'; diff --git a/lib/regular.js b/lib/regular.js deleted file mode 100644 index e92300a..0000000 --- a/lib/regular.js +++ /dev/null @@ -1,246 +0,0 @@ -// Memoize working in object mode (supports any type of arguments) - -'use strict'; - -var customError = require('es5-ext/error/custom') - , indexOf = require('es5-ext/array/#/e-index-of') - , hasListeners = require('event-emitter/has-listeners') - - , apply = Function.prototype.apply; - -// Results are saved internally within array matrix: -// [0] -> Result of calling function with no arguments -// [1] -> Matrix that keeps results when function is called with one argument -// [1][0] -> Array of arguments with which -// function have been called -// [1][1] -> Array of results that matches [1][0] array -// [2] -> Matrix that keeps results when function is called with two arguments -// [2][0] -> Array of first (of two) arguments with which -// function have been called -// [2][1] -> Matrixes that keeps results for two arguments function calls -// Each matrix matches first argument found in [2][0] -// [2][1][x][0] -> Array of second arguments with which -// function have been called. -// [2][1][x][1] -> Array of results that matches [2][1][x][0] -// arguments array -// ...and so on -module.exports = require('./_base')(function (conf, length) { - var map, map1, map2, get, set, clear, count, fn - , hitListeners, initListeners, purgeListeners - , cache = conf.cache = {}, argsCache; - - if (length === 0) { - map = null; - get = conf.get = function () { return map; }; - set = function () { return ((map = 1)); }; - clear = function () { map = null; }; - conf.clearAll = function () { - map = null; - cache = conf.cache = {}; - }; - } else { - count = 0; - if (length === 1) { - map1 = []; - map2 = []; - get = conf.get = function (args) { - var index = indexOf.call(map1, args[0]); - return (index === -1) ? null : map2[index]; - }; - set = function (args) { - map1.push(args[0]); - map2.push(++count); - return count; - }; - clear = function (id) { - var index = indexOf.call(map2, id); - if (index !== -1) { - map1.splice(index, 1); - map2.splice(index, 1); - } - }; - conf.clearAll = function () { - map1 = []; - map2 = []; - cache = conf.cache = {}; - }; - } else if (length === false) { - map = []; - argsCache = {}; - get = conf.get = function (args) { - var index = 0, set = map, i, length = args.length; - if (length === 0) return set[length] || null; - if ((set = set[length])) { - while (index < (length - 1)) { - i = indexOf.call(set[0], args[index]); - if (i === -1) return null; - set = set[1][i]; - ++index; - } - i = indexOf.call(set[0], args[index]); - if (i === -1) return null; - return set[1][i] || null; - } - return null; - }; - set = function (args) { - var index = 0, set = map, i, length = args.length; - if (length === 0) { - set[length] = ++count; - } else { - if (!set[length]) { - set[length] = [[], []]; - } - set = set[length]; - while (index < (length - 1)) { - i = indexOf.call(set[0], args[index]); - if (i === -1) { - i = set[0].push(args[index]) - 1; - set[1].push([[], []]); - } - set = set[1][i]; - ++index; - } - i = indexOf.call(set[0], args[index]); - if (i === -1) { - i = set[0].push(args[index]) - 1; - } - set[1][i] = ++count; - } - argsCache[count] = args; - return count; - }; - clear = function (id) { - var index = 0, set = map, i, args = argsCache[id], length = args.length - , path = []; - if (length === 0) { - delete set[length]; - } else if ((set = set[length])) { - while (index < (length - 1)) { - i = indexOf.call(set[0], args[index]); - if (i === -1) { - return; - } - path.push(set, i); - set = set[1][i]; - ++index; - } - i = indexOf.call(set[0], args[index]); - if (i === -1) { - return; - } - id = set[1][i]; - set[0].splice(i, 1); - set[1].splice(i, 1); - while (!set[0].length && path.length) { - i = path.pop(); - set = path.pop(); - set[0].splice(i, 1); - set[1].splice(i, 1); - } - } - delete argsCache[id]; - }; - conf.clearAll = function () { - map = []; - cache = conf.cache = {}; - argsCache = {}; - }; - } else { - map = [[], []]; - argsCache = {}; - get = conf.get = function (args) { - var index = 0, set = map, i; - while (index < (length - 1)) { - i = indexOf.call(set[0], args[index]); - if (i === -1) return null; - set = set[1][i]; - ++index; - } - i = indexOf.call(set[0], args[index]); - if (i === -1) return null; - return set[1][i] || null; - }; - set = function (args) { - var index = 0, set = map, i; - while (index < (length - 1)) { - i = indexOf.call(set[0], args[index]); - if (i === -1) { - i = set[0].push(args[index]) - 1; - set[1].push([[], []]); - } - set = set[1][i]; - ++index; - } - i = indexOf.call(set[0], args[index]); - if (i === -1) { - i = set[0].push(args[index]) - 1; - } - set[1][i] = ++count; - argsCache[count] = args; - return count; - }; - clear = function (id) { - var index = 0, set = map, i, path = [], args = argsCache[id]; - while (index < (length - 1)) { - i = indexOf.call(set[0], args[index]); - if (i === -1) { - return; - } - path.push(set, i); - set = set[1][i]; - ++index; - } - i = indexOf.call(set[0], args[index]); - if (i === -1) { - return; - } - id = set[1][i]; - set[0].splice(i, 1); - set[1].splice(i, 1); - while (!set[0].length && path.length) { - i = path.pop(); - set = path.pop(); - set[0].splice(i, 1); - set[1].splice(i, 1); - } - delete argsCache[id]; - }; - conf.clearAll = function () { - map = [[], []]; - cache = conf.cache = {}; - argsCache = {}; - }; - } - } - conf.memoized = function () { - var id = get(arguments), value; - if (id != null) { - if (hitListeners) conf.emit('hit', id, arguments, this); - return cache[id]; - } - value = apply.call(fn, this, arguments); - id = get(arguments); - if (id != null) { - throw customError("Circular invocation", 'CIRCULAR_INVOCATION'); - } - id = set(arguments); - cache[id] = value; - if (initListeners) conf.emit('init', id); - return value; - }; - conf.clear = function (id) { - if (cache.hasOwnProperty(id)) { - if (purgeListeners) conf.emit('purge', id); - clear(id); - delete cache[id]; - } - }; - - conf.once('ready', function () { - fn = conf.fn; - hitListeners = hasListeners(conf, 'hit'); - initListeners = hasListeners(conf, 'init'); - purgeListeners = hasListeners(conf, 'purge'); - }); -}); diff --git a/lib/resolve-length.js b/lib/resolve-length.js new file mode 100644 index 0000000..9fb236b --- /dev/null +++ b/lib/resolve-length.js @@ -0,0 +1,15 @@ +'use strict'; + +var toPosInt = require('es5-ext/number/to-pos-integer'); + +module.exports = function (optsLength, fnLength, isAsync) { + var length; + if (isNaN(optsLength)) { + length = fnLength; + if (!(length >= 0)) return 1; + if (isAsync && length) return length - 1; + return length; + } + if (optsLength === false) return false; + return toPosInt(optsLength); +}; diff --git a/methods-plain.js b/methods-plain.js new file mode 100644 index 0000000..15ae105 --- /dev/null +++ b/methods-plain.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./lib/methods')(require('./plain')); diff --git a/methods.js b/methods.js new file mode 100644 index 0000000..7f9797f --- /dev/null +++ b/methods.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./lib/methods')(require('./')); diff --git a/package.json b/package.json index cfbeb20..60e120c 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,8 @@ { "name": "memoizee", "version": "0.3.0", - "description": "Complete memoize/cache solution. Works with any type and length of function arguments", - "main": "lib", - "scripts": { - "test": "node node_modules/tad/bin/tad lib" - }, - "repository": { - "type": "git", - "url": "git://github.com/medikoo/memoize.git" - }, + "description": "Memoize/cache", + "author": "Mariusz Nowak (http://www.medikoo.com/)", "keywords": [ "memoize", "memoizer", @@ -26,22 +19,23 @@ "garbage", "collector" ], - "bugs": { - "email": "medikoo+memoize@medikoo.com", - "url": "https://github.com/medikoo/memoize/issues" - }, - "engines": { - "node": ">=0.4" + "repository": { + "type": "git", + "url": "git://github.com/medikoo/memoize.git" }, "dependencies": { "d": "~0.1.1", "es5-ext": "~0.10.2", - "event-emitter": "0.3.x", - "next-tick": "~0.2.2" + "event-emitter": "~0.3.1", + "lru-queue": "0.1.x", + "next-tick": "~0.2.2", + "timers-ext": "0.1.x" }, "devDependencies": { "tad": "~0.1.21" }, - "author": "Mariusz Nowak (http://www.medikoo.com/)", + "scripts": { + "test": "node node_modules/tad/bin/tad" + }, "license": "MIT" } diff --git a/plain.js b/plain.js new file mode 100644 index 0000000..e75ce83 --- /dev/null +++ b/plain.js @@ -0,0 +1,37 @@ +// To be used internally, memoize factory + +'use strict'; + +var callable = require('es5-ext/object/valid-callable') + , forEach = require('es5-ext/object/for-each') + , extensions = require('./lib/registered-extensions') + , configure = require('./lib/configure-map') + , resolveLength = require('./lib/resolve-length') + + , hasOwnProperty = Object.prototype.hasOwnProperty; + +module.exports = function self(fn/*, options */) { + var options, length, conf; + + callable(fn); + options = Object(arguments[1]); + + // Do not memoize already memoized function + if (hasOwnProperty.call(fn, '__memoized__') && !options.force) return fn; + + // Resolve length; + length = resolveLength(options.length, fn.length, options.async && extensions.async); + + // Configure cache map + conf = configure(fn, length, options); + + // Bind eventual extensions + forEach(extensions, function (fn, name) { + if (options[name]) fn(options[name], conf, options); + }); + + if (self.__profiler__) self.__profiler__(conf); + + conf.updateEnv(); + return conf.memoized; +}; diff --git a/lib/ext/profile.js b/profile.js similarity index 80% rename from lib/ext/profile.js rename to profile.js index ab5e742..50f6770 100644 --- a/lib/ext/profile.js +++ b/profile.js @@ -5,18 +5,16 @@ var partial = require('es5-ext/function/#/partial') , forEach = require('es5-ext/object/for-each') , pad = require('es5-ext/string/#/pad') + , memoize = require('./plain') , max = Math.max + , stats = exports.statistics = {}; - , stats = exports.statistics = {}, ext; - -require('../_base').ext.profile = ext = function (conf) { +Object.defineProperty(memoize, '__profiler__', function (conf) { var id, stack, data; stack = (new Error()).stack; - if (!stack.split('\n').slice(3).some(function (line) { + if (!stack || !stack.split('\n').slice(3).some(function (line) { if ((line.indexOf('/memoizee/') === -1) && - (line.indexOf('/es5-ext/') === -1) && - (line.indexOf('/next/lib/fs/_memoize-watcher') === -1) && (line.indexOf(' (native)') === -1)) { id = line.replace(/\n/g, "\\n").trim(); return true; @@ -25,15 +23,12 @@ require('../_base').ext.profile = ext = function (conf) { id = 'unknown'; } - if (!stats[id]) { - stats[id] = { initial: 0, cached: 0 }; - } + if (!stats[id]) stats[id] = { initial: 0, cached: 0 }; data = stats[id]; - conf.on('init', function () { ++data.initial; }); - conf.on('hit', function () { ++data.cached; }); -}; -ext.force = true; + conf.on('set', function () { ++data.initial; }); + conf.on('get', function () { ++data.cached; }); +}); exports.log = function () { var initial, cached, ordered, ipad, cpad, ppad, toPrc, log; diff --git a/test/_base.js b/test/_base.js deleted file mode 100644 index d253625..0000000 --- a/test/_base.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -module.exports = function (t, a) { - var mfn, m = t(function (conf) { - conf.get = function () { return 'test'; }; - conf.memoized = function (x, y, z) { return conf.fn(z, y, x); }; - conf.clear = function () {}; - conf.clearAll = function () {}; - }); - - a(typeof m, 'function', "Returns"); - mfn = m(function (x, y, z) { return [x, y, z]; }); - a(typeof mfn, 'function', "Generates"); - a.deep(mfn(3, 7, 11), [11, 7, 3], "Works"); - - a(mfn.memoized, true, "Marked as memoized"); - a(m(mfn), mfn, "Do not memoize memoized"); -}; diff --git a/test/ext/async.js b/test/ext/async.js index 732f36d..dcc37f3 100644 --- a/test/ext/async.js +++ b/test/ext/async.js @@ -1,10 +1,8 @@ 'use strict'; -var memoize = require('../../lib') +var memoize = require('../..') , nextTick = require('next-tick'); -require('../../lib/ext/dispose'); - module.exports = function () { return { "Regular": { @@ -58,7 +56,7 @@ module.exports = function () { a(i, 2, "Init Called #2"); a(invoked, 7, "Cb Called #2"); - mfn.clear(3, 7); + mfn.delete(3, 7); a(mfn(3, 7, function (err, res) { ++invoked; @@ -70,8 +68,8 @@ module.exports = function () { }), u, "Again B: Initial"); nextTick(function () { - a(i, 3, "Init After clear"); - a(invoked, 9, "Cb After clear"); + a(i, 3, "Init After delete"); + a(invoked, 9, "Cb After delete"); d(); }); }); @@ -164,7 +162,7 @@ module.exports = function () { nextTick(function () { a(i, 2, "Again Called #2"); - mfn.clear(3, 7); + mfn.delete(3, 7); a(mfn(3, 7, function (err, res) { a.deep([err, res], [null, 10], "Again: Result"); @@ -174,7 +172,7 @@ module.exports = function () { }), u, "Again B: Initial"); nextTick(function () { - a(i, 3, "Call After clear"); + a(i, 3, "Call After delete"); d(); }); }); diff --git a/test/ext/dispose.js b/test/ext/dispose.js index 92241fb..b7dec4f 100644 --- a/test/ext/dispose.js +++ b/test/ext/dispose.js @@ -1,6 +1,6 @@ 'use strict'; -var memoize = require('../../lib') +var memoize = require('../..') , nextTick = require('next-tick'); module.exports = function () { @@ -15,15 +15,15 @@ module.exports = function () { mfn(5, 8); mfn(12, 4); a.deep(value, [], "Pre"); - mfn.clear(5, 8); + mfn.delete(5, 8); a.deep(value, [13], "#1"); value = []; - mfn.clear(12, 4); + mfn.delete(12, 4); a.deep(value, [16], "#2"); value = []; mfn(77, 11); - mfn.clearAll(); + mfn.clear(); a.deep(value, [10, 88], "Clear all"); x = {}; @@ -31,35 +31,12 @@ module.exports = function () { mfn = memoize(function () { return x; }, { dispose: function (val) { invoked = val; } }); - mfn.clear(); - a(invoked, false, "No args: Post invalid clear"); + mfn.delete(); + a(invoked, false, "No args: Post invalid delete"); mfn(); a(invoked, false, "No args: Post cache"); - mfn.clear(); - a(invoked, x, "No args: Pre clear"); - }, - "Method": function (a) { - var fn, value = []; - fn = function (x, y) { return x + y; }; - Object.defineProperties(value, memoize(fn, { - method: 'mfn', - dispose: function (val) { this.push(val); } - })); - - value.mfn(3, 7); - value.mfn(5, 8); - value.mfn(12, 4); - a.deep(value, [], "Pre"); - value.mfn.clear(5, 8); - a.deep(value, [13], "#1"); - value.length = 0; - value.mfn.clear(12, 4); - a.deep(value, [16], "#2"); - - value.length = 0; - value.mfn(77, 11); - value.mfn.clearAll(); - a.deep(value, [10, 88], "Clear all"); + mfn.delete(); + a(invoked, x, "No args: Pre delete"); }, "Ref counter": function (a) { var mfn, fn, value = []; @@ -72,17 +49,17 @@ module.exports = function () { mfn(12, 4); a.deep(value, [], "Pre"); mfn(5, 8); - mfn.clearRef(5, 8); + mfn.deleteRef(5, 8); a.deep(value, [], "Pre"); - mfn.clearRef(5, 8); + mfn.deleteRef(5, 8); a.deep(value, [13], "#1"); value = []; - mfn.clearRef(12, 4); + mfn.deleteRef(12, 4); a.deep(value, [16], "#2"); value = []; mfn(77, 11); - mfn.clearAll(); + mfn.clear(); a.deep(value, [10, 88], "Clear all"); }, "Async": function (a, d) { @@ -99,15 +76,15 @@ module.exports = function () { mfn(5, 8, function () { mfn(12, 4, function () { a.deep(value, [], "Pre"); - mfn.clear(5, 8); + mfn.delete(5, 8); a.deep(value, [13], "#1"); value = []; - mfn.clear(12, 4); + mfn.delete(12, 4); a.deep(value, [16], "#2"); value = []; mfn(77, 11, function () { - mfn.clearAll(); + mfn.clear(); a.deep(value, [10, 88], "Clear all"); d(); }); @@ -126,15 +103,15 @@ module.exports = function () { mfn(5, 8); mfn(12, 4); a.deep(value, [], "Pre"); - mfn.clear(5, 8); + mfn.delete(5, 8); a.deep(value, [13], "#1"); value = []; - mfn.clear(12, 4); + mfn.delete(12, 4); a.deep(value, [16], "#2"); value = []; mfn(77, 11); - mfn.clearAll(); + mfn.clear(); a.deep(value, [10, 88], "Clear all"); }, "Ref counter": function (a) { @@ -148,17 +125,17 @@ module.exports = function () { mfn(12, 4); a.deep(value, [], "Pre"); mfn(5, 8); - mfn.clearRef(5, 8); + mfn.deleteRef(5, 8); a.deep(value, [], "Pre"); - mfn.clearRef(5, 8); + mfn.deleteRef(5, 8); a.deep(value, [13], "#1"); value = []; - mfn.clearRef(12, 4); + mfn.deleteRef(12, 4); a.deep(value, [16], "#2"); value = []; mfn(77, 11); - mfn.clearAll(); + mfn.clear(); a.deep(value, [10, 88], "Clear all"); }, "Async": function (a, d) { @@ -175,15 +152,15 @@ module.exports = function () { mfn(5, 8, function () { mfn(12, 4, function () { a.deep(value, [], "Pre"); - mfn.clear(5, 8); + mfn.delete(5, 8); a.deep(value, [13], "#1"); value = []; - mfn.clear(12, 4); + mfn.delete(12, 4); a.deep(value, [16], "#2"); value = []; mfn(77, 11, function () { - mfn.clearAll(); + mfn.clear(); a.deep(value, [10, 88], "Clear all"); d(); }); diff --git a/test/ext/max-age.js b/test/ext/max-age.js index baee171..7e283ac 100644 --- a/test/ext/max-age.js +++ b/test/ext/max-age.js @@ -1,8 +1,10 @@ 'use strict'; -var memoize = require('../../lib') +var memoize = require('../..') , nextTick = require('next-tick'); +require('../../ext/async'); + module.exports = function () { return { "Regular": { diff --git a/test/ext/max.js b/test/ext/max.js index 8a87fc1..622af00 100644 --- a/test/ext/max.js +++ b/test/ext/max.js @@ -1,6 +1,6 @@ 'use strict'; -var memoize = require('../../lib') +var memoize = require('../..') , nextTick = require('next-tick'); module.exports = function () { @@ -31,31 +31,31 @@ module.exports = function () { a(mfn(5, 8), 13, "Result B #3"); a(i, 3, "Called B #3"); - a(mfn(77, 11), 88, "Result D #1"); // Clear 12, 4 + a(mfn(77, 11), 88, "Result D #1"); // Delete 12, 4 a(i, 4, "Called D #1"); a(mfn(5, 8), 13, "Result B #4"); a(i, 4, "Called B #4"); - a(mfn(12, 4), 16, "Result C #2"); // Clear 3, 7 + a(mfn(12, 4), 16, "Result C #2"); // Delete 3, 7 a(i, 5, "Called C #2"); - a(mfn(3, 7), 10, "Result #5"); // Clear 77, 11 + a(mfn(3, 7), 10, "Result #5"); // Delete 77, 11 a(i, 6, "Called #5"); - a(mfn(77, 11), 88, "Result D #2"); // Clear 5, 8 + a(mfn(77, 11), 88, "Result D #2"); // Delete 5, 8 a(i, 7, "Called D #2"); a(mfn(12, 4), 16, "Result C #3"); a(i, 7, "Called C #3"); - a(mfn(5, 8), 13, "Result B #5"); // Clear 3, 7 + a(mfn(5, 8), 13, "Result B #5"); // Delete 3, 7 a(i, 8, "Called B #5"); a(mfn(77, 11), 88, "Result D #3"); a(i, 8, "Called D #3"); - mfn.clear(77, 11); + mfn.delete(77, 11); a(mfn(77, 11), 88, "Result D #4"); a(i, 9, "Called D #4"); - mfn.clearAll(); + mfn.clear(); a(mfn(5, 8), 13, "Result B #6"); a(i, 10, "Called B #6"); a(mfn(77, 11), 88, "Result D #5"); @@ -141,13 +141,13 @@ module.exports = function () { "Result D #3"); a(i, 8, "Called D #3"); - mfn.clear(77, 11); + mfn.delete(77, 11); a(mfn(77, 11, function (err, res) { a.deep([err, res], [null, 88], "Result D #4"); a(i, 9, "Called D #4"); - mfn.clearAll(); + mfn.clear(); a(mfn(5, 8, function (err, res) { a.deep([err, res], [null, 13], "Result B #6"); @@ -207,31 +207,31 @@ module.exports = function () { a(mfn(5, 8), 13, "Result B #3"); a(i, 3, "Called B #3"); - a(mfn(77, 11), 88, "Result D #1"); // Clear 12, 4 + a(mfn(77, 11), 88, "Result D #1"); // Delete 12, 4 a(i, 4, "Called D #1"); a(mfn(5, 8), 13, "Result B #4"); a(i, 4, "Called B #4"); - a(mfn(12, 4), 16, "Result C #2"); // Clear 3, 7 + a(mfn(12, 4), 16, "Result C #2"); // Delete 3, 7 a(i, 5, "Called C #2"); - a(mfn(3, 7), 10, "Result #5"); // Clear 77, 11 + a(mfn(3, 7), 10, "Result #5"); // Delete 77, 11 a(i, 6, "Called #5"); - a(mfn(77, 11), 88, "Result D #2"); // Clear 5, 8 + a(mfn(77, 11), 88, "Result D #2"); // Delete 5, 8 a(i, 7, "Called D #2"); a(mfn(12, 4), 16, "Result C #3"); a(i, 7, "Called C #3"); - a(mfn(5, 8), 13, "Result B #5"); // Clear 3, 7 + a(mfn(5, 8), 13, "Result B #5"); // Delete 3, 7 a(i, 8, "Called B #5"); a(mfn(77, 11), 88, "Result D #3"); a(i, 8, "Called D #3"); - mfn.clear(77, 11); + mfn.delete(77, 11); a(mfn(77, 11), 88, "Result D #4"); a(i, 9, "Called D #4"); - mfn.clearAll(); + mfn.clear(); a(mfn(5, 8), 13, "Result B #6"); a(i, 10, "Called B #6"); a(mfn(77, 11), 88, "Result D #5"); @@ -317,13 +317,13 @@ module.exports = function () { "Result D #3"); a(i, 8, "Called D #3"); - mfn.clear(77, 11); + mfn.delete(77, 11); a(mfn(77, 11, function (err, res) { a.deep([err, res], [null, 88], "Result D #4"); a(i, 9, "Called D #4"); - mfn.clearAll(); + mfn.clear(); a(mfn(5, 8, function (err, res) { a.deep([err, res], [null, 13], "Result B #6"); diff --git a/test/ext/method.js b/test/ext/method.js deleted file mode 100644 index f8d2df6..0000000 --- a/test/ext/method.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -var memoize = require('../../lib'); - -module.exports = function () { - return { - "No descriptor": function (a) { - var x = {}, i = 0, fn = function () { - ++i; - return this; - }; - - Object.defineProperties(x, memoize(fn, { method: 'foo' })); - a(x.foo(), x, "Context"); - a(x.foo(), x, "Method"); - a(i, 1, "Cached"); - }, - "Descriptor": function (a) { - var x = {}, i = 0, fn = function () { - ++i; - return this; - }; - - Object.defineProperties(x, memoize(fn, - { method: 'foo', writable: false })); - a(x.foo(), x, "Context"); - a.deep(Object.getOwnPropertyDescriptor(x, 'foo'), - { enumerable: false, configurable: true, writable: false, - value: x.foo }); - a(x.foo(), x, "Method"); - a(i, 1, "Cached"); - } - }; -}; diff --git a/test/ext/ref-counter.js b/test/ext/ref-counter.js index 6bb2d02..c263b4c 100644 --- a/test/ext/ref-counter.js +++ b/test/ext/ref-counter.js @@ -1,6 +1,6 @@ 'use strict'; -var memoize = require('../../lib') +var memoize = require('../..') , nextTick = require('next-tick'); module.exports = function () { @@ -8,18 +8,18 @@ module.exports = function () { "Regular": function (a) { var i = 0, fn = function (x, y, z) { ++i; return x + y + z; }, mfn; mfn = memoize(fn, { refCounter: true }); - a(mfn.clearRef(3, 5, 7), null, "Clear before"); + a(mfn.deleteRef(3, 5, 7), null, "Delete before"); a(mfn(3, 5, 7), 15, "Initial"); a(mfn(3, 5, 7), 15, "Cache"); - a(mfn.clearRef(3, 5, 7), false, "Clear #1"); + a(mfn.deleteRef(3, 5, 7), false, "Delete #1"); mfn(3, 5, 7); - a(mfn.clearRef(3, 5, 7), false, "Clear #2"); + a(mfn.deleteRef(3, 5, 7), false, "Delete #2"); mfn(3, 5, 7); - a(mfn.clearRef(3, 5, 7), false, "Clear #3"); + a(mfn.deleteRef(3, 5, 7), false, "Delete #3"); mfn(3, 5, 7); - a(i, 1, "Not cleared"); - a(mfn.clearRef(3, 5, 7), false, "Clear #4"); - a(mfn.clearRef(3, 5, 7), true, "Clear final"); + a(i, 1, "Not deleteed"); + a(mfn.deleteRef(3, 5, 7), false, "Delete #4"); + a(mfn.deleteRef(3, 5, 7), true, "Delete final"); mfn(3, 5, 7); a(i, 2, "Restarted"); mfn(3, 5, 7); @@ -37,7 +37,7 @@ module.exports = function () { mfn = memoize(fn, { async: true, refCounter: true }); - a(mfn.clearRef(3, 7), null, "Clear ref before"); + a(mfn.deleteRef(3, 7), null, "Delete ref before"); a(mfn(3, 7, function (err, res) { a.deep([err, res], [null, 10], "Result #1"); @@ -68,10 +68,10 @@ module.exports = function () { nextTick(function () { a(i, 2, "Again Called #2"); - a(mfn.clearRef(3, 7), false, "Clear ref #1"); - a(mfn.clearRef(3, 7), false, "Clear ref #2"); - a(mfn.clearRef(3, 7), false, "Clear ref #3"); - a(mfn.clearRef(3, 7), true, "Clear ref Final"); + a(mfn.deleteRef(3, 7), false, "Delete ref #1"); + a(mfn.deleteRef(3, 7), false, "Delete ref #2"); + a(mfn.deleteRef(3, 7), false, "Delete ref #3"); + a(mfn.deleteRef(3, 7), true, "Delete ref Final"); a(mfn(3, 7, function (err, res) { a.deep([err, res], [null, 10], "Again: Result"); @@ -81,7 +81,7 @@ module.exports = function () { }), u, "Again B: Initial"); nextTick(function () { - a(i, 3, "Call After clear"); + a(i, 3, "Call After delete"); d(); }); }); @@ -90,18 +90,18 @@ module.exports = function () { "Primitive": function (a) { var i = 0, fn = function (x, y, z) { ++i; return x + y + z; }, mfn; mfn = memoize(fn, { primitive: true, refCounter: true }); - a(mfn.clearRef(3, 5, 7), null, "Clear before"); + a(mfn.deleteRef(3, 5, 7), null, "Delete before"); a(mfn(3, 5, 7), 15, "Initial"); a(mfn(3, 5, 7), 15, "Cache"); - a(mfn.clearRef(3, 5, 7), false, "Clear #1"); + a(mfn.deleteRef(3, 5, 7), false, "Delete #1"); mfn(3, 5, 7); - a(mfn.clearRef(3, 5, 7), false, "Clear #2"); + a(mfn.deleteRef(3, 5, 7), false, "Delete #2"); mfn(3, 5, 7); - a(mfn.clearRef(3, 5, 7), false, "Clear #3"); + a(mfn.deleteRef(3, 5, 7), false, "Delete #3"); mfn(3, 5, 7); - a(i, 1, "Not cleared"); - a(mfn.clearRef(3, 5, 7), false, "Clear #4"); - a(mfn.clearRef(3, 5, 7), true, "Clear final"); + a(i, 1, "Not deleteed"); + a(mfn.deleteRef(3, 5, 7), false, "Delete #4"); + a(mfn.deleteRef(3, 5, 7), true, "Delete final"); mfn(3, 5, 7); a(i, 2, "Restarted"); mfn(3, 5, 7); @@ -119,7 +119,7 @@ module.exports = function () { mfn = memoize(fn, { async: true, primitive: true, refCounter: true }); - a(mfn.clearRef(3, 7), null, "Clear ref before"); + a(mfn.deleteRef(3, 7), null, "Delete ref before"); a(mfn(3, 7, function (err, res) { a.deep([err, res], [null, 10], "Result #1"); @@ -150,10 +150,10 @@ module.exports = function () { nextTick(function () { a(i, 2, "Again Called #2"); - a(mfn.clearRef(3, 7), false, "Clear ref #1"); - a(mfn.clearRef(3, 7), false, "Clear ref #2"); - a(mfn.clearRef(3, 7), false, "Clear ref #3"); - a(mfn.clearRef(3, 7), true, "Clear ref Final"); + a(mfn.deleteRef(3, 7), false, "Delete ref #1"); + a(mfn.deleteRef(3, 7), false, "Delete ref #2"); + a(mfn.deleteRef(3, 7), false, "Delete ref #3"); + a(mfn.deleteRef(3, 7), true, "Delete ref Final"); a(mfn(3, 7, function (err, res) { a.deep([err, res], [null, 10], "Again: Result"); @@ -163,7 +163,7 @@ module.exports = function () { }), u, "Again B: Initial"); nextTick(function () { - a(i, 3, "Call After clear"); + a(i, 3, "Call After delete"); d(); }); }); diff --git a/test/ext/resolvers.js b/test/ext/resolvers.js deleted file mode 100644 index cde42bf..0000000 --- a/test/ext/resolvers.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -var aFrom = require('es5-ext/array/from') - , memoize = require('../../lib'); - -module.exports = function (a) { - return { - "Original arguments": function (a) { - var fn, mfn, x = {}; - fn = function (x, y) { x = y; return aFrom(mfn.args); }; - mfn = memoize(fn, { resolvers: [] }); - - a.deep(mfn(23, 'raz', x), [23, 'raz', x]); - }, - "Resolvers": function () { - var i = 0, fn, r; - fn = memoize(function () { ++i; return arguments; }, - { length: 3, resolvers: [Boolean, String] }); - return { - "No args": function () { - i = 0; - a.deep(aFrom(r = fn()), [false, 'undefined'], "First"); - a(fn(), r, "Second"); - a(fn(), r, "Third"); - a(i, 1, "Called once"); - }, - "Some Args": function () { - var x = {}; - i = 0; - a.deep(aFrom(r = fn(0, 34, x, 45)), [false, '34', x, 45], - "First"); - a(fn(0, 34, x, 22), r, "Second"); - a(fn(0, 34, x, false), r, "Third"); - a(i, 1, "Called once"); - return { - "Other": function () { - a.deep(aFrom(r = fn(1, 34, x, 34)), - [true, '34', x, 34], "Second"); - a(fn(1, 34, x, 89), r, "Third"); - a(i, 2, "Called once"); - } - }; - } - }; - } - }; -}; diff --git a/test/index.js b/test/index.js index c991040..f8794ce 100644 --- a/test/index.js +++ b/test/index.js @@ -91,8 +91,7 @@ module.exports = function (t, a) { }; }, "Serialize": function () { - var i = 0, fn = function () { ++i; return join.call(arguments, '|'); } - , mfn; + var i = 0, fn = function () { ++i; return join.call(arguments, '|'); }, mfn; mfn = t(fn, { serialize: Boolean }); a(mfn(false, 'raz'), 'false|raz', "#1"); a(mfn(0, 'dwa'), 'false|raz', "#2"); @@ -132,13 +131,6 @@ module.exports = function (t, a) { } }; }, - "Original arguments": function (a) { - var fn, mfn, x = {}; - fn = function (x, y) { x = y; return aFrom(mfn.args); }; - mfn = t(fn, { resolvers: [] }); - - a.deep(mfn(23, 'raz', x), [23, 'raz', x]); - }, "Resolvers": function () { var i = 0, fn, r; fn = t(function () { ++i; return arguments; }, @@ -184,11 +176,11 @@ module.exports = function (t, a) { mfn = t(fn); mfn(1, x, 3); mfn(1, x, 4); - mfn.clear(1, x, 4); + mfn.delete(1, x, 4); mfn(1, x, 3); mfn(1, x, 3); a(i, 1, "Pre clear"); - mfn.clear(1, x, 3); + mfn.delete(1, x, 3); mfn(1, x, 3); a(i, 2, "After clear"); @@ -198,7 +190,7 @@ module.exports = function (t, a) { mfn(1, x, 3); mfn(); mfn(); - mfn.clear(); + mfn.delete(); mfn(1, x, 3); a(i, 1, "Proper no arguments clear"); }, @@ -216,7 +208,7 @@ module.exports = function (t, a) { fn(1, x, 3); fn(1, x, 4); a(i, 2, "Pre clear"); - fn.clearAll(); + fn.clear(); fn(1, x, 3); fn(1, x, 4); fn(1, x, 3); @@ -224,34 +216,6 @@ module.exports = function (t, a) { a(i, 4, "After clear"); } }, - "Method": { - "No descriptor": function (a) { - var x = {}, i = 0, fn = function () { - ++i; - return this; - }; - - Object.defineProperties(x, t(fn, { method: 'foo' })); - a(x.foo(), x, "Context"); - a(x.foo(), x, "Method"); - a(i, 1, "Cached"); - }, - "Descriptor": function (a) { - var x = {}, i = 0, fn = function () { - ++i; - return this; - }; - - Object.defineProperties(x, t(fn, - { method: 'foo', writable: false })); - a(x.foo(), x, "Context"); - a.deep(Object.getOwnPropertyDescriptor(x, 'foo'), - { enumerable: false, configurable: true, writable: false, - value: x.foo }); - a(x.foo(), x, "Method"); - a(i, 1, "Cached"); - } - }, "Primitive": { "No args": function (a) { var i = 0, fn = function () { ++i; return arguments[0]; }, mfn; @@ -283,7 +247,7 @@ module.exports = function (t, a) { a(mfn(y, 'bar', 'zeta'), 'foobarzeta', "#1"); a(mfn('foo', 'bar', 'zeta'), 'foobarzeta', "#2"); a(i, 1, "Called once"); - mfn.clear('foo', { toString: function () { return 'bar'; } }, + mfn.delete('foo', { toString: function () { return 'bar'; } }, 'zeta'); a(mfn(y, 'bar', 'zeta'), 'foobarzeta', "#3"); a(i, 2, "Called twice"); @@ -293,18 +257,18 @@ module.exports = function (t, a) { "Regular": function (a) { var i = 0, fn = function (x, y, z) { ++i; return x + y + z; }, mfn; mfn = t(fn, { refCounter: true }); - a(mfn.clearRef(3, 5, 7), null, "Clear before"); + a(mfn.deleteRef(3, 5, 7), null, "Clear before"); a(mfn(3, 5, 7), 15, "Initial"); a(mfn(3, 5, 7), 15, "Cache"); - a(mfn.clearRef(3, 5, 7), false, "Clear #1"); + a(mfn.deleteRef(3, 5, 7), false, "Clear #1"); mfn(3, 5, 7); - a(mfn.clearRef(3, 5, 7), false, "Clear #2"); + a(mfn.deleteRef(3, 5, 7), false, "Clear #2"); mfn(3, 5, 7); - a(mfn.clearRef(3, 5, 7), false, "Clear #3"); + a(mfn.deleteRef(3, 5, 7), false, "Clear #3"); mfn(3, 5, 7); a(i, 1, "Not cleared"); - a(mfn.clearRef(3, 5, 7), false, "Clear #4"); - a(mfn.clearRef(3, 5, 7), true, "Clear final"); + a(mfn.deleteRef(3, 5, 7), false, "Clear #4"); + a(mfn.deleteRef(3, 5, 7), true, "Clear final"); mfn(3, 5, 7); a(i, 2, "Restarted"); mfn(3, 5, 7); @@ -313,18 +277,18 @@ module.exports = function (t, a) { "Primitive": function (a) { var i = 0, fn = function (x, y, z) { ++i; return x + y + z; }, mfn; mfn = t(fn, { primitive: true, refCounter: true }); - a(mfn.clearRef(3, 5, 7), null, "Clear before"); + a(mfn.deleteRef(3, 5, 7), null, "Clear before"); a(mfn(3, 5, 7), 15, "Initial"); a(mfn(3, 5, 7), 15, "Cache"); - a(mfn.clearRef(3, 5, 7), false, "Clear #1"); + a(mfn.deleteRef(3, 5, 7), false, "Clear #1"); mfn(3, 5, 7); - a(mfn.clearRef(3, 5, 7), false, "Clear #2"); + a(mfn.deleteRef(3, 5, 7), false, "Clear #2"); mfn(3, 5, 7); - a(mfn.clearRef(3, 5, 7), false, "Clear #3"); + a(mfn.deleteRef(3, 5, 7), false, "Clear #3"); mfn(3, 5, 7); a(i, 1, "Not cleared"); - a(mfn.clearRef(3, 5, 7), false, "Clear #4"); - a(mfn.clearRef(3, 5, 7), true, "Clear final"); + a(mfn.deleteRef(3, 5, 7), false, "Clear #4"); + a(mfn.deleteRef(3, 5, 7), true, "Clear final"); mfn(3, 5, 7); a(i, 2, "Restarted"); mfn(3, 5, 7); @@ -383,7 +347,7 @@ module.exports = function (t, a) { a(i, 2, "Init Called #2"); a(invoked, 7, "Cb Called #2"); - mfn.clear(3, 7); + mfn.delete(3, 7); a(mfn(3, 7, function (err, res) { ++invoked; @@ -414,7 +378,7 @@ module.exports = function (t, a) { mfn = t(fn, { async: true, refCounter: true }); - a(mfn.clearRef(3, 7), null, "Clear ref before"); + a(mfn.deleteRef(3, 7), null, "Clear ref before"); a(mfn(3, 7, function (err, res) { a.deep([err, res], [null, 10], "Result #1"); @@ -445,10 +409,10 @@ module.exports = function (t, a) { nextTick(function () { a(i, 2, "Again Called #2"); - a(mfn.clearRef(3, 7), false, "Clear ref #1"); - a(mfn.clearRef(3, 7), false, "Clear ref #2"); - a(mfn.clearRef(3, 7), false, "Clear ref #3"); - a(mfn.clearRef(3, 7), true, "Clear ref Final"); + a(mfn.deleteRef(3, 7), false, "Clear ref #1"); + a(mfn.deleteRef(3, 7), false, "Clear ref #2"); + a(mfn.deleteRef(3, 7), false, "Clear ref #3"); + a(mfn.deleteRef(3, 7), true, "Clear ref Final"); a(mfn(3, 7, function (err, res) { a.deep([err, res], [null, 10], "Again: Result"); @@ -551,7 +515,7 @@ module.exports = function (t, a) { nextTick(function () { a(i, 2, "Again Called #2"); - mfn.clear(3, 7); + mfn.delete(3, 7); a(mfn(3, 7, function (err, res) { a.deep([err, res], [null, 10], "Again: Result"); @@ -579,7 +543,7 @@ module.exports = function (t, a) { mfn = t(fn, { async: true, primitive: true, refCounter: true }); - a(mfn.clearRef(3, 7), null, "Clear ref before"); + a(mfn.deleteRef(3, 7), null, "Clear ref before"); a(mfn(3, 7, function (err, res) { a.deep([err, res], [null, 10], "Result #1"); @@ -610,10 +574,10 @@ module.exports = function (t, a) { nextTick(function () { a(i, 2, "Again Called #2"); - a(mfn.clearRef(3, 7), false, "Clear ref #1"); - a(mfn.clearRef(3, 7), false, "Clear ref #2"); - a(mfn.clearRef(3, 7), false, "Clear ref #3"); - a(mfn.clearRef(3, 7), true, "Clear ref Final"); + a(mfn.deleteRef(3, 7), false, "Clear ref #1"); + a(mfn.deleteRef(3, 7), false, "Clear ref #2"); + a(mfn.deleteRef(3, 7), false, "Clear ref #3"); + a(mfn.deleteRef(3, 7), true, "Clear ref Final"); a(mfn(3, 7, function (err, res) { a.deep([err, res], [null, 10], "Again: Result"); @@ -916,11 +880,11 @@ module.exports = function (t, a) { a(mfn(77, 11), 88, "Result D #3"); a(i, 8, "Called D #3"); - mfn.clear(77, 11); + mfn.delete(77, 11); a(mfn(77, 11), 88, "Result D #4"); a(i, 9, "Called D #4"); - mfn.clearAll(); + mfn.clear(); a(mfn(5, 8), 13, "Result B #6"); a(i, 10, "Called B #6"); a(mfn(77, 11), 88, "Result D #5"); @@ -1006,13 +970,13 @@ module.exports = function (t, a) { "Result D #3"); a(i, 8, "Called D #3"); - mfn.clear(77, 11); + mfn.delete(77, 11); a(mfn(77, 11, function (err, res) { a.deep([err, res], [null, 88], "Result D #4"); a(i, 9, "Called D #4"); - mfn.clearAll(); + mfn.clear(); a(mfn(5, 8, function (err, res) { a.deep([err, res], [null, 13], "Result B #6"); @@ -1092,11 +1056,11 @@ module.exports = function (t, a) { a(mfn(77, 11), 88, "Result D #3"); a(i, 8, "Called D #3"); - mfn.clear(77, 11); + mfn.delete(77, 11); a(mfn(77, 11), 88, "Result D #4"); a(i, 9, "Called D #4"); - mfn.clearAll(); + mfn.clear(); a(mfn(5, 8), 13, "Result B #6"); a(i, 10, "Called B #6"); a(mfn(77, 11), 88, "Result D #5"); @@ -1182,13 +1146,13 @@ module.exports = function (t, a) { "Result D #3"); a(i, 8, "Called D #3"); - mfn.clear(77, 11); + mfn.delete(77, 11); a(mfn(77, 11, function (err, res) { a.deep([err, res], [null, 88], "Result D #4"); a(i, 9, "Called D #4"); - mfn.clearAll(); + mfn.clear(); a(mfn(5, 8, function (err, res) { a.deep([err, res], [null, 13], "Result B #6"); @@ -1234,15 +1198,15 @@ module.exports = function (t, a) { mfn(5, 8); mfn(12, 4); a.deep(value, [], "Pre"); - mfn.clear(5, 8); + mfn.delete(5, 8); a.deep(value, [13], "#1"); value = []; - mfn.clear(12, 4); + mfn.delete(12, 4); a.deep(value, [16], "#2"); value = []; mfn(77, 11); - mfn.clearAll(); + mfn.clear(); a.deep(value, [10, 88], "Clear all"); x = {}; @@ -1250,11 +1214,11 @@ module.exports = function (t, a) { mfn = t(function () { return x; }, { dispose: function (val) { invoked = val; } }); - mfn.clear(); + mfn.delete(); a(invoked, false, "No args: Post invalid clear"); mfn(); a(invoked, false, "No args: Post cache"); - mfn.clear(); + mfn.delete(); a(invoked, x, "No args: Pre clear"); }, "Ref counter": function (a) { @@ -1268,17 +1232,17 @@ module.exports = function (t, a) { mfn(12, 4); a.deep(value, [], "Pre"); mfn(5, 8); - mfn.clearRef(5, 8); + mfn.deleteRef(5, 8); a.deep(value, [], "Pre"); - mfn.clearRef(5, 8); + mfn.deleteRef(5, 8); a.deep(value, [13], "#1"); value = []; - mfn.clearRef(12, 4); + mfn.deleteRef(12, 4); a.deep(value, [16], "#2"); value = []; mfn(77, 11); - mfn.clearAll(); + mfn.clear(); a.deep(value, [10, 88], "Clear all"); }, "Async": function (a, d) { @@ -1295,15 +1259,15 @@ module.exports = function (t, a) { mfn(5, 8, function () { mfn(12, 4, function () { a.deep(value, [], "Pre"); - mfn.clear(5, 8); + mfn.delete(5, 8); a.deep(value, [13], "#1"); value = []; - mfn.clear(12, 4); + mfn.delete(12, 4); a.deep(value, [16], "#2"); value = []; mfn(77, 11, function () { - mfn.clearAll(); + mfn.clear(); a.deep(value, [10, 88], "Clear all"); d(); }); @@ -1322,15 +1286,15 @@ module.exports = function (t, a) { mfn(5, 8); mfn(12, 4); a.deep(value, [], "Pre"); - mfn.clear(5, 8); + mfn.delete(5, 8); a.deep(value, [13], "#1"); value = []; - mfn.clear(12, 4); + mfn.delete(12, 4); a.deep(value, [16], "#2"); value = []; mfn(77, 11); - mfn.clearAll(); + mfn.clear(); a.deep(value, [10, 88], "Clear all"); }, "Ref counter": function (a) { @@ -1344,17 +1308,17 @@ module.exports = function (t, a) { mfn(12, 4); a.deep(value, [], "Pre"); mfn(5, 8); - mfn.clearRef(5, 8); + mfn.deleteRef(5, 8); a.deep(value, [], "Pre"); - mfn.clearRef(5, 8); + mfn.deleteRef(5, 8); a.deep(value, [13], "#1"); value = []; - mfn.clearRef(12, 4); + mfn.deleteRef(12, 4); a.deep(value, [16], "#2"); value = []; mfn(77, 11); - mfn.clearAll(); + mfn.clear(); a.deep(value, [10, 88], "Clear all"); }, "Async": function (a, d) { @@ -1371,15 +1335,15 @@ module.exports = function (t, a) { mfn(5, 8, function () { mfn(12, 4, function () { a.deep(value, [], "Pre"); - mfn.clear(5, 8); + mfn.delete(5, 8); a.deep(value, [13], "#1"); value = []; - mfn.clear(12, 4); + mfn.delete(12, 4); a.deep(value, [16], "#2"); value = []; mfn(77, 11, function () { - mfn.clearAll(); + mfn.clear(); a.deep(value, [10, 88], "Clear all"); d(); }); diff --git a/test/lib/configure-map.js b/test/lib/configure-map.js new file mode 100644 index 0000000..6aa3a21 --- /dev/null +++ b/test/lib/configure-map.js @@ -0,0 +1,77 @@ +'use strict'; + +var aFrom = require('es5-ext/array/from') + , memoize = require('../..'); + +module.exports = function () { + return { + "One arg": function (a) { + var i = 0, fn = function (x) { ++i; return x; }, mfn + , y = { toString: function () { return 'foo'; } }; + mfn = memoize(fn, { primitive: true }); + a(mfn(y), y, "#1"); + a(mfn('foo'), y, "#2"); + a(i, 1, "Called once"); + }, + "Clear cache": function (a) { + var i = 0, fn = function (x, y, z) { ++i; return x + y + z; }, mfn + , y = { toString: function () { return 'foo'; } }; + mfn = memoize(fn, { primitive: true }); + a(mfn(y, 'bar', 'zeta'), 'foobarzeta', "#1"); + a(mfn('foo', 'bar', 'zeta'), 'foobarzeta', "#2"); + a(i, 1, "Called once"); + mfn.delete('foo', { toString: function () { return 'bar'; } }, + 'zeta'); + a(mfn(y, 'bar', 'zeta'), 'foobarzeta', "#3"); + a(i, 2, "Called twice"); + }, + "Circular": function (a) { + var i = 0, fn; + fn = memoize(function (x) { + if (++i < 2) fn(x); + }); + a.throws(function () { + fn('foo'); + }, 'CIRCULAR_INVOCATION'); + + i = 0; + fn = memoize(function (x, y) { + if (++i < 2) fn(x, y); + }); + a.throws(function () { + fn('foo', 'bar'); + }, 'CIRCULAR_INVOCATION'); + }, + "Resolvers": function () { + var i = 0, fn, r; + fn = memoize(function () { ++i; return arguments; }, + { length: 3, resolvers: [Boolean, String] }); + return { + "No args": function (a) { + i = 0; + a.deep(aFrom(r = fn()), [false, 'undefined'], "First"); + a(fn(), r, "Second"); + a(fn(), r, "Third"); + a(i, 1, "Called once"); + }, + "Some Args": function (a) { + var x = {}; + i = 0; + a.deep(aFrom(r = fn(0, 34, x, 45)), [false, '34', x, 45], "First"); + a(fn(0, 34, x, 22), r, "Second"); + a(fn(0, 34, x, false), r, "Third"); + a(i, 1, "Called once"); + return { + "Other": function (a) { + a.deep(aFrom(r = fn(1, 34, x, 34)), + [true, '34', x, 34], "Second"); + a(fn(1, 34, x, 89), r, "Third"); + a(i, 2, "Called once"); + } + }; + } + }; + } + }; +}; + diff --git a/test/d.js b/test/lib/methods.js similarity index 68% rename from test/d.js rename to test/lib/methods.js index 904062b..cef1e08 100644 --- a/test/d.js +++ b/test/lib/methods.js @@ -1,10 +1,10 @@ 'use strict'; var d = require('d') - , memoize = require('../lib/regular'); + , memoize = require('../..'); -require('../lib/ext/dispose'); -require('../lib/ext/ref-counter'); +require('../ext/dispose'); +require('../ext/ref-counter'); module.exports = function (t, a) { var value = [], obj = {}; @@ -21,16 +21,16 @@ module.exports = function (t, a) { obj.someFn(12, 4); a.deep(value, [], "Pre"); obj.someFn(5, 8); - obj.someFn.clearRef(5, 8); + obj.someFn.deleteRef(5, 8); a.deep(value, [], "Pre"); - obj.someFn.clearRef(5, 8); + obj.someFn.deleteRef(5, 8); a.deep(value, [13], "#1"); value = []; - obj.someFn.clearRef(12, 4); + obj.someFn.deleteRef(12, 4); a.deep(value, [16], "#2"); value = []; obj.someFn(77, 11); - obj.someFn.clearAll(); - a.deep(value, [10, 88], "Clear all"); + obj.someFn.clear(); + a.deep(value, [10, 88], "Clear"); }; diff --git a/test/lib/normalizers/0.js b/test/lib/normalizers/0.js new file mode 100644 index 0000000..61cf0f1 --- /dev/null +++ b/test/lib/normalizers/0.js @@ -0,0 +1,32 @@ +'use strict'; + +var memoize = require('../../..'); + +module.exports = { + "": function (a) { + var i = 0, fn = function () { ++i; return 3; }; + + fn = memoize(fn); + a(fn(), 3, "First"); + a(fn(1), 3, "Second"); + a(fn(5), 3, "Third"); + a(i, 1, "Called once"); + }, + "Delete": function (a) { + var i = 0, fn, mfn, x = {}; + + fn = function (a, b, c) { + return a + (++i); + }; + mfn = memoize(fn, { length: 0 }); + a(mfn(3), 4, "Init"); + a(mfn(5), 4, "Other"); + a(i, 1, "Pre clear"); + mfn.delete(6, x, 1); + a(i, 1, "After clear"); + a(mfn(6, x, 1), 8, "Reinit"); + a(i, 2, "Reinit count"); + a(mfn(3, x, 1), 8, "Reinit Cached"); + a(i, 2, "Reinit count"); + } +}; diff --git a/test/lib/normalizers/get-primitive-fixed.js b/test/lib/normalizers/get-primitive-fixed.js new file mode 100644 index 0000000..a4701d0 --- /dev/null +++ b/test/lib/normalizers/get-primitive-fixed.js @@ -0,0 +1,43 @@ +'use strict'; + +var memoize = require('../../..'); + +module.exports = { + "": function (a) { + var i = 0, fn = function (x, y, z) { ++i; return x + y + z; }, mfn + , y = { toString: function () { return 'foo'; } }; + mfn = memoize(fn, { primitive: true }); + a(mfn(y, 'bar', 'zeta'), 'foobarzeta', "#1"); + a(mfn('foo', 'bar', 'zeta'), 'foobarzeta', "#2"); + a(i, 1, "Called once"); + }, + "Delete": function (a) { + var i = 0, fn = function (x, y, z) { ++i; return x + y + z; }, mfn + , y = { toString: function () { return 'foo'; } }; + mfn = memoize(fn, { primitive: true }); + a(mfn(y, 'bar', 'zeta'), 'foobarzeta', "#1"); + a(mfn('foo', 'bar', 'zeta'), 'foobarzeta', "#2"); + a(i, 1, "Called once"); + mfn.delete('foo', { toString: function () { return 'bar'; } }, + 'zeta'); + a(mfn(y, 'bar', 'zeta'), 'foobarzeta', "#3"); + a(i, 2, "Called twice"); + }, + "Clear": function (a) { + var i = 0, fn; + fn = memoize(function (x) { + if (++i < 2) fn(x); + }); + a.throws(function () { + fn('foo'); + }, 'CIRCULAR_INVOCATION'); + + i = 0; + fn = memoize(function (x, y) { + if (++i < 2) fn(x, y); + }); + a.throws(function () { + fn('foo', 'bar'); + }, 'CIRCULAR_INVOCATION'); + } +}; diff --git a/test/lib/normalizers/get-regular-1.js b/test/lib/normalizers/get-regular-1.js new file mode 100644 index 0000000..d5827fc --- /dev/null +++ b/test/lib/normalizers/get-regular-1.js @@ -0,0 +1,56 @@ +'use strict'; + +var memoize = require('../../..'); + +module.exports = { + "": function (t, a) { + var i = 0, fn = function (x) { ++i; return x; }; + + fn = memoize(fn); + return { + "No arg": function () { + i = 0; + a(fn(), undefined, "First"); + a(fn(), undefined, "Second"); + a(fn(), undefined, "Third"); + a(i, 1, "Called once"); + }, + "Arg": function () { + var x = {}; + i = 0; + a(fn(x, 8), x, "First"); + a(fn(x, 4), x, "Second"); + a(fn(x, 2), x, "Third"); + a(i, 1, "Called once"); + }, + "Other Arg": function () { + var x = {}; + i = 0; + a(fn(x, 2), x, "First"); + a(fn(x, 9), x, "Second"); + a(fn(x, 3), x, "Third"); + a(i, 1, "Called once"); + } + }; + }, + "Delete": function (a) { + var i = 0, fn, mfn, x = {}; + + fn = function (a, b, c) { + return a + (++i); + }; + mfn = memoize(fn, { length: 1 }); + a(mfn(3), 4, "Init"); + a(mfn(4, x, 1), 6, "Init #2"); + mfn.delete(4); + a(mfn(3, x, 1), 4, "Cached"); + mfn(3, x, 1); + a(i, 2, "Pre clear"); + mfn.delete(3, x, 1); + a(i, 2, "After clear"); + a(mfn(3, x, 1), 6, "Reinit"); + a(i, 3, "Reinit count"); + a(mfn(3, x, 1), 6, "Reinit Cached"); + a(i, 3, "Reinit count"); + } +}; diff --git a/test/lib/normalizers/get-regular-fixed.js b/test/lib/normalizers/get-regular-fixed.js new file mode 100644 index 0000000..feee3f6 --- /dev/null +++ b/test/lib/normalizers/get-regular-fixed.js @@ -0,0 +1,91 @@ +'use strict'; + +var memoize = require('../../..'); + +module.exports = { + "": function (a) { + var i = 0, fn = function (x, y, z) { ++i; return [x, y, z]; }, r; + + fn = memoize(fn); + return { + "No args": function () { + i = 0; + a.deep(r = fn(), [undefined, undefined, undefined], "First"); + a(fn(), r, "Second"); + a(fn(), r, "Third"); + a(i, 1, "Called once"); + }, + "Some Args": function () { + var x = {}; + i = 0; + a.deep(r = fn(x, 8), [x, 8, undefined], "First"); + a(fn(x, 8), r, "Second"); + a(fn(x, 8), r, "Third"); + a(i, 1, "Called once"); + return { + "Other": function () { + a.deep(r = fn(x, 5), [x, 5, undefined], "Second"); + a(fn(x, 5), r, "Third"); + a(i, 2, "Called once"); + } + }; + }, + "Full stuff": function () { + var x = {}; + i = 0; + a.deep(r = fn(x, 8, 23, 98), [x, 8, 23], "First"); + a(fn(x, 8, 23, 43), r, "Second"); + a(fn(x, 8, 23, 9), r, "Third"); + a(i, 1, "Called once"); + return { + "Other": function () { + a.deep(r = fn(x, 23, 8, 13), [x, 23, 8], "Second"); + a(fn(x, 23, 8, 22), r, "Third"); + a(i, 2, "Called once"); + } + }; + } + }; + }, + "Delete": function (a) { + var i = 0, fn, mfn, x = {}; + + fn = function (a, b, c) { + return a + (++i); + }; + mfn = memoize(fn); + a(mfn(3, x, 1), 4, "Init"); + a(mfn(4, x, 1), 6, "Init #2"); + mfn.delete(4, x, 1); + a(mfn(3, x, 1), 4, "Cached"); + mfn(3, x, 1); + a(i, 2, "Pre clear"); + mfn.delete(3, x, 1); + a(i, 2, "After clear"); + a(mfn(3, x, 1), 6, "Reinit"); + a(i, 3, "Reinit count"); + a(mfn(3, x, 1), 6, "Reinit Cached"); + a(i, 3, "Reinit count"); + }, + "Clear": function (a) { + var i = 0, fn, x = {}; + + fn = function () { + ++i; + return arguments; + }; + + fn = memoize(fn, { length: 3 }); + fn(1, x, 3); + fn(1, x, 4); + fn(1, x, 3); + fn(1, x, 4); + a(i, 2, "Pre clear"); + fn.clear(); + fn(1, x, 3); + fn(1, x, 4); + fn(1, x, 3); + fn(1, x, 4); + a(i, 4, "After clear"); + } +}; diff --git a/test/lib/normalizers/get-regular.js b/test/lib/normalizers/get-regular.js new file mode 100644 index 0000000..c6c5fe5 --- /dev/null +++ b/test/lib/normalizers/get-regular.js @@ -0,0 +1,59 @@ +'use strict'; + +var aFrom = require('es5-ext/array/from') + , memoize = require('../../..'); + +module.exports = function () { + return { + "": function (a) { + var i = 0, fn = function () { ++i; return arguments; }, r; + + fn = memoize(fn, { length: false }); + return { + "No args": function () { + i = 0; + a.deep(aFrom(r = fn()), [], "First"); + a(fn(), r, "Second"); + a(fn(), r, "Third"); + a(i, 1, "Called once"); + }, + "Some Args": function () { + var x = {}; + i = 0; + a.deep(aFrom(r = fn(x, 8)), [x, 8], "First"); + a(fn(x, 8), r, "Second"); + a(fn(x, 8), r, "Third"); + a(i, 1, "Called once"); + }, + "Many args": function () { + var x = {}; + i = 0; + a.deep(aFrom(r = fn(x, 8, 23, 98)), [x, 8, 23, 98], "First"); + a(fn(x, 8, 23, 98), r, "Second"); + a(fn(x, 8, 23, 98), r, "Third"); + a(i, 1, "Called once"); + } + }; + }, + "Delete": function (a) { + var i = 0, fn, mfn, x = {}; + + fn = function (a, b, c) { + return a + (++i); + }; + mfn = memoize(fn, { length: false }); + a(mfn(3, x, 1), 4, "Init"); + a(mfn(4, x, 1), 6, "Init #2"); + mfn.delete(4, x, 1); + a(mfn(3, x, 1), 4, "Cached"); + mfn(3, x, 1); + a(i, 2, "Pre clear"); + mfn.delete(3, x, 1); + a(i, 2, "After clear"); + a(mfn(3, x, 1), 6, "Reinit"); + a(i, 3, "Reinit count"); + a(mfn(3, x, 1), 6, "Reinit Cached"); + a(i, 3, "Reinit count"); + } + }; +}; diff --git a/test/lib/normalizers/primitive.js b/test/lib/normalizers/primitive.js new file mode 100644 index 0000000..9c90048 --- /dev/null +++ b/test/lib/normalizers/primitive.js @@ -0,0 +1,18 @@ +'use strict'; + +var memoize = require('../../..') + + , join = Array.prototype.join; + +module.exports = function (a) { + var i = 0, fn = function () { ++i; return join.call(arguments, '|'); } + , y = { toString: function () { return 'foo'; } }, mfn; + mfn = memoize(fn, { primitive: true, length: false }); + a(mfn(y, 'bar', 'zeta'), 'foo|bar|zeta', "#1"); + a(mfn('foo', 'bar', 'zeta'), 'foo|bar|zeta', "#2"); + a(i, 1, "Called once"); + a(mfn(y, 'bar'), 'foo|bar', "#3"); + a(i, 2, "Called twice"); + a(mfn(y, 'bar'), 'foo|bar', "#4"); + a(i, 2, "Called twice #2"); +}; diff --git a/test/lib/registered-extensions.js b/test/lib/registered-extensions.js new file mode 100644 index 0000000..22d9c50 --- /dev/null +++ b/test/lib/registered-extensions.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = function (t, a) { + require('../../ext/async'); + a(typeof t.async, 'function'); +}; diff --git a/test/lib/resolve-length.js b/test/lib/resolve-length.js new file mode 100644 index 0000000..28ab5c0 --- /dev/null +++ b/test/lib/resolve-length.js @@ -0,0 +1,10 @@ +'use strict'; + +module.exports = function (t, a) { + a(t(1, 2), 1, "Options"); + a(t(1, 2, true), 1, "Options: Async "); + a(t(undefined, 2), 2, "Function"); + a(t(undefined, 2, true), 1, "Function: Async"); + a(t(undefined, undefined, false), 1, "Unknown"); + a(t(undefined, undefined, true), 1, "Unknown: async"); +}; diff --git a/test/methods-plain.js b/test/methods-plain.js new file mode 100644 index 0000000..09bfe1e --- /dev/null +++ b/test/methods-plain.js @@ -0,0 +1,34 @@ +'use strict'; + +var d = require('d'); + +require('../ext/dispose'); +require('../ext/ref-counter'); + +module.exports = function (t, a) { + var value = [], obj = {}; + Object.defineProperties(obj, t({ + someFn: d(function (x, y) { a(this, obj); return x + y; }, + { refCounter: true, + dispose: function (val) { value.push(val); } }) + })); + + obj = Object.create(obj); + obj.someFn(3, 7); + obj.someFn(5, 8); + obj.someFn(12, 4); + a.deep(value, [], "Pre"); + obj.someFn(5, 8); + obj.someFn.deleteRef(5, 8); + a.deep(value, [], "Pre"); + obj.someFn.deleteRef(5, 8); + a.deep(value, [13], "#1"); + value = []; + obj.someFn.deleteRef(12, 4); + a.deep(value, [16], "#2"); + + value = []; + obj.someFn(77, 11); + obj.someFn.clear(); + a.deep(value, [10, 88], "Clear all"); +}; diff --git a/test/methods.js b/test/methods.js new file mode 100644 index 0000000..64c0dca --- /dev/null +++ b/test/methods.js @@ -0,0 +1,31 @@ +'use strict'; + +var d = require('d'); + +module.exports = function (t, a) { + var value = [], obj = {}; + Object.defineProperties(obj, t({ + someFn: d(function (x, y) { a(this, obj); return x + y; }, + { refCounter: true, + dispose: function (val) { value.push(val); } }) + })); + + obj = Object.create(obj); + obj.someFn(3, 7); + obj.someFn(5, 8); + obj.someFn(12, 4); + a.deep(value, [], "Pre"); + obj.someFn(5, 8); + obj.someFn.deleteRef(5, 8); + a.deep(value, [], "Pre"); + obj.someFn.deleteRef(5, 8); + a.deep(value, [13], "#1"); + value = []; + obj.someFn.deleteRef(12, 4); + a.deep(value, [16], "#2"); + + value = []; + obj.someFn(77, 11); + obj.someFn.clear(); + a.deep(value, [10, 88], "Clear all"); +}; diff --git a/test/plain.js b/test/plain.js new file mode 100644 index 0000000..8903f21 --- /dev/null +++ b/test/plain.js @@ -0,0 +1,29 @@ +'use strict'; + +module.exports = function (t) { + return { + "": function (a) { + var i = 0, fn = function (x) { ++i; return x; }, mfn + , y = { toString: function () { return 'foo'; } }; + mfn = t(fn, { primitive: true }); + a(typeof mfn, 'function', "Returns"); + a(mfn.__memoized__, true, "Marked"); + a(t(mfn), mfn, "Do not memoize memoized"); + a(mfn(y), y, "#1"); + a(mfn('foo'), y, "#2"); + a(i, 1, "Called once"); + }, + "Clear cache": function (a) { + var i = 0, fn = function (x, y, z) { ++i; return x + y + z; }, mfn + , y = { toString: function () { return 'foo'; } }; + mfn = t(fn, { primitive: true }); + a(mfn(y, 'bar', 'zeta'), 'foobarzeta', "#1"); + a(mfn('foo', 'bar', 'zeta'), 'foobarzeta', "#2"); + a(i, 1, "Called once"); + mfn.delete('foo', { toString: function () { return 'bar'; } }, + 'zeta'); + a(mfn(y, 'bar', 'zeta'), 'foobarzeta', "#3"); + a(i, 2, "Called twice"); + } + }; +}; diff --git a/test/primitive.js b/test/primitive.js deleted file mode 100644 index 375abc4..0000000 --- a/test/primitive.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict'; - -var join = Array.prototype.join; - -module.exports = function (t) { - return { - "No args": function (a) { - var i = 0, fn = function () { ++i; return arguments[0]; }, mfn; - mfn = t(fn, { primitive: true }); - a(mfn('ble'), 'ble', "#1"); - a(mfn({}), 'ble', "#2"); - a(i, 1, "Called once"); - }, - "One arg": function (a) { - var i = 0, fn = function (x) { ++i; return x; }, mfn - , y = { toString: function () { return 'foo'; } }; - mfn = t(fn, { primitive: true }); - a(mfn(y), y, "#1"); - a(mfn('foo'), y, "#2"); - a(i, 1, "Called once"); - }, - "Many args": function (a) { - var i = 0, fn = function (x, y, z) { ++i; return x + y + z; }, mfn - , y = { toString: function () { return 'foo'; } }; - mfn = t(fn, { primitive: true }); - a(mfn(y, 'bar', 'zeta'), 'foobarzeta', "#1"); - a(mfn('foo', 'bar', 'zeta'), 'foobarzeta', "#2"); - a(i, 1, "Called once"); - }, - "Many args: Length false": function (a) { - var i = 0, fn = function () { ++i; return join.call(arguments, '|'); } - , y = { toString: function () { return 'foo'; } }, mfn; - mfn = t(fn, { primitive: true, length: false }); - a(mfn(y, 'bar', 'zeta'), 'foo|bar|zeta', "#1"); - a(mfn('foo', 'bar', 'zeta'), 'foo|bar|zeta', "#2"); - a(i, 1, "Called once"); - a(mfn(y, 'bar'), 'foo|bar', "#3"); - a(i, 2, "Called twice"); - a(mfn(y, 'bar'), 'foo|bar', "#4"); - a(i, 2, "Called twice #2"); - }, - "Clear cache": function (a) { - var i = 0, fn = function (x, y, z) { ++i; return x + y + z; }, mfn - , y = { toString: function () { return 'foo'; } }; - mfn = t(fn, { primitive: true }); - a(mfn(y, 'bar', 'zeta'), 'foobarzeta', "#1"); - a(mfn('foo', 'bar', 'zeta'), 'foobarzeta', "#2"); - a(i, 1, "Called once"); - mfn.clear('foo', { toString: function () { return 'bar'; } }, - 'zeta'); - a(mfn(y, 'bar', 'zeta'), 'foobarzeta', "#3"); - a(i, 2, "Called twice"); - }, - "Circular": function (a) { - var i = 0, fn; - fn = t(function (x) { - if (++i < 2) fn(x); - }); - a.throws(function () { - fn('foo'); - }, 'CIRCULAR_INVOCATION'); - - i = 0; - fn = t(function (x, y) { - if (++i < 2) fn(x, y); - }); - a.throws(function () { - fn('foo', 'bar'); - }, 'CIRCULAR_INVOCATION'); - } - }; -}; diff --git a/test/ext/profile.js b/test/profile.js similarity index 100% rename from test/ext/profile.js rename to test/profile.js diff --git a/test/regular.js b/test/regular.js deleted file mode 100644 index 584d118..0000000 --- a/test/regular.js +++ /dev/null @@ -1,223 +0,0 @@ -'use strict'; - -var aFrom = require('es5-ext/array/from'); - -module.exports = function (t, a) { - return { - "0": function () { - var i = 0, fn = function () { ++i; return 3; }; - - fn = t(fn); - a(fn(), 3, "First"); - a(fn(1), 3, "Second"); - a(fn(5), 3, "Third"); - a(i, 1, "Called once"); - }, - "1": function () { - var i = 0, fn = function (x) { ++i; return x; }; - - fn = t(fn); - return { - "No arg": function () { - i = 0; - a(fn(), undefined, "First"); - a(fn(), undefined, "Second"); - a(fn(), undefined, "Third"); - a(i, 1, "Called once"); - }, - "Arg": function () { - var x = {}; - i = 0; - a(fn(x, 8), x, "First"); - a(fn(x, 4), x, "Second"); - a(fn(x, 2), x, "Third"); - a(i, 1, "Called once"); - }, - "Other Arg": function () { - var x = {}; - i = 0; - a(fn(x, 2), x, "First"); - a(fn(x, 9), x, "Second"); - a(fn(x, 3), x, "Third"); - a(i, 1, "Called once"); - } - }; - }, - "3": function () { - var i = 0, fn = function (x, y, z) { ++i; return [x, y, z]; }, r; - - fn = t(fn); - return { - "No args": function () { - i = 0; - a.deep(r = fn(), [undefined, undefined, undefined], "First"); - a(fn(), r, "Second"); - a(fn(), r, "Third"); - a(i, 1, "Called once"); - }, - "Some Args": function () { - var x = {}; - i = 0; - a.deep(r = fn(x, 8), [x, 8, undefined], "First"); - a(fn(x, 8), r, "Second"); - a(fn(x, 8), r, "Third"); - a(i, 1, "Called once"); - return { - "Other": function () { - a.deep(r = fn(x, 5), [x, 5, undefined], "Second"); - a(fn(x, 5), r, "Third"); - a(i, 2, "Called once"); - } - }; - }, - "Full stuff": function () { - var x = {}; - i = 0; - a.deep(r = fn(x, 8, 23, 98), [x, 8, 23], "First"); - a(fn(x, 8, 23, 43), r, "Second"); - a(fn(x, 8, 23, 9), r, "Third"); - a(i, 1, "Called once"); - return { - "Other": function () { - a.deep(r = fn(x, 23, 8, 13), [x, 23, 8], "Second"); - a(fn(x, 23, 8, 22), r, "Third"); - a(i, 2, "Called once"); - } - }; - } - }; - }, - "Dynamic": function () { - var i = 0, fn = function () { ++i; return arguments; }, r; - - fn = t(fn, { length: false }); - return { - "No args": function () { - i = 0; - a.deep(aFrom(r = fn()), [], "First"); - a(fn(), r, "Second"); - a(fn(), r, "Third"); - a(i, 1, "Called once"); - }, - "Some Args": function () { - var x = {}; - i = 0; - a.deep(aFrom(r = fn(x, 8)), [x, 8], "First"); - a(fn(x, 8), r, "Second"); - a(fn(x, 8), r, "Third"); - a(i, 1, "Called once"); - }, - "Many args": function () { - var x = {}; - i = 0; - a.deep(aFrom(r = fn(x, 8, 23, 98)), [x, 8, 23, 98], "First"); - a(fn(x, 8, 23, 98), r, "Second"); - a(fn(x, 8, 23, 98), r, "Third"); - a(i, 1, "Called once"); - } - }; - }, - "Clear Cache": { - "Specific": function () { - var i = 0, fn, mfn, x = {}; - - fn = function (a, b, c) { - return a + (++i); - }; - - return { - "0": function (a) { - mfn = t(fn, { length: 0 }); - a(mfn(3), 4, "Init"); - a(mfn(5), 4, "Other"); - a(i, 1, "Pre clear"); - mfn.clear(6, x, 1); - a(i, 1, "After clear"); - a(mfn(6, x, 1), 8, "Reinit"); - a(i, 2, "Reinit count"); - a(mfn(3, x, 1), 8, "Reinit Cached"); - a(i, 2, "Reinit count"); - }, - "1": function (a) { - i = 0; - mfn = t(fn, { length: 1 }); - a(mfn(3), 4, "Init"); - a(mfn(4, x, 1), 6, "Init #2"); - mfn.clear(4); - a(mfn(3, x, 1), 4, "Cached"); - mfn(3, x, 1); - a(i, 2, "Pre clear"); - mfn.clear(3, x, 1); - a(i, 2, "After clear"); - a(mfn(3, x, 1), 6, "Reinit"); - a(i, 3, "Reinit count"); - a(mfn(3, x, 1), 6, "Reinit Cached"); - a(i, 3, "Reinit count"); - }, - "3": function (a) { - i = 0; - mfn = t(fn); - a(mfn(3, x, 1), 4, "Init"); - a(mfn(4, x, 1), 6, "Init #2"); - mfn.clear(4, x, 1); - a(mfn(3, x, 1), 4, "Cached"); - mfn(3, x, 1); - a(i, 2, "Pre clear"); - mfn.clear(3, x, 1); - a(i, 2, "After clear"); - a(mfn(3, x, 1), 6, "Reinit"); - a(i, 3, "Reinit count"); - a(mfn(3, x, 1), 6, "Reinit Cached"); - a(i, 3, "Reinit count"); - }, - "Any": function (a) { - i = 0; - mfn = t(fn, { length: false }); - a(mfn(3, x, 1), 4, "Init"); - a(mfn(4, x, 1), 6, "Init #2"); - mfn.clear(4, x, 1); - a(mfn(3, x, 1), 4, "Cached"); - mfn(3, x, 1); - a(i, 2, "Pre clear"); - mfn.clear(3, x, 1); - a(i, 2, "After clear"); - a(mfn(3, x, 1), 6, "Reinit"); - a(i, 3, "Reinit count"); - a(mfn(3, x, 1), 6, "Reinit Cached"); - a(i, 3, "Reinit count"); - } - }; - }, - "All": function (a) { - var i = 0, fn, x = {}; - - fn = function () { - ++i; - return arguments; - }; - - fn = t(fn, { length: 3 }); - fn(1, x, 3); - fn(1, x, 4); - fn(1, x, 3); - fn(1, x, 4); - a(i, 2, "Pre clear"); - fn.clearAll(); - fn(1, x, 3); - fn(1, x, 4); - fn(1, x, 3); - fn(1, x, 4); - a(i, 4, "After clear"); - } - }, - "Circular": function (a) { - var i = 0, fn; - fn = t(function (x) { - if (++i < 2) fn(x); - }); - a.throws(function () { - fn(34); - }, 'CIRCULAR_INVOCATION'); - } - }; -};