Die Sprache Pascal besitzt eine noch recht kurze Lebensgeschichte. Seit ihrer Vorstellung durch Professor Niklaus Wirth sind noch keine 15 Jahre vergangen. Wegen ihres klaren Stils und nicht zuletzt aufgrund des kompakten Sprachkerns existieren inzwischen zahlreiche Implementierungen auf Großrechenanlagen, Mini- und Microcomputern.
Bis heute existiert jedoch noch kein industrieller Sprachstandard im engsten Sinne des Wortes, wie er z.B. für FORTRAN und COBOL nicht zuletzt aus kommerziellen Erwägungen geschaffen wurde. Stattdessen beziehen sich alle Implementierungen auf das Buch von Jensen und Wirth »PASCAL, User Manual and Report« [1], in dem die Sprache PASCAL definiert wird und eine Implementation der Sprache auf einer CDC 6000 vogestellt wird.
Jedoch existieren bereits innerhalb dieses Dokumentes einige Inkonsistenzen (s. auch [3]), so daß eine absolute Kompatibilität zwischen Compilern, die sich an diesem report orientieren, nicht gewährleistet ist. Im Gegensatz zu den Sprachen C, FORTH, BASIC etc. beschränken sich die Unterschiede jedoch auf eher marginale Details, so daß es also keine essentiell verschiedenen »Pascal-Dialekte« gibt.
Nennenswerte Unterschiede gibt es nur in der Realisierung der sogenannten Standardprozeduren und -funktionen. Dabei sind insbesondere die Ein- und Ausgabeoperationen (auf Files) betroffen. Außerdem bieten die meisten Compiler zusätzliche Standardprozeduren. Diese »Verbesserungen« der Sprache machen jedoch Programme, die diese Erweiterungen nutzen, automatisch inkompatibel zu anderen Compilern.
Die vorliegende Realisierung der Sprache Pascal 2.0 auf dem C-128, einem Home-Computer, verfolgt folgende Philosophie: Zunächst sollte eine möglichst vollständige und getreue Implementierung des im report beschriebenen Sprachumfanges erreicht werden. Dabei mußte jedoch auch die Performanz auf dem C-128 berücksichtigt werden, damit die Entwicklung von Pascalprogrammen nicht zu einer langwierigen, disketten- und nervenaufreibenden Tortur wird. Daher erfolgt der Zyklus Quelltextänderung, übersetzung und Testlauf menügesteuert und ohne Zugriffe auf externe Speichermedien. Des weiteren sollten auch große Programme mit größeren Datenstrukturen möglich sein, so daß durch banking jeweils 64 K für Daten und übersetzte Programme zur Verfügung stehen. Letztendlich sollten Compiler und Laufzeitsystem ein einfaches debugging unterstützen, um den Übergang von einem Interpreter zu einem Compiler nicht allzu schwer zu gestalten.
Andererseits bietet der Computer im C-128-Modus unter BASIC eine hervorragende Unterstützung der Hardware (Grafik, Sound, Peripheriegeräte). Um dem Einsteiger in die Sprache Pascal nicht diese attraktiven Möglichkeiten des Computers vorzuenthalten, wurden dem Sprachumfang zahlreiche vordeklarierte Standardroutinen hinzugefügt.
Schließlich existiert noch eine große Palette an low-level- Sprachelementen, die die Fähigkeiten der Sprache nach »unten« zur Maschinensprache hin abrunden. Wie in TURBO-PASCAL und UCSD-Pascal sind außerdem Stringtypen und -operationen vorhanden.
Im zweiten Kapitel wurde die Sprache Pascal mit Beispielen schrittweise vorgestellt. In der Dokumentation werden daher nur Details angesprochen, die dort nur ungenau angesprochen werden konnten, oder die von anderen Implementierungen der Sprache abweichen.
Namen
Bezeichner dürfen beliebig lang sein. Dabei sind jedoch nur die ersten 14 Zeichen signifikant. Bei der Eingabe dürfen keine Buchstaben verwendet werden, die mit der SHIFT-Taste erreicht werden. Dies sind in Abhängigkeit vom Bildschirmmodus Grafikzeichen oder Großbuchstaben.
Datentypen
Die Speicherung der Werte der einzelnen Datentypen erfolgt nach dem folgenden Schema:
Die Standardtypen BOOLEAN, und CHAR, sowie alle Aufzählungstypen mit weniger als 257 Elementen werden in einem Byte gespeichert. Gleiches gilt für Ausschnittypen der Form A..B, wobei gilt
ORD(A)>=0 und ORD(B)<=255
Die übrigen Aufzählungs- und Ausschnittypen, der Typ INTEGER und Zeiger belegen 2 Bytes. Daher gilt für den Wertebereich einer Variablen X des Typs INTEGER:
-MAXINT-1 <= X <= MAXINT
Dabei ist MAXINT eine vordeklarierte Konstante mit dem Wert 32767. Mengen werden als Bit-Strings dargestellt. Dabei muß für Elemente einer Menge gelten:
0 <= ORD(X) <= 95
Dementsprechend benötigt jede Mengenvariable 12 Bytes. Ein Wert des Typs
STRING[Maxlen]
benötigt Maxlen+1 Bytes. Dabei muß Maxlen im Bereich von 1 bis 255 liegen. Fehlt die Angabe einer Maximallänge, so wird Maxlen=80 angenommen. Diese Strings belegen also 81 Bytes.
Nachdem nun der Speicherplatz der elementaren Datentypen bekannt ist, läßt sich der Speicherplatz für zusammengesetze Objekte nach folgenden Regeln bestimmen:
TYPE A: ARRAY [Indextyp] OF Komponententyp;
Bei Arrays berechnet sich die Größe des Typs A aus der Größe des Komponententyps multipliziert mit der Kardinalität des Indextyps.
TYPE F: FILE OF Komponententyp;
Eine Filevariable benötigt neben dem Speicherplatz für den Komponententyp noch 6 Bytes für den sogenannten Filedescriptor. Bei Recordtypen berechnet sich die Größe des Verbundtyps aus der Größe der Komponenten. Komponenten werden in der Reihenfolge ihrer Nennung in der Feldliste gespeichert. Bei varianten Records werden die verschiedenen Varianten auf denselben Speicherbereich abgebildet. Ein Beispiel soll diese Speicherverteilung illus- trieren:
R: RECORD A: INTEGER; Offset 0 B: REAL; Offset 2 CASE C: BOOLEAN OF Offset 7 TRUE: (S: SET OF CHAR; Offset 8 X: 0..9); Offset 20 FALSE: (R: REAL); Offset 8 END;
Die Variable R benötigt den Speicherplatz für die längste Variante, das sind 21 Bytes.
Das Schlüsselwort PACKED wird in Pascal 2.0 ignoriert.
Typkompatibilität
Zuweisungen sind in Pascal nur zwischen kompatiblen Typen zulässig. Die Typen der aktuellen Variablenparameter bei Prozeduren und Funktionen müssen mit den Typen der formalen Parameter übereinstimmen. Bei verschiedenen Implementierungen existieren jedoch geringfügig unterschiedliche Auffassungen darüber, welche strukturierten Typen gleich sind. In Pascal 2.0 gelten dabei folgende Regeln:
Zwei zusammengesetzte Variablen besitzen den selben Typ,
Beispiel:
TYPE FELD = ARRAY [0..1] OF INTEGER; VAR A: ARRAY [0..1] OF INTEGER; B: ARRAY [0..1] OF INTEGER; X,Y: ARRAY [0..1] OF INTEGER; L: FELD; M: FELD;
A und B besitzen nicht den gleichen Typ, obwohl sie eine identische Struktur besitzen. X und Y besitzen den gleichen Typ, da sie in einer Variablendeklaration definiert wurden. L und M besitzen den gleichen Typ, da sie beide mit dem Typbezeichner FELD deklariert wurden. Somit sind folgende Zuweisungen gültig:
X:= Y; L:=M
aber nicht
A:=B; A:=L; M:=X; A:=X
Bei den vordefinierten Typen ist nur der Typ STRING erwähnenswert, da die übrigen Kompatibilitätsregeln bereits in Kapitel 2 genau beschreiben wurden. Ein Stringausdruck ist eine Stringvariable, eine Stringkonstante, ein Einzelzeichen (vom Typ CHAR), ein ARRAY OF CHAR, oder das Ergebnis einer Stringfunktion (vordefiniert oder benutzerdefiniert).
Für Stringzuweisungen gelten folgende Regeln:
Für Wertparameter des Typs String gelten die gleichen Regeln wie für Zuweisungen an Stringvariablen.
Aktuelle Variablenparameter des Typs String müssen denselben Typ wie die formalen Parameter besitzen, dabei gelten die oben definierten Regeln für Pascal 2.0 über die Typkompatibilität zusammengesetzter Typen. Durch den aktiven Kommentar (*$P- *) (s. Abschnitt 4.4.6.2) wird diese Regel dahingehend gelockert, daß die Maximallänge des aktuellen Parameters größer oder gleich der Länge des formalen Parameters sein muß.
Feldbezeichner
Die Selektoren müssen im jeweils innersten Bereich eines Records identifizierbar sein. Natürlich dürfen auch gleichnamige Variablen im Programm existieren.
Beispiele:
TYPE DEMORECORD = RECORD A,B: RECORD A,B: BOOLEAN END; C,D: INTEGER; END; VAR R1 : DEMORECORD; A,B : INTEGER;
Die obigen Deklarationen sind gültig, da die Sichtbarkeit eines Selektors nur auf den innersten Bereich eines Records beschränkt ist, so daß es keine Konflikte zwischen den einzelnen Namen A und B geben kann. In einem With-Statement überdecken die Selektornamen die entsprechenden Variablennamen:
Beispiel: Nach der obigen Deklaration sind folgende Zuweisungen gültig:
A:= 3; B:= 4; WITH R1 DO BEGIN A.A:= TRUE; A.B:= FALSE; WITH B DO BEGIN A:= TRUE; B:= FALSE; END END;
Records mit Varianten
Die Varianten werden auf denselben Speicherplatz abgebildet (Details finden sich im vorangegangenen Abschnitt über Datentypen). Die Werte der Variantenwahl (tagfield) schränken zur Laufzeit den Zugriff auf die Varianten nicht ein. Es erfolgt also keine Prüfung des tagfields.
Beispiel:
TYPE TART = (KARTESISCH, POLAR); KOORDINATE = RECORD CASE ART:TART OF KARTESISCH: (X,Y: REAL); POLAR : (R,PHI: REAL); END; VAR A: KOORDINATE;
Die Variante X belegt den Speicherplatz der Variante R, außerdem erfolgt bei der Zuweisung
A.ART:= KARTESISCH; A.R:= 20.3;
keine Fehlermeldung, obwohl durch die Deklaration des Typs KOORDINATE ausgedrückt wird, daß die Variante R nur für ART = POLAR Gültigkeit besitzt.
With-Anweisung
Durch die Verwendung der With-Anweisung wird der Sichtbarkeitsbereich des genannten Records geöffnet, so daß nachfolgend Selektoren ohne Nennung der Variablen der With-Anweisung verwendet werden können. Wie unter dem Stichwort »Feldbezeichner« beschrieben, überdecken dabei Selektorbezeichner eventuell vorhandene gleichnamige Bezeichner (Variablen, Funktionen,...).
Neben dieser syntaktischen Bedeutung optimiert in Pascal 2.0 die Verwendung der With-Anweisung den erzeugten Code, da sämtliche Adreßberechnungen für die Variable innerhalb der With-Anweisung nur genau einmal ausgeführt werden müssen.
Beispiel:Nach der Deklaration
TYPE R: RECORD FELD3,FELD4,FELD5: INTEGER; END; VAR A: ARRAY [0..4,0..7] OF ^R;
ist folgende Anweisung kompakter und schneller
WITH A[I,J] DO FELD5:= FELD5 - FELD3als die Anweisung
A[I,J]^.FELD5:= A[I,J]^.FELD5 - A[I,J]^.FELD3;
Sprunganweisung
Die Sprunganweisung (GOTO Label) wurde wie im report beschrieben realisiert. Dies bedeutet, daß Sprünge beliebig innerhalb von Blöcken stattfinden können. Hingegen dürfen blockübergreifende Sprünge nur aus einer Prozedur in eine statisch umfassende Prozedur erfolgen. Dabei darf (wie auch im report definiert) nicht von außen in ein FOR..DO, CASE..OF, oder WITH..DO-Statement gesprungen werden. Diese Anweisungen benötigen nämlich zu ihrer korrekten Ausführung Zwischenergebnisse auf dem stack.
Beispiel:
PROGRAM LABELS (INPUT,OUTPUT); LABEL 1,2; VAR J: INTEGER; PROCEDURE P; LABEL 3,4; VAR I: INTEGER; BEGIN GOTO 3;(* 1 *) FOR I:= 1 TO 3 DO BEGIN 3: WRITELN(I); GOTO 4;(* 2 *) END; 4: GOTO 1;(* 3 *) END; (* P *) BEGIN GOTO 3;(* 4 *) FOR J:= 5 DOWNTO 0 DO BEGIN 2: WRITELN(J); IF J=0 THEN GOTO 1;(* 5 *) WRITELN(1/J); 1: WRITELN; P END; GOTO 2;(* 6 *) END.
Dieses Programm dient nur Demonstrationszwecken und ist teilweise syntaktisch fehlerhaft. Nachfolgend werden die durchnumerierten Sprunganweisungen besprochen.
Vorwärtsvereinbarungen
Da die Sprache Pascal explizit zur Übersetzung mit one pass compilern entworfen wurde, muß innerhalb eines Programmes jeder Name vor seinem ersten angewandten Auftreten ein definierendes Auftreten im Quelltext besitzen. Dabei sind nur die beiden folgenden Ausnahmen definiert worden, um selbstreferenzierende Datenstrukturen und indirekte Rekursionen zu ermöglichen:
Beispiel:
TYPE ZEIGER = ^KNOTEN; (* KNOTEN hier noch undefiniert *) KNOTEN = RECORD INFO: INFOTYP; NEXT: ZEIGER; END;
Beispiel:
PROGRAM FORWARDDEKLARATIONEN(INPUT,OUTPUT); PROCEDURE Q(I: INTEGER); FORWARD; PROCEDURE P(C: CHAR); BEGIN Q(ORD(C)); END; (* P *) PROCEDURE Q; BEGIN WRITELN(I); IF I>1 THEN P(CHR(I-1)); END; (* Q *) BEGIN Q(5); END.
Case-Statement
In Pascal 2.0 ist die Angabe eines ELSE-Zweiges hinter den Fallmarken möglich (s. Syntax-Diagramm im Anhang A). Dieser Zweig wird angesprungen, falls der Ausdruck nach dem Schlüsselwort CASE einen Wert liefert, der durch keine Fallmarke erfaßt wird. Wird das Schlüsselwort ELSE nicht angegeben, so wird zur Laufzeit beim Auftreten eines Wertes ohne zugehörige Marke die Fehlermeldung
NO LABEL IN CASE
erzeugt. Zwischen dem Schlüsselwort ELSE und dem Schlüsselwort END ist auch eine (evtl. leere) Folge von Anweisungen, getrennt durch Semikola zulässig.
Bit-Operatoren
Die Operatoren AND, OR und NOT können auch auf Werte vom Typ INTEGER angewendet werden. Es erfolgt dann eine bitweise Verknüpfung der Operatoren durch das logische UND, ODER bzw. eine bitweise Negation der Zahl. Die Operandentypen BOOLEAN und INTEGER dürfen aber nicht gemischt werden. Die Operationen liefern ein Ergebnis des gleichen Typs (INTEGER bzw. BOOLEAN).
Beispiele:
Operation | Ergebnis |
TRUE AND FALSE | FALSE |
3 AND 4 | 0 |
TRUE OR FALSE | TRUE |
3 OR 4 | 7 |
NOT TRUE | FALSE |
NOT 5 | -6 (Einerkomplement) |
TRUE OR 3 | -- Operation nicht zulässig -- |
Absolut adressierte Variablen
Bei einer Variablendeklaration in Pascal 2.0 ist die explizite Angabe einer absoluten Adresse für die Variable möglich. Für diese Variablen wird dann kein Speicher auf dem stack reserviert, sondern die Variable bleibt (auch bei rekursiven Aufrufen) an die bei der Deklaration vergebene Adresse gebunden.
Ein Anwendungsgebiet ist die gemeinsame Verwendung von Variablen aus Maschinenprogrammen und Betriebssystemadressen in Pascalprogrammen. Der Speicherplatzbedarf in Bytes für jeden Variablentyp läßt sich mit der Funktion SIZEOF berechnen.
Auf dem C-128 sind absolute Variablen nur in Bank 1 möglich. Ausgenommen hiervon sind Variablen in der sogenannten common area, die von der Adresse $0000 bis $0400 reicht. Um Speicheradressen in anderen Bereichen anzusprechen, müssen die Prozeduren POKE und PEEK verwendet werden.
Beispiel: Möchten Sie z.B. die Eckpunkte des lfd. Windows fest- stellen, so können Sie auf die in der Zero-Page gespeicherten Werte zurückgreifen:
VAR UNTEN : BYTE [228]; OBEN : BYTE [229]; LINKS : BYTE [230]; RECHTS: BYTE [231];
Mit diesen Variablen können Sie dann wie gewohnt arbeiten:
WRITELN('FENSTER VON', LINKS, ' BIS', RECHTS); LINKS:= LINKS+1; RECHTS:= LINKS+4;
In diesem speziellen Fall sollten Sie jedoch nach einer Veränderung der Betriebssystemvariablen für das aktive Fenster zunächst das Fenster neu initialisieren:
WRITE(CHR(19));
Stringoperationen
Die Deklaration von Stringvariablen wurde bereits in dem Abschnitt über Datentypen besprochen. Neben den in Abschnitt 4.4.4 vorgestellten Stringprozeduren und -funktionen sind noch folgende Operationen mit Strings zulässig, die sich an die Operationen mit skalaren Objekten anlehnen.
Beliebige Stringausdrücke (Stringvariablen, -konstanten, Funktionsergebnisse, ARRAYs OF CHAR und Einzelzeichen vom Typ CHAR) können mit den relationalen Operatoren =, <>, >=, <=, > und < verglichen werden. Das Ergebnis des Vergleiches ist vom Typ BOOLEAN und hängt vom zugrundeliegenden, Commodore-eigenen ASCII Zeichensatz ab (s. Anhang A im Basic-Handbuch).
Beispiel:
'OTTO' > 'ANNA' 'X ' <> 'X' 'X' <> 'x' 'ANNA' < 'ANNABELLE'
Schließlich können beliebige Stringausdrücke durch den Additionsoperator + verkettet (konkateniert) werden:
Beispiel:
VAR S: STRING; N: INTEGER; S:= 'Gemahlin'; N:= 5; S:= 'Hochzeitsgrüße an Sie und Ihre' + STR(N:0:0) + '. ' + S;
Funktionsergebnisse
In Pascal 2.0 kann das Ergebnis einer Funktion einen beliebigen, also auch zusammengesetzten Typ besitzen. Dieser Typ muß durch einen Typbezeichner definiert werden:
Beispiel:
PROGRAM LONGFUNCTIONRESULTS(INPUT,OUTPUT); TYPE RANDOMRECORD: RECORD X,Y: INTEGER; END; VAR R: RANDOMRECORD; S: STRING; FUNCTION RANDOM2: RANDOMRECORD; BEGIN RANDOM2.X:= INT(RANDOM(0)*32767); RANDOM2.Y:= INT(RANDOM(0)*32767) END; (* RANDOM2 *) FUNCTION RANDOMSTRING(SIZE: INTEGER): STRING; VAR S: STRING; BEGIN S:=''; REPEAT S:= S+CHR(INT(RANDOM(0)*20)+ORD('A')); UNTIL LENGTH(S)=SIZE; RANDOMSTRING:= S; END; (* RANDOMSTRING *); BEGIN R:= RANDOM2; S:= RANDOMSTRING(40); END.
Typbindungsoperatoren
Wie in der Sprache C sind in Pascal 2.0 explizite Typumwandlungen möglich. Durch die Nennung eines Typnamens wird ein Wert auf dem Stack an diesen Typ angepaßt:
PROGRAM TYPEBINDING(INPUT,OUTPUT); TYPE MONTH = (JAN, FEB, MRZ, APR, MAI, JUN, JUL, AUG, SEP, OKT, NOV, DEZ); VAR M: MONTH; I: INTEGER; P: ^INTEGER; BEGIN M:= MONTH(3); M:= MONTH(I); M:= MONTH(ORD(SUCC(MONTH)) MOD (ORD(DEZ)+1)); I:= INTEGER(M); I:= INTEGER(P); END.
Bei dieser Typanpassung werden keinerlei Prüfungen durchgeführt. Jedoch sollten Typanpassungen nur zwischen skalaren Werten und Zeigern durchgeführt werden.
Erweiterungen der Syntax
Im report ist die Reihenfolge der Deklarationsteile folgendermaßen festgelegt: Konstanten-, Typ-, Variablen- und dann Prozedurdeklarationen. In Pascal 2.0 können diese Teile in einer beliebigen Reihenfolge und auch mehrmals aufgeführt werden. Damit kann man z.B. Variablendeklarationen, die nur im Rumpf des Hauptprogrammes sichtbar sein sollen, hinter die Prozedurdeklarationen stellen. Insbesondere bei der Benutzung von Include-Files als abgeschlossene Funktionsmodule kann man private Variablen und Typen dieser Module zusammen mit den exportierten Prozeduren aufführen, ohne in Konflikte mit der Syntax zu geraten.
Schließlich wurde die Syntax für Stringkonstanten erweitert. Damit ist es möglich, Stringkonstanten zu definieren, die länger als eine Quelltextzeile sind und die Steuerzeichen enthalten. Außerdem können Stringkonstanten wahlweise durch Anführungszeichen »"« oder Hochkommata »'« eingeschlossen werden. Dabei darf ein String das entsprechende Zeichen nicht enthalten.
Beispiel:
PROGRAM SYNTAX(INPUT,OUTPUT); CONST RVSON = 18; RVSOFF = 146; VAR X: INTEGER; CONST SCONST1 = 'Dieser String ist länger als eine' "einzelne Bildschirmzeile"; SCONST2 = #13 #7 #7 'Dieser String enthält ' # RVSON 'Steuerzeichen' #RVSOFF ; SCONST3 = "In Anführungszeichen ist ein Hoch" "komma (') erlaubt "; BEGIN WRITE(#147, SCONST1, SCONST2); END.
In Pascal 2.0 existieren nur die im report definierten Wortsymbole. Daher kann es nicht zu Konflikten zwischen neu definierten Wortsymbolen und benutzerdefinierten Namen kommen. Die folgende Liste enthält alle Wortsymbole der Sprache Pascal, die nicht als Namen (z.B. für Variablen) verwendet werden dürfen:
and file not to array for of type begin forward or until case function packed var const goto procedure while div if program with do in record downto label repeat else mod set end nil then