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 creates a copy of an object. |
|
obj_count returns the number of variables in an object. |
|
obj_count_num returns the number of number keys in an object. |
|
obj_count_str returns the number of string keys in an object. |
|
obj_create creates an UniScript object. |
|
obj_has_key checks if a given key exists. |
|
obj_info returns a string of the form “obj-name,hex-address”. |
|
obj_keys returns all keys or a range of keys for the given object. |
|
obj_load loads an object saved with obj_save from the hard drive. |
|
obj_lookup returns the value of a given key. |
|
obj_methods returns an object with methods (member functions) for the given object. |
|
obj_parent returns the parent object or 0 if the object does not have a parent. |
|
obj_remove removes a key-value pair from an object. |
|
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 adds a key-value pair to the given object. If the key already exists the value will be overwritten. |
|
obj_set_methods sets the methods (member functions) for the given object. |
|
obj_set_parent sets the parent object. |
|
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 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. This function is invoked when the Garbage-Collector destroys the object. |
|
print a. |
|
Create a string for the debugger. |
|
eval v = a.b |
|
eval a.b = v |
|
eval v = a.b |
|
eval a.b = v |
|
eval a[idx] |
|
eval a[idx] = v |
|
concatenate ([a,b]). |
|
concatenate ([a,b]). Is invoked if a does not have a |
|
concatenate ([a;b]). Is invoked if a does not have a |
__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 |
---|---|
|
Addition (a + b). |
|
Subtraction (a - b). |
|
Division (a/b). |
|
Elementwise Division (a ./ b). |
|
Left-Division (a b). |
|
Modulus (a % b). |
|
Multiplication (a * b). |
|
Elementwise Multiplication (a .* b). |
|
Exponentiation (a ^ b). |
|
Elementweise Exponentiation (a .^ b). |
|
Left Shift (a << b). |
|
Right Shift (a >> b). |
|
Logical AND (a && b). |
|
Logicol OR (a || b). |
|
Exclusive OR (a @ b). |
|
Bitwise AND (a & b). |
|
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 |
---|---|
|
Unary negation operator (-a). |
|
Transpose (a’). |
|
Elementwise Transpose (a.’). |
|
Bitwise Complement (~a). |
Compare Operators
The operators ==, != and ! have default operators which compare the pointer address of objects.
Operator |
Description |
---|---|
|
Less than (a < b). |
|
Less than or equal to (a <= b). |
|
Equal To (a == b). |
|
Not equal to (a != b). |
|
Greater than (a > b). |
|
Greater than or equal to (a >= b). |
|
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