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
This commit is contained in:
Mariusz Nowak
2014-04-27 12:11:06 +02:00
parent ec114973d3
commit eb72d16bf6
61 changed files with 1424 additions and 1481 deletions

View File

@@ -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 = {};

144
lib/configure-map.js Normal file
View File

@@ -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;
};

View File

@@ -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);
});
};

View File

@@ -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); });
});
};

View File

@@ -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); });
});
}
};

View File

@@ -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 = {};
}
});
}
};

View File

@@ -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 = {};
});
}
};

View File

@@ -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;
};

View File

@@ -1,82 +0,0 @@
// Gathers statistical data, and provides them in convinient form
'use strict';
var partial = require('es5-ext/function/#/partial')
, forEach = require('es5-ext/object/for-each')
, pad = require('es5-ext/string/#/pad')
, max = Math.max
, stats = exports.statistics = {}, ext;
require('../_base').ext.profile = ext = function (conf) {
var id, stack, data;
stack = (new Error()).stack;
if (!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;
}
})) {
id = 'unknown';
}
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;
exports.log = function () {
var initial, cached, ordered, ipad, cpad, ppad, toPrc, log;
initial = cached = 0;
ordered = [];
toPrc = function (initial, cached) {
if (!initial && !cached) {
return '0.00';
}
return ((cached / (initial + cached)) * 100).toFixed(2);
};
log = "------------------------------------------------------------\n";
log += "Memoize statistics:\n\n";
forEach(stats, function (data, name) {
initial += data.initial;
cached += data.cached;
ordered.push([name, data]);
}, null, function (a, b) {
return (this[b].initial + this[b].cached) -
(this[a].initial + this[a].cached);
});
ipad = partial.call(pad, " ",
max(String(initial).length, "Init".length));
cpad = partial.call(pad, " ", max(String(cached).length, "Cache".length));
ppad = partial.call(pad, " ", "%Cache".length);
log += ipad.call("Init") + " " +
cpad.call("Cache") + " " +
ppad.call("%Cache") + " Source location\n";
log += ipad.call(initial) + " " +
cpad.call(cached) + " " +
ppad.call(toPrc(initial, cached)) + " (all)\n";
ordered.forEach(function (data) {
var name = data[0];
data = data[1];
log += ipad.call(data.initial) + " " +
cpad.call(data.cached) + " " +
ppad.call(toPrc(data.initial, data.cached)) + " " + name + "\n";
});
log += "------------------------------------------------------------\n";
return log;
};

View File

@@ -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];
};
};

View File

@@ -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));
};

View File

@@ -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);
};

15
lib/methods.js Normal file
View File

@@ -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);
};
};

5
lib/normalizers/0.js Normal file
View File

@@ -0,0 +1,5 @@
'use strict';
var k = require('es5-ext/function/constant');
module.exports = { get: k('') };

View File

@@ -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;
} };
};

View File

@@ -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;
}
};
};

View File

@@ -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;
}
};
};

View File

@@ -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;
}
};
};

View File

@@ -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;
} };

View File

@@ -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');
});
});

View File

@@ -0,0 +1 @@
'use strict';

View File

@@ -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');
});
});

15
lib/resolve-length.js Normal file
View File

@@ -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);
};