addon-sdk/source/lib/method/core.js

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:bd4b06b5f352
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

mercurial