FixScript Classes Documentation

This token processor allows you to use classes and other syntax additions.

Features:

Table of contents

Table of contents:

Types

The built-in types are:

You can also use array, hash and class types. Array types use brackets after the type, for example Float[] describes an array of floats. Hash types contains the type of the key in the brackets, for example Integer[String] describes a hash map with a String key and an Integer value.

Constructors

The syntax for constructors is just a syntax sugar for static methods that create the object and return it. In case you need more flexibility when creating an object (eg. using a different underlying type) you can use static methods directly.

Base object type

Classes can either extend an existing class type or optionally extend a special common class type named Object. This class allows to provide automatic implementation of to_string methods. You can also provide your own implementations of this method for custom string representations.

Some class types can't extend from Object, for example when they need serialization or are backed by other type than an array. In such cases you can also register a to_string method, albeit in a less efficient way. This is achieved by using the Object::set_to_string(obj, func) static method. It simply registers the provided function to a global (self-clearing) hash map with a weak reference key.

Another alternative is to simply put a function reference as a first entry in an array, the function must take one argument and must end with _to_string suffix.

You can then either call the to_string methods directly, or use dump or to_string (with newlines), these are automatically replaced with versions that know about class types. You can of course use the original functions by using @dump and @to_string functions.

This functionality is completely optional. You have to put object.fix file in the root of the scripts and extend the class from the Object type.

Interfaces

Interfaces provide common type for otherwise different classes. This is achieved by creating a wrapper class that contains reference to the original class and a set of function references specific to that class so it can be called in a common way.

The original class contains some method, called as_interface (when the interface class is called Interface) that returns a new instance of this wrapper class. It can also cache it when desirable (using weak references if the same existing interface instance should be always used).

Here is an example:

class Class1
{
    var @field1;
    var @field2;
    var @field3;

    function as_some_interface(): SomeInterface
    {
        return SomeInterface::create(this, Class1::common_method#1);
    }

    function common_method()
    {
        log("Class1");
    }
}

class Class2
{
    function as_some_interface(): SomeInterface
    {
        return SomeInterface::create(this, Class2::common_method#1);
    }

    function common_method()
    {
        log("Class2");
    }
}

class SomeInterface
{
    var @data;
    var @common_method_func;

    constructor create(data, common_method_func)
    {
        this.data = data;
        this.common_method_func = common_method_func;
    }

