Willkommen bei Bartels
Sprachbeschreibung - Deutsche Version Language Description - English Version
Bartels

Bartels System GmbH
Bartels
Bartels AutoEngineer
BAE Produktinfo
BAE Preisliste
BAE Downloads
BAE Dokumentation
BAE Installationsanleitung
BAE Benutzerhandbuch
BAE Bibliotheken
User Language Programmierhandbuch
Vorwort
1 Einleitung
2 Sprachbeschreibung
2.1 Einführung in die User Language Programmierung
2.2 Konventionen
2.3 Datentypen und Definitionen
2.4 Ausdrücke
2.5 Kontrollstrukturen
2.6 Preprozessor-Anweisungen
2.7 Syntaxdefinition
3 Programmiersystem
4 BAE User Language-Programme
A Konventionen und Definitionen
B Index-Variablen-Typen
C Systemfunktionen
BAE Update-Historie
BAE Nächste Version Freigabemitteilungen Vorabinfo
BAE V8.0 Freigabemitteilungen
BAE V7.8 Freigabemitteilungen
BAE V7.6 Freigabemitteilungen
BAE V7.4 Freigabemitteilungen
BAE V7.2 Freigabemitteilungen
BAE V7.0 Freigabemitteilungen
BAE V6.8 Freigabemitteilungen
BAE V6.6 Freigabemitteilungen
BAE V6.4 Freigabemitteilungen
BAE V6.2 Freigabemitteilungen
BAE V6.0 Freigabemitteilungen
BAE V5.4 Freigabemitteilungen
BAE V5.0 Freigabemitteilungen
BAE V4.6 Freigabemitteilungen
BAE V4.4 Freigabemitteilungen
BAE V4.2 Freigabemitteilungen
BAE V4.0 Freigabemitteilungen
BAE V3.4 Freigabemitteilungen
BAE Support
BAE Contrib
BAE Entwickler und Dienstleister
Elektronikentwicklung
Sport
Firmenprofil
Impressum
Bartels :: Bartels AutoEngineer :: BAE Dokumentation :: User Language Programmierhandbuch :: Sprachbeschreibung
Bartels User Language - Programmierhandbuch

Kapitel 2
Sprachbeschreibung

Bartels AutoEngineer® Dokumentation

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.1Einführung in die User Language Programmierung
2.1.1Ein erstes User Language Programm
2.1.2Variablen, Arithmetik und Funktionen
2.1.3Vektoren und Kontrollstrukturen
2.2Konventionen
2.2.1Zwischenraum
2.2.2Identifier
2.2.3Konstanten und konstante Ausdrücke
2.2.4Terminalzeichen-Sequenzen
2.3Datentypen und Definitionen
2.3.1Datentypen
2.3.2Variablen
2.3.3Funktionen
2.3.4Regeln zum Geltungsbereich
2.4Ausdrücke
2.4.1Primäre Ausdrücke
2.4.2Unitäre Ausdrücke
2.4.3Binäre Ausdrücke
2.4.4Liste von Ausdrücken
2.4.5Vorrang und Reihenfolge der Bewertung
2.5Kontrollstrukturen
2.5.1Sequentielle Programm-Elemente
2.5.2Alternativen
2.5.3Repetitionen
2.5.4Kontrollfluss-Steuerung
2.6Preprozessor-Anweisungen
2.6.1Dateieinbindung
2.6.2Konstantendefinition
2.6.3Bedingte Übersetzung
2.6.4BNF-Precompiler
2.6.5Programmaufruftyp und Undo-Mechanismus
2.7Syntaxdefinition
Tabellen
2-1Darstellung von Sonderzeichen
2-2Reservierte Worte
2-3Operatoren
2-4Operator Vorrang und Assoziativität

 

2.1 Einführung in die User Language Programmierung

An 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 Programm

Als 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-Funktion bestehen. Was innerhalb der main-Funktion benötigt wird, ist eine Anweisung, die die gewünschte Meldung ausgibt, sowie eine Anweisung, die eine Benutzerabfrage aktiviert. Beide Anweisungen werden durch den Aufruf entsprechender User Language Systemfunktionen (printf und askstr) realisiert. Diese Systemfunktionen sind dem Compiler bekannt, und in den Interpreter eingebunden, d.h. der Programmierer muss lediglich wissen, wie diese Funktionen aufzurufen sind, und was sie tun (diese Information kann dem Anhang C dieses Handbuchs entnommen werden). Erstellen Sie nun mit Ihrem Texteditor folgendes User Language-Programm und speichern Sie dieses unter dem Dateinamen ulprog.ulc ab (an der File-Extension .ulc erkennt der Compiler, dass es sich um ein User Language Programm handelt):

main()
{
      printf("User Language Program");
      askstr("Press ENTER to continue ",1);
}

Wie Sie sehen, besteht das Programm lediglich aus der Definition der Funktion main. Innerhalb der runden Klammern nach dem Funktionsnamen stehen normalerweise die formalen Parameter der Funktion. Um den Funktionsnamen von anderen Variablennamen zu unterscheiden, sind diese Klammern auch anzugeben, wenn, wie in obigem Beispiel, keine Parameter existieren. Innerhalb der geschweiften Klammern befindet sich der Rumpf der Funktion, d.h. die Anweisungen, die die Funktion ausführen soll. Jede Anweisung wird durch ein Semikolon (;) abgeschlossen. Die erste Anweisung innerhalb der Funktion main ist der Aufruf der Funktion printf, was an der nachfolgenden runden Klammer zu erkennen ist. Als Argument bzw. Parameter wird an printf (in doppelten Anführungszeichen) eine konstante Zeichenkette übergeben, die das Programm bei fehlerfreier Übersetzung und Ausführung am Bildschirm ausgibt. Die zweite Anweisung ist ein Aufruf der Funktion askstr. Diese Funktion gibt in der Eingabe- bzw. Mitteilungszeile des Bartels AutoEngineer ihr erstes Argument in Form eines Prompts aus und wartet auf die Eingabe einer Zeichenkette durch den Anwender. Der zweite Parameter zu askstr gibt dabei die maximal zulässige Länge der einzugebenden Zeichenkette an. Der Aufruf von askstr ist zugleich die letzte Anweisung des Programms, d.h. nach der Bearbeitung des askstr-Aufrufs wird das Programm beendet. Wenn das Programm fertig codiert und unter ulprog.ulc abgespeichert ist, kann es mit folgendem Aufruf des User Language Compilers übersetzt werden:

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 ulprog in der Datei ulcprog.vdb im Bartels AutoEngineer Programmverzeichnis abgespeichert. Jetzt kann das Programm durch den User Language Interpreter ausgeführt werden. Hierzu ist z.B. der Schaltplaneditor des Bartels AutoEngineer aufzurufen und die Funktion Anwenderfunktion im Menü Datei zu aktivieren. Auf die Abfrage nach dem Namen des auszuführenden Programms ist anschließend ulprog einzugeben:

DateiLinke Maustaste (LMB)
AnwenderfunktionLinke Maustaste (LMB)
Programmname ?ulprog Return-/Eingabetaste (CR)

Der Grafikarbeitsbereich des AutoEngineer wird in den Textmodus geschaltet und es wird die Meldung User Language Program angezeigt. Anschließend wird im Eingabefenster der Prompt Press ENTER to continue 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 Funktionen

Anhand 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 /* und */ eingeklammert sind; diese Kommentare dürfen sich über mehrere Zeilen erstrecken, aber sie dürfen nicht verschachtelt werden. Ein anderer Typ von Kommentar beginnt mit // und erstreckt sich jeweils bis zum Zeilenende. Sie sollten sich angewöhnen, Ihre Programme mit derartiger Inline-Dokumentation zu versehen, damit der Programmcode gut verständlich bleibt und somit - auch durch dritte - leichter gepflegt bzw. weiterentwickelt werden kann.

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 tol mit dem Datentyp double definiert. Beispiele für lokale Variablen sind die double-Variablen xd und yd in der Funktion distance. Funktionsparameter sind z.B. c1 und c2 in der Funktion circletest. Diese sind von dem speziell definierten zusammengesetzten struct-Datentyp circle. Initialisierungen globaler und lokaler Variablen lassen sich bereits bei deren Deklaration durchführen. Beispiele hierfür sind die globale Variable tol sowie die lokalen Variablen xd und yd der Funktion distance. Auch zusammengesetzte Datentypen lassen sich initialisieren, wie die Beispiele für die lokalen struct-Variablen c1, c2 und c3 der Funktion main zeigen. Bei der Variablendeklaration besteht darüber hinaus die Möglichkeit, gleich eine ganze Liste von Variablennamen anzugeben (siehe Parameterdeklaration für c1 und c2 in der Funktion circletest).

