Zur Themenübersicht    

3. Scanner und ScannerAutomat



1. UML-Daigramm



2. TScanner

Zum Scannen des Programmtextes wird dieser stückchenweise vom Scanner an den ScannerAutomaten übergeben.
Dabei werden alle lexikalisch korrekten Ausdrücke in einen Token (Kombination aus Ausdruckstyp und konkreten Ausdruck) umgewandelt. Die Token werden dann zu einer Tokenliste zusammengefügt.

2.1. Typendefinitionen

Zum Verwalten der gesammelten Informationen werden folgende Typen benötigt.
type
  TToken = array [1..2] of String;

type
  TTokenListe = array of TToken;
Ein Token besteht zunächst aus dem Ausdruckstyp und dann aus dem konkreten Ausdruck.

2.2. Die wichtigsten Attribute und Methoden der Klasse TScanner

Mit dem Konstruktor "init" und dem Desktruktors "gibFrei" kann wie übliche eine Instanz der Klasse erzeugt bzw. vernichtet werden.
Das Attribut "hatScannerAutomat" dient als Speicherplatz für die "Hat-Beziehung" zum ScannerAutomaten.

function TScanner.ScanneProgrammText (pProgrammtext: String) : TTokenListe;
Diese Anfrage wird vom Formular aus aufgerufen. Als Parameter wird der eingegebene Programmtext übergeben.
Damit einzelne Befehle im Programmtext auch durch Zeilenumbrüche voneinander getrennt werden können, der Automat jedoch möglichst simpel bleibt, werden zunächst alle Zeilenumbrüche durch Leerzeichen ersetzt. Desweiteren wird der Programmtext komplett in Kleinbuchstaben umgewandelt, damit die Groß- und Kleinschreibung der Befehle keine weitere Rolle spielt.
Normalerweise würde der Programmtext zeichenweise an den ScannerAutomaten übergeben werden. Dies würde bei unseren langen Befehlsnamen jedoch zu einem extrem unübersichtlichen und chaotischen ScannerAutomaten führen. Um ihn möglichst simpel und vor allem übersichtlich zu gestalten, ist es daher von großem Vorteil, den Programmtext abschnittsweise an den ScannerAutomaten zu übergeben. Da wir Leerzeichen als Trennzeichen definiert haben, wird der Programmtext naheliegender weise in Abschnitte von einem Leerzeichen zum nächsten zerlegt. Diese Abschnitte werden an den ScannerAutomaten übergeben. Die von ihm zurückgegebenen Token werden dann zu der Tokenliste verknüpft und von der Anfrage zurückgegeben.

function TScanner.ersetzeZeilenumbruchDurchLeerzeichen (pEingabe: String) : String;
In dieser Methode werden alle Zeilenumbrüche in "pEingabe" durch Leerzeichen ersetzt und der umgewandelte String zurückgegeben.

2.3. Entscheidende und interessante Codestellen






Zeilenumbrüche entfernen
In Kleinbuchstaben umwandeln




Programmtext bis zum ersten Leerzeichen
kopieren und an den ScannerAutomaten
weitergeben


Wenn die Eingabe erkannt wurde dann wird der Token zur Tokenliste hinzugefügt.


Den bereits abgearbeiteten Teil vom Programmtext abschneiden
function TScanner.ScanneProgrammText (pProgrammtext: String) : TTokenListe;
var
  tmpAusgabe : TToken;
  aktuelleEingabe : String;
begin
  pProgrammtext := pProgrammtext + #13#10;
  pProgrammtext := ersetzeZeilenumbruchDurchLeerzeichen(pProgrammtext);
  pProgrammtext := LowerCase(pProgrammtext);

  hatScannerAutomat.setzeZurueck;

  SetLength(result,0);
  while length(pProgrammtext) > 0 do
  begin
    aktuelleEingabe := Copy(pProgrammtext,1,pos(' ', pProgrammtext) - 1);

    tmpAusgabe := hatScannerAutomat.verarbeiteEingabe(aktuelleEingabe);

    if (tmpAusgabe[1] <> '') or (tmpAusgabe[2] <> '') then
    begin
      SetLength(result, length(result) + 1);
      result[length(result) - 1][1] := tmpAusgabe[1];
      result[length(result) - 1][2] := tmpAusgabe[2];
    end;

    pProgrammtext := Copy(pProgrammtext,pos(' ', pProgrammtext) + 1,length(pProgrammtext) - pos(' ', pProgrammtext));
  end;
end;


3. TScannerAutomat

Beim Scanner Automaten handelt es sich um einen endlichen deterministschen Automaten. Wenn die Eingabe bekannt ist wird der entsprechende Token zurückgegeben.

3.1. G-Flep Zustandsgraph

So würde ein Teil eines Zustandsgraphen eines Automaten aussehen der die Eingabe zeichenweise verarbeitet.



Da unser Automat ja nun alles in Abschnitten bekommt vereinfacht sich der Zustandsgraph erheblich.

Es wird nur noch ein Zustand benötigt, in dem der Automat immer bleibt. Nachdem etwas eingegeben wurde, das erkannt wurde, ändert sich nichts, es wird nur der entsprechende Token zurückgegeben. Wenn das Eingegebene nicht erkannt wurde wird nichtmals ein Token zurückgegeben.

