Skip to content

Commit

Permalink
lang: Add new "Class" framework adapted from MooTools
Browse files Browse the repository at this point in the history
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 27, 2011
1 parent 87077ea commit bb7272c
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile-test.am
Expand Up @@ -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 \
Expand Down
80 changes: 80 additions & 0 deletions modules/lang.js
Expand Up @@ -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);
92 changes: 92 additions & 0 deletions test/js/testClass.js
@@ -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.