Die Berechnung von Werten erfolgt über Ausdrücke, wobei das Gleichheitszeichen (=) als Zuweisungsoperator fungiert.

Bei der Definition von Funktionen ist ebenfalls ein Datentyp zu spezifizieren. In obigem Beispiel ist die Funktion distance vom Typ double, die Funktion circletest vom Typ int. Wird die Datentypspezifikation - wie bei der Funktion main - weggelassen, dann wird deren Typ automatisch auf int gesetzt. Ein spezieller Datentyp für Funktionen ist void. Jede Funktion außer den void-Funktionen liefert einen zum Funktionsdatentyp kompatiblen Rückgabewert. Die Übergabe des Rückgabewerts geschieht mit der return-Funktion, welche gleichzeitig die Funktionsausführung beendet.

 

2.1.3 Vektoren und Kontrollstrukturen

An 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 int-Variable intary) deklariert und dabei auch bereits (teilweise) initialisiert. Wie leicht zu erkennen ist, definiert das eckige Klammernpaar nach dem Variablennamen intary einen eindimensionalen int-Vektor. Mehrdimensionale Vektoren sind durch entsprechendes Anfügen weiterer eckiger Klammernpaare (intary[][]...[]) zu deklarieren. Zu beachten ist dabei, dass die Angabe einer Feldlänge entfällt. Dies resultiert aus der Fähigkeit der User Language, Vektoren dynamisch zu verwalten, d.h. sowohl dem Compiler als auch dem Interpreter genügt die Information über die Dimension des Vektors. Allerdings erfolgen doch einige Prüfungen, die den Zugriff auf nicht existente Vektorelemente (und damit eine Speicherzugriffsverletzung) unterbinden. So erkennt der Compiler z.B., wenn versucht wird, über einen konstanten, negativen (also ungültigen) Index auf ein Vektorelement zuzugreifen. Ebenso prüft der Interpreter Vektorindizes, die außerhalb des aktuell belegten Vektorfeldbereiches liegen. Beachten Sie hierbei, dass der Index mit dem Wert 0 auf das erste Element eines Vektors verweist.

Eine spezielle Form eines Vektors stellt der Datentyp string dar. User Language erlaubt die direkte Zuweisung von Vektoren kompatiblen Datentyps und gleicher Dimension. Dies wurde bei der Initialisierung der lokalen Variable resstr in der Funktion inttostr ebenso wie bei der Definition dieser Funktion als string und beim Setzen entsprechender Rückgabewerte in den return-Aufrufen ausgenutzt. Zudem lässt sich der Additionsoperator direkt auf string-Datentypen anwenden; das Resultat entspricht dabei einem durch Aneinanderfügen der beiden Additionsoperatoren erzeugten string.

Obiges Programmbeispiel enthält einige Kontrollstrukturen. So wird in der Funktion main über eine for-Schleife die Vektorvariable intary abgearbeitet. In der Funktion inttostr werden eine while- und eine do-while-Schleife verwendet, um die string-Variable resstr zu manipulieren. Des Weiteren enthält die Funktion inttostr eine if-Kontrollstruktur, die in Abhängigkeit vom Wert der lokalen Variablen n jeweils in einen entsprechenden Programmblock verzweigt.

 

2.2 Konventionen

In der User Language sind die Wortklassen Zwischenraum, Identifier, Konstante, reserviertes Wort und Operator definiert.

 

2.2.1 Zwischenraum

Unter die Wortklasse Zwischenraum fallen sowohl Leerstellen, Tabulatorzeichen, Zeilentrenner als auch Kommentare. Kommentare beginnen mit den Zeichen /* und enden mit */; diese Kommentare dürfen nicht verschachtelt sein. Ein weiterer Typ von Kommentar beginnt mit den Zeichen // und erstreckt sich bis zum Zeilenende. Der User Language Compiler wertet Zwischenräume lediglich zur Trennung direkt benachbarter Identifier und reservierter Worte aus; ansonsten werden alle Zwischenräume ignoriert.

 

2.2.2 Identifier

Ein 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 (_) zählt dabei zu den Buchstaben. Der User Language Compiler unterscheidet bei der Auswertung von Identifiern zwischen Groß- und Kleinbuchstaben. Es besteht die Einschränkung, dass Identifier nicht mit reservierten Worten (siehe unten) identisch sein dürfen.

Beispiele:

X_coord   value   P4   File_Name   _euklid

 

2.2.3 Konstanten und konstante Ausdrücke

Nachfolgend sind die in der User Language definierten Konstanten beschrieben. Jeder Konstanten-Typ ist zugleich auch einem Datentyp zugeordnet.

Ganzzahlige Konstanten

Ganzzahlige Konstanten sind dem Datentyp int zugeordnet. Sie bestehen aus einer Folge von Ziffern und werden üblicherweise dezimal interpretiert. Falls die Ziffernfolge mit 0 (Ziffer Null) beginnt, wird sie oktal, d.h. in Basis 8 interpretiert (die Ziffern 8 und 9 sind in diesem Fall nicht zulässig). Beginnt die Ziffernfolge mit 0x bzw. 0X (Ziffer Null, Buchstabe x), dann wird sie hexadezimal, d.h. in Basis 16 interpretiert, wobei dann die Buchstaben a bis f bzw. A bis F als hexadezimale Ziffern mit den Werten 10 bis 15 gelten. Ganzzahlige negative Konstanten gibt es nicht; vielmehr stellt das Minus-Zeichen einen Operator dar, der in Verbindung mit einer ganzzahligen Konstante einen konstanten Ausdruck (siehe unten) bildet.

Beispiele:

1432   073   0xF4A5   9

Gleitkomma-Konstanten

Gleitkomma-Konstanten sind dem Datentyp double zugeordnet. Sie bestehen aus einem ganzzahligen Teil, einem Dezimalpunkt, einem Dezimalbruch, dem Zeichen e bzw. E (Buchstabe e) und einem ganzzahligen Exponenten mit optionalem Vorzeichen. Ganzzahliger Teil, Dezimalbruch sowie Exponent sind dabei Ziffernfolgen. Genau einer der Teile vor oder nach dem Dezimalpunkt (.) darf fehlen; entweder der Dezimalpunkt oder der Exponent beginnend mit dem Buchstaben e darf fehlen.

Beispiele:

2.54   .78   4.   4.1508E-3   0.81037e6   17228E5

Zeichen-Konstanten

Zeichen-Konstanten sind dem Datentyp char zugeordnet. Sie bestehen aus einem einzelnen, in einfache Anführungsstriche (Apostroph) eingeschlossenen Zeichen. Der Wert dieser Konstante ist der numerische Wert des Zeichens im Zeichensatz der Maschine, auf der das User Language-Programm ausgeführt wird.

Tabelle 2-1 enthält eine Liste von Sonderzeichen-Darstellungen mit Hilfe des Fluchtsymbols \ (Backslash).

Tabelle 2-1: Darstellung von Sonderzeichen

Rückwärtsschritt BS \b
Horizontal Tabulator HT \t
Zeilenvorschub LF \n
Seitenvorschub FF \f
Wagenrücklauf CR \r
Fluchtsymbol \ \\
Apostroph ' \'
Nullzeichen NUL\0

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, \0).

Zeichenketten-Konstanten

Zeichenketten-Konstanten (oder auch String-Konstanten) sind dem Datentyp string zugeordnet. Sie bestehen aus einer Folge von Zeichen umgeben von Doppel-Anführungsstrichen. Der Compiler legt ein NUL Zeichen (\0) am Ende der String-Konstanten ab, damit der Interpreter das Ende des konstanten Strings finden kann. Bei der Spezifikation von String-Konstanten muss auch der Doppel-Anführungsstrich mit einem Fluchtsymbol angegeben werden. Daneben existieren dieselben Fluchtsymbol Kombinationen wie für die Zeichen-Konstanten.

Beispiele:

"IC1"   "4.8 kOhm"   "This is a string with Newline\n"

Konstante Ausdrücke

Ein 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-Sequenzen

Reservierte Worte

Tabelle 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

#bnf #define #else #endif #if #ifdef #ifndef #include
#undef break case char continue default do double
else for forall if index int of return
static string struct switch typedef void where while

Operatoren

Die 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 Definitionen

 

2.3.1 Datentypen

Die Bartels User Language stellt die folgenden elementaren Datentypen zur Verfügung:

