diff options
Diffstat (limited to 'toolkit/jetpack/method/core.js')
-rw-r--r-- | toolkit/jetpack/method/core.js | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/toolkit/jetpack/method/core.js b/toolkit/jetpack/method/core.js new file mode 100644 index 000000000..a6a5261e6 --- /dev/null +++ b/toolkit/jetpack/method/core.js @@ -0,0 +1,225 @@ +"use strict"; + +var defineProperty = Object.defineProperty || function(object, name, property) { + object[name] = property.value + return object +} + +// Shortcut for `Object.prototype.toString` for faster access. +var typefy = Object.prototype.toString + +// Map to for jumping from typeof(value) to associated type prefix used +// as a hash in the map of builtin implementations. +var types = { "function": "Object", "object": "Object" } + +// Array is used to save method implementations for the host objects in order +// to avoid extending them with non-primitive values that could cause leaks. +var host = [] +// Hash map is used to save method implementations for builtin types in order +// to avoid extending their prototypes. This also allows to share method +// implementations for types across diff contexts / frames / compartments. +var builtin = {} + +function Primitive() {} +function ObjectType() {} +ObjectType.prototype = new Primitive() +function ErrorType() {} +ErrorType.prototype = new ObjectType() + +var Default = builtin.Default = Primitive.prototype +var Null = builtin.Null = new Primitive() +var Void = builtin.Void = new Primitive() +builtin.String = new Primitive() +builtin.Number = new Primitive() +builtin.Boolean = new Primitive() + +builtin.Object = ObjectType.prototype +builtin.Error = ErrorType.prototype + +builtin.EvalError = new ErrorType() +builtin.InternalError = new ErrorType() +builtin.RangeError = new ErrorType() +builtin.ReferenceError = new ErrorType() +builtin.StopIteration = new ErrorType() +builtin.SyntaxError = new ErrorType() +builtin.TypeError = new ErrorType() +builtin.URIError = new ErrorType() + + +function Method(hint) { + /** + Private Method is a callable private name that dispatches on the first + arguments same named Method: + + method(object, ...rest) => object[method](...rest) + + Optionally hint string may be provided that will be used in generated names + to ease debugging. + + ## Example + + var foo = Method() + + // Implementation for any types + foo.define(function(value, arg1, arg2) { + // ... + }) + + // Implementation for a specific type + foo.define(BarType, function(bar, arg1, arg2) { + // ... + }) + **/ + + // Create an internal unique name if `hint` is provided it is used to + // prefix name to ease debugging. + var name = (hint || "") + "#" + Math.random().toString(32).substr(2) + + function dispatch(value) { + // Method dispatches on type of the first argument. + // If first argument is `null` or `void` associated implementation is + // looked up in the `builtin` hash where implementations for built-ins + // are stored. + var type = null + var method = value === null ? Null[name] : + value === void(0) ? Void[name] : + // Otherwise attempt to use method with a generated private + // `name` that is supposedly in the prototype chain of the + // `target`. + value[name] || + // Otherwise assume it's one of the built-in type instances, + // in which case implementation is stored in a `builtin` hash. + // Attempt to find a implementation for the given built-in + // via constructor name and method name. + ((type = builtin[(value.constructor || "").name]) && + type[name]) || + // Otherwise assume it's a host object. For host objects + // actual method implementations are stored in the `host` + // array and only index for the implementation is stored + // in the host object's prototype chain. This avoids memory + // leaks that otherwise could happen when saving JS objects + // on host object. + host[value["!" + name] || void(0)] || + // Otherwise attempt to lookup implementation for builtins by + // a type of the value. This basically makes sure that all + // non primitive values will delegate to an `Object`. + ((type = builtin[types[typeof(value)]]) && type[name]) + + + // If method implementation for the type is still not found then + // just fallback for default implementation. + method = method || Default[name] + + + // If implementation is still not found (which also means there is no + // default) just throw an error with a descriptive message. + if (!method) throw TypeError("Type does not implements method: " + name) + + // If implementation was found then just delegate. + return method.apply(method, arguments) + } + + // Make `toString` of the dispatch return a private name, this enables + // method definition without sugar: + // + // var method = Method() + // object[method] = function() { /***/ } + dispatch.toString = function toString() { return name } + + // Copy utility methods for convenient API. + dispatch.implement = implementMethod + dispatch.define = defineMethod + + return dispatch +} + +// Create method shortcuts form functions. +var defineMethod = function defineMethod(Type, lambda) { + return define(this, Type, lambda) +} +var implementMethod = function implementMethod(object, lambda) { + return implement(this, object, lambda) +} + +// Define `implement` and `define` polymorphic methods to allow definitions +// and implementations through them. +var implement = Method("implement") +var define = Method("define") + + +function _implement(method, object, lambda) { + /** + Implements `Method` for the given `object` with a provided `implementation`. + Calling `Method` with `object` as a first argument will dispatch on provided + implementation. + **/ + return defineProperty(object, method.toString(), { + enumerable: false, + configurable: false, + writable: false, + value: lambda + }) +} + +function _define(method, Type, lambda) { + /** + Defines `Method` for the given `Type` with a provided `implementation`. + Calling `Method` with a first argument of this `Type` will dispatch on + provided `implementation`. If `Type` is a `Method` default implementation + is defined. If `Type` is a `null` or `undefined` `Method` is implemented + for that value type. + **/ + + // Attempt to guess a type via `Object.prototype.toString.call` hack. + var type = Type && typefy.call(Type.prototype) + + // If only two arguments are passed then `Type` is actually an implementation + // for a default type. + if (!lambda) Default[method] = Type + // If `Type` is `null` or `void` store implementation accordingly. + else if (Type === null) Null[method] = lambda + else if (Type === void(0)) Void[method] = lambda + // If `type` hack indicates built-in type and type has a name us it to + // store a implementation into associated hash. If hash for this type does + // not exists yet create one. + else if (type !== "[object Object]" && Type.name) { + var Bulitin = builtin[Type.name] || (builtin[Type.name] = new ObjectType()) + Bulitin[method] = lambda + } + // If `type` hack indicates an object, that may be either object or any + // JS defined "Class". If name of the constructor is `Object`, assume it's + // built-in `Object` and store implementation accordingly. + else if (Type.name === "Object") + builtin.Object[method] = lambda + // Host objects are pain!!! Every browser does some crazy stuff for them + // So far all browser seem to not implement `call` method for host object + // constructors. If that is a case here, assume it's a host object and + // store implementation in a `host` array and store `index` in the array + // in a `Type.prototype` itself. This avoids memory leaks that could be + // caused by storing JS objects on a host objects. + else if (Type.call === void(0)) { + var index = host.indexOf(lambda) + if (index < 0) index = host.push(lambda) - 1 + // Prefix private name with `!` so it can be dispatched from the method + // without type checks. + implement("!" + method, Type.prototype, index) + } + // If Got that far `Type` is user defined JS `Class`. Define private name + // as hidden property on it's prototype. + else + implement(method, Type.prototype, lambda) +} + +// And provided implementations for a polymorphic equivalents. +_define(define, _define) +_define(implement, _implement) + +// Define exports on `Method` as it's only thing being exported. +Method.implement = implement +Method.define = define +Method.Method = Method +Method.method = Method +Method.builtin = builtin +Method.host = host + +module.exports = Method |