home *** CD-ROM | disk | FTP | other *** search
-
- Pascomp
-
- Pascal baut einen Compiler
-
-
-
- Teil 3: Scanner
-
-
- Nachdem wir uns in der letzten Folge fundamentale Grundkenntnisse
- zum Bau eines Compilers erarbeitet haben, wollen wir nun die ein-
- zelnen Komponenten des Compilers genauer beleuchten und dabei mit
- dem Scanner beginnen. Aufgaben und Arbeitsweise dieses wichtigen
- Compiler- Bestandteiles sollen im Folgenden genauer betrachtet
- werden.
-
- Altmeister Wirth umschreibt den Aufgabenbereich des Scanners wie
- folgt: " Die Aufgabe des Scanners ist das Herausgreifen des näch-
- sten Symbols aus dem Quelltextprogramm nach den Regeln der ge-
- wählten lexikographischen Darstellung. Es ist üblich, daß gewisse
- Symbole aus mehreren Zeichen bestehen. Der Scanner bildet sodann
- Zeichenfolgen in Symbole ab. Zeichen sind somit atomare Einheiten
- im Eingabetext des Rechners, Symbole dagegen sind Grundbausteine
- der durch die Syntax unabhängig von bestimmten Zeichensätzen de-
- finierten Sprache. "
-
- Prinzipiell ist die lexikalische Analyse eine Teilaufgabe der
- syntaktischen Analyse (Parser) und könnte in einem normalen Par-
- sing- Algorithmus eingebaut werden. Über die Vorteile der separa-
- ten Konvertierung des aus einer Zeichenfolge bestehenden Source-
- programms in eine Folge von semantisch relevanten Symbolen wurde
- in der letzten Folge eingehend berichtet. Zur besseren Übersicht
- sind die Aufgaben des Scanners in der nachfolgenden Abbildung
- nochmals graphisch dargestellt und zusammengefaßt :
-
- Abbildung 1 : Scanner Interface.
-
- ┌────────────┐ ┌─────────┐
- │ Source │ ┌──────────────>──────────┤ Symbol │
- │ Programmm │ │ ┌──────<──────────┤ │
- └──────┬─────┘ │ │ │ │
- │ ┌──┴───────┴──┐ │ │
- └─>────────┤ lexikalische│ │ │
- ┌─<────────┤ Analyse │ │ │
- │ └────────┬────┘ │ Tabelle │
- │ │ └─────────┘
- ┌─────┴─────┐ │
- │ Parser │ │
- │ │ │
- └───────────┘ │
- │
- ┌───────────┴──────────────────────┐
- │ │
- │ Protokollierer │
- │ │
- └──────────────────────────────────┘
-
-
-
- Spezifikation und Realisierung der lexikalischen Analyse
-
- Als Eingabe für den Symbolentschlüsseler (deutscher Ausdruck für
- Scanner) dient das in einer Programmiersprache (hier PASCAL) ver-
- faßte Quelltextprogramm. In aller Regel besteht dieser Text aus
- Buchstaben, Ziffern und Sonderzeichen (+,-,*,/,....). Die Ausgabe
- stellt eine Folge von Symbolen dar, die das Quelltextprogramm in
- komprimierter und abstrakter Darstellungsweise enthält. Jedes
- einzelne Symbol ist dabei ein Tripel der folgenden Gestalt :
-
- S = (Symbolklasse, Merkmal, Position)
-
- Es bedeuten hierbei :
- Symbolklasse : trägt die syntaktischen Informationen. Jedes Ele-
- ment der Symbolklassen-Menge entspricht genau einem Terminal der
- Zerteiler- Grammatik. Der Typ 'Symbolklasse' könnte in PASCAL
- etwa folgendermaßen definiert werden :
-
- Type Symbolklasse=
- (Bezeichner, Ganze_Zahl, Real_Zahl, Zeichen, ...., Beginsymb,
- Endsymb, Assignsymb,..);
-
- -Merkmal:
- identifiziert die Symbole einer Klasse. So kann beispielsweise
- eine Indexnummer ein Merkmal genau dann für einen Bezeichner
- sein, wenn der Index die Stelle in einer Tabelle angibt, an der
- der Bezeichner vermerkt ist. Im Gegensatz dazu ist bei ganzen
- Zahlen (Integer-Zahlen) eben diese Zahl selbst das Merkmal des
- Symbols.
-
- -Position:
- spiegelt im Symboltripel die Position des aktuellen Symbols im
- Quelltext wider. Dieser Eintrag wird benötigt, um bei eventuellen
- Fehlern im Sourceprogramm positionsbezogene Fehlerausgaben machen
- zu können.
-
- Beispiele für Symboltripel sind :
-
- (Bezeichner, 42, (10, 20))
- (Progsymb, /, ( 1, 1))
- (Ganze_Zahl,111, (20, 21))
- ......
-
-
- Grundsymbole und spezielle Zeichenfolgen
-
- Unter dem Begriff Grundsymbol versteht man die Zusammenfassung
- von Bezeichnern, Zahlen und Sondersymbolen.
-
- Dabei bedeuten :
-
- Bezeichner(Identifier) : Bezeichner bestehen aus einer Folge von
- Buchstaben und Ziffern, die mit einem Buchstaben beginnen muß.
- Bezeichner dienen zur Identifizierung von Objekten in Programmen
- wie beispielsweise Variablen, Label, Typen, Konstanten usw. Zur
- sinnvollen Benennung von Objekten sollte die Länge von Bezeich-
- nern weder begrenzt noch auf signifikante Zeichen beschränkt
- sein. An die im Zusammenhang mit der Längenbegrenzung auftreten-
- den Probleme erinnern sich sicherlich die FORTRAN- und BASIC-
- Programmierer, während die User von ALGOL und ADA nie mit derar-
- tigen Schwierigkeiten konfrontiert wurden.
-
- Wie bereits in der letzten Folge ausführlich behandelt, lassen
- sich auch Bezeichner mit Hilfe des Automatenschemas graphisch
- darstellen und erläutern. Der Bezeichnerautomat sieht in den mei-
- sten Fällen folgendermaßen aus :
-
-
- Abbildung 2 : Bezeichnerautomat.
- Bu = Buchstaben-
- ┌───<─┐ menge
- │ │ Bu, Zi
- ┌─┴──┐ │ Zi = Ziffernmenge
- (q[1]) ────────────> │q[2]├>─┘
- └────┘
-
-
- Dabei variiert der Umfang der Buchstabenmenge von Programmier-
- sprache zu Programmiersprache. So ist in der Sprachdefinition von
- MODULA-2 vereinbart, daß Groß- und Kleinbuchstaben als verschie-
- dene Zeichen zu betrachten sind. Der PASCAL-Report läßt dagegen
- diese Frage für PASCAL offen. So gilt bei Berkeley-PASCAL die
- gleiche Regelung wie bei MODULA- 2. Turbo-PASCAL macht, wie die
- meisten Leser wohl wissen, keinen Unterschied zwischen Groß- und
- Kleinschreibung. Wieder andere Pascal- Versionen erlauben nur
- großgeschriebene Schlüsselwörter. Einige Beispiele für Bezeichner
- sind :
-
- BEZEICHNER, E41, Hallo, .....
-
-
- Zahlen :
- Eine Zahl ist eine Folge von Ziffern, die eventuell Sonderzeichen
- ('+','-','.') und Buchstaben ('E','e') enthalten kann. Beispiele
- für Zahlen sind :
-
- +0, .14, +1.23e-13, .....
-
- Sondersymbole :
- Die Sondersymbole werden in der Literatur üblicherweiser in drei
- Klassen eingeteilt :
-
- -Wortsymbole , die der gleichen Syntax wie Bezeichner genügen,
- und die eine in der Programmiersprache fest vorgegebene Bedeu-
- tung besitzen. Unter den Wortsymbolen unterscheidet man noch
- weiterhin
-
- -reservierte Wörter und vordefinierte Bezeichner .
- Reservierte Wörter dürfen nur in der Weise benutzt werden,
- wie sie in der Programmiersprache vorgesehen sind ('be-
- gin', 'end', 'repeat', ... in PASCAL). Im Gegensatz dazu
- kann vordefinierten Bezeichnern ('put', 'get', 'new', ...
- in PASCAL ) in Programmen eine neue Bedeutung zugewiesen
- werden.
-
- -Einfache Symbole, die durch einzelne Sonderzeichen dargestellt
- werden. Beispiele einfacher Symbole sind : '+', '-' (beide als
- diadische Operatoren fungierend), '*', '/', .... ü
-
- -Zusammengesetzte Symbole werden durch eine Folge von zwei oder
- mehreren Sonderzeichen dargestellt, wie beispielsweise : '=',
- '**', '<=', '>=', '<>', ...
-
-
- Eine wichtige Rolle in der lexikalischen Analyse spielt darüber-
- hinaus das Leerzeichen (Blank) . Diesem kommt in verschiedenen
- Programmiersprachen eine unterschiedlich große Bedeutung zu, so
- daß man darüber informiert sein sollte. Während das Blank in den
- meisten Programmiersprachen die Aufgabe eines Separators erfüllt,
- d.h. aufeinanderfolgende Bezeichner, Zahlen oder Schlüsselwörter
- trennt, ist das Leerzeichen in einigen Programmiersprachen, wie
- beispielsweise FORTRAN, völlig bedeutungslos. Es kann hier sogar
- in einem Grundsymbol stehen. Folgendes FORTRAN-Statement mag
- hierfür als Beispiel dienen :
-
- DO 10 I = 1.5
- A(I)=X+B(I)
- 10 CONTINUE
-
- Auf den ersten Blick könnte man meinen, es handele sich hier um
- eine FORTRAN-DO Schleife, in der an die Variable I nacheinander
- die Werte 1,2,3,4 und 5 zugewiesen werden. In diesem Falle müßte
- jedoch anstelle des Punktes ein Komma stehen. Eine Programmier-
- sprache mit wohldefinierter Syntax würde solche 'mißverständli-
- chen' Statements nicht zulassen. FORTRAN akzeptiert dies jedoch
- als korrektes Zuweisungs-Statement, in der die Variable DO10I den
- Wert 1.5 erhält. Man erzählt sich in diesem Zusammenhang immer
- wieder gerne die Geschichte von dem unbenannten US-Raumschiff,
- das eigentlich zur Venus sollte, aber doch sein Ziel verfehlte
- und verloren ging, weil der Bordrechner einen Software-Fehler
- hatte, der auf eben diesen Fall zurückzuführen war.
-
-
- Erkennung von Grundsymbolen
-
- Die Erkennug von Grundsymbolen wird mitunter auch als Ausblenden
- von Symbolen bezeichnet. Der Symbolentschlüsseler (Scanner) muß
- in der Lage sein, das Ende jedes Symbols zu erkennen. Das hört
- sich simpel an, ist es aber in bestimmten Spezialfällen nicht
- immer. Betrachten wir dazu das folgende PASCAL-Programmstück :
-
- .....
- var a:real;
- begin
- a:=2
- end;
- .....
-
-
- Das vorliegende Problem ist klar : An die Real-Variable a wird
- eine Integer-Konstante (2) zugewiesen.
- Daß es sich aber um eine Integer- Konstante handelt, kann erst
- beim Lesen des 'n' (von 'end') entschieden werden, da das 'e'
- (von 'end') auch den Anfang des Exponenten einer Real-Konstanten
- kennzeichnen kann. Der Scanner muß demnach zwei Zeichen voraus-
- schauen, um eine Entscheidung treffen zu können.
-
- Symbole werden daher in aller Regel nach dem Prinzip des längsten
- Musters ausgeblendet. Der Scannerautomat liest dabei solange Zei-
- chen für Zeichen ein, bis es im Zustand q keinen Übergang mit dem
- nächsten Zeichen mehr gibt. Dann ist der aktuelle Zustand q ent-
- weder ein Endzustand des Automaten, an dem die eingelesene Zei-
- chenfolge als Symbol akzeptiert wird, oder der Automat wird auf
- den zuletzt durchlaufenen Endzustand zurückgesetzt, und die bis
- dahin gelesene Zeichenfolge wird akzeptiert. Zur Programmierung
- des Automaten gilt auch hier das bereits in der letzten Folge
- Gesagte :
-
- - entweder der Scannerautomat wird als Übergangsmatrix implemen-
- tiert, was in PASCAL-Struktur etwa so aussähe :
-
- while symbol noch nicht erkannt do
- zustand:=matrix[zustand,nächstes_zeichen]
- .......
-
- - oder die Übergangsmatrix wird ausprogrammiert :
-
- while symbol noch nicht erkannt do
- begin
- .....
- case zustand of
- 0 : case nächstes_Zeichen of
- buchstabe : zustand := .....
- ... end ;
- .....
- end ;
- .....
-
- end;
-
-
- Verwaltung von Symbolen
-
- Daß einmal erkannte Bezeichner nicht irgendwo im Speicher ver-
- schwinden dürfen, ist klar. Es muß in späteren Phasen der Über-
- setzung immer wieder möglich sein, auf die bereits gelesenen Sym-
- bole zurückzugreifen. Dies ist zum Beispiel bei der Anfertigung
- eines protokollierten Listings erforderlich. Dazu müssen einmal
- gelesene und erkannte Symbole in irgendeiner Art und Weise gele-
- sen und abgespeichert werden. Die dafür geeignete Datenstruktur
- ist die Symboltabelle, deren Aufgabe es ist, Bezeichner und Wort-
- symbole zu codieren.
-
- Diese Codierung muß eine bijektive Abbildung vom Bezeichnertext
- auf ein zur Indizierung geeignetes Merkmal sein (diese Merkmale
- können Ausschnitte aus der Menge der ganzen Zahlen sein). Die
- Operationen, die auf der Datenstruktur 'Symboltabelle' mindestens
- verfügbar sein sollten, sind :
-
- - Initialisierung:
- Eintragung der Standardbezeichner und Wortsymbole.
-
- - Codierung:
- Abbildung des Bezeichnertextes auf das Merkmal. Dies geschieht
- durch Suchen des Bezeichnertextes in der Tabelle und Neueintra-
- gung, falls der Bezeichner noch nicht in der Symboltabelle ent-
- halten ist. Dieses bedingt, daß ein wiederholtes Auftreten des
- gleichen Bezeichners keinen Neueintrag zur Folge hat.
-
- - Decodierung:
- Abbildung des Merkmals auf den Bezeichnertext.
-
- - Sortierung:
- Ordnen der Symboltabelle in lexikographischer Reihenfolge.
-
- - nächstes_Symbol:
- Bestimmung des nachfolgenden Symbols.
-
- Die Operation 'Decodierung' findet bei der Protokollierung und
- beim Crossreferencing Anwendung, während die Operationen 'Sortie-
- rung' und 'nächstes Symbol' nur beim Crossreferencing benötigt
- werden.
-
-
- Zur Implementierung der Symboltabelle
-
- Die beiden bekanntesten Verfahren, Symboltabellen zu implementie-
- ren, sind die Darstellung als binärer Baum und die Organisation
- der Symboltabelle durch Hashing. Beide Methoden werden in der
- Praxis etwa gleichermaßen benutzt, so daß auf beide hier kurz
- eingegangen werden soll.
-
-
- Darstellung der Symboltabelle durch Hashing
-
- Beim Hashing geht man von einer totalen Funktion h aus. h heißt
- die Hashfunktion und ordnet jedem Bezeichner A eindeutig einen
- Wert h(A), den Hashcode von A, zu. h(A) muß innerhalb eines vor-
- gegebenen Intervalls [0..hashmax] liegen und kann als Index einer
- Hashtabelle benutzt werden. Hierin kann ein zum Bezeichner A ge-
- hörendes Element abgespeichert bzw. gesucht werden.
- Beispiel einer Hashfunktion:
-
- Sei A ein Bezeichnertext und A der i-te Buchstabe des Bezeich-
- ners, dann könnte
-
- h(A)= (ord(A,,t1)+ord(ALaenge(A))) mod (hashmax+1)
-
- eine mögliche Hashfunktion sein.
-
- Durch die Abbildung eines Bezeichnertextes auf einen Index des
- Hashvektors können mitunter auch Probleme auftreten : Gibt es
- nämlich in einer Symboltabelle Einträge zu Bezeichnern mit unter-
- schiedlichen Bezeichnertexten, aber gleichem Hashcode, so treten
- Kollisionen auf. Kollisionen können aufgelöst werden, indem alle
- Bezeichner mit gleichem Hashcode in eine Liste aufgenommen wer-
- den. Der Anfang einer Liste kann über den Hashcode bestimmt wer-
- den. Man spricht dann von einer Kollisionsauflösung durch Verket-
- tung (chaining), die in dem folgenden Bildchen erläutert wird :
-
- Abbildung 3 : Implementierung der Symboltabelle durch Hashing mit
- Kollisionsauflösung durch Verkettung.
-
- ┌────────────────────────────────────────┐
- Quelltext : │ while x1 y1 abc xy1 .... │
- └────────────────────────────────────────┘
-
-
-
-
- Type Stentry = record
- ┌────┬─────┬────┬────┬────┬─────┬─────┐
- Anfang : Textindex; │ │ │ │ │ │ │ │
- │ │ │ │ │ │ │ │
- ├────┼─────┼────┼────┼────┼─────┼─────┤
- Länge : Textindex; │ │ 5 │ 2 │ │ │ │ │
- │ │whsym│ bez│ bez│ bez│ bez│ │
- ├────┼─────┼────┼────┼────┼─────┼─────┤
- Ki : Symbolklasse; │ │ │ │ │ │ │ │
- │ │ │ │ │ │ │ │
- ├────┼─────┼────┼────┼────┼─────┼─────┤
- nächster: Stindex; │ │ │ │ │ │ │ │
- │ │ │ │ │ │ │ │
- ├────┼─────┼────┼────┼────┼─────┼─────┤
- Kollision:Stindex; │ │ │ │ │ │ │ │
- │ │ ~ │ . │ ~ │ ~ │ ~ │ │
- └────┴┬────┴┬───┴─┬──┴─┬──┴──┬──┴─────┘
- end; │ │ │ │ │ │
- │ │ └───┼────┼─────┘
- │ │ │ │
- │ ┌──┘ │ │
- ┌─────┘ │ ┌────┘ │
- │ ┌────┘ │ ┌───┘
- ┌──┬──┬──┬┴─┬─┴┬──┬──┬─┴┬──┬─┴┬──┬──┬──┬──┬──┐
- Hashvektor: │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
- └──┴──┴──┴──┴┬┬┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘...
- ││
- x1 ──>─┘└─<─ xy1
-
-
- Verwendete Hashfunktion : (ord(Text[1] + ord(Text[länge(Text)])
-
- mod (hashmax + 1)
-
-
-
- Eine weitere Möglichkeit der Kollisionsauflösung ist das Rehas-
- hing . Dabei werden bei Kollisionen solange weitere Hashfunktio-
- nen auf den Bezeichnertext angewendet, bis keine Kollisionen mehr
- auftreten.
-
-
- Darstellung der Symboltabelle als Heap.
-
- Bei dieser Implementierungsstrategie wird ein binärer Baum aufge-
- baut, der als Heap verwaltet wird. Zur Erinnerung : Ein binärer
- Baum ist genau dann ein Heap, wenn für jeden Knoten des Baumes
- gilt : Der Wert des linken Sohnes ist kleiner und der des rechten
- Sohnes größer als der Wert des Vater-Knotens. Die Symbole werden
- in der Reihenfolge ihres Eintreffens in den Baum eingefügt. Der
- zugehörige Einfüge-Algorithmus sieht in PASCAL- Notation dann
- etwa folgendermaßen aus :
-
-
- type heap= ^heappointer;
- heappointer= record
- s:symbol;
- left_son,right_son:heap;
- end;
- ........
-
- procedure insert_symbol(s:symbol; var h:heap);
- var h1:heap;
- begin
- if h=nil then
- (* der Baum ist durchsucht worden; s wird als neuer Knoten
- eingefügt *)
- begin
- new(h1);
- h1^.s:=s;
- h:=h1;
- end
- else if h^.s<s then
- insert_symbol(s,h^.left_son)
- else if h^.s>s then
- insert_symbol(s,h^.right_son)
- else
- (* nix. h^.s=s --> s ist bereits im Baum enthalten und
- wird nicht mehr eingefügt *)
- end ; (* of insert_symbol *)
-
-
- Unter der Voraussetzung des zufälligen Eintreffens von Elementen,
- liegt bei der Organisation der Symboltabelle als binärer Baum der
- Zeitaufwand für Suchen und Einfügen gleichermaßen in der Größen-
- ordnung O(log2n). Das beschriebene Heap-Verfahren ist das effi-
- zienteste, das auf Vergleichen basiert und findet auch in unserem
- Übersetzer Verwendung.
-
- Wem die hier beschriebenen Erläuterungen für die Symboltabelle
- nicht ausreichen sollten, der findet in der DATA-BOX noch zwei
- Implementierungen zu diesem Thema.
-
-
- Reduzierte Automaten
-
- Es existiert ein Algorithmus, der zu jedem endlichen Automaten
- einen reduzierten endlichen Automaten konstruiert, der die glei-
- che Sprache akzeptiert. Der Vorteil, der sich aus der Existenz
- eines solchen Algorithmus ergibt, liegt darin, sich bei der Pla-
- nung und Entwicklung eines Automaten auf funktionelle Gesicht-
- spunkte beschränken zu können, ohne auf die Effizienz des Automa-
- ten (z.B. Größe der Zustandsmenge ) achten zu müssen.
-
- Die dem Verfahren des reduzierten endlichen Automaten zugrunde-
- liegende Idee besteht darin, die Menge der Zustände zu partitio-
- nieren (aufzuteilen). Dazu geht man wie folgt vor: In der ersten
- Partitionierung teilt man die Menge der Zustände in zwei Blöcke
- von Endzuständen und Nicht-Endzuständen auf. Man ersetzt nun die
- Ergebnisse der Abbildungen von Zustand und Zeichen in der Über-
- gangstabelle durch die zugehörigen Blöcke. Wenn sich in diesem
- Block nach diesem Vorgang gleiche Spalten gebildet haben, so faßt
- man diese Spalten zu einem neuen Block zusammen. Dieser Vorgang
- wird solange durchgeführt, bis sich keine neuen Blöcke mehr bil-
- den lassen. Aus den so konstruierten Partitionen werden dann die
- Zustände des reduzierten endlichen Automaten gebildet.
-
-
- Zuguterletzt sei hier noch der vollständige Scanner unseres Über-
- setzers abgedruckt. Mit den geschaffenen Grundlagen dürfte es ein
- Leichtes sein, seinen Aufbau zu verstehen. Viel Spaß beim Nach-
- vollziehen!
-
-
- procedure insymbol;
- (* bestimmt das naechste Symbol aus der Eingabe *)
- label 1,2,3;
- var i,k:integer;
- digit : packed array[1..strglgth] of char;
- (* Buffer fuer Ziffern *)
- string: packed array[1..strglgth] of char; (* Buffer fuer
- Stringkonstante *)
- lvp : csp;
- test : boolean;
- wert : long_integer;
-
- procedure nextch;
- (* liest das naechste Zeichen aus der Eingabe und weist es an ch
- zu *)
- begin
- if eol then
- begin
- if list
- (* komplettes Compiler-Listing *) then writeln(output);
- endofline;
- linepos:=1; (* Zeiger auf den Zeilenpuffer 'line', der immer
- genau eine Eingabezeile aufnimmt *)
- end;
- if not eof(input) then
- begin
- eol:=eoln(input);
- read(input,ch);
- enterline(ch); (* fuegt das neu gelesene Zeichen in den
- Zeilenpuffer 'line' ein *)
- if list then write(output,ch);
- chcnt:=chcnt+1 (* 'charactercounter' *)
- end
- else
- begin
- writeln(output,' *** eof encountered');
- test:=false
- end;
- end;
-
- procedure options;
- (* erkennt Compileroptionen, die mit (*$<Option>{+,-} eingeleitet
- werden.
- Optionen sind :
- l : kommentiertes Compiler-Listing
- d :Debug-Option : Wenn diese eingeschaltet ist, wird Code
- zur Ueber- pruefung von Grenzbereichen, Aufzaehlungsty-
- pen ,... erzeugt.
- c : Compiler-Option. Eingeschaltet erzeugt diese Option in-
- terpretierbaren P-Code *)
-
-
- begin
- repeat
- nextch;
- if (ch<>'*') and (ch<>'}') then
- begin
- if ch='l' then
- begin
- nextch;
- list:=ch='+';
- if not list then writeln(output)
- end
- else
- if ch='d' then
- begin
- nextch;
- debug:=ch='+'
- end
- else
- if ch='c' then
- begin
- nextch;
- prcode:=ch='+'
- end;
- nextch
- end
- until ch<>',';
- end;
-
- begin (* of insymbol *)
- 1:
- repeat
- while (ch=' ') and not eol do nextch; (* ueberlese
- Blanks..... *)
- test:=eol;
- if test then nextch (* und Separatoren. *)
- until not test;
- if chartp[ch]=illegal then
- (* chartp gibt den typ jedes Zeichens an. Es wird dabei nach
- Buchstaben, Ziffern und Sonderzeichen unterschieden *)
- begin
- (* illegales Zeichen erkannt. *)
- sy:=othersy;
- op:=noop;
- error(399);
- nextch
- end
- else
- case chartp[ch] of
- letter: (* Bezeichner einlesen *)
- begin
- k:=0;
- repeat
- if k<8 then (* Aufnahme des Bezeichners in einen Zwi-
- schenpuffer *)
- begin k:=k+1;
- id[k]:=ch
- end;
- nextch
- (* Das Ende eines Bezeichners ist durch einen Separa-
- tor bestimmt *)
- until chartp[ch] in [special,illegal,chstrquo,chcolon,
- chperiod,chlt,chgt,chlparen,chspace];
- (* kk gibt die Laenge des letzten Bezeichners,
- k die Laenge des neuen Bezeichners an *)
- if k>=kk then kk:=k
- else
- repeat
- (* Fuelle die unbenutzten Stellen des id-arrays mit
- Blanks auf *)
- id[kk]:=' ';
- kk:=kk-1
- until kk=k;
- (* Der Bezeichner ist nun in id eingelesen. *)
- for i:=frw[k] to frw[k+1]-1 do
- if rw[i]=id then (* ist id ein reserviertes Wort??*)
- begin
- (* id ist reserviertes Wort *)
- sy:=rsy[i];
- op:=rop[i];
- goto 2
- end;
- (* Nee, id ist kein reserviertes Wort !. id ist ein
- neudefinierter Bezeichner *)
- sy:=ident;op:=noop;
- 2:end;
-
- number: (* Zahl einlesen *)
- begin
- op:=noop;
- i:=0;
- repeat (* Einlesen der Ziffern in den Ziffernpuffer *)
- i:=i+1;
- if i<=digmax then digit[i]:=ch;
- nextch
- until chartp[ch]<>number;
- if (ch='.') or (ch='e') then
- begin
- (* Realzahl einlesen *)
- k:=i;
- if ch='.' then
- begin
- k:=k+1;
- if k<=digmax then digit[k]:=ch;
- nextch;
- if ch='.' then
- begin
- ch:=':';
- goto 3
- end;
- if chartp[ch]<>number then error(201)
- else
- repeat
- k:=k+1;
- if k<=digmax then digit[k]:=ch;
- nextch
- until chartp[ch]<>number
- end;
- if ch='e' then
- begin
- (* Exponent einlesen *)
- k:=k+1;
- if k<=digmax then digit[k]:=ch;
- nextch;
- if(ch='+') or(ch='-') then
- begin
- k:=k+1;
- if k<=digmax then digit[k]:=ch;
- nextch
- end;
- if chartp[ch]<>number then error(201)
- else
- repeat
- k:=k+1;
- if k<=digmax then digit[k]:=ch;
- nextch
- until chartp[ch]<>number
- end;
- (* Zahl ist in Ziffernpuffer eingelesen *)
- new(lvp);
- sy:=realconst;
- lvp^.cclass:=reel;
- with lvp^ do
- begin
- for i:=1 to strglgth do rval[i]:=' ';
- if k<=digmax then
- for i:=2 to k+1 do rval[i]:=digit[i-1]
- else
- begin
- error(203);
- rval[2]:='0';
- rval[3]:='.';
- rval[4]:='0'
- end
- end;
- val.valp:=lvp
- end
- else
- 3:begin (* Konstante ist vom Typ (long)integer *)
- if i>digmax then
- begin
- error(203);
- val.ival:=0
- end
- else
- with val do
- begin
- wert:=0;
- for k:=1 to i do
- wert:=wert*10+ordint[digit[k]];
- if wert<=maxint then
- begin
- (* integer-Konstante *)
- sy:=intconst;
- ival:=int(wert);
- end
- else
- begin
- (* longinteger-Konstante *)
- sy:=longconst;
- new(lvp);
- lvp^.cclass:=long;
- lvp^.lval:=wert;
- val.valp:=lvp;
- end;
- end
- end
- end;
- chstrquo: (* Stringkonstante einlesen *)
- begin
- lgth:=0;
- sy:=stringconst;
- op:=noop;
- repeat
- repeat
- nextch;
- lgth:=lgth+1;
- if lgth<=strglgth then string[lgth]:=ch
- (* ^^ String in Stringpuffer zwischenspeichern *)
- until (eol) or (ch='''');
- if eol then error(202) else nextch
- until ch<>'''';
- lgth:=lgth-1;
- if lgth=0 then error(205) else
- if lgth=1 then val.ival:=ord(string[1]) (* string ist ein-
- zelner Character*)
- else
- begin
- new(lvp);
- lvp^.cclass:=strg;
- if lgth>strglgth then
- begin
- (* String zu lang *)
- error(399);
- lgth:=strglgth;
- (* Rest der Stringskonstanten wird einfach abge-
- schnitten *)
- end;
- with lvp^ do
- begin
- slgth:=lgth;
- for i:=1 to lgth do sval[i]:=string[i]
- end;
- val.valp:=lvp;
- end
- end;
- (* SONDERZEICHEN *)
- chcolon :
- begin
- op:=noop;
- nextch;
- if ch='=' then
- begin
- sy:=becomes;
- nextch
- end
- else
- sy:=colon
- end;
- chperiod:
- begin
- op:=noop;
- nextch;
- if ch='.' then
- begin
- sy:=colon;
- nextch
- end
- else
- sy:=period
- end;
- chlt:
- begin
- nextch;
- sy:=relop;
- if ch='=' then
- begin
- op:=leop;
- nextch
- end
- else
- if ch='>' then
- begin
- op:=neop;
- nextch
- end
- else
- op:=ltop
- end;
- chgt:
- begin
- nextch;
- sy:=relop;
- if ch='=' then
- begin
- op:=geop;
- nextch
- end
- else
- op:=gtop
- end;
- chlparen:
- (* Kommentare *)
- begin
- nextch;
- if ch='*' then
- begin
- nextch;
- if ch='$' then options;
- repeat
- while (ch<>'*') and not eof(input) do nextch;
- nextch
- until (ch=')') or eof(input);
- nextch;
- goto 1
- end;
- sy:=lparent;
- op:=noop
- end;
- special :
- begin
- sy:=ssy[ch];
- op:=sop[ch];
- nextch
- end;
- chspace:
- sy:=othersy;
- (* Kommentare mit { *)
- lbrace:
- begin
- nextch;
- if ch='$' then options;
- while (ch<>'}') and not eof(input) do nextch;
- nextch;
- goto 1
- end;
- end;
- end;
-
- So, das war's für's Erste! In der nächsten Folge beschäftigen wir
- uns mit dem Parsing und der semantischen Analyse.