Content-type: text/html
Das Kommando esh akzepiert Kommentare im Stil von C/C++ und verwendet einen Präprozessor ähnlich zu C/C++. Dieser wird weiter unten beschrieben. Eine Zeile, die mit #! startet, wird nicht von esh interpretiert. Sie dient primär dazu, den Interpreter für die Ausführung des Skripts zu definieren. Die Interpreterkennung hat die Form
Im äußersten Modus (außerhalb eines Blocks oder eines Funktionsrumpfes) wird jeder Ausdruck sofort nach dem Lesen ausgewertet. Falls der Ausdruck nicht durch einen Strichpunkt abgeschlossen wurde und ein Ergebnis liefert, wird dieses, gefolgt vom Abschlußzeichen, ausgegeben.
Zum Beispiel wird die Zeile
Falls esh ohne Skriptnamen aufgerufen wird, oder falls anstelle des Skriptnamens ein einzelnes Minus steht, werden die Befehlszeilen von der Standardeingabe gelesen. Falls die Standardeingabe und die Standardausgabe mit einem Terminal verbunden sind, läuft esh interaktiv und readline wird für die Eingabe vom Terminal verwendet. Die Steuerzeichen von readline sind aktiv und ein Rufzeichen ! zu Beginn einer Zeile erlaubt die Eingabe von History-Befehlen und Systemaufrufen.
Die Verwendung von readline im interaktiven Modus und die automatische Ausgabe von Resultaten machen esh zu einem komfortablen Tischrechner.
Der EFEU-Befehlsinterpreter ist mit C-Bibliotheksfunktionen implementiert, esh ist ein einfaches Programm, das diese Funktionen nutzt. Der Interpreter kann Datenpointer gemeinsam mit C-Funktionen nutzen und mit eigenen Funktionen und Datentypen erweitert werden. Er eignet sich zur Verarbeitung von Konfigurationsdateien und zum Testen von Funktionen.
Falls EFEU für gemeinsam genutzte Bibliotheken kompiliert wurde, kann esh zur Laufzeit erweitert werden. Falls ein dynamisches Linken von Funktionen nicht möglich ist, können die Erweiterungen in einer Kopie von esh.c eingebaut werden.
Der verwendete Präprozessor hat eine ähnliche Syntax wie der C-Präprozessor. Es gibt jedoch einen wesentlichen Unterschied: Der Präprozessor wird nicht zur Vorverarbeitung der gesamten Datei verwendet, sondern ist zeilenweise implementiert und arbeitet direkt mit dem Befehlsinterpreter zusammen. Insbesonders kann durch Verändern von Variablen im äußeren Modus auch die Verarbeitung nachfolgender Präprozessorzeilen beeinflußt werden.
Eine Direktive (Präprozessoranweisung) wird mit einem Gittersymbol # eingeleitet. Danach folgt der Name der Direktive und die zugehörigen Argumente.
Eine Präprozessorzeile, bei der nach dem Startzeichen (#) ein Sonderzeichen folgt, wird ignoriert. Insbesonders gilt das für die Interpreterkennung #!.
In esh ist die folgende Konstruktion erlaubt:
str header = paste("/", "SubDir", "MyHeader");
#include "<" + header + ">"
Die Direktiven
Die einfachste Form eines bedingten Ausdrucks hat die Form:
Ein etwas komplexerer Ausdruck könnte etwa so aussehen:
Wie bereits im Abschnitt "Einbinden von Dateien" erwähnt, können in expr beliebige Variablen oder Funktionen, die vor der bedingten Verarbeitung erklärt wurden, verwendet werden.
Der Name eines Makros muß mit einem Buchstaben oder einem Unterstreichungszeichen beginnen und darf nur Buchstaben, Ziffern oder Unterstreichungszeichen enthalten.
In esh werden Makros nur selten verwendet. In fast allen Fällen sind Variablen und Funktionen die bessere Lösung. Normalerweise werden Makros nur zum Absichern von Headerdateien gegen mehrfache Einbindung verwendet.
Ein Makrodefinition kann mit der #undef Direktive gelöscht werden.
Zum Beispiel deklariert
int x; double a, b; x = (int y = 5);
zunächst die Ganzzahlvariable x und die Gleitkommavariablen a und b. Anschließend wird die Gaanzzahlvariable y mit dem Initialisierungswert 5 eingerichtet und der Rückgabewert (der Wert 5) der Variablen x zugewiesen.
Variablen werden in einer Hierarchie von Variablentabellen gespeichert. An oberster Stelle steht die globale Variablentabelle, an unterster Stelle die lokale oder aktuelle Variablentabelle. Neue Variablen werden in der lokalen Variablentabelle generiert. Die Suche von Variablen erfolgt von unten nach oben. Im äußersten Verarbeitungsmodus stimmt die lokale Variablentabelle mit der globalen Variablentabelle überein.
Zu jedem vordefinierten Datentyp im EFEU-Interpreter gibt es einen zugehörigen Datentyp in C. Der Interpreter kennt keine Pointer, aber manche Datentypen werden durch Pointer auf C-Datentypen repräsentiert.
Der Interpreter unterscheidet zwischen L-Werten und Konstanten. Ein L-Wert ist alles, was auf der linken Seite einer Zuweisung stehen darf. Typische L-Werte sind Variablen. Das Resultat eines Terms oder eines Funktionsaufrufes kann ein L-Wert sein.
| esh Type | C Type |
| bool | int |
| int8_t | int8_t |
| int16_t | int16_t |
| int | int |
| int32_t | int32_t |
| int64_t | int64_t |
| varint | int64_t |
| uint8_t | uint8_t |
| uint16_t | uint16_t |
| unsigned | unsigned int |
| uint32_t | uint32_t |
| uint64_t | uint64_t |
| varsize | uint64_t |
Die Syntax von Ganzzahlkonstanten ist wie in C/C++. Die Schlüsselworter true (Ganzzahlwert 0) und false (Ganzzahlwert 1) sind vom Typ bool.
Zeichenketten (Strings) werden in esh völlig anders als in C implementiert. Sie werden nicht als Felder vom Type char eingerichtet, sondern haben den Datentyp str, der über einen char Pointer eingerichtet wird. Falls einem String ein Wert zugeordnet wird, wird die gesamte Zeichenkette und nicht die Adresse kopiert. Stringkopien erfolgen immer mit dynamischer Speicherzuweisung und es gibt eine Speicherbereinigung (Garbage-Collection) für Strings und generell für alle Objekte mit dynamisch zugewiesenem Speicherbereich.
Zeichenkonstanten werden von einfachen, Stringkonstanten von doppelten Hochkommas begrenzt. Der Gegenschrägstrich wird wie in C als Fluchtsymbol verwendet.
Für lange Stringkonstanten gibt es das Schlüsselwort string, welches in der folgenden Form verwendet wird:
Unmittelbar nach ! muß ein Zeilenvorschub stehen und ! muss das erste Zeichen der letzten Zeile sein. Eine so definierte Zeichenkonstante enthält immer einen Zeilenvorschub am Ende. Der Gegenschrägstrich wird nur mehr zum Schutz eines ! am Anfang einer Zeile verwendet. Diese Konstruktion kann an beliebiger Stelle innerhalb eines Ausdrucks stehen.
Innerhalb solcher Stringdefinitionen werden Kommentare im C/C++-Style überlesen und Präprozessor-Direktiven interpretiert.
So kann zum Beispiel mit
der Inhalt der Datei file in den String s geladen werden. Aber Achtung: Die Datei file wird vom Präprozessor überarbeitet.
Anmerkung: In EFEU (und damit auch in esh) können Nullstrings (Zeichenpointer auf NULL) wie gewöhnliche Strings verwendet werden. Nullstrings werden jedoch anders als Leerstrings (Zeichenketten, die nur aus der Abschlußnull bestehen) behandelt. Die EFEU-Bibliotheken beinhalten eine Reihe von Hilfsfunktionen zur Handhabung von Zeichenketten mit dynamisch zugewiesenem Speicher. Auch können sie mit Stringkonstanten gemischt werden. Die Speicherverwaltungsfunktionen von EFEU wissen, ob der Speicher eines Strings freigegeben werden kann.
Der Datentyp _Ref_ und alle anderen Datentypen mit Pointerrepräsentation (wie str) sind wiederum vom Datentyp _Ptr_ abgeleitet, dieser ist auch der Typ der Konstanten NULL.
Datentypen mit einem Unterstreichungszeichen am Anfang und Ende des Namens sind für die interne Verwendung reserviert. Normalerweise werden keine Variablen dieser Typen deklariert. Aber sie können in Argumentlisten von virtuellen Funktionen auftreten, etwa um zwischen der Konstanten NULL (vom Type _Ptr_) und einer Zeichenkette, die mit NULL initialisiert wurde, zu unterscheiden.
Jedes Objekt vom Type List_t hat die folgenden Komponenten:
Im ersten Fall, und falls das Datenfeld mit einer Liste von Werten initialisiert wird, kann dim entfallen und die Größe des Datenfeldes wird durch die Zahl der Elemente der Liste bestimmt. Im zweiten Fall wird implizit ein neuer Datentyp eingerichtet. Falls hier keine Größe oder 0 angegeben wird, handelt es sich um einen Vektor variabler Länge. Die Größe wird dynamisch angepaßt.
Falls mehr als eine Dimension benötigt wird, wird eine Deklaration der Form:
Datenfelder werden bei der Verwendung immer in ein Objekt vom Typ EfiVec gepackt. Ein Datenfeld kann in eine Liste konvertiert werden und die Werte eines Datenfeldes können durch Zuweisung einer Liste verändert werden. Falls eine Liste weniger Elemente enthält, als das Datenfeld auf der linken Seite, werden nur die zugehörigen Elemente verändert.
Neben den gewöhnlichen Datenfeldern stehen in EFEU noch die folgenden Datentypen für ein mächtigeres Hantieren mit Daten zur Verfügung:
Der einfachste Weg um einen neuen Datentyp einzurichten, ist typedef, z.B:
Strukturen werden mit der struct Anweisung erzeugt. Die Syntax ist
Die folgenden zwei Datentypen
struct T1 {
int a;
int b;
}
struct T2 : int a {
int b;
}
haben gleiche Komponenten, aber nur T2 kann stellvertretend für ein int verwendet werden.
Jeder zuvor definierte Datentyp kann bei der Definition einer Datenstruktur verwendet werden. Es gibt aber keine Vorwärtsdeklaration.
Jedes Objekt mit einem Strukturdatentyp kann in eine Liste umgewandelt werden und jede Liste läßt sich in ein Strukturobjekt konvertieren.
Beispiel für eine etwas komplexere Struktur:
struct MyDataType {
int i;
double d;
str s;
int v[10];
};
Datentypen müssen nicht benannt werden. Eine Variable kann z.B. auch mit einer namenlosen Struktur eingerichtet werden:
struct {
int i;
double d;
} data;
Falls eine gleichwertige, benannte Struktur existiert, wird diese Stellvertretend eingesetzt. Sollten mehr als zwei gleichwertige Strukturen existieren, ist undefiniert, welche zum Zug kommt. Zwei Strukturen sind gleichwertig, wenn alle Komponenten gleichen Namen und Datentyp haben.
Der EFEU Interpreter unterstützt Aufzählungstypen. Die Syntax ist:
Die folgende Anweisung:
Color c1 = "Red"; Color c2 = 0; str s = Color::Red; int n = Color::Red;
Die Funktion enumlist(Typ) liefert eine Liste aller Kennungen des Aufzählungstypes Typ oder eine leere Liste, falls Typ keine Kennungen hat oder kein Aufzählungstype ist. Vergleiche dazu auch den Abschnitt KLASSIFIKATIONEN weiter unten.
Die Möglichkeiten dieser Datentypkonstruktionen werden am besten an einem Beispiel demonstriert:
Der folgende Code
construct DATA : int a, double b {
str date = today();
a;
data = {
double x = 3 * a;
y = 2 * b;
};
}
generiert die Datenstruktur
struct DATA {
str date;
int a;
struct {
double x;
double y;
} data;
}
und die Konstruktionsfunktionen
virtual DATA DATA (List_t) virtual DATA DATA (int a, double b)
Die Variante mit der Liste als Argument wird für jede Struktur eingerichtet. Die spezifisch mit construct eingerichtete Version hat die Parameter int a, double b, wie sie in der ersten Datenzeile angegeben wurde.
Der Aufruf DATA(2, 3.5) generiert ein Objekt vom Typ DATA mit den folgenden Werten:
{"2011-12-04", 2, {6.00000, 7.00000}}
Die allgemeine Syntax ist:
Die Variablenliste varlist kann auch leer sein. In diesem Fall können bei expr nur globale Variablen genutzt werden.
Diese Form der Datentypkonstruktion wird implizit bei der Aufbereitung und Unmformung von EDB-Datenfiles genutzt.
Die nachfolgenden Tabellen zeigen die verfügbaren Operatoren von esh, sie sind absteigend nach ihrer Priorität aufgelistet. Operatoren, die nicht durch eine horizontale Linie voneinander getrennt sind, haben die gleiche Priorität.
| Linke Operatoren | ||
| :: | global | ::name |
| ++ | pre increment | ++lvalue |
| - | pre decrement | -lvalue |
| ~ | complement | ~expr |
| ! | not | !expr |
| - | unary minus | -expr |
| + | unary plus | +expr |
| { | list grouping | { expr [, expr ] } |
| ( | grouping | ( expr ) |
| [ | expression | [ expr ] |
| () | cast (type conversion) | (type) expr |
| () | lvalue cast | (type &) expr |
Der "expression" Operator liefert einen Ausdruck ohne ihn auszuwerten. Er kann einer Variablen zugewiesen oder einer Funktion für eine spätere Auswertung übergeben werden.
Ein Datentyp, der von runden Klammern eingeschlossen ist, bildet einen Cast-Operator, der eine Umwandlung des nachfolgenden Terms auf den gewünschten Datentyp erzwingt.
Der EFEU-Interpreter erlaubt auch casts von L-Werten. Diese sind für alle Datentypen zulässig, für die es wechselseitige Konvertierungen gibt.
Beispiel: Durch die Anweisungen
str s = "5"; (int &) s++;
wird die Zeichenkette s auf "6" gesetzt.
| Rechte Operatoren | ||
| ++ | post increment | lvalue++ |
| - | post decrement | lvalue- |
| :: | scope resolution | type::name |
| :: | variable selection | vartab::name |
| . | member selection | expr.name |
| [] | subscripting | expr[expr] |
| () | function call | expr(list) |
| Arithmetische Operatoren | ||
| * | multiply | expr * expr |
| / | division | expr / expr |
| % | modulo (remainder) | expr % expr |
| + | add (plus) | expr + expr |
| - | subtract | expr - expr |
| << | shift left | expr << expr |
| >> | shift right | expr >> expr |
| Vergleichsoperatoren | ||
| < | less than | expr < expr |
| <= | less than or equal | expr <= expr |
| > | greater than | expr > expr |
| >= | greater than or equal | expr >= expr |
| == | equal | expr == expr |
| != | not equal | expr != expr |
| Bitoperatoren | ||
| & | bitwise AND | expr & expr |
| ^ | bitwise exclusive OR | expr ^ expr |
| | | bitwise inclusive OR | expr | expr |
| Logische Operatoren | ||
| && | logical AND | expr && expr |
| || | logical OR | expr || expr |
| Bedingter Ausdruck und Bereichsoperator | ||
| ? : | conditional operator | cond ? expr1 : expr2 |
| : : | range operator | start : end [ : step ] |
| Zuweisungsoperatoren | ||
| = | simple assignment | lvalue = expr |
| *= | multiply and assign | lvalue *= expr |
| /= | divide and assign | lvalue /= expr |
| %= | modulo and assign | lvalue %= expr |
| += | add and assign | lvalue += expr |
| -= | subtract and assign | lvalue -= expr |
| <<= | shift left and assign | lvalue <<= expr |
| >>= | shift right and assign | lvalue >>= expr |
| &= | AND and assign | lvalue &= expr |
| ^= | exclusive OR and assign | lvalue ^= expr |
| |= | inclusive OR and assign | lvalue |= expr |
| Listentrenner | ||
| , | list separator | expr , expr |
Falls der Rückgabewert nicht verwendet wird (die übliche Anwendung), gibt es keinen Unterschied im Verhalten vom Komma-Operator und Listentrenner.
Bei Schleifen kann ein Block vorzeitig mit der break Anweisung verlassen werden. Die continue Anweisung startet einen neuen Zyklus.
Die Syntax der Switch-Anweisung lautet:
switch (expr)
{
label:
cmdlist
label:
cmdlist
}
wobei label wahlweise case val oder default sein kann.
Der Ausdruck val wird dabei bereits beim Einlesen der Anweisung
ausgewertet. Der Wert von expr wird mit den einzelnen Werten der
Labels in der Reihenfolge des Auftretens verglichen.
Falls der Vergleich wahr liefert, werden alle nachfolgenden
Anweisungen bis zum Erreichen eines der Anweisungen
break, continue, return oder dem Ende des Switch-Blocks
ausgeführt. Falls keiner der Labels mit expr übereinstimmt,
werden die Anweisungen nach default (falls vorhanden) ausgeführt.
Im Gegensatz zu C kann jeder Datentyp, für den der Vergleichsoperator == definiert ist, für Switch-Anweisungen verwendet werden. Insbesonders können Zeichenketten und reguläre Ausdrücke in Switch-Anweisungen verwendet werden.
Die folgenden Funktionsdefinitionen sind gleichwertig:
int f (int x) x + 1;
int f (int x) return x + 1;
inline int f (int x) { return x + 1; }
Im EFEU-Befehlsinterpreter hat das Schlüsselwort inline primär etwas mit Sichtbarkeit zu tun. Eine inline Funktion sieht alle Variablentabellen, die auch in der Zeile mit dem Funktionsaufruf sichtbar waren. Alle Funktionen, die nur aus einem einzelnen Ausdruck bestehen, gelten als inline Funktionen.
Das folgende Beispiel zeigt einen Anwendungsfall für das Schlüsselwort inline:
inline str f (str fmt)
{
return psub(fmt);
}
{
str x = "foo";
f("x = $(x)");
}
Die Funktion psub substituiert Parameter entsprechend einem Formatstring. Falls f nicht inline ist, ist die Variable x für psub unsichtbar und die Substitution $(x) schlägt fehl.
Der Overhead für den Aufruf einer inline-Funktion ist geringer als der einer normale Funktion, allerdings kann es zu Seiteneffekten bezüglich der Sichtbarkeit von Variablen kommen.
Funktionen haben den Datentyp Func und jede Funktion kann auch als Variable (Funktionsname ohne Argumentliste) angesprochen werden. Die Standarddarstellung einer Funktion (z.B: Eingabe des Funktionsnamens im äußersten Modus) ist der Prototype der Funktion.
Wie in C++ können Funktionsargumente Vorgabewerte besitzen. Diese müssen dann beim Aufruf nicht angegeben werden. Die allgemeine Form eines Funktionsarguments ist:
Eine virtuelle Funktion kann auch in eine gewöhnliche Funktion umgewandelt werden. Dies erfolgt mit einem Prototyp-Cast wie im folgendem Beispiel:
Func f = operator+ (int a, int b);;
Nun kann f zur Addition von zwei Ganzzahlwerten verwendet werden. Beachte die zwei Strichpunkte am Ende der Zuweisung: Der erste gehört zum Prototyp (und unterscheidet ihn von einer Funktionsdefinition), der zweite schließt den Ausdruck ab.
Falls nach dem Funktionsnamen ein & steht, kann die Funktion nur für L-Werte verwendet werden. Eine gebundene Funktion wird folgend aufgerufen:
Dabei ist obj ein Objekt vom Type btype. Der Datentyp einer typgebundenen Funktion ist ObjFunc. Dabei kann es sich sowohl um eine virtuelle, als auch um eine gewöhnliche Funktion handeln.
In gebundenen Funktionen kann mit dem Schlüsselwort this auf das zugehörige Datenobjekt zugegriffen werden.
Bei der ersten Schreibweise muß nach op ein Leerzeichen folgen, vor op kann ein Leerzeichen stehen.
Damit linke Operatoren von rechten unterscheidbar sind, werden sie intern mit dem Zusatz () versehen (z.B: -() für die Negation. Dies ist bei der Definition von Funktionen zu beachten.
Folgende Terme sind gleichwertig:
Operatoren sind in der Regel virtuelle Funktionen. Alle Zuweisungsoperatoren sind gebundene, virtuelle Funktionen.
Konstruktoren haben die Form
Im Gegensatz dazu ist
Konverter haben die Form
Die Ausgangsdaten werden unter dem Namen this referiert. Falls der Zieldatentype void ist, definiert die Funktion den Destruktor für den Datentyp, der jedesmal aufgerufen wird, wenn ein Objekt diesen Types gelöscht wird.
Copy-Konstruktor und Destruktor können als Spezialfall eines Konverters gesehen werden. Wegen der internen Speicherbereinigung werden sie kaum benötigt. Bei ihrer Definition ist besondere Vorsicht notwendig: Sobald hier ein Objekt dieses Types kopiert wird (z.B. bei der Weitergabe an eine andere Funktion), führt der Aufruf des Konstruktors/Destruktors zu einer endlosen Rekursion.
Es gibt zwei Möglichkeiten, eine Klassifikation einzurichten:
bool Type_t::classification (str name, str def)
Folgende Klassifikationsarten stehen für alle Datentypen zur Verfügung:
Die Klassifikationsarten switch, test und flag sind Sonderfälle der generische Klassifikationsart generic. Diese hat den folgenden Aufbau:
test-group:
switch-group:
Zusätzliche Informationen können mit esh selbst abgerufen werden. Die Option --info liefert eine Schnittstelle zu eingebauten Informationen. Im Interpreter kann auf diese Informationen mit der Funktion Info() zugegriffen werden.
Die Nutzung der eingebauten Informationen hat den Vorteil, dass sie immer vollständig und aktuell sind. Allerdings sind die zugehörigen Erläuterungen (wenn es welche gibt) sehr knapp gehalten.
Falls die Verwendung einer Funktion nicht klar ist: Durch Eingabe des Funktionsnamens wird der Prototype (bei virtuellen Funktionen auch alle Überladungen) angezeigt.
Informationen zu einem Datentyp foo können interaktiv mithilfe von foo.info(["mode"]) abgerufen werden. Diese Anweisung entspricht dem Aufruf von esh mit der Option --info=[mode:]/Type/foo. Die Angabe von mode ist optional.
Einzelne Parameter eines Datentyps können auch über Komponenten abgerufen werden. Eine Liste aller verfügbaren Komponenten kann mit Type_t.var abgerufen werden.
Weitere nützliche Variablen und Funktionen:
liefert Informationen zu dem Argument. liefert den Datentyp des Arguments.
void vtabstack (int = 0, IO = iostd)
Copyright (C) 1994, 2001 Erich Frühstück