Permalink
Browse files

lang: Add new "Class" framework adapted from MooTools

The current strategy of using __proto__ and _init to implement prototype chains
is non-standard and error-prone. Use something a bit more automatic to help us
along.

Additionally, when patches come for inheriting from gi objects, we can use the
existing framework to hoist ourselves without too much effort.

https://bugzilla.gnome.org/show_bug.cgi?id=662582
  • Loading branch information...
magcius committed Oct 24, 2011
1 parent 87077ea commit bb7272c68628feb96991cb9751e041bd1d145c3b
Showing with 173 additions and 0 deletions.
  1. +1 −0 Makefile-test.am
  2. +80 −0 modules/lang.js
  3. +92 −0 test/js/testClass.js
View
@@ -175,6 +175,7 @@ EXTRA_DIST += \
test/js/testself.js \
test/js/testByteArray.js \
test/js/testCairo.js \
+ test/js/testClass.js \
test/js/testDBus.js \
test/js/testGDBus.js \
test/js/testEverythingBasic.js \
View
@@ -128,5 +128,85 @@ function defineAccessorProperty(object, name, getter, setter) {
object.__defineSetter__(name, setter);
}
+// Class magic
+// Adapted from MooTools, MIT license
+// https://github.com/mootools/moootools-core
+
+function _Base() {
+}
+
+_Base.prototype.__name__ = '_Base';
+_Base.prototype.toString = function() {
+ return '[object ' + this.__name__ + ']';
+}
+
+function _parent() {
+ if (!this.__caller__)
+ throw new TypeError("The method 'parent' cannot be called");
+
+ let caller = this.__caller__;
+ let name = caller._name;
+ let parent = caller._owner.__super__;
+
+ let previous = parent ? parent.prototype[name] : undefined;
+
+ if (!previous)
+ throw new TypeError("The method '" + name + "' is not on the superclass");
+
+ return previous.apply(this, arguments);
+}
+
+function wrapFunction(obj, name, meth) {
+ if (meth._origin) meth = meth._origin;
+
+ function wrapper() {
+ this.__caller__ = wrapper;
+ let result = meth.apply(this, arguments);
+ this.__caller__ = null;
+ return result;
+ }
+
+ wrapper._origin = meth;
+ wrapper._name = name;
+ wrapper._owner = obj;
+
+ return wrapper;
+}
+
+function Class(params) {
+ if (!params.Name) {
+ throw new TypeError("Classes require an explicit 'name' parameter.");
+ }
+
+ let newClass = function() {
+ if (!this._init)
+ return this;
+
+ return this._init.apply(this, arguments);
+ };
+
+ let parent = params.Extends;
+ if (!parent)
+ parent = _Base;
+
+ newClass.__super__ = parent;
+ newClass.prototype = Object.create(parent.prototype);
+
+ for (let prop in params) {
+ let value = params[prop];
+
+ if (typeof value === 'function')
+ value = wrapFunction(newClass, prop, value);
+
+ newClass.prototype[prop] = value;
+ }
+
+ newClass.prototype.constructor = newClass;
+ newClass.prototype.__name__ = params.Name;
+ newClass.prototype.parent = _parent;
+
+ return newClass;
+}
+
// Merge stuff defined in native code
copyProperties(imports.langNative, this);
View
@@ -0,0 +1,92 @@
+// application/javascript;version=1.8
+
+const Lang = imports.lang;
+
+function assertArrayEquals(expected, got) {
+ assertEquals(expected.length, got.length);
+ for (let i = 0; i < expected.length; i ++) {
+ assertEquals(expected[i], got[i]);
+ }
+}
+
+const MagicBase = new Lang.Class({
+ Name: 'MagicBase',
+
+ _init: function(a, buffer) {
+ if (buffer) buffer.push(a);
+ this.a = a;
+ },
+
+ foo: function(a, buffer) {
+ buffer.push(a);
+ return a * 3;
+ }
+});
+
+const Magic = new Lang.Class({
+ Name: 'Magic',
+
+ Extends: MagicBase,
+
+ _init: function(a, b, buffer) {
+ this.parent(a, buffer);
+ if (buffer) buffer.push(b);
+ this.b = b;
+ },
+
+ foo: function(a, b, buffer) {
+ let val = this.parent(a, buffer);
+ buffer.push(b);
+ return val * 2;
+ }
+});
+
+const ToStringOverride = new Lang.Class({
+ Name: 'ToStringOverride',
+
+ toString: function() {
+ let oldToString = this.parent();
+ return oldToString + '; hello';
+ }
+});
+
+function testClassFramework() {
+ let newMagic = new MagicBase('A');
+ assertEquals('A', newMagic.a);
+}
+
+function testInheritance() {
+ let buffer = [];
+
+ let newMagic = new Magic('a', 'b', buffer);
+ assertArrayEquals(['a', 'b'], buffer);
+
+ buffer = [];
+ let val = newMagic.foo(10, 20, buffer);
+ assertArrayEquals([10, 20], buffer);
+ assertEquals(10*6, val);
+}
+
+function testConstructor() {
+ assertEquals(Magic, Magic.prototype.constructor);
+
+ let newMagic = new Magic();
+ assertEquals(Magic, newMagic.constructor);
+}
+
+function testInstanceOf() {
+ let newMagic = new Magic();
+
+ assertTrue(newMagic instanceof Magic);
+ assertTrue(newMagic instanceof MagicBase);
+}
+
+function testToString() {
+ let newMagic = new MagicBase();
+ assertEquals('[object MagicBase]', newMagic.toString());
+
+ let override = new ToStringOverride();
+ assertEquals('[object ToStringOverride]; hello', override.toString());
+}
+
+gjstestRun();

0 comments on commit bb7272c

Please sign in to comment.