.. highlightlang:: us .. index:: CreateObject, Excel, obj_create .. _uniscript-objects: UniScript Objects ================= In chapter :ref:`activex-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. .. _introduction-objects: Introduction ------------ UniScript-Objects are created using the :ref:`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: .. index:: gc, garbage collector, pointer, objects :: 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 :ref:`gc` function. (see :ref:`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. .. index:: object constructor 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. :ref:`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 :ref:`has_key ` (:ref:`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]] .. _object-constructor-syntax: 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; .. index:: obj_copy, obj_count, obj_count_num, obj_count_str, obj_create .. index:: obj_has_key, obj_info, obj_keys, obj_load, obj_lookup, obj_parent .. index:: obj_remove, obj_save, obj_set_at, obj_set_methods, obj_set_parent .. index:: obj_set_str_return, set_method_table .. _standard-methods: 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; .. us.makeindex obj, UniScript-Object-Functions .. include:: ../ftab/obj.ftab .. _methods-for-objects: Methods for Objects ------------------- Beside the standard methods as ``copy``, ``keys``, ``set_at``, etc. (see help for :ref:`obj_copy`, :ref:`obj_keys`, :ref:`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 = [.]; .. index:: set_method_table The function call :: set_method_table(this, "my"); will invoke the ``my_methods`` functions. ``my_methods`` creates the object with the methods names. :ref:`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 :ref:`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; .. index:: overloading .. _operator-overloading: 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()); .. index:: __destructor__, __print__, __format__, __setproperty__ .. index:: __getprop__, __setprop__, __veceval__, __vecassign__ .. index:: __catcol__, __catcol2__, __catrow__ .. list-table:: :header-rows: 1 * - 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. .. index:: __setprop__ ``__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; } .. index:: __getprop__ ``__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]; } .. index:: __veceval__ ``__veceval__`` Will be executed for expressions of the form ``v = obj[index]``. :: def my_veceval(obj, index) { return obj.lookup(index); } .. index:: __vecassign__ ``__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] .. index:: __add__, __sub__, __div__, __ediv__, __ldiv__, __mod__ .. index:: __mul__, __emul__, __power__, __epower__, __shl__, __shr__ .. index:: __and__, __or__, __xor__, __bitand__, __bitor__ **Arithmetic Binary Operators** .. list-table:: :header-rows: 1 * - 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** .. list-table:: :header-rows: 1 * - 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. .. list-table:: :header-rows: 1 * - 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. .. _inheritance: 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("\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("\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"); } .. _functions-calls-with-uniscript-objects: 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]); .. _example-objects: 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(); } :sub:`id-528674`