#!/usr/local/bin/perl
#
# [Turing_Simulator.pl] 28.03.1996 by R.Scholz <mrz@informatik.uni-jena.de>
#
# Beschreibung: simuliert eine Turing-Maschine.
#
# Aufruf: Turing_Simulator [Programm-Datei.tur [Eingabe-Band]]
#
# Übung 3, Aufgabe (1), Vorlesung PS
#
# Vereinbarungen:
#
# 1. Natürliche Zahlen sind unär kodiert:	1 --> 1
#						2 --> 11
#						3 --> 111 usw.
#
# 2. Bei Programmende sollte der Lesekopf am Anfang des Ergebniswortes stehen.
#
# 3. Das Band ist zweiseitig.
#
# 4. Es erfolgt keine Kontrolle, ob die Eingabewörter richtig sind;
#    z.B. beim Programm 'add.tur' ist das Eingabeband '10101' zulässig,führt aber zum
#    falschen Ergebnis '00111'. Man kann aber das 2. Wort durch 2 oder mehr Nullen
#    nach rechts abgrenzen: '101001' erzeugt '001101'.
#
# 5. Die zulässigen Zeichen auf dem Band sind per Default '0' und'1'.
#    Kann durch Variable $ALPHABET eingestellt werden bzw.
#    durch 'ALPHABET  {chars}' im Programmfile ( z.B.: ALPHABET   012RST )
#    Das Eingabeband wird daraufhin geprüft.
#
# 6. Die Turingmaschine startet mit dem Anfangszustand $Start_Zustand=Start,
#    der Kopf steht an Position 0;
#    dort ist außerdem das Eingabeband abgespeichert.
#    Das Programm wird bei Erreichen des Endzustandes $End_Zustand=Ende
#    beendet.



printf "\n\n\t\tTuring_Simulator: Simulation einer Turing Maschine.\n\n";
printf "\t\t\t(C) 1996 mrz\@informatik.uni-jena.de\n\n\n";
printf "Aufruf:  Turing_Simulator.pl [prog.tur [EB]] \n\n";

$Start_Zustand=Start;
$End_Zustand=Ende;
%ZUSTAENDE=(Start,"",Ende,"");	# nur zur Anzeige für den Nutzer

$ALPHABET="01";			# zulässige Zeichen auf dem Band
%BAND=();			# das Arbeitsband
$AKT_ZUSTAND=$Start_Zustand;	# aktueller Zustand der Turingmaschine bzw. des Programms,
				# Startzustand festlegen.
$AKT_ZEICHEN=0;			# gerade gelesenes Zeichen vom Arbeitsband
$STEPS=-1;			# Anzahl der Schritte bis Programmende
				# Ist die Schrittanzahl eine Interpretationsfrage ?
				# -> was ist die Anzahl beim EB=0  ??
				#	0 oder 1 ??
$KOPF_POS=0;			# aktuelle Kopfposition
$LEFT_POS=$RIGHT_POS=0;		# linke u. rechte Bandposition
$DIR=stop;			# Richtung, in der der Kopf bewegt werden soll
$ZEICHEN=0;			# zu schreibendes Zeichen
$ZUSTAND=0;			# neuer anzunehmender Zustand

&check_input;			# Eingabeparameter (file,band)
&read_turing_program;		# Programm in Kontrolleinheit einlesen
&init_BAND;			# Eingabeband auf Arbeitsband schreiben

printf "Programmfile:         '$PROGRAM' \n";
printf "Eingabeband:          '$EB' \n";
printf "Alphabet:             '$ALPHABET' \n";
printf "Mögliche Zustände:    ' ";
foreach (sort keys %ZUSTAENDE) { printf "$_ " }
printf "'\n\n";





#---------------------------------- Main loop ---------------------------------

do
 {  $AKT_ZEICHEN=$BAND{$KOPF_POS}; &show_Zustand;

    ($DIR,$ZEICHEN,$ZUSTAND)=&return_KE($AKT_ZUSTAND,$AKT_ZEICHEN);
    
    # test, ob die passende Regel vorhanden ist:
    if ($ZEICHEN eq '')
     { printf "\nFehler bei Programmabarbeitung: keine Regel für: [Zustand='$AKT_ZUSTAND',Zeichen='$AKT_ZEICHEN'] ! \n";
       die "(wahrscheinlich ist Ihr Programmfile '$PROGRAM' nicht korrekt.) \n\n";
     }

    # test, ob für den Zustand überhaupt eine Regel existiert:
   if (not exists $ZUSTAENDE{$ZUSTAND})
     { printf "\nFehler bei Programmabarbeitung: keine Regel für: [Zustand='$ZUSTAND',Zeichen='$AKT_ZEICHEN'] ! \n";
       die "(wahrscheinlich ist Ihr Programmfile '$PROGRAM' nicht korrekt.) \n\n";
     }

    $BAND{$KOPF_POS}=$ZEICHEN;		# schreibe Zeichen auf akt. Kopfposition
    
    if  ($DIR eq right)			# nach Rechts gehen ?
      {  $KOPF_POS++;
	 if ($KOPF_POS > $RIGHT_POS) { $BAND{$KOPF_POS}=0; $RIGHT_POS++ }
      }
    elsif  ($DIR eq left)		# oder nach Links ?
      {	 $KOPF_POS--;
	 if ($KOPF_POS < $LEFT_POS) { $BAND{$KOPF_POS}=0; $LEFT_POS-- }
      }
    $AKT_ZUSTAND=$ZUSTAND; $STEPS++;	# jetzt neuen Zustand annehmen
 }
