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] } ];
205 # move_ip( $ip [,regex] )
207 # Move $ip according to its delta on the storage.
209 # If a regex ( a C<qr//> object ) is specified, then $ip will move as
210 # long as the pointed character match the supplied regex.
212 # Example: given the code C<;foobar;> (assuming the IP points on the
213 # first C<;>) and the regex C<qr/[^;]/>, the IP will move in order to
217 my ($self, $ip, $re) = @_;
218 my $storage = $self->storage;
221 my $orig = $ip->get_position;
222 # moving as long as we did not reach the condition.
223 while ( $storage->get_char($ip->get_position) =~ $re ) {
224 $self->_move_ip_forward($ip);
225 $self->abort("infinite loop")
226 if $ip->get_position == $orig;
229 # we moved one char too far.
231 $self->_move_ip_forward($ip);
235 # moving one step beyond...
236 $self->_move_ip_forward($ip);
244 # Abort the interpreter with the given reason, as well as the current
245 # file and coordinate of the offending instruction.
249 my $file = $self->get_file;
250 my $v = $self->get_curip->get_position;
251 croak
"$file $v: ", @_;
258 # Issue a warning if the interpreter has DEBUG enabled.
262 $self->get_DEBUG or return;
268 # set_input( $string )
270 # Preload the input buffer with the given value.
273 my ($self, $str) = @_;
281 # Fetch a character of input from the input buffer, or else, directly
287 return substr($$self{input
}, 0, 1, '') if length $self->input;
289 my $rv = sysread(STDIN
, $char, 1);
290 return $char if length $char;
295 # - Code and Data Storage
298 # read_file( filename )
300 # Read a file (given as argument) and store its code.
302 # Side effect: clear the previous code.
305 my ($self, $file) = @_;
309 open BF
, "<$file" or croak
"$!";
311 local $/; # slurp mode.
317 $self->set_file( $file );
318 $self->store_code( $code );
325 # Store the given code in the Lahey space.
327 # Side effect: clear the previous code.
330 my ($self, $code) = @_;
331 $self->debug( "Storing code\n" );
332 $self->storage->clear;
333 $self->storage->store( $code );
341 # run_code( [params] )
343 # Run the current code. That is, create a new Instruction Pointer and
344 # move it around the code.
346 # Return the exit code of the program.
350 $self->set_params( [ @_ ] );
353 $self->debug( "\n-= NEW RUN (".$self->get_file.") =-\n" );
355 # Create the first Instruction Pointer.
356 $self->set_ips( [ Language
::Befunge
::IP
->new($$self{dimensions
}) ] );
357 $self->set_retval(0);
359 # Loop as long as there are IPs.
360 $self->next_tick while scalar @
{ $self->get_ips };
362 # Return the exit code.
363 return $self->get_retval;
370 # Finish the current tick and stop just before the next tick.
376 $self->debug( "Tick!\n" );
378 # Process the set of IPs.
379 $self->set_newips( [] );
380 $self->process_ip while $self->set_curip( shift @
{ $self->get_ips } );
383 $self->set_ips( $self->get_newips );
390 # Process the current ip.
393 my ($self, $continue) = @_;
394 $continue = 1 unless defined $continue;
395 my $ip = $self->get_curip;
397 # Fetch values for this IP.
398 my $v = $ip->get_position;
399 my $ord = $self->storage->get_value( $v );
400 my $char = $self->storage->get_char( $v );
403 $self->debug( "#".$ip->get_id.":$v: $char (ord=$ord) Stack=(@{$ip->get_toss})\n" );
405 # Check if we are in string-mode.
406 if ( $ip->get_string_mode ) {
407 if ( $char eq '"' ) {
408 # End of string-mode.
409 $self->debug( "leaving string-mode\n" );
410 $ip->set_string_mode(0);
412 } elsif ( $char eq ' ' ) {
413 # A serie of spaces, to be treated as one space.
414 $self->debug( "string-mode: pushing char ' '\n" );
415 $self->move_ip( $ip, qr/ / );
420 $self->debug( "string-mode: pushing char '$char'\n" );
425 # Not in string-mode.
426 if ( exists $self->get_ops->{$char} ) {
427 # Regular instruction.
428 my $meth = $self->get_ops->{$char};
432 # Not a regular instruction: reflect.
433 $self->debug( "the command value $ord (char='$char') is not implemented.\n");
439 # Tick done for this IP, let's move it and push it in the
440 # set of non-terminated IPs.
441 $self->move_ip( $self->get_curip );
442 push @
{ $self->get_newips }, $ip unless $ip->get_end;
450 # $lbi->_move_ip_forward( $ip );
452 # move $ip one step further, according to its velocity. if $ip gets out
453 # of bounds, then a wrapping is performed (according to current
454 # interpreter wrapping implementation) on the ip.
456 sub _move_ip_forward
{
457 my ($self, $ip) = @_;
458 my $storage = $self->storage;
460 # fetch the current position of the ip.
461 my $v = $ip->get_position;
462 my $d = $ip->get_delta;
464 # now, let's move the ip.
467 if ( $v->bounds_check($storage->min, $storage->max) ) {
468 # within bounds - store new position.
469 $ip->set_position( $v );
471 # wrap needed - this will update the position.
472 $self->_wrapping->wrap( $storage, $ip );
482 =head2 new( [filename, ] [ Key => Value, ... ] )
484 Create a new Befunge interpreter. As an optional first argument, you
485 can pass it a filename to read Funge code from (default: blank
486 torus). All other arguments are key=>value pairs. The following
487 keys are accepted, with their default values shown:
490 Syntax => 'befunge98',
491 Storage => 'laheyspace'
495 The following is a list of attributes of a Language::Befunge
496 object. For each of them, a method C<get_foobar> and C<set_foobar>
497 exists, which does what you can imagine - and if you can't, then i
498 wonder why you are reading this! :-)
502 =item get_curip() / set_curip()
504 the current Instruction Pointer processed (a L::B::IP object)
506 =item get_DEBUG() / set_DEBUG()
508 wether the interpreter should output debug messages (a boolean)
510 =item get_dimensions() / set_dimensions()
512 the number of dimensions this interpreter works in.
514 =item get_file() / set_file()
516 the script filename (a string)
518 =item get_handprint() / set_handprint()
520 the handprint of the interpreter
522 =item get_ips() / set_ips()
524 the current set of IPs travelling in the Lahey space (an array
527 =item get_newips() / set_newips()
529 the set of IPs that B<will> travel in the Lahey space B<after> the
530 current tick (an array reference)
532 =item get_ops() / set_ops()
534 the current supported operations set.
536 =item get_params() / set_params()
538 the parameters of the script (an array reference)
540 =item get_retval() / set_retval()
542 the current return value of the interpreter (an integer)
547 =head1 PUBLIC METHODS
553 =item move_ip( $ip [, $regex] )
555 Move the C<$ip> according to its delta on the storage.
557 If C<$regex> ( a C<qr//> object ) is specified, then C<$ip> will move as
558 long as the pointed character match the supplied regex.
560 Example: given the code C<;foobar;> (assuming the IP points on the
561 first C<;>) and the regex C<qr/[^;]/>, the IP will move in order to
565 =item abort( reason )
567 Abort the interpreter with the given reason, as well as the current
568 file and coordinate of the offending instruction.
573 Issue a warning if the interpreter has DEBUG enabled.
576 =item set_input( $string )
578 Preload the input buffer with the given value.
583 Fetch a character of input from the input buffer, or else, directly
591 =head2 Code and Data Storage
595 =item read_file( filename )
597 Read a file (given as argument) and store its code.
599 Side effect: clear the previous code.
602 =item store_code( code )
604 Store the given code in the Lahey space.
606 Side effect: clear the previous code.
617 =item run_code( [params] )
619 Run the current code. That is, create a new Instruction Pointer and
620 move it around the code.
622 Return the exit code of the program.
627 Finish the current tick and stop just before the next tick.
632 Process the current ip.
644 Write standard libraries.
651 Although this module comes with a full set of tests, maybe there are
652 subtle bugs - or maybe even I misinterpreted the Funge-98
653 specs. Please report them to me.
655 There are some bugs anyway, but they come from the specs:
661 About the 18th cell pushed by the C<y> instruction: Funge specs just
662 tell to push onto the stack the size of the stacks, but nothing is
663 said about how user will retrieve the number of stacks.
667 About the load semantics. Once a library is loaded, the interpreter is
668 to put onto the TOSS the fingerprint of the just-loaded library. But
669 nothing is said if the fingerprint is bigger than the maximum cell
670 width (here, 4 bytes). This means that libraries can't have a name
671 bigger than C<0x80000000>, ie, more than four letters with the first
672 one smaller than C<P> (C<chr(80)>).
674 Since perl is not so rigid, one can build libraries with more than
675 four letters, but perl will issue a warning about non-portability of
676 numbers greater than C<0xffffffff>.
681 =head1 ACKNOWLEDGEMENTS
683 I would like to thank Chris Pressey, creator of Befunge, who gave a
684 whole new dimension to both coding and obfuscating.
694 Jerome Quelin, E<lt>jquelin@cpan.orgE<gt>
696 Development is discussed on E<lt>language-befunge@mongueurs.netE<gt>
699 =head1 COPYRIGHT & LICENSE
701 Copyright (c) 2001-2008 Jerome Quelin, all rights reserved.
703 This program is free software; you can redistribute it and/or modify
704 it under the same terms as Perl itself.