From 67bd73965519d1eac16007fd1b732576c9b17858 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Mon, 22 Sep 2014 14:30:18 +0200 Subject: [PATCH] WeakMap based functionality --- lib/weak.js | 115 +++++++++++++++++++++++++++++++++++++++++++++ test/lib/weak.js | 39 +++++++++++++++ test/weak-plain.js | 36 ++++++++++++++ test/weak.js | 33 +++++++++++++ weak-plain.js | 3 ++ weak.js | 3 ++ 6 files changed, 229 insertions(+) create mode 100644 lib/weak.js create mode 100644 test/lib/weak.js create mode 100644 test/weak-plain.js create mode 100644 test/weak.js create mode 100644 weak-plain.js create mode 100644 weak.js diff --git a/lib/weak.js b/lib/weak.js new file mode 100644 index 0000000..59d937f --- /dev/null +++ b/lib/weak.js @@ -0,0 +1,115 @@ +'use strict'; + +var customError = require('es5-ext/error/custom') + , defineLength = require('es5-ext/function/_define-length') + , partial = require('es5-ext/function/#/partial') + , copy = require('es5-ext/object/copy') + , normalizeOpts = require('es5-ext/object/normalize-options') + , callable = require('es5-ext/object/valid-callable') + , d = require('d') + , WeakMap = require('es6-weak-map') + , resolveLength = require('./resolve-length') + , extensions = require('./registered-extensions') + , resolveResolve = require('./resolve-resolve') + , resolveNormalize = require('./resolve-normalize') + + , slice = Array.prototype.slice, defineProperties = Object.defineProperties + , clearOnDispose; + +clearOnDispose = function () { + throw customError("Clear of Weak Map based configuration is not possible together with " + + " 'dispose' option", 'CLEAR_WITH_DISPOSE'); +}; + +module.exports = function (memoize) { + return function (fn/*, options*/) { + var map, length, options = normalizeOpts(arguments[1]), memoized, resolve, normalizer; + + callable(fn); + length = resolveLength(options.length, fn.length, options.async && extensions.async); + options.length = length ? length - 1 : 0; + map = new WeakMap(); + + if (options.resolvers) resolve = resolveResolve(options.resolvers); + if (options.normalizer) normalizer = resolveNormalize(options.normalizer); + + if ((length === 1) && !normalizer && !(options.async && extensions.async) && + !(options.dispose && extensions.dispose) && !(options.maxAge && extensions.maxAge) && + !(options.max && extensions.max) && !(options.refCounter && extensions.refCounter)) { + return defineProperties(function (obj) { + var result; + if (resolve) obj = resolve(arguments)[0]; + if (map.has(obj)) return map.get(obj); + result = fn.call(this, obj); + if (map.has(obj)) throw customError("Circular invocation", 'CIRCULAR_INVOCATION'); + map.set(obj, result); + return result; + }, { + __memoized__: d(true), + delete: d(function (obj) { + if (resolve) obj = resolve(arguments)[0]; + return map.delete(obj); + }), + clear: d(map.clear.bind(map)) + }); + } + memoized = defineProperties(defineLength(function (obj) { + var memoizer, args = arguments; + if (resolve) { + args = resolve(args); + obj = args[0]; + } + memoizer = map.get(obj); + if (!memoizer) { + if (normalizer) { + options = copy(options); + options.normalizer = copy(normalizer); + options.normalizer.get = partial.call(options.normalizer.get, obj); + options.normalizer.set = partial.call(options.normalizer.set, obj); + if (options.normalizer.delete) { + options.normalizer.delete = partial.call(options.normalizer.delete, obj); + } + } + map.set(obj, memoizer = memoize(partial.call(fn, obj), options)); + } + return memoizer.apply(this, slice.call(args, 1)); + }, length), { + __memoized__: d(true), + delete: d(defineLength(function (obj) { + var memoizer, args = arguments; + if (resolve) { + args = resolve(args); + obj = args[0]; + } + memoizer = map.get(obj); + if (!memoizer) return; + memoizer.delete.apply(this, slice.call(args, 1)); + }, length)), + clear: d(options.dispose ? clearOnDispose : map.clear.bind(map)) + }); + if (!options.refCounter || !extensions.refCounter) return memoized; + defineProperties(memoized, { + deleteRef: d(defineLength(function (obj) { + var memoizer, args = arguments; + if (resolve) { + args = resolve(args); + obj = args[0]; + } + memoizer = map.get(obj); + if (!memoizer) return null; + return memoizer.deleteRef.apply(this, slice.call(args, 1)); + }, length)), + getRefCount: d(defineLength(function (obj) { + var memoizer, args = arguments; + if (resolve) { + args = resolve(args); + obj = args[0]; + } + memoizer = map.get(obj); + if (!memoizer) return 0; + return memoizer.getRefCount.apply(this, slice.call(args, 1)); + }, length)) + }); + return memoized; + }; +}; diff --git a/test/lib/weak.js b/test/lib/weak.js new file mode 100644 index 0000000..be685c4 --- /dev/null +++ b/test/lib/weak.js @@ -0,0 +1,39 @@ +'use strict'; + +var memoize = require('../..'); + +require('../ext/dispose'); +require('../ext/ref-counter'); + +module.exports = function (t, a) { + var value = [], obj = {}, memoized, count = 0, x, y, z; + t = t(memoize); + memoized = t(function (arg, x, y) { a(arg, obj); return x + y; }, + { refCounter: true, dispose: function (val) { value.push(val); } }); + + a(memoized(obj, 3, 7), 10); + a(memoized(obj, 5, 8), 13); + a(memoized(obj, 12, 4), 16); + a.deep(value, [], "Pre"); + a(memoized(obj, 5, 8), 13); + memoized.deleteRef(obj, 5, 8); + a.deep(value, [], "Pre"); + memoized.deleteRef(obj, 5, 8); + a.deep(value, [13], "#1"); + value = []; + memoized.deleteRef(obj, 12, 4); + a.deep(value, [16], "#2"); + + value = []; + memoized(obj, 77, 11); + + x = {}; + y = {}; + z = {}; + memoized = t(function (arg) { return ++count; }); + a(memoized(x), 1); + a(memoized(y), 2); + a(memoized(x), 1); + a(memoized(z), 3); + a(count, 3); +}; diff --git a/test/weak-plain.js b/test/weak-plain.js new file mode 100644 index 0000000..d593fac --- /dev/null +++ b/test/weak-plain.js @@ -0,0 +1,36 @@ +'use strict'; + +require('../ext/dispose'); +require('../ext/ref-counter'); + +module.exports = function (t, a) { + var value = [], obj = {}, memoized, count = 0, x, y, z; + memoized = t(function (arg, x, y) { a(arg, obj); return x + y; }, + { refCounter: true, dispose: function (val) { value.push(val); } }); + + a(memoized(obj, 3, 7), 10); + a(memoized(obj, 5, 8), 13); + a(memoized(obj, 12, 4), 16); + a.deep(value, [], "Pre"); + a(memoized(obj, 5, 8), 13); + memoized.deleteRef(obj, 5, 8); + a.deep(value, [], "Pre"); + memoized.deleteRef(obj, 5, 8); + a.deep(value, [13], "#1"); + value = []; + memoized.deleteRef(obj, 12, 4); + a.deep(value, [16], "#2"); + + value = []; + memoized(obj, 77, 11); + + x = {}; + y = {}; + z = {}; + memoized = t(function (arg) { return ++count; }); + a(memoized(x), 1); + a(memoized(y), 2); + a(memoized(x), 1); + a(memoized(z), 3); + a(count, 3); +}; diff --git a/test/weak.js b/test/weak.js new file mode 100644 index 0000000..b76b797 --- /dev/null +++ b/test/weak.js @@ -0,0 +1,33 @@ +'use strict'; + +module.exports = function (t, a) { + var value = [], obj = {}, memoized, count = 0, x, y, z; + memoized = t(function (arg, x, y) { a(arg, obj); return x + y; }, + { refCounter: true, dispose: function (val) { value.push(val); } }); + + a(memoized(obj, 3, 7), 10); + a(memoized(obj, 5, 8), 13); + a(memoized(obj, 12, 4), 16); + a.deep(value, [], "Pre"); + a(memoized(obj, 5, 8), 13); + memoized.deleteRef(obj, 5, 8); + a.deep(value, [], "Pre"); + memoized.deleteRef(obj, 5, 8); + a.deep(value, [13], "#1"); + value = []; + memoized.deleteRef(obj, 12, 4); + a.deep(value, [16], "#2"); + + value = []; + memoized(obj, 77, 11); + + x = {}; + y = {}; + z = {}; + memoized = t(function (arg) { return ++count; }); + a(memoized(x), 1); + a(memoized(y), 2); + a(memoized(x), 1); + a(memoized(z), 3); + a(count, 3); +}; diff --git a/weak-plain.js b/weak-plain.js new file mode 100644 index 0000000..ed95e45 --- /dev/null +++ b/weak-plain.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./lib/weak')(require('./plain')); diff --git a/weak.js b/weak.js new file mode 100644 index 0000000..c33e408 --- /dev/null +++ b/weak.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./lib/weak')(require('./'));