char Zeichen aus dem Zeichensatz der Maschine
int ganzzahliger numerischer Wert
doubledoppelt genauer numerischer Gleitkommawert
stringZeichenkette (char-Vektor)
index Index auf definierte DDB-Struktur des Bartels AutoEngineer

Daneben ist die Verwendung folgender zusammengesetzter Datentypen möglich:

VektorZusammenfassung von Elementen gleichen Datentyps
structZusammenfassung von Elementen unterschiedlichen Datentyps

Datentyp-Konvertierung

Verschiedene 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 char in int oder char in string, sowie int in double; ebenfalls zulässige Umwandlungen - jedoch mit möglichem Informationsverlust - sind int in char, sowie double in int. Der Compiler gibt Fehlermeldungen aus, wenn auch unter Ausnutzung der Typkonvertierungsregeln keine Typkompatibilität erreicht werden kann.

 

2.3.2 Variablen

Alle 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 Datentypen

Beispiel für die Deklaration von char-Variablen:

char c;
char TAB = '\t', NEWLINE = '\n';

In obigem Beispiel werden die char-Variablen c (nicht initialisiert) sowie TAB und NEWLINE (initialisiert mit dem Tabulator- bzw. dem Zeilenvorschub-Zeichen) deklariert.

Beispiel für die Deklaration von int-Variablen:

int i, MAXLINELEN = 80;
int pincount = 0;

In obigem Beispiel werden die int-Variablen i (nicht initialisiert) und MAXLINELEN (initialisiert mit dem Wert 80), sowie pincount (initialisiert mit 0) vereinbart.

Beispiel für die Deklaration von double-Variablen:

double x_coord, y_coord;
double MMTOINCH = 1.0/25.4;
double starttime = clock();

In obigem Beispiel werden die double-Variablen x_coord und y_coord (nicht initialisiert), MMTOINCH (initialisiert mit einem numerischen Ausdruck), sowie starttime deklariert; die Variable starttime wird initialisiert mit dem Resultatwert der (System-)Funktion clock, welche die Prozessor-Zeit zurückgibt.

Beispiel für die Deklaration von string-Variablen:

string s1;
string ProgName = "TESTPROGRAM", ProgVer = "V1.0";
string ProgHeader = ProgName+"\t"+ProgVer;

In obigem Beispiel werden die string-Variablen s1 (nicht initialisiert), ProgName und ProgVer (initialisiert mit TESTPROGRAM und V1.0), sowie ProgHeader deklariert; ProgHeader wird dabei mit einem Ausdruck initialisiert, der sich durch Aneinanderfügen der string-Variablen ProgName, des Tabulatorzeichens, sowie der string-Variablen ProgVer ergibt.

Beispiel für die Deklaration von index-Variablen:

index L_MACRO macro;
index L_CNET net1, net2;

In obigem Beispiel werden die index-Variablen macro (vom index-Variablentyp L_MACRO) sowie net1 und net2 (vom index-Variablentyp L_CNET) vereinbart. Bei der Deklaration von index-Variablen besteht die Spezifikation des Datentyps aus dem Schlüsselwort index und zusätzlich dem Namen des index-Variablentyps (hier L_MACRO bzw. L_CNET). Die Namen für die index-Variablentypen sind vordefiniert (siehe Anhang B). Es muss sichergestellt sein, dass in einem Programm nur zueinander kompatible index-Variablentypen verwendet werden. Dies beruht auf der Tatsache, dass über index-Datentypen der Zugriff auf entsprechende Einträge aus der Design-Datenbank (DDB) des Bartels AutoEngineer definiert wird; die Verfügbarkeit dieser DDB-Einträge unterscheidet sich je nach Interpreterumgebung (im Schaltplaneditor sind andere Datentypen definiert als im Layout). Bei der Verwendung zueinander nicht kompatibler index-Variablentypen im selben Programm gibt der User Language Compiler eine entsprechende Fehlermeldung aus und erzeugt keinen Code. Ähnlich verhält sich der User Language Interpreter; beim Versuch ein User Language-Programm aufzurufen, das zur Interpreterumgebung inkompatible index-Datentyp-Referenzen enthält, gibt das System eine entsprechende Fehlermeldung aus und führt das betreffende Programm nicht aus. Die Information über die Kompatibilität der index-Datentypen ist dem Anhang A bzw. dem Anhang B zu entnehmen.

Vektoren

Unter 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 int-Vektoren intary und intfield (ein- bzw. dreidimensional), des zweidimensionalen double-Vektors valtab, sowie des eindimensionalen string-Vektors TECHNOLOGIES. In den Deklarationen für valtab und TECHNOLOGIES sind Initialisierungen angegeben, die den folgenden Zuweisungen entsprechen:

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 einem eindimensionalen char-Vektor entspricht. Die Deklarationen

string s;

und

char s[];

sind also äquivalent.

Strukturen

Unter 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 struct, dem Namen der Strukturdefinition, und - in geschweiften Klammern - den Definitionen der Strukturelemente. Die Strukturdeklaration besteht aus dem Schlüsselwort struct, dem Namen einer gültigen Strukturdefinition, sowie dem Namen der Variablen, die der Strukturdefinition zugeordnet wird. Strukturdefinition und Strukturdeklaration können zusammengefasst werden, wobei dann auch der Name für die Strukturdefinition entfallen kann. Initialisierungen innerhalb von Strukturdeklarationen sind in der aus den Vektordeklarationen bekannten Nomenklatur zulässig.

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 coordpair, die Deklaration der Variablen elementsize (Struktur vom Typ coordpair), die Definition der Struktur elementdes, die Deklaration der Variablen element (Struktur vom Typ elementdes), sowie die Deklaration der Strukturvariablen program. Die in den Deklarationen für elementsize, element und program enthaltenen Initialisierungen entsprechen den folgenden Zuweisungen:

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 typedef gefolgt von der Spezifikation des Datentyps sowie dem zur Bezeichnung dieses Datentyps zusätzlich einzuführenden Namen. Ein mit typedef eingeführter Name kann anschließend zur Typspezifikation bei der Deklaration von Variablen, Funktionen und Funktionsparametern verwendet werden.

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 routmatrix (zweidimensionaler int-Vektor), netlist (eindimensionaler index-Vektor vom Typ L_CNET), sowie polygonlist (eindimensionaler Vektor aus Strukturen, die ihrerseits ein int-Element und einen struct-Vektor enthalten) deklariert.

 

2.3.3 Funktionen

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

Funktionsdefinition

Die 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 void zur Verfügung, der dem Compiler angibt, dass die Funktion keinen Rückgabewert liefert. Wird die Datentypspezifikation weggelassen, dann wird der Funktion automatisch der Datentyp int zugeordnet. Nach dem Funktionsnamen folgt die Parameterdefinition. Diese besteht aus einer in runde Klammern eingeschlossenen Liste von durch Kommata getrennten Parameternamen bzw. Parameterdeklarationen. Für den Fall, dass die Funktion gar keine Parameter enthält, ist lediglich das Klammernpaar anzugeben. Alle definierten Parameter (außer den bereits in der Liste der Parameterdefinitionen deklarierten sowie den int-Typen) müssen nach der Liste der Parameterdefinitionen explizit vereinbart werden. Diese Parameterdeklarationen sind wie Variablendeklarationen vorzunehmen. Nach dem Funktionskopf muss der Funktionsrumpf definiert werden. Dieser ist ein durch geschweifte Klammern umschlossener Block von Anweisungen.

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übergabe

Jede 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 return-Anweisung ein Funktionsergebnis gesetzt, das anschließend vom Aufrufer innerhalb des Ausdrucks, der den Funktionsaufruf enthält, ausgewertet werden kann.

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 Programmstruktur

Jede Funktion der User Language behält nach ihrem Aufruf solange die Kontrolle, bis sie auf einen weiteren Funktionsaufruf, auf eine return-Anweisung, oder nach der Abarbeitung der letzten Anweisung der Funktion auf das Funktionsende trifft. Bei einem Funktionsaufruf wird die Kontrolle an die aufgerufene Funktion weitergegeben. Beim Erreichen einer return-Anweisung oder am Funktionsende wird die Kontrolle an den Aufrufer der Funktion zurückgegeben. Ist in einem Programm eine Anwenderfunktion mit dem Namen main definiert, dann erzeugt der User Language Compiler Programm-Code, der dafür sorgt, dass - unmittelbar nach der Initialisierung der globalen Variablen - diese Funktion aufgerufen wird. Der Kontrollfluss eines User Language-Programms beginnt also üblicherweise bei der Funktion main. Da bei rekursionsfreier Programmierung jede Funktion irgendwann ihre Kontrolle wieder an den Aufrufer zurückgibt, fällt die Kontrolle schließlich wieder an die main-Funktion. Wird darin dann das Funktionsende oder eine return-Anweisung erreicht, dann ist auch das Programmende erreicht, und der User Language Interpreter kann den Kontrollfluss beenden.

