9. UniScript-Objekte

In Kapitel COM-Schnittstelle wurde die Verwendung von ActiveX-Objekten in UniScript gezeigt.

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'

In den folgenden Abschnitten soll gezeigt werden, wie Objekte mit UniScript erzeugt und verwendet werden.

9.1. Einführung

UniScript-Objekte werden mit der Funktion obj_create erzeugt. Diese Funktion erzeugt ein leeres Objekt. Diesem Objekt können anschließend Variablen und Funktionen zugefügt werden.

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

Das Objekt c hat nun zwei Variablen unit und val.

Alternativ kann die kürzere Objekt-Konstruktor-Schreibweise verwendet werden.

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

Der Punkt hinter der öffnenden eckigen Klammer („[„) hat den Zweck, dass der Interpreter (und der Programmierer) erkennen kann, dass es sich nicht um ein gewöhnliches Array handelt. Ohne Punkt würde in diesem Fall eine Variant-Matrix mit 5 Elementen erzeugt:

c = [unit = "Nm", val = [1,2,3,4] ] // c ist hier kein Objekt

Objekte können an Variablen zugewiesen werden. Die Anweisung

b = c;

bewirkt, dass b und c auf das selbe Objekt zeigen. Die Anweisung b = c; kopiert also kein Objekt. Das Objekt kann über den Zeiger b oder c geändert werden.

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

Bei gewöhnlichen UniScript-Vektoren und Matrizen, werden die Elemente dagegen kopiert:

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

Schreibt man nun c = 0, zeigt c nicht mehr auf das Objekt, es ist nur noch über b erreichbar. b = 0 führt dazu, dass das Objekt gar nicht mehr erreichbar ist. Es wird dann vom sogenannten garbage collector aus dem Speicher entfernt. Der garbage collector kann auch vom Programmierer aufgerufen werden (siehe gc im Index der Online-Hilfe).

Variablen können anstatt über die Schreibweise

Zeiger-auf-Objekt "." Name = Wert

auch über die Indexschreibweise erzeugt oder geändert werden.

Anstatt

c.unit = "Nm"

kann auch

c["unit"] = "Nm"

geschrieben werden. Falls der Variablen-Name kein gültiger UniScript-Bezeichner ist, z. B. weil er Leerzeichen, Sonderzeichen oder Umlaute enthält, oder der Bezeichner ein UniScript-Schlüsselwort ist, ist nur die zweite Schreibweise möglich.

Ein Objekt führt intern zwei Tabellen, eine mit Namen (Strings) und eine mit Zahlen-Schlüsseln.

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

c enthält nun zwei Key-Wert-Paare, eins in der Tabelle mit Zahlenschlüsseln und das andere in der Tabelle mit den Zeichenketten-Schlüsseln (String-Keys).

In der Objekt-Konstruktor-Schreibweise kann dies auch in der Form

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

geschrieben werden.

Die Zahlenkeys sind vom Typ double. Sie können auch negativ sein und brauchen auch keine ganzen Zahlen zu sein:

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

UniScript-Objekte enthalten Funktionen, die auch als Member-Funktionen oder Methoden des Objekts bezeichnet werden.

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

liefert alle Schlüssel des Objekts c aus der Tabelle mit Zahlenschlüsseln. k ist ein Vektor mit den Schlüsselwerten.

In der Online-Hilfe finden Sie die Beschreibung aller Methoden unter den Namen obj_xxx, also z. B. obj_keys.

Es ist kein Fehler auf einen Index zuzugreifen, für den kein Wert existiert: a = c[111] liefert 0.0. Mit der Methode has_key (obj_has_key) kann geprüft werden, ob der Key existiert: c.has_key(123) liefert TRUE (1), wenn c[123] existiert und FALSE (0), wenn c[123] nicht existiert.

Objekte können als Variablen Zahlen, komplexe Zahlen und Strings, sowie Vektoren und Matrizen dieser drei Typen enthalten. Außerdem Zeiger auf ActiveX-Objekte (siehe COM-Schnittstelle) und Variant-Matrizen.

Objekte können auch Zeiger auf Objekte enthalten. Dadurch sind Datenstrukturen wie Listen möglich:

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

bzw. in Objekt-Konstruktor-Schreibweise:

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

9.2. Objekt-Konstruktor-Schreibweise

Grammatik

prop_val:
        NUMBER                { counter = counter + 1; } /* Regel 1 */
        |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; }           /* Regel 11 */
        prop_list
        "]"
        ;

Beispiele:

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

Weitere Beispiele:

a = [. 1, 2, 3]

entspricht:

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

entspricht:

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

entspricht:

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

9.3. Standard-Methoden

Die folgenden Funktionen können immer in der Form:

obj_xxx(o, ...)

oder, kürzer, in der Form:

o.xxx(...)

geschrieben werden. Also z. B. a = o.lookup("key") anstatt a = obj_lookup(o, "key").

Hat die Funktion nur einen Parameter (obj) können bei der Punktschreibweise die Klammern weg gelassen werden:

