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 # FIXME: wtf? always use get_/set_ or mutators, but not a mix of them!
23 get_dimensions
=> 'dimensions',
25 get_params
=> 'params',
26 get_retval
=> 'retval',
27 get_storage
=> 'storage',
31 get_newips
=> 'newips',
33 get_handprint
=> 'handprint',
36 set_dimensions
=> 'dimensions',
38 set_params
=> 'params',
39 set_retval
=> 'retval',
43 set_newips
=> 'newips',
45 set_handprint
=> 'handprint',
49 _wrapping
=> '_wrapping',
52 # Public variables of the module.
60 # my $interpreter = LBI->new( $opts )
62 # Create a new funge interpreter. One can pass some options as a hash
63 # reference, with the following keys:
64 # - file: the filename to read funge code from (default: blank storage)
65 # - syntax: the tunings set (default: 'befunge98')
66 # - dims: the number of dimensions
67 # - ops: the Ops subclass used in this interpreter
68 # - storage: the Storage subclass used in this interpreter
69 # - wrapping: the Wrapping subclass used in this interpreter
71 # Usually, the "dims", "ops", "storage" and "wrapping" keys are left
72 # undefined, and are implied by the "syntax" key.
74 # Depending on the value of syntax will change the interpreter
75 # internals: set of allowed ops, storage implementation, wrapping. The
76 # following values are recognized for 'syntax' (with in order: the
77 # number of dimensions, the set of operation loaded, the storage
78 # implementation and the wrapping implementation):
80 # - unefunge98: 1, LBO:Unefunge98, LBS:Generic::AoA, LBW:LaheySpace
81 # - befunge98: 2, LBO:Befunge98, LBS:2D:Sparse, LBW:LaheySpace
82 # - trefunge98: 3, LBO:GenericFunge98, LBS:Generic::AoA, LBW:LaheySpace
83 # - 4funge98: 4, LBO:GenericFunge98, LBS:Generic::AoA, LBW:LaheySpace
84 # - 5funge98: 5, LBO:GenericFunge98, LBS:Generic::AoA, LBW:LaheySpace
88 # If none of those values suit your needs, you can pass the value
89 # 'custom' and in that case you're responsible for also giving
90 # appropriate values for the keys 'dims', 'ops', 'storage', 'wrapping'.
91 # Note that those values will be ignored for all syntax values beside
95 my ($class, $opts) = @_;
97 $opts //= { dims
=> 2 };
98 unless(exists($$opts{syntax
})) {
100 croak
("If you pass a 'dims' attribute, it must be numeric.")
101 if $$opts{dims
} =~ /\D/;
107 if(exists($defaults{$$opts{dims
}})) {
108 $$opts{syntax
} = $defaults{$$opts{dims
}};
110 $$opts{syntax
} = $$opts{dims
} . 'funge98';
114 # select the classes to use, depending on the wanted syntax.
115 my $lbo = 'Language::Befunge::Ops::';
116 my $lbs = 'Language::Befunge::Storage::';
117 my $lbw = 'Language::Befunge::Wrapping::';
118 given ( $opts->{syntax
} ) {
119 when ('unefunge98') {
120 $opts->{dims
} = 1 unless defined $opts->{dims
};
121 $opts->{ops
} = $lbo . 'Unefunge98' unless defined $opts->{ops
};
122 $opts->{storage
} = $lbs . 'Generic::AoA' unless defined $opts->{storage
};
123 $opts->{wrapping
} = $lbw . 'LaheySpace' unless defined $opts->{wrapping
};
126 $opts->{dims
} = 2 unless defined $opts->{dims
};
127 $opts->{ops
} = $lbo . 'Befunge98' unless defined $opts->{ops
};
128 $opts->{storage
} = $lbs . '2D::Sparse' unless defined $opts->{storage
};
129 $opts->{wrapping
} = $lbw . 'LaheySpace' unless defined $opts->{wrapping
};
131 when ('trefunge98') {
132 $opts->{dims
} = 3 unless defined $opts->{dims
};
133 $opts->{ops
} = $lbo . 'GenericFunge98' unless defined $opts->{ops
};
134 $opts->{storage
} = $lbs . 'Generic::AoA' unless defined $opts->{storage
};
135 $opts->{wrapping
} = $lbw . 'LaheySpace' unless defined $opts->{wrapping
};
137 when (/(\d+)funge98$/) { # accept values like "4funge98"
138 $opts->{dims
} = $1 unless defined $opts->{dims
};
139 $opts->{ops
} = $lbo . 'GenericFunge98' unless defined $opts->{ops
};
140 $opts->{storage
} = $lbs . 'Generic::AoA' unless defined $opts->{storage
};
141 $opts->{wrapping
} = $lbw . 'LaheySpace' unless defined $opts->{wrapping
};
143 default { croak
"syntax '$opts->{syntax}' not recognized." }
146 # load the classes (through UNIVERSAL::require)
148 $opts->{storage
}->use;
149 $opts->{wrapping
}->use;
152 my $wrapping = $opts->{wrapping
}->new;
154 dimensions
=> $opts->{dims
},
155 storage
=> $opts->{storage
}->new( $opts->{dims
}, Wrapping
=> $wrapping ),
162 ops
=> $opts->{ops
}->get_ops_map,
165 handprint
=> 'JQBF', # the official handprint
166 _wrapping
=> $wrapping,
170 # read the file if needed.
171 defined($opts->{file
}) and $self->read_file( $opts->{file
} );
188 # Move $ip according to its delta on the storage. Spaces and comments
189 # (enclosed with semi-colons ';') are skipped silently.
192 my ($self, $ip) = @_;
194 my $storage = $self->get_storage;
195 my $orig = $ip->get_position;
196 $self->_move_ip_once($ip);
200 my $pos = $ip->get_position;
201 $char = $storage->get_char($pos);
204 if ( $char eq ' ' ) {
205 $self->_move_ip_till( $ip, qr/ / ); # skip all spaces
206 $self->_move_ip_once($ip); # skip last space
211 if ( $char eq ';' ) {
212 $self->_move_ip_once($ip); # skip comment ';'
213 $self->_move_ip_till( $ip, qr/[^;]/ ); # till just before matching ';'
214 $self->_move_ip_once($ip); # till matching ';'
215 $self->_move_ip_once($ip); # till just after matching ';'
227 # Abort the interpreter with the given reason, as well as the current
228 # file and coordinate of the offending instruction.
232 my $file = $self->get_file;
233 my $v = $self->get_curip->get_position;
234 croak
"$file $v: ", @_;
241 # Issue a warning if the interpreter has DEBUG enabled.
245 $self->get_DEBUG or return;
251 # set_input( $string )
253 # Preload the input buffer with the given value.
256 my ($self, $str) = @_;
264 # Fetch a character of input from the input buffer, or else, directly
270 return substr($$self{input
}, 0, 1, '') if length $self->input;
272 my $rv = sysread(STDIN
, $char, 1);
273 return $char if length $char;
278 # - Code and Data Storage
281 # read_file( filename )
283 # Read a file (given as argument) and store its code.
285 # Side effect: clear the previous code.
288 my ($self, $file) = @_;
292 open BF
, "<$file" or croak
"$!";
294 local $/; # slurp mode.
300 $self->set_file( $file );
301 $self->store_code( $code );
308 # Store the given code in the Lahey space.
310 # Side effect: clear the previous code.
313 my ($self, $code) = @_;
314 $self->debug( "Storing code\n" );
315 $self->get_storage->clear;
316 $self->get_storage->store( $code );
324 # run_code( [params] )
326 # Run the current code. That is, create a new Instruction Pointer and
327 # move it around the code.
329 # Return the exit code of the program.
333 $self->set_params( [ @_ ] );
336 $self->debug( "\n-= NEW RUN (".$self->get_file.") =-\n" );
338 # Create the first Instruction Pointer.
339 $self->set_ips( [ Language
::Befunge
::IP
->new($$self{dimensions
}) ] );
340 $self->set_retval(0);
342 # Loop as long as there are IPs.
343 $self->next_tick while scalar @
{ $self->get_ips };
345 # Return the exit code.
346 return $self->get_retval;
353 # Finish the current tick and stop just before the next tick.
359 $self->debug( "Tick!\n" );
361 # Process the set of IPs.
362 $self->set_newips( [] );
363 $self->process_ip while $self->set_curip( shift @
{ $self->get_ips } );
366 $self->set_ips( $self->get_newips );
373 # Process the current ip.
376 my ($self, $continue) = @_;
377 $continue = 1 unless defined $continue;
378 my $ip = $self->get_curip;
380 # Fetch values for this IP.
381 my $v = $ip->get_position;
382 my $ord = $self->get_storage->get_value( $v );
383 my $char = $self->get_storage->get_char( $v );
386 $self->debug( "#".$ip->get_id.":$v: $char (ord=$ord) Stack=(@{$ip->get_toss})\n" );
388 # Check if we are in string-mode.
389 if ( $ip->get_string_mode ) {
390 if ( $char eq '"' ) {
391 # End of string-mode.
392 $self->debug( "leaving string-mode\n" );
393 $ip->set_string_mode(0);
395 } elsif ( $char eq ' ' ) {
396 # A serie of spaces, to be treated as one space.
397 $self->debug( "string-mode: pushing char ' '\n" );
398 $self->_move_ip_till( $ip, qr/ / );
403 $self->debug( "string-mode: pushing char '$char'\n" );
408 $self->_do_instruction($char);
412 # Tick done for this IP, let's move it and push it in the
413 # set of non-terminated IPs.
414 if ( $ip->get_string_mode ) {
415 $self->_move_ip_once( $self->get_curip );
417 $self->move_ip( $self->get_curip );
419 push @
{ $self->get_newips }, $ip unless $ip->get_end;
426 # $lbi->_do_instruction( $char );
428 # interpret instruction $char according to loaded ops map.
430 sub _do_instruction
{
431 my ($self, $char) = @_;
433 if ( exists $self->get_ops->{$char} ) {
434 # regular instruction.
435 my $meth = $self->get_ops->{$char};
436 $meth->($self, $char);
439 # not a regular instruction: reflect.
440 my $ord = ord($char);
441 $self->debug( "the command value $ord (char='$char') is not implemented.\n");
442 $self->get_curip->dir_reverse;
448 # $lbi->_move_ip_once( $ip );
450 # move $ip one step further, according to its velocity. if $ip gets out
451 # of bounds, then a wrapping is performed (according to current
452 # interpreter wrapping implementation) on the ip.
455 my ($self, $ip) = @_;
456 my $storage = $self->get_storage;
458 # fetch the current position of the ip.
459 my $v = $ip->get_position;
460 my $d = $ip->get_delta;
462 # now, let's move the ip.
465 if ( $v->bounds_check($storage->min, $storage->max) ) {
466 # within bounds - store new position.
467 $ip->set_position( $v );
469 # wrap needed - this will update the position.
470 $self->_wrapping->wrap( $storage, $ip );
476 # _move_ip_till( $ip,regex )
478 # Move $ip according to its delta on the storage, as long as the pointed
479 # character match the supplied regex (a qr// object).
481 # Example: given the code C<;foobar;> (assuming the IP points on the
482 # first C<;>) and the regex C<qr/[^;]/>, the IP will move in order to
486 my ($self, $ip, $re) = @_;
487 my $storage = $self->get_storage;
489 my $orig = $ip->get_position;
490 # moving as long as we did not reach the condition.
491 while ( $storage->get_char($ip->get_position) =~ $re ) {
492 $self->_move_ip_once($ip);
493 $self->abort("infinite loop")
494 if $ip->get_position == $orig;
497 # we moved one char too far.
499 $self->_move_ip_once($ip);
509 =head2 new( [filename, ] [ Key => Value, ... ] )
511 Create a new Befunge interpreter. As an optional first argument, you
512 can pass it a filename to read Funge code from (default: blank
513 torus). All other arguments are key=>value pairs. The following
514 keys are accepted, with their default values shown:
517 Syntax => 'befunge98',
518 Storage => 'laheyspace'
522 The following is a list of attributes of a Language::Befunge
523 object. For each of them, a method C<get_foobar> and C<set_foobar>
524 exists, which does what you can imagine - and if you can't, then i
525 wonder why you are reading this! :-)
529 =item get_curip() / set_curip()
531 the current Instruction Pointer processed (a L::B::IP object)
533 =item get_DEBUG() / set_DEBUG()
535 wether the interpreter should output debug messages (a boolean)
537 =item get_dimensions() / set_dimensions()
539 the number of dimensions this interpreter works in.
541 =item get_file() / set_file()
543 the script filename (a string)
545 =item get_handprint() / set_handprint()
547 the handprint of the interpreter
549 =item get_ips() / set_ips()
551 the current set of IPs travelling in the Lahey space (an array
554 =item get_newips() / set_newips()
556 the set of IPs that B<will> travel in the Lahey space B<after> the
557 current tick (an array reference)
559 =item get_ops() / set_ops()
561 the current supported operations set.
563 =item get_params() / set_params()
565 the parameters of the script (an array reference)
567 =item get_retval() / set_retval()
569 the current return value of the interpreter (an integer)
573 the C<LB::Storage> object containing the playfield.
578 =head1 PUBLIC METHODS
584 =item move_ip( $ip [, $regex] )
586 Move the C<$ip> according to its delta on the storage.
588 If C<$regex> ( a C<qr//> object ) is specified, then C<$ip> will move as
589 long as the pointed character match the supplied regex.
591 Example: given the code C<;foobar;> (assuming the IP points on the
592 first C<;>) and the regex C<qr/[^;]/>, the IP will move in order to
596 =item abort( reason )
598 Abort the interpreter with the given reason, as well as the current
599 file and coordinate of the offending instruction.
604 Issue a warning if the interpreter has DEBUG enabled.
607 =item set_input( $string )
609 Preload the input buffer with the given value.
614 Fetch a character of input from the input buffer, or else, directly
622 =head2 Code and Data Storage
626 =item read_file( filename )
628 Read a file (given as argument) and store its code.
630 Side effect: clear the previous code.
633 =item store_code( code )
635 Store the given code in the Lahey space.
637 Side effect: clear the previous code.
648 =item run_code( [params] )
650 Run the current code. That is, create a new Instruction Pointer and
651 move it around the code.
653 Return the exit code of the program.
658 Finish the current tick and stop just before the next tick.
663 Process the current ip.
675 Write standard libraries.
682 Although this module comes with a full set of tests, maybe there are
683 subtle bugs - or maybe even I misinterpreted the Funge-98
684 specs. Please report them to me.
686 There are some bugs anyway, but they come from the specs:
692 About the 18th cell pushed by the C<y> instruction: Funge specs just
693 tell to push onto the stack the size of the stacks, but nothing is
694 said about how user will retrieve the number of stacks.
698 About the load semantics. Once a library is loaded, the interpreter is
699 to put onto the TOSS the fingerprint of the just-loaded library. But
700 nothing is said if the fingerprint is bigger than the maximum cell
701 width (here, 4 bytes). This means that libraries can't have a name
702 bigger than C<0x80000000>, ie, more than four letters with the first
703 one smaller than C<P> (C<chr(80)>).
705 Since perl is not so rigid, one can build libraries with more than
706 four letters, but perl will issue a warning about non-portability of
707 numbers greater than C<0xffffffff>.
712 =head1 ACKNOWLEDGEMENTS
714 I would like to thank Chris Pressey, creator of Befunge, who gave a
715 whole new dimension to both coding and obfuscating.
725 Jerome Quelin, E<lt>jquelin@cpan.orgE<gt>
727 Development is discussed on E<lt>language-befunge@mongueurs.netE<gt>
730 =head1 COPYRIGHT & LICENSE
732 Copyright (c) 2001-2008 Jerome Quelin, all rights reserved.
734 This program is free software; you can redistribute it and/or modify
735 it under the same terms as Perl itself.