Skip to content

Instantly share code, notes, and snippets.

@vic
Created November 5, 2008 18:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vic/22397 to your computer and use it in GitHub Desktop.
Save vic/22397 to your computer and use it in GitHub Desktop.
/**
* 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