Classes and namespaces

Classes and namespaces

The keyword namespace is used to declare new namespaces. A namespace is a collection of functions, classes, variables, etc. aggregated in a single unit. Namespaces can also contain other (inner) namespaces. Under the hood, a namespace is just a named global object. The keyword super can be used to access members of the parent namespace that have the same name as those in the inner namespace.

Declaration of a namespace:

namespace <name>
{
  [ <function> | <var> | <const> | <class> | <namespace> ]*
}

Classes

The keyword class is used to define new classes (types of objects):

class <class-name> [ : <parent-class name> ]
{
  [ <function> | <constructor> | <property> | <var> | <const> | <this var> | <class> ]*
}
Where:
<function> is a standard function declaration. Such functions can be used as instance or static methods of the class.
<constructor> is a function with predefined name this.
<property> is a special type of member function. See more details below.
<var> is a standard var declaration – allows to define class or module variables (a.k.a. static variables)
<const> is a declaration of named constant.
<class> is a declaration of inner class.
<this var> is a declaration of member variable[s]:
     this var <name> [= <value>] [, <name> = <value> , ...];

Constructors

The function with the name this, declared in the context of a class, serves the role of the function-constructor for all instances of the class:

class Tor
{
  this var one = 1; // declaration of member variable
  function this() { this.two = 2; } // constructor
}

The main goal of the constructor is to initialize new instances of the class (a.k.a. objects) by creating and initializing all the fields that the instance shall contain. The object that is being created is accessible through the variable named this inside the constructor 1).

The compiler invokes the constructor when it handles the new operator:

var tor = new Tor();
//  tor.one == 1 and
//  tor.two == 2 at this point

In any other aspect, function this() is an ordinary function that may have any number of parameters defined, including the optional parameters.

Methods

A function with any other name, declared in the context of a class, is known as a method of that class. If a method has a reference to the this variable, it is called an instance method (a.k.a. dynamic). If it does not refer to the this variable, it is a class method (a.k.a. static). In principle, the same function can be used as both, an instance and a class method. When invoked as a class method, this will have a reference to the class or namespace where the method is defined. When invoked as an instance method ( e.g. obj.method() ) this will be equal to the object instance (obj in this case).

Instance Methods

Instance methods must be called in the context of particular object of the class:

class Tor
{
  function rotate( angle ) {  this.angle = angle; this.update(); }
}
var tor = new Tor();
tor.rotate(270);     // Tor.rotate() method called in the context
                     // of a particular instance - 'tor'

Static Methods

Static method of a class or namespace can be called by using the ClassOrNamespace.Method notation (a.k.a. the "dot notation"):

class Tor
{
  var all = [];
  function rotateAll( angle ) {  for(var t in Tor.all) t.rotate(angle);  }
}
Tor.rotateAll(270); // Tor.rotateAll() method called as a class method

Properties

TIScript supports definitions of computable (a.k.a. virtual) properties. These are special functions that are executed to set or get the value of a property. The syntax of a computable property definition:

property <function-name> ( <parameter> )
{
  [ <statement> | get <statement> | set <statement> ]*
}

The body of the property can contain statements that may appear in a normal function body and also the special get and set sections. The get section (one or more) is executed conditionally when the function is invoked for getting  the property value, and the set section for setting the property value. Example:

class Baz
{
  function this() { this._first = 1; this._second = 2; }

  property first( val )
  {
    get return this._first; // single expression get block
    set this._first = val;  // single expression set block
  }

  property second( val )
  {
    stdout << "second";
    get
    {
      stdout << "get\n";
      return this._second + 2;
    }
    set
    {
      stdout << "set\n";
      this._second = val - 2;
    }
  }
}
Note that the property function declaration must have a single parameter defined, e.g. val.

Undefined Property Handler

It is possible to define a so-called Undefined Property Handler, which deals with undefined properties. The syntax of the UPH is similar to the computable property handler, except for the undefined name token used in its name:

property undefined ( <name>, <value> )
{
  [<statement> | get <statement> | set <statement> ]*
}

The UPH function must have precisely two parameters: the name of the property, and its value (used in the property set operation). UPH’s can be used in cases where the set of properties is unknown upfront. For example, you may wish to access the fields of a recordset as properties:

property Recordset.undefined(name, val)
{
  get return this.getFieldValue(name);
}
...

var rs = DB.exec("SELECT firstName, secondName FROM employee");
var n1 = rs.firstName;  // redirect to rs.getFieldValue(#firstName); call
var n2 = rs.secondName; // through property undefined()

Class/Namespace variables

Class, or namespace, variables are defined in the context of a class or namespace by using the var keyword. Example:

namespace Toz
{
  var all = []; // create variable that contains empty array;
  var singleton = { something: 123; }; // create variable
                                       // that contains object;
  var counter = 0; // create variable that contains integer zero;
}

To access such variables, use their fully qualified names:

Toz.all.push("anything");
Toz.counter += 1;

Constants

Namespace constants (a.k.a. constants) are read-only variables, defined in the context of a type by using the const keyword. Example:

namespace Toz
{
  const ALIGN_LEFT = 1;
  const ALIGN_CENTER = 2;
  const ALIGN_RIGHT = 3;
}

To access such constants, use their fully qualified names:

switch(alignment)
{
  case Toz.ALIGN_LEFT: ... break;
  case Toz.ALIGN_CENTER: ... break;
  case Toz.ALIGN_RIGHT: ... break;
  default: ... break;
}

1) The idea for using this for a constructor name was borrowed from the D language