From 242b08e091b33e835e9cd7351d2b70645ad458c9 Mon Sep 17 00:00:00 2001 From: Eugene Lazutkin Date: Mon, 29 May 2017 00:55:28 -0500 Subject: [PATCH] Generated dist. --- advise.js | 2 +- dist/advices/counter.js | 29 ++ dist/advices/flow.js | 34 ++ dist/advices/memoize.js | 60 ++++ dist/advices/time.js | 22 ++ dist/advices/trace.js | 35 ++ dist/advise.js | 193 ++++++++++ dist/bases/Mixer.js | 11 + dist/bases/Replacer.js | 16 + dist/dcl.js | 696 +++++++++++++++++++++++++++++++++++++ dist/mixins/Cleanup.js | 38 ++ dist/mixins/Destroyable.js | 9 + package.json | 3 +- 13 files changed, 1146 insertions(+), 2 deletions(-) create mode 100644 dist/advices/counter.js create mode 100644 dist/advices/flow.js create mode 100644 dist/advices/memoize.js create mode 100644 dist/advices/time.js create mode 100644 dist/advices/trace.js create mode 100644 dist/advise.js create mode 100644 dist/bases/Mixer.js create mode 100644 dist/bases/Replacer.js create mode 100644 dist/dcl.js create mode 100644 dist/mixins/Cleanup.js create mode 100644 dist/mixins/Destroyable.js diff --git a/advise.js b/advise.js index 3141977..7911ad1 100644 --- a/advise.js +++ b/advise.js @@ -1,4 +1,4 @@ -/* UMD.define */ (typeof define=='function'&&define||function(d,f,m){m={module:module,require:require};module.exports=f.apply(null,d.map(function(n){return m[n]||require(n)}))}) +/* UMD.define */ (typeof define=="function"&&define||function(d,f,m){m={module:module,require:require};module.exports=f.apply(null,d.map(function(n){return m[n]||require(n)}))}) ([], function () { 'use strict'; diff --git a/dist/advices/counter.js b/dist/advices/counter.js new file mode 100644 index 0000000..fecac68 --- /dev/null +++ b/dist/advices/counter.js @@ -0,0 +1,29 @@ +(function(_,f,g){g=window.dcl;g=g.advices||(g.advices={});g.counter=f(window.dcl);}) +(['../dcl'], function (dcl) { + 'use strict'; + + var Counter = new dcl(null, { + declaredClass: 'dcl/advices/counter/Counter', + constructor: function () { + this.reset(); + }, + reset: function () { + this.calls = this.errors = 0; + }, + advice: function () { + var self = this; + return { + before: function () { + ++self.calls; + }, + after: function (args, result) { + if (result instanceof Error) { + ++self.errors; + } + } + }; + } + }); + + return function(){ return new Counter; }; +}); diff --git a/dist/advices/flow.js b/dist/advices/flow.js new file mode 100644 index 0000000..a22def8 --- /dev/null +++ b/dist/advices/flow.js @@ -0,0 +1,34 @@ +(function(_,f,g){g=window;g=g.dcl||(g.dcl={});g=g.advices||(g.advices={});g.flow=f();}) +([], function () { + 'use strict'; + + var flowStack = [], flowCount = {}; + + return { + advice: function (name) { + return { + before: function () { + flowStack.push(name); + if (flowCount[name]) { + ++flowCount[name]; + } else { + flowCount[name] = 1; + } + }, + after: function () { + --flowCount[name]; + flowStack.pop(); + } + }; + }, + inFlowOf: function (name) { + return flowCount[name]; + }, + getStack: function () { + return flowStack; + }, + getCount: function () { + return flowCount; + } + }; +}); diff --git a/dist/advices/memoize.js b/dist/advices/memoize.js new file mode 100644 index 0000000..328052e --- /dev/null +++ b/dist/advices/memoize.js @@ -0,0 +1,60 @@ +(function(_,f,g){g=window;g=g.dcl||(g.dcl={});g=g.advices||(g.advices={});g.memoize=f();}) +([], function () { + 'use strict'; + + return { + advice: function (name, keyMaker) { + return keyMaker ? + { + around: function (sup) { + return function () { + var key = keyMaker(this, arguments), cache = this.__memoizerCache, dict; + if (!cache) { + cache = this.__memoizerCache = {}; + } + if (cache.hasOwnProperty(name)) { + dict = cache[name]; + } else { + dict = cache[name] = {}; + } + if (dict.hasOwnProperty(key)) { + return dict[key]; + } + return dict[key] = sup ? sup.apply(this, arguments) : void 0; + }; + } + } : + { + around: function (sup) { + return function (first) { + var cache = this.__memoizerCache, dict; + if (!cache) { + cache = this.__memoizerCache = {}; + } + if (cache.hasOwnProperty(name)) { + dict = cache[name]; + } else { + dict = cache[name] = {}; + } + if (dict.hasOwnProperty(first)) { + return dict[first]; + } + return dict[first] = sup ? sup.apply(this, arguments) : undefined; + }; + } + }; + }, + guard: function (name) { + return { + after: function () { + var cache = this.__memoizerCache; + if (cache && name) { + delete cache[name]; + } else { + this.__memoizerCache = {}; + } + } + }; + } + }; +}); diff --git a/dist/advices/time.js b/dist/advices/time.js new file mode 100644 index 0000000..2f7749d --- /dev/null +++ b/dist/advices/time.js @@ -0,0 +1,22 @@ +(function(_,f,g){g=window;g=g.dcl||(g.dcl={});g=g.advices||(g.advices={});g.time=f();}) +([], function () { + 'use strict'; + + var uniq = 0; + + return function (name) { + var inCall = 0, label = name || ('Timer #' + uniq++); + return { + before: function () { + if (!(inCall++)) { + console.time(label); + } + }, + after: function () { + if (!--inCall) { + console.timeEnd(label); + } + } + }; + }; +}); diff --git a/dist/advices/trace.js b/dist/advices/trace.js new file mode 100644 index 0000000..e2a8174 --- /dev/null +++ b/dist/advices/trace.js @@ -0,0 +1,35 @@ +(function(_,f,g){g=window;g=g.dcl||(g.dcl={});g=g.advices||(g.advices={});g.trace=f();}) +([], function () { + 'use strict'; + + var lvl = 0; + + function rep (ch, n) { + if (n < 1) { return ''; } + if (n == 1) { return ch; } + var h = rep(ch, Math.floor(n / 2)); + return h + h + ((n & 1) ? ch : ''); + + } + + function pad (value, width, ch) { + var v = value.toString(); + return v + rep(ch || ' ', width - v.length); + } + + return function (name, level) { + return { + before: function () { + ++lvl; + console.log((level ? pad(lvl, 2 * lvl) : '') + this + ' => ' + + name + '(' + Array.prototype.join.call(arguments, ', ') + ')'); + }, + after: function (args, result) { + console.log((level ? pad(lvl, 2 * lvl) : '') + this + ' => ' + + name + (result && result instanceof Error ? ' throws' : ' returns') + + ' ' + result); + --lvl; + } + }; + }; +}); diff --git a/dist/advise.js b/dist/advise.js new file mode 100644 index 0000000..030e10b --- /dev/null +++ b/dist/advise.js @@ -0,0 +1,193 @@ +(function(_,f){window.advise=f();}) +([], function () { + 'use strict'; + + function Node (parent) { + this.parent = parent || this; + } + + Node.prototype = { + removeTopic: function (topic) { + var n = 'next_' + topic, p = 'prev_' + topic; + if (this[n] && this[p]) { + this[n][p] = this[p]; + this[p][n] = this[n]; + } + }, + remove: function () { + this.removeTopic('before'); + this.removeTopic('around'); + + // remove & recreate around advices + var parent = this.parent, next = this.next_around; + this.removeTopic('after'); + for (; next && next !== parent; next = next.next_around) { + next.around = next.originalAround(next.prev_around.around); + } + }, + addTopic: function (node, topic) { + var n = 'next_' + topic, p = 'prev_' + topic, + prev = node[p] = this[p] || this; + node[n] = this; + prev[n] = this[p] = node; + }, + addAdvice: function (advice) { + var node = new Node(this); + if (advice.before) { + node.before = advice.before; + this.addTopic(node, 'before'); + } + if (advice.around) { + node.originalAround = advice.around; + this.addTopic(node, 'around'); + node.around = advice.around(node.prev_around.around || null); + } + if (advice.after) { + node.after = advice.after; + this.addTopic(node, 'after'); + } + return node; + } + }; + + Node.prototype.unadvise = Node.prototype.remove; + + function addNode (root, topic) { + return function (f) { + var node = new Node(root); + node[topic] = f; + root.addTopic(node, topic); + }; + } + + function makeStub (value) { + var root = new Node(); + if (value) { + if (typeof value.advices == 'object') { + var advices = value.advices; + advices.before.forEach(addNode(root, 'before')); + advices.after. forEach(addNode(root, 'after')); + advices.around && addNode(root, 'around')(advices.around); + } else { + addNode(root, 'around')(value); + } + } + function stub () { + var result, thrown, p; + // running the before chain + for (p = root.prev_before; p && p !== root; p = p.prev_before) { + p.before.apply(this, arguments); + } + // running the around chain + if (root.prev_around && root.prev_around !== root) { + try { + result = root.prev_around.around.apply(this, arguments); + } catch (error) { + result = error; + thrown = true; + } + } + // running the after chain + for (p = root.next_after; p && p !== root; p = p.next_after) { + p.after.call(this, arguments, result, makeReturn, makeThrow); + } + if (thrown) { + throw result; + } + return result; + + function makeReturn (value) { result = value; thrown = false; } + function makeThrow (value) { result = value; thrown = true; } + }; + stub.node = root; + return stub; + } + + function convert (value, advice, instance, name, type) { + if (!value || !(value.node instanceof Node)) { + value = makeStub(value); + value.node.instance = instance; + value.node.name = name; + value.node.type = type; + } + var node = value.node.addAdvice(advice); + return {value: value, handle: node}; + } + + function combineHandles (handles) { + var handle = { + remove: function () { + handles.forEach(function (handle) { handle.remove(); }); + } + } + handle.unadvise = handle.remove; + return handle; + } + + function advise (instance, name, advice) { + var prop = getPropertyDescriptor(instance, name), handles = []; + if (prop) { + if (prop.get || prop.set) { + var result; + if (prop.get && advice.get) { + result = convert(prop.get, advice.get, instance, name, 'get'); + prop.get = result.value; + handles.push(result.handle); + } + if (prop.set && advice.set) { + result = convert(prop.set, advice.set, instance, name, 'set'); + prop.set = result.value; + handles.push(result.handle); + } + } else { + if (prop.value && advice) { + result = convert(prop.value, advice, instance, name, 'value'); + prop.value = result.value; + handles.push(result.handle); + } + } + } else { + prop = {writable: true, configurable: true, enumerable: true}; + if (advice.get || advice.set) { + if (advice.get) { + result = convert(null, advice.get, instance, name, 'get'); + prop.get = result.value; + handles.push(result.handles); + } + if (advice.set) { + result = convert(null, advice.set, instance, name, 'set'); + prop.set = result.value; + handles.push(result.handles); + } + } else { + result = convert(null, advice, instance, name, 'value'); + prop.value = result.value; + handles.push(result.handles); + } + } + Object.defineProperty(instance, name, prop); + return combineHandles(handles); + } + + // export + + advise.before = function(instance, name, f){ return advise(instance, name, {before: f}); }; + advise.after = function(instance, name, f){ return advise(instance, name, {after: f}); }; + advise.around = function(instance, name, f){ return advise(instance, name, {around: f}); }; + advise.Node = Node; + + advise._instantiate = function(advice, previous, node){ return advice(previous); }; + + return advise; + + // copied from dcl.js so we can be independent + function getPropertyDescriptor (o, name) { + while (o && o !== Object.prototype) { + if (o.hasOwnProperty(name)) { + return Object.getOwnPropertyDescriptor(o, name); + } + o = Object.getPrototypeOf(o); + } + return null; + } +}); diff --git a/dist/bases/Mixer.js b/dist/bases/Mixer.js new file mode 100644 index 0000000..57b8310 --- /dev/null +++ b/dist/bases/Mixer.js @@ -0,0 +1,11 @@ +(function(_,f,g){g=window.dcl;g=g.bases||(g.bases={});g.Mixer=f(window.dcl);}) +(['../dcl'], function (dcl) { + 'use strict'; + + return dcl(null, { + declaredClass: 'dcl/bases/Mixer', + constructor: function (x) { + Object.defineProperties(this, dcl.populatePropsNative({}, x)); + } + }); +}); diff --git a/dist/bases/Replacer.js b/dist/bases/Replacer.js new file mode 100644 index 0000000..41efb34 --- /dev/null +++ b/dist/bases/Replacer.js @@ -0,0 +1,16 @@ +(function(_,f,g){g=window.dcl;g=g.bases||(g.bases={});g.Replacer=f(window.dcl);}) +(['../dcl'], function (dcl) { + 'use strict'; + + return dcl(null, { + declaredClass: 'dcl/bases/Replacer', + constructor: function (x) { + var props = dcl.populatePropsNative({}, x); + Object.keys(props).forEach(function (name) { + if (name in this) { + Object.defineProperty(this, name, props[name]); + } + }, this); + } + }); +}); diff --git a/dist/dcl.js b/dist/dcl.js new file mode 100644 index 0000000..1b82c35 --- /dev/null +++ b/dist/dcl.js @@ -0,0 +1,696 @@ +(function(_,f){window.dcl=f();}) +([], function () { + 'use strict'; + + // set up custom names + var mname = '_meta', pname = 'prototype', cname = 'constructor'; + + + // MODULE: restricted Map shim (used by C3 MRO) + + var M; // our map implementation if not defined + if (typeof Map == 'undefined') { + // our fake, inefficient, incomplete, yet totally correct Map + M = function () { + this.list = []; + this.size = 0; + }; + M.prototype = { + has: function (key) { return this.get(key); }, + get: function (key) { + for (var i = 0, n = this.list.length; i < n; i += 2) { + if (key === this.list[i]) { + return this.list[i + 1]; + } + } + // returns undefined if not found + }, + set: function (key, value) { + for (var i = 0, n = this.list.length; i < n; i += 2) { + if (key === this.list[i]) { + this.list[i + 1] = value; + return this; + } + } + this.list.push(key, value); + ++this.size; + return this; + } + }; + } else { + M = Map; + } + + + // MODULE: C3 MRO + + function c3mro (bases) { + // build a connectivity matrix + var connectivity = new M(); + bases.forEach(function (base) { + (base[mname] ? base[mname].bases : [base]).forEach(function (base, index, array) { + if (connectivity.has(base)) { + var value = connectivity.get(base); + ++value.counter; + if (index) { + value.links.push(array[index - 1]); + } + } else { + connectivity.set(base, { + links: index ? [array[index - 1]] : [], + counter: index + 1 == array.length ? 0 : 1 + }); + } + }); + }); + // Kahn's algorithm + var output = [], unreferenced = []; + // find unreferenced bases + bases.forEach(function (base) { + var last = base[mname] ? base[mname].bases[base[mname].bases.length - 1] : base; + if (!connectivity.get(last).counter) { + unreferenced.push(last); + } + }); + while (unreferenced.length) { + var base = unreferenced.pop(); + output.push(base); + var value = connectivity.get(base); + value.links.forEach(updateCounter); + } + // final checks and return + if (connectivity.size != output.length) { + dcl._error('cycle'); + } + return output; + + function updateCounter (base) { + var value = connectivity.get(base); + if (!--value.counter) { + unreferenced.push(base); + } + } + } + + + // MODULE: handling properties + + function updateProps (props, defaults, augmentDescriptor, augmentWritable) { + if ('configurable' in defaults) { + props = props.map(augmentDescriptor('configurable', defaults.configurable)); + } + if ('enumerable' in defaults) { + props = props.map(augmentDescriptor('enumerable', defaults.enumerable)); + } + if ('writable' in defaults) { + props = props.map(augmentWritable(defaults.writable)); + } + return props; + } + + var descriptorProperties = {configurable: 1, enumerable: 1, value: 1, writable: 1, get: 1, set: 1}; + + function toProperties(x, defaults) { + var props, descriptors; + if (x instanceof dcl.Prop) { + props = x.x; + } else { + Object.getOwnPropertyNames(x).forEach(function(key) { + var prop = Object.getOwnPropertyDescriptor(x, key); + if (prop.get || prop.set) { + // accessor descriptor + descriptors = descriptors || {}; + descriptors[key] = prop; + } else { + // data descriptor + var value = prop.value; + if (value instanceof dcl.Prop) { + props = props || {}; + props[key] = value.x; + } else { + if (defaults.detectProps && value && typeof value == 'object') { + if (Object.keys(value).every(function (name) { return descriptorProperties[name] === 1; })) { + props = props || {}; + props[key] = value; + return; + } + } + descriptors = descriptors || {}; + descriptors[key] = prop; + } + } + }); + } + if (props) { + props = updateProps(props, defaults, augmentDescriptor, augmentWritable); + } + if (descriptors) { + descriptors = updateProps(descriptors, defaults, replaceDescriptor, replaceWritable); + } + if (descriptors && props) { + Object.keys(props).forEach(function(key) { + descriptors[key] = props[key]; + }); + } + return descriptors || props || {}; + } + + function cloneDescriptor (descriptor) { // shallow copy + var newDescriptor = {}; + Object.keys(descriptor).forEach(function (name) { + newDescriptor[name] = descriptor[name]; + }); + return newDescriptor; + } + + function augmentDescriptor(name, value) { + return typeof value == 'function' ? value(name) : function(descriptor) { + if (!descriptor.hasOwnProperty(name)) { + descriptor[name] = value; + } + }; + } + + function augmentWritable(value) { + return typeof value == 'function' ? value : function(descriptor) { + if (!descriptor.get && !descriptor.set && !descriptor.hasOwnProperty('writable')) { + descriptor.writable = value; + } + }; + } + + function replaceDescriptor(name, value) { + return typeof value == 'function' ? value(name) : function(descriptor) { + descriptor[name] = value; + }; + } + + function replaceWritable(value) { + return typeof value == 'function' ? value : function(descriptor) { + if (descriptor.hasOwnProperty('value')) { + descriptor.writable = value; + } + }; + } + + function getPropertyDescriptor (o, name) { + while (o && o !== Object.prototype) { + if (o.hasOwnProperty(name)) { + return Object.getOwnPropertyDescriptor(o, name); + } + o = Object.getPrototypeOf(o); + } + return null; + } + + + // MODULE: produce properties + + function recordProp(props, o, recorded) { + return function (name) { + if (recorded[name] !== 1) { + recorded[name] = 1; + props[name] = Object.getOwnPropertyDescriptor(o, name); + } + }; + } + + function populatePropsNative (props, o) { + var recorded = {}; + while (o && o !== Object.prototype) { + Object.getOwnPropertyNames(o).forEach(recordProp(props, o, recorded)); + o = Object.getPrototypeOf(o); + } + return props; + } + + // populate properties with simple properties + function populateProps (props, mixins, special) { + var newSpecial = {}; + mixins.forEach(function (base) { + // copy properties for dcl objects + if (base[mname]) { + var baseProps = base[mname].props; + Object.keys(baseProps).forEach(function (name) { + if (special.hasOwnProperty(name)) { + newSpecial[name] = special[name]; + } else { + props[name] = baseProps[name]; + } + }); + return; + } + // copy properties for regular objects + populatePropsNative(props, base[pname]); + }); + return newSpecial; + } + + var empty = {}; + + function weaveProp (name, bases, weaver) { + var state = {prop: null, backlog: []}; + weaver.start && weaver.start(state); + bases.forEach(function (base) { + var prop; + if (base[mname]) { + var baseProps = base[mname].props; + if (baseProps.hasOwnProperty(name)) { + prop = baseProps[name]; + } + } else { + prop = getPropertyDescriptor(base[pname], name); + if (!prop && name == cname) { + prop = {configurable: true, enumerable: false, writable: true, value: base}; + } + } + if (!prop) { + return; + } + var newProp = cloneDescriptor(prop), prevProp; + if (prop.get || prop.set) { + // accessor + var superGet = isSuper(prop.get) && prop.get.spr.around, + superSet = isSuper(prop.set) && prop.set.spr.around; + if (superGet || superSet) { + processBacklog(state, weaver); + prevProp = state.prop; + } + if (superGet) { + if (prevProp) { + newProp.get = prop.get.spr.around( + prevProp.get || prevProp.set ? + prevProp.get : adaptValue(prevProp.value)); + } else { + newProp.get = prop.get.spr.around(null); + } + state.prop = null; + } + if (superSet) { + newProp.set = prop.set.spr.around(prevProp && prevProp.set); + state.prop = null; + } + if ((!prop.get || isSuper(prop.get) && !prop.get.spr.around) && (!prop.set || isSuper(prop.set) && !prop.set.spr.around)) { + return; // skip descriptor: no actionable value + } + } else { + // data + if (isSuper(prop.value) && prop.value.spr.around) { + processBacklog(state, weaver); + prevProp = state.prop; + if (prevProp) { + newProp.value = prop.value.spr.around( + prevProp.get || prevProp.set ? + adaptGet(prevProp.get) : prevProp.value); + } else { + newProp.value = prop.value.spr.around(name !== cname && empty[name]); + } + state.prop = null; + } + if (!prop.value || isSuper(prop.value) && !prop.value.spr.around) { + return; // skip descriptor: no actionable value + } + } + if (state.prop) { + if (newProp.get || newProp.set) { + if (state.backlog.length) { + state.backlog = []; + } + } else { + state.backlog.push(convertToValue(state.prop)); + } + } + state.prop = newProp; + }); + processBacklog(state, weaver); + return weaver.stop ? weaver.stop(state) : state.prop; + } + + var dclUtils = {adaptValue: adaptValue, adaptGet: adaptGet, + convertToValue: convertToValue, cloneDescriptor: cloneDescriptor, + augmentDescriptor: augmentDescriptor, augmentWritable: augmentWritable, + replaceDescriptor: replaceDescriptor, replaceWritable: replaceWritable + }; + + function processBacklog (state, weaver) { + if (state.backlog.length) { + state.backlog.push(convertToValue(state.prop)); + state.prop = weaver.weave(state.backlog, dclUtils); + state.backlog = []; + } + } + + function adaptValue (f) { + return f ? function () { return f; } : null; + } + + function adaptGet (f) { + return f ? function () { return f.call(this).apply(this, arguments); } : null; + } + + function convertToValue (prop) { + if (prop.get || prop.set) { + var newProp = cloneDescriptor(prop); + delete newProp.get; + delete newProp.set; + newProp.value = adaptGet(prop.get); + return newProp; + } + return prop; + } + + + // dcl helpers + + function getAccessorSideAdvices (name, bases, propName) { + var before = [], after = []; + bases.forEach(function (base) { + if (base[mname]) { + var prop = base[mname].props[name]; + if (typeof prop == 'object') { + if (prop.get || prop.set) { + var f = prop[propName]; + if (isSuper(f)) { + f.spr.before && before.push(f.spr.before); + f.spr.after && after .push(f.spr.after); + } + } + } + } + }); + if (before.length > 1) { before.reverse(); } + return {before: before, after: after}; + } + + function getDataSideAdvices (name, bases) { + var before = [], after = []; + bases.forEach(function (base) { + if (base[mname]) { + var prop = base[mname].props[name]; + if (typeof prop == 'object') { + var f = prop.get || prop.set ? prop.get : prop.value; + if (isSuper(f)) { + f.spr.before && before.push(f.spr.before); + f.spr.after && after .push(f.spr.after); + } + } + } + }); + if (before.length > 1) { before.reverse(); } + return {before: before, after: after}; + } + + function makeStub (aroundStub, beforeChain, afterChain) { + var beforeLength = beforeChain.length, afterLength = afterChain.length, + stub = function () { + var result, thrown, i; + // running the before chain + for (i = 0; i < beforeLength; ++i) { + beforeChain[i].apply(this, arguments); + } + // running the around chain + if (aroundStub) { + try { + result = aroundStub.apply(this, arguments); + } catch (error) { + result = error; + thrown = true; + } + } + // running the after chain + for (i = 0; i < afterLength; ++i) { + afterChain[i].call(this, arguments, result, makeReturn, makeThrow); + } + if (thrown) { + throw result; + } + return result; + + function makeReturn (value) { result = value; thrown = false; } + function makeThrow (value) { result = value; thrown = true; } + }; + stub.advices = {around: aroundStub, before: beforeChain, after: afterChain}; + return stub; + } + + function makeCtr (baseClass, finalProps, meta) { + var proto = baseClass ? + Object.create(baseClass[pname], finalProps) : + Object.defineProperties({}, finalProps), + ctr = proto[cname]; + + ctr[mname] = meta; + ctr[pname] = proto; + meta.bases[meta.bases.length - 1] = ctr; + + return ctr; + } + + + // MODULE: weavers + + function weaveAround (chain, utils) { + var newProp = utils.cloneDescriptor(chain[chain.length - 1]); + + if (newProp.get || newProp.set) { + // convert to value + newProp.value = utils.adaptGet(newProp.get); + delete newProp.get; + delete newProp.set; + } + + return newProp; + } + + function weaveChain (chain, utils) { + if (this.reverse) { + chain.reverse(); + } + + var newProp = utils.cloneDescriptor(chain[chain.length - 1]); + + // extract functions + chain = chain.map(function (prop) { + return prop.get || prop.set ? utils.adaptGet(prop.get) : prop.value; + }); + + newProp.value = function () { + for (var i = 0; i < chain.length; ++i) { + chain[i].apply(this, arguments); + } + }; + + return newProp; + } + + + // MODULE: dcl (the main function) + + function dcl (superClass, props, options) { + // parse arguments + if (superClass instanceof Array) { + // skip + } else if (typeof superClass == 'function') { + superClass = [superClass]; + } else if (!superClass) { + superClass = []; + } else { + options = props; + props = superClass; + superClass = []; + } + props = toProperties(props || {}, options || {}); + + // find the base class, and mixins + var bases = [], baseClass = superClass[0], mixins = [], baseIndex = -1; + if (superClass.length) { + if (superClass.length > 1) { + bases = c3mro(superClass).reverse(); + if (baseClass[mname]) { + baseIndex = baseClass[mname].bases.length - 1; + } else { + mixins = bases.slice(1); + baseIndex = 0; + } + } else { + if (baseClass[mname]) { + bases = baseClass[mname].bases.slice(0); + baseIndex = bases.length - 1; + } else { + bases = [baseClass]; + baseIndex = 0; + } + } + if (bases[baseIndex] === baseClass) { + mixins = bases.slice(baseIndex + 1); + } else { + baseClass = null; + mixins = bases.slice(0); + } + } + + // add a stand-in for our future constructor + var faux = {}, special = {}; + faux[mname] = {bases: bases, props: props, special: special}; + dcl.chainAfter(faux, cname); + bases.push(faux); + mixins.push(faux); + + // collect meta + + // merge from bases + superClass.forEach(function (base) { + if (base[mname]) { + var baseSpecial = base[mname].special; + Object.keys(baseSpecial).forEach(function (name) { + dcl.chainWith(faux, name, baseSpecial[name]); + }); + } + }); + + // inspect own props + Object.keys(props).forEach(function (name) { + if (!special.hasOwnProperty(name)) { + var prop = props[name]; + if (prop.get || prop.set) { + if (isSuper(prop.get) || isSuper(prop.set)) { + dcl.chainWith(faux, name, dcl.weaveSuper); + } + } else { + if (isSuper(prop.value)) { + dcl.chainWith(faux, name, dcl.weaveSuper); + } + } + } + }); + + // collect simple props, and a list of special props + var finalProps = {}, finalSpecial = populateProps(finalProps, mixins, special); + if (!finalSpecial.hasOwnProperty(cname)) { + finalSpecial[cname] = special.hasOwnProperty(cname) ? special[cname] : dcl.weaveAfter; + } + + // process special props + Object.keys(finalSpecial).forEach(function (name) { + var prop = weaveProp(name, bases, finalSpecial[name]); + if (!prop) { + prop = {configurable: true, enumerable: false, writable: true, value: function nop () {}}; + } + var newProp = cloneDescriptor(prop), advices; + if (prop.get || prop.set) { + // accessor descriptor + advices = getAccessorSideAdvices(name, bases, 'get'); + newProp.get = dcl._makeStub(prop.get, advices.before, advices.after); + advices = getAccessorSideAdvices(name, bases, 'set'); + newProp.set = dcl._makeStub(prop.set, advices.before, advices.after); + } else { + // data descriptor + advices = getDataSideAdvices(name, bases); + var stub = dcl._makeStub(prop.value, advices.before, advices.after); + advices = getAccessorSideAdvices(name, bases, 'set'); + stub.advices.set = advices; + newProp.value = stub; + } + finalProps[name] = newProp; + }); + + return dcl._makeCtr(baseClass, finalProps, faux[mname]); + } + + + // build export + + // guts, do not use them! + + dcl._error = function (msg) { + throw new Error(msg); + }; + dcl._makeSuper = makeSuper; + dcl._makeCtr = makeCtr; + dcl._makeStub = makeStub; + + // utilities + + dcl.populatePropsNative = populatePropsNative; + dcl.getPropertyDescriptor = getPropertyDescriptor; + + // meta + + dcl.Prop = function Prop (x) { this.x = x; }; + dcl.prop = function prop(x) { return new dcl.Prop(x); }; + + dcl.isInstanceOf = function isInstanceOf (o, ctr) { + if (o instanceof ctr) { + return true; + } + if (o && o[cname] && o[cname][mname]) { + for (var bases = o[cname][mname].bases, i = bases.length - 2; i >= 0; --i) { + var base = bases[i]; + if (base === ctr || !base[mname] && base[pname] instanceof ctr) { + return true; + } + } + } + return false; + }; + + // chains + + function chainWith (ctr, name, weaver) { + if (ctr && ctr[mname]) { + var special = ctr[mname].special; + if (special.hasOwnProperty(name)) { + var own = special[name]; + if (own === weaver || own.name === weaver.name || weaver.name === 'super') { + return true; + } + if (own.name !== 'super') { + dcl._error('different weavers: ' + name); + } + } + special[name] = weaver; + return true; + } + return false; + } + + dcl.weaveBefore = {name: 'before', weave: weaveChain, reverse: true}; + dcl.weaveAfter = {name: 'after', weave: weaveChain}; + dcl.weaveSuper = {name: 'super', weave: weaveAround}; + + dcl.chainWith = chainWith; + dcl.chainBefore = function (ctr, name) { return dcl.chainWith(ctr, name, dcl.weaveBefore); }; + dcl.chainAfter = function (ctr, name) { return dcl.chainWith(ctr, name, dcl.weaveAfter); }; + + // super & AOP + + function makeSuper (advice, S) { + var f = function superNop () {}; + f.spr = new S(advice); + return f; + } + + function isSuper (f) { return f && f.spr instanceof dcl.Super; } + + dcl.Super = function Super (f) { this.around = f; }; + dcl.isSuper = isSuper; + + dcl.Advice = dcl(dcl.Super, { + declaredClass: "dcl.Advice", + constructor: function () { + this.before = this.around.before; + this.after = this.around.after; + this.around = this.around.around; + } + }); + + dcl.advise = function (advice) { return dcl._makeSuper(advice, dcl.Advice); }; + + dcl.superCall = dcl.around = function (f) { return dcl._makeSuper(f, dcl.Super); }; + dcl.before = function (f) { return dcl.advise({before: f}); }; + dcl.after = function (f) { return dcl.advise({after: f}); }; + + // export + + return dcl; +}); diff --git a/dist/mixins/Cleanup.js b/dist/mixins/Cleanup.js new file mode 100644 index 0000000..121265c --- /dev/null +++ b/dist/mixins/Cleanup.js @@ -0,0 +1,38 @@ +(function(_,f){window.dcl.mixins.Cleanup=f(window.dcl,window.dcl.mixins.Destroyable);}) +(['../dcl', './Destroyable'], function (dcl, Destroyable) { + 'use strict'; + + return dcl(Destroyable, { + declaredClass: 'dcl/mixins/Cleanup', + constructor: function () { + this.__cleanupStack = []; + }, + pushCleanup: function (resource, cleanup) { + var f = cleanup ? function () { cleanup(resource); } : function () { resource.destroy(); }; + this.__cleanupStack.push(f); + return f; + }, + popCleanup: function (dontRun) { + if (dontRun) { + return this.__cleanupStack.pop(); + } + this.__cleanupStack.pop()(); + }, + removeCleanup: function (f) { + for (var i = this.__cleanupStack.length - 1; i >= 0; --i) { + if (this.__cleanupStack[i] === f) { + this.__cleanupStack.splice(i, 1); + return true; + } + } + }, + cleanup: function () { + while (this.__cleanupStack.length) { + this.__cleanupStack.pop()(); + } + }, + destroy: function () { + this.cleanup(); + } + }); +}); diff --git a/dist/mixins/Destroyable.js b/dist/mixins/Destroyable.js new file mode 100644 index 0000000..ffa1e34 --- /dev/null +++ b/dist/mixins/Destroyable.js @@ -0,0 +1,9 @@ +(function(_,f,g){g=window.dcl;g=g.mixins||(g.mixins={});g.Destroyable=f(window.dcl);}) +(['../dcl'], function (dcl) { + 'use strict'; + + var Destroyable = dcl(null, {declaredClass: 'dcl/mixins/Destroyable'}); + dcl.chainBefore(Destroyable, 'destroy'); + + return Destroyable; +}); diff --git a/package.json b/package.json index d72437d..0bec0c1 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ ], "browserGlobals": { "!root": "dcl", - "./dcl": "dcl" + "./dcl": "dcl", + "./advise": "advise" } }