Decorators of functions, classes and namespaces

Decorators of functions, classes and namespaces

Introduction

Decorators 1) are a meta-programming feature. A decorator is a function aimed to modify another function or method. Usually, decorator functions create another function that wraps the call of the original (decorated) function with some additional pre- and/or post-processing.

In TIScript, decorators are ordinary functions that accept one or more parameters. The first parameter is always a reference to the object (function, class or namespace) being decorated. The name of the decorator function should start with the @ character (at-symbol).

Here is an example of the @returns decorator. This decorator creates a proxy function which verifies the return value of the original, decorated, function:

function @returns(func, return_type)
{
  return function(params..) // returning proxy-function of the 'func'
  {
    var rv = func.apply(this, params); // call the 'func'
    if( typeof rv != return_type )     // verify its return type
      throw String.printf("Function %s expected to return %s but got %s",
                           func.name, return_type, typeof rv);
    return rv;
  }
}

When declared, such decorator can be used to ensure that a function returns a value of desired type:

@returns #integer
function SumInt(a, b)
{
  return a + b;
}

Syntax of decorator invocation

The core syntax of TIScript was extended to support functions and methods with decorators. Function declaration now looks like:

[<decorator-list>] function <name> ( <arguments > ) { <function body> } -- named function;
[<decorator-list>] : <arguments > : <statement> -- anonymous lambda statement;
[<decorator-list>] : <arguments > { <function body> } -- anonymous lambda function.

Where the decorator-list is a list of one or more decorator calls. Each decorator call is a name of a decorator function (starting with ‘@’), and an optional whitespace-separated list of parameter values for the decorator invocation.

@decoratorname [p1 [p2 [... pN ]]] <function-or-lambda-declaration>

Empty decorator

Sometimes you need to use an empty decorator that is used for e.g. creating some function rather than decorating another one. Empty decorators end with a ; (semicolon):

@decoratorname [p1 [p2 [... pN ]]] ;

The decorator function

The function name must start with the @ character (at-symbol) in order to be used as a decorator.

The decorator function must have at least one parameter. This parameter is used to pass the reference to the object being decorated into the decorator function.

The decorator function is an ordinary function and even can be used in other places as any other function.

this

this environment variable inside the decorator refers to the current namespace/class where the decorator is applied. If the decorator is used for a global function, then this refers to the global namespace object.

Sample of decorators use

Here is a fragment of ScIDE source code:

@key 'N' @CTRL @NOSHIFT @NOALT : { openFile(); return true; } // Ctrl+N
@key 'S' @CTRL @SHIFT @NOALT : { saveAllDocuments(); return true; } // Ctrl+Shift+S

These two lines attach event handlers (anonymous functions on the right)  to the self.onKey event ( see declaration of function @key() below ). @CTRL, @SHIFT and @NOALT are also decorators, each of them adds its own filtering expression to the chain with the event handler code at the end.

Here is how such decorators are implemented:

// decorator '@key' - filters Event.KEY_DOWN events by keyCode and ctrl, shift, alt flags.
// Establishes chain of event handlers on onKey
function @key(func, keyCode = undefined, modifiers..)
{
  function t(evt)
  {
    var r = false;
    if( evt.type == Event.KEY_DOWN &&
        (keyCode === undefined || (keyCode == evt.keyCode)) )
          r = func.call(this,evt);
    if(t.next) return t.next.call(this,evt) || r;
    return r;
  }
  // note 'this' in decorators is a current namespace - class or global (ns)
  var principal = this instanceof Behavior ? this : self;
  t.next = principal.onKey;
  principal.onKey = t;
}

// decorator '@CTRL' - pass if evt.ctrlKey === true
function @CTRL(func) { return function(evt) { if( evt.ctrlKey === true ) return func.call(this,evt); } }
// decorator '@NOCTRL' - pass if evt.ctrlKey === false
function @NOCTRL(func) { return function(evt) { if( evt.ctrlKey === false ) return func.call(this,evt); } }

1) Decorators in TIScript are modeled after the decorators in the Python programming language.