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