MicroNova SANSHO.JS is a tiny public-domain Javascript library based on the notion of class object, code-named after a "tiny but hot" kind of Japanese pepper.

1. Core

1.1. Class Object

In SANSHO.JS, a class object C is a regular Javascript object that supports the following methods:

For example,

// x is an instance of C

var x = C.newInstance();

// so the following is true

console.debug(C.isClassOf(x));

// and the following is false

console.debug(C.isClassOf({}));

A class object C always has the following three properties (class triad):

These class triad properties are also available in C._self and C._constructor, so for example (C._self)._class = C, and x._class = C for any instance x of C since C._self is the prototype of x (x._class = C._self._class = C).

SANSHO.JS creates global SANSHO class object. To distinguish SANSHO class object names from regular Javascript constructor function names (such as "Object", "String", etc.), SANSHO class object names are all in capital letters.

Note that class triad properties are all system properties (non-configurable, non-writable, non-enumerable).

1.2. Extension

A class object D is an extension of another class object C when:

Each SANSHO class object C has C.extend(extender) method to create its extension class object which takes an optional extender function as follows:

var D = C.extend
(
  function(_self, _class)
  {
    // this is extender function that takes two arguments _self and _class
    // _self argument is the object to become D._self
    // _self._super is C._self
    // _class argument is the object to become D
    // _class._super is C
    // this is C here

    // set up _self and _class objects here
  }
);

SANSHO.JS uses standard Javascript function closure for super-class access via _self and _class function arguments, allowing each method function to have access to the class hierarchy on creation. This makes SANSHO.JS class extension mechanism to be tiny and yet flexible.

Super-class methods can be called using standard Javascript call or apply method on this object as follows:

var D = SANSHO.extend
(
  function(_self, _class)
  {
    _self.init = function(config)
    {
      _self._super.init.call(this, config);
    }
  }
);

When necessary, super-class methods can be "promoted" to avoid call or apply as follows:

SANSHO.modify
(
  function(_self, _class)
  {
    var _classId = 0;

    _class.setSystemProperty(_class, '_classId', _classId);
    
    _class.extend = function(method)
    {
      return function(modifier)
      {
        return method.call
        (
          this, 
          function(_self, _class)
          {
            _class.setSystemProperty(_class, '_classId', ++_classId);
            return modifier.call(this, _self, _class);
          }
        );
      };
    }(_class.extend);

    _self.promote = _class.promote = function(name)
    {
      var promotedName = name + '__' + this._class._classId;

      if (!this.hasOwnProperty(promotedName))
      {
        this._class.setSystemProperty(this, promotedName, this[name]);
      }

      return promotedName;
    }
  }
);

var D = SANSHO.extend
(
  function(_self, _class)
  {
    _self.init = function(_superInit)
    {
      return function(config)
      {
        this[_superInit](config);
      }
    }(_self.promote('init'));
  }
);

Note that it is not possible to create instances of D inside the extender function because D._constructor is not set yet. In order to do post-extension class object modification, use D.modify(modifier) as follows:

var D = C.extend
(
  // no extender needed here if constructor is not modified (see below)
)
.modify
(
  // modify D

  function(_self, _class)
  {
    // this is modifier function with the same arguments as extender
    // _self and _class can be modified
    // also new instances of _class can be created
    // this is D here

    _class.defaultInstance = _class.newInstance();
  }
);

For the default class object SANSHO, its _super properties are all undefined (SANSHO._super, SANSHO._self._super, and SANSHO._constructor._super).

1.3. Constructor

For a SANSHO class object C, C._constructor is created by C.makeConstructor() method, which by default returns the following function:

// SANSHO default constructor

function(config)
{
  if (config)
  {
    // copy all enumerable properties of config to this if given

    for (var k in config)
    {
      this[k] = config[k];
    }
  }

  // call this.init with config

  this.init(config);
}

SANSHO._self.init is defined as an empty function, so by default this.init above (= SANSHO._self.init) does nothing. However, init (or any other instance method) can be overridden either at class level or at instance level:

// class level overridden init

var C = SANSHO.extend
(
  function(_self, _class)
  {
    _self.init = function(config)
    {
      console.debug('C');
    }
  }
);

var D = C.extend
(
  function(_self, _class)
  {
    _self.init = function(config)
    {
      console.debug('D');

      // call init of super class (= C)

      _self._super.init.call(this, config);
    }
  }
);

// this should print 'D' followed by 'C', because d.init = D._self.init

var d = D.newInstance();

// init can be overridden at instance level as follows to print 'E' followed by 'D' followed by 'C'

var e = D.newInstance
(
  {
    // set init method of particular instance d

    init: function(config)
    {
      console.debug('E');

      // call instance prototype method (= D.init)

      this._self.init.call(this, config);
    }
  }
);