Rekursive Funktionen

Funktionen 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 Geltungsbereich

Bei 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 static kann der Geltungsbereich von Funktionen und globalen Variablen eingeschränkt werden auf den aktuell zu übersetzenden Programmtext. Die Speicherklasse static dient insbesondere der Vermeidung von Namenskonflikten beim Binden bzw. Linken unterschiedlicher Programm-Module bzw. Libraries.

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ücke

In 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ücke

Primä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 Objekte

Jeder 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-Klammerung

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

Funktionsaufruf

Ein 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-Element

Ein 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 string-Wert. Der Ausdruck vor der eckigen Klammer referenziert dabei den Vektor, der Ausdruck innerhalb der eckigen Klammern wird als int-Wert interpretiert und dient der Indizierung des Vektor-Elements. Der Indizierungs-Wert darf dabei nicht negativ sein; der Wert 0 (Null) verweist auf das erste Element des Vektors. Beim Speichern auf einen Vektor erfolgt ggf. eine dynamische Anpassung des Vektor-Feldbereichs durch den User Language Interpreter. Der lesende Zugriff auf einen Vektor ist nur innerhalb des aktuell definierten Feldbereiches zulässig. Die folgende Funktion strisoctal überprüft, ob der als Parameter übergebene string-Wert nur oktale Ziffern (0 bis 7) enthält und gibt für diesen Fall den Wert 1, in allen anderen Fällen den Wert 0 zurück:

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-Werte anwendbaren Systemfunktion strlen durchgeführt. Im folgenden Programm-Konstrukt hingegen geschieht dies mit Hilfe einer speziellen int-Variablen (filecount):

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 .ddb erzeugt, und anschließend wird diese Liste ausgegeben.

Zugriff auf Struktur- oder Index-Element

Ein 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 index-Variablen immer zulässig, während aus struct-Variablen nur die zuvor initialisierten gelesen werden können (ansonsten Speicherzugriffs-Verletzung und entsprechende Fehlermeldung durch den Interpreter). Der folgende Programmteil definiert eine Liste bestehend aus Strukturen und erzeugt für jedes aktuell verfügbare Layout-Macro einen Listen-Eintrag mit dem Macro-Namen und der Macro-Klasse:

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ücke

Unitäre Ausdrücke, auch bezeichnet als Unary, sind rechts-assoziativ und umfassen alle Operatoren, die einen einzelnen Operanden bewerten.

Inkrement und Dekrement

Die Operatoren ++ bzw. -- verändern ihre Operanden, indem sie den Wert 1 zum Wert ihres Operanden addieren bzw. subtrahieren. Sind diese Operatoren einem unitären Ausdruck vorangestellt, dann entspricht das Resultat dem Wert des Operanden nach dessen Inkrementierung bzw. Dekrementierung. Im anderen Fall, also wenn ++ bzw. -- auf den Operanden folgt, dann wird zwar der Operand inkrementiert bzw. dekrementiert, das Resultat entspricht jedoch dem Wert des Operanden vor der Durchführung der Operation. Dies bedeutet, dass in einem Kontext, in dem der Resultatwert einer Inkrement- bzw. Dekrement-Operation weiter verwendet wird, die entsprechenden Ausdrücke unterschiedliche Bedeutung haben. Besitzt zum Beispiel die Variable count den Wert 12 dann erhält die Variable n durch die Zuweisung

n = count-- ;

den Wert 12, durch die Zuweisung

n = --count ;

jedoch den Wert 11 (der Wert von count ergibt sich in beiden Fällen zu 11).

Arithmetische Negation

Der unitäre Operator - liefert den Wert der arithmetischen Negation seines Operanden, das Resultat ist also der mit (-1) multiplizierte Wert des Operanden.

Logische Negation

Der unitäre Operator ! liefert den Wert der logischen Negation seines Operanden. Das Resultat ist dabei 1 für einen Operanden mit dem Null-Wert (0 oder Leerstring für string-Operand), und 0 für alle anderen Operanden-Werte.

Bit-Komplement

Der Operator ~ komplementiert die einzelnen Bits in seinem Operanden, liefert also als Ergebnis das 1-Komplement seines Operanden.

 

2.4.3 Binäre Ausdrücke

Binäre Ausdrücke, auch bezeichnet als Binary, umfassen alle Operatoren, die zwei Operanden bewerten.

Produkt

Die Operatoren für Multiplikation und Division sind links-assoziativ und erzeugen jeweils einen Produkt-Ausdruck. Der binäre Operator * ist kommutativ und assoziativ und bezeichnet die Multiplikation. Das Ergebnis dieser Operation ist der Wert des Produktes seiner beiden Operanden. Der binäre Operator / bezeichnet die Division. Das Ergebnis dieser Operation ist der Wert, der durch die Division des Wertes des ersten Operanden (Divident) geteilt durch den Wert des zweiten Operanden (Divisor) der Operation zustande kommt. Da eine Division durch Null nicht zulässig ist, ist der Wert 0 für den Divisor auch nicht erlaubt. Der binäre Operator % liefert den Rest nach Division seiner beiden Operanden. Gleitkomma-Operanden sind hierbei nicht zulässig; ebenso ist der Wert 0 für den Divisor nicht erlaubt; ein Beispiel für die Verwendung des %-Operators ist der Ausdruck

febdays = (year%4==0 && year%100!=0 || year%400==0) ? 29 : 28 ;

in dem zunächst überprüft wird, ob der Wert von year der Jahreszahl eines Schaltjahres entspricht, und anschließend in Abhängigkeit vom Ergebnis dieser Überprüfung der Wert der Variablen febdays gesetzt wird.

Summe

Die Operatoren für Addition und Subtraktion sind links-assoziativ und erzeugen jeweils einen Summen-Ausdruck. Der binäre Operator + bezeichnet die Addition. Für den Fall, dass dieser Operator auf numerische Werte angewendet wird, ist das Ergebnis dieser Operation die Summe der Operanden, und die Operation ist in diesem Fall assoziativ und kommutativ; wird diese Operation auf Zeichenketten angewendet, dann ergibt sich das Resultat zu dem string-Wert, der sich durch Aneinanderfügen der string-Werte der Operanden ergibt. Der binäre Operator - bezeichnet die Subtraktion und liefert als Ergebnis den Wert der Differenz seiner Operanden.

Schiebe-Operation

Die Schiebe-Operatoren << und >> sind links-assoziativ und können auf ganzzahlige Operanden angewendet werden. Der binäre Operator << bezeichnet die bitweise Links-Verschiebung. Das Ergebnis dieser Operation ist der Wert, der sich ergibt, wenn das Bitmuster des ersten Operanden um die Anzahl der durch den zweiten Operanden gegebenen Stellen nach links verschoben wird (von rechts her werden dabei Bits mit dem Wert 0 nachgeschoben; das Ergebnis ist undefiniert, wenn der zweite Operand negativ ist). Der binäre Operator >> bezeichnet die bitweise Rechts-Verschiebung. Das Ergebnis dieser Operation ist der Wert, der sich ergibt, wenn das Bitmuster des ersten Operanden um die Anzahl der durch den zweiten Operanden gegebenen Stellen nach rechts verschoben wird (das Ergebnis ist undefiniert, wenn der zweite Operand negativ ist).

Vergleich

Die binären Operatoren < (kleiner), <= (kleiner oder gleich), > (größer) und >= (größer oder gleich) liefern den int-Wert 0, wenn die angegebene Relation in Bezug auf die beiden Operanden falsch ist, und den Wert 1, wenn die Relation vorliegt. Diese Operatoren sind auch direkt auf string-Typen anwendbar.

Äquivalenz-Vergleich

Die binären Operatoren == (gleich) und != (ungleich) liefern den int-Wert 0, wenn die angegebene Äquivalenz-Relation in Bezug auf die beiden Operanden falsch ist, und den Wert 1, wenn die Relation vorliegt. Die Äquivalenz-Vergleichs-Operatoren sind auch direkt auf string-Typen anwendbar; sie verhalten sich analog zu den anderen Vergleichs-Operatoren, haben jedoch geringeren Vorrang.

Bitweise Und-Verknüpfung

