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 erzeugt eine Kopie eines Objekts. |
|
obj_count liefert die Anzahl an Variablen in einem Objekt. |
|
obj_count_num liefert die Anzahl an Elementen mit Nummern-Keys in einem Objekt. |
|
obj_count_str liefert die Anzahl an Elementen mit String-Keys in einem Objekt. |
|
obj_create erzeugt ein UniScript-Objekt. |
|
obj_has_key prüft, ob der gegebene Schlüssel (key) existiert. |
|
obj_info liefert einen String der Form „obj-name,hex-adresse“. |
|
obj_keys liefert die Keys (oder eine Auswahl an Keys) eines Objekts. |
|
obj_load lädt ein mit obj_save gespeichertes Objekt aus einer Datei oder aus einem String. |
|
obj_lookup liefert den Wert zu einem Schlüssel (Key). |
|
obj_methods liefert das Objekt mit den Methoden (Member-Funktionen) des Objekts. |
|
obj_parent liefert das Parent-Objekt oder 0 wenn das Objekt kein Parent-Objekt hat. |
|
obj_remove entfernt ein key-value-Paar aus dem Objekt. |
|
obj_save speichert ein Objekt in einer Datei oder in einer Zeichenkette als XML-Datei oder Binärdatei. |
|
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 setzt Methoden (Member-Funktionen) für das Objekt. |
|
obj_set_parent setzt das Parent-Objekt. |
|
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 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 |
---|---|
|
Destruktor. Diese Funktion wird aufgerufen, wenn der Garbage-Collector ein Objekt zerstört. |
|
print a. |
|
Zeichenkette für Debugger erzeugen. |
|
v = a.b auswerten. |
|
a.b = v auswerten. |
|
v = a.b auswerten. |
|
a.b = v auswerten. |
|
a[idx] auswerten. |
|
a[idx] = v auswerten. |
|
Verkettung ([a,b]). |
|
Verkettung ([a,b]). Wird aufgerufen, wenn a keine |
|
Verkettung ([a;b]). |
|
Verkettung ([a;b]). Wird aufgerufen, wenn a keine |
__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 |
---|---|
|
Additions-Operator (a + b). |
|
Subtraktions-Operator (a - b). |
|
Divisision (a/b). |
|
Elementweise Divisision (a ./ b). |
|
Links-Division (a b). |
|
Modulus-Operator (a % b). |
|
Multiplikation (a * b). |
|
Elementweise Multiplikation (a .* b). |
|
Exponentiation (a ^ b). |
|
Elementweise Exponentiation (a .^ b). |
|
Shift-Links (a << b). |
|
Shift-Rechts (a >> b). |
|
Und-Operator (a && b). |
|
Oder-Operator (a || b). |
|
Exklusiv-Oder-Operator (a @ b). |
|
Bitweises And (a & b). |
|
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 |
---|---|
|
Vorzeichen-Minus (-a). |
|
Transponierung (a‘). |
|
Elementweise Transponierung (a.‘). |
|
Bitweises Complement (~a). |
Vergleichsoperatoren
Die Operatoren ==
, !=
und !
haben Default-Operatoren, die die
Adressen der Objekte vergleichen.
Operator |
Anmerkung |
---|---|
|
Kleiner-Als-Operator (a < b). |
|
Kleiner-Gleich-Operator (a <= b). |
|
Gleichheitsoperator (a == b). |
|
Ungleich-Operator (a != b). |
|
Größer-Als-Operator (a > b). |
|
Größer-Gleich-Operator (a >= b). |
|
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