#headandbottom("52","EUMEL-Benutzerhandbuch","TEIL 2 : ELAN","2")# #pagenr ("%", 52)##setcount(1)##block##pageblock# #headeven# #center#EUMEL-Benutzerhandbuch #center#____________________________________________________________ #end# #headodd# #center#TEIL 2 : ELAN #center#____________________________________________________________ #end# #bottomeven# #center#____________________________________________________________ 2 - % #right#GMD #end# #bottomodd# #center#____________________________________________________________ GMD #right#2 - % #end# 2.5 Programmstruktur Ein ELAN-Programm kann aus mehreren Moduln (Bausteinen) aufgebaut sein, die in ELAN PACKETs genannt werden. Das letzte PACKET wird "main packet" genannt, weil in diesem das eigentliche Benutzerprogramm (Hauptprogramm) enthalten ist. Dies soll eine Empfehlung sein, in welcher Reihenfolge die Elemente eines PACKETs geschrieben werden sollen: Ein "main packet" kann aus folgenden Elementen bestehen: a) Deklarationen und Anweisungen. Diese müssen nicht in einer bestimmten Reihen­ folge im Programm erscheinen, sondern es ist möglich, erst in dem Augenblick zu deklarieren, wenn z.B. eine neue Variable benötigt wird. Es ist jedoch gute Pro­ grammierpraxis, die meisten Deklarationen an den Anfang eines Programms oder Programmteils (Refinement, Prozedur) zu plazieren. ; ____________________________________________________________________________ ........................... Beispiel: ......................... INT VAR erste zahl, zweite zahl; page; put ("erste Zahl = "); get (erste zahl); put ("zweite Zahl ="); get (zweite zahl) ____________________________________________________________________________ b) Deklarationen, Refinements und Anweisungen. In diesem Fall ist es notwendig, die Refinements hintereinander zu plazieren. Refinement-Aufrufe und/oder Anweisungen sollten textuell vorher erscheinen. ; . Innerhalb der Refinements sind Anweisungen und/oder Deklarationen möglich. ____________________________________________________________________________ ........................... Beispiel: ......................... INT VAR erste zahl, zweite zahl; loesche bildschirm; lies zwei zahlen ein. loesche bildschirm: page. lies zwei zahlen ein: put ("erste Zahl = "); get (erste zahl); put ("zweite Zahl ="); get (zweite zahl). ____________________________________________________________________________ c) Deklarationen, Prozeduren und Anweisungen. Werden Prozeduren vereinbart, sollte man sie nach den Deklarationen plazieren. Danach sollten die Anweisungen folgen: ; ; Mehrere Prozeduren werden durch ";" voneinander getrennt. In diesem Fall sind die Datenobjekte aus den Deklarationen außerhalb von Prozeduren statisch, d.h. während der gesamten Laufzeit des Programm vorhanden. Solche Datenobjekte werden auch PACKET-Daten genannt. Im Gegensatz dazu sind die Datenobjekte aus Deklarationen in Prozeduren dynamische Datenobjekte, die nur während der Bearbeitungszeit der Prozedur existieren. Innerhalb einer Prozedur dürfen wieder­ um Refinements verwendet werden. Ein Prozedur-Rumpf hat also den formalen Aufbau wie unter a) oder b) geschildert. Die Refinements und Datenobjekte, die innerhalb einer Prozedur deklariert wurden, sind lokal zu dieser Prozedur, d.h. können von außerhalb nicht angesprochen werden. ____________________________________________________________________________ ........................... Beispiel: ......................... INT VAR erste zahl, zweite zahl; PROC vertausche (INT VAR a, b): INT VAR x; x := a; a := b; b := x END PROC vertausche; put ("erste Zahl = "); get (erste zahl); put ("zweite Zahl ="); get (zweite zahl); IF erste zahl > zweite zahl THEN vertausche (erste zahl, zweite zahl) FI ____________________________________________________________________________ d) Deklarationen, Prozeduren, Anweisungen und PACKET-Refinements. Zusätzlich zu der Möglichkeit c) ist es erlaubt, neben den Anweisungen außerhalb einer Prozedur auch Refinements zu verwenden: ; ; . Diese Refinements können nun in Anweisungen außerhalb der Prozeduren benutzt werden oder auch durch die Prozeduren (im letzteren Fall spricht man analog zu globalen PACKET-Daten auch von PACKET-Refinements oder globalen Refine­ ments). In PACKET-Refinements dürfen natürlich keine Datenobjekte verwandt werden, die lokal zu einer Prozedur sind. ____________________________________________________________________________ ........................... Beispiel: ......................... INT VAR erste zahl, zweite zahl; PROC vertausche (INT VAR a, b): INT VAR x; x := a; a := b; b := x END PROC vertausche; loesche bildschirm; lies zwei zahlen ein; ordne die zahlen. loesche bildschirm: page. lies zwei zahlen ein: put ("erste Zahl = "); get (erste zahl); put ("zweite Zahl ="); get (zweite zahl). ordne die zahlen: IF erste zahl > zweite zahl THEN vertausche (erste zahl, zweite zahl) FI ____________________________________________________________________________ #page# 2.6 Zusammengesetzte Datentypen In ELAN gibt es die Möglichkeit, gleichartige oder ungleichartige Datenobjekte zu einem Objekt zusammenzufassen. 2.6.1 Reihung Die Zusammenfassung gleichartiger Datenobjekte, wird in ELAN eine Reihung (ROW) genannt. Die einzelnen Objekte einer Reihung werden Elemente genannt. Eine Reihung wird folgendermaßen deklariert: - Schlüsselwort #on("i")##on("b")#ROW#off("i")##off("b")# - Anzahl der zusammengefaßten Elemente (INT-Denoter oder durch LET definierter Name) - Datentyp der Elemente - Zugriffsrecht ( #on("i")##on("b")#VAR#off("i")##off("b")# oder #on("i")##on("b")#CONST#off("i")##off("b")# ) - Name der Reihung. ____________________________________________________________________________ ........................... Beispiel: ......................... ROW 10 INT VAR feld ____________________________________________________________________________ Im obigen Beispiel wird eine Reihung von 10 INT-Elementen deklariert. ROW 10 INT ist ein (neuer, von den elementaren unterschiedlicher) Datentyp, für den keine Opera­ tionen definiert sind, außer der Zuweisung. Das Accessrecht (VAR im obigen Bei­ spiel) und der Name ('feld') gilt - wie bei den elementaren Datentypen - für diesen neuen Datentyp, also für alle 10 Elemente. Warum gibt es keine Operationen außer der Zuweisung? Das wird sehr schnell einsichtig, wenn man bedenkt, daß es ja sehr viele Datentypen (zusätzlich zu den elementaren) gibt, weil Reihungen von jedem Datentyp gebildet werden können: ROW 1 INT ROW 1 REAL ROW 2 INT ROW 2 REAL : : ROW maxint INT ROW maxint REAL ROW 1 TEXT ROW 1 BOOL ROW 2 TEXT ROW 2 BOOL : : ROW maxint TEXT ROW maxint BOOL Für die elementaren INT-, REAL-, BOOL- und TEXT-Datentypen sind unter­ schiedliche Operationen definiert. Man müßte nun für jeden dieser zusammengesetz­ ten Datentypen z.B. auch 'get'- und 'put'-Prozeduren schreiben, was allein vom Schreibaufwand sehr aufwendig wäre. Das ist der Grund dafür, daß es keine vorgege­ bene Operationen auf zusammengesetzte Datentypen gibt. Zugegebenermaßen könnte man mit solchen Datentypen, die nur über eine Operation verfügen (Zuweisung), nicht sehr viel anfangen, wenn es nicht eine weitere vorgege­ bene Operation gäbe, die Subskription. Sie erlaubt es, auf die Elemente einer Reih­ ung zuzugreifen und den Datentyp der Elemente "aufzudecken". Form: Rowname #on("i")##on("b")#[#off("i")##off("b")# Indexwert #on("i")##on("b")#]#off("i")##off("b")# Beispiel: feld [3] bezieht sich auf das dritte Element der Reihung 'feld' und hat den Datentyp INT. Für INT-Objekte haben wir aber einige Operationen, mit denen wir arbeiten können. ____________________________________________________________________________ ........................... Beispiele: ........................ feld [3] := 7; feld [4] := feld [3] + 4; ____________________________________________________________________________ Eine Subskription "schält" also vom Datentyp ein ROW ab und liefert ein Element der Reihung. Die Angabe der Nummer des Elements in der Reihung nennt man Subskript oder Index (in obigem Beispiel '3'). Der Subskript wird in ELAN in eckigen Klammern angegeben, um eine bessere Unterscheidung zu den runden Klammern in Ausdrücken zu erreichen. Ein subskribiertes ROW-Datenobjekt kann also überall da verwendet werden, wo ein entsprechender Datentyp benötigt wird (Ausnahme: nicht als Schlei­ fenvariable). ____________________________________________________________________________ ........................... Beispiel: ......................... PROC get (ROW 10 INT VAR feld): INT VAR i; FOR i FROM 1 UPTO 10 REP put (i); put ("tes Element bitte:"); get (feld [i]); line END REP END PROC get; PROC put (ROW 10 INT CONST feld): INT VAR i; FOR i FROM 1 UPTO 10 REP put (i); put ("tes Element ist:"); put (feld [i]); line END REP END PROC put ____________________________________________________________________________ In diesen Beispielen werden Reihungen als Parameter benutzt. Diese beiden Prozeduren werden im folgenden Beispiel benutzt um 10 Werte einzu­ lesen und die Summe zu berechnen: ____________________________________________________________________________ ........................... Beispiel: ......................... ROW 10 INT VAR werte; lies werte ein; summiere sie; drucke die summe und einzelwerte. lies werte ein: get (werte). summiere sie: INT VAR summe :: 0, i; FOR i FROM 1 UPTO 10 REP summe INCR werte [i] END REP. drucke die summe und einzelwerte: put (werte); line; put ("Summe:"); put (summe). ____________________________________________________________________________ Da es möglich ist, von jedem Datentyp eine Reihung zu bilden, kann man natürlich auch von einer Reihung eine Reihung bilden: ____________________________________________________________________________ ........................... Beispiel: ......................... ROW 5 ROW 10 INT VAR matrix ____________________________________________________________________________ Für eine "doppelte" Reihung gilt das für "einfache" Reihungen gesagte. Wiederum existieren keine Operationen für dieses Datenobjekt (außer der Zuweisung), jedoch ist es durch Subskription möglich, auf die Elemente zuzugreifen: matrix [3] liefert ein Datenobjekt mit dem Datentyp ROW 10 INT. Subskribiert man jedoch 'matrix' nochmals, so erhält man ein INT: matrix [2] [8] (jede Subskription "schält" von Außen ein ROW vom Datentyp ab). #page# 2.6.2 Struktur Strukturen sind Datenverbunde wie Reihungen, aber die Komponenten können unglei­ chartige Datentypen haben. Die Komponenten von Strukturen heißen Felder (Reihun­ gen: Elemente) und der Zugriff auf ein Feld Selektion (Reihungen: Subskription). Eine Struktur ist - genauso wie bei Reihungen - ein eigener Datentyp, der in einer Deklaration angegeben werden muß. Die Deklaration einer Struktur sieht folgendermaßen aus: - Schlüsselwort #schl ("STRUCT#off("i")##off("b")# - unterschiedliche Datenobjekte in Klammern. Die Datenobjekte werden mit Datentyp und Namen angegeben - Zugriffsrecht ( #on("i")##on("b")#VAR#off("i")##off("b")# oder #on("i")##on("b")#CONST#off("i")##off("b")# ) - Name der Struktur. ____________________________________________________________________________ ........................... Beispiel: ......................... STRUCT (TEXT name, INT alter) VAR ich ____________________________________________________________________________ Wiederum existieren keine Operationen auf Strukturen außer der Zuweisung und der Selektion, die es erlaubt, Komponenten aus einer Struktur herauszulösen. Die Selektion hat folgende Form: Objektname #on("i")##on("b")#.#off("i")##off("b")# Feldname Beispiele: ich . name ich . alter Die erste Selektion liefert einen TEXT-, die zweite ein INT-Datenobjekt. Mit diesen (selektierten) Datenobjekten kann - wie gewohnt - gearbeitet werden (Ausnahme: nicht als Schleifenvariable). Zum Datentyp einer Struktur gehören auch die Feldnamen: ____________________________________________________________________________ ........................... Beispiel: ......................... STRUCT (TEXT produkt name, INT artikel nr) VAR erzeugnis ____________________________________________________________________________ Die obige Struktur ist ein anderer Datentyp als im ersten Beispiel dieses Abschnitts, da die Namen der Felder zur Unterscheidung hinzugezogen werden. Für Strukturen - genauso wie bei Reihungen - kann man sich neue Operationen definieren. Im folgenden Programm werden eine Struktur, die Personen beschreibt, die Prozedu­ ren 'put', 'get' und der dyadische Operator HEIRATET definiert. Anschließend werden drei Paare verHEIRATET. ____________________________________________________________________________ ........................... Beispiel: ......................... PROC get (STRUCT (TEXT name, vorname, INT alter) VAR p): put ("bitte Nachname:"); get ( p.name); put ("bitte Vorname:"); get ( p.vorname); put ("bitte Alter:"); get ( p.alter); line END PROC get; PROC put (STRUCT (TEXT name, vorname, INT alter) CONST p): put (p.vorname); put (p.name); put ("ist"); put (p.alter); put ("Jahre alt"); line END PROC put; OP HEIRATET (STRUCT (TEXT name, vorname, INT alter) VAR w, STRUCT (TEXT name, vorname, INT alter) CONST m): w.name := m.name END OP HEIRATET; ____________________________________________________________________________ ____________________________________________________________________________ ........................... Beispiel: ......................... ROW 3 STRUCT (TEXT name, vorname, INT alter) VAR frau, mann; personendaten einlesen; heiraten lassen; paardaten ausgeben. personendaten einlesen: INT VAR i; FOR i FROM 1 UPTO 3 REP get (frau [i]); get (mann [i]) END REP. heiraten lassen: FOR i FROM 1 UPTO 3 REP frau [i] HEIRATET mann [i] END REP. paardaten ausgeben: FOR i FROM 1 UPTO 3 REP put (frau [i]); put ("hat geheiratet:"); line; put (mann [i]); line END REP. ____________________________________________________________________________ Reihungen und Strukturen dürfen miteinander kombiniert werden, d.h. es darf eine Reihung in einer Struktur erscheinen oder es darf eine Reihung von einer Struktur vorgenommen werden. Selektion und Subskription sind in diesen Fällen in der Reihen­ folge vorzunehmen, wie die Datentypen aufgebaut wurden (von außen nach innen). #page# 2.6.3 LET-Konstrukt für zusammengesetzte Datentypen Die Verwendung von Strukturen oder auch Reihungen kann manchmal schreibauf­ wendig sein. Mit dem LET-Konstrukt darf man Datentypen einen Namen geben. Dieser Name steht als Abkürzung und verringert so die Schreibarbeit. Zusätzlich wird durch die Namensgebung die Lesbarkeit des Programms erhöht. Form: #on("i")##on("b")#LET#off("i")##off("b")# Name #on("i")##on("b")#=#off("i")##off("b")# Datentyp Der Name darf nur aus Großbuchstaben (ohne Blanks) bestehen. ____________________________________________________________________________ ........................... Beispiel: ......................... LET PERSON = STRUCT (TEXT name, vorname, INT alter); PROC get (PERSON VAR p): put ("bitte Nachname:"); get ( p.name); put ("bitte Vorname:"); get ( p.vorname); put ("bitte Alter:"); get ( p.alter); line END PROC get; PROC put (PERSON CONST p): put (p.vorname); put (p.name); put ("ist"); put (p.alter); put ("Jahre alt"); line END PROC put; OP HEIRATET (PERSON VAR f, PERSON CONST m): f.name := m.name END OP HEIRATET; ROW 3 PERSON VAR mann, frau; ____________________________________________________________________________ Überall, wo der abzukürzende Datentyp verwandt wird, kann stattdessen der Name PERSON benutzt werden. Wohlgemerkt: PERSON ist kein neuer Datentyp, sondern nur ein Name, der für STRUCT (....) steht. Der Zugriff auf die Komponenten des abgekürzten Datentyps bleibt erhalten (was bei abstrakten Datentypen, die später erklärt werden, nicht mehr der Fall ist). Neben der Funktion der Abkürzung von Datentypen kann das LET-Konstrukt auch zur Namensgebung für Denoter verwandt werden (siehe 2.3.1.2). 2.6.4 Denoter für zusammengesetzte Datentypen (Konstruktor) Oft ist es notwendig, Datenverbunden Werte zuzuweisen (z.B.: bei der Initialisierung). Dies kann durch normale Zuweisungen erfolgen: ____________________________________________________________________________ ........................... Beispiel: ......................... LET PERSON = STRUCT (TEXT name, vorname, INT alter); PERSON VAR mann; mann.name := "meier"; mann.vorname := "egon"; mann.alter := 27 ____________________________________________________________________________ Eine andere Möglichkeit für die Wertbesetzung von Datenverbunden ist der Konstruk­ tor: Form: Datentyp #on("i")##on("b")#:#off("i")##off("b")# #on("i")##on("b")#(#off("i")##off("b")# Wertliste #on("i")##on("b")#)#off("i")##off("b")# In der Wertliste wird für jede Komponente des Datentyps, durch Kommata getrennt, ein Wert aufgeführt. Besteht eine der Komponenten wiederum aus einem Datenver­ bund, muß innerhalb des Konstruktors wiederum ein Konstruktor eingesetzt werden. ____________________________________________________________________________ ........................... Beispiel: ......................... LET PERSON = STRUCT (TEXT name, vorname, INT alter); PERSON VAR mann, frau; frau := PERSON : ( "niemeyer", "einfalt", 65); frau HEIRATET PERSON : ( "meier", "egon", 27) ____________________________________________________________________________ Ein Konstruktor ist also ein Mechanismus, um ein Datenobjekt eines Datenverbundes in einem Programm zu notieren. Konstruktoren sind natürlich für Reihungen auch möglich: ____________________________________________________________________________ ........................... Beispiel: ......................... ROW 7 INT VAR feld; feld := ROW 7 INT : ( 1, 2, 3, 4, 5, 6, 7); ____________________________________________________________________________ #page# 2.7 Abstrakte Datentypen 2.7.1 Definition neuer Datentypen Im Gegensatz zur LET-Vereinbarung für Datentypen, bei der lediglich ein neuer Name für einen bereits vorhandenen Datentyp eingeführt wird und bei der somit auch keine neuen Operationen definiert werden müssen (weil die Operationen für den abzukürzenden Datentyp verwandt werden können), wird durch eine TYPE-Verein­ barung ein gänzlich neuer Datentyp eingeführt. Form: #on("i")##on("b")#TYPE#off("i")##off("b")# Name #on("i")##on("b")#=#off("i")##off("b")# Feinstruktur Der Name darf nur aus Großbuchstaben (ohne Blanks) bestehen. Die Feinstruktur (konkreter Typ, Realisierung des Datentyps) kann jeder bereits definierte Datentyp sein. ____________________________________________________________________________ ........................... Beispiel: ......................... TYPE PERSON = STRUCT (TEXT name, vorname, INT alter) ____________________________________________________________________________ Der neudefinierte Datentyp wird abstrakter Datentyp genannt. Im Gegensatz zu Strukturen und Reihungen stehen für solche Datentypen noch nicht einmal die Zuwei­ sung zur Verfügung. Ein solcher Datentyp kann genau wie alle anderen Datentypen verwendet werden (Deklarationen, Parameter, wertliefernde Prozeduren, als Kompo­ nenten in Reihungen und Strukturen usw.). Wird der Datentyp über die Schnittstelle des PACKETs anderen Programmteilen zur Verfügung gestellt, so müssen Operatoren und/oder Prozeduren für den Datentyp ebenfalls "herausgereicht" werden. Da dann der neudefinierte Datentyp genauso wie alle anderen Datentypen verwandt werden kann, aber die Komponenten (Feinstruktur) nicht zugänglich sind, spricht man von abstrakten Datentypen. Welche Operationen sollten für einen abstrakten Datentyp zur Verfügung stehen? Obwohl das vom Einzelfall abhängt, werden meistens folgende Operationen und Prozeduren definiert: - 'get'- und 'put'-Prozeduren. - Zuweisung (auch für die Initialisierung notwendig). - Denotierungs-Prozedur (weil kein Konstruktor für den abstrakten Datentyp außer­ halb des definierenden PACKETs zur Verfügung steht) 2.7.2 Konkretisierung Um neue Operatoren und/oder Prozeduren für einen abstrakten Datentyp zu schrei­ ben, ist es möglich, auf die Komponenten des Datentyps (also auf die Feinstruktur) mit Hilfe des Konkretisierers zuzugreifen. Der Konkretisierer arbeitet ähnlich wie die Subskription oder Selektion: er ermöglicht eine typmäßige Umbetrachtung vom ab­ strakten Typ zum Datentyp der Feinstruktur. Form: #on("i")##on("b")#CONCR#off("i")##off("b")# #on("i")##on("b")#(#off("i")##off("b")# Ausdruck #on("i")##on("b")#)#off("i")##off("b")# ____________________________________________________________________________ ........................... Beispiel: ......................... TYPE MONAT = INT; PROC put (MONAT CONST m): put ( CONCR (m)) END PROC put; ____________________________________________________________________________ Der Konkretisierer ist bei Feinstrukturen notwendig, die von elementarem Datentyp sind. Besteht dagegen die Feinstruktur aus Reihungen oder Strukturen, dann wird durch eine Selektion oder Subskription eine implizite Konkretisierung vorgenommen. ____________________________________________________________________________ ........................... Beispiel: ......................... TYPE LISTE = ROW 100 INT; LISTE VAR personal nummer; ... personal nummer [3] := ... (* das gleiche wie *) CONCR (personal nummer) [3] := ... ____________________________________________________________________________ 2.7.3 Denoter für abstrakte Datentypen (Konstruktor) Denoter für neudefinierte Datentypen werden mit Hilfe des Konstruktors gebildet: Form: Datentyp #on("i")##on("b")#:#off("i")##off("b")# #on("i")##on("b")#(#off("i")##off("b")# Wertliste #on("i")##on("b")#)#off("i")##off("b")# In der Wertliste wird für jede Komponente des Datentyps, durch Kommata getrennt, ein Wert aufgeführt. Besteht eine der Komponenten wiederum aus einem Datenver­ bund, muß innerhalb des Konstruktors wiederum ein Konstruktor eingesetzt werden. ____________________________________________________________________________ ........................... Beispiel: ......................... TYPE GEHALT = INT; GEHALT VAR meins :: GEHALT : (10000); ____________________________________________________________________________ Besteht die Feinstruktur aus einem Datenverbund, muß der Konstruktor u.U. mehrfach geschachtelt angewandt werden: ____________________________________________________________________________ ........................... Beispiel: ......................... TYPE KOMPLEX = ROW 2 REAL; KOMPLEX CONST x :: KOMPLEX : ( ROW 2 REAL : ( 1.0, 2.0)); ____________________________________________________________________________ Auf die Feinstruktur über den Konkretisierer eines neudefinierten Datentyps darf nur in dem PACKET zugegriffen werden, in dem der Datentyp definiert wurde. Der Konstruk­ tor kann ebenfalls nur in dem typdefinierenden PACKET verwandt werden. ____________________________________________________________________________ ........................... Beispiel: ......................... PACKET widerstaende DEFINES WIDERSTAND, REIHE, PARALLEL, :=, get, put: TYPE WIDERSTAND = INT; OP := (WIDERSTAND VAR l, WIDERSTAND CONST r): CONCR (l) := CONCR (r) END OP :=; PROC get (WIDERSTAND VAR w): INT VAR i; get (i); w := WIDERSTAND : (i) END PROC get; PROC put (WIDERSTAND CONST w): put (CONCR (w)) END PROC put; WIDERSTAND OP REIHE (WIDERSTAND CONST l, r): WIDERSTAND : ( CONCR (l) + CONCR (r)) END OP REIHE; WIDERSTAND OP PARALLEL (WIDERSTAND CONST l, r): WIDERSTAND : ((CONCR (l) * CONCR (r)) DIV (CONCR (l) + CONCR (r))) END OP PARALLEL END PACKET widerstaende ____________________________________________________________________________ Dieses Programm realisiert den Datentyp WIDERSTAND und mit den Operationen eine Fachsprache, mit dem man nun leicht WIDERSTANDs-Netzwerke berechnen kann, wie z.B. folgendes: +---R4---+ | | +---R1---+ +---R5---+ | | | | ---+ +---R3---+ +--- | | | | +---R2---+ +---R6---+ | | +---R7---+ Zur Berechnung des Gesamtwiderstandes kann nun folgendes Programm geschrieben werden: ____________________________________________________________________________ ........................... Beispiel: ......................... ROW 7 WIDERSTAND VAR r; widerstaende einlesen; gesamtwiderstand berechnen; ergebnis ausgeben. widerstaende einlesen: INT VAR i; FOR i FROM 1 UPTO 7 REP put ("bitte widerstand R"); put (i); put (":"); get (r [i]); END REP. gesamtwiderstand berechnen: WIDERSTAND CONST rgesamt :: (r [1] PARALLEL r [2]) REIHE r [3] REIHE (r [4] PARALLEL r [5] PARALLEL r [6] PARALLEL r [7]). ergebnis ausgeben: line; put (rgesamt). ____________________________________________________________________________ #page# 2.8 Dateien Dateien werden benötigt, wenn - Daten über die Abarbeitungszeit eines Programms aufbewahrt werden sollen; - der Zeitpunkt oder Ort der Datenerfassung nicht mit dem Zeitpunkt oder Ort der Datenverarbeitung übereinstimmt; - die gesamte Datenmenge nicht auf einmal in den Zentralspeicher eines Rechners paßt; - die Anzahl und/oder Art der Daten nicht von vornherein bekannt sind. Eine Datei ("file") ist eine Zusammenfassung von Daten, die auf Massenspeichern aufbewahrt wird. Dateien sind in bestimmten Informationsmengen, den Sätzen ("re­ cords") organisiert. 2.8.1 Datentypen FILE und DIRFILE In ELAN gibt es zwei Arten von Dateien. Sie werden durch die Datentypen FILE und DIRFILE realisiert: FILE: sequentielle Dateien. Die Sätze können nur sequentiell gelesen bzw. geschrieben werden. Eine Positionierung ist nur zum nächsten Satz möglich. DIRFILE: indexsequentielle Dateien. Die Positionierung erfolgt direkt mit Hilfe eines Schlüssels ("key") oder Index, kann aber auch sequentiell vorgenommen werden. #on("b")#Wichtig: #off("b")# DIRFILEs sind auf dem EUMEL-System standardmäßig nicht implementiert! Deswe­ gen wird auf diesen Dateityp hier nicht weiter eingegangen. #page# 2.8.2 Deklaration und Assoziierung Dateien müssen in einem ELAN-Programm - wie alle anderen Objekte auch - deklariert werden. Form: #on("i")##on("b")#FILE#off("i")##off("b")# #on("i")##on("b")#VAR#off("i")##off("b")# interner Dateibezeichner ____________________________________________________________________________ ........................... Beispiel: ......................... FILE VAR f ____________________________________________________________________________ Dabei ist zu beachten, daß im EUMEL-System alle FILEs als VAR deklariert werden müssen, denn jede Lese/Schreib-Operation verändert einen FILE. Dateien werden normalerweise vom Betriebsystem eines Rechners aufbewahrt und verwaltet. Somit ist eine Verbindung von einem ELAN-Programm, in dem eine Datei unter einem Namen - wie jedes andere Datenobjekt auch - angesprochen werden soll, und dem Betriebssystem notwendig. Dies erfolgt durch die sogenannte Assozi­ ierungsprozedur. Die Assoziierungsprozedur 'sequential file' hat die Aufgabe, eine in einem Programm deklarierte FILE VAR mit einer bereits vorhandenen oder noch einzurichtenden Datei des EUMEL-Systems zu koppeln. Form: #on("i")##on("b")#sequential file#off("i")##off("b")# #on("i")##on("b")#(#off("i")##off("b")# Betriebsrichtung, Dateiname #on("i")##on("b")#)#off("i")##off("b")# Es gibt folgende Betriebsrichtungen (TRANSPUTDIRECTIONs): input: Die Datei kann vom Programm nur gelesen werden. Durch 'input' wird bei der Asso­ ziierung automatisch auf den ersten Satz der Datei positioniert. Ist die zu lesende Datei nicht vorhanden, wird ein Fehler gemeldet. output: Die Datei kann vom Programm nur beschrieben werden. Durch 'output' wird bei der Assoziierung automatisch hinter den letzten Satz der Datei positioniert (bei einer leeren Datei also auf den ersten Satz). Ist die Datei vor der Assoziierung nicht vor­ handen, wird sie automatisch eingerichtet. modify: Im EUMEL-System gibt es noch die Betriebsrichtung 'modify'. Die Datei kann vom Programm in beliebiger Weise gelesen und beschrieben werden. Im Gegensatz zu den Betriebsrichtungen 'input' und 'output', bei denen ausschließlich ein rein sequentielles Lesen oder Schreiben erlaubt ist, kann bei 'modify' beliebig positioniert, gelöscht, eingefügt und neu geschrieben werden. Nach erfolgter Assoziiierung ist auf den zuletzt bearbeiteten Satz positioniert. Die Datei wird automatisch eingerichtet, wenn sie vor der Assoziierung nicht vorhanden war. Der zweite Parameter der Assoziierungsprozedur gibt an, unter welchem Namen die Datei in der Task existiert oder eingerichtet werden soll. ____________________________________________________________________________ ........................... Beispiel: ......................... FILE VAR meine datei :: sequential file (output, "xyz"); ____________________________________________________________________________ Folgendes Beispiel zeigt ein Programm, welches eine Datei liest und auf dem Ausga­ bemedium ausgibt: ____________________________________________________________________________ ........................... Beispiel: ......................... FILE VAR f :: sequential file (input, "datei1"); TEXT VAR satz; WHILE NOT eof (f) REP getline (f, satz); putline (satz); END REP. ____________________________________________________________________________ Eine genau Übersicht der für Dateien existierende Operatoren und Prozeduren finden Sie im Teil 5.3. #page# 2.9 Abstrakte Datentypen im EUMEL-System 2.9.1 Datentyp TASK Tasks müssen im Rechnersystem eindeutig identifiziert werden; sogar im EUMEL- Rechner-Netz sind Tasks eindeutig identifizierbar. Dazu wird der spezielle Datentyp 'TASK' benutzt, denn die Identifizierung einer Task über den Namen ist nicht eindeu­ tig. Der Benutzer kann ja einen Tasknamen ändern, eine Task löschen und eine neue Task mit gleichem Namen einrichten, die jedoch nicht gleich reagiert. Somit werden Tasks eindeutig über Variablen vom Datentyp TASK identifiziert. ____________________________________________________________________________ ........................... Beispiel: ......................... TASK VAR plotter := task ("PLOTTER 1") ____________________________________________________________________________ Die Taskvariable 'plotter' bezeichnet jetzt die Task im System, die augenblicklich den Namen "PLOTTER 1" hat. Die Prozedur 'task' liefert den systeminternen Taskbe­ zeichner. Nun sind Taskvariablen auch unter Berücksichtigung der Zeit und nicht nur im aktuel­ len Systemzustand eindeutig. Der Programmierer braucht sich also keine Sorgen darüber zu machen, daß seine Taskvariable irgendwann einmal eine "falsche" Task (nach Löschen von "PLOTTER 1" neu eingerichtete gleichen oder anderen Namens) identifiziert. Wenn die Task "PLOTTER 1" gelöscht worden ist, bezeichnet 'plotter' keine gültige Task mehr. Unbenannte Tasks haben alle den Pseudonamen "-". Sie können nur über Taskvari­ ablen angesprochen werden. ____________________________________________________________________________ ........................... Beispiel: ......................... PROC generate shutup manager: TASK VAR son; begin ("shutup", PROC shutup manager, son) END PROC generate shutup manager; PROC shutup manager: disable stop; command dialogue (TRUE); REP break; line; IF yes ("shutup") THEN clear error; shutup FI PER END PROC shutup manager ____________________________________________________________________________ Ein Taskvariable wird zum Beispiel als Parameter für die Prozedur 'begin' benötigt. begin #on("b")#PROC begin (TEXT CONST son name, PROC start, TASK VAR new task)#off("b")# Die Prozedur richtet eine Sohntask mit Namen 'son name' (im Beispiel: shutup) ein, die mit der Prozedur 'start' (im Beispiel: shutup manager) gestartet wird. 'new task' (im Beispiel: son) identifiziert den Sohn, falls die Sohntask korrekt eingerich­ tet wurde. #page# 2.9.2 Datentyp THESAURUS Ein Thesaurus ist ein Namensverzeichnis, das bis zu 200 Namen beinhalten kann. Dabei muß jeder Name mindestens ein Zeichen und höchstens 100 Zeichen lang sein. Steuerzeichen (code < 32) werden im Namen folgendermaßen umgesetzt: #on("i")##on("b")#steuerzeichen#off("b")##off("i")# wird umgesetzt in #on("i")##on("b")#"""" + code(steuerzeichen) + """"#off("b")##off("i")# Ein Thesaurus ordnet jedem eingetragenen Namen einen Index zwischen 1 und 200 (einschließlich) zu. Diese Indizes bieten dem Anwender die Möglichkeit, Thesauri zur Verwaltung benannter Objekte zu verwenden. (Der Zugriff erfolgt dann über den Index eines Namens in einem Thesaurus). So werden Thesauri u.a. von der Dateiverwaltung benutzt. Sie bilden die Grundlage der ALL- und SOME-Operatoren. ____________________________________________________________________________ ........................... Beispiel: ......................... initialisiere; arbeite thesaurus ab. initialisiere: THESAURUS VAR eine auswahl :: SOME (myself); TEXT VAR thesaurus element; INT VAR index :: 0. arbeite thesaurus ab: REPEAT get (eine auswahl, thesaurus element, index); IF thesaurus element = "" THEN LEAVE arbeite thesaurus ab FI; fuehre aktionen durch PER. fuehre aktionen durch: edit (thesaurus element); lineform (thesaurus element); pageform (thesaurus element); print (thesaurus element). ____________________________________________________________________________ Dieses Beispiel führt für eine Auswahl der in der Task befindlichen Dateien nachein­ ander die Kommandos 'edit', 'lineform', 'pageform' und 'print' aus. Die benutzten Operatoren und Prozeduren leisten folgendes: #ix("SOME")# #on("b")#THESAURUS OP SOME (TASK CONST task) #off("b")# Der Operator bietet das Verzeichnis der in der angegeben Task befindlichen Dateien zum Editieren an. Namen, die nicht gewünscht sind, müssen aus dem Verzeichnis gelöscht werden. #ix("get")# #on("b")#PROC get (THESAURUS CONST t, TEXT VAR name, INT VAR index) #off("b")# Die Prozedur liefert den nächsten Eintrag aus dem angegebenen Thesaurus 't'. 'Nächster' heißt hier, der kleinste vorhandene mit einem Index größer als 'index'. Dabei wird in 'name'der Name und in 'index'der Index des Eintrags geliefert. #page# 2.9.3 Datenräume Datenräume sind die Grundlage von Dateien im EUMEL-System. Einen Datenraum kann man sich als eine Sammlung von Daten vorstellen (u.U. leer). Man kann einem Datenraum durch ein Programm einen Datentyp "aufprägen". Nach einem solchen "Aufpräge"-Vorgang kann der Datenraum wie ein "normaler" Datentyp behandelt werden. Standarddateien (FILEs) sind eine besondere Form von Datenräumen. Sie können nur Texte aufnehmen, da sie ja hauptsächlich für die Kommunikation mit dem Menschen (vorwiegend mit Hilfe des Editors bzw. Ein-/ Ausgabe) gedacht sind. Will man Zahlen in einen FILE ausgeben, so müssen diese zuvor in Texte umgewandelt werden. Hier­ für stehen Standardprozeduren zur Verfügung (z.B. 'put (f, 17)'). Will man aber Dateien zur Kommunikation zwischen Programmen verwenden, die große Zahlenmengen austauschen, verursachen die Umwandlungen von Zahlen in TEXTe und umgekehrt unnötigen Rechenaufwand. Zu diesem Zweck werden im EUMEL-System Datenräume eingesetzt, die es gestatten, beliebige Strukturen (Typen) in Dateien zu speichern. Solche Datenräume kann man weder mit dem Editor noch mit dem Standarddruckprogramm (print) bearbeiten, da diese ja den Typ des in dem Datenraum gespeicherten Objektes nicht kennen. 2.9.3.1 Datentyp DATASPACE Datenräume können als eigener Datentyp (DATASPACE) in einem Programm behan­ delt werden. Somit können Datenräume (als Ganzes) ohne Kenntnis eines eventuell (vorher oder später) aufgeprägten Typs benutzt werden. Als Operationen auf DATASPACE-Objekte sind nur Transporte, Löschen und Zuwei­ sung zugelassen. ____________________________________________________________________________ ........................... Beispiel: ......................... DATASPACE VAR ds ____________________________________________________________________________ Für Datenräume ist die Zuweisung definiert. Der Zuweisungsoperator (':=') bewirkt eine Kopie des Datenraums vom rechten auf den linken Operanden. ____________________________________________________________________________ ........................... Beispiel: ......................... DATASPACE VAR datenraum :: nilspace; ____________________________________________________________________________ Die Prozedur 'nilspace' liefert einen leeren Datenraum. Der Datenraum 'datenraum' ist also eine Kopie des leeren Datenraums. Die Prozeduren und Operatoren für Datenräume werden im Teil 5.4.7 beschrieben. #page# 2.9.3.2 BOUND-Objekte Wie bereits erwähnt, kann man einem Datenraum einen Datentyp aufprägen. Dazu werden #ib#BOUND#ie#-Objekte benutzt. Mit dem Schlüsselwort #on("i")##on("b")#BOUND#off("i")##off("b")#, welches in der Deklaration vor den Datentyp gestellt wird, teilt man dem ELAN-Compiler mit, daß die Werte eines Datentyps in einem Datenraum gespeichert sind bzw. gespeichert werden sollen. ____________________________________________________________________________ ........................... Beispiel: ......................... BOUND ROW 1000 REAL VAR liste ____________________________________________________________________________ Die Ankopplung des BOUND-Objekts an eine Datei erfolgt mit dem Operator #on("i")##on("b")#:=#off("i")##off("b")#. Form: BOUND-Objekt #on("i")##on("b")#:=#off("i")##off("b")# Datenraum ____________________________________________________________________________ ........................... Beispiel: ......................... BOUND ROW 1000 REAL VAR gehaltsliste := new ("Gehälter") ____________________________________________________________________________ Die Prozedur 'new' kreiert dabei einen leeren Datenraum (hier mit dem Namen 'Ge­ hälter'), der mit Hilfe der Zuweisung (hier: Initialisierung) an die Variable 'gehaltsliste' gekoppelt wird. Nun kann man mit der 'gehaltsliste' arbeiten wie mit allen anderen Feldern auch. Die Daten, die in 'gehaltsliste' gespeichert, werden eigentlich im Datenraum 'Gehälter' abgelegt. ____________________________________________________________________________ ........................... Beispiel: ......................... gehaltsliste [5] := 10 000.0; (* Traumgehalt *) gehaltsliste [index] INCR 200.0; (* usw. *) ____________________________________________________________________________ Man kann auch Prozeduren schreiben, die auf der Gehaltsliste arbeiten. ____________________________________________________________________________ ........................... Beispiel: ......................... PROC sort (ROW 1000 REAL VAR liste): ... END PROC sort; ... sort (CONCR (gehaltsliste)); ... ____________________________________________________________________________ Man beachte, daß der formale Parameter der Prozedur 'sort' nicht mit BOUND spezi­ fiziert werden darf (BOUND wird nur bei der Deklaration des Objekts angegeben). Das ist übrigens ein weiterer wichtiger Vorteil von BOUND-Objekten: man kann alle Prozeduren des EUMEL-Systems auch für BOUND-Objekte verwenden, nur die Datentypen müssen natürlich übereinstimmen. Häufige Fehler bei der Benutzung von Datenräumen - Wenn man an ein DATASPACE-Objekt zuweist (z.B.: DATASPACE VAR ds := new ("mein datenraum")), so erhält man, wie bereits erwähnt, eine Kopie des Datenraums in 'ds'. Koppelt man jetzt 'ds' an ein BOUND-Objekt an und führt Änderungen durch, so wirken diese nur auf die Kopie und nicht auf die Quelle. Für Änderungen in der Quelle, also in der vom Datei-Manager verwalteten Datei, ist stets direkt anzukoppeln. ____________________________________________________________________________ ........................... Beispiel: ......................... BOUND ROW 10 INT VAR reihe; INT VAR i; PROC zeige dsinhalt (TEXT CONST datenraum): BOUND ROW 10 INT VAR inhalt := old (datenraum); INT VAR j; line; putline ("Inhalt:" + datenraum); FOR j FROM 1 UPTO 10 REP put (inhalt (j)) PER END PROC zeige dsinhalt; (* falsch: es wird auf der Kopie gearbeitet: *) DATASPACE VAR ds := new ("Gegenbeispiel: Zahlen 1 bis 10"); reihe := ds; besetze reihe; zeige dsinhalt ("Gegenbeispiel: Zahlen 1 bis 10"); (* richtig: es wird auf dem Datenraum gearbeitet: *) reihe := new ("Beispiel: Zahlen 1 bis 10"); besetze reihe; zeige dsinhalt ("Beispiel: Zahlen 1 bis 10"). besetze reihe: FOR i FROM 1 UPTO 10 REP reihe (i) := i PER. ____________________________________________________________________________ Der Datenraum 'Gegenbeispiel: Zahlen 1 bis 10' wird nicht mit Werten besetzt, sondern die Kopie dieses Datenraums, der unbenannte Datenraum 'ds'. Auf dem direkt angekoppelten Datenraum 'Beispiel: Zahlen 1 bis 10' werden die Werte gespeichert. - Wenn man ein DATASPACE-Objekt benutzt, ohne den Datei-Manager zu verwenden, so muß man selbst dafür sorgen, daß dieses Objekt nach seiner Benutzung wieder gelöscht wird. Das Löschen geschieht durch die Prozedur 'forget'. Ein automatisches Löschen von DATASPACE-Objekten erfolgt nicht bei Programmende (sonst könnten sie ihre Funktion als Datei nicht erfüllen). Nur durch 'forget' oder beim Löschen einer Task werden alle ihr gehörenden DATASPACE-Objekte gelöscht und der belegte Speicherplatz freigegeben. - Ferner ist zu beachten, daß vor der Ankopplung an ein BOUND-Objekt das DATASPACE-Objekt initialisiert wird (im Normalfall mit 'nilspace'). ____________________________________________________________________________ ........................... Beispiel: ......................... DATASPACE VAR ds := nilspace; BOUND ROW 1000 REAL VAR real feld := ds; .... real feld [index] := wert; .... forget (ds) (* Datenraum löschen, damit der Platz wieder verwendet wird *) ____________________________________________________________________________ - Will man auf die Feinstruktur eines BOUND-Objekts zugreifen, so muß man strenggenommen den Konkretisierer benutzen: Form: #on("i")##on("b")#CONCR#off("i")##off("b")# #on("i")##on("b")#(#off("i")##off("b")# Ausdruck #on("i")##on("b")#)#off("i")##off("b")# Der Konkretisierer ermöglicht eine typmäßige Umbetrachtung vom BOUND-Objekt zum Datentyp der Feinstruktur. Ist der Zugriff jedoch eindeutig, so wird 'CONCR' automatisch vom Compiler ergänzt. ____________________________________________________________________________ ........................... Beispiel: ......................... BOUND INT VAR i := old ("i-Wert"); INT VAR x; x := wert. wert: IF x < 0 THEN 0 ELSE CONCR (i) FI. ____________________________________________________________________________ In diesem Beispiel muß der Konkretisierer benutzt werden, da sonst der Resultattyp des Refinements nicht eindeutig ist (BOUND oder INT?). 2.9.3.3 Definition neuer Dateitypen Durch die Datenräume und die Datentyp-Definition von ELAN ist es für Programmie­ rer relativ einfach, neue Datei-Datentypen zu definieren. In der Regel reicht der Datentyp FILE für "normale" Anwendungen aus, jedoch kann es manchmal sinnvoll und notwendig sein, neue Datei-Typen für spezielle Aufgaben zu definieren. In diesem Abschnitt soll an dem Beispiel DIRFILE (welcher zwar im ELAN-Standard definiert, aber nicht im EUMEL-System realisiert ist) gezeigt werden, wie ein neuer Datei-Datentyp definiert wird: ____________________________________________________________________________ ........................... Beispiel: ......................... PACKET dirfiles DEFINES DIRFILE, :=, dirfile, getline, ...: LET maxsize = 1000; TYPE DIRFILE = BOUND ROW maxsize TEXT; (* DIRFILE besteht aus TEXTen; Zugriff erfolgt ueber einen Schluessel, der den Index auf die Reihung darstellt *) OP := (DIRFILE VAR dest, DATASPACE CONST space): CONCR (dest) := space END OP :=; DATASPACE PROC dirfile (TEXT CONST name): IF exists (name) THEN old (name) ELSE new (name) FI END PROC dirfile; PROC getline (DIRFILE CONST df, INT CONST index, TEXT VAR record): IF index <= 0 THEN errorstop ("access before first record") ELIF index > maxsize THEN errorstop ("access after last record") ELSE record := df [index] FI END PROC getline; PROC putline (DIRFILE CONST df, INT CONST index, TEXT VAR record): ... END PROC putline; ... END PACKET dirfiles; ____________________________________________________________________________ Die Prozedur 'dirfile' ist die Assoziierungsprozedur für DIRFILEs (analog 'sequential file' bei FILEs). 'dirfile' liefert entweder einen bereits vorhandenen Datenraum oder richtet einen neuen ein. Um eine Initialisierung mit der 'dirfile'-Prozedur vorneh­ men zu können, braucht man auch einen Zuweisungsoperator, der den Datenraum an den DIRFILE-Datentyp koppelt. Zugriffe auf einen DIRFILE sind nun relativ einfach zu schreiben. Im obigen Beispiel wird nur die Prozedur 'getline' gezeigt. Nun ist es möglich, Programme zu schreiben, die den DIRFILE-Datentyp benut­ zen. ____________________________________________________________________________ ........................... Beispiel: ......................... DIRFILE VAR laeufer :: dirfile ("Nacht von Borgholzhausen"); INT VAR nummer; TEXT VAR name; REP put ("Startnummer bitte:"); get (nummer); line; put ("Name des Laeufers:"); get (name); putline (laeufer, nummer, name); line UNTIL no ("weiter") END REP; ... ____________________________________________________________________________ #page# 2.9.4 Datentyp INITFLAG Im Multi-User-System ist es oft notwendig, Pakete beim Einrichten einer neuen Task in dieser neu zu initialisieren. Das muß z.B. bei der Dateiverwaltung gemacht werden, da die neue Task ja nicht die Dateien des Vaters erbt. Mit Hilfe von INITFLAG-Objekten kann man zu diesem Zweck feststellen, ob ein Paket in dieser Task schon initialisiert wurde. INITFLAG #on("b")#TYPE INITFLAG #off("b")# Erlaubt die Deklaration entsprechender Flaggen. := #on("b")#OP := (INITFLAG VAR flag, BOOL CONST flagtrue) #off("b")# Erlaubt die Initialisierung von INITFLAGs initialized #on("b")#BOOL PROC initialized (INITFLAG VAR flag) #off("b")# Wenn die Flagge in der Task A auf TRUE oder FALSE gesetzt wurde, dann liefert sie beim ersten Aufruf den entsprechenden Wert, danach immer TRUE (in der Task A!). Beim Einrichten von Söhnen wird die Flagge in den Sohntasks automatisch auf FALSE gesetzt. So wird erreicht, daß diese Prozedur in den neu eingerichteten Söhnen und Enkeltasks genau beim ersten Aufruf FALSE liefert. ____________________________________________________________________________ ........................... Beispiel: ......................... PACKET stack DEFINES push, pop: INITFLAG VAR in this task := FALSE ; INT VAR stack pointer ; ROW 1000 INT VAR stack ; PROC push (INT CONST value) : initialize stack if necessary ; .... END PROC push ; PROC pop (INT VAR value) : initialize stack if necessary ; .... END PROC pop ;. initialize stack if necessary : IF NOT initialized (in this task) THEN stack pointer := 0 FI . END PACKET stack ____________________________________________________________________________