Programmierpraktikum Funktionales Programmieren und Compilerbau

Werbung
Technische Universität München
Institut für Informatik
Prof. Tobias Nipkow, Ph.D.
N. Schirmer, M. Blume
WS 2002/2003
Teil 1
Übungsblatt 1
16.10.2002
Programmierpraktikum
Funktionales Programmieren und
Compilerbau
Warm-Up in Haskell
Abgabetermin: Mittwoch, 30. 10. 2002
Im ersten Semester wurde Gofer von Mark P. Jones als funktionale Programmiersprache eingeführt.
Für dieses Praktikum wird Haskell verwendet. Da beide Sprachen aus dem gleichen Prozess hervorgegangen sind, ähneln sie sich in weiten Teilen. In diesem Übungsblatt geht es darum, grundlegende
Sprachelemente von Haskell vorzustellen bzw. zu wiederholen.
Ziel ist, einen Interpreter und einen Compiler für arithmetische Ausdrücke zu programmieren. Zum
Schluss soll der entstandene Code auch noch decompiliert werden.
Aufgabe 1: Modellierung arithmetischer Ausdrücke/Interpreter
Wir definieren mit dem Schlüsselwort data einen eigenen Datentyp Expr, der einen arithmetischen
Ausdruck repräsentiert. Er besitzt zwei Konstruktoren, Const und BinOp. Konstruktoren konstruieren einen Wert eines Typs. So wie z. B. 2 ein Wert des Typs Integer ist, ist Add ein Wert vom
Typ Op oder sind Const 4.3 oder BinOp (Const 3) Sub (BinOp (Const 2) Add (Const 1))
Werte vom Typ Expr. testE enthält den Ausdruck (2 + 3) * ( 4 - 1).
Mit deriving Show wird Haskell mitgeteilt, dass der durch data UnserTyp definierte Typ eine
Instanz der Typklasse Show ist. So ist sichergestellt, dass z. B. bei der Eingabe von testE in Hugs
der Ausdruck ausgegeben werden kann.
module Arith where
type Value = Float
data Expr = Const Value | BinOp Expr Op Expr
deriving Show
data Op
= Add | Sub | Mul | Div
deriving Show
testE = BinOp (BinOp (Const 2) Add (Const 3)) Mul (BinOp (Const 4)
Sub (Const 1))
Es soll nun zunächst ein Interpreter zur Berechnung des arithmetischen Ausdrucks implementiert
werden. Im Gegensatz zu einem Compiler, der einen Ausdruck in eine Befehlsfolge für eine Maschine übersetzt, berechnet ein Interpreter den Wert eines Ausdrucks direkt auf seiner abstrakten
Darstellung (abstrakte Syntax).
1
Implementieren Sie die Funktion
interprete :: Expr -> Value
Als Hilfsfunktion dient
apply :: Op -> Value -> Value -> Value
Z. B. berechnet apply Add 3.2 0.8 den Wert 4.
Aufgabe 2: Eine einfache Stackmaschine
Nun soll die Funktion run entwickelt werden, die ein einfaches Programm abarbeitet und den Stack
nach Ausführung des Programms zurückgibt. run simuliert in unserem Beispiel also eine einfache
Stackmaschine, die nur einen Operanden-Stack hat. Als Instruktionen kommen daher nur Push
und arithmetische Operationen in Frage, wobei für die arithmetische Operation die obersten zwei
Stackelemente die Operanden sind.
data Instr = Push Value | Apply Op
deriving Show
type Stack = [Value]
Die Befehlsliste [Push 3, Push 1, Apply Sub] hätte auf den Stack folgende Auswirkung:
Befehl
Push 3
Push 1
Apply Sub
Stack
[3]
[1,3]
[2]
Implementieren Sie die Funktion execute, die auf dem Stack eine Instruktion abarbeitet! Verwenden Sie execute, um die Funktion run zu definieren!
execute :: Stack -> Instr -> Stack
type Prog = [Instr]
run :: Prog -> Stack
Hinweis: Schreiben Sie run zunächst ohne weitere Hilfsfunktionen außer execute. Anschließend
können Sie run auch sehr elegant mit der Higher Order Function foldl implementieren. Die
Funktion foldl hat die Funktionalität foldl :: (a -> b -> a) -> a -> [b] -> a.
Sie ruft die Funktion, die im ersten Parameter übergeben wird mit dem zweiten Parameter und
dem ersten Element der Liste auf. Mit dem Ergebnis und dem zweiten Element der Liste wendet
Sie die Funktion wieder an usw., bis das Ende der Liste erreicht ist.
Beispiel: foldl (/) 64 [4,2,4]
Das Ergebnis ist 2.0.
Aufgabe 3: Compiler
Jetzt ist endlich der Compiler dran!
Setzen Sie die Funktion compile :: Expr -> Prog um!
Anmerkung: Es gilt die folgende Gleichung:
interprete = head . run . compile
oder ausführlich: interprete expr = head (run (compile expr))
Der Operator . ist die Komposition von Funktionen, wobei zuerst die rechteste Funktion auf das
2
Argument angewendet wird. Es ist übrigens typisch für die funktionale Programmierung, die Argumente in der Funktionsdefinition wegzulassen.
Aufgabe 4: Disassembler (uncompile)
In dieser Aufgabe geht es darum, die Umkehrfunktion zu compile zu entwerfen. Sie soll aus einem
gegebenen Programm den arithmetischen Ausdruck konstruieren. Daher gilt: uncompile (compile
expr) = expr
Tipp: Es ist nützlich, die Hilfsfunktion uncompileInstr zu definieren, die eine einzelne Instruktion decompiliert. Sie könnte ähnlich wie die Funktion execute einen Stack verwenden, mit dem
Unterschied, dass diesmal Ausdrücke auf dem Stack liegen!
uncompile :: Prog -> Expr
type ExpStack = [Expr]
uncompileInstr :: ExpStack -> Instr -> ExpStack
Aufgabe 5: Expressions mit Variablen
In dieser letzten Aufgabe sollen nun in dem Typ Expr neben Konstanten auch Variablen zugelassen
werden:
type Variable = String
data Expr = Const Value | Var Variable | Exp Expr Op Expr
deriving Show
Den Speicher realisieren wir durch eine Funktion. Diese Funktion muss zuerst mittels buildStore
aufgebaut werden.
update nimmt eine Funktion, ein Tupel, und gibt eine neue Funktion zurück. buildStore wendet
update auf eine Liste von Tupeln an und erzeugt somit die Funktion, die den Speicher repräsentiert.
type Store = Variable -> Value
buildStore :: [(Variable,Value)] -> Store
buildStore = foldl update (\x -> error ((show x) ++ "not defined in store"))
update :: Store -> (Variable,Value) -> Store
update store (x,y) = \v -> if v==x then y else store v
Ist z. B. ein Wert speicher vom Typ Store gegeben, so kann mit speicher ’x’ auf den Wert
der Variable x zugegriffen werden.
Der Speicher kann z. B. so erzeugt werden:
store = buildStore [(’x’,20),(’y’,10)]
Passen Sie alle Funktionen an die Verwendung von Variablen an! Bei den Funktionen interprete,
execute und run soll der Speicher als Parameter übergeben werden. Der Typ Instr sieht bei der
Verwendung von Variablen folgendermaßen aus:
data Instr = Push Value | Load Variable | Apply Op
deriving Show
Geben Sie die Funktion buildStore ohne die Verwendung der Hilfsfunktion foldl an!
3
Herunterladen