File Coverage

blib/lib/Language/Befunge/Interpreter.pm
Criterion Covered Total %
statement 168 168 100.0
branch 78 80 97.5
condition 4 4 100.0
subroutine 21 21 100.0
pod 10 10 100.0
total 281 283 99.2


line stmt bran cond sub pod time code
1             #
2             # This file is part of Language-Befunge
3             #
4             # This software is copyright (c) 2003 by Jerome Quelin.
5             #
6             # This is free software; you can redistribute it and/or modify it under
7             # the same terms as the Perl 5 programming language system itself.
8             #
9 67     67   38263 use 5.010;
  67         152  
10 67     67   225 use strict;
  67         69  
  67         1076  
11 67     67   183 use warnings;
  67         72  
  67         2320  
12              
13             package Language::Befunge::Interpreter;
14             # ABSTRACT: an interpreter for Language::Befunge
15             $Language::Befunge::Interpreter::VERSION = '5.000';
16 67     67   210 use Carp;
  67         72  
  67         3897  
17 67     67   6224 use Language::Befunge::Debug;
  67         84  
  67         2437  
18 67     67   23728 use Language::Befunge::IP;
  67         122  
  67         1732  
19 67     67   27678 use UNIVERSAL::require;
  67         60888  
  67         478  
20              
21             # FIXME: wtf? always use get_/set_ or mutators, but not a mix of them!
22             use Class::XSAccessor
23 67         904 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 67     67   3846 };
  67         77  
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 2414 my ($class, $opts) = @_;
95              
96 92   100     538 $opts //= { dims => 2 };
97 92 100       396 unless(exists($$opts{syntax})) {
98 71   100     218 $$opts{dims} //= 2;
99             croak("If you pass a 'dims' attribute, it must be numeric.")
100 71 100       367 if $$opts{dims} =~ /\D/;
101 70         404 my %defaults = (
102             1 => 'unefunge98',
103             2 => 'befunge98',
104             3 => 'trefunge98',
105             );
106 70 100       250 if(exists($defaults{$$opts{dims}})) {
107 67         240 $$opts{syntax} = $defaults{$$opts{dims}};
108             } else {
109 3         12 $$opts{syntax} = $$opts{dims} . 'funge98';
110             }
111             }
112              
113             # select the classes to use, depending on the wanted syntax.
114 91         137 my $lbo = 'Language::Befunge::Ops::';
115 91         133 my $lbs = 'Language::Befunge::Storage::';
116 91         169 my $lbw = 'Language::Befunge::Wrapping::';
117 91 100       412 if ( $opts->{syntax} eq 'unefunge98' ) {
    100          
    100          
    100          
118 7 100       14 $opts->{dims} = 1 unless defined $opts->{dims};
119 7 100       20 $opts->{ops} = $lbo . 'Unefunge98' unless defined $opts->{ops};
120 7 100       16 $opts->{storage} = $lbs . 'Generic::AoA' unless defined $opts->{storage};
121 7 100       18 $opts->{wrapping} = $lbw . 'LaheySpace' unless defined $opts->{wrapping};
122             } elsif ( $opts->{syntax} eq 'befunge98' ) {
123 68 100       223 $opts->{dims} = 2 unless defined $opts->{dims};
124 68 100       332 $opts->{ops} = $lbo . 'Befunge98' unless defined $opts->{ops};
125 68 100       263 $opts->{storage} = $lbs . '2D::Sparse' unless defined $opts->{storage};
126 68 100       262 $opts->{wrapping} = $lbw . 'LaheySpace' unless defined $opts->{wrapping};
127             } elsif ( $opts->{syntax} eq 'trefunge98' ) {
128 8 100       20 $opts->{dims} = 3 unless defined $opts->{dims};
129 8 100       21 $opts->{ops} = $lbo . 'GenericFunge98' unless defined $opts->{ops};
130 8 100       20 $opts->{storage} = $lbs . 'Generic::AoA' unless defined $opts->{storage};
131 8 100       19 $opts->{wrapping} = $lbw . 'LaheySpace' unless defined $opts->{wrapping};
132             } elsif ( $opts->{syntax} =~ /(\d+)funge98$/ ) {
133 7 100       21 $opts->{dims} = $1 unless defined $opts->{dims};
134 7 100       24 $opts->{ops} = $lbo . 'GenericFunge98' unless defined $opts->{ops};
135 7 100       19 $opts->{storage} = $lbs . 'Generic::AoA' unless defined $opts->{storage};
136 7 100       20 $opts->{wrapping} = $lbw . 'LaheySpace' unless defined $opts->{wrapping};
137             } else {
138 1         20 croak "syntax '$opts->{syntax}' not recognized.";
139             }
140              
141             # load the classes (through UNIVERSAL::require)
142 90         718 $opts->{ops}->use;
143 90         740 $opts->{storage}->use;
144 90         673 $opts->{wrapping}->use;
145              
146             # create the object
147 90         722 my $wrapping = $opts->{wrapping}->new;
148             my $self = {
149             dimensions => $opts->{dims},
150             storage => $opts->{storage}->new( $opts->{dims}, Wrapping => $wrapping ),
151             file => "STDIN",
152             _input => '',
153             params => [],
154             retval => 0,
155             curip => undef,
156             ops => $opts->{ops}->get_ops_map,
157 89         518 ips => [],
158             newips => [],
159             handprint => 'JQBF', # the official handprint
160             _wrapping => $wrapping,
161             };
162 86         319 bless $self, $class;
163              
164             # read the file if needed.
165 86 100       316 defined($opts->{file}) and $self->read_file( $opts->{file} );
166              
167             # return the object.
168 86         327 return $self;
169             }
170              
171              
172              
173              
174             # -- PUBLIC METHODS
175              
176             # - Utilities
177              
178              
179             #
180             # move_ip( $ip )
181             #
182             # Move $ip according to its delta on the storage. Spaces and comments
183             # (enclosed with semi-colons ';') are skipped silently.
184             #
185             sub move_ip {
186 2372     2372 1 1939 my ($self, $ip) = @_;
187              
188 2372         2001 my $storage = $self->get_storage;
189 2372         2503 $self->_move_ip_once($ip);
190 2372         2626 my $char;
191             my %seen_before;
192 2372         1601 MOVE: while (1) {
193             # sanity check
194 3116         3021 my $pos = $ip->get_position;
195             $self->abort("infinite loop")
196 3116 100       4657 if exists($seen_before{$pos});
197 3115         4175 $seen_before{$pos} = 1;
198 3115         5124 $char = $storage->get_char($pos);
199              
200             # skip spaces
201 3115 100       4838 if ( $char eq ' ' ) {
202 719         1784 $self->_move_ip_till( $ip, qr/ / ); # skip all spaces
203 718         1283 $self->_move_ip_once($ip); # skip last space
204 718         1129 redo MOVE;
205             }
206              
207             # skip comments
208 2396 100       2802 if ( $char eq ';' ) {
209 26         37 $self->_move_ip_once($ip); # skip comment ';'
210 26         86 $self->_move_ip_till( $ip, qr/[^;]/ ); # till just before matching ';'
211 26         45 $self->_move_ip_once($ip); # till matching ';'
212 26         43 $self->_move_ip_once($ip); # till just after matching ';'
213 26         59 redo MOVE;
214             }
215              
216 2370         4003 last MOVE;
217             }
218             }
219              
220              
221             #
222             # abort( reason )
223             #
224             # Abort the interpreter with the given reason, as well as the current
225             # file and coordinate of the offending instruction.
226             #
227             sub abort {
228 16     16 1 15 my $self = shift;
229 16         39 my $file = $self->get_file;
230 16         39 my $v = $self->get_curip->get_position;
231 16         45 croak "$file $v: ", @_;
232             }
233              
234              
235             #
236             # set_input( $string )
237             #
238             # Preload the input buffer with the given value.
239             #
240             sub set_input {
241 6     6 1 15 my ($self, $str) = @_;
242 6         28 $self->_set_input($str);
243             }
244              
245              
246             #
247             # get_input( )
248             #
249             # Fetch a character of input from the input buffer, or else, directly
250             # from stdin.
251             #
252              
253             sub get_input {
254 62     62 1 70 my $self = shift;
255 62 100       234 return substr($$self{_input}, 0, 1, '') if length $self->_get_input;
256 58         47 my $char;
257 58         850 my $rv = sysread(STDIN, $char, 1);
258 58 100       201 return $char if length $char;
259 7         18 return undef;
260             }
261              
262              
263             # - Code and Data Storage
264              
265             #
266             # read_file( filename )
267             #
268             # Read a file (given as argument) and store its code.
269             #
270             # Side effect: clear the previous code.
271             #
272             sub read_file {
273 7     7 1 569 my ($self, $file) = @_;
274              
275             # Fetch the code.
276 7         5 my $code;
277 7 100       367 open BF, "<$file" or croak "$!";
278             {
279 6         10 local $/; # slurp mode.
  6         18  
280 6         97 $code = ;
281             }
282 6         35 close BF;
283              
284             # Store code.
285 6         19 $self->set_file( $file );
286 6         15 $self->store_code( $code );
287             }
288              
289              
290             #
291             # store_code( code )
292             #
293             # Store the given code in the Lahey space.
294             #
295             # Side effect: clear the previous code.
296             #
297             sub store_code {
298 216     216 1 96693 my ($self, $code) = @_;
299 216         523 debug( "Storing code\n" );
300 216         762 $self->get_storage->clear;
301 216         548 $self->get_storage->store( $code );
302             }
303              
304              
305             # - Run methods
306              
307              
308             #
309             # run_code( [params] )
310             #
311             # Run the current code. That is, create a new Instruction Pointer and
312             # move it around the code.
313             #
314             # Return the exit code of the program.
315             #
316             sub run_code {
317 201     201 1 99416 my $self = shift;
318 201         537 $self->set_params( [ @_ ] );
319              
320             # Cosmetics.
321 201         708 debug( "\n-= NEW RUN (".$self->get_file.") =-\n" );
322              
323             # Create the first Instruction Pointer.
324 201         1016 $self->set_ips( [ Language::Befunge::IP->new($$self{dimensions}) ] );
325 201         248 $self->set_retval(0);
326              
327             # Loop as long as there are IPs.
328 201         151 $self->next_tick while scalar @{ $self->get_ips };
  2919         5756  
329              
330             # Return the exit code.
331 193         833 return $self->get_retval;
332             }
333              
334              
335             #
336             # next_tick( )
337             #
338             # Finish the current tick and stop just before the next tick.
339             #
340             sub next_tick {
341 2726     2726 1 1873 my $self = shift;
342              
343             # Cosmetics.
344 2726         4270 debug( "Tick!\n" );
345              
346             # Process the set of IPs.
347 2726         2920 $self->set_newips( [] );
348 2726         1966 $self->process_ip while $self->set_curip( shift @{ $self->get_ips } );
  5474         12657  
349              
350             # Copy the new ips.
351 2718         3931 $self->set_ips( $self->get_newips );
352             }
353              
354              
355             #
356             # process_ip( )
357             #
358             # Process the current ip.
359             #
360             sub process_ip {
361 2756     2756 1 2290 my ($self, $continue) = @_;
362 2756 50       3802 $continue = 1 unless defined $continue;
363 2756         2428 my $ip = $self->get_curip;
364              
365             # Fetch values for this IP.
366 2756         2256 my $v = $ip->get_position;
367 2756         4752 my $ord = $self->get_storage->get_value( $v );
368 2756         4573 my $char = $self->get_storage->get_char( $v );
369              
370             # Cosmetics.
371 2756         5997 debug( "#".$ip->get_id.":$v: $char (ord=$ord) Stack=(@{$ip->get_toss})\n" );
  2756         10167  
372              
373             # Check if we are in string-mode.
374 2756 100       4028 if ( $ip->get_string_mode ) {
375 400 100       597 if ( $char eq '"' ) {
    100          
376             # End of string-mode.
377 36         62 debug( "leaving string-mode\n" );
378 36         56 $ip->set_string_mode(0);
379              
380             } elsif ( $char eq ' ' ) {
381             # A serie of spaces, to be treated as one space.
382 8         18 debug( "string-mode: pushing char ' '\n" );
383 8         38 $self->_move_ip_till( $ip, qr/ / );
384 8         27 $ip->spush( $ord );
385              
386             } else {
387             # A banal character.
388 356         587 debug( "string-mode: pushing char '$char'\n" );
389 356         594 $ip->spush( $ord );
390             }
391              
392             } else {
393 2356         2726 $self->_do_instruction($char);
394             }
395              
396 2748 50       4822 if ($continue) {
397             # Tick done for this IP, let's move it and push it in the
398             # set of non-terminated IPs.
399 2748 100       3545 if ( $ip->get_string_mode ) {
400 400         532 $self->_move_ip_once( $self->get_curip );
401             } else {
402 2348         3276 $self->move_ip( $self->get_curip );
403             }
404 2748 100       4892 push @{ $self->get_newips }, $ip unless $ip->get_end;
  2550         5726  
405             }
406             }
407              
408             #-- PRIVATE METHODS
409              
410             #
411             # $lbi->_do_instruction( $char );
412             #
413             # interpret instruction $char according to loaded ops map.
414             #
415             sub _do_instruction {
416 2436     2436   2038 my ($self, $char) = @_;
417              
418 2436 100       3633 if ( exists $self->get_ops->{$char} ) {
419             # regular instruction.
420 2422         2355 my $meth = $self->get_ops->{$char};
421 2422         4435 $meth->($self, $char);
422              
423             } else {
424             # not a regular instruction: reflect.
425 14         18 my $ord = ord($char);
426 14         40 debug( "the command value $ord (char='$char') is not implemented.\n");
427 14         37 $self->get_curip->dir_reverse;
428             }
429             }
430              
431              
432             #
433             # $lbi->_move_ip_once( $ip );
434             #
435             # move $ip one step further, according to its velocity. if $ip gets out
436             # of bounds, then a wrapping is performed (according to current
437             # interpreter wrapping implementation) on the ip.
438             #
439             sub _move_ip_once {
440 6349     6349   4599 my ($self, $ip) = @_;
441 6349         5186 my $storage = $self->get_storage;
442              
443             # fetch the current position of the ip.
444 6349         5074 my $v = $ip->get_position;
445 6349         5063 my $d = $ip->get_delta;
446              
447             # now, let's move the ip.
448 6349         9325 $v += $d;
449              
450 6349 100       10607 if ( $v->bounds_check($storage->min, $storage->max) ) {
451             # within bounds - store new position.
452 6128         9216 $ip->set_position( $v );
453             } else {
454             # wrap needed - this will update the position.
455 221         586 $self->get_wrapping->wrap( $storage, $ip );
456             }
457             }
458              
459              
460             #
461             # _move_ip_till( $ip,regex )
462             #
463             # Move $ip according to its delta on the storage, as long as the pointed
464             # character match the supplied regex (a qr// object).
465             #
466             # Example: given the code C<;foobar;> (assuming the IP points on the
467             # first C<;>) and the regex C, the IP will move in order to
468             # point on the C.
469             #
470             sub _move_ip_till {
471 758     758   757 my ($self, $ip, $re) = @_;
472 758         764 my $storage = $self->get_storage;
473              
474 758         638 my $orig = $ip->get_position;
475             # moving as long as we did not reach the condition.
476 758         1383 while ( $storage->get_char($ip->get_position) =~ $re ) {
477 1681         2094 $self->_move_ip_once($ip);
478 1681 100       3739 $self->abort("infinite loop")
479             if $ip->get_position == $orig;
480             }
481              
482             # we moved one char too far.
483 757         1439 $ip->dir_reverse;
484 757         963 $self->_move_ip_once($ip);
485 757         1351 $ip->dir_reverse;
486             }
487              
488              
489             1;
490              
491             __END__