n = o.count;
oParent = o.parent;
  UniScript-Objekt-Funktionen
obj_copy obj_copy erzeugt eine Kopie eines Objekts.
obj_count obj_count liefert die Anzahl an Variablen in einem Objekt.
obj_count_num obj_count_num liefert die Anzahl an Elementen mit Nummern-Keys in einem Objekt.
obj_count_str obj_count_str liefert die Anzahl an Elementen mit String-Keys in einem Objekt.
obj_create obj_create erzeugt ein UniScript-Objekt.
obj_has_key obj_has_key prüft, ob der gegebene Schlüssel (key) existiert.
obj_info obj_info liefert einen String der Form „obj-name,hex-adresse“.
obj_keys obj_keys liefert die Keys (oder eine Auswahl an Keys) eines Objekts.
obj_load obj_load lädt ein mit obj_save gespeichertes Objekt aus einer Datei oder aus einem String.
obj_lookup obj_lookup liefert den Wert zu einem Schlüssel (Key).
obj_methods obj_methods liefert das Objekt mit den Methoden (Member-Funktionen) des Objekts.
obj_parent obj_parent liefert das Parent-Objekt oder 0 wenn das Objekt kein Parent-Objekt hat.
obj_remove obj_remove entfernt ein key-value-Paar aus dem Objekt.
obj_save obj_save speichert ein Objekt in einer Datei oder in einer Zeichenkette als XML-Datei oder Binärdatei.
obj_set_at obj_set_at fügt ein Schlüssel-Wert-Paar in das Objekt ein. Falls der Schlüssel bereits existierte wird der Wert durch den neuen Wert ersetzt.
obj_set_methods obj_set_methods setzt Methoden (Member-Funktionen) für das Objekt.
obj_set_parent obj_set_parent setzt das Parent-Objekt.
obj_set_str_return obj_set_str_return legt fest, ob obj_lookup einen leeren String oder die Zahl 0.0 zurück gibt oder eine Ausnahme erzeugt, wenn ein Schlüssel nicht vorhanden ist.
set_method_table set_method_table setzt Methoden (Member-Funktionen) für ein Objekt.

9.4. Methoden für Objekte

Neben den Standard-Methoden wie copy, keys, set_at, deren Beschreibung Sie in der Online-Hilfe unter obj_copy, obj_keys, obj_set_at finden, können einem Objekt weitere Methoden zugefügt werden.

Um ein Objekt um Funktionen (auch Member-Funktionen oder Methoden genannt) zu erweitern muss ein Objekt erzeugt werden, dass die Namen der Methoden den UniScript-Funktionsnamen zuordnet.

Beispiel:

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

Die Funktion „my_test“ kann folgendermaßen geschrieben werden:

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

Falls nun die Anweisungen

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

ausgeführt werden, versucht der UniScript-Interpreter die Funktion „my_test“ aufzurufen. Der Interpreter übergibt der Funktion „my_test“ als ersten Parameter zusätzlich das Objekt obj als ersten Parameter.

Die Funktion my_create bezeichnet man als Konstruktor-Funktion, da sie ein Objekt konstruiert.

Sie erzeugt zunächst ein UniScript-Objekt:

this = [.];

Der Aufruf

set_method_table(this, "my");

bewirkt, dass die Funktion my_methods aufgerufen wird. my_methods erzeugt ein Objekt mit den Methodennamen. set_method_table kopiert dieses Objekt in eine globale Tabelle (g_method_table) und setzt das Objekt mit den Methoden für this mit der Funktion obj_set_methods. Alle mit my_create() erzeugten Objekte erhalten das selbe Objekt mit den Methoden, d. h. my_methods() wird nur einmal aufgerufen.

Zum Schluß gibt der Konstruktor das erzeugte Objekt zurück:

return this;

9.5. Überladen von Operatoren

Die meisten Operatoren können nicht direkt auf Objekte angewendet werden.

Die folgenden Zeilen führen zu einem Laufzeitfehler „+: Operand type incorrect“:

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

Die Anweisung

a == b

liefert den Wert 0, d.h. a ist nicht gleich b. Setzt man b.val = 123 liefert a == b immer noch FALSE. Der Grund ist, das der Gleichheitsoperator nur überprüft ob a und b auf das selbe Objekt zeigen. D.h. nach den Anweisungen

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

bzw.

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

ist der Ausdruck a == b erfüllt. Entsprechend verhält sich der Ungleich-Operator (a != b). Der Not-Operator (!a) dient dazu festzustellen, ob die Objekt-Erzeugung erfolgreich war.

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

Man könnte auch schreiben:

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

Ein Zeiger auf ein Objekt kann also auch mit einer Zahl verglichen werden, wobei die 0 die einzige sinnvolle Zahl ist.

Die Bedeutung dieser Operatoren kann umdefiniert werden.