Der binäre Operator & bezeichnet die bitweise Und-Verknüpfung (AND) und ist assoziativ und kommutativ. Das Resultat dieser Operation ist der Wert, der sich ergibt, wenn die Bits der beiden Operanden in jeder Position der Und-Verknüpfung unterworfen werden.

Bitweise Exklusiv-Oder-Verknüpfung

Der binäre Operator ^ bezeichnet die bitweise Exklusiv-Oder-Verknüpfung (XOR) und ist assoziativ und kommutativ. Das Resultat dieser Operation ist der Wert, der sich ergibt, wenn die Bits der beiden Operanden in jeder Position der Exklusiv-Oder-Verknüpfung unterworfen werden.

Bitweise Oder-Verknüpfung

Der binäre Operator | bezeichnet die bitweise Oder-Verknüpfung (OR) und ist assoziativ und kommutativ. Das Resultat dieser Operation ist der Wert, der sich ergibt, wenn die Bits der beiden Operanden in jeder Position der Oder-Verknüpfung unterworfen werden.

Logische Und-Verknüpfung

Der binäre Operator && bezeichnet die logische Und-Verknüpfung (AND) und ist links-assoziativ. Das Resultat dieser Operation ist der int-Wert 1, wenn die Werte beider Operanden vom Null-Wert (0 oder Leerstring für string-Operanden) verschieden sind; im anderen Fall ergibt sich das Resultat zum Wert 0. Die logische Und-Verknüpfung wird strikt von links nach rechts durchgeführt; der rechte Operand wird dabei nur bewertet, wenn der linke Operand kein Null-Wert ist. Im Ausdruck

x<100 && fct(x)

wird die Funktion fct nur dann aufgerufen, wenn der Wert von x kleiner als 100 ist.

Logische Oder-Verknüpfung

Der binäre Operator || bezeichnet die logische Oder-Verknüpfung (OR) und ist links-assoziativ. Das Resultat dieser Operation ist der int-Wert 1, wenn einer der beiden Operanden-Wert vom Null-Wert (0 oder Leerstring für string-Operanden) verschieden ist; im anderen Fall ergibt sich das Resultat zum Wert 0. Die logische Oder-Verknüpfung wird strikt von links nach rechts durchgeführt; der rechte Operand wird dabei nur bewertet, wenn der Wert des linken Operanden dem Null-Wert entspricht. Im Ausdruck

test1() || test2()

wird die Funktion test2 also nur dann aufgerufen, wenn zuvor der Aufruf der Funktion test1 das Resultat 0 geliefert hat.

Bedingte Bewertung

Der Operator ?: bezeichnet die bedingte Bewertung (conditional) und ist rechts-assoziativ. Vor dem ?:-Operator hat ein binärer Ausdruck zu stehen, der in jedem Fall bewertet wird. Ist der Wert dieses Ausdrucks vom Null-Wert (0 oder Leerstring für string) verschieden, dann ergibt sich das Resultat dieser Operation zum Wert des zweiten Ausdrucks (dieser befindet sich zwischen dem Fragezeichen und dem Doppelpunkt des Operators und kann wiederum eine bedingte Bewertung sein); im anderen Fall, also wenn der erste Operand dem Null-Wert entspricht, dann ergibt sich das Resultat zum Wert des dritten Ausdrucks (dieser folgt auf den Doppelpunkt Fragezeichen des Operators und kann ebenfalls eine bedingte Bewertung sein). Der allgemeine Ausdruck

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 ?:-Operators zur Berechnung des Maximums zweier Werte:

maxval = (val1>=val2) ? val1 : val2 ;

Zuweisungen

Der binäre Operator = bezeichnet die einfache Zuweisung, die binären Operatoren *=, /=, %=, +=, -=, >>=, <<=, &=, ^= und |= bezeichnen die zusammengesetzten Zuweisungen. Alle diese Operatoren sind rechts-assoziativ. Der linke Operand muss immer ein unitärer Ausdruck sein, der rechte Operand darf seinerseits ein Zuweisungs-Ausdruck sein. Bei der einfachen Zuweisung ersetzt der Wert des rechten Operanden den Wert des durch den linken Operanden referenzierten Objekts. Eine zusammengesetzte Zuweisung der allgemeinen Form

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 a, b und c zu 60, 12 und -57.

 

2.4.4 Liste von Ausdrücken

Jeder 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 a, b und c zu 60, 12 und -57.

 

2.4.5 Vorrang und Reihenfolge der Bewertung

In 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

OperationOperator(en)Verarbeitungsfolge
Primary () [] . links nach rechts
Unary ! ~ ++ -- - rechts nach links
Product * / % links nach rechts
Sum + - links nach rechts
Shift << >> links nach rechts
Comparison < <= > >= links nach rechts
Equality == != links nach rechts
Bit And & links nach rechts
Bit Xor ^ links nach rechts
Bit Or | links nach rechts
Logical And && links nach rechts
Logical Or || links nach rechts
Conditional ?: rechts nach links
Assignment = += -= etc. rechts nach links
Expression List, links nach rechts
 

2.5 Kontrollstrukturen

In 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-Elemente

Anweisungen

Eine Anweisung besteht aus einem Ausdruck (siehe Kapitel 2.4) gefolgt von einem Semikolon (;). Somit sind z.B.

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öcke

Ein Block besteht aus einer Folge von Vereinbarungen (siehe Kapitel 2.3.2) und Anweisungen und ist in geschweifte Klammern ({ und }) einzuschließen. Ein Block ist syntaktisch äquivalent zur einfachen Anweisung, kann also auch immer anstelle einer einfachen Anweisung angegeben werden.

 

2.5.2 Alternativen

Alternativen sind Verzweigungen, die die Ausführung bestimmter abhängiger Anweisungen vom Wert eines Ausdrucks abhängig machen.

if- und if-else-Anweisung

Die if-Anweisung hat die allgemeine Form

if (expression)
      statement

Die abhängige Anweisung der if-Anweisung wird nur ausgeführt, wenn der if-Ausdruck einen vom Nullwert (0 bzw. Leerstring für string-Ausdruck) verschiedenen Wert besitzt; andernfalls wird sie übersprungen.

Die if-else-Anweisung besitzt die allgemeine Form

if (expression)
      statement
else
      statement

Zunächst wird der Ausdruck der if-else-Anweisung bewertet. Ist dabei das Resultat vom Null-Wert (0 bzw. Leerstring beim string-Ausdruck) verschieden, wird die erste abhängige Anweisung ausgeführt. Die zweite abhängige Anweisung wird genau dann ausgeführt, wenn das Resultat des Ausdrucks dem Null-Wert entspricht. Die abhängigen Anweisungen der if-else-Anweisung können selbstverständlich wiederum if-else-Anweisungen sein. Damit können auch Verschachtelungen und Ketten von if-else-Anweisungen der Form

if (expression)
      statement
else if (expression) {
      if (expression)
              statement
      }
else if (expression)
      statement
:
else
      statement

gebildet werden. Bei verschachtelten if-else-Anweisungen gehört die else-Anweisung immer zum nächstfrüheren if, für das noch kein else-Teil existiert. In folgendem Beispiel wird die Klasse des aktuell geladenen SCM-Elements abgefragt, und der Wert der Variablen classname wird entsprechend gesetzt:

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 Anweisung

Die switch-Anweisung sorgt dafür, dass in Abhängigkeit vom Wert eines auf Gleichheit prüfenden Äquivalenz-Ausdrucks bestimmte Anweisungen ausgeführt werden und andere nicht. Die allgemeine Form der switch-Anweisung ist gegeben durch

switch (expression)
      statement

Jeder Anweisung innerhalb der abhängigen switch-Anweisung kann eine beliebige Anzahl von case-Marken der Form

case expression :

oder

default :

vorausgehen. Die zwischen case-Marken befindlichen Anweisungen stehen in Abhängigkeit zu den unmittelbar vorhergehenden case-Marken. Die abhängigen Anweisungen einer case-Marke werden nur dann ausgeführt, wenn der Wert des switch-Ausdrucks mit dem Wert des case-Ausdrucks übereinstimmt; die default-Marke spezifiziert dabei einen beliebigen Wert, d.h. die einer default-Marke folgenden abhängigen Anweisungen kommen immer zur Ausführung. case-Marken selbst haben keinen Einfluss auf die sequentielle Ausführung einer Reihe von Anweisungen; die Ausführung wird fortgesetzt, als ob keine case-Marken vorhanden sind. Um eine switch-Anweisung zu verlassen wird üblicherweise in einem case-Abschnitt die break-Anweisung (siehe auch Kapitel 2.5.4) benutzt. In folgendem Beispiel wird die Klasse des aktuell geladenen SCM-Elements abgefragt, und der Wert der Variablen classname wird entsprechend gesetzt:

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 Repetitionen

