|
1 "use strict"; |
|
2 |
|
3 var defineProperty = Object.defineProperty || function(object, name, property) { |
|
4 object[name] = property.value |
|
5 return object |
|
6 } |
|
7 |
|
8 // Shortcut for `Object.prototype.toString` for faster access. |
|
9 var typefy = Object.prototype.toString |
|
10 |
|
11 // Map to for jumping from typeof(value) to associated type prefix used |
|
12 // as a hash in the map of builtin implementations. |
|
13 var types = { "function": "Object", "object": "Object" } |
|
14 |
|
15 // Array is used to save method implementations for the host objects in order |
|
16 // to avoid extending them with non-primitive values that could cause leaks. |
|
17 var host = [] |
|
18 // Hash map is used to save method implementations for builtin types in order |
|
19 // to avoid extending their prototypes. This also allows to share method |
|
20 // implementations for types across diff contexts / frames / compartments. |
|
21 var builtin = {} |
|
22 |
|
23 function Primitive() {} |
|
24 function ObjectType() {} |
|
25 ObjectType.prototype = new Primitive() |
|
26 function ErrorType() {} |
|
27 ErrorType.prototype = new ObjectType() |
|
28 |
|
29 var Default = builtin.Default = Primitive.prototype |
|
30 var Null = builtin.Null = new Primitive() |
|
31 var Void = builtin.Void = new Primitive() |
|
32 builtin.String = new Primitive() |
|
33 builtin.Number = new Primitive() |
|
34 builtin.Boolean = new Primitive() |
|
35 |
|
36 builtin.Object = ObjectType.prototype |
|
37 builtin.Error = ErrorType.prototype |
|
38 |
|
39 builtin.EvalError = new ErrorType() |
|
40 builtin.InternalError = new ErrorType() |
|
41 builtin.RangeError = new ErrorType() |
|
42 builtin.ReferenceError = new ErrorType() |
|
43 builtin.StopIteration = new ErrorType() |
|
44 builtin.SyntaxError = new ErrorType() |
|
45 builtin.TypeError = new ErrorType() |
|
46 builtin.URIError = new ErrorType() |
|
47 |
|
48 |
|
49 function Method(hint) { |
|
50 /** |
|
51 Private Method is a callable private name that dispatches on the first |
|
52 arguments same named Method: |
|
53 |
|
54 method(object, ...rest) => object[method](...rest) |
|
55 |
|
56 Optionally hint string may be provided that will be used in generated names |
|
57 to ease debugging. |
|
58 |
|
59 ## Example |
|
60 |
|
61 var foo = Method() |
|
62 |
|
63 // Implementation for any types |
|
64 foo.define(function(value, arg1, arg2) { |
|
65 // ... |
|
66 }) |
|
67 |
|
68 // Implementation for a specific type |
|
69 foo.define(BarType, function(bar, arg1, arg2) { |
|
70 // ... |
|
71 }) |
|
72 **/ |
|
73 |
|
74 // Create an internal unique name if `hint` is provided it is used to |
|
75 // prefix name to ease debugging. |
|
76 var name = (hint || "") + "#" + Math.random().toString(32).substr(2) |
|
77 |
|
78 function dispatch(value) { |
|
79 // Method dispatches on type of the first argument. |
|
80 // If first argument is `null` or `void` associated implementation is |
|
81 // looked up in the `builtin` hash where implementations for built-ins |
|
82 // are stored. |
|
83 var type = null |
|
84 var method = value === null ? Null[name] : |
|
85 value === void(0) ? Void[name] : |
|
86 // Otherwise attempt to use method with a generated private |
|
87 // `name` that is supposedly in the prototype chain of the |
|
88 // `target`. |
|
89 value[name] || |
|
90 // Otherwise assume it's one of the built-in type instances, |
|
91 // in which case implementation is stored in a `builtin` hash. |
|
92 // Attempt to find a implementation for the given built-in |
|
93 // via constructor name and method name. |
|
94 ((type = builtin[(value.constructor || "").name]) && |
|
95 type[name]) || |
|
96 // Otherwise assume it's a host object. For host objects |
|
97 // actual method implementations are stored in the `host` |
|
98 // array and only index for the implementation is stored |
|
99 // in the host object's prototype chain. This avoids memory |
|
100 // leaks that otherwise could happen when saving JS objects |
|
101 // on host object. |
|
102 host[value["!" + name] || void(0)] || |
|
103 // Otherwise attempt to lookup implementation for builtins by |
|
104 // a type of the value. This basically makes sure that all |
|
105 // non primitive values will delegate to an `Object`. |
|
106 ((type = builtin[types[typeof(value)]]) && type[name]) |
|
107 |
|
108 |
|
109 // If method implementation for the type is still not found then |
|
110 // just fallback for default implementation. |
|
111 method = method || Default[name] |
|
112 |
|
113 |
|
114 // If implementation is still not found (which also means there is no |
|
115 // default) just throw an error with a descriptive message. |
|
116 if (!method) throw TypeError("Type does not implements method: " + name) |
|
117 |
|
118 // If implementation was found then just delegate. |
|
119 return method.apply(method, arguments) |
|
120 } |
|
121 |
|
122 // Make `toString` of the dispatch return a private name, this enables |
|
123 // method definition without sugar: |
|
124 // |
|
125 // var method = Method() |
|
126 // object[method] = function() { /***/ } |
|
127 dispatch.toString = function toString() { return name } |
|
128 |
|
129 // Copy utility methods for convenient API. |
|
130 dispatch.implement = implementMethod |
|
131 dispatch.define = defineMethod |
|
132 |
|
133 return dispatch |
|
134 } |
|
135 |
|
136 // Create method shortcuts form functions. |
|
137 var defineMethod = function defineMethod(Type, lambda) { |
|
138 return define(this, Type, lambda) |
|
139 } |
|
140 var implementMethod = function implementMethod(object, lambda) { |
|
141 return implement(this, object, lambda) |
|
142 } |
|
143 |
|
144 // Define `implement` and `define` polymorphic methods to allow definitions |
|
145 // and implementations through them. |
|
146 var implement = Method("implement") |
|
147 var define = Method("define") |
|
148 |
|
149 |
|
150 function _implement(method, object, lambda) { |
|
151 /** |
|
152 Implements `Method` for the given `object` with a provided `implementation`. |
|
153 Calling `Method` with `object` as a first argument will dispatch on provided |
|
154 implementation. |
|
155 **/ |
|
156 return defineProperty(object, method.toString(), { |
|
157 enumerable: false, |
|
158 configurable: false, |
|
159 writable: false, |
|
160 value: lambda |
|
161 }) |
|
162 } |
|
163 |
|
164 function _define(method, Type, lambda) { |
|
165 /** |
|
166 Defines `Method` for the given `Type` with a provided `implementation`. |
|
167 Calling `Method` with a first argument of this `Type` will dispatch on |
|
168 provided `implementation`. If `Type` is a `Method` default implementation |
|
169 is defined. If `Type` is a `null` or `undefined` `Method` is implemented |
|
170 for that value type. |
|
171 **/ |
|
172 |
|
173 // Attempt to guess a type via `Object.prototype.toString.call` hack. |
|
174 var type = Type && typefy.call(Type.prototype) |
|
175 |
|
176 // If only two arguments are passed then `Type` is actually an implementation |
|
177 // for a default type. |
|
178 if (!lambda) Default[method] = Type |
|
179 // If `Type` is `null` or `void` store implementation accordingly. |
|
180 else if (Type === null) Null[method] = lambda |
|
181 else if (Type === void(0)) Void[method] = lambda |
|
182 // If `type` hack indicates built-in type and type has a name us it to |
|
183 // store a implementation into associated hash. If hash for this type does |
|
184 // not exists yet create one. |
|
185 else if (type !== "[object Object]" && Type.name) { |
|
186 var Bulitin = builtin[Type.name] || (builtin[Type.name] = new ObjectType()) |
|
187 Bulitin[method] = lambda |
|
188 } |
|
189 // If `type` hack indicates an object, that may be either object or any |
|
190 // JS defined "Class". If name of the constructor is `Object`, assume it's |
|
191 // built-in `Object` and store implementation accordingly. |
|
192 else if (Type.name === "Object") |
|
193 builtin.Object[method] = lambda |
|
194 // Host objects are pain!!! Every browser does some crazy stuff for them |
|
195 // So far all browser seem to not implement `call` method for host object |
|
196 // constructors. If that is a case here, assume it's a host object and |
|
197 // store implementation in a `host` array and store `index` in the array |
|
198 // in a `Type.prototype` itself. This avoids memory leaks that could be |
|
199 // caused by storing JS objects on a host objects. |
|
200 else if (Type.call === void(0)) { |
|
201 var index = host.indexOf(lambda) |
|
202 if (index < 0) index = host.push(lambda) - 1 |
|
203 // Prefix private name with `!` so it can be dispatched from the method |
|
204 // without type checks. |
|
205 implement("!" + method, Type.prototype, index) |
|
206 } |
|
207 // If Got that far `Type` is user defined JS `Class`. Define private name |
|
208 // as hidden property on it's prototype. |
|
209 else |
|
210 implement(method, Type.prototype, lambda) |
|
211 } |
|
212 |
|
213 // And provided implementations for a polymorphic equivalents. |
|
214 _define(define, _define) |
|
215 _define(implement, _implement) |
|
216 |
|
217 // Define exports on `Method` as it's only thing being exported. |
|
218 Method.implement = implement |
|
219 Method.define = define |
|
220 Method.Method = Method |
|
221 Method.method = Method |
|
222 Method.builtin = builtin |
|
223 Method.host = host |
|
224 |
|
225 module.exports = Method |