UNPKG

call-stack

Version:

call-stack module (originally by Finley.Z.M.F)

370 lines (307 loc) 8.66 kB
/** * Expose `Callstack` */ exports = module.exports = Callstack; exports.interface = Interface; /** * This interface function only for the basic properties of a call-stack object. * * @param {String} [name] The call-stack name (default value of 'anonymous'). * @param {Function} [functional] Used to extend the properties and methods of the call-stack. */ function Interface(name, functional) { // Initializing an empty array object, // used to manage the stack layers. var stack = new Array(0); // Initialize the stack pointer to 0. var pointer = 0; // External interfaces. var api = {}; /** * The name of the call-stack. * * @type {String} * @getter */ api.__defineGetter__('name', function() { return name || 'anonymous'; }); /** * The length of the call-stack. * * @type {Number} * @getter */ api.__defineGetter__('length', function() { return stack.length; }); /** * The pointer position of the call-stack. * * @type {Number} * @getter */ api.__defineGetter__('pointer', function() { return pointer; }); /** * This value is true, * if moved pointer to the end of the call-stack. * * @type {Boolean} * @getter */ api.__defineGetter__('isEnd', function() { return stack.length ? pointer + 1 === stack.length : 0 === pointer; }); /** * This value is true, * if the pointer is invalid. * * @type {Boolean} * @getter */ api.__defineGetter__('isInvalid', function() { return stack.length ? 0 > pointer || stack.length <= pointer : 0 !== pointer; }); /** * Configure the stack pointer. * * @type {Number} * @throws {TypeError} If `n` is't a positive number or `n` is invalid for the pointer position * @setter */ api.__defineSetter__('pointer', function(n) { if ('number' !== typeof n || n < 0 || isNaN(n)) { throw TypeError('n must be a positive number'); } try { pointer = n < 0 ? stack.length + n : n; throwInvalid(this); } catch(e) { throw new TypeError('n is invalid.'); } }); /** * The previous call-stack layer. * * The stack pointer is not in the starting position, * referencing the property, this stack pointer will * move one step to the left. * * @type {Mixed} * @getter */ api.__defineGetter__('__previous', function() { return throwInvalid(this, function() { return 0 === pointer ? void 0 : stack[pointer -= 1]; }); }); /** * The current call-stack layer. * * @type {Mixed} * @getter */ api.__defineGetter__('__current', function() { return throwInvalid(this, function() { return stack[pointer]; }); }); /** * The next call-stack layer. * * The stack pointer is not in the starting position, * referencing the property, this stack pointer will * move one step to the right. * * @type {Mixed} * @getter */ api.__defineGetter__('__next', function() { return throwInvalid(this, function() { return this.isEnd ? void 0 : stack[pointer += 1]; }); }); /** * The last call-stack layer * * The stack pointer is not in the starting position, * referencing the property, this stack pointer will * move to the end. * * @type {Mixed} * @getter */ api.__defineGetter__('__end', function() { return throwInvalid(this, function() { return stack.length ? stack[pointer = stack.length - 1] : void 0; }); }); /** * Append the call-stack layer. * * If the call-stack has one or more layers, * this call-stack pointer will move one step to the right. * * @type {Mixed} * @setter */ api.__defineSetter__('__append', function(layer) { return throwInvalid(this, function() { pointer = stack.length; stack.push(layer); }); }); // Extend the call-stack properties. if ('function' === typeof functional) { functional.call(api, stack, pointer, api); } return api; } /** * The factory function to create a new call-stack object. * * @param {String} [name] The call-stack name (default value of 'anonymous'). * @param {Number} [maxLayers] Limit the length of the call-stack layers. * @param {Function} [constructor] The constructor for allocation failure. * @returns {Objact} call-stack */ function Callstack(/*name, maxLayers, constructor*/) { var name, maxLayers, constructor; switch(typeof arguments[0]) { case 'function': constructor = arguments[0]; break; case 'number': maxLayers = arguments[0]; break; case 'string': name = arguments[0]; break; default: name = 'anonymous'; } switch(typeof arguments[1]) { case 'function': constructor = constructor || arguments[1]; break; case 'number': maxLayers = maxLayers || arguments[1]; default: maxLayers = 10; } if (!constructor && 'function' === typeof arguments[2]) { constructor = arguments[2]; } return Interface(name, function(stack, pointer, api) { /** * Limit the length of the call-stack layers. * * @param {Number} n * @throws {TypeError} If n is't a positive number. * @returns {Callstack} */ api.setMaxLayers = function setMaxLayers(n) { if ('number' !== typeof n || n < 0 || isNaN(n)) { throw TypeError('n must be a positive number'); } maxLayers = n; return this; }; /** * See `Callstack.__previous` */ api.previous = function() { return this.__previous; }; /** * See `Callstack.__current` */ api.current = function() { return this.__current; }; /** * See `Callstack.__next` */ api.next = function() { return this.__next; }; /** * See `Callstack.__end` */ api.end = function() { return this.__end; }; /** * See `Callstack.pointer` */ api.reset = function(n) { this.pointer = arguments.length ? 0 : n; }; /** * Append the stack layers. * * @param {Mixed} layer * @returns {Callstack} */ api.push = function push(layer) { if (maxLayers && stack.length > maxLayers) { console.error('warning: possible call-stack memory leak detected.'); console.trace(); } stack.push.apply(stack, arguments); return this; }; /** * By moving the stack pointer to traverse the stack layers. * * Returns false if the iterator will break through. * If we want to continue to walk through, this stack pointer * must move one step to the right. * * @param {Function} iterate * @param {Mixed} context * @throws {TypeError} If `iterate` is't a function. */ api.each = function each(iterate, context) { if ('function' !== typeof iterate) { throw new TypeError('iterate must be a function'); } var iterater = iterate.bind(context || this); while (!this.isEnd) { if (false !== iterater(this.__current, this.pointer, this)) { this.__next; if (this.isEnd) { iterater(this.__current, this.pointer, this); return; } continue; } } }; /** * Allocate stack layers * * When the stack layer assignment is completed, * if you configured a constructor for the call-stack * the parameters of this method could be used on the stack constructor. * * @returns {Mixed} A call-stack layer or a results of ran constructors. */ api.alloc = function alloc() { if (stack.length) { if (pointer > 0) { pointer -= 1; } return stack.shift(); } if ('function' === typeof constructor) { return constructor.apply(this, arguments); } return arguments[0]; }; return api.setMaxLayers(maxLayers); }); } // Pointer goes out of scope, // it will trigger an exception. function throwInvalid(stack, fallback) { if (stack.isInvalid) { var err = new Error('the call-stack pointer is invalid.'); err.name = 'Callstack:' + stack.name; throw err; } if (fallback) { return fallback.call(stack); } };