until ($AKT_ZUSTAND eq $End_Zustand);	# besser als bei 'stop' abzubrechen.
&show_Zustand;

printf "\n$STEPS Schritte benötigt.\n\n";


# --------------------------------- Ende -------------------------------------









# ------------------------------ Subroutines ---------------------------------


sub check_input
# Behandlung der Kommandozeile, Default-Parameter setzen.
{
   local $t;
   if (@ARGV[0] ne "")						# 1. Parameter
     { 
	$t=@ARGV[0]; split(/\./,$t); $_=pop(@_);		# endet Filename auf .tur ?
	if ($_ eq tur)
	  {
	     $PROGRAM=$t;
	     die "\nFehler: Datei '$PROGRAM' nicht gefunden!\n" unless (-f $PROGRAM)
	  }
	else
	  {
	     die "\nFehler: 1. Argument ('$t') muß Name des Programms sein und auf '.tur' enden !\n\n"
	  }
     }
   else { $PROGRAM="add.tur" }
	
   if (@ARGV[1] ne "")						# 2. Parameter
     {
	if (@ARGV[1] =~ /^[$ALPHABET]*$/)					# Eingabeband aus $ALPHABET ?
	  { $EB=@ARGV[1] }
	else 
	  {
	     die "\nFehler: 2. Argument ('@ARGV[1]') muß das Eingabeband sein, bestehend aus ^[$ALPHABET]*\$ !\n\n"
	  }
     }
   else { $EB=110111 }
}

sub read_turing_program
# liest das Turing-Programm aus der Datei $PROGRAM ein
# und erzeugt daraus die Kontrolleinheit KE
{
   local $line;
   local $Z,$gz,$ri,$sz,$nz;

   open(PROG,$PROGRAM) || die "\nFehler: Kann Programm-Datei '$PROGRAM' nicht öffnen!\n\n";
   while (chomp($line=<PROG>))
     {
	next if ($line =~ /^#/);			# skip comments
	if ($line =~ /^(ALPHABET)/)
	 {
	    split(/\s/,$line);
	    $ALPHABET=shift(@_);$ALPHABET=shift(@_);
	    next;
	 }
	($Z,$gz,$ri,$sz,$nz)=split(/\s/,$line);		# split at whitespaces
        next if ($ri !~ /(stop)|(right)|(left)/);	# read only turing-commands
	if (index($ALPHABET,$gz) == -1)			# $gz korrekt ?
	  { printf "Fehler in folgender Zeile des Programms '$PROGRAM' :\n\n"; 
	    printf "\t$line \n\n";
	    die "Das Zeichen '$gz' ist nicht im zulässigen Alphabet '$ALPHABET' enthalten !\n\n";
	  }
#	  $AAA=quotemeta $ALPHABET;
        if (index($ALPHABET,$sz) == -1)			# $sz korrekt ?
	  { printf "Fehler in folgender Zeile des Programms '$PROGRAM' :\n\n"; 
	    printf "\t$line \n\n";
	    die "Das Zeichen '$sz' ist nicht im zulässigen Alphabet '$ALPHABET' enthalten !\n\n";
	  }
	if (length($Z) >8)
	  { printf "Fehler in folgender Zeile des Programms '$PROGRAM' :\n\n"; 
	    printf "\t$line \n\n";
	    die "Die Länge des Zustandes '$Z' ist zu groß (>8) !\n\n";
	  }
	if (length($nz) >8)
	  { printf "Fehler in folgender Zeile des Programms '$PROGRAM' :\n\n"; 
	    printf "\t$line \n\n";
	    die "Die Länge des Zustandes '$nz' ist zu groß (>8) !\n\n";
	  }
        $KE{$Z,$gz}="$ri,$sz,$nz";			# Aufbau der Kontrolleinheit
	$ZUSTAENDE{$Z}="";				# zur Ausgabe aller
							# möglichen Zustände
     }
   close PROG;
}

sub return_KE
# input: Zustand + gelesenes Zeichen
# output: Richtung,zu schreibenes Zeichen, neuer Zustand
{
   local ($zustand,$zeichen)=@_;
   split(/,/,$KE{$zustand,$zeichen});
}


sub init_BAND
# belegt das Band mit dem Eingabeband EB
# %BAND muß nicht mit 0 initialisiert werden, das passiert automatisch,
# wenn das Band vergrößert wird (siehe Main-loop)
{
   local $i;
   $i=length($EB)-1;
   for (0..$i) { $BAND{$_}=substr($EB,$_,1) }
   $RIGHT_POS=$i;
}

sub show_Zustand
# Zeigt den Zustand während der Programmabarbeitung an
{
   printf "Zustand = [$AKT_ZUSTAND]";
   for (1..(10-length($AKT_ZUSTAND))) { printf " " }
   printf "AB = [";
   for ($LEFT_POS..$RIGHT_POS) { printf "$BAND{$_}" }
   printf "]\nKopf:\t\t\t    ";
   for (1..($KOPF_POS-$LEFT_POS )) { printf " " }
   printf "#\n";	
}