|
|
2.3.1 DatentypenDie Bartels User Language stellt die folgenden elementaren Datentypen zur Verfügung:
Daneben ist die Verwendung folgender zusammengesetzter Datentypen möglich:
Datentyp-KonvertierungVerschiedene Operatoren können implizite Datentypumwandlungen verursachen. So setzen eine Reihe arithmetischer Operationen definierte Datentypen bzw. Datentyppaare für ihre Operanden voraus. Ebenso wird bei der Zuweisung eines Wertes an eine Variable oder der Übergabe von Funktionsparametern eine entsprechende Datentypkompatibilität verlangt. Sofern an irgendeiner Stelle im Programm die geforderte Kompatibilität nicht vorliegt, wird versucht, den Wert des betroffenen Operanden in den gewünschten Datentyp zu überführen (man spricht von einem "type cast"). Diese Typkonvertierung läuft nach folgenden Regeln ab: Zulässige Umwandlungen ohne Informationsverlust sind
2.3.2 VariablenAlle globalen und lokalen Variablen müssen vor ihrem Gebrauch deklariert werden, damit sowohl ihr Datentyp als auch ihr Name definiert sind. Durch derartige Vereinbarungen wird festgelegt, wie einzelne, vom Benutzer eingeführte Namen durch die User Language zu interpretieren sind. Jede Vereinbarung besteht aus einer Datentypspezifikation sowie einer Liste von Deklaratoren; die Deklaratoren wiederum bestehen aus dem Variablennamen sowie wahlweise einer Initialisierung der Variablen. Elementare DatentypenBeispiel für die Deklaration von
char c; char TAB = '\t', NEWLINE = '\n'; In obigem Beispiel werden die
Beispiel für die Deklaration von
int i, MAXLINELEN = 80; int pincount = 0; In obigem Beispiel werden die
Beispiel für die Deklaration von
double x_coord, y_coord; double MMTOINCH = 1.0/25.4; double starttime = clock(); In obigem Beispiel werden die
Beispiel für die Deklaration von
string s1; string ProgName = "TESTPROGRAM", ProgVer = "V1.0"; string ProgHeader = ProgName+"\t"+ProgVer; In obigem Beispiel werden die
Beispiel für die Deklaration von
index L_MACRO macro; index L_CNET net1, net2; In obigem Beispiel werden die
VektorenUnter einem Vektor, auch bezeichnet als Feld oder Array, versteht man die Zusammenfassung einzelner Elemente gleichen Datentyps. Bei der Deklaration von Vektorvariablen wird neben der Spezifikation des Datentyps und der Definition des Variablennamens zusätzlich die Angabe der Vektordimension benötigt. Diese Dimensions-Angabe erfolgt durch Anfügen eckiger Klammern an den Variablennamen, wobei jeweils ein Klammernpaar einer Dimension entspricht. Bei der Initialisierung von Vektor-Elementen sind die Initialisierungswerte durch Kommata zu trennen, und jede Vektordimension ist in geschweifte Klammern einzuschließen. Beispiel für die Deklaration von Vektor-Variablen: int intary[], intfield[][][]; double valtab[][] = { { 1.0, 2.54, 3.14 }, { 1.0/valtab[0][1], clock() } }; string TECHNOLOGIES[] = { "TTL", "AC", "ACT", "ALS", "AS", "F", "H", "HC", "HCT", "HCU", "L", "LS", "S" }; Obiges Beispiel enthält die Deklarationen der
valtab[0][0] = 1.0; valtab[0][1] = 2.54; valtab[0][2] = 3.14; valtab[1][0] = 1.0/valtab[0][1]; valtab[1][1] = clock(); TECHNOLOGIES[0] = "TTL"; TECHNOLOGIES[1] = "AC"; TECHNOLOGIES[2] = "ACT"; : TECHNOLOGIES[11] = "LS"; TECHNOLOGIES[12] = "S"; Es sei an dieser Stelle nochmals darauf hingewiesen, dass in der
User Language der elementare Datentyp
string s; und char s[]; sind also äquivalent. StrukturenUnter einer Struktur versteht man die Zusammenfassung mehrerer, jeweils durch Name und Datentyp definierter Elemente, die in einem bestimmten verarbeitungstechnischen oder auch nur logischen Zusammenhang stehen. Bei der Vereinbarung von Strukturen unterscheidet man die Strukturdefinition und die Strukturdeklaration. Die Strukturdefinition besteht aus dem Schlüsselwort
Beispiel für die Vereinbarung von Strukturen: // Structure declarations struct coordpair { double x double y; }; struct coordpair elementsize = { bae_planwsux()-bae_planwslx(), bae_planwsuy()-bae_planwsly() }; struct elementdes { string fname, ename; int class; struct coordpair origin, size; } element = { bae_planfname(), bae_planename(), bae_planddbclass(), { bae_planwsnx(), bae_planwsny() }, elementsize }; struct { string id, version; struct { int day; string month; int year; } reldate; } program = { "UL PROGRAM", "Version 1.1", { 4, "July", 1992 } }; Obiges Beispiel enthält die Definition der Struktur
elementsize.x=bae_planwsux()-bae_planwslx(); elementsize.y=bae_planwsuy()-bae_planwsly(); element.fname=bae_planfname(); element.ename=bae_planename(); element.class=bae_planddbclass(); element.origin.x=bae_planwsnx(); element.origin.y=bae_planwsny(); element.size=plansize; program.id="UL PROG"; program.version="Version 1.1"; program.reldate.day=4; program.reldate.month="July"; program.reldate.year=1992; Auch die Definition von Vektoren aus Strukturen bzw. die Verwendung von Vektordatentypen innerhalb von Strukturen ist möglich, wie das folgende Beispiel demonstriert: struct drilldef { index L_DRILL drilltool; struct { double x, y; } drillcoords[]; } drilltable[]; Datentypumbenennung
Bartels User Language verfügt über einen Mechanismus zur Umbenennung von Datentypen. Dabei handelt es sich nicht um die Schaffung eines neuen Datentyps, sondern lediglich um die Vergabe eines zusätzlichen Namens für einen bereits bekannten Typ. Eine derartige Typumbennung erfolgt durch die Angabe des Schlüsselwortes
Beispiel für Typ-Umbennungen: typedef index L_CNET NETLIST[]; typedef int IARY[]; typedef IARY MAT_2[]; typedef struct { int pointcount; struct { int t; double x,y; } pointlist[]; } POLYLIST[]; MAT_2 routmatrix; NETLIST netlist; POLYLIST polygonlist; In obigem Beispiel werden die drei Variablen
2.3.3 FunktionenBei komplexen Operationen und Berechnungen ist es meist nicht notwendig, zu wissen, wie das Ergebnis zustande kommt; interessant ist lediglich das Ergebnis selbst. Auch ist es wünschenswert, bestimmte Bearbeitungsfolgen immer wieder verwenden zu können. Mit Hilfe von Funktionen ist die Zerlegung großer Problemstellungen in kleinere, d.h. die Modularisierung und Vereinfachung von Programmen möglich. In der Bartels User Language wird unterschieden zwischen den Systemfunktionen und den durch den Anwender definierten Funktionen. FunktionsdefinitionDie Definitionen der Systemfunktionen sind dem Compiler bekannt, die Funktionen selbst sind in den Interpreter eingebunden. Anhang C enthält die Beschreibung dieser Systemfunktionen. Der Anwender kann also bei der Implementierung seiner Programme die Systemfunktionen verwenden und hat darüber hinaus die Möglichkeit, eigene Funktionen zu definieren. Eine Funktionsdefinition besteht aus dem Funktionskopf (Header) und dem Funktionsrumpf (Block). Der Header enthält eine Typspezifikation und den Namen der Funktion, sowie die Definition und Deklaration der Funktionsparameter. Die Typspezifikation definiert den Datentyp des Wertes, den die Funktion an den Aufrufer zurückliefert (Rückgabewert). Hierbei steht zusätzlich der Datentyp
Beispiele für Funktionsdefinitionen: double netroutwidth(index L_CNET net) // Get the routing width of a given net // Returns : width or 0.0 if two pins with different width { index L_CPIN pin; // Pin index int pincnt=0; // Pin count double rw=0.0; // Rout width // Loop thru all pins forall (pin of net) { // Test if the pin introduces a new rout width if (pin.RWIDTH!=rw && pincnt++>0) return(0.0); // Set the rout width rw=pin.RWIDTH; } // Return the rout width return(rw); } int allpartsplaced() // Test if all netlist-defined parts are placed // Returns : 1 if all parts are placed or zero otherwise { index L_CPART cpart; // Connection part index // Loop thru the connection part list forall (cpart where !cpart.USED) // Unplaced part matched return(0); // All parts are placed return(1); } double getdistance(xs,ys,xe,ye) // Get the distance between two points // Returns : the distance length value double xs, ys; // Start point coordinate double xe, ye; // End point coordinate { double xd=xe-xs; // X distance double yd=ye-ys; // Y distance // Calculate and return the distance (Pythagoras) return(sqrt(xd*xd+yd*yd)); } double arclength(r,a1,a2) // Get arc segment length by radius and start-/end-point angle // Returns : the arc segment length value double r; // Radius double a1; // Start point angle (in radians) double a2; // End point angle (in radians) { // Arc; "absolute" angle between start and end point double arc = a1<a2 ? a2-a1 : 2*PI()+a2-a1; // Get and return the arc segment length return(arc*r); } double getangle(cx,cy,x,y) // Get the angle of a circle arc point // Returns : the angle (in radians; range [0,2*PI]) double cx, cy; // Circle center coordinate double x, y; // Circle arc point coordinate { double res; // Result value // Get arc tangent of angle defined by circle point res=atan2(y-cy,x-cx); // Test the result if (res<0.0) // Get the "absolute" angle value res=PI()-res; // Return the result value return(res); } double PI() // Returns the value of PI in radians { // Convert 180 degree and return the result return(cvtangle(180.0,1,2)); } void cputimeuse(rn,st) // Report CPU time usage (in seconds) string rn; // Routine name double st; // Start time { // Print CPU time elapsed since start time printf("(%s) Elapsed CPU Time = %6.1f [Sec]\n",rn,clock()-st); } Funktionsaufruf und WertübergabeJede in einem User Language-Programm (bzw. Programmtext) per Definition bekannte Funktion kann innerhalb dieses Programmes (bzw. in dem entsprechenden Programm-Modul) auch aufgerufen werden. Bei der Verwendung von User Language Systemfunktionen besteht jedoch die Einschränkung, dass in einem Programm nur zueinander kompatible Systemfunktionen verwendet werden können. Dies beruht auf der Tatsache, dass über derartige Funktionsaufrufe ggf. Aktionen ausgelöst werden, die nur in einer bestimmten Interpreterumgebung ausgeführt werden können (die Funktion zur Festlegung von Plotparametern im CAM-Prozessor des AutoEngineers kann selbstverständlich nicht im Schaltplaneditor aufgerufen werden). Bei der Verwendung zueinander nicht kompatibler Systemfunktionen gibt der User Language Compiler eine Fehlermeldung aus und erzeugt keinen Programm-Code. Ähnlich verhält sich der User Language Interpreter; beim Versuch ein User Language-Programm aufzurufen, das zur Interpreterumgebung inkompatible Systemfunktions-Referenzen enthält, erzeugt das System eine entsprechende Fehlermeldung und führt das betreffende Programm nicht aus. Die Information über die Kompatibilität der User Language-Systemfunktionen ist dem Anhang A bzw. dem Anhang C zu entnehmen. Der Funktionsaufruf setzt sich zusammen aus dem Funktionsnamen und der in runden Klammern eingeschlossenen Liste der aktuellen Parameter, die der Funktion übergeben werden soll. Die Inhalte der global definierten Variablen eines Programms stehen grundsätzlich in jeder Funktion desselben Geltungsbereichs zur Verfügung, d.h. globale Variablen können zur Übergabe von Werten an Funktionen benutzt werden. Darüber hinaus besteht die Möglichkeit der Wertübergabe über Funktionsparameter. Die Übergabe per Parameter kann einfach kontrolliert werden und ist daher i.d.R. der Methode der Übergabe über globale Variablen vorzuziehen. Die Liste der aktuellen Parameter, also der Ausdrücke, die man bei einem Funktionsaufruf übergibt, muss mit der formalen Parameterdefinition (also Anzahl und Datentypen) der aufzurufenden Funktion übereinstimmen. Beim Funktionsaufruf werden die Werte der aktuellen Parameter in die entsprechenden formalen Parameter kopiert. Nach erfolgreicher Beendigung der Funktion wird jeder durch die Funktion geänderte Parameterwert wieder auf den aktuellen Parameter zurückgespeichert, sofern dieser eine Variablenreferenz darstellt. Schließlich besteht noch die Möglichkeit der Wertübergabe über den Rückgabewert der Funktion. Dabei wird innerhalb der Funktion mit Hilfe der
Beispiel für Funktionsaufruf und Wertübergabe: // Date structure struct date { int day, month, year; }; // Global program variables string globalstr="Global string"; int fctcallcount=0; // Main program main() { // Local variables of main string resultstr="function not yet called"; struct date today = { 0, 0, 0 }; double p=0.0, b=2.0, e=10.0; // Print the global variables printf("fctcallcount=%d, %s\n",fctcallcount,globalstr); // Print the local variables printf("resultstr=\"%s\"\n",resultstr); printf("today : %d,%d,%d",today.day,today.month,today.year); printf("\t\tb=%.1f, e=%.1f, p=%.1f\n",b,e,p); // Call function resultstr=function(today,b,e,p); // Print the global variables printf("fctcallcount=%d, %s\n",fctcallcount,globalstr); // Print the local variables printf("resultstr=\"%s\"\n",resultstr); printf("today : %d,%d,%d",today.day,today.month,today.year); printf("\t\tb=%.1f, e=%.1f, p=%.1f\n",b,e,p); } string function(curdate,base,exponent,power) struct date curdate; // Current date parameter double base; // Base parameter double exponent; // Exponent parameter double power; // Power parameter { // Increment the function call count fctcallcount++; // Set the global string globalstr="Global string changed by function"; // Get the current date get_date(curdate.day,curdate.month,curdate.year); // Calculate the power power=pow(base,exponent); // Return with a result string return("function result string"); } Obiges Beispiel-Programm erzeugt folgende Ausgabe: fctcallcount=0, Global string resultstr="function not yet called" today : 0,0,0 b=2.0, e=10.0, p=0.0 fctcallcount=1, Global string changed by function resultstr="function result string" today : 4,6,92 b=2.0, e=10.0, p=1024.0 Kontrollfluss und ProgrammstrukturJede Funktion der
User Language behält nach ihrem Aufruf solange die Kontrolle, bis sie auf einen weiteren Funktionsaufruf, auf eine
Rekursive FunktionenFunktionen dürfen rekursiv benutzt werden, d.h. eine Funktion darf sich direkt oder indirekt selbst aufrufen. Dies ist jedoch nur dann sinnvoll, wenn sich bei jedem Aufruf ein Zustand derart verändert, dass sich irgendwann ein eindeutig definierter Endzustand einstellt, mit dessen Hilfe sich die Rekursion abbrechen lässt. Durch die rekursive Programmierung von Funktionen kann man im Allgemeinen Programm-Code einsparen und eventuell die Lesbarkeit erhöhen. Die Programmlaufzeit und der Speicherplatzbedarf hingegen werden sich durch Rekursionen erhöhen. Daher sollte man jeweils prüfen, ob die Verwendung einer Rekursion tatsächlich nützlich ist. Auch besteht die Gefahr, dass man "versehentlich" eine Endlosrekursion implementiert, d.h. eine Rekursion, die nie den Endzustand erreicht. Der User Language Interpreter wird bei der Abarbeitung einer solchen Endlosrekursion irgendwann einen Speicher- bzw. Stapel-Überlauf feststellen, da für jeden Funktionsaufruf zumindest eine Rücksprung-Adresse gespeichert werden muss. 2.3.4 Regeln zum GeltungsbereichBei der Referenzierung der Objekte eines User Language-Programms muss der Compiler jeweils die Gültigkeit der Referenz überprüfen. Hierzu ist jedem Objekt ein bestimmter Geltungsbereich innerhalb des Programms zugeordnet. Innerhalb dieses Geltungsbereiches ist das entsprechende Objekt bekannt und kann referenziert werden. Es wird unterschieden zwischen globalem und lokalem Geltungsbereich. Der globale Geltungsbereich erstreckt sich über das gesamte Programm (also auch auf noch einzubindende, getrennt kompilierte Programmteile bzw. Libraries), während die lokalen Geltungsbereiche des Programms Bezug nehmen auf die Funktionsdefinitionen. Die Funktionen eines Programms sind global, d.h. im gesamten Programm gültig. Variablen- und Typ-Definitionen, die in einer Funktion vorgenommen werden, gelten nur lokal innerhalb dieser Funktion; außerhalb von Funktionen gelten derartige Definitionen als global. Die Parameterdefinitionen einer Funktione werden wie lokale Variablen behandelt und gelten daher immer nur lokal innerhalb der betreffenden Funktion. Strukturdefinitionen gelten allgemein als global im aktuell zu übersetzenden Programmtext. Durch die Zuweisung der Speicherklasse
Um Namenskonflikte zu vermeiden, müssen die Elemente jeder Objektklasse innerhalb ihres Geltungsbereichs jeweils unterschiedliche Namen besitzen. Bei der Referenzierung haben die lokalen Objekte Vorrang vor den globalen.
Datentypen und Definitionen |
|