Repetitionen 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-Anweisung

Die while-Anweisung hat die allgemeine Form

while (expression)
      statement

Die abhängige Anweisung wird solange wiederholt, wie der Wert des while-Ausdrucks ungleich dem Nullwert (0 oder Leerstring für string-Ausdruck) ist. Das folgende Programm dient dazu, die Inhalte von ASCII-Dateien auf dem Bildschirm anzuzeigen:

// 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 continue-Anweisung (siehe auch Kapitel 2.5.4) wird in obigem Beispiel verwendet, um wieder an den Beginn der while-Anweisung zu springen; die break-Anweisungen (siehe auch Kapitel 2.5.4) dienen dazu, im Fehlerfall die while-Anweisung zu verlassen.

do-while-Anweisung

Die do-while-Anweisung hat die allgemeine Form

do
      statement
while (expression);

Die abhängige Anweisung wird solange wiederholt, bis der Wert des do-while-Ausdrucks gleich dem Nullwert (0 oder Leerstring für string-Ausdruck) ist. Die abhängige Anweisung wird also im Unterschied zur while-Anweisung mindestens einmal ausgeführt.

for-Anweisung

Die for-Anweisung hat die allgemeine Form

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 string-Ausdruck) ist, die abhängige Anweisung ausgeführt und der dritte Ausdruck bewertet. Der erste for-Ausdruck dient also der Initialisierung, der zweite der Schleifenende-Prüfung, und der dritte typischerweise einer Inkrementierung. Jeder einzelne der drei for-Ausdrücke darf auch fehlen. Die folgende Funktion strwords zerlegt einen als Parameter übergebenen string-Wert in seine einzelnen (durch Blank oder Tabulator) getrennten Worte, speichert diese in einer Liste ab und gibt sie anschließend in umgekehrter Reihenfolge aus:

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-Anweisung

Die forall-Anweisung hat die allgemeine Form

forall (identifier1 of identifier2 where expression)
      statement

Die forall-Anweisung dient der automatischen sequentiellen Abarbeitung der aktuell verfügbaren Elemente eines index-Datentyps. Der erste Identifier muss eine index-Variable referenzieren, über die der Typ des abzuarbeitenden forall-Index spezifiziert wird. Die forall-Anweisung sorgt selbsttätig für die Initialisierung und Inkrementierung dieser Variablen. Ist keine Inkrementierung mehr möglich, weil das letzte Element der index-Liste erreicht ist, dann ist automatisch auch das Ende-Kriterium erreicht und die Programmausführung wird mit der Anweisung, die der forall-Anweisung folgt, fortgesetzt. Die of-Anweisung innerhalb der forall-Anweisung dient der Spezifikation eines dem forall-Index hierarchisch übergeordneten index-Wertes, d.h. der zweite Identifier muss eine index-Variable referenzieren, innerhalb der die Abarbeitung des forall-Index per Definition zulässig ist. Der where-Ausdruck schließlich dient dazu, zu entscheiden, ob die abhängige Anweisung für den aktuellen forall-Index ausgeführt werden soll (Wert des Ausdrucks ungleich dem Nullwert) oder nicht (Wert des Ausdrucks gleich dem Nullwert). Sowohl die of-Anweisung als auch die where-Anweisung können wahlweise weggelassen werden, so dass die kürzeste Form der forall-Anweisung gegeben ist durch

forall (identifier1)
      statement

Die index-Variablen-Typen sind in Anhang B beschrieben. Folgendes Beispiel gibt für alle platzierten Bauteile der aktuell geladenen physikalischen Netzliste eine Liste der Pins aus, die mit dem Netz vcc verbunden sind:

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-Steuerung

Neben den bisher vorgestellten Kontrollstrukturen verfügt die User Language über einige spezielle Kontrollstrukturen, mit denen der Kontrollfluss zusätzlich gesteuert werden kann.

break-Anweisung

Die break-Anweisung hat die allgemeine Form

break;

Die break-Anweisung muss sich in der Abhängigkeit einer repetitiven (while, do-while, for oder forall) oder einer switch-Anweisung befinden (andernfalls Fehlermeldung durch den Compiler). Sie sorgt für den Abbruch der nächstgelegenen repetitiven bzw. switch-Anweisung, d.h. die Ausführung des Programms wird mit der Anweisung fortgesetzt, die der abgebrochenen Anweisung normalerweise folgt.

continue-Anweisung

Die continue-Anweisung hat die allgemeine Form

continue;

Die continue-Anweisung muss sich in der Abhängigkeit einer repetitiven (while, do-while, for oder forall) Anweisung befinden (andernfalls Fehlermeldung durch den Compiler). Sie sorgt dafür, dass die Ausführung des Programms an der Stelle fortgesetzt wird, an der über die Wiederholung der nächstgelegenen repetitiven Anweisung entschieden wird.

Funktionsaufruf und return-Anweisung

Sowohl der Funktionsaufruf als auch die return-Anweisung wurden bereits vorgestellt. Sie seien an dieser Stelle jedoch nochmals aufgeführt, um zu verdeutlichen, dass auch sie den Kontrollfluss entscheidend beeinflussen.

Der Funktionsaufruf ist ein Ausdruck, der einen Sprung zur ersten Anweisung der entsprechenden Funktionsdefinition bewirkt. Die return-Anweisung darf nur innerhalb von Funktionen verwendet werden. Sie sorgt dafür, dass die Ausführung des Programms unmittelbar nach dem Funktionsaufruf der den Sprung in die Funktion veranlasst hat, fortgesetzt wird, d.h. es erfolgt ein Abbruch der Funktionsausführung, und der Kontrollfluss wird wieder an den Aufrufer der Funktion zurückgegeben. Die allgemeine Form der return-Anweisung ist gegeben durch

return;

bzw.

return expression;

Enthält die return-Anweisung keinen Ausdruck, dann ist der Rückgabewert des Funktionsaufrufes undefiniert. Im anderen Fall ergibt sich der Rückgabewert der Funktion aus dem return-Ausdruck, der typ-kompatibel zu dem für die Funktion definierten Datentyp sein muss (ansonsten Fehlermeldung durch den Compiler). Trifft der User Language Compiler auf das Ende eines Funktionsblocks, dann erzeugt er, sofern die letzte Anweisung des Blocks nicht eindeutig als return-Anweisung erkannt werden kann, an dieser Stelle Programm-Code, der einer return-Anweisung (ggf. mit zum Funktionsdatentyp kompatiblem Null-Wert) entspricht. Die Erzeugung eines Nullwertes ist hierbei allerdings nicht für zusammengesetzte Datentypen möglich. D.h., Funktionsdefinitionen der Form

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-Anweisungen

Der Bartels User Language Compiler kann spezielle Preprozessor-Anweisungen zur Steuerung der Quelltextabarbeitung verarbeiten. Derartige Anweisungen beginnen mit dem Zeichen # und werden durch das Zeilenende begrenzt. Preprozessor-Anweisungen sind an beliebigen Stellen des Quelltextes zulässig; ihr Wirkungsbereich erstreckt sich bis zum Ende des jeweiligen Quelltextes.

 

2.6.1 Dateieinbindung

Im Sprachumfang der User Language ist die aus Standard C bekannte #include-Anweisung enthalten. Die #include-Anweisung dient dazu, Deklarationen und Funktionen, die in unterschiedlichen Programmen identisch verwendet werden und in einer gesonderten Includedatei abgelegt sind, in die gewünschten Quelldateien einzubinden. Dadurch lässt sich der Aufwand für die Entwicklung und Pflege von User Language-Programmen erheblich reduzieren (eine in verschiedenen Programmen benötigte Funktion muss nur einmal im Quelltext definiert werden). Die Syntax für die #include-Anweisung lautet:

#include "filename" EOLN

