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
- elmprogramming.com, ein ausführliches und gut lesbares Online-Buch.
- elm-lang.org, die offizielle Webseite;
- Kapitel 6 und 7 in meinen Vorlesungsskript zum Kurs Programmierparadigmen
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
.
elm/src/
-Ordner,
gehen in den elm
-Ordner und probieren das Programm aus:
Sie müssen für diese Aufgabe keine Lösung abgeben.elm repl
import Noxy exposing (..)
parse "xzyxzyx"
True : Bool
parse "xzyxyxx"
False : Bool
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"
'this is a string, too'
"This is Lucy's car"
'content: counter(listing) ". ";'
"
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.
-
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.
Abgabeformat: Eine reguläre Grammatik, als Textdatei, PDF, Word, handschriftlich, wie auch immer.
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
import Quotes.elm as Machine
Abgabeformat: Eine Elm-Datei names Quotes.elm
\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.
MealyMachine.elm
, die dieser Erweiterung Rechnung trägt. Ihr
Datentyp
MealyMachine
hat nun zwei Typenvariablen:
<type alias MealyMachine state outputAlphabet = ...
parse
ist nun auch anders, beipsielsweise:
{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
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
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
"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.