· 8 years ago · Feb 21, 2018, 01:44 AM
1/**
2 * Redy - a prototype for a ruby like javascript.
3 * Parts of this software are derived from JS.Class by James Coglan.
4 *
5 *
6 * This file is licensed under the Ruby licenense.
7 * Copyright 2008. Victor Hugo Borja <vic.borja gmail.com>
8 */
9
10Redy = {
11 extend : function(object, methods) {
12 if (!methods) { return object; }
13 for (var prop in methods) {
14 var getter = methods.__lookupGetter__ && methods.__lookupGetter__(prop),
15 setter = methods.__lookupSetter__ && methods.__lookupSetter__(prop);
16 if (getter || setter) {
17 if (getter) object.__defineGetter__(prop, getter);
18 if (setter) object.__defineSetter__(prop, setter);
19 } else {
20 if (object[prop] === methods[prop]) continue;
21 object[prop] = methods[prop];
22 }
23 }
24 return object;
25 },
26
27 array: function(iterable) {
28 if (!iterable) return [];
29 if (iterable.toArray) return iterable.toArray();
30 var length = iterable.length, results = [];
31 while (length--) results[length] = iterable[length];
32 return results;
33 },
34
35 indexOf: function(haystack, needle) {
36 for (var i = 0, n = haystack.length; i < n; i++) {
37 if (haystack[i] === needle) return i;
38 }
39 return -1;
40 },
41
42 isFn: function(object) {
43 return object instanceof Function;
44 }
45};
46
47
48Redy.Message = function(name, target) {
49 Redy.MethodMissing.addMethod(name);
50 this.name = name, this.target = this.original = target;
51 return this;
52};
53Redy.Message.send = function() {
54 var args = Redy.array(arguments), name = args.shift();
55 return new Redy.Message(name).send(this, args);
56};
57Redy.extend(Redy.Message.prototype, {
58 real : function() {
59 var fn = this;
60 while (fn && fn.target && typeof fn.target !== 'function') fn = fn.target;
61 return fn;
62 },
63 restore: function() {
64 return Redy.isFn(this.original) && !this.original.isMissing &&
65 (this.target = this.original) && this;
66 },
67 hasFun : function() {
68 return Redy.isFn(this.target) && !this.target.isMissing;
69 },
70 send : function(self, arguments) {
71 var fun = this.getMethod(self), name = this.name;
72 if (fun && !fun.isMissing)
73 return fun.apply(self, arguments);
74
75 fun = this.getMethod(self, 'methodMissing');
76 if (fun && !fun.isMissing) {
77 return fun.apply(self, [name].concat(arguments));
78 }
79
80 throw "No such method `"+name+"' in "+self;
81 },
82 getMethod : function(self, name) {
83 name = name || this.name;
84 if (!self.klass) return Redy.isFn(self[name]) && self[name];
85 var sendsite = this.getSendSite(self, name);
86 return sendsite && sendsite.target;
87 },
88 getSendSite : function(self, name) {
89 if (!self.klass) return undefined;
90 name = name || this.name;
91 var sendsite, real, removed;
92 var fromMod = function(mod) {
93 if (mod && mod['@methods'] && (sendsite = mod['@methods'][name]))
94 real = sendsite.real();
95 if (real && !real.hasFun())
96 (real = sendsite.restore()) || delete mod['@methods'][name];
97 return real && real.hasFun() && real;
98 };
99 var fromAnc = function(module) {
100 var mod, a = Redy.Module.prototype.ancestors.apply(module);
101 for (var i = 0, n = a.length; i < n; i++) {
102 if ( (mod = a[i]) && fromMod(mod) ) {
103 module['@methods'][name] = new Redy.Message(name, sendsite);
104 break;
105 }
106 }
107 return real;
108 };
109 return (self.eigen && fromMod(self.eigen)) ||
110 (self.klass && fromMod(self.klass)) ||
111 (self.eigen && fromAnc(self.eigen)) ||
112 (self.klass && fromAnc(self.klass));
113 }
114});
115
116Redy.MethodMissing = function(name) {
117 if (!name) return this;
118 var missing = function() {
119 return (new Redy.Message(name)).send(this, arguments);
120 };
121 missing.name = name;
122 missing.isMissing = function(onObj) {
123 return !(new Redy.Message(name)).getMethod(onObj);
124 };
125 return missing;
126};
127
128Redy.extend(Redy.MethodMissing, {
129 addMethod : function (name) {
130 if (Redy.MethodMissing.prototype[name])
131 return Redy.MethodMissing.prototype[name];
132 Redy.MethodMissing.prototype[name] = Redy.MethodMissing(name);
133 return Redy.MethodMissing.prototype[name];
134 },
135 addMethods: function(object) {
136 var methods = [], property;
137
138 if (object instanceof Array)
139 for(var i = 0, n = object.length, p; i < n; i ++) {
140 p = object[i];
141 Number(p) !== p && this.addMethod(p);
142 }
143 else for (property in object)
144 Number(property) !== property && this.addMethod(property);
145
146 object.prototype &&
147 this.addMethods(object.prototype);
148 }
149});
150
151Redy.Object = function() {
152 var ctor = function(){};
153 ctor.prototype = new Redy.MethodMissing();
154 return new ctor;
155};
156Redy.Object.mew = Redy.Object;
157Redy.extend(Redy.Object.prototype, {
158 initialize: function() {}
159});
160
161
162Redy.Kernel = function(){};
163Redy.extend(Redy.Kernel.prototype, {
164 __send__ : Redy.Message.send,
165 send: Redy.Message.send,
166 extend : function(mod) {
167 this.eigen.include(mod);
168 return this;
169 },
170 unextend : function(mod) {
171 this.eigen.uninclude(mod);
172 return this;
173 },
174 method : function(name) {
175 },
176 inspect: function() {
177 return this.toString();
178 }
179});
180
181Redy.Module = function(methods, body) {
182 var mod = Redy.Object.mew();
183 Redy.Module.initialize(mod, methods, body);
184 mod.klass = Redy.Module;
185 return mod;
186};
187Redy.Module.mew = Redy.Module;
188Redy.Module.initialize = function(mod, methods, body) {
189 mod['@methods'] = mod['@methods'] || {};
190 mod['@includes'] = mod['@includes'] || [];
191 for (var name in methods) {
192 if (Redy.isFn(methods[name]))
193 mod['@methods'][name] = new Redy.Message(name, methods[name]);
194 else throw "Invalid property value for "+name+' '+methods[name];
195 }
196 Redy.isFn(body) && body(mod);
197};
198
199Redy.extend(Redy.Module.prototype, {
200 name: function(name) {
201 return name && (this['@name'] = name) || this['@name'];
202 },
203
204 appendFeatures: function(toMod) {
205 var methods = this['@methods'];
206 for (var name in methods) {
207 if (toMod['@methods'][name])
208 toMod['@methods'][name].target = methods[name];
209 else
210 toMod['@methods'][name] = new Redy.Message(name, methods[name]);
211 }
212 },
213
214 removeFeatures: function(toMod) {
215 for (name in this['@methods']) {
216 if(toMod['@methods'][name]) delete toMod['@methods'][name].target;
217 delete toMod['@methods'][name];
218 }
219 },
220
221 include : function(mixin) {
222 if (!mixin) return this;
223 mixin.constructor === Object && (mixin = Redy.Module.mew(mixin));
224 if (mixin.klass !== Redy.Module) throw "Can only include modules.";
225 if (mixin === this) throw "Cannot include itself";
226 if (Redy.indexOf(this['@includes'], mixin) === -1)
227 this['@includes'].unshift(mixin);
228 mixin.appendFeatures(this);
229 mixin.included(this);
230 return this;
231 },
232
233 uninclude: function(mixin) {
234 if (mixin.klass !== Redy.Module) throw "Not a module "+mixin;
235 mixin.removeFeatures(this);
236 var mods = [], includes = this['@includes'], i = includes.length;
237 while (i--) includes[i] !== mixin && mods.unshift(mixin);
238 this['@includes'] = mods;
239 mixin.unincluded(this);
240 return this;
241 },
242
243 included: function(inMod) { },
244 unincluded: function(inMod) { },
245
246 ancestors: function() {
247 var ancestors = [this].concat(this['@includes']);
248 if (this === Redy.Kernel) return ancestors;
249 for(var sp = this.superclass ; sp ; sp = sp.superclass) {
250 ancestors.push(sp);
251 }
252 ancestors.push(Redy.Kernel);
253 return ancestors;
254 },
255
256 defineMethod : function(name, method) {
257 this['@methods'][name] = new Redy.Message(name, method);
258 this.methodAdded(name);
259 },
260
261 removeMethod: function(name) {
262 if(this['@methods'][name]) delete this['@methods'][name].target;
263 delete this['@methods'][name];
264 },
265
266 instanceMethod : function(name) {
267 var fun = Redy.MethodMissing.getMethod(this, name);
268 var thing = this.klass == Redy.Class ? "class" : "module";
269 if (!fun) throw "Undefined method `"+name+"' for "+thing+" `"+this+"'";
270 return fun;
271 },
272
273 instanceMethods : function() {
274 var ary = [];
275 for (var name in this['@methods']) ary.push(name);
276 return ary;
277 },
278
279 methodDefined: function(name) {
280 var fun = Redy.MethodMissing.getMethod(this, name);
281 return !!fun;
282 },
283
284 methodAdded: function() {},
285 methodRemoved: function() {}
286});
287
288Redy.Class = function(parent, methods, body) {
289 var klass = Redy.Object.prototype.constructor.mew();
290 klass.klass = Redy.Class;
291 klass.superclass = parent;
292 Redy.Module.initialize(klass, methods, body);
293 return klass;
294};
295Redy.Class.mew = Redy.Class;
296Redy.extend(Redy.Class.prototype, {
297 allocate : function() {
298 var obj = Redy.Object.prototype.constructor.mew();
299 obj.klass = this;
300 obj.eigen = Redy.Class.prototype.constructor.mew(this);
301 return obj;
302 },
303 mew : function() {
304 var obj = this.allocate();
305 obj.initialize.apply(obj, arguments);
306 return obj;
307 }
308});
309
310Redy.Module = (function(proto) {
311 var module = Redy.Class.mew(Redy.Object, proto);
312 module.klass = module;
313 module.superclass = Redy.Object;
314 module.prototype = proto;
315 module.name("Module");
316 module.initialize = Redy.Module.initialize;
317 module.mew = Redy.Module.mew;
318 return module;
319})(Redy.Module.prototype);
320
321Redy.Kernel = (function(proto) {
322 var kern = Redy.Module.mew(proto);
323 kern.prototype = proto;
324 kern.name("Kernel");
325 return kern;
326})(Redy.Kernel.prototype);
327
328
329Redy.Class = (function(proto) {
330 var klass = Redy.Class.mew(Redy.Module, proto);
331 klass.klass = klass;
332 klass.superclass = Redy.Module;
333 klass.prototype = proto;
334 klass.mew = Redy.Class.mew;
335 klass.name("Class");
336 return klass;
337})(Redy.Class.prototype);
338
339
340Redy.Object = (function(proto) {
341 var obj = Redy.Class.mew(undefined, proto);
342 Redy.Module.superclass = obj;
343 obj.prototype = proto;
344 obj.include(Redy.Kernel);
345 obj.name("Object");
346 return obj;
347})(Redy.Object.prototype);
348
349
350/**************
351 * Redy ends here.
352 * The following is an example program.
353 */
354
355var hello = Redy.Object.mew();
356
357var Person = Redy.Class.mew(Redy.Object, {
358 initialize : function(name) {
359 print("New person named "+name);
360 this.name = name;
361 },
362 hello : function() {
363 print("Hello "+this.name);
364 }
365});
366
367
368var vic = Person.mew("VICO");
369var hugo = Person.mew("HUGO");
370vic.hello();
371
372Person.defineMethod("adios", function() {
373 print("Goodbye ! "+this.name);
374});
375
376vic.adios();
377hugo.adios();
378
379var Programmer = Redy.Module.mew({
380 ruby : function() {
381 print(this.name+" likes ruby !");
382 }
383});
384vic.extend(Programmer);
385vic.ruby();
386
387hugo.extend({
388 methodMissing : function() {
389 var args = Redy.array(arguments), method = args.shift();
390 print("Called missing method "+method+" in "+this.name);
391 }
392});
393
394hugo.ruby();
395hugo.include();
396
397var Programmer2 = Redy.Module.mew({
398 ruby : function() {
399 print(this.name+" likes ruby to heart !!");
400 },
401 ecma : function() {
402 print(this.name+" hacks javascript with ruby flavour !!");
403 }
404});
405Programmer.include(Programmer2);
406
407vic.ruby();
408vic.ecma();
409
410print("Now removing and unincluding");
411Programmer2.removeMethod('ruby');
412Programmer2.name("Programmer 2");
413print(Programmer2.name());
414vic.ruby();
415vic.ecma();
416
417Programmer.uninclude(Programmer2);
418vic.extend({
419 methodMissing : function() {
420 var args = Redy.array(arguments), method = args.shift();
421 print("Called missing method "+method+" in "+this.name);
422 }
423});
424vic.ecma(); // no such method
425
426vic.send("hello");
427Some.mew();