Die #include-Anweisung sorgt dafür, dass der User Language Compiler an dieser Stelle mit der Kompilierung der in der #include-Anweisung angegebenen Quellcode-Datei beginnt. Diese Anweisung wird durch das Zeilenende abgeschlossen. Ist das Dateiende der Includedatei erreicht, dann wird der Übersetzungsvorgang an der der #include-Anweisung nachfolgenden Anweisung fortgesetzt. Innerhalb einer Includedatei sind wiederum #include-Anweisungen zulässig, sofern sich dadurch keine Mehrfachbearbeitung identischer Quellcodedateien (Datenrekursion!) ergibt. Bei der Verwendung von #include-Anweisungen ist zu berücksichtigen, dass bestimmte Teile der eingebundenen Includedateien in dem jeweiligen Programm u.U. gar nicht benötigt werden. Man sollte beim Compilerlauf daher in jedem Fall den Optimierer aktivieren, um redundante Programmteile zu eliminieren. Das folgende Beispiel zeigt das Include-File baecall.ulh mit der Routine call zur Aktivierung von AutoEngineer-Menüfunktionen:

// 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 Zoom All-Kommandos im AutoEngineer; hierbei wird die über das Include-File baecall.ulh eingebundene Routine call verwendet:

// ZOOMALL -- Call the BAE Zoom All command
#include "baecall.ulh"        // BAE menu call include
main()
{
    // Call Zoom All
    call(101);
}
 

2.6.2 Konstantendefinition

Eine 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-Anweisung ist durch die Syntax

#define IDENT EOLN

gegeben. Hierbei wird lediglich ein Name definiert, dessen Existenz durch die Preprozessor-Anweisungen #ifdef bzw. #ifndef abgefragt werden kann (siehe hierzu Kapitel 2.6.3).

Typische Beispiele für die Verwendung der #define-Anweisung sind:

#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 Übersetzung

Eine 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 #define-Anweisung) definiert ist. Eine Preprozessor-Anweisung der Form

#ifndef IDENT EOLN

überprüft, ob der über den Identifier spezifizierte Name unbekannt ist.

Die auf #if, #ifdef bzw. #ifndef folgenden Zeilen des Programmtextes werden nur dann übersetzt, wenn die entsprechende Bedingung erfüllt. Ansonsten wird der entsprechenden Programmteil lediglich auf korrekte Syntax geprüft.

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 #else und #endif von der eigentlichen Übersetzung ausgenommen. Ist die If-Bedingung nicht erfüllt, dann wird der Programmteil zwischen der If-Anweisung und #else (oder in dessen Abwesenheit #endif) von der Übersetzung ausgenommen. Derartige If-Preprozessor-Konstruktionen dürfen verschachtelt sein.

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-Precompiler

In 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 #bnf-Statement darf sich über beliebig viele Zeilen des Quelltextes erstrecken. Diese Anweisung aktiviert den BNF-Precompiler des Bartels User Language Compilers. BNF (Backus Naur Form) ist ein Formalismus zur Beschreibung der Syntax einer Sprache. Die BNF-Definition (innerhalb der geschweiften Klammern des #bnf-Statements) einer Sprache besteht aus einer Folge von Regeln durch die die Grammatik der Sprache (d.h. die in dieser Sprache gültigen Folgen von Worten bzw. Zeichen zur Bildung von Sätzen) festgelegt wird. In der BNF-Notation besteht eine Regel aus einem eindeutigen Grammatikbegriff (non-terminal symbol), dem über den Doppelpunkt-Operator : (zu lesen als "besteht aus") eine Folge von einer oder mehreren alternativen (durch das Zeichen | voneinander getrennten) Formulierungen zugewiesen wird. Eine Formulierung wiederum besteht aus einer Folge von Grammatikbegriffen und Eingabesymbolen (terminal symbols), wobei auch leere Formulierungen zulässig sind. Der Grammatikbegriff der ersten Regel einer BNF-Definition wird als Startsymbol bezeichnet. Durch die rekursive Verwendung von Grammatikbegriffen innerhalb der Regeln lassen sich unendlich viele bzw. unendlich lange zulässige Sätze definieren. Die Definition einer jeden Regel ist durch einen Strichpunkt ; abzuschließen.

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 IDENT (Identifier), NUMBER (numerische Konstante), SQSTR (Single Quoted String, Zeichenkette in einfachen Anführungszeichen), DQSTR (Double Quoted String, Zeichenkette in Doppelten Anführungszeichen), EOLN (End Of Line, Zeilenende \n), EOF (End Of File; Dateiende bzw. End of String bei der Abarbeitung von Zeichenketten), EOFINC (End Of Include File, Ende Dateieinbindung) und UNKNOWN (nicht explizit definierte Sonderzeichenketten) nicht näher spezifizierte Terminalsymbole aus den entsprechenden Wortklassen. Die Spezifikation spezieller, anwendungsspezifischer Terminalsymbole geschieht durch die Angabe der entsprechenden Zeichenketten in Anführungszeichen, wobei der BNF-Precompiler automatisch eine Unterteilung in reservierte Worte (Schlüsselwort, Keyword; Identifier bestehend aus mindestens 3 Zeichen) und Operatoren (Single Character Operatoren bestehend aus einem Sonderzeichen bzw. Double Character Operatoren bestehend aus zwei Sonderzeichen) vornimmt.

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 /* (Kommentarstart) und */ (Kommentarende) für zeilenübergreifende Kommentare ein. Diese Zuordnung lässt sich mit Hilfe eines speziellen Kommandos am Beginn der BNF-Definition ändern. Die diesbezügliche Anweisung für die Defaulteinstellung lautet wie folgt:

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-Anweisung in die BNF-Definition eingetragen wird. Will man also z.B. sowohl /* und */ für zeilenübergreifende Kommentare als auch den Operator // für Kommentare bis zum Zeilenende definieren, dann sind explizit die folgenden beiden COMMENT-Anweisungen am Beginn der BNF-Definition einzutragen:

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 (( und )) eingeschlossenen Angabe einer User Language-Anwenderfunktion, die bei Erkennung des entsprechenden Symbols auszulösen ist. Per Konvention müssen diese Anwenderfunktionen vom Datentyp int sein; der Rückgabewert einer solchen Funktion muss Null sein, wenn kein Fehler aufgetreten ist, und (-1) im anderen Fall. Optional ist ein Parameter vom Datentyp int zulässig, welcher in der BNF-Definition als Konstante in runden Klammern nach dem Funktionsnamen anzugeben ist.

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 EOLN in der BNF-Definition ist demnach nur sinnvoll bzw. zulässig, wenn für die Dateibearbeitung auch die Zeilenendeerkennung aktiviert wird. Mit der Scannerfunktion synscanigncase kann die per Default aktivierte Unterscheidung zwischen Groß- und Kleinschreibung bei der Schlüsselworterkennung deaktiviert bzw. wieder aktiviert werden. Mit der Parserfunktion synparseincfile wird die Bearbeitung einer eingebundenen Datei (Includedatei) aktiviert, d.h. der Parser setzt bei Aufruf dieser Funktion den Einlesevorgang unmittelbar am Beginn der namentlich spezifizierten Includedatei fort. Sobald das Ende der Includedatei erreicht ist, wird das EOFINC Terminalsymbol als erkannt gemeldet, und der Einlesevorgang wird an der in der übergeordneten Datei unterbrochenen Stelle fortgesetzt. Bei Verwendung der Funktion synparseincfile ist demnach auch eine entsprechende Einbindung des Terminalsymbols EOFINC in die BNF-Definition erforderlich (andernfalls ist dieses Terminalsymbol obsolet).

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-Mechanismus

Setzen des Programmaufruftyps

Über die Preprozessoranweisung #pragma kann der Aufruftyp des generierten User Language-Programms explizit gesetzt werden. Damit kann bei der Kompilierung ungeachtet der Verwendung modulspezifischer Systemfunktionen oder Indexvariablen die Kompatibilität des User Language-Programms erweitert oder eingeschränkt werden. Es können die in der nachfolgenden Tabelle angegebenen Aufruftypen spezifiziert werden (siehe hierzu auch Anhang A.1.2).

Aufruftyp Gültige Interpreterumgebung(en)
ULCALLERSTDalle BAE Programm-Module
ULCALLERCAPalle Schematic Capture Programm-Module
ULCALLERSCMSchaltplaneditor
ULCALLERLAYalle Layout Programm-Module
ULCALLERGEDLayouteditor
ULCALLERAR Autorouter
ULCALLERCAMCAM-Prozessor
ULCALLERCV CAM-View
ULCALLERICDalle IC Design Programm-Module
ULCALLERCEDChipeditor

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 Inkompatible Index-/Funktions-Referenz(en)!. Bei Verwendung obiger #pragma-Anweisung ist das generierte Programm in allen Interpreterumgebungen aufrufbar, auch wenn im Programm zur aktuellen Interpreterumgebung inkompatible Indexvariablen oder Funktionen verwendet werden. Es liegt dann in der Verantwortung des Programmierers, den Programmfluss so zu steuern, dass nur solche Indexvariablen bzw. Funktionen tatsächlich referenziert bzw. aufgerufen werden, die zur aktuellen Interpreterumgebung kompatibel sind. Damit lassen sich Programme mit ähnlicher Funktionalität für unterschiedliche Interpreterumgebungen implementieren. Sollte es zur Programmlaufzeit dennoch zu Zugriffen auf inkompatible Indexvariablen oder Funktionen kommen, dann bricht der User Language Interpreter das Programm mit einer entsprechenden Fehlermeldung (UL(Zeile): System-Funktion in dieser Umgebung nicht verfuegbar!) ab.

