Created
November 5, 2008 18:43
-
-
Save vic/22397 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Redy - a prototype for a ruby like javascript. | |
* Parts of this software are derived from JS.Class by James Coglan. | |
* | |
* | |
* This file is licensed under the Ruby licenense. | |
* Copyright 2008. Victor Hugo Borja <vic.borja gmail.com> | |
*/ | |
Redy = { | |
extend : function(object, methods) { | |
if (!methods) { return object; } | |
for (var prop in methods) { | |
var getter = methods.__lookupGetter__ && methods.__lookupGetter__(prop), | |
setter = methods.__lookupSetter__ && methods.__lookupSetter__(prop); | |
if (getter || setter) { | |
if (getter) object.__defineGetter__(prop, getter); | |
if (setter) object.__defineSetter__(prop, setter); | |
} else { | |
if (object[prop] === methods[prop]) continue; | |
object[prop] = methods[prop]; | |
} | |
} | |
return object; | |
}, | |
array: function(iterable) { | |
if (!iterable) return []; | |
if (iterable.toArray) return iterable.toArray(); | |
var length = iterable.length, results = []; | |
while (length--) results[length] = iterable[length]; | |
return results; | |
}, | |
indexOf: function(haystack, needle) { | |
for (var i = 0, n = haystack.length; i < n; i++) { | |
if (haystack[i] === needle) return i; | |
} | |
return -1; | |
}, | |
isFn: function(object) { | |
return object instanceof Function; | |
} | |
}; | |
Redy.Message = function(name, target) { | |
Redy.MethodMissing.addMethod(name); | |
this.name = name, this.target = this.original = target; | |
return this; | |
}; | |
Redy.Message.send = function() { | |
var args = Redy.array(arguments), name = args.shift(); | |
return new Redy.Message(name).send(this, args); | |
}; | |
Redy.extend(Redy.Message.prototype, { | |
real : function() { | |
var fn = this; | |
while (fn && fn.target && typeof fn.target !== 'function') fn = fn.target; | |
return fn; | |
}, | |
restore: function() { | |
return Redy.isFn(this.original) && !this.original.isMissing && | |
(this.target = this.original) && this; | |
}, | |
hasFun : function() { | |
return Redy.isFn(this.target) && !this.target.isMissing; | |
}, | |
send : function(self, arguments) { | |
var fun = this.getMethod(self), name = this.name; | |
if (fun && !fun.isMissing) | |
return fun.apply(self, arguments); | |
fun = this.getMethod(self, 'methodMissing'); | |
if (fun && !fun.isMissing) { | |
return fun.apply(self, [name].concat(arguments)); | |
} | |
throw "No such method `"+name+"' in "+self; | |
}, | |
getMethod : function(self, name) { | |
name = name || this.name; | |
if (!self.klass) return Redy.isFn(self[name]) && self[name]; | |
var sendsite = this.getSendSite(self, name); | |
return sendsite && sendsite.target; | |
}, | |
getSendSite : function(self, name) { | |
if (!self.klass) return undefined; | |
name = name || this.name; | |
var sendsite, real, removed; | |
var fromMod = function(mod) { | |
if (mod && mod['@methods'] && (sendsite = mod['@methods'][name])) | |
real = sendsite.real(); | |
if (real && !real.hasFun()) | |
(real = sendsite.restore()) || delete mod['@methods'][name]; | |
return real && real.hasFun() && real; | |
}; | |
var fromAnc = function(module) { | |
var mod, a = Redy.Module.prototype.ancestors.apply(module); | |
for (var i = 0, n = a.length; i < n; i++) { | |
if ( (mod = a[i]) && fromMod(mod) ) { | |
module['@methods'][name] = new Redy.Message(name, sendsite); | |
break; | |
} | |
} | |
return real; | |
}; | |
return (self.eigen && fromMod(self.eigen)) || | |
(self.klass && fromMod(self.klass)) || | |
(self.eigen && fromAnc(self.eigen)) || | |
(self.klass && fromAnc(self.klass)); | |
} | |
}); | |
Redy.MethodMissing = function(name) { | |
if (!name) return this; | |
var missing = function() { | |
return (new Redy.Message(name)).send(this, arguments); | |
}; | |
missing.name = name; | |
missing.isMissing = function(onObj) { | |
return !(new Redy.Message(name)).getMethod(onObj); | |
}; | |
return missing; | |
}; | |
Redy.extend(Redy.MethodMissing, { | |
addMethod : function (name) { | |
if (Redy.MethodMissing.prototype[name]) | |
return Redy.MethodMissing.prototype[name]; | |
Redy.MethodMissing.prototype[name] = Redy.MethodMissing(name); | |
return Redy.MethodMissing.prototype[name]; | |
}, | |
addMethods: function(object) { | |
var methods = [], property; | |
if (object instanceof Array) | |
for(var i = 0, n = object.length, p; i < n; i ++) { | |
p = object[i]; | |
Number(p) !== p && this.addMethod(p); | |
} | |
else for (property in object) | |
Number(property) !== property && this.addMethod(property); | |
object.prototype && | |
this.addMethods(object.prototype); | |
} | |
}); | |
Redy.Object = function() { | |
var ctor = function(){}; | |
ctor.prototype = new Redy.MethodMissing(); | |
return new ctor; | |
}; | |
Redy.Object.mew = Redy.Object; | |
Redy.extend(Redy.Object.prototype, { | |
initialize: function() {} | |
}); | |
Redy.Kernel = function(){}; | |
Redy.extend(Redy.Kernel.prototype, { | |
__send__ : Redy.Message.send, | |
send: Redy.Message.send, | |
extend : function(mod) { | |
this.eigen.include(mod); | |
return this; | |
}, | |
unextend : function(mod) { | |
this.eigen.uninclude(mod); | |
return this; | |
}, | |
method : function(name) { | |
}, | |
inspect: function() { | |
return this.toString(); | |
} | |
}); | |
Redy.Module = function(methods, body) { | |
var mod = Redy.Object.mew(); | |
Redy.Module.initialize(mod, methods, body); | |
mod.klass = Redy.Module; | |
return mod; | |
}; | |
Redy.Module.mew = Redy.Module; | |
Redy.Module.initialize = function(mod, methods, body) { | |
mod['@methods'] = mod['@methods'] || {}; | |
mod['@includes'] = mod['@includes'] || []; | |
for (var name in methods) { | |
if (Redy.isFn(methods[name])) | |
mod['@methods'][name] = new Redy.Message(name, methods[name]); | |
else throw "Invalid property value for "+name+' '+methods[name]; | |
} | |
Redy.isFn(body) && body(mod); | |
}; | |
Redy.extend(Redy.Module.prototype, { | |
name: function(name) { | |
return name && (this['@name'] = name) || this['@name']; | |
}, | |
appendFeatures: function(toMod) { | |
var methods = this['@methods']; | |
for (var name in methods) { | |
if (toMod['@methods'][name]) | |
toMod['@methods'][name].target = methods[name]; | |
else | |
toMod['@methods'][name] = new Redy.Message(name, methods[name]); | |
} | |
}, | |
removeFeatures: function(toMod) { | |
for (name in this['@methods']) { | |
if(toMod['@methods'][name]) delete toMod['@methods'][name].target; | |
delete toMod['@methods'][name]; | |
} | |
}, | |
include : function(mixin) { | |
if (!mixin) return this; | |
mixin.constructor === Object && (mixin = Redy.Module.mew(mixin)); | |
if (mixin.klass !== Redy.Module) throw "Can only include modules."; | |
if (mixin === this) throw "Cannot include itself"; | |
if (Redy.indexOf(this['@includes'], mixin) === -1) | |
this['@includes'].unshift(mixin); | |
mixin.appendFeatures(this); | |
mixin.included(this); | |
return this; | |
}, | |
uninclude: function(mixin) { | |
if (mixin.klass !== Redy.Module) throw "Not a module "+mixin; | |
mixin.removeFeatures(this); | |
var mods = [], includes = this['@includes'], i = includes.length; | |
while (i--) includes[i] !== mixin && mods.unshift(mixin); | |
this['@includes'] = mods; | |
mixin.unincluded(this); | |
return this; | |
}, | |
included: function(inMod) { }, | |
unincluded: function(inMod) { }, | |
ancestors: function() { | |
var ancestors = [this].concat(this['@includes']); | |
if (this === Redy.Kernel) return ancestors; | |
for(var sp = this.superclass ; sp ; sp = sp.superclass) { | |
ancestors.push(sp); | |
} | |
ancestors.push(Redy.Kernel); | |
return ancestors; | |
}, | |
defineMethod : function(name, method) { | |
this['@methods'][name] = new Redy.Message(name, method); | |
this.methodAdded(name); | |
}, | |
removeMethod: function(name) { | |
if(this['@methods'][name]) delete this['@methods'][name].target; | |
delete this['@methods'][name]; | |
}, | |
instanceMethod : function(name) { | |
var fun = Redy.MethodMissing.getMethod(this, name); | |
var thing = this.klass == Redy.Class ? "class" : "module"; | |
if (!fun) throw "Undefined method `"+name+"' for "+thing+" `"+this+"'"; | |
return fun; | |
}, | |
instanceMethods : function() { | |
var ary = []; | |
for (var name in this['@methods']) ary.push(name); | |
return ary; | |
}, | |
methodDefined: function(name) { | |
var fun = Redy.MethodMissing.getMethod(this, name); | |
return !!fun; | |
}, | |
methodAdded: function() {}, | |
methodRemoved: function() {} | |
}); | |
Redy.Class = function(parent, methods, body) { | |
var klass = Redy.Object.prototype.constructor.mew(); | |
klass.klass = Redy.Class; | |
klass.superclass = parent; | |
Redy.Module.initialize(klass, methods, body); | |
return klass; | |
}; | |
Redy.Class.mew = Redy.Class; | |
Redy.extend(Redy.Class.prototype, { | |
allocate : function() { | |
var obj = Redy.Object.prototype.constructor.mew(); | |
obj.klass = this; | |
obj.eigen = Redy.Class.prototype.constructor.mew(this); | |
return obj; | |
}, | |
mew : function() { | |
var obj = this.allocate(); | |
obj.initialize.apply(obj, arguments); | |
return obj; | |
} | |
}); | |
Redy.Module = (function(proto) { | |
var module = Redy.Class.mew(Redy.Object, proto); | |
module.klass = module; | |
module.superclass = Redy.Object; | |
module.prototype = proto; | |
module.name("Module"); | |
module.initialize = Redy.Module.initialize; | |
module.mew = Redy.Module.mew; | |
return module; | |
})(Redy.Module.prototype); | |
Redy.Kernel = (function(proto) { | |
var kern = Redy.Module.mew(proto); | |
kern.prototype = proto; | |
kern.name("Kernel"); | |
return kern; | |
})(Redy.Kernel.prototype); | |
Redy.Class = (function(proto) { | |
var klass = Redy.Class.mew(Redy.Module, proto); | |
klass.klass = klass; | |
klass.superclass = Redy.Module; | |
klass.prototype = proto; | |
klass.mew = Redy.Class.mew; | |
klass.name("Class"); | |
return klass; | |
})(Redy.Class.prototype); | |
Redy.Object = (function(proto) { | |
var obj = Redy.Class.mew(undefined, proto); | |
Redy.Module.superclass = obj; | |
obj.prototype = proto; | |
obj.include(Redy.Kernel); | |
obj.name("Object"); | |
return obj; | |
})(Redy.Object.prototype); | |
/************** | |
* Redy ends here. | |
* The following is an example program. | |
*/ | |
var hello = Redy.Object.mew(); | |
var Person = Redy.Class.mew(Redy.Object, { | |
initialize : function(name) { | |
print("New person named "+name); | |
this.name = name; | |
}, | |
hello : function() { | |
print("Hello "+this.name); | |
} | |
}); | |
var vic = Person.mew("VICO"); | |
var hugo = Person.mew("HUGO"); | |
vic.hello(); | |
Person.defineMethod("adios", function() { | |
print("Goodbye ! "+this.name); | |
}); | |
vic.adios(); | |
hugo.adios(); | |
var Programmer = Redy.Module.mew({ | |
ruby : function() { | |
print(this.name+" likes ruby !"); | |
} | |
}); | |
vic.extend(Programmer); | |
vic.ruby(); | |
hugo.extend({ | |
methodMissing : function() { | |
var args = Redy.array(arguments), method = args.shift(); | |
print("Called missing method "+method+" in "+this.name); | |
} | |
}); | |
hugo.ruby(); | |
hugo.include(); | |
var Programmer2 = Redy.Module.mew({ | |
ruby : function() { | |
print(this.name+" likes ruby to heart !!"); | |
}, | |
ecma : function() { | |
print(this.name+" hacks javascript with ruby flavour !!"); | |
} | |
}); | |
Programmer.include(Programmer2); | |
vic.ruby(); | |
vic.ecma(); | |
print("Now removing and unincluding"); | |
Programmer2.removeMethod('ruby'); | |
Programmer2.name("Programmer 2"); | |
print(Programmer2.name()); | |
vic.ruby(); | |
vic.ecma(); | |
Programmer.uninclude(Programmer2); | |
vic.extend({ | |
methodMissing : function() { | |
var args = Redy.array(arguments), method = args.shift(); | |
print("Called missing method "+method+" in "+this.name); | |
} | |
}); | |
vic.ecma(); // no such method | |
vic.send("hello"); | |
Some.mew(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment