Grundlagen der Programmierung 3 A - Compiler A: Phasen Lexikalische Analyse; Scanner

Die Seite wird erstellt Merlin Geißler
 
WEITER LESEN
Grundlagen der Programmierung 3 A - Compiler A: Phasen Lexikalische Analyse; Scanner
Grundlagen der Programmierung 3 A
         Compiler A: Phasen

       Lexikalische Analyse; Scanner

    Prof. Dr. Manfred Schmidt-Schauß

          Sommersemester 2018
Compiler; Übersetzungsprogramme

        Ein Übersetzer (Compiler) ist ein Programm, das
        ein Wort einer formalen Sprache S1 (den Quelltext)
        in ein Wort einer anderen formalen Sprache S2 (den Zieltext)
        umwandelt,
        wobei die Semantik erhalten bleibt.
        (Semantik meist separat erklärt)

      Beispiele: Programme in Haskell, Java, Python, oder PASCAL,
      werden in Maschinenkode, C, Assembler, oder Bytecode übersetzt.
      LaTex-Eingaben werden in eine PDF-Datei (bzw. ps-Datei)
      übersetzt

Grundlagen der Programmierung 2 (Compiler-A)                           – 2/21 –
Compiler für Programmiersprachen

        Ein Compiler ist ein Programm, das
        ein S1 -Programm (das Quellprogramm)
        in ein S2 -Programm (das Zielprogramm) umwandelt,
        wobei die (operationale) Semantik erhalten bleibt

Grundlagen der Programmierung 2 (Compiler-A)                – 3/21 –
Phasen eines Compilers

      Lexikalische Analyse (Scanning): Text des Programms wird in
                    Tokenstrom umgewandelt
      Syntaxanalyse (Parsing): Tokenstrom wird entsprechend der
                   Grammatik in einen Syntaxbaum transformiert
      Semantische Analyse: Typcheck, Scope von Variablen, . . .
      Zwischencode-Erzeugung: Generierung von Code für eine
                  abstrakte Maschine
      Codeoptimierung: Verbesserung des Zwischencodes
      Code-Erzeugung: Erzeugung des Ausgabe-Programms (z.B. in C,
                  Assembler)

Grundlagen der Programmierung 2 (Compiler-A)                        – 4/21 –
Phasen eines Compilers: Diagramm

                                                   Lexikalische
             Quelltext (String)
                                                     Analyse
                                                                        Tokenstrom
                                                  Syntaktische
                                                    Analyse
                                    Syntaxbaum
                                                  Semantische
                                                    Analyse
                                                                  Syntaxbaum (annotiert)
                                                  Zwischencode-
                                                   erzeugung
                                   Zwischencode
                                                      Code-
                                                   optimierung
                                                                  Zwischencode (optimiert)
                                                     Code-
                                                   generierung

                                                                                  maschinennaher Code

Grundlagen der Programmierung 2 (Compiler-A)                                                      – 5/21 –
Beispiele zu den Phasen
                 Lexikalische Analyse
                 Z.B. IF 13 == X1 THEN A “ wird zu:
                       ”
                 ’IF’, 13, ==, ’X1’, ’THEN’,’A’

