|
|
Dieses Kapitel enthält die Sprachbeschreibung der Bartels User Language. Hierbei wird detailliert auf die Elemente der Bartels User Language eingegangen, und wo nötig wird deren Verwendung durch Beispiele veranschaulicht. Das Kapitel enthält außerdem Erläuterungen zur Bedienung der Programmierumgebung sowie zur Schnittstelle zum Bartels AutoEngineer. Inhalt
2.1 Einführung in die User Language ProgrammierungAn dieser Stelle sollen anhand kleiner Programmbeispiele die wichtigsten Sprachelemente der User Language kurz vorgestellt werden, ohne zunächst Wert auf Vollständigkeit zu legen bzw. auf Details oder gar Ausnahmeregelungen einzugehen. Ziel dabei ist, möglichst schnell die Vorgehensweise bei der User Language Programmierung aufzuzeigen. 2.1.1 Ein erstes User Language ProgrammAls erstes soll ein Programm erstellt werden, welches lediglich eine Meldung ausgibt und dann auf eine Eingabe wartet, um den Programmlauf abzubrechen. Dies ist im Übrigen bereits ein Programmkonstrukt, das innerhalb des
Bartels AutoEngineer relativ häufig benötigt wird. Wie Sie bereits aus der Einleitung wissen, muss ein
User Language-Programm mindestens aus der
main() { printf("User Language Program"); askstr("Press ENTER to continue ",1); } Wie Sie sehen, besteht das Programm lediglich aus der Definition der Funktion
ulc ulprog Falls keine Fehler auftreten, gibt der Compiler die folgende Meldung auf dem Bildschirm aus: ============================== BARTELS USER LANGUAGE COMPILER ============================== Quellcodedatei "ulprog.ulc" wird kompiliert... Programm 'ulprog' erfolgreich generiert. Quellcodedatei "ulprog.ulc" erfolgreich kompiliert. Keine Fehler, keine Warnungen. User Language Compiler-Lauf erfolgreich beendet. Der Compiler hat das Programm übersetzt und unter dem Namen
Der Grafikarbeitsbereich des AutoEngineer wird in den Textmodus geschaltet und es wird die Meldung angezeigt. Anschließend wird im Eingabefenster der Prompt angezeigt. Betätigt der Anwender daraufhin die Return-Taste, dann wird das User Language-Programm beendet und der Grafikarbeitsbereich wieder in den Grafikmodus zurückgeschaltet. 2.1.2 Variablen, Arithmetik und FunktionenAnhand des nächsten Beispiels sollen eine ganze Reihe weiterer spezifischer Eigenschaften der User Language veranschaulicht werden. Das folgende Programm überprüft einige durch ihren Mittelpunkt und ihren Radius definierte Kreise daraufhin, ob sie sich überlappen (Bohrdatentest?!), und gibt entsprechende Meldungen aus: // Circle Test Program double tol=0.254*5; // Tolerance struct pos { // Position descriptor double x; // X coordinate double y; // Y coordinate }; struct circle { // Circle descriptor double rad; // Circle radius struct pos c; // Circle position }; // Main program main() { // Define three circles struct circle c1 = { 4.5, { 19.4, 28.3} }; struct circle c2 = { 17.0, { 37.6, 9.71} }; struct circle c3 = { 1.5E01, { 25, 0.2e2} }; // Perform circle test printf("Circle 1 - 2 overlap : %d\n",circletest(c1,c2)); printf("Circle 1 - 3 overlap : %d\n",circletest(c1,c3)); printf("Circle 2 - 3 overlap : %d\n",circletest(c2,c3)); // Prompt for continue askstr("Press ENTER to continue ",1); } int circletest(c1,c2) // Circle test function // Returns: nonzero if overlapping or zero else struct circle c1,c2 /* Test circles 1 and 2 */; { double d /* Distance value */; // Get circle center point distances d=distance(c1.c,c2.c); // Error tolerant check distance against radius sum return(d<=(c1.rad+c2.rad+tol)); } double distance(p1,p2) // Get distance between two points // Returns: distance length value struct pos p1 /* Point 1 */; struct pos p2 /* Point 2 */; { double xd=p2.x-p1.x /* X distance */; double yd=p2.y-p1.y /* Y distance */; // Calculate and return distance return(sqrt(xd*xd+yd*yd)); } Obiger Quelltext enthält eine Reihe von Kommentaren, die durch
Das Programm enthält weiterhin eine Reihe von Variablendefinitionen. Variablen müssen grundsätzlich vor ihrer Verwendung definiert werden. Dabei wird sowohl der Datentyp als auch der Variablenname festgelegt. Unterschieden wird zwischen globalen Variablen, lokalen Variablen und Funktionsparametern. Der Geltungsbereich globaler Variablen erstreckt sich über das gesamte Programm. Lokale Variablen sind nur innerhalb der Funktion, in der sie definiert wurden, gültig. Funktionsparameter dienen dazu, Werte an Funktionen zu übergeben. In obigem Beispiel wird als einzige globale Variable
Die Berechnung von Werten erfolgt über Ausdrücke, wobei das Gleichheitszeichen
( Bei der Definition von Funktionen ist ebenfalls ein Datentyp zu spezifizieren. In obigem Beispiel ist die Funktion
2.1.3 Vektoren und KontrollstrukturenAn dieser Stelle sei abschließend ein Beispiel für die Verwendung von Vektoren und Kontrollstrukturen aufgeführt. Das nachfolgende Programm wandelt eine ganze Liste von Integerwerten in Strings um und gibt diese aus: // Integer list int intary[]={ 0,17,-12013,629,0770,0xFF,-16*4+12 }; // Main program main() { int i /* Loop control variable */; // Set last integer value intary[10]=(-1); // Loop through integer list for (i=0;i<=10;i++) // Print integer and integer string printf("%8d : \"%s\"\n",intary[i],inttostr(intary[i])); // Prompt for continue askstr("Press ENTER to continue ",1); } string inttostr(int intval) // Convert integer value to a string // Returns: resulting string { string resstr="" /* Result string */; int n=intval,i=0 /* Integer value, loop counter */; char sign /* Sign character */; // Test for negative integer value if (n==0) // Return zero integer string return("0"); else if (n>0) // Set sign to plus character sign='+'; else { // Make integer value positive n=-n; // Set sign to minus character sign='-'; } // Build result string do { // Get and append next character resstr[i++]=n%10+'0'; } while ((n/=10)!=0); // Append zeros while (i++<15) resstr+='0'; // Append sign character resstr+=sign; // Reverse string strreverse(resstr); // Return string result return(resstr); } In obigem Programm wird ein Vektor aus Integerwerten (globale
Eine spezielle Form eines Vektors stellt der Datentyp
Obiges Programmbeispiel enthält einige Kontrollstrukturen. So wird in der Funktion
2.2 KonventionenIn der User Language sind die Wortklassen Zwischenraum, Identifier, Konstante, reserviertes Wort und Operator definiert. 2.2.1 ZwischenraumUnter die Wortklasse Zwischenraum fallen sowohl Leerstellen, Tabulatorzeichen, Zeilentrenner als auch Kommentare. Kommentare beginnen mit den Zeichen
2.2.2 IdentifierEin Identifier ist der Name einer Variablen, einer Funktion oder einer symbolischen Konstanten. Jeder Identifier darf aus einer Folge von Buchstaben und Ziffern bestehen, wobei das erste Zeichen immer ein Buchstabe sein muss. Der Unterstrich
( Beispiele: X_coord value P4 File_Name _euklid 2.2.3 Konstanten und konstante AusdrückeNachfolgend sind die in der User Language definierten Konstanten beschrieben. Jeder Konstanten-Typ ist zugleich auch einem Datentyp zugeordnet. Ganzzahlige KonstantenGanzzahlige Konstanten sind dem Datentyp
Beispiele: 1432 073 0xF4A5 9 Gleitkomma-KonstantenGleitkomma-Konstanten sind dem Datentyp
Beispiele: 2.54 .78 4. 4.1508E-3 0.81037e6 17228E5 Zeichen-KonstantenZeichen-Konstanten sind dem Datentyp
Tabelle 2-1 enthält eine Liste von Sonderzeichen-Darstellungen mit Hilfe des Fluchtsymbols
Tabelle 2-1: Darstellung von Sonderzeichen
Beliebige Bitmuster können durch die Angabe des Fluchtsymbols, gefolgt von bis zu drei oktalen Ziffern spezifiziert werden. Ein Spezialfall dieser Konstruktion ist das Nullzeichen (NUL,
Zeichenketten-KonstantenZeichenketten-Konstanten (oder auch String-Konstanten) sind dem Datentyp
Beispiele: "IC1" "4.8 kOhm" "This is a string with Newline\n" Konstante AusdrückeEin konstanter Ausdruck ist ein Ausdruck, der nur aus konstanten Werten und Operatoren zusammengesetzt ist. Solche Ausdrücke werden bereits vom Compiler bewertet (CEE, Constant Expression Evaluation) und müssen somit nicht erst zur Laufzeit durch den Interpreter berechnet werden. Dies bedeutet, dass, wo immer Konstanten benötigt werden, statt dessen auch entsprechende konstante Ausdrücke verwendet werden können, ohne dass sich dadurch Nachteile hinsichtlich der Programmgröße oder der Programmlaufzeit ergeben. Beispiele: int i=19-(010+0x10); CEE: int i=-5; double d=-(4.7+2*16.3); CEE: double d=-37.3; string s="Part"+' '+"IC1"; CEE: string s="Part IC1"; 2.2.4 Terminalzeichen-SequenzenReservierte WorteTabelle 2-2 enthält die Liste der Identifier, die in der Bartels User Language als Schlüsselwörter reserviert sind. Diese Identifier dürfen nur in ihrer vordefinierten Bedeutung verwendet werden: Tabelle 2-2: Reservierte Worte
OperatorenDie in Tabelle 2-3 aufgeführten Zeichen(-Sequenzen) sind in der Bartels User Language als Operatoren definiert und lösen je nach Kontext spezielle Aktionen aus: Tabelle 2-3: Operatoren
2.3 Datentypen und Definitionen2.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. 2.4 AusdrückeIn diesem Abschnitt werden die in der User Language verfügbaren Operatoren für Ausdrücke vorgestellt. Die Reihenfolge, in der dabei vorgegangen wird, entspricht dem abnehmenden Vorrang der Operatoren, wobei jeweils auch auf die Assoziativität d.h. die Verarbeitungsfolge der jeweiligen Operation eingegangen wird. Jeder Operator ist entweder links- oder rechts-assoziativ, je nachdem ob er von links oder rechts implizit geklammert wird. Der Vorrang gibt an, mit welcher Priorität ein Ausdruck bewertet wird. Abgesehen vom Vorrang ist die Reihenfolge der Bewertung undefiniert. Es ist der Implementierung freigestellt, Teilausdrücke auch dann, wenn diese Nebeneffekte verursachen, in möglichst effizienter Reihenfolge zu bewerten. Die Reihenfolge für die Evaluierung von Nebeneffekten ist dabei nicht definiert. Ausdrücke mit assoziativen und kommutativen Operatoren lassen sich beliebig umordnen. Eine bestimmte Reihenfolge in der Bewertung von Operanden kann nur durch Zuweisung an (temporäre) Variablen erzwungen werden. 2.4.1 Primäre AusdrückePrimäre Ausdrücke, auch bezeichnet als einfache Ausdrücke oder Primary, sind Konstanten, Verweise auf Objekte, Ausdrucks-Klammerungen, Funktionsaufrufe, sowie die Auswahl von Elementen aus einem Vektor, einer Struktur oder einem Index. Diese Operationen sind links-assoziativ. Konstanten und Verweise auf ObjekteJeder geeignet vereinbarte Identifier gilt als primärer Ausdruck, der auf ein Objekt verweist. Auch ganzzahlige, Gleitkomma-, Zeichen- und String-Konstanten sind primäre Ausdrücke; die Darstellungsformen für Konstanten wurden in Kapitel 2.2.3 vorgestellt. Ausdrucks-KlammerungEin geklammerter Ausdruck besteht aus einem in runde Klammern eingeschlossenen Ausdruck und stellt einen primären Ausdruck dar. Mit Hilfe dieses Operators können Ausdrücke explizit geklammert werden, wodurch eine definierte Änderung der Reihenfolge der Bewertung herbeigeführt werden kann. Da der Multiplikations-Operator z.B. höheren Vorrang hat als der Additions-Operator, ergibt sich der Wert des Ausdrucks a + b * c aus der Summe aus a und dem Produkt b*c, während er sich in (a + b) * c aus dem Produkt aus der Summe a+b und c ergibt. FunktionsaufrufEin Funktionsaufruf besteht aus einem Identifier zur Bezeichnung der aufzurufenden Funktion gefolgt von einer in runde Klammern eingeschlossenen Liste von Ausdrücken, die durch Kommata getrennt sind. Die Werte der Ausdrücke innerhalb der runden Klammern repräsentieren die aktuellen Parameter für den Funktionsaufruf. Sofern eine Funktion per Definition einen Resultatwert liefert, kann der entsprechende Funktionsaufruf in einem beliebigen anderen Ausdruck verwendet werden. Die Liste der Ausdrücke innerhalb der runden Klammern kann leer sein. Typische Funktionsaufrufe sind z.B. init_states(pass=1); printf("This is a message!\n"); printf("Element %s\n",bae_planename()); xpos=nref_xcoord(ask_partname())-bae_planwsnx(); Zugriff auf Vektor-ElementEin einfacher Ausdruck gefolgt von einem Ausdruck in eckigen Klammern ist wiederum ein einfacher Ausdruck. Mit dieser Operation erfolgt die Auswahl eines Elements aus einem Vektor oder
int strisoctal(string str) { for (i=0;i<strlen(str);i++) if (!isdigit(str[i]) || str[i]=='8' || str[i]=='9') return(0); return(1); } In obigem Beispiel wird die Feldlängen-Kontrolle für den Vektor-Lese-Zugriff mit Hilfe der auf
string curfilename="", filelist[]; int i, filecount=0; while (scandirfnames(".",".ddb",curfilename)==1) filelist[filecount++]=curfilename; for (i=0;i<filecount;i++) printf("File %s \n",filelist[i]); In obigem Beispiel wird zunächst eine Liste aller im aktuellen Verzeichnis auffindbaren Dateinamen mit der Endung
Zugriff auf Struktur- oder Index-ElementEin einfacher Ausdruck gefolgt von einem Punkt und einem Identifier ist wieder ein einfacher Ausdruck. Der erste Ausdruck referenziert dabei eine Struktur bzw. einen Index-Typ, und der Identifier nach dem Punkt-Operator muss ein definiertes Element aus dieser Struktur bzw. diesem Index-Typ bezeichnen. Der schreibende Zugriff auf Struktur-Elemente ist grundsätzlich immer möglich; auf die Elemente von Index-Typen hingegen darf nicht gespeichert werden (Fehlermeldung durch den Compiler). Umgekehrt ist der lesende Zugriff auf die Elemente einer aktuell gültigen
int macrocnt=0; struct { string name; int class; } macrolist[]; index L_MACRO macro; forall (macro) { macrolist[macrocnt].name=macro.NAME; macrolist[macrocnt++].class=macro.CLASS; } 2.4.2 Unitäre AusdrückeUnitäre Ausdrücke, auch bezeichnet als Unary, sind rechts-assoziativ und umfassen alle Operatoren, die einen einzelnen Operanden bewerten. Inkrement und DekrementDie Operatoren
n = count-- ; den Wert 12, durch die Zuweisung n = --count ; jedoch den Wert 11 (der Wert von
Arithmetische NegationDer unitäre Operator
Logische NegationDer unitäre Operator
Bit-KomplementDer Operator
2.4.3 Binäre AusdrückeBinäre Ausdrücke, auch bezeichnet als Binary, umfassen alle Operatoren, die zwei Operanden bewerten. ProduktDie Operatoren für Multiplikation und Division sind links-assoziativ und erzeugen jeweils einen Produkt-Ausdruck. Der binäre Operator
febdays = (year%4==0 && year%100!=0 || year%400==0) ? 29 : 28 ; in dem zunächst überprüft wird, ob der Wert von
SummeDie Operatoren für Addition und Subtraktion sind links-assoziativ und erzeugen jeweils einen Summen-Ausdruck. Der binäre Operator
Schiebe-OperationDie Schiebe-Operatoren
VergleichDie binären Operatoren
Äquivalenz-VergleichDie binären Operatoren
Bitweise Und-VerknüpfungDer binäre Operator
Bitweise Exklusiv-Oder-VerknüpfungDer binäre Operator
Bitweise Oder-VerknüpfungDer binäre Operator
Logische Und-VerknüpfungDer binäre Operator
x<100 && fct(x) wird die Funktion
Logische Oder-VerknüpfungDer binäre Operator
test1() || test2() wird die Funktion
Bedingte BewertungDer Operator
result = logexpr ? trueexpr : falsexpr ; für die Zuweisung einer bedingte Bewertung an ein Resultat entspricht also der folgenden Kontrollstruktur: if (logexpr) result = trueexpr ; else result = falseexpr ; Beispiel für die Verwendung des
maxval = (val1>=val2) ? val1 : val2 ; ZuweisungenDer binäre Operator
expr1 <operator>= expr2 entspricht dem Ausdruck expr1 = expr1 <operator> (expr2) wobei jedoch expr1 nur einmal bewertet wird (man beachte die Klammerung des Ausdrucks expr2). In der Zuweisungsfolge a = 5 ; b = 3-a ; c = b+a ; c -= a *= b += 4+2*a ; ergeben sich die Werte der Variablen
2.4.4 Liste von AusdrückenJeder Ausdruck kann aus einer Liste von durch Kommata getrennten binären Ausdrücken bestehen. Zwei Ausdrücke, die durch Komma getrennt sind, werden dabei von links nach rechts bewertet; der Wert des linken Ausdrucks wird berechnet, aber nicht weiter verwendet. Typ und Wert des Resultats einer Liste von Ausdrücken entsprechen dem Typ und Wert des rechten Ausdrucks innerhalb der Ausdruck-Liste. Diese Operation ist links-assoziativ. In einem Kontext, in dem das Komma eine spezielle Bedeutung hat, wie etwa in der Liste von Argumenten für einen Funktionsaufruf, oder in einer Liste von Initialisierungen, kann der hier beschriebene Komma-Operator nur innerhalb von Klammern verwendet werden. Durch die Zuweisung c -= (a=5, b=3-a, c=b+a, a*=b+=4+2*a) ; ergeben sich die Werte der Variablen
2.4.5 Vorrang und Reihenfolge der BewertungIn Tabelle 2-4 sind nochmals der Vorrang und die Assoziativität der Operatoren der Bartels User Language zusammengefasst dargestellt. Tabelle 2-4: Operator Vorrang und Assoziativität
2.5 KontrollstrukturenIn diesem Abschnitt werden die in der User Language verfügbaren Kontrollstrukturen vorgestellt. Kontrollstrukturen definieren die Reihenfolge, in der die Aktionen eines Programms ausgeführt werden. Grundsätzlich wird in der Bartels User Language - entsprechend den Grundlagen der strukturierten Programmierung - unterschieden zwischen sequentiellen Programm-Elementen, Alternativen und Repetitionen (englisch: CAR - Concatenation, Alternation, Repetition). 2.5.1 Sequentielle Programm-ElementeAnweisungenEine Anweisung besteht aus einem Ausdruck (siehe
Kapitel 2.4) gefolgt von einem Semikolon
( tabulator = '\t' ; distance = sqrt(a*a+b*b) ; filename += extension = ".ddb" ; ++ary[i] ; printf("Part %s ;\n",partname) ; gültige Anweisungen. Fehlt der Ausdruck vor dem Semikolon, hat die Anweisung also die Form ; dann liegt liegt der Sonderfall der leeren Anweisung vor. Die leere Anweisung kann in Ausnahmefällen dazu dienen, abhängige Anweisungen (z.B. innerhalb von Schleifen) zu definieren. Grundsätzlich sind sonst aber nur solche Anweisungen sinnvoll, die auch einen Seiteneffekt haben, d.h. irgendeine Variable durch eine Zuweisung verändern oder eine Funktion aktivieren. In der User Language sind allerdings auch Anweisungen zulässig, die keine Seiteneffekte haben wie z.B. 27+16.3; ++11; Der User Language Compiler erkennt Ausdrücke ohne Seiteneffekte und gibt ggf. entsprechende Warnungen aus. Anweisungen, die in einer Kontext-Abhängigkeit zu einer Alternative bzw. Repetition (siehe unten) stehen, werden im Folgenden als abhängige Anweisungen bezeichnet. BlöckeEin Block besteht aus einer Folge von Vereinbarungen (siehe
Kapitel 2.3.2) und Anweisungen und ist in geschweifte Klammern
( 2.5.2 AlternativenAlternativen sind Verzweigungen, die die Ausführung bestimmter abhängiger Anweisungen vom Wert eines Ausdrucks abhängig machen. if- und if-else-AnweisungDie
if (expression) statement Die abhängige Anweisung der
Die
if (expression) statement else statement Zunächst wird der Ausdruck der
if (expression) statement else if (expression) { if (expression) statement } else if (expression) statement : else statement gebildet werden. Bei verschachtelten
string classname="SCM "; if (bae_planddbclass()==800) classname+="Sheet"; else if (bae_planddbclass()==801 || bae_planddbclass()==803) classname+="Symbol/Label"; else if (bae_planddbclass()==802) classname+="Marker"; else { classname="***INVALID***"; printf("No valid element loaded!\n"); } switch AnweisungDie
switch (expression) statement Jeder Anweisung innerhalb der abhängigen
case expression : oder default : vorausgehen. Die zwischen
string classname="SCM "; switch (bae_planddbclass()) { case 800 : classname+="Sheet"; break; case 801 : case 803 : classname+="Symbol/Label"; break; case 802 : classname+="Marker"; break; default : classname="***INVALID***"; printf("No valid element loaded!\n"); } 2.5.3 RepetitionenRepetitionen sind Anweisungen, mit deren Hilfe Schleifen zur wiederholten, also repetitiven Abarbeitung bestimmter Teile des Programms definiert werden können. Jede repetitive Anweisung verfügt auch über einen Mechanismus zur Überprüfung einer Ende-Bedingung, bei der die Schleifen-Bearbeitung beendet wird. Wird diese Ende-Bedingung nie erreicht, dann spricht man von einer Endlos-Schleife. Läuft ein Programm während der Abarbeitung in eine derartige Endlos-Schleife, dann kann es den Kontrollfluss nicht mehr von sich aus an den Aufrufer zurückgeben. In solchen Fällen liegt in der Regel ein schwerer Programmierfehler vor. Erkennt der User Language Compiler eine Endlos-Schleife, wozu er in bestimmten Fällen in der Lage ist, dann gibt er eine entsprechende Fehlermeldung aus und erzeugt keinen Programm-Code. while-AnweisungDie
while (expression) statement Die abhängige Anweisung wird solange wiederholt, wie der Wert des
// ASCII file view main() { string fname; // File name int fh; // File handle string curstr=""; // Current input string // Set the file error handle mode fseterrmode(0); // Print the program banner printf("ASCII FILE VIEWER STARTED\n"); // Repeatedly ask for the input file name while (fname=askstr("File Name (press RETURN to exit) : ",40)) { // Open the input file printf("\n"); if ((fh=fopen(fname,0))==(-1)) { printf("File open failure!\n"); continue; } // Get the current input string while (fgets(curstr,128,fh)==0) // Print the current input string puts(curstr); // Test on read errors; close the file if (!feof(fh) || fclose(fh)) // Read or close error break; } } Die
do-while-AnweisungDie
do statement while (expression); Die abhängige Anweisung wird solange wiederholt, bis der Wert des
for-AnweisungDie
for (expression1; expression2; expression3) statement und ist äquivalent zu experession1; while (expression2) { statement; expression3; } Zunächst wird der erste Ausdruck bewertet. Anschließend werden, solange der zweite Ausdruck nicht gleich dem Nullwert (0 bzw. Leerstring für
void strwords(string s) { string strlist[]; int strcount,i,j; char c; for ( strcount=0,j=0,i=0 ; c=s[i++] ; ) { if (c==' ' || c=='\t') { if (j>0) { strcount++; j=0; } continue; } strlist[strcount][j++]=c; } for ( i=strcount ; i>=0 ; i-- ) printf("%s\n",strlist[i]); } forall-AnweisungDie
forall (identifier1 of identifier2 where expression) statement Die
forall (identifier1) statement Die
index L_CPART part; index L_CPIN pin; forall (part where part.USED) { forall (pin of part where pin.NET.NAME=="vcc") printf("Part %s, Pin %s ;\n",part.NAME,pin.NAME); 2.5.4 Kontrollfluss-SteuerungNeben den bisher vorgestellten Kontrollstrukturen verfügt die User Language über einige spezielle Kontrollstrukturen, mit denen der Kontrollfluss zusätzlich gesteuert werden kann. break-AnweisungDie
break; Die
continue-AnweisungDie
continue; Die
Funktionsaufruf und return-AnweisungSowohl der Funktionsaufruf als auch die
Der Funktionsaufruf ist ein Ausdruck, der einen Sprung zur ersten Anweisung der entsprechenden Funktionsdefinition bewirkt. Die
return; bzw. return expression; Enthält die
struct structname functionname() { } provozieren eine Compiler-Fehlermeldung, da der User Language Interpreter beim lesenden Zugriff auf ein Element des Rückgabewertes einer solchen Funktion in jedem Fall eine Speicherzugriffs-Verletzung (Memory Protection Fault) feststellen würde. 2.6 Preprozessor-AnweisungenDer
Bartels User Language Compiler kann spezielle Preprozessor-Anweisungen zur Steuerung der Quelltextabarbeitung verarbeiten. Derartige Anweisungen beginnen mit dem Zeichen
2.6.1 DateieinbindungIm Sprachumfang der
User Language ist die aus Standard C bekannte
#include "filename" EOLN Die
// baecall.ulh -- BAE menu call facilities void call(int menuitem) // Call a BAE menu function { // Perform the BAE menu call if (bae_callmenu(menuitem)) { // Error; print error message and exit from program perror("BAE menu call fault!"); exit(-1); } } Das folgende Beispiel zeigt den Source-Code für das Programm
zoomall zur Ausführung des
-Kommandos im
AutoEngineer; hierbei wird die über das Include-File
baecall.ulh eingebundene Routine
// ZOOMALL -- Call the BAE Zoom All command #include "baecall.ulh" // BAE menu call include main() { // Call Zoom All call(101); } 2.6.2 KonstantendefinitionEine Preprozessor-Anweisung der Form #define IDENT constexpr EOLN sorgt dafür, dass im nachfolgenden Programmtext der über IDENT spezifizierte Identifier jeweils durch den Wert des konstanten Ausdrucks (constexpr) ersetzt wird. Die Anweisung wird durch das Zeilenende abgeschlossen. Diese Anweisung eignet sich insbesondere zur Definition wesentlicher Konstanten. Die Gültigkeit der Konstantendefinition erstreckt sich bis zum Ende des Programmtextes, sofern die Definition nicht vorher durch eine Preprozessor-Anweisung der Form #undef IDENT EOLN gelöscht wird. Eine besondere Form der
#define IDENT EOLN gegeben. Hierbei wird lediglich ein Name definiert, dessen Existenz durch die Preprozessor-Anweisungen
Typische Beispiele für die Verwendung der
#define DDBCLASSLAYOUT 100 #define mmtoinch 25.4 #define inchtomm 1.0/mmtoinch #define REPABORT "Operation aborted." #define ERRCLASS "Operation not allowed for this element!" #define GERMAN 1 #define ENGLISH 0 #define LANGUAGE GERMAN #define DEBUG 2.6.3 Bedingte ÜbersetzungEine Preprozessor-Anweisung der Form #if constexpr EOLN überprüft, ob der angegebene konstante Ausdruck einen von Null verschiedenen Wert besitzt. Eine Preprozessor-Anweisung der Form #ifdef IDENT EOLN überprüft, ob der über den Identifier spezifizierte Name (durch eine
#ifndef IDENT EOLN überprüft, ob der über den Identifier spezifizierte Name unbekannt ist. Die auf
Auf eine If-Preprozessor-Anweisung kann optional eine Prepozessor-Anweisung der Form #else EOLN folgen. Durch eine Preprozessor-Anweisung der Form #endif EOLN wird die entsprechende If-Konstruktion abgeschlossen. Wenn die
If-Bedingung erfüllt ist, dann werden alle Zeilen zwischen
Das folgende Beispiel illustriert die Möglichkeiten der bedingten Übersetzung: #define ENGLISH 0 #define GERMAN 1 #define LANGUAGE ENGLISH #if LANGUAGE == GERMAN #define MSGSTART "Programm gestartet." #endif #if LANGUAGE == ENGLISH #define MSGSTART "Program started." #endif #define DEBUG main() { #ifdef DEBUG perror(MSGSTART); #endif : } 2.6.4 BNF-PrecompilerIn die Bartels User Language ist ein BNF-Precompiler zur Realisierung von Interface-Programmen für die Bearbeitung von Fremddatenformaten integriert. Unter Verwendung der zugehörigen Scanner- und Parserfunktionen lassen sich in einfacher und eleganter Weise Programme zur Verarbeitung praktisch beliebiger ASCII-Datenformate implementieren. Jeder User Language-Programmtext darf eine Preprozessor-Anweisung der Form #bnf { ... } enthalten. Das
Die in der BNF-Definition angegebenen Terminalsymbole definieren den Wortschatz, d.h. die Menge der zulässigen Worte der Sprache. Hierbei kennzeichnen die Schlüsselwörter
Beispiele für die Spezifikation von Schlüsselwörtern sind: "SECTION" 'ENDSEC' 'Inch' 'begin' "end" 'include' Beispiele für die Spezifikation von Double Character Operatoren sind: '<=' '++' "&&" "+=" '::' ':=' Beispiele für die Spezifikation von Single Character Operatoren sind: '+' '-' "*" "/" "=" "[" "}" '#' '.' Zwischenräume (Leerzeichen, Tabulatoren, Zeilentrenner) sind üblicherweise bei der Definition einer Grammatik nicht signifikant; sie dienen lediglich der Trennung von benachbarten Symbolen. Auch Kommentare stellen letztendlich nichts anderes als Zwischenräume dar. Von Bedeutung ist in in diesem Zusammenhang lediglich die Definition entsprechender Kommentarbegrenzungs-Operatoren.
Per Default trägt der BNF-Precompiler hierfür die Operatoren
COMMENT ('/*', '*/'); Durch die Angabe nur eines Kommentarbegrenzungsoperators wie z.B. in COMMENT ('//'); werden Kommentare konfiguriert, die sich bis zum Zeilenende erstrecken. Es ist zu beachten, dass die Kommentardefaulteinstellung verloren geht, sobald auch nur eine
COMMENT ('/*', '*/'); COMMENT ('//'); Eine Besonderheit des
Bartels User Language BNF-Precompilers besteht darin, dass zu jedem in einer Formulierung aufgeführten Grammatikbegriff bzw. Eingabesymbol optional eine Aktion definiert werden kann. Die Definition einer Aktion besteht aus der in runden Klammern
( Die exakte Definition der Syntax der BNF-Notation ist in der Syntaxdefinition der Bartels User Language (siehe Kapitel 2.7) enthalten. Der BNF-Precompiler übersetzt die BNF-Definition in User Language-Maschinencode, der eine State-Maschine zur Abarbeitung eines Textes in der definierten Sprache festlegt. Zur Aktivierung dieser State-Maschine stehen die User Language-Systemfunktionen synparsefile und synparsestring zur Verfügung. Die Funktion synparsefile aktiviert einen Parser zur Abarbeitung einer durch Dateinamen spezifizierten Eingabedatei während mit der Funktion synparsestring eine Zeichenkette anstelle eines Dateiinhaltes abgearbeitet werden kann. Beide Funktionen lösen nach Bedarf automatisch die definierten Aktionen bzw. Anwenderfunktionen aus. Innerhalb dieser Anwenderfunktionen kann mit der Systemfunktion synscanline die aktuelle Zeilennummer der Eingabedatei und mit synscanstring die aktuell eingelesene Zeichenkette ermittelt werden, wobei die aktuelle Zeichenkette nach Bedarf auch einer semantischen Prüfung unterzogen werden kann. Die Funktionen synparsefile bzw. synparsestring werden beendet, sobald das Ende der Eingabedatei bzw. des abzuarbeitenden Strings erreicht ist oder ein Syntaxfehler (bzw. ein durch eine Anwenderfunktion erkannter semantischer Fehler) aufgetreten ist. Der Vollständigkeit halber sei an dieser Stelle noch auf die Systemfunktionen
synscaneoln,
synscanigncase und
synparseincfile hingewiesen. Mit der Scannerfunktion
synscaneoln kann die Zeilenendeerkennung des BNF-Parsers aktiviert bzw. deaktiviert werden (default: Zeilenendeerkennung deaktiviert). Die Verwendung des Terminalsymbols
Die genauen Beschreibungen der Scanner- und Parserfunktionen finden sich im Anhang C. Die Funktionalität des BNF-Precompilers soll an dieser Stelle anhand eines Beispiels zur Übernahme von Bauteil-Platzierungsdaten in das aktuell im Layouteditor geladene Layout verdeutlicht werden. Die Eingabedaten sollen dabei nach folgendem Schema aufgebaut sein: // This is a comment @ LAYOUT # This is a comment extending to the end of line UNITS { LENGTH = ( 1.0 INCH ) ; ANGLE = ( 1.0 DEGREE ) ; } PLACEMENT { 'ic1' : 'dil16' { POSITION = (0.000,0.000) ; ROTATION = 0.000 ; MIRROR = 0 ; } 'ic2' : 'dil16' { POSITION = (2.250,0.100) ; } 'ic3' : 'dil16' { POSITION = (1.000,0.394) ; ROTATION = 23.500 ; } 'ic4' : 'so16' { POSITION = (0.000,0.700) ; ROTATION = 0.000 ; MIRROR = 1 ; } } END Das Programm zum Einlesen von Platzierungsdaten aus externen Dateien gemäß obigem Beispiel kann unter Verwendung des BNF-Precompilers und der Scanner- und Parserfunktionen auf folgende Weise implementiert werden: // READLPLC -- Read Layout Placement from ASCII File //_________________________________________________________ // BNF input syntax definition #bnf { COMMENT ("//", "@") ; COMMENT ("#") ; placefile : "LAYOUT" placeunits placedata "END" ; placeunits : "UNITS" "{" "LENGTH" "=" "(" floatnum placelengthunit ")" ";" "ANGLE" "=" "(" floatnum placeangleunit ")" ";" "}" ; placelengthunit : "INCH" (p_unitl(1)) | "MM" (p_unitl(2)) | "MIL" (p_unitl(3)) ; placeangleunit : "DEGREE" (p_unita(1)) | "RAD" (p_unita(2)) ; placedata : "PLACEMENT" "{" placecommands "}" ; placecommands : placecommands placecommand | ; placecommand : identifier (p_pname) ":" identifier (p_plname) "{" placepos placerot placemirror "}" (p_storepart) ; placepos : "POSITION" "=" "(" floatnum (p_px) "," floatnum (p_py) ")" ";" ; placerot : "ROTATION" "=" floatnum (p_pa) ";" | ; placemirror : "MIRROR" "=" NUMBER (p_pm) ";" | ; identifier : SQSTR (p_ident) ; floatnum : NUMBER (p_fltnum(0)) | "-" NUMBER (p_fltnum(1)) ; } //______________________________________________________________ // Globals double plannx=bae_planwsnx(); // Element origin X coordinate double planny=bae_planwsny(); // Element origin Y coordinate double lenconv; // Length conversion factor double angconv; // Angle conversion factor string curpn; // Current part name string curpln; // Current physical library name double curx,cury; // Current coordinates double cura = 0.0; // Current angle (default: 0.0) int curm = 0; // Current mirror flag (default: 0) string curid; // Current identifier double curflt; // Current float value struct partdes { // Part descriptor string pn; // Part name string pln; // Physical library name double x,y; // Coordinates double a; // Angle int m; // Mirror flag } pl[]; // Part list int pn=0; // Part count //______________________________________________________________ // Main program main() { string fname; // Input file name // Test if layout loaded if (bae_planddbclass()!=100) errormsg("Command not allowed for this element!",""); // Get and test the placement file name if ((fname=askstr("Placement File : ",40))=="") errormsg("Operation aborted.",""); // Parse the placement file perror("Reading placement data..."); parseerr(synparsefile(fname),fname); // Perform the placement placement(); // Done perror("Operation completed without errors."); } //______________________________________________________________ // Part list management and placement void gcpart() // Get or create some part list entry { index L_CPART cpart; // Part index index L_NREF nref; // Named reference index int slb=0; // Search lower boundary int sub=pn-1; // Search upper boundary int idx; // Search index int compres; // Compare result // Loop until search area empty while (slb<=sub) { // Get the search index idx=(slb+sub)>>1; // Get and test the compare result if ((compres=strcmp(curpn,pl[idx].pn))==0) errormsg("Multiple defined part '%s'!",curpn); // Update the search area if (compres<0) sub=idx-1; else slb=idx+1; } // Check if part is placed already forall (nref where curpn==nref.NAME) // Part already placed; abort return; // Check net list consistence forall (cpart where curpn==cpart.NAME) { // Check the plname if (curpln!=cpart.PLNAME) // Netlist definition mismatch errormsg("Wrong part macro name '%s'!",curpln); // Done break; } // Insert the new entry to the part list pn++; for (idx=pn-2;idx>=slb;idx--) pl[idx+1]=pl[idx]; pl[slb].pn=curpn; pl[slb].pln=curpln; pl[slb].x=curx; pl[slb].y=cury; pl[slb].a=cura; pl[slb].m=curm; } void placement() // Perform the placement { int i; // Loop control variable // Iterate part list for (i=0;i<pn;i++) { // Place the part if (ged_storepart(pl[i].pn,pl[i].pln, pl[i].x,pl[i].y,pl[i].a,pl[i].m)) errormsg("Error placing part '%s'!",pl[i].pn); } } //______________________________________________________________ // Error handling void parseerr(status,fn) // Handle a syntax/parser error int status; // Scan status string fn; // File name { string msg; // Error message // Evaluate the scan status switch (status) { case 0 : // No error return; case 1 : msg="No BNF definition available!"; break; case 2 : msg="Parser already active!"; break; case 3 : sprintf(msg," Error opening file '%s'!",fn); break; case 4 : msg="Too many open files!"; break; case 5 : sprintf(msg,"[%s/%d] Fatal read/write error!", fn,synscanline()); break; case 6 : sprintf(msg,"[%s/%d] Scan item '%s' too long!", fn,synscanline(),synscanstring()); break; case 7 : sprintf(msg,"[%s/%d] Syntax error at '%s'!", fn,synscanline(),synscanstring()); break; case 8 : sprintf(msg,"[%s/%d] Unexpected end of file!", fn,synscanline()); break; case 9 : sprintf(msg,"[%s/%d] Stack overflow (BNF too complex)!", fn,synscanline()); break; case 10 : sprintf(msg,"[%s/%d] Stack underflow (BNF erroneous)!", fn,synscanline()); break; case 11 : sprintf(msg,"[%s/%d] Error from parse action function!", fn,synscanline()); break; default : sprintf(msg,"Unknown parser error code %d!",status); break; } // Print the error message errormsg(msg,""); } void errormsg(string errfmt,string erritem) // Print an error message with error item and exit from program { string errmsg; // Error message string // Build and print the error message string sprintf(errmsg,errfmt,erritem); perror(errmsg); // Exit from program exit(-1); } //______________________________________________________________ // Parser action routines int p_unitl(code) // Handle the length units definition request // Returns : zero if done or (-1) on error { // Set the length conversion factor switch (code) { case 1 : lenconv=cvtlength(curflt,1,0); break; // Inch case 2 : lenconv=cvtlength(curflt,2,0); break; // mm case 3 : lenconv=cvtlength(curflt,3,0); break; // mil default : return(-1); // Error } // Return without errors return(0); } int p_unita(code) // Handle the angle units definition request // Returns : zero if done or (-1) on error { // Set the angle conversion factor switch (code) { case 1 : angconv=cvtangle(curflt,1,0); break; // Deg case 2 : angconv=cvtangle(curflt,2,0); break; // Rad default : return(-1); // Error } // Return without errors return(0); } int p_storepart() // Handle the store part request // Returns : zero if done or (-1) on error { // Get or create the part list entry gcpart(); // Re-init the current angle and mirror mode cura=0.0; curm=0; // Return without errors return(0); } int p_pname() // Receive a part name // Returns : zero if done or (-1) on error { // Store the current part name strlower(curpn=curid); // Return without errors return(0); } int p_plname() // Receive a physical library name // Returns : zero if done or (-1) on error { // Store the current physical library name strlower(curpln=curid); // Return without errors return(0); } int p_px() // Receive a part X coordinate // Returns : zero if done or (-1) on error { // Store the current part X coordinate curx=curflt*lenconv+plannx; // Return without errors return(0); } int p_py() // Receive a part Y coordinate // Returns : zero if done or (-1) on error { // Store the current part Y coordinate cury=curflt*lenconv+planny; // Return without errors return(0); } int p_pa() // Receive a part angle // Returns : zero if done or (-1) on error { // Store the current part angle cura=curflt*angconv; // Return without errors return(0); } int p_pm() // Receive a part mirror flag // Returns : zero if done or (-1) on error { // Get and store the current part mirror flag curm=atoi(synscanstring())==0?0:1; // Return without errors return(0); } int p_ident() // Receive an identifier // Returns : zero if done or (-1) on error { // Store the current string curid=synscanstring(); // Return without errors return(0); } int p_fltnum(negflag) // Receive a float value // Returns : zero if done or (-1) on error int negflag; // Negative number flag { // Get the current float value curflt=atof(synscanstring()); // Set negative on request if (negflag) curflt*=(-1); // Return without errors return(0); } //______________________________________________________________ // User Language program end 2.6.5 Programmaufruftyp und Undo-MechanismusSetzen des ProgrammaufruftypsÜber die Preprozessoranweisung
Mit der Preprozessoranweisung #pragma ULCALLERSTD kann der Aufruftyp des generierten
User Language-Programms explizit auf Standard (STD) gesetzt werden. Ohne obige Preprozessoranweisung quittiert der
User Language Compiler die Verwendung von Funktionen und Indexvariablen zueinander inkompatibler Aufruftypen mit der Fehlermeldung
. Bei Verwendung obiger
Undo-Mechanismus konfigurierenDie Ausführung eines User Language-Programms generiert per Standard einen Undoschritt im BAE-System. Die Präprozessoranweisung #pragma ULCALLERNOUNDO deaktiviert den Undo-Mechanismus für das kompilierte Programm. Damit können redundante Undoschritte für Programme, die keine für das Undo relevanten Operationen ausführen, vermieden werden. 2.7 SyntaxdefinitionNachfolgend ist die Definition der
User Language-Syntax in BNF(Backus-Naur-Form)-ähnlicher Notation aufgelistet. Kommentare sind dabei durch
/* User Language Source Code Syntax */ /* Program definition */ program : progdefs ; progdefs : progdefs progdef | ; progdef : preproccmd | typedef | storageclass vardef | storageclass fctdef ; /* Preprocessor command */ preproccmd :
Sprachbeschreibung |
|