3.2. Die wichtigsten Attribute und Methoden der Klasse TScannerAutomat

Mit dem Konstruktor "init" und dem Desktruktors "gibFrei" kann wie üblich eine Instanz der Klasse erzeugt bzw. vernichtet werden.
Die Attribute "aktuellerZustand : TZustand" und "startZustand : TZustand" speichern den aktuellen Zustand und den Startzustand. Der aktuelle Zustand ändert sich jedoch nie, da sich der Automat nach jeder Eingabe wieder im Ausgangszustand befindet.

function TScannerAutomat.verarbeiteEingabe (pEingabe: String) : TToken;
Diese Methode wird vom Scanner aufgerufen. Sie ermittelt mit Hilfe der Ausgabefunktion aus der Eingabe (und dem aktuellen Zustand) den entsprechenden Token.
Falls die Eingabe nicht bekannt ist, wird ein leerer Token zurückgegeben.

function TScannerAutomat.UebergangsFunktionF (pAktuellerZustand: TZustand; pEingabe: String) : TZustand;
Normalerweise wird mit dieser Methode aus aktuellem Zustand und einer Eingabe der Folgezustand ermittelt.
Da unser Automat jedoch nur einen Zustand kennt, ist ihr Rückgabewert immer der Startzustand.

function TScannerAutomat.AusgabeFunktionG (pAktuellerZustand: TZustand; pEingabe: String) : TToken;
Hier wird jeder Eingabe, die zu Benders Sprache gehört, ein Token, bestehend aus Typ und Ausdruck, zugeordnet. Gehört der Ausdruck lexikalisch nicht zur Sprache, wird ein leerer Token zurückgegeben.

procedure TScannerAutomat.setzeZurueck;
Mit dieser Methode wird der aktuelle Zustand auf den Startzustand gesetzt.

3.3. Entscheidende und interessante Codestellen


Wenn keine der folgenden Eingaben vorliegt
dann wird ein leerer Token zurückgegeben.




Wenn die Eingabe bekannt ist, wird sie in
den zweiten Teil des Tokens geschrieben.
Analog zu der in der Sprachdefinition gemachten
Einteilung in verschiedene Ausdruckstypen wird
der Typ in den ersten Teil des Tokens geschrieben.
function TScannerAutomat.AusgabeFunktionG (pAktuellerZustand: TZustand; pEingabe: String) : TToken;
begin
  result[1] := '';
  result[2] := '';  

  case pAktuellerZustand of
    zStart : begin
      if pEingabe = 'vor' then
      begin
        result[1] := 'befehl';
        result[2] := 'vor';
      end
      else if pEingabe = 'drehe_links' then
      begin
        result[1] := 'befehl';
        result[2] := 'drehe_links';
      end
      else if pEingabe = 'drehe_rechts' then
      begin
        result[1] := 'befehl';
        result[2] := 'drehe_rechts';
      end
      else if pEingabe = 'nimm' then
      begin
        result[1] := 'befehl';
        result[2] := 'nimm';
      end
      else if pEingabe = 'gib' then
      begin
        result[1] := 'befehl';
        result[2] := 'gib';
      end
      else if pEingabe = 'vornefrei' then
      begin
        result[1] := 'anfrage';
        result[2] := 'vornefrei';
      end
      else if pEingabe = 'itemda' then
      begin
        result[1] := 'anfrage';
        result[2] := 'itemda';
      end
      else if pEingabe = 'blicktnach_unten' then
      begin
        result[1] := 'anfrage';
        result[2] := 'blicktnach_unten';
      end
      else if pEingabe = 'blicktnach_oben' then
      begin
        result[1] := 'anfrage';
        result[2] := 'blicktnach_oben';
      end
      else if pEingabe = 'blicktnach_rechts' then
      begin
        result[1] := 'anfrage';
        result[2] := 'blicktnach_rechts';
      end
      else if pEingabe = 'blicktnach_links' then
      begin
        result[1] := 'anfrage';
        result[2] := 'blicktnach_links';
      end
      else if pEingabe = 'itemsimrucksack' then
      begin
        result[1] := 'anfrage';
        result[2] := 'itemsimrucksack';
      end                            
      else if pEingabe = 'wenn' then
      begin
        result[1] := 'kontroll';
        result[2] := 'wenn';
      end
      else if pEingabe = 'dann' then
      begin
        result[1] := 'kontroll';
        result[2] := 'dann';
      end
      else if pEingabe = 'sonst' then
      begin
        result[1] := 'kontroll';
        result[2] := 'sonst';
      end
      else if pEingabe = 'solangewie' then
      begin
        result[1] := 'kontroll';
        result[2] := 'solangewie';
      end
      else if pEingabe = 'tue' then
      begin
        result[1] := 'kontroll';
        result[2] := 'tue';
      end
      else if pEingabe = #123 then
      begin
        result[1] := 'kontroll';
        result[2] := #123;
      end
      else if pEingabe = #125 then
      begin
        result[1] := 'kontroll';
        result[2] := #125;
      end
      else if pEingabe = 'not' then
      begin
        result[1] := 'kontroll';
        result[2] := 'not';
      end

    end; //of pAktuellerZustand : zStart


  end;
end;