[SPOILER] Perl Quiz of the Week #24 (Turing Machine simulation)
Dan Sanderson <[email protected]> Fri, 17 Sep 2004 09:57:04 -0700 (PDT)
| Newsgroups | gmane.comp.lang.perl.qotw.discuss |
|---|---|
| Message-ID | <[email protected]> |
My Turing machine simulator is simple, and not very different from some of
the solutions posted. I used an array for the tape, and unshift'ed a
blank (and re-calibrated the tape head) whenever the tape head fell off
the left.
Supports a --debug option, which prints a line to STDERR for each
iteration including the state, the tape, and tape head position. Also
supports a --maxiterations option, in case a billion iterations isn't
enough (or too much).
I also started work on a macro processor, but I lost interest. :)
Great puzzle, easy and fun! Thanks!
-- Dan
- - -
#!/usr/bin/perl -w
# turing, a simulation of a Turing Machine
# By Dan Sanderson
# For Perl Quiz of the Week # 24
use Getopt::Long;
my $debug = 0;
my $maxiterations = 1_000_000_000;
my $result = GetOptions('debug' => \$debug,
'maxiterations:i' => \$maxiterations,
);
my $state;
my @tape; # Undefined cells are considered blank.
my $headpos = 0;
# The instruction table.
# $progtable{oldstate}->{oldtape} = [newstate, newtape, headmove]
my %progtable;
if (!$result || (scalar(@ARGV) != 1 && scalar(@ARGV) != 2)) {
die "Usage: $0 [--debug] [--maxiterations=#] programfile [tapestring]\n";
}
# Initialize tape string.
if (defined($ARGV[1])) {
die "Invalid initial tapestring\n" if $ARGV[1] !~ /^[a-zA-Z0-9_]+$/;
@tape = split(//, $ARGV[1]);
}
# Read instruction table from file.
open my $fh, $ARGV[0] or die "Could not open $ARGV[0] for reading: $!\n";
while (defined(my $line = <$fh>)) {
# Strip comments and whitespace, skip lines without commands.
$line =~ s/\#.*$//;
$line =~ s/\s+$//;
$line =~ s/^\s+//;
next if !$line;
my ($oldstate, $oldtape, $newstate, $newtape, $headmove) = split(/\s+/, $line);
if (!defined($oldstate)
|| !defined($oldtape)
|| !defined($newstate)
|| !defined($newtape)
|| !defined($headmove)
) {
die "Syntax error in program on line $.: too few elements\n";
}
if ($oldtape !~ /^[a-zA-Z0-9_]$/
|| $newtape !~ /^[a-zA-Z0-9_]$/
|| $oldtape !~ /^\w+$/
|| $newtape !~ /^\w+$/
|| ($headmove ne 'L' && $headmove ne 'R')
) {
die "Syntax error in program on line $.: invalid elements\n";
}
# Initialize state from first instruction.
$state = $oldstate if (!defined($state));
# Detect instruction overwrites.
die "Attempt to overwrite an instruction on line $.\n"
if (exists($progtable{$oldstate})
&& exists($progtable{$oldstate}->{$oldtape}));
# Store instruction in table.
$progtable{$oldstate}->{$oldtape} = [$newstate, $newtape, $headmove];
}
close $fh;
# A subroutine that prints the state and tape, with head position indicated.
sub display_state {
printf STDERR "%-20s", $state;
for (my $i = 0; $i <= $#tape; $i++) {
if ($i == $headpos) {
print STDERR "[".(defined($tape[$i]) ? $tape[$i] : '_')."]";
} else {
print STDERR defined($tape[$i]) ? $tape[$i] : '_';
}
}
if ($headpos > $#tape) {
print STDERR '_' x ($headpos - $#tape - 1), '[_]';
}
print STDERR "\n";
}
my $iterations = 0;
my $action;
while (exists($progtable{$state})
&& exists($progtable{$state}->{(defined($tape[$headpos]) ? $tape[$headpos] : '_')})
&& ($action = $progtable{$state}->{(defined($tape[$headpos]) ? $tape[$headpos] : '_')})
) {
&display_state if $debug;
my ($newstate, $newtape, $headmove) = (@{$action});
$state = $newstate;
if ($newtape eq '_') {
# Continue to use undef (not '_') for blanks in tape array.
undef($tape[$headpos]);
} else {
$tape[$headpos] = $newtape;
}
if ($headmove eq 'L') {
--$headpos;
} elsif ($headmove eq 'R') {
++$headpos;
}
# Shift tape if head falls off the left.
if ($headpos < 0) {
unshift @tape, undef;
$headpos = 0;
}
die "Maximum iterations reached ($iterations), aborting\n"
if (++$iterations > $maxiterations);
}
&display_state if $debug;
# Print the final tape, starting from first non-blank to last non-blank,
# outputting interior blanks as '_'.
shift @tape while (!defined($tape[0]));
pop @tape while (!defined($tape[$#tape]));
@tape = map { !defined($_) ? '_' : $_ } @tape;
print @tape, "\n";
exit(0);
__END__