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