Turing machine
Julien Quint (Pom) <pom-gt/[email protected]> Sat, 18 Sep 2004 15:18:21 +0900
| Newsgroups | gmane.comp.lang.perl.qotw.discuss |
|---|---|
| Message-ID | <[email protected]> |
Here is my Turing machine implementation. The code is very messy, but I
have already spent too much time on it to clean it up, sorry...
I have provided a few options:
--start allows you to start at any position in the tape (normally you
start at position 0, where the first character of the tape is; positive
values move to the right of the tape, negative values move backward).
--init allows you to specify the initial state. (This is useful when
using the extended syntax, for instance. See below)
--debug shows the machine working.
--notrim outputs the portion of the tape that was worked on. By
default, whitespace is trimmed from the output.
--help displays an help message and quits.
--extend turns on the extended syntax. This is just syntactic sugar and
adds no special abilities to the machine. In the extended syntax, you
can use patterns such as /a-z0-9/ to specify character ranges in the
first state/symbol pairs. You can also use ¥1, ¥2, ... ¥9. In the next
three fields (next state, symbol to write and direction to go) you can
use $1, ... $9 to use the values that were matched in the lefthand part
and `...` to include perl code to be evaluated. This is *very* brittle
and doesn't work for everything. (In a nutshell: everything is put into
a string then passed to eval. A `code` section is transformed into
"@{[do{code}]}" in order to be evaluated inside a string. This is a
really ugly hack.)
Here is a short program that turns a word into uppercase, using the
extended syntax.
s0 /A-Z0-9/ s0 $1 R
s0 /a-z/ s0 `uc($1)` R
It is equivalent to:
s0 A s0 A R
s0 B s0 B R
...
s0 9 s0 9 R
s0 a s0 A R
...
s0 z s0 Z R
I also rewrote the decimal addition (excellent program!) to show some
features of the extended syntax:
SeekR1 _ SeekR2 _ R
SeekR/12/ /0-9/ SeekR$1 $2 R
SeekR2 _ Dec _ L
Dec /1-9/ NoMoveSeekL `$1-1` R
Dec 0 Dec 9 L
Dec _ CleanUp _ R
CleanUp 9 CleanUp _ R
CleanUp _ End _ R
SeekL1 /0-9/ SeekL1 $1 L
SeekL1 _ Inc _ L
Inc _ NoMoveSeekR 1 L
Inc /0-8/ NoMoveSeekR `$1+1` L
Inc 9 Inc 0 L
NoMoveSeek/LR/ /0-9_/ Seek`$1`1 $2 $1
Here is the code. Again I apologize for the mess :)
--
Julien
:wq
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long;
# Command line options
my $DEBUG = 0;
my $HELP = 0;
my $START = 0;
my $TRIM = 1;
my $INIT = "";
my $EXT = 0;
GetOptions("debug!" => \$DEBUG, "extended!" => \$EXT, help => \$HELP,
"initial=s" => \$INIT, "start=s" => \$START, "trim!" => \$TRIM);
# Regex for an atomic unit pattern (normally just \w, but more complex
with the
# extended syntax)
my $PAT1 = $EXT ? '(?:\w|\/[\w-]+\/|\\\\\d)' : "\\w";
my $PAT2 = $EXT ? '(?:\w|\$\d|`[^`]+`)' : "\\w";
my $PATLR = $EXT ? '(?:\w|\$\d|`[^`]+`)' : "[LR]";
# Display the help message then quit
if ($HELP) {
print <<HELP;
Usage: $0 [options] program_file [tape]
Options are:
--debug, --nodebug: turns debugging on or off.
--extended, --noextended: use extended syntax or not.
--help: shows this message and exits.
--initial q: intial state is q.
--start p: starts at position p in the tape (first position is 0).
--trim, --notrim: trim _ characters at beginning and end of output.
HELP
exit;
}
# Get the arguments (program file and optional initial tape)
my ($program, $tape) = @ARGV;
die "No program given.\n" if !defined $program;
warn "Extra arguments ignored: @ARGV[2 .. $#ARGV]\n" if @ARGV > 2;
# Read in the program
my %states = ();
my $q = $INIT;
open PROGRAM, $program or die "Cannot open program file $program: $!\n";
while (<PROGRAM>) {
my $line = $_;
# Normalize space and remove comments, skip empty lines
s/\s+/ /g;
s/^ //;
s/#.*$//;
s/ $//;
next if !$_;
# Syntax checking
if (!m!($PAT1+) ($PAT1) ($PAT2+) ($PAT2) ($PATLR)!) {
my $msg = "Sytnax error, line $.:\n $line";
$msg .= "(perhaps you forgot the --extend flag?)\n" if !$EXT;
die $msg;
}
# Get value for initial state
$q = $1 if $q eq "";
my $rx = "$1 $2";
my $rest = [map { s/`([^`]+)`/\@{[do{$1}]}/g; $_ } my @rest = ($3,
$4, $5)];
$rx =~ s!/([\w-]+)/!([$1])!g if $EXT;
die "Redefinition of state $1 / symbol $2 at line $..\n"
if exists $states{$rx};
$states{$rx} = $rest;
print STDERR "$rx => @$rest\n" if $DEBUG;
}
close PROGRAM;
# Initialize the tape and check the start state
die "Cannot use $q as initial state.\n" if $q =~ /\W/;
my $pos = $START;
$tape = "_" if !defined $tape;
die "Illegal symbols on tape.\n" if $tape =~ /\W/;
my @tape = split //, $tape;
# Add symbols to the tape if the start position is outside of the range
# specified by the initial values
if ($pos < 0) {
unshift @tape, ("_") x -$pos;
$pos = 0;
} else {
push @tape, ("_") x ($pos - $#tape);
}
my $start = 0;
my $end = $#tape;
# Run the program
while (my @states = grep { "$q $tape[$pos]" =~ /^$_$/ } keys %states) {
die "Too many possible matches for $q/$tape[$pos] (",
join(", ", map { s! !/!; $_ } @states), ").\n" if @states > 1;
"$q $tape[$pos]" =~ /$states[0]/;
my ($next, $new, $move) = map { eval "\"$_\"" }
@{$states{$states[0]}};
die "Next state cannot be $next.\n" if $next !~ /^\w+$/;
die "Cannot write symbol \"$new\" on tape.\n" if $new !~ /^\w$/;
die "Cannot move in direction \"$move\".\n" if $move !~ /^[LR]$/;
print STDERR "... @tape ...\n", " " x (2 * $pos + 4),
"^ $q -> $next, $new, $move\n" if $DEBUG;
# Update the state
$q = $next;
$tape[$pos] = $new;
$end = $pos if $end < $pos;
# The tape is infinite but stored in an array, so if we go past one
end we
# have to add symbols.
if ($move eq "L") {
--$pos;
--$start if $pos < $start;
if ($pos < 0) {
$pos = 0;
$start = 1;
++$end;
unshift @tape, "_";
}
} else {
++$pos;
if ($pos == @tape) {
push @tape, "_";
}
}
}
# Show the state one last time (once the program has finished running)
print STDERR "... @tape ...\n", " " x (2 * $pos + 4), "^ $q\n" if
$DEBUG;
# Output the result
my $result = join "", @tape[$start .. $end];
if ($TRIM) {
print STDERR "Trimming result $result\n" if $DEBUG;
$result =~ s/^_+//;
$result =~ s/_+$//;
}
print "$result\n";