Undo-Mechanismus konfigurieren

Die 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 Syntaxdefinition

Nachfolgend ist die Definition der User Language-Syntax in BNF(Backus-Naur-Form)-ähnlicher Notation aufgelistet. Kommentare sind dabei durch /* und */ begrenzt. Der Doppelpunkt (:) entspricht einem Zuweisungs-Operator und ist folglich zu lesen als "besteht aus". Das Zeichen | kennzeichnet Alternativen. Identifier werden durch das Symbol IDENT, Konstanten durch NUMBER (numerisch), SQSTR (Zeichen) und DQSTR (Zeichenkette) gekennzeichnet. Das Symbol EOLN definiert das Zeilenende. Die Terminalzeichen-Sequenzen (also die reservierten Worte und Operatoren) sind fett gedruckt dargestellt.

/* User Language Source Code Syntax */

/* Program definition */

program
        : progdefs
        ;

progdefs
        : progdefs progdef
        |
        ;

progdef
        : preproccmd
        | typedef
        | storageclass vardef
        | storageclass fctdef
        ;

/* Preprocessor command */

preproccmd
        : #include DQSTR EOLN
        | #define IDENT optexpr EOLN
        | #undef IDENT EOLN
        | #if expression EOLN
        | #ifdef IDENT EOLN
        | #ifndef IDENT EOLN
        | #else EOLN
        | #endif EOLN
        | #bnf { syntaxdef } 
        | #pragma pragmaval
        ;

pragmaval
        : ULCALLERSTD
        | ULCALLERCAP
        | ULCALLERSCM
        | ULCALLERLAY
        | ULCALLERGED
        | ULCALLERAR
        | ULCALLERCAM
        | ULCALLERCV
        | ULCALLERICD
        | ULCALLERCED
        | ULCALLERNOUNDO
        ;

/* Type definition */

typedef
        : typedef declaration
        ;

/* Variable definition */

vardef
        : typespec initdecs ; 
        ;

/* Function definition */

fctdef
        : fcttype IDENT ( fctpars ) fctpardecs { cmditems }         ;

fcttype
        : typespec
        | void 
        |
        ;

fctpars
        : fctpardefs
        |
        ;

fctpardefs
        : fctpardefs , IDENT
        | IDENT
        ;

fctpardecs
        : fctpardecs vardef
        |
        ;

/* Storage class */

storageclass
        | static 
        | structdef
        ;

/* Type specification */

typespec
        : int 
        | double 
        | char 
        | string 
        | index IDENT
        | structdef
        | IDENT
        ;

/* Struct definition */

structdef
        : struct IDENT
        | struct IDENT { members } 
        | struct { members } 
        ;

members
        : members declaration
        | declaration
        ;

/* Declaration */

declaration
        : typespec decs ; 
        ;

decs
        : decs , declarator
        | declarator
        ;

/* Initialized declarator list */

initdecs
        : initdecs , initdec
        | initdec
        |
        ;

initdec
        : declarator
        | declarator = initializer
        ;

/* Declarator */

declarator
        : IDENT
        | declarator [ ] 
        ;

/* Initializer */

initializer
        : assignment
        | { initializers } 
        ;

initializers
        : initializers , initializer
        | initializer
        ;

/* Command block */

cmdblock
        : { cmditems } 
        | cmditem
        ;

/* Command list */

cmditems
        : cmditems cmditem
        |
        ;

/* Command item */

cmditem
        : preproccmd
        | typedef
        | vardef
        | ifcmd
        | switchcmd
        | forcmd
        | whilecmd
        | docmd
        | forallcmd
        | return optexpr ; 
        | break ; 
        | continue ; 
        | optexpr ; 
        ;

/* If control structure */

ifcmd
        : if ( expression ) cmdblock elsecmd
        ;

elsecmd
        : else cmdblock
        |
        ;

/* Switch control structure */

switchcmd
        : switch ( expression ) { caseblocks } 
        ;

caseblocks
        : caseblocks caseblock
        |
        ;

caseblock
        : cases cmditems
        ;

cases
        : cases case
        | case
        ;

case
        : case expression : 
        | default : 
        ;

/* For control structure */

forcmd
        : for ( optexpr ; optexpr ; optexpr ) cmdblock
        ;

/* While control structure */

whilecmd
        : while ( expression ) cmdblock
        ;

/* Do control structure */

docmd
        : do cmdblock while ( expression ) ; 
        ;

/* Forall control structure */

forallcmd
        : forall ( IDENT forallof forallwhere ) cmdblock
        ;

forallof
        : of IDENT
        |
        ;

forallwhere
        : where expression
        |
        ;

/* Expression */

optexpr
        : expression
        |
        ;

expression
        : expression , assignment
        | assignment
        ;

/* Assignment */

assignment
        : unary = assignment
        | unary |= assignment
        | unary ^= assignment
        | unary &= assignment
        | unary <<= assignment
        | unary >>= assignment
        | unary += assignment
        | unary -= assignment
        | unary *= assignment
        | unary /= assignment
        | unary %= assignment
        | conditional
        ;

/* Conditional evaluation */

conditional
        : log_or
        | log_or ? conditional : conditional
        ;

/* Logical OR */

log_or
        : log_and
        | log_and || log_or
        ;

/* Logical AND */

log_and
        : bit_or
        | bit_or && log_and
        ;

/* Bit OR */

bit_or
        : bit_xor
        | bit_or | bit_xor
        ;

/* Bit Exclusive OR */

bit_xor
        : bit_and
        | bit_xor ^ bit_and
        ;

/* Bit AND */

bit_and
        : equality
        | bit_and & equality
        ;

/* Equivalence comparison */

equality
        : comparison
        | equality == comparison
        | equality != comparison
        ;

/* Comparison */

comparison
        : shift
        | comparison < shift
        | comparison <= shift
        | comparison > shift
        | comparison >= shift
        ;

/* Shift operations */

shift
        : sum
        | shift << sum
        | shift >> sum
        ;

/* Addition and substraction */

sum
        : product
        | sum + product
        | sum - product
        ;

/* Multiplication and division */

product
        : unary
        | product * unary
        | product / unary
        | product % unary
        ;

/* Unary operators */

unary
        : primary
        | primary ++ 
        | primary -- 
        | - unary
        | ! unary
        | ~ unary
        | ++ unary
        | -- unary
        ;

/* Primary operators */

primary
        : IDENT
        | NUMBER
        | SQSTR
        | DQSTR
        | ( expression ) 
        | IDENT ( optexpr ) 
        | primary [ expression ] 
        | primary . IDENT
        ;

/* BNF Precompiler syntax definition */

syntaxdef
        : commentdef grammar
        ;

commentdef
        : COMMENT ( commentdel commentend ) ; 
        |
        ;

commentend
        : , commentdel
        |
        ;

commentdel
        : SQSTR
        | DQSTR
        ;

grammar
        : grammar rule
        | rule
        ;

rule
        : IDENT : forms ; 
        | IDENT : forms | ; 
        ;

forms
        : form | forms
        | form
        ;

form
        : form symbol action
        | symbol action
        ;

symbol
        : IDENT
        | SQSTR
        | DQSTR
        | IDENT 
        | NUMBER 
        | SQSTR 
        | DQSTR 
        | EOLN 
        | EOF 
        | EOFINC 
        | UNKNOWN 
        ;

action
        | ( IDENT ( NUMBER ) ) 
        | ( IDENT ) 
        |
        ;

/* BNF syntax description file end */
Bartels :: Bartels AutoEngineer :: BAE Dokumentation :: User Language Programmierhandbuch :: Sprachbeschreibung

Sprachbeschreibung
© 1985-2025 Oliver Bartels F+E • Aktualisiert: 26. January 2007, 17:23 [UTC]

© 1985-2025 Oliver Bartels F+E Bartels Startseite Kontakt und Impressum

Webentwicklung Baumeister Mediasoft Engineering

Sprachbeschreibung - Deutsche Version Language Description - English Version