    function common_method()
    {
        common_method_func(data);
    }
}

The this variable

The this variable used in instance functions and constructors is a normal variable. It can be assigned, or it can even contain a null value (in fact any kind of value). This is because the concept of classes is separated from the underlying implemenation type.

This can be used for various things, for example in callbacks you can pass array of multiple values instead of just the object and unpack it in the code by assigning the object instance into the this variable. Another example is using of weak references (again typically in callbacks).

Some methods can check explicitly for nulls in this. For example comparison functions or to_string implementations.

A slight downside of this approach is that when a null is passed to an instance method, there is no check at the time of method call, it will error only when the object is accessed in the method, for some methods it can even succeed if it's not accessed at all.

Class definitions using private constants

Sometimes you may want to define classes without actually using the classes token processor. For example if you're not using classes (or want it optional) but still provide the comfort of using them. Another example is the ability to make multiple versions of classes token processor (or other token processors) to work together.

The format is simple, to declare a class use a @class_SomeClass private constant with a string value (can be empty). The string can contain these attributes:

The value is delimited by '=', multiple attributes are separated by ',' and when the attribute can contain multiple values they're separated by a ':'. No whitespace is allowed.

You can define methods by declaring a @method_SomeClass_method_name_1 private constant, where the last part is a number of parameters (including the implied 'this' parameter). You can either specify static methods using the static attribute for the class, or just use static instead of method in the private constant name. Undefined methods are still recognized based on the prefix (all parameters and the return type are Dynamic in that case).

You can define global functions by using a @global_some_func_1 private constant to define a function named some_func with a single parameter.

Every method uses the '(SomeType, Integer, Float): Boolean' format to specify the parameters. This means specifying the types of the parameters (the parameter for 'this' is omitted for non-static methods), optionally followed by a return type. Whitespace is allowed.

This is an example how it can look:

const @class_SomeClass = "extend=BaseClass,prefix=somecls,struct=SCLS,static=create#2:static#0";
const @field_SomeClass_field1 = "Integer";
const @field_SomeClass_field2 = "Boolean";
const @static_SomeClass_create_2 = "(Integer, Boolean): SomeClass";
const @method_SomeClass_instance_function_3 = "(Integer, Boolean)";
const @global_some_func_2 = "(SomeClass, Integer): Boolean";

API

The API allows other token processors to integrate with classes processing. To get access to the API, just import classes script and obtain the context. Then you can register various hooks. The hooks are added based on actual needs.

You can also use the API without classes, just use the prefixes class_context_ and class_type_ for the methods and pass the objects as a first parameter.

const {
	TYPE_DYNAMIC,
	TYPE_VOID,
	TYPE_INTEGER,
	TYPE_FLOAT,
	TYPE_BOOLEAN,
	TYPE_STRING
};

const {
	EXT_TYPE_CLASS,
	EXT_TYPE_ARRAY,
	EXT_TYPE_HASH
};

class ClassContext
{
	static function get(fname: String): ClassContext;
	function register_function_call(name: String, get_types_func, adjust_call_func, data);
	function register_postprocess(func, data);
	function get_class(name: String): ClassType;
	function get_const_type(name: String): ClassType;
	function get_local_type(name: String): ClassType;
	function get_variable_type(name: String): ClassType;
}

class ClassType
{
	static function create_array(base: ClassType): ClassType;
	static function create_hash(base: ClassType, index: ClassType): ClassType;
	function get_class_name(): String;
	function get_parent_class(): ClassType;
	function get_base(): ClassType;
	function get_index(): ClassType;
	function to_string(): String;
	static function dump_list(list: ClassType[]);
}

The types are divided into two enumerations. The simple types are just integers and you can directly use them (cast from/to ClassType). The extended types are contained in an array and the first entry is the type.

ClassContext class

static function get(fname: String): ClassContext
Obtains context for given script file name.
function register_function_call(name: String, get_types_func, adjust_call_func, data)
Registers adjustment of function call to otherwise not defined function (the resolution is done as last). Multiple registrations to the same name can be done. Provide a name of the function (without parameter count, you can pass null to match all names) and the callbacks with these signatures:
function get_types(data, name: String, num_params: Integer, line: Integer): ClassType[]
Called when the function name is matched, provides number of the parameters. You need to return an array of types (the first index is for the return type, the rest is for the expected types of the parameters) or null if there is no function for given number of parameters. The returned array is modified and passed to the adjust_call callback.
function adjust_call(data, name: String, types: ClassType[], tokens, src: String, start: Integer, end: Integer): Integer
Called after the function call is generated with all type conversions. The types (after any conversion) are provided in an array (the previously given array is modified). You have also access to the generated tokens and the start and the ending index (exclusive). You need to return the new ending index after custom adjustments.
function register_postprocess(func, data)
Registers function to be called after processing of classes. The registered functions are called in a reversed order to allow wrapping behavior of different token processors. The signature of the function is:
function postprocess(data, fname: String, tokens, src: String)
Called after processing of classes.
function get_class(name: String): ClassType
Returns class type for given name. Must be called within processing.
function get_const_type(name: String): ClassType
Returns the type for a constant. Returns -1 if the constant with given name is not defined. Must be called within processing.
function get_local_type(name: String): ClassType
Returns the type for a local variable. Returns -1 if the local variable with given name is not defined. Must be called within processing.
function get_variable_type(name: String): ClassType
Returns the type for a variable. Returns -1 if the variable with given name is not defined. Must be called within processing.

ClassType class

static function create_array(base_type: ClassType): ClassType
Creates an array type with given base type.
static function create_hash(base_type: ClassType, index_type: ClassType): ClassType
Creates a hash type with given base and index type.
function get_class_name(): String
Returns name of class.
function get_parent_class(): ClassType
Returns parent class.
function get_base(): ClassType
Returns the base type of array or hash.
function get_index(): ClassType
Returns the index type of hash.
function to_string(): String
Returns the string representation of the type.
static function dump_list(list: ClassType[])
Dumps list of types in string representation.