create Language::Befunge::Storage::Generic::Vec, and test it.
[language-befunge.git] / lib / Language / Befunge / Storage / Generic / Vec.pm
blob5dd3ca722ca84fe1d667070b4fa2d4ffae0965ae
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::Storage::Generic::Vec;
11 require 5.006;
12 use strict;
13 use warnings;
14 no warnings 'portable'; # "Bit vector size > 32 non-portable" warnings on x64
15 use Carp;
16 use Language::Befunge::Vector;
17 use Language::Befunge::IP;
18 use base qw{ Language::Befunge::Storage };
19 use Config;
21 my $cell_size_in_bytes = $Config{ivsize};
22 my $cell_size_in_bits = $cell_size_in_bytes * 8;
23 # -- CONSTRUCTOR
26 # try to load speed-up LBSGVXS
27 eval 'use Language::Befunge::Storage::Generic::Vec::XS';
28 if ( defined $Language::Befunge::Storage::Generic::Vec::XS::VERSION ) {
29 my $xsversion = $Language::Befunge::Vector::XS::VERSION;
30 my @subs = qw[
31 get_value _get_value set_value _set_value _offset __offset _is_xs expand _expand
33 foreach my $sub ( @subs ) {
34 no strict 'refs';
35 no warnings 'redefine';
36 my $lbsgvxs_sub = "Language::Befunge::Storage::Generic::Vec::XS::$sub";
37 *$sub = \&$lbsgvxs_sub;
43 # new( dimensions )
45 # Creates a new Lahey Space.
47 sub new {
48 my $package = shift;
49 my $dimensions = shift;
50 my %args = @_;
51 my $usage = "Usage: $package->new(\$dimensions, Wrapping => \$wrapping)";
52 croak $usage unless defined $dimensions;
53 croak $usage unless $dimensions > 0;
54 croak $usage unless exists $args{Wrapping};
55 my $self = {
56 nd => $dimensions,
57 wrapping => $args{Wrapping},
59 bless $self, $package;
60 $self->clear();
61 return $self;
65 # -- PUBLIC METHODS
68 # clear( )
70 # Clear the torus.
72 sub clear {
73 my $self = shift;
74 $$self{min} = Language::Befunge::Vector->new_zeroes($$self{nd});
75 $$self{max} = Language::Befunge::Vector->new_zeroes($$self{nd});
76 $$self{torus} = chr(0) x $cell_size_in_bytes;
77 $self->set_value($$self{min}, 32);
82 # expand( v )
84 # Expand the torus to include the provided point.
86 sub expand {
87 my ($self, $point) = @_;
88 my ($old_min, $old_max) = ($$self{min}, $$self{max});
89 return if $point->bounds_check($$self{min}, $$self{max});
91 $point = $point->copy();
92 my $nd = $$self{nd};
94 my ($new_min, $new_max) = ($old_min->copy, $old_max->copy);
95 foreach my $d (0..$nd-1) {
96 $new_min->set_component($d, $point->get_component($d))
97 if $new_min->get_component($d) > $point->get_component($d);
98 $new_max->set_component($d, $point->get_component($d))
99 if $new_max->get_component($d) < $point->get_component($d);
101 my $old_size = $old_max - $old_min;
102 my $new_size = $new_max - $new_min;
104 # if we have nothing to do, skip out early.
105 return if $old_size == $new_size;
107 # figure out the new storage size
108 my $storage_size = $self->_offset($new_max, $new_min, $new_max) + 1;
110 # figure out what a space looks like on this architecture.
111 # Note: vec() is always big-endian, but the XS module is host-endian.
112 # So we have to use an indirect approach.
113 my $old_value = $self->get_value($self->min);
114 $self->set_value($self->min, 32);
115 my $new_value = vec($$self{torus}, 0, $cell_size_in_bits);
116 $self->set_value($self->min, $old_value);
117 # allocate new storage
118 my $new_torus = " " x $cell_size_in_bytes;
119 vec($new_torus, 0, $cell_size_in_bits) = $new_value;
120 $new_torus x= $storage_size;
121 for(my $v = $new_min->copy; defined($v); $v = $v->rasterize($new_min, $new_max)) {
122 if($v->bounds_check($old_min, $old_max)) {
123 my $length = $old_max->get_component(0) - $v->get_component(0);
124 my $old_offset = $self->_offset($v);
125 my $new_offset = $self->_offset($v, $new_min, $new_max);
126 vec( $new_torus , $new_offset, $cell_size_in_bits)
127 = vec($$self{torus}, $old_offset, $cell_size_in_bits);
130 $$self{min} = $new_min;
131 $$self{max} = $new_max;
132 $$self{torus} = $new_torus;
137 # store( code, [vector] )
139 # Store the given code at the specified vector. If the coordinates
140 # are omitted, then the code is stored at the origin (0, 0).
142 # Return the size of the code inserted, as a vector.
144 # The code is a string, representing a block of Funge code. Rows are
145 # separated by newlines. Planes are separated by form feeds. A complete list
146 # of separators follows:
148 # Axis Delimiter
149 # X (none)
150 # Y \n
151 # Z \f
152 # 4 \0
154 # The new-line and form-feed delimiters are in the Funge98 spec. However,
155 # there is no standardized separator for dimensions above Z. Currently,
156 # dimensions 4 and above use \0, \0\0, \0\0\0, etc. These are dangerously
157 # ambiguous, but are the only way I can think of to retain reverse
158 # compatibility. Suggestions for better delimiters are welcome. (Using XML
159 # would be really ugly, I'd prefer not to.)
161 # This function actually enumerates the input twice: once to determine the max
162 # (so we only have to call expand() once), and once to do the actual storing.
164 sub store {
165 my ($self, $code, $base) = @_;
166 my $nd = $$self{nd};
167 $base = Language::Befunge::Vector->new_zeroes($$self{nd}) unless defined $base;
169 # support for any eol convention
170 $code =~ s/\r\n/\n/g;
171 $code =~ s/\r/\n/g;
173 # The torus is a tree of arrays of numbers.
174 # The tree is N levels deep, where N is the number of dimensions.
175 # Each number is the ordinal value of the character held in this cell.
177 my @separators = ("", "\n", "\f");
178 push(@separators, "\0"x($_-3)) for (4..$nd); # , "\0", "\0\0", "\0\0\0"...
179 my @sizes = map { 0 } (1..$nd);
180 sub _code_split_helper {
181 my ($d, $code, $sep, $sizes) = @_;
182 my $rv = [split($$sep[$d], $code)];
183 $rv = [ map { _code_split_helper($d-1,$_,$sep,$sizes) } (@$rv) ]
184 if $d > 0;
185 $$sizes[$d] = scalar @$rv if scalar @$rv > $$sizes[$d];
186 return $rv;
188 my $coderef = _code_split_helper($nd - 1, $code, \@separators, \@sizes);
190 # Figure out the rectangle size and the end-coordinate (max).
191 my $size = Language::Befunge::Vector->new(@sizes);
192 my $max = Language::Befunge::Vector->new(map { $_ - 1 } (@sizes));
193 $max += $base;
195 # Enlarge torus to make sure our new values will fit.
196 $self->expand( $base );
197 $self->expand( $max );
199 # Store code.
200 TOP: for(my $v = $base->copy; defined($v); $v = $v->rasterize($base, $max)) {
201 my $cv = $v - $base;
202 my $code = $coderef;
203 foreach my $ent (reverse $cv->get_all_components()) {
204 next TOP unless exists $$code[$ent];
205 $code = $$code[$ent];
207 next TOP if $code eq ' ';
208 $self->set_value($v, ord($code));
211 return $size;
216 # store_binary( code, [vector] )
218 # Store the given code at the specified coordinates. If the coordinates
219 # are omitted, then the code is stored at the Origin(0, 0) coordinates.
221 # Return the size of the code inserted, as a vector.
223 # This is binary insertion, that is, EOL and FF sequences are stored in
224 # Funge-space instead of causing the dimension counters to be reset and
225 # incremented. The data is stored all in one row.
227 sub store_binary {
228 my ($self, $code, $base) = @_;
229 my $nd = $$self{nd};
230 $base = Language::Befunge::Vector->new_zeroes($$self{nd})
231 unless defined $base;
233 # The torus is a tree of arrays of numbers.
234 # The tree is N levels deep, where N is the number of dimensions.
235 # Each number is the ordinal value of the character held in this cell.
237 my @sizes = length($code);
238 push(@sizes,1) for(2..$nd);
240 # Figure out the min, max, and size
241 my $size = Language::Befunge::Vector->new(@sizes);
242 my $max = Language::Befunge::Vector->new(map { $_ - 1 } (@sizes));
243 $max += $base;
245 # Enlarge torus to make sure our new values will fit.
246 $self->expand( $base );
247 $self->expand( $max );
249 # Store code.
250 for(my $v = $base->copy; defined($v); $v = $v->rasterize($base, $max)) {
251 my $char = substr($code, 0, 1, "");
252 next if $char eq " ";
253 $self->set_value($v, ord($char));
255 return $size;
260 # get_char( vector )
262 # Return the character stored in the torus at the specified location. If
263 # the value is not between 0 and 255 (inclusive), get_char will return a
264 # string that looks like "<np-0x4500>".
266 # B</!\> As in Funge, code and data share the same playfield, the
267 # character returned can be either an instruction B<or> raw data. No
268 # guarantee is made that the return value is printable.
270 sub get_char {
271 my $self = shift;
272 my $v = shift;
273 my $ord = $self->get_value($v);
274 # reject invalid ascii
275 return sprintf("<np-0x%x>",$ord) if ($ord < 0 || $ord > 255);
276 return chr($ord);
281 # my $val = get_value( vector )
283 # Return the number stored in the torus at the specified location. If
284 # the value hasn't yet been set, it defaults to the ordinal value of a
285 # space (ie, #32).
287 # B</!\> As in Funge, code and data share the same playfield, the
288 # number returned can be either an instruction B<or> a data (or even
289 # both... Eh, that's Funge! :o) ).
291 sub get_value {
292 my ($self, $v) = @_;
293 my $val = 32;
295 if ($v->bounds_check($$self{min}, $$self{max})) {
296 my $off = $self->_offset($v);
297 $val = vec($$self{torus}, $off, $cell_size_in_bits);
299 return $self->_u32_to_s32($val);
304 # set_value( vector, value )
306 # Write the supplied value in the torus at the specified location.
308 # B</!\> As in Funge, code and data share the same playfield, the
309 # number stored can be either an instruction B<or> a data (or even
310 # both... Eh, that's Funge! :o) ).
312 sub set_value {
313 my ($self, $v, $val) = @_;
315 # Ensure we can set the value.
316 $self->expand($v);
317 my $off = $self->_offset($v);
318 vec($$self{torus}, $off, $cell_size_in_bits) = $self->_s32_to_u32($val);
323 # my $str = rectangle( start, size )
325 # Return a string containing the data/code in the specified rectangle.
327 # Note that for useful data to be returned, the "size" vector must contain at
328 # least a "1" for each component. After all, a world of 10 width, 10 length
329 # but zero height would contain 0 planes. So, if any components of the
330 # "size" vector do contain a 0, "" is returned.
332 sub rectangle {
333 my ($self, $v1, $v2) = @_;
334 my $nd = $$self{nd};
336 # Fetch the data.
337 my $data = "";
338 my $min = $v1;
339 foreach my $d (0..$nd-1) {
340 # each dimension must >= 1, otherwise the rectangle will be empty.
341 return "" unless $v2->get_component($d);
342 # ... but we need to offset by -1, to calculate $max
343 $v2->set_component($d, $v2->get_component($d) - 1);
345 my $max = $v1 + $v2;
346 # No separator is used for the first dimension, for obvious reasons.
347 # Funge98 specifies lf/cr/crlf for a second-dimension separator.
348 # Funge98 specifies a form feed for a third-dimension separator.
349 # Funge98 doesn't specify what dimensions 4 and above should use.
350 # We use increasingly long strings of null bytes.
351 # (4d uses 1 null byte, 5d uses 2, 6d uses 3, etc)
352 my @separators = "";
353 push(@separators,"\n") if $nd > 1;
354 push(@separators,"\f") if $nd > 2;
355 push(@separators,"\0"x($_-3)) for (4..$nd); # , "\0", "\0\0", "\0\0\0"...
356 my $prev = $min->copy;
357 for(my $v = $min->copy; defined($v); $v = $v->rasterize($min, $max)) {
358 foreach my $d (0..$$self{nd}-1) {
359 $data .= $separators[$d]
360 if $prev->get_component($d) != $v->get_component($d);
362 $prev = $v;
363 $data .= $self->get_char($v);
365 return $data;
370 # my %labels = labels_lookup( )
372 # Parse the Lahey space to find sequences such as C<;:(\w[^\s;])[^;]*;>
373 # and return a hash reference whose keys are the labels and the values
374 # an anonymous array with two vectors: a vector describing the absolute
375 # position of the character B<just after> the trailing C<;>, and a
376 # vector describing the velocity that lead to this label.
378 # This method will only look in the cardinal directions; west, east,
379 # north, south, up, down and so forth.
381 # This allow to define some labels in the source code, to be used by
382 # C<Inline::Befunge> (and maybe some extensions).
384 sub labels_lookup {
385 my $self = shift;
386 my $labels = {};
388 my ($min, $max) = ($$self{min}, $$self{max});
389 $max = $max->copy;
390 my $nd = $$self{nd};
391 my @directions = ();
392 foreach my $dimension (0..$nd-1) {
393 # for the loop below, $max actually needs to be the point *after* the
394 # greatest point ever written to; otherwise the last column is skipped.
395 $max->set_component($dimension, $max->get_component($dimension)+1);
397 # build the array of (non-diagonal) vectors
398 my $v1 = Language::Befunge::Vector->new_zeroes($nd);
399 my $v2 = $v1->copy;
400 $v1->set_component($dimension,-1);
401 push(@directions,$v1);
402 $v2->set_component($dimension, 1);
403 push(@directions,$v2);
406 R: for(my $this = $min->copy; defined($this); $this = $this->rasterize($min, $max)) {
407 V: for my $v (@directions) {
408 next R unless $self->get_char($this) eq ";";
409 my ($label, $loc) = $self->_labels_try( $this, $v );
410 next V unless defined($label);
412 # How exciting, we found a label!
413 croak "Help! I found two labels '$label' in the funge space"
414 if exists $labels->{$label};
415 $$labels{$label} = [$loc, $v];
419 return $labels;
424 # my $vector = min()
426 # Returns a Vector object, pointing at the beginning of the torus.
427 # If nothing has been stored to a negative offset, this Vector will
428 # point at the origin (0,0).
430 sub min {
431 my $self = shift;
432 return $$self{min}->copy;
437 # my $vector = max()
439 # Returns a Vector object, pointing at the end of the torus.
440 # This is usually the largest position which has been written to.
442 sub max {
443 my $self = shift;
444 return $$self{max}->copy;
448 # -- PRIVATE METHODS
451 # _offset(v [, min, max])
453 # Return the offset (within the torus bitstring) of the vector. If min and max
454 # are provided, return the offset within a hypothetical torus which has those
455 # dimensions.
457 sub _offset {
458 my ($self, $v, $min, $max) = @_;
459 my $nd = $$self{nd};
460 my $off_by_1 = Language::Befunge::Vector->new(map { 1 } (1..$nd));
461 $min = $$self{min} unless defined $min;
462 $max = $$self{max} unless defined $max;
463 my $tsize = $max + $off_by_1 - $min;
464 my $toff = $v - $min;
465 my $rv = 0;
466 my $levsize = 1;
467 foreach my $d (0..$nd-1) {
468 $rv += $toff->get_component($d) * $levsize;
469 $levsize *= $tsize->get_component($d);
471 return $rv;
476 # _labels_try( $start, $delta )
478 # Try in the specified direction if the funge space matches a label
479 # definition. Return undef if it wasn't a label definition, or the name
480 # of the label if it was a valid label.
482 # called internally by labels_lookup().
484 sub _labels_try {
485 my ($self, $start, $delta) = @_;
486 my $comment = "";
487 my $wrapping = $$self{wrapping};
488 my $ip = Language::Befunge::IP->new($$self{nd});
489 my $min = $self->min;
490 my $max = $self->max;
491 $ip->set_position($start->copy);
492 $ip->set_delta($delta);
494 # don't affect the parent
495 #$vector = $vector->copy();
496 # Fetch the whole comment stuff.
497 do {
498 # Calculate the next cell coordinates.
499 my $v = $ip->get_position;
500 my $d = $ip->get_delta;
502 # now, let's move the ip.
503 $v += $d;
505 if ( $v->bounds_check($min, $max) ) {
506 $ip->set_position( $v );
507 } else {
508 $wrapping->wrap( $self, $ip );
511 $comment .= $self->get_char($ip->get_position());
512 } while ( $comment !~ /;.$/ );
514 # Check if the comment matches the pattern.
515 $comment =~ /^:(\w[^\s;]*)[^;]*;.$/;
516 return undef unless defined $1;
517 return undef unless length $1;
518 return ($1, $ip->get_position());
522 sub _s32_to_u32 {
523 my ($self, $value) = @_;
524 $value = 0xffffffff + ($value+1)
525 if $value < 0;
526 return $value;
529 sub _u32_to_s32 {
530 my ($self, $value) = @_;
531 $value = -2147483648 + ($value & 0x7fffffff)
532 if($value & 0x80000000);
533 return $value;
536 sub _is_xs { 0 }
539 __END__
541 =head1 NAME
543 Language::Befunge::LaheySpace::Generic - a generic N-dimensional LaheySpace.
546 =head1 SYNOPSIS
548 # create a 3-dimensional LaheySpace.
549 my $torus = Language::Befunge::LaheySpace::Generic->new(3);
550 $torus->clear();
551 $torus->store(<<"EOF");
552 12345
553 67890
554 \fabcde
555 fghij
558 Note you usually don't need to use this module directly.
559 B<Language::Befunge::Interpreter> can optionally use it. If you are
560 considering using it, you should really install
561 L<Language::Befunge::Storage::Generic::Vec::XS> too, as this module is
562 dreadfully slow without it. If you cannot install that, you should
563 use L<Language::Befunge::Storage::Generic::AoA> instead, it will perform
564 better.
567 =head1 DESCRIPTION
569 This module implements a traditional Lahey space.
572 =head1 CONSTRUCTOR
574 =head2 new( dimensions )
576 Creates a new Lahey Space.
579 =head1 PUBLIC METHODS
581 =head2 clear( )
583 Clear the torus.
586 =head2 expand( v )
588 Expand the torus to include the provided point.
591 =head2 store( code, [vector] )
593 Store the given code at the specified vector. If the coordinates
594 are omitted, then the code is stored at the origin (0, 0).
596 Return the size of the code inserted, as a vector.
598 The code is a string, representing a block of Funge code. Rows are
599 separated by newlines. Planes are separated by form feeds. A complete list of
600 separators follows:
602 Axis Delimiter
603 X (none)
604 Y \n
605 Z \f
606 4 \0
608 The new-line and form-feed delimiters are in the Funge98 spec. However, there
609 is no standardized separator for dimensions above Z. Currently, dimensions 4
610 and above use \0, \0\0, \0\0\0, etc. These are dangerously ambiguous, but are
611 the only way I can think of to retain reverse compatibility. Suggestions for
612 better delimiters are welcome. (Using XML would be really ugly, I'd prefer not
613 to.)
616 =head2 store_binary( code, [vectir] )
618 Store the given code at the specified coordinates. If the coordinates
619 are omitted, then the code is stored at the Origin(0, 0) coordinates.
621 Return the size of the code inserted, as a vector.
623 This is binary insertion, that is, EOL and FF sequences are stored in
624 Funge-space instead of causing the dimension counters to be reset and
625 incremented. The data is stored all in one row.
628 =head2 get_char( vector )
631 Return the character stored in the torus at the specified location. If
632 the value is not between 0 and 255 (inclusive), get_char will return a
633 string that looks like "<np-0x4500>".
635 B</!\> As in Funge, code and data share the same playfield, the
636 character returned can be either an instruction B<or> raw data. No
637 guarantee is made that the return value is printable.
640 =head2 get_value( vector )
642 Return the number stored in the torus at the specified location. If
643 the value hasn't yet been set, it defaults to the ordinal value of a
644 space (ie, #32).
646 B</!\> As in Funge, code and data share the same playfield, the
647 number returned can be either an instruction B<or> a data (or even
648 both... Eh, that's Funge! :o) ).
651 =head2 set_value( vector, value )
653 Write the supplied value in the torus at the specified location.
655 B</!\> As in Funge, code and data share the same playfield, the
656 number stored can be either an instruction B<or> a data (or even
657 both... Eh, that's Funge! :o) ).
660 =head2 move_ip_forward( ip )
662 Move the given ip forward, according to its delta.
665 =head2 wrap( position, delta )
667 Handle LaheySpace wrapping, if necessary.
670 =head2 rectangle( start, size )
672 Return a string containing the data/code in the specified rectangle.
674 Note that for useful data to be returned, the "size" vector must contain at
675 least a "1" for each component. After all, a world of 10 width, 10 length
676 but zero height would contain 0 planes. So, if any components of the
677 "size" vector do contain a 0, "" is returned.
680 =head2 labels_lookup( )
682 Parse the Lahey space to find sequences such as C<;:(\w[^\s;])[^;]*;>
683 and return a hash reference whose keys are the labels and the values
684 an anonymous array with two vectors: a vector describing the absolute
685 position of the character B<just after> the trailing C<;>, and a
686 vector describing the velocity that lead to this label.
688 This method will only look in the cardinal directions; west, east,
689 north, south, up, down and so forth.
691 This allow to define some labels in the source code, to be used by
692 C<Inline::Befunge> (and maybe some extensions).
694 =cut
697 =head2 min()
699 Returns a Vector object, pointing at the beginning of the torus.
700 If nothing has been stored to a negative offset, this Vector will
701 point at the origin (0,0).
704 =head2 max()
706 Returns a Vector object, pointing at the end of the torus.
707 This is usually the largest position which has been written to.
710 =head1 PRIVATE METHODS
712 =head2 _labels_try( start, delta )
714 Try in the specified direction if the funge space matches a label
715 definition. Return undef if it wasn't a label definition, or the name
716 of the label if it was a valid label.
719 =head1 BUGS
721 None known. Please inform me if you find one.
724 =head1 SEE ALSO
726 L<Language::Befunge::Storage::Generic::Vec::XS>, L<Language::Befunge>.
729 =head1 AUTHOR
731 Mark Glines, E<lt>infinoid@cpan.orgE<gt>
732 Jerome Quelin, E<lt>jquelin@cpan.orgE<gt>
734 Development is discussed on E<lt>language-befunge@mongueurs.netE<gt>
737 =head1 COPYRIGHT & LICENSE
739 Copyright (c) 2001-2008 Jerome Quelin, all rights reserved.
741 This program is free software; you can redistribute it and/or modify
742 it under the same terms as Perl itself.
745 =cut