File Coverage

blib/lib/Language/Befunge/Interpreter.pm
Criterion Covered Total %
statement 174 174 100.0
branch 70 72 97.2
condition 4 4 100.0
subroutine 21 21 100.0
pod 10 10 100.0
total 279 281 99.2


line stmt bran cond sub pod time code
1             #
2             # This file is part of Language::Befunge.
3             # Copyright (c) 2001-2009 Jerome Quelin, all rights reserved.
4             #
5             # This program is free software; you can redistribute it and/or modify
6             # it under the same terms as Perl itself.
7             #
8             #
9              
10             package Language::Befunge::Interpreter;
11 68     68   100317 use 5.010;
  68         250  
  68         3159  
12              
13 68     68   455 use strict;
  68         145  
  68         3137  
14 68     68   429 use warnings;
  68         134  
  68         3160  
15              
16 68     68   409 use Carp;
  68         144  
  68         7319  
17 68     68   17101 use Language::Befunge::Debug;
  68         152  
  68         4879  
18 68     68   74183 use Language::Befunge::IP;
  68         248  
  68         12710  
19 68     68   115214 use UNIVERSAL::require;
  68         164473  
  68         1222  
20              
21             # FIXME: wtf? always use get_/set_ or mutators, but not a mix of them!
22             use Class::XSAccessor
23 68         2045 getters => {
24             get_dimensions => 'dimensions',
25             get_file => 'file',
26             get_params => 'params',
27             get_retval => 'retval',
28             get_storage => 'storage',
29             get_curip => 'curip',
30             get_ips => 'ips',
31             get_newips => 'newips',
32             get_ops => 'ops',
33             get_handprint => 'handprint',
34             get_wrapping => '_wrapping',
35             _get_input => '_input',
36             },
37             setters => {
38             set_dimensions => 'dimensions',
39             set_file => 'file',
40             set_params => 'params',
41             set_retval => 'retval',
42             set_curip => 'curip',
43             set_ips => 'ips',
44             set_newips => 'newips',
45             set_ops => 'ops',
46             set_handprint => 'handprint',
47             _set_input => '_input',
48 68     68   7424 };
  68         165  