Grundlagen der Programmierung 2 (Compiler-A)          – 6/21 –
Beispiele zu den Phasen
                 Lexikalische Analyse
                 Z.B. IF 13 == X1 THEN A “ wird zu:
                       ”
                 ’IF’, 13, ==, ’X1’, ’THEN’,’A’
                 Syntaxanalyse (Parsing):
                 Programmtext anhand Grammatik strukturieren.
                 Erstellen Herleitungsbaum, dann Syntaxbaum:
                 Z.B. (’IF’, 13, ==, ’X1’, ’THEN’, ’A’) wird zu:
                                                   •                                         IFTHEN
                                                         %
                  0
                      IF0 u      •{                      0
                                                             THEN0   *0
                                                                          A0             x
                                                                                        ==
                                                                                                            &   0 0
                                                                                                                A
                           {               #
                                              0                                    }         &
                      13       ==                  X10                         13                 0
                                                                                                      X10

Grundlagen der Programmierung 2 (Compiler-A)                                                                          – 6/21 –
Beispiele zu den Phasen
                 Lexikalische Analyse
                 Z.B. IF 13 == X1 THEN A “ wird zu:
                       ”
                 ’IF’, 13, ==, ’X1’, ’THEN’,’A’
                 Syntaxanalyse (Parsing):
                 Programmtext anhand Grammatik strukturieren.
                 Erstellen Herleitungsbaum, dann Syntaxbaum:
                 Z.B. (’IF’, 13, ==, ’X1’, ’THEN’, ’A’) wird zu:
                                                   •                                         IFTHEN
                                                         %
                  0
                      IF0 u      •{                      0
                                                             THEN0   *0
                                                                          A0             x
                                                                                        ==
                                                                                                            &   0 0
                                                                                                                A
                           {               #
                                              0                                    }         &
                      13       ==                  X10                         13                 0
                                                                                                      X10

                 Semantische Analyse
                 Typüberprüfungen:    Z.B. 1 + ’a’ wird zurückgewiesen.
                 Scopeprüfungen: Sind Variablen/Funktionen/. . . definiert?
                           Sind sie an der richtigen Stelle deklariert?

Grundlagen der Programmierung 2 (Compiler-A)                                                                          – 6/21 –
Weitere Komponenten

                 Symboltabelle speichert wesentliche Attribute (Typ,
                 Gültigkeitsbereich, etc.) der Bezeichner des Quellprogramms.
                 Fehlerbehandlung Analyse der Fehler, Lokalisierung im
                 Quellprogramm,
                 Meldung an den Benutzer.
                 Evtl. automatische Behebung des Fehlers und Fortsetzung der
                 Übersetzung

Grundlagen der Programmierung 2 (Compiler-A)                                – 7/21 –
Weitere mögliche Phasen

                 Einleseroutine; Zeichenbehandlung abhängig vom
                 Prozessor bzw. Betriebssystem
                 I.a. Standard-Routinen (Einlesen, Zeichenkodierung)
                 Präprozessor Vorverarbeitung des Quellprogramms
                 (i.a. Makro-Expansion)
                 Assembler                     übersetzt das Assemblerprogramm in
                 Objektcode.
                 Binder/Lader Umsetzung in ausführbaren Maschinenkode
                 plus Hinzunahme von anderem Objektcode z.B. aus Bibliothek

Grundlagen der Programmierung 2 (Compiler-A)                                         – 8/21 –
Front-End und Back-End

      Aufteilung des Compilers:
          Front-End: maschinenunabhängiger Teil
                     (z.B. Syntaxanalyse, lexikalische Analyse,
                     Typüberprüfung, Zwischencode-Erzeugung,
                     Zwischencode-Optimierung).
           Back-End: maschinenspezifischer Teil
                     (z. B. I/O, Codegenerierung)

Grundlagen der Programmierung 2 (Compiler-A)                      – 9/21 –
Worte, Zeichenketten, formale Sprachen

      Gegeben: ein Alphabet Σ
      Definitionen:
       • Eine Zeichenkette (String) ist endliche Folge von Σ-Symbolen
       • ε      bezeichnet den leeren String
             ∗
       • Σ : die Menge aller Worte über Σ
       • formale Sprache über Σ: ist definiert als eine Teilmenge von Σ∗

      Beispiele
      Σ = {a, b, c},
      ac, cbaaaabcc sind Strings über Σ.

Grundlagen der Programmierung 2 (Compiler-A)                        – 10/21 –
Beispiele

                 Σ = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}:
                 Die Ziffernrepräsentationen aller natürlichen Zahlen
                 ist eine formale Sprache über Σ.
                 Z.B. 111, 7, 3210999, ...
                 Σ = {A, . . . , Z, a, . . . , z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ), (, . . .}.
                 Alle möglichen Haskell-Programme (als Strings) sind eine
                 formale Sprache über Σ.
                 Z.B. die Zeichenfolge:
                 main = map quadrat []

Grundlagen der Programmierung 2 (Compiler-A)                                                    – 11/21 –
Reguläre formale Sprachen: Formalismen

      Beschreibungsmöglichkeiten
      für reguläre formale Sprachen:
         •        reguläre Ausdrücke

         •        (rechts- bzw. links-)reguläre Grammatik
                  (nur ein Nichtterminal am rechten bzw.
                    linken Ende von rechten Seiten von Produktionen erlaubt)

         •        DEA (deterministischer endlicher Automat): akzeptierte Sprache

         •        NEA (nicht-deterministischer endlicher Automat): akzeptierte Sprache

      Diese sind alle äquivalent in folgendem Sinn:
          die gleichen formalen Sprachen sind damit definierbar

Grundlagen der Programmierung 2 (Compiler-A)                                    – 12/21 –
Reguläre Ausdrücke: Beispiele

      0+(1+2+3+4+5+6+7+8+9)(0+1+2+3+4+5+6+7+8+9)∗
           positive ganze Zahlen (als Text)

Grundlagen der Programmierung 2 (Compiler-A)   – 13/21 –
Reguläre Ausdrücke: Beispiele

      0+(1+2+3+4+5+6+7+8+9)(0+1+2+3+4+5+6+7+8+9)∗
           positive ganze Zahlen (als Text)

      (A + ... + Z)(A + ... + Z + a + ... + z)∗
             Worte, die mit Großbuchstaben beginnen

Grundlagen der Programmierung 2 (Compiler-A)          – 13/21 –
Reguläre Ausdrücke: Beispiele

      0+(1+2+3+4+5+6+7+8+9)(0+1+2+3+4+5+6+7+8+9)∗
           positive ganze Zahlen (als Text)

      (A + ... + Z)(A + ... + Z + a + ... + z)∗
             Worte, die mit Großbuchstaben beginnen

      (A + ... + Z)(A + ... + Z + a + ... + z)(A + ... + Z + a + ... + z)
                      Worte aus drei Buchstaben, die mit Großbuchstaben beginnen

      Nicht regulär:
      alle geklammerten Ausdrücke wie (1 + ((2 + 3) + 9)

Grundlagen der Programmierung 2 (Compiler-A)                                       – 13/21 –
Lexikalische Analyse (Tokenizer, Scanner)

      Aufgaben-Aufteilung des Compilers:
         Zuerst              Scanner           Zeichen und Symbol-Analyse
                                               für reguläre formale Sprache
         Dann                Parser            Syntax-Analyse
                                               für kontextfreie formale Sprache
      Aufgaben des Scanners:
         •        Erkennung von: Zahlen, Bezeichner, Namen, Schlüsselworte, Strings
         •        Aufbau der Symboltabelle
         •        Zeichenstrom → Tokenstrom und Weitergabe an den Parser
      Es gibt Scanner-Generatoren (Z.B. lex).
      Allerdings ist es oft einfacher, einen Scanner selbst zu schreiben.
      Z.B. unter Benutzung endlicher Automaten.

Grundlagen der Programmierung 2 (Compiler-A)                                       – 14/21 –
Scanner: Etwas genauer

         •        Einlesen des Programmtexts als lineare Zeichenkette
         •        Beseitigung von Kommentaren, Leer- und Tabulatorzeichen.
         •        Zusammenfassen von Teilstrings
                  (Schlüsselworte, Bezeichner, Zahlenwerte, u ä)
         •        Ergebnis: Strom von Token
      Ein Token kann bestehen aus:
         Markierung:                      Art des Tokens (Schlüsselwort, Name, Zahl,. . . )
         Attribute:                       wie String, Zahlwert, Position im Eingabefile.

         Üblich:              Eigenes Ende-Token für Eingabestrom.

Grundlagen der Programmierung 2 (Compiler-A)                                                   – 15/21 –
Beispiel für Tokens

         Eingabe                 Tokenmarkierung   Attribut   Position in der Eingabe
         if                      Keyword           if         113
         123                     Num               123        500
         x1                      Id                ’x1’       1001
         ∗                       Mult              Code(∗)    2000
Grammatik-Zerlegung:

                 
                 
                  Zahl                                  ::=   Ziffer | Ziffer Zahl
                  Ziffer                                ::=   0|1|2|3|4|5|6|7|8|9
                 
                 
         Scanner   Bezeichner                            ::=   Char | Char RestBez
                                                         ::=   Alphanum RestBez | Alphanum
                 
                 
                 
                  RestBez
                                                         ::=   Ziffer | Char
                 
                   Alphanum
                               
                                E             ::=   E+T | T
         Parser                  T             ::=   T∗F | F
                                               ::=   (E) | Zahl | Bezeichner
                               
                                 F

      Parseralphabet = {Tokenmarkierungen}
      Parseralphabet im Beispiel: {+,*,Zahl, Bezeichner,),( }
      Der Parser behandelt alle Bezeichner als ein einziges Terminal

Grundlagen der Programmierung 2 (Compiler-A)                                           – 17/21 –
Compiler-Phasen: Struktur und Typen

      Typen der Funktionen, in Haskell-Notation:
         Scanner:                              String -> [Token]

         deterministischer                     [Token] -> ([Token], Syntaxbaum)
         Teil-Parser:
                                               Tokenliste -> (Rest der Tokenliste,
                                                          Syntaxbaum der konsumierten Tokenliste)

         Teil-Parser                           [Token] -> [([Token], Syntaxbaum)]
         mit Backtracking                      Tokenliste -> Liste der Möglichkeiten

         Gesamt-Parser:                        [Token] -> Syntaxbaum

Grundlagen der Programmierung 2 (Compiler-A)                                             – 18/21 –
Fehlererkennung des Scanners

         •        falsche Zahlen Z.B. 123.a
         •        falsche Escape-Folgen in Strings,
                  ein fehlendes String-Endezeichen,
                  oder evtl. Längenüberschreitungen.
         •        Bezeichner, die nicht der Konvention entsprechen
         •        Ungültige Symbole in bestimmten Kontexten
      Nicht erkennbar für den Scanner:
         •        Klammerfehler,
         •        Klammerfehler bzgl. (tief) geschachtelter Kommentare
         •        falsche Schlüsselworte.

Grundlagen der Programmierung 2 (Compiler-A)                             – 19/21 –
Scannen von Kommentaren und Strings

      String im Eingabestrom:
            = Unterstring mit linkem und rechtem Begrenzungssymbol ”.
      Bekannte Problematik: String im String?
      Beachte: ” trennt, erzeugt aber keine Klammerstruktur
      sinnvolle Lösungsalternativen:
         • Escape-symbol \ als Kommando:
           \” im String → ”
           \\ im String → \
       • Verdopplung: ”” im String → ”
      Nachteil: Exponentiell große Strings könnten
         in der Eingabe erforderlich sein
         abhängig von der Tiefe bzw. der Anzahl Scanner-Durchgänge.

      Der Scanner darf keine Leerzeichen in Strings entfernen

Grundlagen der Programmierung 2 (Compiler-A)                       – 20/21 –
Scannen von Kommentaren:

         Zeilen-                               Anfangssymbol
         kommentare                            z.B. * oder ; oder % oder −− oder //.

         geklammerte                           Kommentar-Begrenzer:
         Kommentare                            Z.B. Anfang: /∗ , Ende: ∗/.
                                               Aktuelle Programmiersprachen:
                                                  nur eine Klammerebene;

                                                  Echte Kommentar-Schachtelung
                                                  muss vom Parser erkannt werden.
      Beachte Zeilenkommentar im String sind Stringzeichen.

Grundlagen der Programmierung 2 (Compiler-A)                                           – 21/21 –
Sie können auch lesen