9. UniScript Objects

In chapter COM-Interface the application of the ActiveX interface in UniScript was shown.

xls = CreateObject("Excel.Application")
xls.Visible = 1
xls.Workbooks.Add()
xls.ActiveSheet.Range("b1").Value = "sin(x)"
y = sin(linspace(0, 2*PI))
xls.ActiveSheet.Range("b2:b101").Value = y'

The following chapters describe how UniScript objects can be created and used.

9.1. Introduction

UniScript-Objects are created using the obj_create function. The function creates an empty object. After the object is created functions and variables can be added:

c = obj_create()
c.unit = "Nm"
c.val = [1,2,3,4]

The object c has two variables unit and val.

Alternatively the following, shorter object-constructor can be used to create the object and add variables.

c = [. unit = "Nm", val = [1,2,3,4] ]

The period behind the opening square bracket (“[“) tells the interpreter (and the programmer) that this is not a normal array. Without the period a variant matrix with 5 elements would have been created:

c = [unit = "Nm", val = [1,2,3,4] ] // c is not an object

Objects can be assigned to variables. The assignment

b = c;

causes that b and c point to the same object. The assignment b = c; does not copy the object. The object can be altered using the pointer b or c.

b.unit = "kg";
print c.unit        // => "kg"

For usual UniScript vectors and matrices the assignment operator will create a copy of the vector or matrix:

v1 = [1,3,7,123]
v2 = v1
v2[1] = 11
print v1[1] // => 1

If you assign a new value to c, c = 0, c does not point to the object any longer. The object can still be accessed using the b pointer. b = 0 will destroy the last pointer to the object. It will be removed from the memory by the garbage collector. The garbage collector can be invoked using the gc function. (see gc in the online help).

Instead of following syntax

object_pointer "." Name = Value

a variable can be accessed and using the index syntax:

Instead of

c.unit = "Nm"

one can write

c["unit"] = "Nm"

If the variable name is not a valid UniScript name, e.g. the name contains blank characters, or special characters like ‘+’ or ‘-‘ or the name is a key word only the index syntax can be used.

Internally, an object contains two tables. One table for string keys and one table for number keys. Each table maps unique keys to values.

c = obj_create()
c[1] = 123
c["1"] = 456

c contains now two key-value pairs (elements), one in the number table and one in the string table.

In the object-constructor syntax one can write:

c = [. [1] = 123, ["1"] = 456]

The integer keys can also be negative values

c[-4] = "hello"
c[0] = 1+2i
c[16237623] = -11

UniScript objects contain a number of member functions (methods).

k = c.keys(-1e308, 1e308)

returns all keys of the object c from the number table. k is a vector with the key values.

The online help contains help for all methods (obj_xxx, e.g. obj_keys).

It is not an error to access an index in the integer table for which no value is specified: a = c[111] returns the value 0.0. The method has_key (obj_has_key) can be used to check if the key exists: c.has_key(123) returns TRUE (1) if c[123] exists and FALSE (0) if c[123] does not exists.

Objects can contain as values numbers, complex numbers, strings, vectors and matrices of the three data types as well as COM objects pointers and UniScript variant matrices.

Objects can contain pointers to objects. It is possible to build complex data structures like lists:

list = obj_create();
list.val = 123;
list.next = obj_create();
list.next.val = 456;
list.next.next = 0;

or in object-constructor syntax:

list = [. val = 123, next = [. val = 456, next = 0]]

9.2. Object-Constructor Syntax

Grammar

prop_val:
        NUMBER                { counter = counter + 1; } /* Rule1 */
        |STRING               { counter = counter + 1; } /* R2 */
        | "(" exp ")"         { counter = counter + 1; } /* R3 */
        | NUMBER "=" exp      { counter = NUMBER + 1; }  /* R4 */
        | identifier "=" exp                             /* R5 */
        | "[" exp "]" "=" exp                            /* R6 */
        | STRING "=" exp                                 /* R7 */
        ;

prop_list:
        /* empty */                                      /* R8 */
        | prop_val                                       /* R9 */
        | prop_list "," prop_val                         /* R10 */
        ;

constructor:
        "[."                  { counter = 1; }           /* R11 */
        prop_list
        "]"
        ;

Example:

[.]     // Rule 11, R8
[.123]  // R11, R9, R1
[.(1+2i)] // R11, R9, R3
[. "test 1" = 123] // R11, R9, R7
[. a = 1, b = 2] // R11, R9, R10, R5

Other Examples:

a = [. 1, 2, 3]

is identical to:

a = [.]; a[1] = 1; a[2] = 2; a[3] = 3;
a = [. 5 = 1, 2, 3]

is identical to:

a = [.]; a[5] = 1; a[6] = 2; a[7] = 3;
a = [. [5] = 1, 2, 3]

is identical to:

a = [.]; a[5] = 1; a[1] = 2; a[2] = 3;

9.3. Standard Methods

The following functions can always be written as:

obj_xxx(o, ...)

or shorter in the form:

o.xxx(...)

Instead of a = o.lookup() one can write a = obj_lookup(o).

If the function has only one parameter (obj) the braces can be omitted:

n = o.count;
oParent = o.parent;
  UniScript-Object-Functions
obj_copy obj_copy creates a copy of an object.
obj_count obj_count returns the number of variables in an object.
obj_count_num obj_count_num returns the number of number keys in an object.
obj_count_str obj_count_str returns the number of string keys in an object.
obj_create obj_create creates an UniScript object.
obj_has_key obj_has_key checks if a given key exists.
obj_info obj_info returns a string of the form “obj-name,hex-address”.
obj_keys obj_keys returns all keys or a range of keys for the given object.
obj_load obj_load loads an object saved with obj_save from the hard drive.
obj_lookup obj_lookup returns the value of a given key.
obj_methods obj_methods returns an object with methods (member functions) for the given object.
obj_parent obj_parent returns the parent object or 0 if the object does not have a parent.
obj_remove obj_remove removes a key-value pair from an object.
obj_save obj_save saves an object as an XML file or as an binary file on the hard drive or as a string.
obj_set_at obj_set_at adds a key-value pair to the given object. If the key already exists the value will be overwritten.
obj_set_methods obj_set_methods sets the methods (member functions) for the given object.
obj_set_parent obj_set_parent sets the parent object.
obj_set_str_return obj_set_str_return specifies the return value of obj_lookup. If a key does not exist, obj_lookup may return 0, an empty string or throw an exception.
set_method_table set_method_table sets the methods (member functions) for the given object.

9.4. Methods for Objects

Beside the standard methods as copy, keys, set_at, etc. (see help for obj_copy, obj_keys, obj_set_at), new methods can be added to objects.

To add a function to an object (also called member function or method) an object must be created that maps the method name to a UniScript function.

Example:

def my_methods()
{
    return [. test = "my_test"];
}
def my_create()
{
    this = [.];
    set_method_table(this, "my");
    return this;
}

The function “my_test” could look like this:

def my_test(this, a, b)
{
    return a+b;
}

If the expressions

obj = my_create();
obj.test(3,4);

are executed the UniScript interpreter will try to invoke the “my_test” function. The interpreter will pass the object obj as the first parameter.

The my_create function is called the constructor function. my_create will first create the UniScript object:

this = [.];

The function call

set_method_table(this, "my");

will invoke the my_methods functions. my_methods creates the object with the methods names. set_method_table will than copy the object into a global table (g_method_table) and sets the object with the methods for this with the help of the obj_set_methods function. All objects created by my_create will contain the same object with the methods, i.e. my_methods will only invoked ones.

At the end the constructor will return the object:

return this;

9.5. Operator Overloading

Most of the operators cannot be applied directly to objects.

The following code will create the run time error “+: Operand type incorrect”:

a = [. val = 123];
b = [. val = 456];
c = a + b;

The compare operator

a == b

will return the value 0, i.e a is not identical to b. If b.val = 123 is set a == b would return the value FALSE. The == operator only returns TRUE if a and b point to the same object.

a = obj_create();
a.val = 123;
b = a;

or

a = [.val = 123];
b = a;

a == b will return TRUE. The Not Equal operator a != b will also compare the pointers and not the content. The Not operator (!a) can be used to check if the object has been created.

a = [.];
if (!a) {
    error()
}

or

a = [.];
if (a == 0) {
    error()
}

An object pointer can be compared with a number, but only the number 0 makes sense. You can redefine the function of most built-in operators.

Example:

def my_add(this, b)
{
    result = this.copy();
    result.val = this.val + b.val;
    return result;
}
def my_add2(this, b)
{
    result = this.copy();
    result.val = b.val + this.val;
    return result;
}
def my_print(this)
{
    print this.val;
}
def my_format(this)
{
    return sprintf("%s=%f", this.type, this.val);
}
def my_tonumber(this)
{
    return this.val;
}
def my_methods()
{
    return [.
        tonumber = "my_tonumber",
        __print__ = "my_print",
        __format__ = "my_format",
        __add__ = "my_add",
        __add2__ = "my_add2"
        ];
}
def my(num)
{
    this = [. type = "my"];
    set_method_table(this, "my");
    if (nargsin() == 1) {
        this.val = num;
    } else {
        this.val = 0;
    }
    return this;
}
a = my(2);
b = my(3);
d = my(3);
printf("\na + b = %f\n", (a+b).tonumber());
Operator Description
__destructor__ Destructor. This function is invoked when the Garbage-Collector destroys the object.
__print__ print a.
__format__ Create a string for the debugger.
__getproperty__ eval v = a.b
__setproperty__ eval a.b = v
__getprop__ eval v = a.b
__setprop__ eval a.b = v
__veceval__ eval a[idx]
__vecassign__ eval a[idx] = v
__catcol__ concatenate ([a,b]).
__catcol2__ concatenate ([a,b]). Is invoked if a does not have a __catcol__ method.
__catrow__ concatenate ([a;b]). Is invoked if a does not have a __catrow__ method.

__setprop__

With __setprop__ a UniScript function can be invoked to execute an expression of the form obj.prop = value.

Example:

def my_setprop(obj, val)
{
    obj["prop"] = val;
}

Inside the property-set function the assignment of the form obj.property = value cannot be used. Otherwise the function would be invoked recursively. One of the following forms should be use to set the value:

obj["prop_name"] = val;
obj.set_at("prop_name", val);
obj_set_at(obj, "prop_name", val);

__setproperty__

__setproperty__ has an extra parameter ssProp.

Example:

def my_setprop(obj, val, ssProp)
{
    obj[ssProp] = val;
}

__getprop__

__getprop__ is called if an expression of the form obj.prop stand on the right side: val = obj.prop.

def my_getprop(obj)
{
    return obj["prop_name"];
}

__getproperty__

def my_getproperty(obj, ssProp)
{
    return obj[ssProp];
}

__veceval__

Will be executed for expressions of the form v = obj[index].

def my_veceval(obj, index)
{
    return obj.lookup(index);
}

__vecassign__

Will be executed for expressions of the form obj[index] = val.

def my_vecassign(this, index, val)
{
    this.set_at(index, val);
}

Example:

def my_getprop(this)
{
    print "in getprop"
    return this.lookup("prop");
}
def my_getproperty(this, ssProp)
{
    print "in getproperty"
    return this.lookup(strupper(ssProp));
}
def my_setprop(this, val)
{
    print "in setprop"
    this.set_at("prop", val);
}
def my_setproperty(this, val, ssProp)
{
    print "in setproperty"
    return this.set_at(strupper(ssProp), val);
}
def my_veceval(this, idx)
{
    print "in veceval"
    return this.lookup(idx);
}
def my_vecassign(this, idx, val)
{
    print "in vecassign"
    this.set_at(idx, val);
}
def my_methods()
{
    return  [.
    tonumber = "tonumber",
    __setprop__ = [. prop = "my_setprop"],
    __getprop__ = [. prop = "my_getprop"],
    __getproperty__ = "my_getproperty",
    __setproperty__ = "my_setproperty",
    __veceval__ = "my_veceval",
    __vecassign__ = "my_vecassign"]
}
def my(num)
{
    this = [.type = "my"];
    set_method_table(this, "my", 1);
    if (nargsin() == 1) {
        this[1] = num;
    } else {
        this[1] = 0;
    }
    return this;
}
a = my(11)
a.prop = 123
print a.prop
a.xyz = 456
print a.xyz
a[11] = 12
print a[11]

Arithmetic Binary Operators

Operator Description
__add__ Addition (a + b).
__sub__ Subtraction (a - b).
__div__ Division (a/b).
__ediv__ Elementwise Division (a ./ b).
__ldiv__ Left-Division (a b).
__mod__ Modulus (a % b).
__mul__ Multiplication (a * b).
__emul__ Elementwise Multiplication (a .* b).
__power__ Exponentiation (a ^ b).
__epower__ Elementweise Exponentiation (a .^ b).
__shl__ Left Shift (a << b).
__shr__ Right Shift (a >> b).
__and__ Logical AND (a && b).
__or__ Logicol OR (a || b).
__xor__ Exclusive OR (a @ b).
__bitand__ Bitwise AND (a & b).
__bitor__ Bitwise OR (a | b).

For all arithmetic binary operators a second form exists: __xxx2__, e.g. __add2__. This function is invoked if the left operant does not have an __add__ function.

Arithmetische unary Operators

Operator Description
__uminus__ Unary negation operator (-a).
__trans__ Transpose (a’).
__etrans__ Elementwise Transpose (a.’).
__bitcmpl__ Bitwise Complement (~a).

Compare Operators

The operators ==, != and ! have default operators which compare the pointer address of objects.

Operator Description
__lt__ Less than (a < b).
__le__ Less than or equal to (a <= b).
__eq__ Equal To (a == b).
__ne__ Not equal to (a != b).
__gt__ Greater than (a > b).
__ge__ Greater than or equal to (a >= b).
__not__ Not (!a).

For all binary operators a second form exists: __xxx2__, e.g. __gt2__. This function is invoked if the left operant does not have an __gt__ function.

9.6. Inheritance

Sometimes a group of objects have common properties such as Line, Circle and Rectangle. The common properties are position and pen. The individual properties could be the width an height of the rectangle the diameter of the circle and the end point of the line. It could make sense to create an base object for the common properties. The child objects could inherit the properties of the base object (also called parent object or Superclass object).

In the following example the base object is called shape and the objects which inherit shape are called circle and rect.

def shape_set_name(this, s)
{
    this.name = s;
}
def shape_methods()
{
    return [. set_name = "shape_set_name"];
}
def shape(x, y)
{
    this = [. x = x, y = y, name = ""];
    set_method_table(this, "shape");
    return this;
}
def circle_plot(this)
{
    printf("<circle x='%d' y='%d' d='%d'/>\n", this.x, this.y, this.d);
}
def circle_methods()
{
    return [. plot = "circle_plot"];
}
def circle(x, y, d)
{
    this = [. __parent__ = shape(x, y), d = d];
    return set_method_table(this, "circle");
}
def rect_plot(this)
{
    printf("<rect x='%d' y='%d' a='%d' b='%d'/>\n", this.x, this.y, this.a, this.b);
}
def rect_set_name(this, s)
{
    this.name = strupper(s);
}
def rect_methods()
{
    return [.     plot = "rect_plot",
                set_name = "rect_set_name"];
}
def rect(x, y, a, b)
{
    this = [. __parent__ = shape(x, y), a = a, b = b];
    return set_method_table(this, "rect");
}

9.7. Functions calls with UniScript Objects

Some programming languages have the following options to pass function parameters:

  • Named Arguments
  • Arguments with default values
  • Variable number of arguments

UniScript does not offer these technique, but they can be simulated using objects.

Named arguments:

def test_named(arg)
{
    return arg.a + arg.b;
}
test_named([.b = 1, a = 2])
test_named([.a = 2, b = 1])

Arguments with default values:

def test_default(arg)
{
    d = [.a = 11, b = 22];
    for (i in arg.keys) d[i] = arg[i];
    return d.a + d.b;
}
test_default([.a = 1000])
test_default([.b = 1000])
test_default([.a = 1, b = 2])
test_default([.])

Variable number of arguments:

def test_var(args)
{
    print "test_var"
    for (i in args.keys(1)) {
        print args[i]
    }
}
test_var([. 11111, "Hallo", ([1,2,3]), (2+3i)]);
test_var([. 1,2,3,4]);

9.8. Example

The following example is an object to read and write data to a file.

def file_methods()
{
    return [.
        read = "file_read",
        write = "file_write",
        name = "file_get_name",
        close = "file_close",
        __shl__ = "file_shl",
        __destructor__ = "file_close",
        __format__ = "file_format"];
}
def file_open(ssFileName, ssMode)
{
    fp = fopen(ssFileName, ssMode);
    if (!fp) {
        return 0;
    }
    this = [. fp = fp, name = ssFileName];
    set_method_table(this, "file");
    return this;
}
def file_format(file)
{
    return sprintf("fp = %d\nname = %s", file.fp, file.name);
}
def file_read(file, ssType, n)
{
    if (nargsin() == 2) {
        return fread(file.fp, ssType);
    } else {
        return fread(file.fp, ssType, n);
    }
}
def file_write(file, ssType, dat)
{
    return fwrite(file.fp, ssType, dat);
}
def file_get_name(file)
{
    return file["name"];
}
def file_close(file)
{
    fclose(file.fp);
}
// file << val
def file_shl(this, other)
{
    if (type(other) == "real") {
        this.write("double", other);
    } else if (type(other) == "string") {
        this.write("char", other);
    } else {
        other.write(this);
    }
    return this;
}
def f_int32_write(this, file)
{
    file.write("int", this.val);
}
def f_int32_methods()
{
    return [. write = "f_int32_write"];
}
def f_int32(v)
{
    return set_method_table([. type = "f_int32", val = v], "f_int32");
}
def test()
{
    file = file_open("d:/test.txt", "wb");
    file << [1, 3] << f_int32([4,5,6]) << "This is a test!";
    file.close();
    file = file_open("d:/test.txt", "rb");
    print file.read("double", 2);
    print file.read("int", 3);
    print file.read("char");
    file.close();
}

id-528674