call-stack
Version:
call-stack module (originally by Finley.Z.M.F)
370 lines (307 loc) • 8.66 kB
JavaScript
/**
* 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);
}
};