2 # This file is part of Language::Befunge.
3 # Copyright (c) 2001-2008 Jerome Quelin, all rights reserved.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the same terms as Perl itself.
10 package Language
::Befunge
::Interpreter
;
17 use Language
::Befunge
::IP
;
18 use UNIVERSAL
::require;
20 use base
qw{ Class
::Accessor
::Fast
};
21 __PACKAGE__
->mk_accessors( qw{ storage _wrapping input
} );
23 # Public variables of the module.
31 # my $interpreter = LBI->new( $opts )
33 # Create a new funge interpreter. One can pass some options as a hash
34 # reference, with the following keys:
35 # - file: the filename to read funge code from (default: blank storage)
36 # - syntax: the tunings set (default: 'befunge98')
37 # - dims: the number of dimensions
38 # - ops: the Ops subclass used in this interpreter
39 # - storage: the Storage subclass used in this interpreter
40 # - wrapping: the Wrapping subclass used in this interpreter
42 # Usually, the "dims", "ops", "storage" and "wrapping" keys are left
43 # undefined, and are implied by the "syntax" key.
45 # Depending on the value of syntax will change the interpreter
46 # internals: set of allowed ops, storage implementation, wrapping. The
47 # following values are recognized for 'syntax' (with in order: the
48 # number of dimensions, the set of operation loaded, the storage
49 # implementation and the wrapping implementation):
51 # - unefunge98: 1, LBO:Unefunge98, LBS:Generic::AoA, LBW:LaheySpace
52 # - befunge98: 2, LBO:Befunge98, LBS:2D:Sparse, LBW:LaheySpace
53 # - trefunge98: 3, LBO:GenericFunge98, LBS:Generic::AoA, LBW:LaheySpace
54 # - 4funge98: 4, LBO:GenericFunge98, LBS:Generic::AoA, LBW:LaheySpace
55 # - 5funge98: 5, LBO:GenericFunge98, LBS:Generic::AoA, LBW:LaheySpace
59 # If none of those values suit your needs, you can pass the value
60 # 'custom' and in that case you're responsible for also giving
61 # appropriate values for the keys 'dims', 'ops', 'storage', 'wrapping'.
62 # Note that those values will be ignored for all syntax values beside
66 my ($class, $opts) = @_;
68 $opts //= { dims
=> 2 };
69 unless(exists($$opts{syntax
})) {
71 croak
("If you pass a 'dims' attribute, it must be numeric.")
72 if $$opts{dims
} =~ /\D/;
78 if(exists($defaults{$$opts{dims
}})) {
79 $$opts{syntax
} = $defaults{$$opts{dims
}};
81 $$opts{syntax
} = $$opts{dims
} . 'funge98';
85 # select the classes to use, depending on the wanted syntax.
86 my $lbo = 'Language::Befunge::Ops::';
87 my $lbs = 'Language::Befunge::Storage::';
88 my $lbw = 'Language::Befunge::Wrapping::';
89 given ( $opts->{syntax
} ) {
91 $opts->{dims
} = 1 unless defined $opts->{dims
};
92 $opts->{ops
} = $lbo . 'Unefunge98' unless defined $opts->{ops
};
93 $opts->{storage
} = $lbs . 'Generic::AoA' unless defined $opts->{storage
};
94 $opts->{wrapping
} = $lbw . 'LaheySpace' unless defined $opts->{wrapping
};
97 $opts->{dims
} = 2 unless defined $opts->{dims
};
98 $opts->{ops
} = $lbo . 'Befunge98' unless defined $opts->{ops
};
99 $opts->{storage
} = $lbs . '2D::Sparse' unless defined $opts->{storage
};
100 $opts->{wrapping
} = $lbw . 'LaheySpace' unless defined $opts->{wrapping
};
102 when ('trefunge98') {
103 $opts->{dims
} = 3 unless defined $opts->{dims
};
104 $opts->{ops
} = $lbo . 'GenericFunge98' unless defined $opts->{ops
};
105 $opts->{storage
} = $lbs . 'Generic::AoA' unless defined $opts->{storage
};
106 $opts->{wrapping
} = $lbw . 'LaheySpace' unless defined $opts->{wrapping
};
108 when (/(\d+)funge98$/) { # accept values like "4funge98"
109 $opts->{dims
} = $1 unless defined $opts->{dims
};
110 $opts->{ops
} = $lbo . 'GenericFunge98' unless defined $opts->{ops
};
111 $opts->{storage
} = $lbs . 'Generic::AoA' unless defined $opts->{storage
};
112 $opts->{wrapping
} = $lbw . 'LaheySpace' unless defined $opts->{wrapping
};
114 default { croak
"syntax '$opts->{syntax}' not recognized." }
117 # load the classes (through UNIVERSAL::require)
119 $opts->{storage
}->use;
120 $opts->{wrapping
}->use;
123 my $wrapping = $opts->{wrapping
}->new;
125 dimensions
=> $opts->{dims
},
126 storage
=> $opts->{storage
}->new( $opts->{dims
}, Wrapping
=> $wrapping ),
133 ops
=> $opts->{ops
}->get_ops_map,
136 handprint
=> 'JQBF', # the official handprint
137 _wrapping
=> $wrapping,
141 # read the file if needed.
142 defined($opts->{file
}) and $self->read_file( $opts->{file
} );
152 # The following is a list of attributes of a Language::Befunge
153 # object. For each of them, a method C<get_foobar> and C<set_foobar>
154 # exists, which does what you can imagine - and if you can't, then i
155 # wonder why you are reading this! :-)
157 # get_curip() / set_curip()
158 # the current Instruction Pointer processed (a L::B::IP object)
160 # get_DEBUG() / set_DEBUG()
161 # wether the interpreter should output debug messages (a boolean)
163 # get_dimensions() / set_dimensions()
164 # the number of dimensions this interpreter works in.
166 # get_file() / set_file()
167 # the script filename (a string)
169 # get_handprint() / set_handprint()
170 # the handprint of the interpreter
172 # get_ips() / set_ips()
173 # the current set of IPs travelling in the Lahey space (an array
176 # get_newips() / set_newips()
177 # the set of IPs that B<will> travel in the Lahey space B<after> the
178 # current tick (an array reference)
180 # get_ops() / set_ops()
181 # the current supported operations set.
183 # get_params() / set_params()
184 # the parameters of the script (an array reference)
186 # get_retval() / set_retval()
187 # the current return value of the interpreter (an integer)
190 my @attrs = qw
[ dimensions file params retval DEBUG curip ips newips ops handprint
];
191 foreach my $attr ( @attrs ) {
192 my $code = qq[ sub get_
$attr { return \
$_[0]->{$attr} } ];
193 $code .= qq[ sub set_
$attr { \
$_[0]->{$attr} = \
$_[1] } ];
207 # Move $ip according to its delta on the storage. Spaces and comments
208 # (enclosed with semi-colons ';') are skipped silently.
211 my ($self, $ip) = @_;
213 my $storage = $self->storage;
214 my $orig = $ip->get_position;
217 # moving one step beyond...
218 $self->_move_ip_once($ip);
219 my $pos = $ip->get_position;
220 $self->abort("infinite loop") if $pos == $orig;
223 $char = $storage->get_char($pos);
224 if ( $char eq ';' ) {
225 $self->_move_ip_once($ip); # skip comment ';'
226 $self->_move_ip_till( $ip, qr/[^;]/ ); # till just before matching ';'
227 $self->_move_ip_once($ip); # till matching ';'
228 $self->_move_ip_once($ip); # till just after matching ';'
230 } while ( $char eq ' ' );
237 # Abort the interpreter with the given reason, as well as the current
238 # file and coordinate of the offending instruction.
242 my $file = $self->get_file;
243 my $v = $self->get_curip->get_position;
244 croak
"$file $v: ", @_;
251 # Issue a warning if the interpreter has DEBUG enabled.
255 $self->get_DEBUG or return;
261 # set_input( $string )
263 # Preload the input buffer with the given value.
266 my ($self, $str) = @_;
274 # Fetch a character of input from the input buffer, or else, directly
280 return substr($$self{input
}, 0, 1, '') if length $self->input;
282 my $rv = sysread(STDIN
, $char, 1);
283 return $char if length $char;
288 # - Code and Data Storage
291 # read_file( filename )
293 # Read a file (given as argument) and store its code.
295 # Side effect: clear the previous code.
298 my ($self, $file) = @_;
302 open BF
, "<$file" or croak
"$!";
304 local $/; # slurp mode.
310 $self->set_file( $file );
311 $self->store_code( $code );
318 # Store the given code in the Lahey space.
320 # Side effect: clear the previous code.
323 my ($self, $code) = @_;
324 $self->debug( "Storing code\n" );
325 $self->storage->clear;
326 $self->storage->store( $code );
334 # run_code( [params] )
336 # Run the current code. That is, create a new Instruction Pointer and
337 # move it around the code.
339 # Return the exit code of the program.
343 $self->set_params( [ @_ ] );
346 $self->debug( "\n-= NEW RUN (".$self->get_file.") =-\n" );
348 # Create the first Instruction Pointer.
349 $self->set_ips( [ Language
::Befunge
::IP
->new($$self{dimensions
}) ] );
350 $self->set_retval(0);
352 # Loop as long as there are IPs.
353 $self->next_tick while scalar @
{ $self->get_ips };
355 # Return the exit code.
356 return $self->get_retval;
363 # Finish the current tick and stop just before the next tick.
369 $self->debug( "Tick!\n" );
371 # Process the set of IPs.
372 $self->set_newips( [] );
373 $self->process_ip while $self->set_curip( shift @
{ $self->get_ips } );
376 $self->set_ips( $self->get_newips );
383 # Process the current ip.
386 my ($self, $continue) = @_;
387 $continue = 1 unless defined $continue;
388 my $ip = $self->get_curip;
390 # Fetch values for this IP.
391 my $v = $ip->get_position;
392 my $ord = $self->storage->get_value( $v );
393 my $char = $self->storage->get_char( $v );
396 $self->debug( "#".$ip->get_id.":$v: $char (ord=$ord) Stack=(@{$ip->get_toss})\n" );
398 # Check if we are in string-mode.
399 if ( $ip->get_string_mode ) {
400 if ( $char eq '"' ) {
401 # End of string-mode.
402 $self->debug( "leaving string-mode\n" );
403 $ip->set_string_mode(0);
405 } elsif ( $char eq ' ' ) {
406 # A serie of spaces, to be treated as one space.
407 $self->debug( "string-mode: pushing char ' '\n" );
408 $self->_move_ip_till( $ip, qr/ / );
413 $self->debug( "string-mode: pushing char '$char'\n" );
418 $self->_do_instruction($char);
422 # Tick done for this IP, let's move it and push it in the
423 # set of non-terminated IPs.
424 if ( $ip->get_string_mode ) {
425 $self->_move_ip_once( $self->get_curip );
427 $self->move_ip( $self->get_curip );
429 push @
{ $self->get_newips }, $ip unless $ip->get_end;
436 # $lbi->_do_instruction( $char );
438 # interpret instruction $char according to loaded ops map.
440 sub _do_instruction
{
441 my ($self, $char) = @_;
443 if ( exists $self->get_ops->{$char} ) {
444 # regular instruction.
445 my $meth = $self->get_ops->{$char};
449 # not a regular instruction: reflect.
450 my $ord = ord($char);
451 $self->debug( "the command value $ord (char='$char') is not implemented.\n");
452 $self->get_curip->dir_reverse;
458 # $lbi->_move_ip_once( $ip );
460 # move $ip one step further, according to its velocity. if $ip gets out
461 # of bounds, then a wrapping is performed (according to current
462 # interpreter wrapping implementation) on the ip.
465 my ($self, $ip) = @_;
466 my $storage = $self->storage;
468 # fetch the current position of the ip.
469 my $v = $ip->get_position;
470 my $d = $ip->get_delta;
472 # now, let's move the ip.
475 if ( $v->bounds_check($storage->min, $storage->max) ) {
476 # within bounds - store new position.
477 $ip->set_position( $v );
479 # wrap needed - this will update the position.
480 $self->_wrapping->wrap( $storage, $ip );
486 # _move_ip_till( $ip,regex )
488 # Move $ip according to its delta on the storage, as long as the pointed
489 # character match the supplied regex (a qr// object).
491 # Example: given the code C<;foobar;> (assuming the IP points on the
492 # first C<;>) and the regex C<qr/[^;]/>, the IP will move in order to
496 my ($self, $ip, $re) = @_;
497 my $storage = $self->storage;
499 my $orig = $ip->get_position;
500 # moving as long as we did not reach the condition.
501 while ( $storage->get_char($ip->get_position) =~ $re ) {
502 $self->_move_ip_once($ip);
503 $self->abort("infinite loop")
504 if $ip->get_position == $orig;
507 # we moved one char too far.
509 $self->_move_ip_once($ip);
519 =head2 new( [filename, ] [ Key => Value, ... ] )
521 Create a new Befunge interpreter. As an optional first argument, you
522 can pass it a filename to read Funge code from (default: blank
523 torus). All other arguments are key=>value pairs. The following
524 keys are accepted, with their default values shown:
527 Syntax => 'befunge98',
528 Storage => 'laheyspace'
532 The following is a list of attributes of a Language::Befunge
533 object. For each of them, a method C<get_foobar> and C<set_foobar>
534 exists, which does what you can imagine - and if you can't, then i
535 wonder why you are reading this! :-)
539 =item get_curip() / set_curip()
541 the current Instruction Pointer processed (a L::B::IP object)
543 =item get_DEBUG() / set_DEBUG()
545 wether the interpreter should output debug messages (a boolean)
547 =item get_dimensions() / set_dimensions()
549 the number of dimensions this interpreter works in.
551 =item get_file() / set_file()
553 the script filename (a string)
555 =item get_handprint() / set_handprint()
557 the handprint of the interpreter
559 =item get_ips() / set_ips()
561 the current set of IPs travelling in the Lahey space (an array
564 =item get_newips() / set_newips()
566 the set of IPs that B<will> travel in the Lahey space B<after> the
567 current tick (an array reference)
569 =item get_ops() / set_ops()
571 the current supported operations set.
573 =item get_params() / set_params()
575 the parameters of the script (an array reference)
577 =item get_retval() / set_retval()
579 the current return value of the interpreter (an integer)
584 =head1 PUBLIC METHODS
590 =item move_ip( $ip [, $regex] )
592 Move the C<$ip> according to its delta on the storage.
594 If C<$regex> ( a C<qr//> object ) is specified, then C<$ip> will move as
595 long as the pointed character match the supplied regex.
597 Example: given the code C<;foobar;> (assuming the IP points on the
598 first C<;>) and the regex C<qr/[^;]/>, the IP will move in order to
602 =item abort( reason )
604 Abort the interpreter with the given reason, as well as the current
605 file and coordinate of the offending instruction.
610 Issue a warning if the interpreter has DEBUG enabled.
613 =item set_input( $string )
615 Preload the input buffer with the given value.
620 Fetch a character of input from the input buffer, or else, directly
628 =head2 Code and Data Storage
632 =item read_file( filename )
634 Read a file (given as argument) and store its code.
636 Side effect: clear the previous code.
639 =item store_code( code )
641 Store the given code in the Lahey space.
643 Side effect: clear the previous code.
654 =item run_code( [params] )
656 Run the current code. That is, create a new Instruction Pointer and
657 move it around the code.
659 Return the exit code of the program.
664 Finish the current tick and stop just before the next tick.
669 Process the current ip.
681 Write standard libraries.
688 Although this module comes with a full set of tests, maybe there are
689 subtle bugs - or maybe even I misinterpreted the Funge-98
690 specs. Please report them to me.
692 There are some bugs anyway, but they come from the specs:
698 About the 18th cell pushed by the C<y> instruction: Funge specs just
699 tell to push onto the stack the size of the stacks, but nothing is
700 said about how user will retrieve the number of stacks.
704 About the load semantics. Once a library is loaded, the interpreter is
705 to put onto the TOSS the fingerprint of the just-loaded library. But
706 nothing is said if the fingerprint is bigger than the maximum cell
707 width (here, 4 bytes). This means that libraries can't have a name
708 bigger than C<0x80000000>, ie, more than four letters with the first
709 one smaller than C<P> (C<chr(80)>).
711 Since perl is not so rigid, one can build libraries with more than
712 four letters, but perl will issue a warning about non-portability of
713 numbers greater than C<0xffffffff>.
718 =head1 ACKNOWLEDGEMENTS
720 I would like to thank Chris Pressey, creator of Befunge, who gave a
721 whole new dimension to both coding and obfuscating.
731 Jerome Quelin, E<lt>jquelin@cpan.orgE<gt>
733 Development is discussed on E<lt>language-befunge@mongueurs.netE<gt>
736 =head1 COPYRIGHT & LICENSE
738 Copyright (c) 2001-2008 Jerome Quelin, all rights reserved.
740 This program is free software; you can redistribute it and/or modify
741 it under the same terms as Perl itself.