Beispiel:

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 Anmerkung
__destructor__ Destruktor. Diese Funktion wird aufgerufen, wenn der Garbage-Collector ein Objekt zerstört.
__print__ print a.
__format__ Zeichenkette für Debugger erzeugen.
__getproperty__ v = a.b auswerten.
__setproperty__ a.b = v auswerten.
__getprop__ v = a.b auswerten.
__setprop__ a.b = v auswerten.
__veceval__ a[idx] auswerten.
__vecassign__ a[idx] = v auswerten.
__catcol__ Verkettung ([a,b]).
__catcol2__ Verkettung ([a,b]). Wird aufgerufen, wenn a keine __catcol__-Methode hat.
__catrow__ Verkettung ([a;b]).
__catrow2__ Verkettung ([a;b]). Wird aufgerufen, wenn a keine __catrow__-Methode hat.

__setprop__

Mit __setprop__ kann eine UniScript-Funktion aufgerufen werden, wenn eine Anweisung der Form obj.prop = value ausgeführt wird.

Beispiel:

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

Innerhalb der property-set-Funktion darf die Form obj.property = value nicht verwendet werden, sonst würde sich die Funktion endlos rekursiv aufrufen. Es sollte eine der Formen

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

verwendet werden.

__setproperty__

__setproperty__ hat einen Parameter zusätzlich: ssProp.

Beispiel:

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

__getprop__

__getprop__ wird verwendet, wenn ein Ausdruck der Form obj.prop auf der rechten Seite verwendet wird: val = obj.prop.

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

__getproperty__

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

__veceval__

Wird bei Ausdrücken wie v = obj[index] aufgerufen.

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

__vecassign__

Wird bei Ausdrücken wie obj[index] = val aufgerufen.

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

Beispiel:

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  [.
    __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]

Arithmetische binäre Operatoren

Operator Anmerkung
__add__ Additions-Operator (a + b).
__sub__ Subtraktions-Operator (a - b).
__div__ Divisision (a/b).
__ediv__ Elementweise Divisision (a ./ b).
__ldiv__ Links-Division (a b).
__mod__ Modulus-Operator (a % b).
__mul__ Multiplikation (a * b).
__emul__ Elementweise Multiplikation (a .* b).
__power__ Exponentiation (a ^ b).
__epower__ Elementweise Exponentiation (a .^ b).
__shl__ Shift-Links (a << b).
__shr__ Shift-Rechts (a >> b).
__and__ Und-Operator (a && b).
__or__ Oder-Operator (a || b).
__xor__ Exklusiv-Oder-Operator (a @ b).
__bitand__ Bitweises And (a & b).
__bitor__ Bitweises Or (a | b).

Von allen arithmetischen binäre Operatoren existiert die Form __xxx2__, z. B. __add2__. Diese Funktionen werden aufgerufen, wenn der linke Operand keine __add__-Funktion besitzt.

Arithmetische unäre Operatoren

Operator Anmerkung
__uminus__ Vorzeichen-Minus (-a).
__trans__ Transponierung (a‘).
__etrans__ Elementweise Transponierung (a.‘).
__bitcmpl__ Bitweises Complement (~a).

Vergleichsoperatoren

Die Operatoren ==, != und ! haben Default-Operatoren, die die Adressen der Objekte vergleichen.

Operator Anmerkung
__lt__ Kleiner-Als-Operator (a < b).
__le__ Kleiner-Gleich-Operator (a <= b).
__eq__ Gleichheitsoperator (a == b).
__ne__ Ungleich-Operator (a != b).
__gt__ Größer-Als-Operator (a > b).
__ge__ Größer-Gleich-Operator (a >= b).
__not__ Nicht-Operator (!a).

Von allen binären Vergleichsoperatoren existiert die Form __xxx2__, z. B. __gt2__. Diese Funktionen werden aufgerufen, wenn der linke Operand keine __gt__-Funktion besitzt.

9.6. Vererbung

Manchmal hat man eine Gruppe von Objekten, die gemeinsame Eigenschaften haben. Beispiel: Linie, Kreis, Rechteck. Die gemeinsamen Eigenschaften sind z. B. die Position, oder die Dicke der Randlinie. Die individuellen Eigenschaften wären beim Rechteck die Seitenlängen a und b, beim Kreis der Durchmesser d und bei der Linie die Endposition p2. Es wäre dann sinnvoll ein Objekt zu erzeugen mit den gemeinsamen Eigenschaften, das als Basis-Objekt bezeichnet wird. Die Child-Objekte erben die Eigenschaften des Basis-Objekts (auch Parent-Objekt oder Superclass-Objekt genannt).

In diesem Beispiel soll das Basis-Objekt shape genannt werden und die Objekte, die die Eigenschaften von shape erben sollen, heissen circle und 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. Funktionsaufrufe mit UniScript-Objekten

Einige Programmiersprachen haben bei Funktionsaufrufen die folgenden Möglichkeiten:

  • Benannte Argumente
  • Argumente mit Defaultwerten
  • Variable Länge der Parameterliste

In UniScript sind diese Techniken nicht möglich. Sie können jedoch mit Hilfe von Objekten simuliert werden.

Benannte Argumente:

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

Argumente mit Defaultwerten:

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 Länge der Parameterliste:

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. Beispiel

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