"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