By default SANSHO.JS constructors take single config argument, but when necessary, makeConstructor() and newInstance() class object methods can be modified by the extender function:

var RECT = SANSHO.extend
(
  function(_self, _class)
  {
    // make constructor to take two arguments

    _class.makeConstructor = function()
    {
      return function(width, height)
      {
        this.width = width;
        this.height = height;
      }
    };

    // make newInstance also take two arguments

    _class.newInstance = function(width, height)
    {
      return new this._constructor(width, height);
    };

    // returns the area of a rectangle

    _class.areaOf = function(rect)
    {
      return rect.width * rect.height;
    };

    _self.area = function()
    {
      return this._class.areaOf(this);
    };
  }
);

// create a new instance of RECT with width = 5, height = 10

var r = RECT.newInstance(5, 10);

// this should print '50'

console.debug(r.area());
console.debug(RECT.areaOf(r));

If standard new Javascript syntax is preferred, the _constructor property can be used as follows:

var Rect = SANSHO.extend
(
  function(_self, _class)
  {
    // make constructor to take two arguments

    _class.makeConstructor = function()
    {
      return function(width, height)
      {
        this.width = width;
        this.height = height;
      }
    };

    // make newInstance also take two arguments

    _class.newInstance = function(width, height)
    {
      return new this._constructor(width, height);
    };

    // returns the area of a rectangle

    _class.area = function(rect)
    {
      return rect.width * rect.height;
    };
  }
)._constructor;

// create a new instance of RECT with width = 5, height = 10

var r = new Rect(5, 10);

// this should print '50'

console.debug(r.area());
console.debug(Rect._class.areaOf(r));

1.4. Other methods

Other methods defined in SANSHO are:

Note that these methods are available in all class objects.

1.5. Core Source Code

Source code of SANSHO core is as follows:

var SANSHO = (function() {
  var setupClass = function(_self, _class, _superConstructor) {
    var _constructor = this.setSystemProperty(this.makeConstructor(), 'prototype', _self);

    this.setSystemProperty(_constructor, '_self', _self);
    this.setSystemProperty(_constructor, '_class', _class);
    this.setSystemProperty(_constructor, '_constructor', _constructor);

    this.setSystemProperty(_self, '_self', _self);
    this.setSystemProperty(_self, '_class', _class);
    this.setSystemProperty(_self, '_constructor', _constructor);

    this.setSystemProperty(_class, '_self', _self);
    this.setSystemProperty(_class, '_class', _class);
    this.setSystemProperty(_class, '_constructor', _constructor);

    this.setSystemProperty(_constructor, '_super', _superConstructor);
  };

  var _class = {
    setSystemProperty: function(object, property, value) {
      return Object.defineProperty(object, property, {value: value});
    },

    extendObject: function(_super) {
      return this.setSystemProperty(new (this.setSystemProperty(function(){}, 'prototype', _super))(), '_super', _super);
    },

    makeConstructor: function() {
      return function(config) {
        if (config) {
          for (var k in config) {
            this[k] = config[k];
          }
        }

        this.init(config);
      };
    },

    modify: function(modifier) {
      modifier.call(this, this._self, this);
      return this;
    },

    extend: function(extender) {
      var _class = this.extendObject(this);
      var _self = this.extendObject(this._self);
      
      if (extender) {
        extender.call(this, _self, _class);
      }

      setupClass.call(_class, _self, _class, this._constructor);
      
      return _class;
    },

    newInstance: function(config) {
      return new this._constructor(config);
    },

    isClassOf: function(instance) {
      return (instance instanceof this._constructor);
    }
  };

  var _self = {
    init: function(config) {
    }
  };

  setupClass.call(_class, _self, _class);

  return _class;
})();

2. TYPE Utilities

SANSHO.TYPE is a class object holding type-related utility methods. In this minimal version, object and range (objects with numeric length property) types are supported.

SANSHO.TYPE holds instances of itself _object and _range with the following iteration methods:

Each iteration method above returns optional target argument after iteration by default, and takes the following iteration function f as an argument:

function(source, key, target)
{
  // source: source object given to the iteration method
  // key: iterating key (property name, index, or current object in case of iterateProperty)
  // target: target argument given to the iteration method

  // return nothing from this function to continue iteration till the end
  // and return target after iteration

  // or return any value (not undefined) here to break out of
  // iteration and return this value instead of default target
}

A few _object iterator examples:

var x = {};
var y = SANSHO.extendObject(x);

x.a = 4;
x.b = 9;

y.a = 6;
y.c = 3;

var z = SANSHO.TYPE._object.iterateAll
(
  y,
  function(source, key, target)
  {
    target[key] = source[key];
  },
  {}
);

