File Coverage

blib/lib/Acme/Turing.pm
Criterion Covered Total %
statement 85 89 95.5
branch 20 26 76.9
condition 10 16 62.5
subroutine 9 9 100.0
pod 6 6 100.0
total 130 146 89.0


line stmt bran cond sub pod time code
1             package Acme::Turing;
2              
3 1     1   729 use strict;
  1         3  
  1         40  
4 1     1   5 use Carp;
  1         1  
  1         118  
5 1     1   5 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         5  
  1         1351  
6              
7             require Exporter;
8              
9             @ISA = qw(Exporter);
10             @EXPORT = qw();
11             @EXPORT_OK = qw();
12             $VERSION = '0.02';
13              
14             %EXPORT_TAGS = ( all => [ @EXPORT_OK ] );
15              
16             #--- Create the Turing machine.
17             sub new {
18 3     3 1 140 my $invocant = shift;
19 3   33     25 my $class = ref($invocant) || $invocant;
20 3         23 my $self = {
21             steps => undef,
22             spec => {},
23             tape => [],
24             tape_pos => 0,
25             cur_state => 'START',
26             @_ };
27              
28 3   100     15 $self->{'steps'} ||= 250;
29 3         5 my $tapelen = 200;
30 3         89 $self->{'tape'} = [ (" ") x $tapelen ];
31 3         12 $self->{'tape_pos'} = int($tapelen / 2);
32              
33 3         18 return bless($self, $class);
34             }
35              
36             # Add an entry to the spec hash.
37             sub add_spec {
38 19     19 1 57 my $self = shift;
39 19         28 my ($hkey, $hentry) = @_;
40 19 50       39 Carp::croak("No entry defined") unless defined($hentry);
41              
42 19         51 $self->{'spec'}{$hkey} = $hentry;
43 19         34 return;
44             }
45              
46             # Initialize the tape.
47             sub init_tape {
48 1     1 1 7 my $self = shift;
49 1         4 my ($startpos, @symbols) = @_;
50 1         2 my @Tape = @{$self->{'tape'}};
  1         70  
51 1 50 33     12 Carp::croak("Start position $startpos is not on tape")
52             if $startpos < 0 || $startpos > $#Tape;
53              
54 1         2 my $i;
55 1         13 for ($i = 0; $i < @symbols ; $i++) {
56 2         9 $self->{'tape'}[$startpos + $i] = $symbols[$i];
57             }
58 1         23 return;
59             }
60              
61             # Step the machine to the next state. The next state is returned.
62             sub step {
63 352     352 1 481 my $self = shift;
64             # $ps = previous state. $tp = tape position. $ts = tape symbol.
65 352         627 my $ps = $self->{'cur_state'};
66 352         476 my $tp = $self->{'tape_pos'};
67 352         485 my $ts = $self->{'tape'}[$tp];
68              
69             # Find the instructions for this state and tape symbol. If the tape
70             # symbol doesn't exist, try ANY; if that doesn't exist, fail.
71 352         519 my $st_key = "$ps:$ts";
72 352 100       1013 if (! defined($self->{'spec'}{$st_key})) {
73 216         216 $st_key = "$ps:ANY";
74 216 50       524 die "Machine aborted: no action defined for state $ps/symbol $ts"
75             unless defined($self->{'spec'}{$st_key});
76             }
77 352         510 my $actions = $self->{'spec'}{$st_key};
78 352         1253 my ($inst1, $next_state) = split /:/, $actions;
79              
80             # Parse the instructions (P, L, R, E).
81 352         867 $inst1 =~ s/\s//g;
82 352         967 my @instruc = split /,/, $inst1;
83 352         589 foreach (@instruc) {
84 693 100       2960 if (/^P/) { # Write to the tape
    100          
    50          
85 89         126 my $data = substr($_, 1);
86 89 50       283 $self->{'tape'}[$tp] = $data if $data ne "";
87             } elsif (/^E/) {
88 10         25 $self->{'tape'}[$tp] = ' ';
89             } elsif (/^[LR]/) {
90             #--- Move the tape. If we go beyond the end, make it bigger.
91 594         674 my @Tape = @{$self->{'tape'}};
  594         16970  
92 594 100       1796 $tp += (substr($_,0,1) eq 'L') ? -1 : 1;
93 594 50       8664 if ($tp < 0) {
    100          
94 0         0 unshift(@Tape, (" ") x 50);
95 0         0 $self->{'tape'} = [ @Tape ];
96 0         0 $tp += 50;
97             } elsif ($tp > @Tape - 1) {
98 1         25 push(@Tape, (" ") x 50);
99 1         88 $self->{'tape'} = [ @Tape ];
100             } else { ; }
101             } else {
102 0         0 warn "Invalid instruction <$_> for state $ps";
103             }
104             }
105              
106 352         640 $self->{'tape_pos'} = $tp;
107 352         490 $self->{'cur_state'} = $next_state;
108 352         963 return $next_state;
109             }
110              
111             # Print part of the current contents of the tape. We print the
112             # symbol at the current tape position, along with L symbols to
113             # the left and R to the right.
114             sub print_tape {
115 356     356 1 624 my $self = shift;
116 356         467 my ($L, $R) = @_;
117 356   50     851 $L ||= 2; $R ||= 2; # Defaults
  356   50     521  
118 356         386 my $i;
119 356         457 my $tp = $self->{'tape_pos'};
120              
121 356         881 for ($i = $tp - $L; $i <= $tp + $R; $i++) {
122 2327 100       65738 print " Tape [$i] ", ($i == $tp) ? ">>> " : " ",
123             "$self->{'tape'}[$i]\n";
124             }
125 356         801 return;
126             }
127              
128             # Run the machine.
129             sub run {
130 3     3 1 12 my $self = shift;
131 3         12 my ($L, $R) = @_;
132 3   100     13 $L ||= 2; $R ||= 2;
  3   100     12  
133              
134 3         5 my $current_state = 'START';
135 3         5 my $step_num = 0;
136 3         92 printf "%4d %s\n", $step_num, $current_state;
137 3         13 $self->print_tape(2,2);
138 3         10 while ($current_state ne 'STOP') {
139 352         8093 print '-' x 60, "\n";
140 352         1201 $current_state = $self->step();
141 352         422 $step_num++;
142 352         10065 printf "%4d %s\n", $step_num, $current_state;
143 352         1352 $self->print_tape($L,$R);
144 352 100       1355 if ($step_num == $self->{'steps'}) {
145 2         43 print "------> Reached maximum number of steps.\n";
146 2         9 last;
147             }
148             }
149 3         66 print "---> Machine stopped.\n";
150 3         15 return;
151             }
152              
153             1;
154              
155             __END__