[SPOILER] Perl Quiz of the Week #24 (Turing Machine simulation)

Ned D Hanks <[email protected]> 20 Sep 2004 08:15:39 -0600
Newsgroups gmane.comp.lang.perl.qotw.discuss
Message-ID <[email protected]>
This is my first solution to a qotw.  I was not sure about the use of Data::Dumper, I 
like using it for debuging. I'm think its mentioned in the camel book.

It sounded hard at first but once I started went along quickly.

I used split and then validated each element of a line so I could give more
useful error messages.

I used an array for the tape.  This was simpler than a hash.

I am going to add some way of stopping infinite loops.  I was thinking of a counter
per program line and an overall program counter.  The per line counter would get reset 
on a "state/tape value" change.

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

my $debug = exists $ENV{DEBUG} ? $ENV{DEBUG} : 0;

my $blank = '_';
my @tape = ( $blank );
my $tapepos = 0;
my $state;
my %program;

##########################
# read program from file
##########################

sub read_program ($)
{
   my ($file) = @_;
   my %program;
   my $line = 1;
   my ($os, $od, $ns, $nd, $m, $junk);
   open (F, "< $file") || die "ERROR: Unable to open $file\n";

   while(<F>)
   {
      chop;
      next if /^\s*#/;
      next if /^\s*$/;

      ($os, $od, $ns, $nd, $m, $junk) = split(/[\s#]+/);
      $state = $os if !defined $state;  # initialize state
      if ($os !~ /\w+/) {print STDERR "ERROR line $line: invalid state $os\nline:$_\n";exit 1};
      if ($od !~ /\w/)  {print STDERR "ERROR line $line: invalid data $od\nline:$_\n";exit 1};
      if ($ns !~ /\w+/) {print STDERR "ERROR line $line: invalid state $ns\nline:$_\n";exit 1};
      if ($nd !~ /\w/)  {print STDERR "ERROR line $line: invalid data $nd\nline:$_\n";exit 1};
      if ($m  !~ /L|R/) {print STDERR "ERROR line $line: invalid tape move $m\nline:$_\n";exit 1};
      if (defined $program{$os}{$od})
      {
         print STDERR "ERROR line $line: state/data already defined\nline:$_\n";
	 exit 1
      };
      $program{$os}{$od} = {
	      state => $ns,
	      data => $nd,
	      move => $m};
   }

   close(F);

   return %program;
}

##########################
# Main
##########################

my $file = shift;
die "ERROR: program filename not specified\n" unless defined $file;
@tape = split('', shift) if @ARGV;

print STDERR "tape:", join(":", @tape), "\n" if $debug;

%program = read_program($file);

print Dumper(%program) if $debug;

# Execute program
my $tapedata;
my $curstate;

$tapepos = 0;

while(1)
{
   $curstate = $state;
   $tapedata = $tape[$tapepos];
   if ( $debug )
   {
      print STDERR "$curstate:";
      print STDERR "$tapedata:";
   } 
   last if !defined $program{$curstate}{$tapedata};
   if ($debug) 
   {
      print STDERR "$program{$curstate}{$tapedata}->{state}:";
      print STDERR "$program{$curstate}{$tapedata}->{data}:";
      print STDERR "$program{$curstate}{$tapedata}->{move}:";
      print "\n";
   }

   $state = $program{$curstate}{$tapedata}->{state};
   $tape[$tapepos] = $program{$curstate}{$tapedata}->{data};

   if ($program{$curstate}{$tapedata}->{move} eq 'R')
   {
      $tapepos++;
      if(!defined $tape[$tapepos])
      {
         $tape[$tapepos] = $blank;
      }
   }
   else # tapepos eq L
   {
      $tapepos--;
      if ($tapepos < 0)
      {
	 unshift(@tape,$blank);
	 $tapepos = 0;
      }
   }
   print STDERR "\n>" if $debug;
   my $j = <STDIN> if $debug;
}
print STDERR "\n" if $debug;

# Print tape

my $start;
my $end;

print STDERR "tape:", join(":", @tape), "\n" if $debug;

for ($start = 0; $start < @tape; $start++)
{
   last if $tape[$start] ne $blank;
   #$start++;
}

for ($end = @tape - 1; $end >=0 ; $end--)
{
   last if $tape[$end] ne $blank;
}

print STDERR "start:$start:end:$end\n" if $debug;

for my $i ($start..$end)
{
   print $tape[$i];
}
print "\n";