4.7 Programmierprojekt: Reguläre Sprachen anwenden

Abgabetermin: Montag, 8. Mai 2023

Aufgaben: Die in diesem Teilkapitel (4.7) als Testataufgabe gekennzeichneten Aufgaben.

Format: Je nach Aufgabe als Text/PDF/Word oder als kompilierfähige Elm-Quelldatei. Ich werde bei jeder Testataufgabe darauf extra eingehen.

Material: Eine Auflistung aller Elm-Quelltextdateien (und kompilierter Beispielanwendungen), die ich Ihnen für dieses Projekt zur Verfügung stelle, finden Sie ganz am Ende dieses Teilkapitels.

Fragen: Es ist mir klar, dass die für dieses Projekt Dinge tun müssen, die Sie wahrscheinlich noch nicht können, z.B. mit Elm eine Tabelle für die Webseite erstellen. Aber um solche Fragen zu beantworten bin ja ich da.

Um zur mündlichen Prüfung zugelassen zu werden, müssen Sie als Vorleistung mehrere kleinere Programmierprojekte erfolgreich abschließen. Dieses ist das erste davon. Ziel ist es, mittels regulärer Grammatiken einen Eingabe-String in bestimmte, sinnvolle Bestandteile zu zerlegen und diese dann farbig unterlegt darzustellen. Notwendig zum Erlangen des Testats ist, dass Sie die Testataufgaben in diesem Teilkapitel zufriedenstellend lösen. Hierfür brauchen Sie eine lauffähige Elm-Umgebung. In Rechnerraum GII/303 ist diese bereits installiert. Bei Elm handelt es sich um eine funktionale Programmiersprache (also ähnlich wie Racket, aber einerseits strenger und andererseits mit freundlicherer Syntax), die speziell für die Entwicklung client-seitiger Web-Anwendungen geschaffen wurde. Als "Endprodukt" können Sie also Ihr Elm-Programm in eine Webseite kompilieren, die Sie dann einfach mit einem Browser öffnen können.

Elm

Elm ist im Raum GII/303 bereits installiert. Ich empfehle Ihnen natürlich, es auch bei sich auf Ihrem Laptop (sofern vorhanden) zu installieren. Zur Einführung in Elm empfehle ich Ihnen

Falls Sie Schwierigkeiten haben, Elm-Programme zum Laufen zu bringen, setzen Sie sich bitte frühzeitig mit mir in Verbindung.

Endliche Automaten in Elm

Nehmen wir als Beispiel den endlichen Automaten für die Noxy-Sprache ("no \(xy\)"):

Ein (deterministischer) Automat beschreibt ja immer eine Prozedur, die wir auch implementieren können. In diesem Fall ist dies recht einfach: überprüfe, ob ein Wort mit \(x\) endet und nicht das Teilwort \(xy\) enthält. Ich möchte aber ein allgemeines Framework, mit dem ich in Elm mit regulären Sprachen arbeiten kann - schlussendlich mit deutlich komplexeren als der von dem obigen Automaten akzeptierten.

