| Language-Befunge documentation | Contained in the Language-Befunge distribution. |
Create a new Befunge interpreter. As an optional first argument, you can pass it a filename to read Funge code from (default: blank torus). All other arguments are key=>value pairs. The following keys are accepted, with their default values shown:
Dimensions => 2,
Syntax => 'befunge98',
Storage => 'laheyspace'
The following is a list of attributes of a Language::Befunge
object. For each of them, a method get_foobar and set_foobar
exists, which does what you can imagine - and if you can't, then i
wonder why you are reading this! :-)
the current Instruction Pointer processed (a L::B::IP object)
the number of dimensions this interpreter works in.
the script filename (a string)
the handprint of the interpreter
the current set of IPs travelling in the Lahey space (an array reference)
the set of IPs that will travel in the Lahey space after the current tick (an array reference)
the current supported operations set.
the parameters of the script (an array reference)
the current return value of the interpreter (an integer)
the LB::Storage object containing the playfield.
the LB::Wrapping object driving wrapping policy. Private.
Move the $ip according to its delta on the storage.
If $regex ( a qr// object ) is specified, then $ip will move as
long as the pointed character match the supplied regex.
Example: given the code ;foobar; (assuming the IP points on the
first ;) and the regex qr/[^;]/, the IP will move in order to
point on the r.
Abort the interpreter with the given reason, as well as the current file and coordinate of the offending instruction.
Preload the input buffer with the given value.
Fetch a character of input from the input buffer, or else, directly from stdin.
Read a file (given as argument) and store its code.
Side effect: clear the previous code.
Store the given code in the Lahey space.
Side effect: clear the previous code.
Run the current code. That is, create a new Instruction Pointer and move it around the code.
Return the exit code of the program.
Finish the current tick and stop just before the next tick.
Process the current ip.
Write standard libraries.
Although this module comes with a full set of tests, maybe there are subtle bugs - or maybe even I misinterpreted the Funge-98 specs. Please report them to me.
There are some bugs anyway, but they come from the specs:
About the 18th cell pushed by the y instruction: Funge specs just
tell to push onto the stack the size of the stacks, but nothing is
said about how user will retrieve the number of stacks.
About the load semantics. Once a library is loaded, the interpreter is
to put onto the TOSS the fingerprint of the just-loaded library. But
nothing is said if the fingerprint is bigger than the maximum cell
width (here, 4 bytes). This means that libraries can't have a name
bigger than 0x80000000, ie, more than four letters with the first
one smaller than P (chr(80)).
Since perl is not so rigid, one can build libraries with more than
four letters, but perl will issue a warning about non-portability of
numbers greater than 0xffffffff.
I would like to thank Chris Pressey, creator of Befunge, who gave a whole new dimension to both coding and obfuscating.
Jerome Quelin, <jquelin@cpan.org>
Development is discussed on <language-befunge@mongueurs.net>
Copyright (c) 2001-2009 Jerome Quelin, all rights reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| Language-Befunge documentation | Contained in the Language-Befunge distribution. |
# # This file is part of Language::Befunge. # Copyright (c) 2001-2009 Jerome Quelin, all rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the same terms as Perl itself. # # package Language::Befunge::Interpreter; use 5.010; use strict; use warnings; use Carp; use Language::Befunge::Debug; use Language::Befunge::IP; use UNIVERSAL::require; # FIXME: wtf? always use get_/set_ or mutators, but not a mix of them! use Class::XSAccessor getters => { get_dimensions => 'dimensions', get_file => 'file', get_params => 'params', get_retval => 'retval', get_storage => 'storage', get_curip => 'curip', get_ips => 'ips', get_newips => 'newips', get_ops => 'ops', get_handprint => 'handprint', get_wrapping => '_wrapping', _get_input => '_input', }, setters => { set_dimensions => 'dimensions', set_file => 'file', set_params => 'params', set_retval => 'retval', set_curip => 'curip', set_ips => 'ips', set_newips => 'newips', set_ops => 'ops', set_handprint => 'handprint', _set_input => '_input', }; # Public variables of the module. $| = 1; # -- CONSTRUCTOR # # my $interpreter = LBI->new( $opts ) # # Create a new funge interpreter. One can pass some options as a hash # reference, with the following keys: # - file: the filename to read funge code from (default: blank storage) # - syntax: the tunings set (default: 'befunge98') # - dims: the number of dimensions # - ops: the Ops subclass used in this interpreter # - storage: the Storage subclass used in this interpreter # - wrapping: the Wrapping subclass used in this interpreter # # Usually, the "dims", "ops", "storage" and "wrapping" keys are left # undefined, and are implied by the "syntax" key. # # Depending on the value of syntax will change the interpreter # internals: set of allowed ops, storage implementation, wrapping. The # following values are recognized for 'syntax' (with in order: the # number of dimensions, the set of operation loaded, the storage # implementation and the wrapping implementation): # # - unefunge98: 1, LBO:Unefunge98, LBS:Generic::AoA, LBW:LaheySpace # - befunge98: 2, LBO:Befunge98, LBS:2D:Sparse, LBW:LaheySpace # - trefunge98: 3, LBO:GenericFunge98, LBS:Generic::AoA, LBW:LaheySpace # - 4funge98: 4, LBO:GenericFunge98, LBS:Generic::AoA, LBW:LaheySpace # - 5funge98: 5, LBO:GenericFunge98, LBS:Generic::AoA, LBW:LaheySpace # ...and so on. # # # If none of those values suit your needs, you can pass the value # 'custom' and in that case you're responsible for also giving # appropriate values for the keys 'dims', 'ops', 'storage', 'wrapping'. # Note that those values will be ignored for all syntax values beside # 'custom'. # sub new { my ($class, $opts) = @_; $opts //= { dims => 2 }; unless(exists($$opts{syntax})) { $$opts{dims} //= 2; croak("If you pass a 'dims' attribute, it must be numeric.") if $$opts{dims} =~ /\D/; my %defaults = ( 1 => 'unefunge98', 2 => 'befunge98', 3 => 'trefunge98', ); if(exists($defaults{$$opts{dims}})) { $$opts{syntax} = $defaults{$$opts{dims}}; } else { $$opts{syntax} = $$opts{dims} . 'funge98'; } } # select the classes to use, depending on the wanted syntax. my $lbo = 'Language::Befunge::Ops::'; my $lbs = 'Language::Befunge::Storage::'; my $lbw = 'Language::Befunge::Wrapping::'; given ( $opts->{syntax} ) { when ('unefunge98') { $opts->{dims} = 1 unless defined $opts->{dims}; $opts->{ops} = $lbo . 'Unefunge98' unless defined $opts->{ops}; $opts->{storage} = $lbs . 'Generic::AoA' unless defined $opts->{storage}; $opts->{wrapping} = $lbw . 'LaheySpace' unless defined $opts->{wrapping}; } when ('befunge98') { $opts->{dims} = 2 unless defined $opts->{dims}; $opts->{ops} = $lbo . 'Befunge98' unless defined $opts->{ops}; $opts->{storage} = $lbs . '2D::Sparse' unless defined $opts->{storage}; $opts->{wrapping} = $lbw . 'LaheySpace' unless defined $opts->{wrapping}; } when ('trefunge98') { $opts->{dims} = 3 unless defined $opts->{dims}; $opts->{ops} = $lbo . 'GenericFunge98' unless defined $opts->{ops}; $opts->{storage} = $lbs . 'Generic::AoA' unless defined $opts->{storage}; $opts->{wrapping} = $lbw . 'LaheySpace' unless defined $opts->{wrapping}; } when (/(\d+)funge98$/) { # accept values like "4funge98" $opts->{dims} = $1 unless defined $opts->{dims}; $opts->{ops} = $lbo . 'GenericFunge98' unless defined $opts->{ops}; $opts->{storage} = $lbs . 'Generic::AoA' unless defined $opts->{storage}; $opts->{wrapping} = $lbw . 'LaheySpace' unless defined $opts->{wrapping}; } default { croak "syntax '$opts->{syntax}' not recognized." } } # load the classes (through UNIVERSAL::require) $opts->{ops}->use; $opts->{storage}->use; $opts->{wrapping}->use; # create the object my $wrapping = $opts->{wrapping}->new; my $self = { dimensions => $opts->{dims}, storage => $opts->{storage}->new( $opts->{dims}, Wrapping => $wrapping ), file => "STDIN", _input => '', params => [], retval => 0, curip => undef, ops => $opts->{ops}->get_ops_map, ips => [], newips => [], handprint => 'JQBF', # the official handprint _wrapping => $wrapping, }; bless $self, $class; # read the file if needed. defined($opts->{file}) and $self->read_file( $opts->{file} ); # return the object. return $self; } # -- PUBLIC METHODS # - Utilities # # move_ip( $ip ) # # Move $ip according to its delta on the storage. Spaces and comments # (enclosed with semi-colons ';') are skipped silently. # sub move_ip { my ($self, $ip) = @_; my $storage = $self->get_storage; $self->_move_ip_once($ip); my $char; my %seen_before; MOVE: while (1) { # sanity check my $pos = $ip->get_position; $self->abort("infinite loop") if exists($seen_before{$pos}); $seen_before{$pos} = 1; $char = $storage->get_char($pos); # skip spaces if ( $char eq ' ' ) { $self->_move_ip_till( $ip, qr/ / ); # skip all spaces $self->_move_ip_once($ip); # skip last space redo MOVE; } # skip comments if ( $char eq ';' ) { $self->_move_ip_once($ip); # skip comment ';' $self->_move_ip_till( $ip, qr/[^;]/ ); # till just before matching ';' $self->_move_ip_once($ip); # till matching ';' $self->_move_ip_once($ip); # till just after matching ';' redo MOVE; } last MOVE; } } # # abort( reason ) # # Abort the interpreter with the given reason, as well as the current # file and coordinate of the offending instruction. # sub abort { my $self = shift; my $file = $self->get_file; my $v = $self->get_curip->get_position; croak "$file $v: ", @_; } # # set_input( $string ) # # Preload the input buffer with the given value. # sub set_input { my ($self, $str) = @_; $self->_set_input($str); } # # get_input( ) # # Fetch a character of input from the input buffer, or else, directly # from stdin. # sub get_input { my $self = shift; return substr($$self{_input}, 0, 1, '') if length $self->_get_input; my $char; my $rv = sysread(STDIN, $char, 1); return $char if length $char; return undef; } # - Code and Data Storage # # read_file( filename ) # # Read a file (given as argument) and store its code. # # Side effect: clear the previous code. # sub read_file { my ($self, $file) = @_; # Fetch the code. my $code; open BF, "<$file" or croak "$!"; { local $/; # slurp mode. $code = <BF>; } close BF; # Store code. $self->set_file( $file ); $self->store_code( $code ); } # # store_code( code ) # # Store the given code in the Lahey space. # # Side effect: clear the previous code. # sub store_code { my ($self, $code) = @_; debug( "Storing code\n" ); $self->get_storage->clear; $self->get_storage->store( $code ); } # - Run methods # # run_code( [params] ) # # Run the current code. That is, create a new Instruction Pointer and # move it around the code. # # Return the exit code of the program. # sub run_code { my $self = shift; $self->set_params( [ @_ ] ); # Cosmetics. debug( "\n-= NEW RUN (".$self->get_file.") =-\n" ); # Create the first Instruction Pointer. $self->set_ips( [ Language::Befunge::IP->new($$self{dimensions}) ] ); $self->set_retval(0); # Loop as long as there are IPs. $self->next_tick while scalar @{ $self->get_ips }; # Return the exit code. return $self->get_retval; } # # next_tick( ) # # Finish the current tick and stop just before the next tick. # sub next_tick { my $self = shift; # Cosmetics. debug( "Tick!\n" ); # Process the set of IPs. $self->set_newips( [] ); $self->process_ip while $self->set_curip( shift @{ $self->get_ips } ); # Copy the new ips. $self->set_ips( $self->get_newips ); } # # process_ip( ) # # Process the current ip. # sub process_ip { my ($self, $continue) = @_; $continue = 1 unless defined $continue; my $ip = $self->get_curip; # Fetch values for this IP. my $v = $ip->get_position; my $ord = $self->get_storage->get_value( $v ); my $char = $self->get_storage->get_char( $v ); # Cosmetics. debug( "#".$ip->get_id.":$v: $char (ord=$ord) Stack=(@{$ip->get_toss})\n" ); # Check if we are in string-mode. if ( $ip->get_string_mode ) { if ( $char eq '"' ) { # End of string-mode. debug( "leaving string-mode\n" ); $ip->set_string_mode(0); } elsif ( $char eq ' ' ) { # A serie of spaces, to be treated as one space. debug( "string-mode: pushing char ' '\n" ); $self->_move_ip_till( $ip, qr/ / ); $ip->spush( $ord ); } else { # A banal character. debug( "string-mode: pushing char '$char'\n" ); $ip->spush( $ord ); } } else { $self->_do_instruction($char); } if ($continue) { # Tick done for this IP, let's move it and push it in the # set of non-terminated IPs. if ( $ip->get_string_mode ) { $self->_move_ip_once( $self->get_curip ); } else { $self->move_ip( $self->get_curip ); } push @{ $self->get_newips }, $ip unless $ip->get_end; } } #-- PRIVATE METHODS # # $lbi->_do_instruction( $char ); # # interpret instruction $char according to loaded ops map. # sub _do_instruction { my ($self, $char) = @_; if ( exists $self->get_ops->{$char} ) { # regular instruction. my $meth = $self->get_ops->{$char}; $meth->($self, $char); } else { # not a regular instruction: reflect. my $ord = ord($char); debug( "the command value $ord (char='$char') is not implemented.\n"); $self->get_curip->dir_reverse; } } # # $lbi->_move_ip_once( $ip ); # # move $ip one step further, according to its velocity. if $ip gets out # of bounds, then a wrapping is performed (according to current # interpreter wrapping implementation) on the ip. # sub _move_ip_once { my ($self, $ip) = @_; my $storage = $self->get_storage; # fetch the current position of the ip. my $v = $ip->get_position; my $d = $ip->get_delta; # now, let's move the ip. $v += $d; if ( $v->bounds_check($storage->min, $storage->max) ) { # within bounds - store new position. $ip->set_position( $v ); } else { # wrap needed - this will update the position. $self->get_wrapping->wrap( $storage, $ip ); } } # # _move_ip_till( $ip,regex ) # # Move $ip according to its delta on the storage, as long as the pointed # character match the supplied regex (a qr// object). # # Example: given the code C<;foobar;> (assuming the IP points on the # first C<;>) and the regex C<qr/[^;]/>, the IP will move in order to # point on the C<r>. # sub _move_ip_till { my ($self, $ip, $re) = @_; my $storage = $self->get_storage; my $orig = $ip->get_position; # moving as long as we did not reach the condition. while ( $storage->get_char($ip->get_position) =~ $re ) { $self->_move_ip_once($ip); $self->abort("infinite loop") if $ip->get_position == $orig; } # we moved one char too far. $ip->dir_reverse; $self->_move_ip_once($ip); $ip->dir_reverse; } 1; __END__