49              
50              
51             # Public variables of the module.
52             $| = 1;
53              
54              
55             # -- CONSTRUCTOR
56              
57              
58             #
59             # my $interpreter = LBI->new( $opts )
60             #
61             # Create a new funge interpreter. One can pass some options as a hash
62             # reference, with the following keys:
63             # - file: the filename to read funge code from (default: blank storage)
64             # - syntax: the tunings set (default: 'befunge98')
65             # - dims: the number of dimensions
66             # - ops: the Ops subclass used in this interpreter
67             # - storage: the Storage subclass used in this interpreter
68             # - wrapping: the Wrapping subclass used in this interpreter
69             #
70             # Usually, the "dims", "ops", "storage" and "wrapping" keys are left
71             # undefined, and are implied by the "syntax" key.
72             #
73             # Depending on the value of syntax will change the interpreter
74             # internals: set of allowed ops, storage implementation, wrapping. The
75             # following values are recognized for 'syntax' (with in order: the
76             # number of dimensions, the set of operation loaded, the storage
77             # implementation and the wrapping implementation):
78             #
79             # - unefunge98: 1, LBO:Unefunge98, LBS:Generic::AoA, LBW:LaheySpace
80             # - befunge98: 2, LBO:Befunge98, LBS:2D:Sparse, LBW:LaheySpace
81             # - trefunge98: 3, LBO:GenericFunge98, LBS:Generic::AoA, LBW:LaheySpace
82             # - 4funge98: 4, LBO:GenericFunge98, LBS:Generic::AoA, LBW:LaheySpace
83             # - 5funge98: 5, LBO:GenericFunge98, LBS:Generic::AoA, LBW:LaheySpace
84             # ...and so on.
85             #
86             #
87             # If none of those values suit your needs, you can pass the value
88             # 'custom' and in that case you're responsible for also giving
89             # appropriate values for the keys 'dims', 'ops', 'storage', 'wrapping'.
90             # Note that those values will be ignored for all syntax values beside
91             # 'custom'.
92             #
93             sub new {
94 92     92 1 3499 my ($class, $opts) = @_;
95              
96 92   100     800 $opts //= { dims => 2 };
97 92 100       647 unless(exists($$opts{syntax})) {
98 71   100     358 $$opts{dims} //= 2;
99 71 100       492 croak("If you pass a 'dims' attribute, it must be numeric.")
100             if $$opts{dims} =~ /\D/;
101 70         580 my %defaults = (
102             1 => 'unefunge98',
103             2 => 'befunge98',
104             3 => 'trefunge98',
105             );
106 70 100       396 if(exists($defaults{$$opts{dims}})) {
107 67         346 $$opts{syntax} = $defaults{$$opts{dims}};
108             } else {
109 3         18 $$opts{syntax} = $$opts{dims} . 'funge98';
110             }
111             }
112              
113             # select the classes to use, depending on the wanted syntax.
114 91         228 my $lbo = 'Language::Befunge::Ops::';
115 91         205 my $lbs = 'Language::Befunge::Storage::';
116 91         185 my $lbw = 'Language::Befunge::Wrapping::';
117 91         288 given ( $opts->{syntax} ) {
118 91         940 when ('unefunge98') {
119 7 100       32 $opts->{dims} = 1 unless defined $opts->{dims};
120 7 100       35 $opts->{ops} = $lbo . 'Unefunge98' unless defined $opts->{ops};
121 7 100       29 $opts->{storage} = $lbs . 'Generic::AoA' unless defined $opts->{storage};
122 7 100       58 $opts->{wrapping} = $lbw . 'LaheySpace' unless defined $opts->{wrapping};
123             }
124 84         258 when ('befunge98') {
125 68 100       311 $opts->{dims} = 2 unless defined $opts->{dims};
126 68 100       458 $opts->{ops} = $lbo . 'Befunge98' unless defined $opts->{ops};
127 68 100       408 $opts->{storage} = $lbs . '2D::Sparse' unless defined $opts->{storage};
128 68 100       692 $opts->{wrapping} = $lbw . 'LaheySpace' unless defined $opts->{wrapping};
129             }
130 16         36 when ('trefunge98') {
131 8 100       34 $opts->{dims} = 3 unless defined $opts->{dims};
132 8 100       43 $opts->{ops} = $lbo . 'GenericFunge98' unless defined $opts->{ops};
133 8 100       48 $opts->{storage} = $lbs . 'Generic::AoA' unless defined $opts->{storage};
134 8 100       75 $opts->{wrapping} = $lbw . 'LaheySpace' unless defined $opts->{wrapping};
135             }
136 8         40 when (/(\d+)funge98$/) { # accept values like "4funge98"
137 7 100       33 $opts->{dims} = $1 unless defined $opts->{dims};
138 7 100       33 $opts->{ops} = $lbo . 'GenericFunge98' unless defined $opts->{ops};
139 7 100       29 $opts->{storage} = $lbs . 'Generic::AoA' unless defined $opts->{storage};
140 7 100       55 $opts->{wrapping} = $lbw . 'LaheySpace' unless defined $opts->{wrapping};
141             }
142 1         2 default { croak "syntax '$opts->{syntax}' not recognized." }
  1         28  
143             }
144              
145             # load the classes (through UNIVERSAL::require)
146 90         1254 $opts->{ops}->use;
147 90         1348 $opts->{storage}->use;
148 90         1928 $opts->{wrapping}->use;
149              
150             # create the object
151 90         1208 my $wrapping = $opts->{wrapping}->new;
152 89         1108 my $self = {
153             dimensions => $opts->{dims},
154             storage => $opts->{storage}->new( $opts->{dims}, Wrapping => $wrapping ),
155             file => "STDIN",
156             _input => '',
157             params => [],
158             retval => 0,
159             curip => undef,
160             ops => $opts->{ops}->get_ops_map,
161             ips => [],
162             newips => [],
163             handprint => 'JQBF', # the official handprint
164             _wrapping => $wrapping,
165             };
166 86         672 bless $self, $class;
167              
168             # read the file if needed.
169 86 100       435 defined($opts->{file}) and $self->read_file( $opts->{file} );
170              
171             # return the object.
172 86         586 return $self;
173             }
174              
175              
176              
177              
178             # -- PUBLIC METHODS
179              
180             # - Utilities
181              
182              
183             #
184             # move_ip( $ip )
185             #
186             # Move $ip according to its delta on the storage. Spaces and comments
187             # (enclosed with semi-colons ';') are skipped silently.
188             #
189             sub move_ip {
190 2372     2372 1 3418 my ($self, $ip) = @_;
191              
192 2372         3765 my $storage = $self->get_storage;
193 2372         4890 $self->_move_ip_once($ip);
194 2372         5548 my $char;
195             my %seen_before;
196 2372         2695 MOVE: while (1) {
197             # sanity check
198 3116         5379 my $pos = $ip->get_position;
199 3116 100       14018 $self->abort("infinite loop")
200             if exists($seen_before{$pos});
201 3115         8585 $seen_before{$pos} = 1;
202 3115         15277 $char = $storage->get_char($pos);
203              
204             # skip spaces
205 3115 100       7945 if ( $char eq ' ' ) {
206 719         3339 $self->_move_ip_till( $ip, qr/ / ); # skip all spaces
207 718         2359 $self->_move_ip_once($ip); # skip last space
208 718         2092 redo MOVE;
209             }
210              
211             # skip comments
212 2396 100       4461 if ( $char eq ';' ) {
213 26         77 $self->_move_ip_once($ip); # skip comment ';'
214 26         164 $self->_move_ip_till( $ip, qr/[^;]/ ); # till just before matching ';'
215 26         100 $self->_move_ip_once($ip); # till matching ';'
216 26         88 $self->_move_ip_once($ip); # till just after matching ';'
217 26         84 redo MOVE;
218             }
219              
220 2370         7270 last MOVE;
221             }
222             }
223              
224              
225             #
226             # abort( reason )
227             #
228             # Abort the interpreter with the given reason, as well as the current
229             # file and coordinate of the offending instruction.
230             #
231             sub abort {
232 16     16 1 30 my $self = shift;
233 16         53 my $file = $self->get_file;
234 16         55 my $v = $self->get_curip->get_position;
235 16         305 croak "$file $v: ", @_;
236             }
237              
238              
239             #
240             # set_input( $string )
241             #
242             # Preload the input buffer with the given value.
243             #
244             sub set_input {
245 6     6 1 24 my ($self, $str) = @_;
246 6         121 $self->_set_input($str);
247             }
248              
249              
250             #
251             # get_input( )
252             #
253             # Fetch a character of input from the input buffer, or else, directly
254             # from stdin.
255             #
256              
257             sub get_input {
258 62     62 1 110 my $self = shift;
259 62 100       545 return substr($$self{_input}, 0, 1, '') if length $self->_get_input;
260 58         80 my $char;
261 58         40411 my $rv = sysread(STDIN, $char, 1);
262 58 100       343 return $char if length $char;
263 7         20 return undef;
264             }
265              
266              
267             # - Code and Data Storage
268              
269             #
270             # read_file( filename )
271             #
272             # Read a file (given as argument) and store its code.
273             #
274             # Side effect: clear the previous code.
275             #
276             sub read_file {
277 7     7 1 6471 my ($self, $file) = @_;
278              
279             # Fetch the code.
280 7         16 my $code;
281 7 100       3929 open BF, "<$file" or croak "$!";
282             {
283 6         18 local $/; # slurp mode.
  6         32  
284 6         2095 $code = ;
285             }
286 6         97 close BF;
287              
288             # Store code.
289 6         41 $self->set_file( $file );
290 6         28 $self->store_code( $code );
291             }
292              
293              
294             #
295             # store_code( code )
296             #
297             # Store the given code in the Lahey space.
298             #
299             # Side effect: clear the previous code.
300             #
301             sub store_code {
302 216     216 1 253676 my ($self, $code) = @_;
303 216         1333 debug( "Storing code\n" );
304 216         1311 $self->get_storage->clear;
305 216         1080 $self->get_storage->store( $code );
306             }
307              
308              
309             # - Run methods
310              
311              
312             #
313             # run_code( [params] )
314             #
315             # Run the current code. That is, create a new Instruction Pointer and
316             # move it around the code.
317             #
318             # Return the exit code of the program.
319             #
320             sub run_code {
321 201     201 1 276476 my $self = shift;
322 201         2072 $self->set_params( [ @_ ] );
323              
324             # Cosmetics.
325 201         1400 debug( "\n-= NEW RUN (".$self->get_file.") =-\n" );
326              
327             # Create the first Instruction Pointer.
328 201         2127 $self->set_ips( [ Language::Befunge::IP->new($$self{dimensions}) ] );
329 201         532 $self->set_retval(0);
330              
331             # Loop as long as there are IPs.
332 201         285 $self->next_tick while scalar @{ $self->get_ips };
  2904         12149  
333              
334             # Return the exit code.
335 193         1773 return $self->get_retval;
336             }
337              
338              
339             #
340             # next_tick( )
341             #
342             # Finish the current tick and stop just before the next tick.
343             #
344             sub next_tick {
345 2711     2711 1 3398 my $self = shift;
346              
347             # Cosmetics.
348 2711         7035 debug( "Tick!\n" );
349              
350             # Process the set of IPs.
351 2711         5577 $self->set_newips( [] );
352 2711         3304 $self->process_ip while $self->set_curip( shift @{ $self->get_ips } );
  5444         22891  
353              
354             # Copy the new ips.
355 2703         7718 $self->set_ips( $self->get_newips );
356             }
357              
358              
359             #
360             # process_ip( )
361             #
362             # Process the current ip.
363             #
364             sub process_ip {
365 2741     2741 1 3525 my ($self, $continue) = @_;
366 2741 50       8261 $continue = 1 unless defined $continue;
367 2741         4636 my $ip = $self->get_curip;
368              
369             # Fetch values for this IP.
370 2741         4682 my $v = $ip->get_position;
371 2741         8511 my $ord = $self->get_storage->get_value( $v );
372 2741         9568 my $char = $self->get_storage->get_char( $v );
373              
374             # Cosmetics.
375 2741         12094 debug( "#".$ip->get_id.":$v: $char (ord=$ord) Stack=(@{$ip->get_toss})\n" );
  2741         20508  
376              
377             # Check if we are in string-mode.
378 2741 100       7489 if ( $ip->get_string_mode ) {
379 385 100       944 if ( $char eq '"' ) {
    100          
380             # End of string-mode.
381 36         98 debug( "leaving string-mode\n" );
382 36         87 $ip->set_string_mode(0);
383              
384             } elsif ( $char eq ' ' ) {
385             # A serie of spaces, to be treated as one space.
386 8         30 debug( "string-mode: pushing char ' '\n" );
387 8         70 $self->_move_ip_till( $ip, qr/ / );
388 8         43 $ip->spush( $ord );
389              
390             } else {
391             # A banal character.
392 341         11020 debug( "string-mode: pushing char '$char'\n" );
393 341         959 $ip->spush( $ord );
394             }
395              
396             } else {
397 2356         6177 $self->_do_instruction($char);
398             }
399              
400 2733 50       21189 if ($continue) {
401             # Tick done for this IP, let's move it and push it in the
402             # set of non-terminated IPs.
403 2733 100       6108 if ( $ip->get_string_mode ) {
404 385         900 $self->_move_ip_once( $self->get_curip );
405             } else {
406 2348         5678 $self->move_ip( $self->get_curip );
407             }
408 2733 100       8442 push @{ $self->get_newips }, $ip unless $ip->get_end;
  2535         10467  
409             }
410             }
411              
412             #-- PRIVATE METHODS
413              
414             #
415             # $lbi->_do_instruction( $char );
416             #
417             # interpret instruction $char according to loaded ops map.
418             #
419             sub _do_instruction {
420 2436     2436   3340 my ($self, $char) = @_;
421              
422 2436 100       7008 if ( exists $self->get_ops->{$char} ) {
423             # regular instruction.
424 2422         4688 my $meth = $self->get_ops->{$char};
425 2422         8100 $meth->($self, $char);
426              
427             } else {
428             # not a regular instruction: reflect.
429 14         29 my $ord = ord($char);
430 14         72 debug( "the command value $ord (char='$char') is not implemented.\n");
431 14         71 $self->get_curip->dir_reverse;
432             }
433             }
434              
435              
436             #
437             # $lbi->_move_ip_once( $ip );
438             #
439             # move $ip one step further, according to its velocity. if $ip gets out
440             # of bounds, then a wrapping is performed (according to current
441             # interpreter wrapping implementation) on the ip.
442             #
443             sub _move_ip_once {
444 6334     6334   8175 my ($self, $ip) = @_;
445 6334         9558 my $storage = $self->get_storage;
446              
447             # fetch the current position of the ip.
448 6334         9012 my $v = $ip->get_position;
449 6334         8868 my $d = $ip->get_delta;
450              
451             # now, let's move the ip.
452 6334         27302 $v += $d;
453              
454 6334 100       19432 if ( $v->bounds_check($storage->min, $storage->max) ) {
455             # within bounds - store new position.
456 6113         18015 $ip->set_position( $v );
457             } else {
458             # wrap needed - this will update the position.
459 221         1309 $self->get_wrapping->wrap( $storage, $ip );
460             }
461             }
462              
463              
464             #
465             # _move_ip_till( $ip,regex )
466             #
467             # Move $ip according to its delta on the storage, as long as the pointed
468             # character match the supplied regex (a qr// object).
469             #
470             # Example: given the code C<;foobar;> (assuming the IP points on the
471             # first C<;>) and the regex C, the IP will move in order to
472             # point on the C.
473             #
474             sub _move_ip_till {
475 758     758   1080 my ($self, $ip, $re) = @_;
476 758         1268 my $storage = $self->get_storage;
477              
478 758         1345 my $orig = $ip->get_position;
479             # moving as long as we did not reach the condition.
480 758         2268 while ( $storage->get_char($ip->get_position) =~ $re ) {
481 1681         4374 $self->_move_ip_once($ip);
482 1681 100       8087 $self->abort("infinite loop")
483             if $ip->get_position == $orig;
484             }
485              
486             # we moved one char too far.
487 757         2690 $ip->dir_reverse;
488 757         2362 $self->_move_ip_once($ip);
489 757         2739 $ip->dir_reverse;
490             }
491              
492              
493             1;
494             __END__