Ich will nun ein Elm-Programm für die Noxy-Sprache schreiben. Große Teile meines Codes werden aber für jeden Automaten, den ich implementieren will, identisch sein. Diesen lagere ich aus in eine Datei FiniteStateMachine.elm. Als erstes definieren wir einen Datenytp Fsm (als Abkürzung für finite state machine:

module FiniteStateMachine exposing (Fsm, parse)


type alias Fsm state =
    { startState : state
    , transition : state -> Char -> state
    , acceptingStates : List state
    }

Der Datentyp Fsm hat eine Typenvariable state; dies rührt daher, dass wir im Allgemeinen ja nicht wissen, was die Zustandsmenge \(Q\) ist; diese ist von Automat zu Automat verschieden. Eigentlich bräuchte unser Automat noch ein Eingabealphabet \(\Sigma\), ich könnte also zwei Typenvariablen verwenden

type alias Fms inputAlphabet state = ...
.

Allerdings verzichte ich der Einfachheit halber darauf und nehme immer den Datentyp Char; langfristig mag es aber vorteilhaft sein, alternative Alphabete zuzulassen. Hier ist meine Implementierung von \(\hat{\delta}\), der erweiterten Zustandsübergangsfunktion:

deltaHat : Fsm state -> state -> List Char -> state
deltaHat machine state list =
    case list of
        [] ->
            state

        c :: rest ->
            deltaHat machine (machine.transition state c) rest

und schließlich die Funktion parse, die einen String entgegennimmt und dann ausgibt, ob der zur Sprache gehört:

parse : Fsm state -> String -> Bool
parse machine inputString =
    let
        charList =
            String.toList inputString

        finalState =
            deltaHat machine machine.startState charList
    in
    List.member finalState machine.acceptingStates

Soweit der Code, der wohl für alle endlichen Automaten, ob einfach oder kompliziert, identisch ist. Um den endlichen Automaten für die Noxy-Sprache zur vervollständigen, müssen wir noch \(\qstart\), \(F\) und \(\delta\) spezifizieren, also startState, delta, acceptingStates. Dies habe ich in der Datei FsmNoxy.elm getan:

module Noxy exposing (..)

import FiniteStateMachine as Fsm exposing (Fsm)


type State
    = S
    | X
    | Y


noxyFsm : Fsm State
noxyFsm =
    { startState = S
    , acceptingStates = [ X ]
    , transition = transition
    }


parse : String -> Bool
parse inputString =
    Fsm.parse noxyFsm inputString


transition : State -> Char -> State
transition q c =
    case ( q, c ) of
        ( S, 'y' ) ->
            S

        ( S, 'z' ) ->
            S

        ( S, 'x' ) ->
            X

        ( X, 'x' ) ->
            X

        ( X, 'y' ) ->
            Y

        ( X, 'z' ) ->
            S

        ( Y, _ ) ->
            Y

        _ ->
            Y

                        

Der problemspezifische Code besteht also fast ausschließlich aus dem Code für transition.

Übungsaufgabe Speichern Sie FiniteStateMachine.elm und FsmNoxy.elm in Ihrem elm/src/-Ordner, gehen in den elm-Ordner und probieren das Programm aus:
elm repl
import Noxy exposing (..)
parse "xzyxzyx"
True : Bool
parse "xzyxyxx"
False : Bool
Sie müssen für diese Aufgabe keine Lösung abgeben.

Als zweites Beispiel implementiere ich einen Parser für die aa:aaa.aa-a-aa-Sprache, die ich von nun an die ColonDashPeriod-Sprache nenne, nach den englischen Namen der Zeichen :-. Hier ist der endliche Automat aus Kapitel 4.4, allerdings habe ich die Zustände umbenannt, damit sie mehr Sinn ergeben:

Sie finden den Quelltext in der Datei FsmColonDashPeriod.elm. Der Code von transition : State -> Char -> State unterscheidet sich fast nur quantitativ (es handelt sich um einen komplexeren Automaten); allerdings lasse ich hier neben \(a\) alle alphanumerischen Zeichen zu; hierfür dient mir die Funktion
Char.isAlphaNum : Char -> Bool

Eine Grammatik für Single Quotes und Double Quotes

Jetzt sind Sie an der Reihe. In Programmiersprachen wie zum Beispiel Javascript dürfen Sie Strings mit einem Double Quote umranden, also

"this is a string"
so wie in Java oder Elm auch, allerdings können Sie einen String auch mit Single Quotes einschließen:
'this is a string, too'
Dies ist nicht unbedingt gutes Design, hat aber den Vorteil, dass Sie bequem innerhalb eines Strings wiederum Anführungszeichen setzen können:
"This is Lucy's car"
aber eben auch
'content: counter(listing) ". ";'
Die beiden " in 'content: counter(listing) ". ";' sind also selber String-Inhalt, keine Begrenzung. All dies ist auch in csv-Dateiene (comma separated values) erlaubt. Allerdings dürfen Strings keine Zeilenumbrüche enthalten. Enthält eine Zeile also ein öffnendes ", so muss es ein entsprechendes schließendes " geben.

Testataufgabe Erstellen Sie eine reguläre Grammatik für die Sprache Quotes aller Zeilen mit korrekter "Quotierung". Das heißt, alle öffnenden Anführungszeichen müssen wieder geschlossen werden. Zum Beispiel
  • Adresse: 'Brueckenstrasse', 'Goerlitz', 'Germany' \(\in L\);
  • Adresse: "Dongchuan Road", "Shanghai", "China' \(\not \in L\), da drittes öffnendes " nicht geschlossen
  • Die Hauptstadt in "Game of Thrones" heisst "King's Landing" \(\in L\), da das ' nicht als Anführungszeichen gilt,
  • style='content:"hello' \(\in L\), weil das " sich innerhalb eines '...'-Quotes und nicht als Anführungszeichen gilt.
Wenn Sie noch Zweifel haben, gehen Sie auf quotes.html und probieren verschiedene Strings aus.

Abgabeformat: Eine reguläre Grammatik, als Textdatei, PDF, Word, handschriftlich, wie auch immer.

Testataufgabe Schreiben Sie nun eine Datei Quotes.elm, die, analog zu FsmColonDashPeriod.elm einen endlichen Automaten für diese Grammatik implementiert. Insbesondere muss Ihre Datei eine Funktion parse : String -> Bool und einen String exampleString : String definieren.

Unter colondashperiod.html finden Sie einen entsprechenden "Checker" für die ColonDashPeriod-Sprache. Den Quellcode für die graphische Oberfläche finden Sie in WebsiteForFiniteStateMachines.elm. Wenn Sie dort also die Zeile

import ColonDashPeriod as Machine
ersetzen durch
import Quotes.elm as Machine
dann soll die Anwendung genau so laufen wie zuvor, nur mit der Quotes-Grammatik.

Abgabeformat: Eine Elm-Datei names Quotes.elm

Testataufgabe Wir modifizieren die Sprache Quotes zu einer Sprache QuotesAndLines. Der Input besteht aus mehreren, durch ein Newline \n getrennten Zeilen, und jede Zeile an sich muss ein korrektes Quotes-Wort sein. Also
                            "I'm Dominik", 
                            he said. "How are you doing?"
                        
ist ein Wort in der Sprache QuotesAndLines, aber
                            String x = "hello
                            my friend"; 
                        
ist kein Wort, weil in der ersten Zeile das öffnende " kein schließendes hat.

Entwerfen Sie eine reguläre Grammatik für diese Sprache und einen endlichen Automaten und implementieren Sie diesen. Als "Endprodukt" sollen Sie Ihre Elm-Anwendung zu einer Webseite wie colondashperiod.html kompilieren. wie

Abgabeformat: Wie in der Aufgabe zuvor, also eine Datei QuotesAndLines.elm.

Automaten mit Ausgabe

Endlicher Automaten, so wie wir sie bis jetzt betrachtet haben, geben einfach nur ja oder nein aus, d.h., Sie überprüfen einfach nur, ob das Eingabewort in der Sprache ist oder nicht. In der Praxis ist das uns nicht genug: die reguläre Grammatik dient auch dazu, das Eingabewort in sinnvolle logische Bestandteile zu zerlegen. In der Quotes-Grammatik könnte das zum Beispiel bedeuten, das Eingabewort anhand des Anführungszeichen zu zerlegen; in der Web-Anwendung könnet das bedeuten, diese Teile farbig zu markieren, zum Beispiel

{myString = "This is Dominik's string"; singleQuoteString = 'this " is not a quote'; counter++}

Definieren wir erst einmal das zugrundeliegende theoretische Konzept.

Definition (Mealy-Automat). Ein Mealy-Automat ist ein endlicher Automat, der zusätzlich zu \(\Sigma, Q, \qstart, F, \delta\) noch ein endliches Ausgabealphabet \(\Lambda\) und eine Ausgabefunktion \begin{align*} \lambda : Q \times \Sigma \rightarrow \Lambda \end{align*} hat. Jeder Übergang erzeugt also ein Ausgabesymbol.
Beispiel. Betrachten wir die Sprache \(L\) aller Wörter \(\alpha \in \{a,b,c\}\), in denen gerade viele \(a\)'s und beliebig viele \(b\)'s vorkommen. Hier ist ein endlicher Automat:
Wir wollen nun zusätzlich bei jedem Zeichen wissen, ob wir bis jetzt gerade viele oder ungerade viele \(a\)'s gelesen haben (ob wir also "innerhalb" oder "außerhalb" eines \(a\)-Blocks sind); eine farbliche Markierung könnte so aussehen:
bcbabcbbbcabbcbababbcb
Eine solche farbliche Markierung können wir bequem mit einem Mealy-Automaten erzeugen:
Testataufgabe Ändern Sie die Datei FiniteStateMachine.elm ab zu einer Datei MealyMachine.elm, die dieser Erweiterung Rechnung trägt. Ihr Datentyp MealyMachine hat nun zwei Typenvariablen: <
type alias MealyMachine state outputAlphabet = ...
Ihre Signatur von parse ist nun auch anders, beipsielsweise:
parse: MealyMachine state outputAlphabet -> String -> List (Char, outputAlphabet)
Im obigen Beispiel ist das Ausgabealphabet {black,red,green} und der Aufruf von parse sollte es also so etwas ergeben:
parse evenAmachine "bcaccac"                            
[('b',"black"), ('c',"black"), ('a',"red"), ('c',"green"), ('c',"green"),  ('a',"red"), ('c',"black")] : List (Char, String)

Implementieren Sie also die Funktion parse entsprechend um, so dass eine Liste von Input-Output-Symbolpaaren zurückgegeben wird.

Implementieren Sie eine Funktion parseAndMerge, die benachbarte Characters zu einem String zusammenfügt, wenn Ihr Ausgabesymbol gleich ist, also

parseAndMerge evenAmachine "bcaccac"                            
[("bc","black"), ("a","red"), ("cc", "green"),  ("a","red"), ("c","black")] : List (Char, String)

Abgabeformat: eine Datei MealyMachine.elm

Testataufgabe Ändern Sie nun WebsiteForFiniteStateMachines.elm in eine Datei WebsiteForMealyMachines um, die die Ausgabe der MealyMachine als farbig markierten Output darstellt. Sie enthält also oben eine Zeile
import YourSpecificMachine exposing (..)
wobei YourSpecificMachine.elm einen bestimmten Mealy-Automaten mit Output-Alphabet String implementiert und einen String exampleString und eine Funktion parse : String -> List (String, String) definiert.

Schreiben Sie eine Datei QuotesAndLinesMealy.elm, die wie QuotesAndLines.elm funktioniert, wo aber parse statt nur Bool eine List (String, String) zurückliefert.

In Elm können Sie ja Html-Textelemente erschaffen per

Html.text "dies ist ein Text
Font-Farbe und Hintergrundfarbe können Sie ändern, indem Sie das zum Beispiel in ein span-Objekt einpacke:
Html.span [ Html.Attributes.style "color" "red"
          , Html.Attributes.style "backgroundColor" "pink" ] 
          [Html.text "dies ist ein Text"]

Abgabeformat: die Dateien WebsiteForMealyMachines.elm und QuotesAndLinesMealy.elm

Übungsaufgabe Implementieren Sie jetzt etwas wie CSVVisualizer.html. Schreiben Sie eine reguläre Grammatik für das CSV-Format. Das ist ein Format für Tabellen, wo jede Textzeile für eine Tabellenzeile steht und innerhalb einer Zeile die Inhalte der Tabellenzellen durch ein Komma separiert sind. Achten Sie darauf, dass ein Komma nicht als Separator zählt, wenn es innerhalb von Anführungzeichen steht, also wie zum Beispiel in "Washington, D.C.".

Schreiben Sie eine Mealy-Automaten dafür. Als Output-Alphabet würden eventuell vier Symbole ausreichen: Content, Comma, Newline, Quote, wobei eben dann ein in Anführungszeichen stehendes , nicht als Comma, sondenr als Content gewertet würde. Anhand der Beschriftung mit den Output-Symbolen können Sie dann eine List (List String) erzeugen, die Ihre Tabelle als Liste von Zeilen (wobei eine Zeile wiederum eine Liste von Strings ist) darstellt.

Abgabeformat: eine Datei CSVMachine, welche zusammen mit WebsiteForMealyMachines.elm eine lauffähige Web-Anwendung zur Visualisierung von Daten im CSV-Format ergibt.

Material