// z should be {a:6, b:9, c:3}

var w = SANSHO.TYPE._object.iterateOwn
(
  y,
  function(source, key, target)
  {
    target[key] = source[key];
  },
  {}
);

// w should be {a:6, c:3}

var q = SANSHO.TYPE._object.iterateChain
(
  '_super',
  y,
  function(source, node, target)
  {
    SANSHO.TYPE._object.iterateAll
    (
      source,
      function(source, key, target)
      {
        var list = target[key] || [];

        if (node.hasOwnProperty(key))
        {
          list.push(node[key]);
        }
        else
        {
          list.push(undefined);
        }

        target[key] = list;
      },
      target
    );
  },
  {}
);

// q should be {a:[6,4], b:[undefined,9]}, c:[3,undefined]}

// and a simple "clone" function:

var clone = function _clone(source)
{
  var TYPE = SANSHO.TYPE;

  if (TYPE.isObject(source))
  {
    return TYPE._object.iterateOwn
    (
      source, 
      function(source, k, target)
      {
        target[k] = _clone(source[k]);
      },
      new source.constructor()
    );
  }
  else
  {
    return source;
  }
};

// z should look the same as q

var z = clone(q);

// but deeply cloned, so the following is false

console.debug(z.a === q.a)

And a few _range iterator examples:

var x = [5,3,2,4,1];

// x should become [1,2,3,4,5] by insertion sort

SANSHO.TYPE._range.iterateForward
(
  x,
  function(source, i1)
  {
    var v = source[i1];

    SANSHO.TYPE._range.iterateBackward
    (
      {length: i1},
      function(dummy, i2)
      {
        var w = source[i2];

        if (w < v)
        {
          return true;
        }
        else
        {
          source[i2] = v;
          source[i1] = w;

          i1 = i2;
        }
      }
    );
  }
);

// iterateBackward can be used as infinite loop

var k = SANSHO.TYPE._range.iterateBackward
(
  {length: -1},
  function(source, index)
  {
    var z = -index;

    if (988027 % z == 0)
    {
      return z;
    }
  }
);

// k should be 991

TYPE._object and TYPE._range has isTypeOf(x) method which calls TYPE.isObject(x) and TYPE.isRange(x), respectively.

2,1 TYPE Source Code

Source code of SANSHO TYPE is as follows:

SANSHO.modify
(
  function(_self, _class)
  {
    _class.TYPE = _class.extend
    (
      function(_self, _class)
      {
        _class.isNone = function(x)
        {
          return ((x === undefined) || (x === null));
        };

        _class.isObject = function(x)
        {
          return ((x !== null) && (typeof(x) == 'object'));
        };

        _class.isRange = function(x)
        {
          return (x && (typeof(x['length']) == 'number')) || (x === '');
        };
      }
    )
    .modify
    (
      function(_self, _class)
      {
        _class._object = _class.newInstance
        (
          {
            isTypeOf: function(x)
            {
              return this._class.isObject(x);
            },

            iterateOwn: function(source, f, target)
            {
              var undef = undefined;
              var keys = Object.keys(source);
              var length = keys.length;
              var breakValue;

              while (length--)
              {
                breakValue = f(source, keys[length], target);

                if (breakValue !== undef)
                {
                  return breakValue;
                }
              }

              return target;
            },

            iterateAll: function(source, f, target)
            {
              var undef = undefined;
              var breakValue;

              for (var k in source)
              {
                breakValue = f(source, k, target);

                if (breakValue !== undef)
                {
                  return breakValue;
                }
              }

              return target;
            },

            iterateChain: function(chainProperty, source, f, target)
            {
              var undef = undefined;
              var breakValue;

              var node = source;

              while (node)
              {
                breakValue = f(source, node, target);

                if (breakValue !== undef)
                {
                  return breakValue;
                }

                node = node[chainProperty];
              }

              return target;
            }
          }
        );

        _class._range = _class.newInstance
        (
          {
            isTypeOf: function(x)
            {
              return this._class.isRange(x);
            },

            iterateForward: function(source, f, target)
            {
              var undef = undefined;
              var length = source.length;
              var breakValue;

              for (var i = 0; i < length; i ++)
              {
                breakValue = f(source, i, target);

                if (breakValue !== undef)
                {
                  return breakValue;
                }
              }

              return target;
            },

            iterateBackward: function(source, f, target)
            {
              var undef = undefined;
              var length = source.length;
              var breakValue;

              while (length--)
              {
                breakValue = f(source, length, target);

                if (breakValue !== undef)
                {
                  return breakValue;
                }
              }

              return target;
            }
          }
        );
      }
    );
  }
);