Bei dieser Registermaschine handelt es sich um ein sehr einfaches Exemplar, das nur natürliche Zahlen und eine handvoll Befehle verarbeiten kann.
Die Befehle bi werden von 1 bis n durchnummeriert. Bei B handelt es sich um den Befehlszähler, der angibt, welcher Befehl bi als nächstes ausgeführt werden soll. Dieser Zähler – er gehört formal gesehen zur Recheneinheit – ist der erste Wert der Registermaschinenkonfiguration.
Deren zweiter Wert, formal gesehen auch zur Recheneinheit gehörend, ist der Akkumulator beziehungsweise das Arbeitsregister. Auf diesem werden alle Operationen ausgeführt. Das heißt, dass ein Befehl, wie „Addiere 1“ den Wert im Akkumulator C0 um eins erhöht.
Die weiteren Register C1 bis Cn bilden den „Arbeitsspeicher“.
-- Alle möglichen Operationen definieren
data Instr = Load Int | CLoad Int | Store Int | Add Int | CAdd Int
| Sub Int | CSub Int | Mult Int | CMult Int | Div Int | CDiv Int |
Goto Int | IfGoto Int | End deriving Eq
-- Ein Programm ist eine Liste von Operationen
type Program = [Instr]
-- Die Konfiguration einer Registermaschine besteht hier aus einer Liste
-- von Ints
type Config = [Int]
-- Das eigentliche „Ausführen“ (deshalb exec von execute) der Anweisungen
-- geschieht hier. Bei den Indizes ist allerdings Vorsicht geboten: Listen
-- werden beginnend mit Null indiziert, aber die Register c_i sind
-- beginnend mit 1 indiziert. Deshalb gilt: c!!(a-1) == c_i
-- Argumente: Eine Anweisung, aktuelle Konfiguration der Registermaschine;
-- Ausgabe: Neue Konfiguration
exec :: Instr -> Config -> Config
-- Wert aus Register laden
exec (Load a) (b:c0:c) = (b+1):(c!!(a-1)):c
-- Übergebenen Wert laden
exec (CLoad a) (b:c0:c) = (b+1):a:c
-- c0 in beliebiges Register speichern
-- b inkrementieren, c0 bleibt wie es ist, dann alle Elemente bis zum
-- a-ten anhängen, c0 im a-ten Element speichern und den Rest der Zustände
-- anhängen
exec (Store a) (b:c0:c) = (b+1):c0:(take (a-1) c) ++ c0:(drop a c)
-- Addition
exec (Add a) (b:c0:c) = (b+1):(c0 + (c!!(a-1))):c
exec (CAdd a) (b:c0:c) = (b+1):(c0 + a):c
-- Subtraktion, negative Werte „gibt es nicht“, deshalb auch die
-- if-then-else-Abfrage
exec (Sub a) (b:c0:c) = if c0 >= (c!!(a-1))
then (b+1):(c0 - (c!!(a-1))):c
else (b+1):0:c
exec (CSub a) (b:c0:c) = if c0 >= a
then (b+1):(c0 - a):c
else (b+1):0:c
-- Multiplikation
exec (Mult a) (b:c0:c) = (b+1):(c0 * (c!!(a-1))):c
exec (CMult a) (b:c0:c) = (b+1):(c0 * a):c
-- Division (ganzzahlig, deshalb div statt /)
exec (Div a) (b:c0:c) = (b+1):(c0 `div` (c!!(a-1))):c
exec (CDiv a) (b:c0:c) = (b+1):(c0 `div` a):c
-- Sprungbefehle
exec (Goto a) (b:c) = a:c
exec (IfGoto a) (b:c0:c) = if c0 == 0 then (a:c0:c) else (b+1:c0:c)
-- Zur Sicherheit wird hier auch End behandelt, obwohl rm in diesem
-- Fall eigentlich Abbrechen sollte
exec (End) c = c
-- Beliebiges Programm ausführen
-- Argumente: Das Programm natürlich, eine Startkonfiguration, wichtig
-- dabei: b muss >= 1 sein!; Ausgabe: Endzustand der Registermaschine
rm :: Program -> Config -> Config
-- Wieder zwei große Vorsicht! Hier greift wieder das Problem, dass Listen
-- ab 0, wir Menschen aber ab 1 zählen. Deshalb: Aktueller Schritt minus
-- Eins == Element in der Liste der Instruktionen p. Außerdem kann man die
-- Liste p *nicht* zerteilen (also p:ps z.B.), denn für Gotos müssen immer
-- *alle* Anweisungen verfügbar sein, damit auch zurück gesprungen
-- werden kann und die Sprungadressen immer stimmen!
rm p (b:c0:c) = if (p!!(b-1)) == End
-- Aktuellen Zustand ausgeben, wenn End-Anweisung erreicht wird
then (b:c0:c)
-- Sonst: Aktuelle Anweisung ausführen und zurückgegebenen Status
-- für einen weiteren rekursiven Aufruf benutzen
else rm p (exec (p!!(b-1)) (b:c0:c))
Ein wiederum sehr einfaches Programm für diese Registermaschine wäre folgendes:
p :: Program
p = [Load 1, Add 2, Store 3, End]
c :: Config
c = [1, 0, 2, 3, 0]
Dieses Programm liest zunächst den Wert aus Register 1 in den Akkumulator, addiert den Wert aus Register 2 hinzu und speichert den Wert aus C0 im 3. Register. Mathematisch ausgedrückt tut das Programm einfach nur folgendes: C1 + C2 = C3
Lässt man das Programm nun mit der Startkonfiguration c laufen, so ist das Ergebnis: [4,5,2,3,5]. 2 + 3 ist also, wie man erwarten kann, gleich fünf.
Ich habe auch noch ein weiteres Beispielprogramm für diese Registermaschine veröffentlicht. Es handelt sich um die Implementierung eines endlichen Automats.