1 | /// boo.js --- Base primitives for prototypical OO
|
2 | //
|
3 | // Copyright (c) 2011 Quildreen "Sorella" Motta <quildreen@gmail.com>
|
4 | //
|
5 | // Permission is hereby granted, free of charge, to any person
|
6 | // obtaining a copy of this software and associated documentation files
|
7 | // (the "Software"), to deal in the Software without restriction,
|
8 | // including without limitation the rights to use, copy, modify, merge,
|
9 | // publish, distribute, sublicense, and/or sell copies of the Software,
|
10 | // and to permit persons to whom the Software is furnished to do so,
|
11 | // subject to the following conditions:
|
12 | //
|
13 | // The above copyright notice and this permission notice shall be
|
14 | // included in all copies or substantial portions of the Software.
|
15 | //
|
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23 |
|
24 | /// Module boo
|
25 | void function(root, exports) {
|
26 |
|
27 | //// -- Aliases -------------------------------------------------------------
|
28 | var slice = [].slice
|
29 | var keys = Object.keys
|
30 | var inherit = Object.create
|
31 | var define = Object.defineProperty
|
32 | var descriptor = Object.getOwnPropertyDescriptor
|
33 | var has_getter_p = function () {
|
34 | try {
|
35 | return true === Object.create( {}
|
36 | , { x: { get:
|
37 | function(){
|
38 | return true }}}).x }
|
39 | catch(e){ return false }}()
|
40 |
|
41 |
|
42 |
|
43 | //// -- Interfaces ----------------------------------------------------------
|
44 |
|
45 | ///// Interface DataObject
|
46 | // DataObject :: { "to_data" -> () -> Object }
|
47 |
|
48 | ///// Interface Mixin
|
49 | // Mixin :: Object | DataObject
|
50 |
|
51 |
|
52 | //// -- Helpers -------------------------------------------------------------
|
53 |
|
54 | ///// Function copy_property
|
55 | // :internal:
|
56 | // Copies a property from ``source`' to ``target`'.
|
57 | //
|
58 | // copy_property! :: Object, target:Object*, String -> target
|
59 | function copy_property(source, target, property) {
|
60 | !has_getter_p? target[property] = source[property]
|
61 | : /* otherwise */ define(target, property, descriptor(source, property))
|
62 |
|
63 | return target
|
64 | }
|
65 |
|
66 | ///// Function data_obj_p
|
67 | // :internal:
|
68 | // Checks if the given subject matches the ``DataObject`` interface
|
69 | //
|
70 | // data_obj_p :: Any -> Bool
|
71 | function data_obj_p(subject) {
|
72 | return subject != null
|
73 | && typeof subject.to_data == 'function' }
|
74 |
|
75 |
|
76 | ///// Function resolve_mixins
|
77 | // :internal:
|
78 | // Returns the proper object for the given mixin.
|
79 | //
|
80 | // resolve_mixin :: Mixin -> Object
|
81 | function resolve_mixin(subject) {
|
82 | return data_obj_p(subject)? subject.to_data()
|
83 | : /* otherwise */ subject }
|
84 |
|
85 |
|
86 | ///// Function fast_extend
|
87 | // :internal:
|
88 | // Extends the target object with the provided mixins, using a
|
89 | // right-most precedence rule — when a there's a property conflict, the
|
90 | // property defined in the last object wins.
|
91 | //
|
92 | // ``DataObject``s are properly handled by the ``resolve_mixin``
|
93 | // function.
|
94 | //
|
95 | // :warning: low-level
|
96 | // This function is not meant to be called directly from end-user
|
97 | // code, use the ``extend`` function instead.
|
98 | //
|
99 | // fast_extend! :: target:Object*, [Mixin] -> target
|
100 | function fast_extend(object, mixins) {
|
101 | var i, j, len, mixin, props, key
|
102 | for (i = 0, len = mixins.length; i < len; ++i) {
|
103 | mixin = resolve_mixin(mixins[i])
|
104 | props = keys(mixin)
|
105 | for (j = props.length; j--;) {
|
106 | key = props[j]
|
107 | copy_property(mixin, object, key) }}
|
108 |
|
109 | return object }
|
110 |
|
111 |
|
112 |
|
113 | //// -- Basic primitives ----------------------------------------------------
|
114 |
|
115 | ///// Function extend
|
116 | // Extends the target object with the provided mixins, using a
|
117 | // right-most precedence rule.
|
118 | //
|
119 | // :see-also:
|
120 | // - ``fast_extend`` — lower level function.
|
121 | // - ``merge`` — pure version.
|
122 | //
|
123 | // extend! :: target:Object*, Mixin... -> target
|
124 | function extend(target) {
|
125 | return fast_extend(target, slice.call(arguments, 1)) }
|
126 |
|
127 |
|
128 | ///// Function merge
|
129 | // Creates a new object that merges the provided mixins, using a
|
130 | // right-most precedence rule.
|
131 | //
|
132 | // :see-also:
|
133 | // - ``extend`` — impure version.
|
134 | //
|
135 | // merge :: Mixin... -> Object
|
136 | function merge() {
|
137 | return fast_extend({}, arguments) }
|
138 |
|
139 |
|
140 | ///// Function derive
|
141 | // Creates a new object inheriting from the given prototype and extends
|
142 | // the new instance with the provided mixins.
|
143 | //
|
144 | // derive :: proto:Object, Mixin... -> Object <| proto
|
145 | function derive(proto) {
|
146 | return fast_extend(inherit(proto), slice.call(arguments, 1)) }
|
147 |
|
148 |
|
149 | ///// Function make
|
150 | // Constructs a new instance of the given object.
|
151 | //
|
152 | // If the object provides an ``init`` function, that function is
|
153 | // invoked to do initialisation on the new instance.
|
154 | //
|
155 | // make :: proto:Object, Any... -> Object <| proto
|
156 | function make(base) {
|
157 | return Base.make.apply(base, slice.call(arguments, 1)) }
|
158 |
|
159 |
|
160 |
|
161 | //// -- Root object ---------------------------------------------------------
|
162 |
|
163 | ///// Object Base
|
164 | // The root object for basing all the OOP code. Provides the previous
|
165 | // primitive combinators in an easy and OOP-way.
|
166 | var Base = {
|
167 |
|
168 | ////// Function make
|
169 | // Constructs new instances of the object the function is being
|
170 | // applied to.
|
171 | //
|
172 | // If the object provides an ``init`` function, that function is
|
173 | // invoked to do initialisation on the new instance.
|
174 | //
|
175 | // make :: @this:Object, Any... -> Object <| this
|
176 | make:
|
177 | function _make() {
|
178 | var result = inherit(this)
|
179 | if (typeof result.init == 'function')
|
180 | result.init.apply(result, arguments)
|
181 |
|
182 | return result }
|
183 |
|
184 | ////// Function derive
|
185 | // Constructs a new object that inherits from the object this function
|
186 | // is being applied to, and extends it with the provided mixins.
|
187 | //
|
188 | // derive :: @this:Object, Mixin... -> Object <| this
|
189 | , derive:
|
190 | function _derive() {
|
191 | return fast_extend(inherit(this), arguments) }}
|
192 |
|
193 |
|
194 |
|
195 | //// -- Exports -------------------------------------------------------------
|
196 | exports.extend = extend
|
197 | exports.merge = merge
|
198 | exports.derive = derive
|
199 | exports.make = make
|
200 | exports.Base = Base
|
201 | exports.internal = { data_obj_p : data_obj_p
|
202 | , fast_extend : fast_extend
|
203 | , resolve_mixin : resolve_mixin
|
204 | , copy_property : copy_property
|
205 | }
|
206 |
|
207 | }
|
208 | ( this
|
209 | , typeof exports == 'undefined'? this.boo = this.boo || {}
|
210 | /* otherwise, yay modules! */: exports
|
211 | )
|