File Coverage

blib/lib/AI/Evolve/Befunge/Critter.pm
Criterion Covered Total %
statement 253 253 100.0
branch 95 96 98.9
condition 6 6 100.0
subroutine 25 25 100.0
pod 5 5 100.0
total 384 385 99.7


line stmt bran cond sub pod time code
1             package AI::Evolve::Befunge::Critter;
2 5     5   95674 use strict;
  5         13  
  5         172  
3 5     5   28 use warnings;
  5         9  
  5         193  
4              
5 5     5   4428 use Language::Befunge;
  5         158193  
  5         58  
6 5     5   5235 use Language::Befunge::Storage::Generic::Vec;
  5         58549  
  5         62  
7 5     5   5680 use IO::File;
  5         11097  
  5         787  
8 5     5   174 use Carp;
  5         11  
  5         260  
9 5     5   937 use Perl6::Export::Attrs;
  5         11806  
  5         65  
10 5     5   344 use Scalar::Util qw(weaken);
  5         10  
  5         491  
11              
12 5     5   27 use base 'Class::Accessor::Fast';
  5         10  
  5         1520  
13             __PACKAGE__->mk_accessors(
14             # basic values
15             qw{ boardsize codesize code color dims maxlen maxsize minsize },
16             # token currency stuff
17             qw{ tokens codecost itercost stackcost repeatcost threadcost },
18             # other objects we manage
19             qw{ blueprint physics interp }
20             );
21              
22 5     5   4233 use AI::Evolve::Befunge::Util;
  5         15  
  5         61  
23 5     5   12393 use aliased 'AI::Evolve::Befunge::Critter::Result' => 'Result';
  5         9  
  5         53  
24              
25             =head1 NAME
26              
27             AI::Evolve::Befunge::Critter - critter execution environment
28              
29              
30             =head1 DESCRIPTION
31              
32             This module is where the actual execution of Befunge code occurs. It
33             contains everything necessary to set up and run the code in a safe
34             (sandboxed) Befunge universe.
35              
36             This universe contains the Befunge code (obviously), as well as the
37             current board game state (if any). The Befunge code exists in the
38             negative vector space (with the origin at 0, Befunge code is below
39             zero on all axes). Board game info, if any, exists as a square (or
40             hypercube) which starts at the origin.
41              
42             The layout of befunge code space looks like this (for a 2d universe):
43              
44             |----------| |
45             |1 | |
46             |09876543210123456789|
47             ---+--------------------+---
48             -10|CCCCCCCCCC |-10
49             -9|CCCCCCCCCC| | -9
50             -8|CCCCCCCCCC | -8
51             -7|CCCCCCCCCC| | -7
52             -6|CCCCCCCCCC | -6
53             -5|CCCCCCCCCC| | -5
54             -4|CCCCCCCCCC | -4
55             -3|CCCCCCCCCC| | -3
56             -2|CCCCCCCCCC | -2
57             -1|CCCCCCCCCC| | -1
58             --0| - - - - -BBBB - - -|0--
59             1| BBBB | 1
60             2| BBBB | 2
61             3| BBBB | 3
62             4| | 4
63             5| | | 5
64             6| | 6
65             7| | | 7
66             8| | 8
67             9| | | 9
68             ---+--------------------+---
69             |09876543210123456789|
70             |1 | |
71             |----------| |
72              
73             Where:
74              
75             C is befunge code. This is the code under test.
76             B is boardgame data. Each location is binary 0, 1 or 2 (or
77             whatever tokens the game uses to represent
78             unoccupied spaces, and the various player
79             pieces). The B section only exists for
80             board game applications.
81              
82             Everything else is free for local use. Note that none of this is
83             write protected - a program is free to reorganize and/or overwrite
84             itself, the game board, results table, or anything else within the
85             space it was given.
86              
87             The universe is implemented as a hypercube of 1 or more dimensions.
88             The universe size is simply the code size times two, or the board size
89             times two, whichever is larger. If the board exists in 2 dimensions
90             but the code exists in more, the board will be represented as a square
91             starting at (0,0,...) and will only exist on plane 0 of the non-(X,Y)
92             axes.
93              
94             Several attributes of the universe are pushed onto the initial stack,
95             in the hopes that the critter can use this information to its
96             advantage. The values pushed are (in order from the top of the stack
97             (most accessible) to the bottom (least accessible)):
98              
99             * the Physics token (implying the rules of the game/universe)
100             * the number of dimensions this universe operates in
101             * The number of tokens the critter has left (see LIMITS, below)
102             * The iter cost (see LIMITS, below)
103             * The repeat cost (see LIMITS, below)
104             * The stack cost (see LIMITS, below)
105             * The thread cost (see LIMITS, below)
106             * The code size (a Vector)
107             * The maximum storage size (a Vector)
108             * The board size (a Vector) if operating in a boardgame universe
109              
110             If a Critter instance will have it's ->invoke() method called more
111             than once (for board game universes, it is called once per "turn"),
112             the storage model is not re-created. The critter is responsible for
113             preserving enough of itself to handle multiple invocations properly.
114             The Language::Befunge Interpreter and Storage model are preserved,
115             though a new IP is created each time, and (for board game universes)
116             the board data segment is refreshed each time.
117              
118              
119             =head1 LIMITS
120              
121             This execution environment is sandboxed. Every attempt is made to
122             keep the code under test from escaping the environment, or consuming
123             an unacceptable amount of resources.
124              
125             Escape is prevented by disabling all file operations, I/O operations,
126             system commands like fork() and system(), and commands which load
127             (potentially insecure) external Befunge semantics modules.
128              
129             Resource consumption is limited through the use of a currency system.
130             The way this works is, each critter starts out with a certain amount
131             of "Tokens" (the critter form of currency), and every action (like an
132             executed befunge instruction, a repeated command, a spawned thread,
133             etc) incurs a cost. When the number of tokens drops to 0, the critter
134             dies. This prevents the critter from getting itself (and the rest of
135             the system) into trouble.
136              
137             For reference, the following things are specifically tested for:
138              
139             =over 4
140              
141             =item Size of stacks
142              
143             =item Number of stacks
144              
145             =item Storage size (electric fence)
146              
147             =item Number of threads
148              
149             =item "k" command repeat count
150              
151             =item "j" command jump count
152              
153             =item "x" command dead IP checks (setting a null vector)
154              
155             =back
156              
157             Most of the above things will result in spending some tokens. There
158             are a couple of exceptions to this: a storage write outside the
159             confines of the critter's fence will result in the interpreter
160             crashing and the critter dying with it; similarly, a huge "j" jump
161             count will also kill the critter.
162              
163             The following commands are removed entirely from the interpreter's Ops
164             hash:
165              
166             , (Output Character)
167             . (Output Integer)
168             ~ (Input Character)
169             & (Input Integer)
170             i (Input File)
171             o (Output File)
172             = (Execute)
173             ( (Load Semantics)
174             ) (Unload Semantics)
175              
176              
177             =head1 CONSTRUCTOR
178              
179             =head2 new
180              
181             Critter->new(Blueprint => \$blueprint, Physics => \$physics,
182             IterPerTurn => 10000, MaxThreads => 100, Config => \$config,\n"
183             MaxStack => 1000,Color => 1, BoardSize => \$vector)";
184              
185             Create a new Critter object.
186              
187             The following arguments are required:
188              
189             =over 4
190              
191             =item Blueprint
192              
193             The blueprint object, which contains the code for this critter. Also
194             note, we also use the Blueprint object to cache a copy of the storage
195             object, to speed up creation of subsequent Critter objects.
196              
197             =item Physics
198              
199             The physics object controls the semantics of how the universe
200             operates. Mainly it controls the size of the game board (if any).
201              
202             =item Config
203              
204             The config object, see L.
205              
206             =item Tokens
207              
208             Tokens are the basic form of life currency in this simulation.
209             Critters have a certain amount of tokens at the beginning of a run
210             (controlled by this value), and they spend tokens to perform tasks.
211             (The amount of tokens required to perform a task depends on the
212             various "Cost" values, below.)
213              
214             When the number of tokens reaches 0, the critter dies (and the
215             interpreter is killed).
216              
217             =back
218              
219              
220             The following arguments are optional:
221              
222             =over 4
223              
224              
225             =item CodeCost
226              
227             This is the number of tokens the critter pays (up front, at birth
228             time) for the codespace it inhabits. If the blueprint's CodeSize
229             is (8,8,8), 8*8*8 = 512 spaces are taken up. If the CodeCost is 1,
230             that means the critter pays 512 tokens just to be born. If CodeCost
231             is 2, the critter pays 1024 tokens, and so on.
232              
233             If not specified, this will be pulled from the variable "codecost" in
234             the config file. If that can't be found, a default value of 1 is
235             used.
236              
237              
238             =item IterCost
239              
240             This is the number of tokens the critter pays for each command it
241             runs. It is a basic operational overhead, decremented for each clock
242             tick for each running thread.
243              
244             If not specified, this will be pulled from the variable "itercost" in
245             the config file. If that can't be found, a default value of 2 is
246             used.
247              
248              
249             =item RepeatCost
250              
251             This is the number of tokens the critter pays for each time a command
252             is repeated (with the "k" instruction). It makes sense for this value
253             to be lower than the IterCost setting, as it is somewhat more
254             efficient.
255              
256             If not specified, this will be pulled from the variable "repeatcost"
257             in the config file. If that can't be found, a default value of 1 is
258             used.
259              
260              
261             =item StackCost
262              
263             This is the number of tokens the critter pays for each time a value
264             is pushed onto the stack. It also has an effect when the critter
265             creates a new stack; the number of stack entries to be copied is
266             multiplied by the StackCost to determine the total cost.
267              
268             If not specified, this will be pulled from the variable "stackcost"
269             in the config file. If that can't be found, a default value of 1 is
270             used.
271              
272              
273             =item ThreadCost
274              
275             This is a fixed number of tokens the critter pays for spawning a new
276             thread. When a new thread is created, this cost is incurred, plus the
277             cost of duplicating all of the thread's stacks (see StackCost, above).
278             The new threads will begin incurring additional costs from the
279             IterCost (also above), when it begins executing commands of its own.
280              
281             If not specified, this will be pulled from the variable "threadcost"
282             in the config file. If that can't be found, a default value of 10 is
283             used.
284              
285              
286             =item Color
287              
288             This determines the color of the player, which (for board games)
289             indicates which type of piece the current player is able to play. It
290             has no other effect, and thus, it is not necessary for non-boardgame
291             physics models.
292              
293             If not specified, a default value of 1 is used.
294              
295              
296             =item BoardSize
297              
298             If specified, a board game of the given size (specified as a Vector
299             object) is created.
300              
301             =back
302              
303             =cut
304              
305             sub new {
306 39     39 1 11816 my $package = shift;
307 39         228 my %args = (
308             # defaults
309             Color => 1,
310             @_
311             );
312             # args
313 39         140 my $usage =
314             "Usage: $package->new(Blueprint => \$blueprint, Physics => \$physics,\n"
315             ." Tokens => 2000, BoardSize => \$vector, Config => \$config)";
316 39 100       145 croak $usage unless exists $args{Config};
317 38 100       211 $args{Tokens} = $args{Config}->config('tokens' , 2000) unless defined $args{Tokens};
318 38 100       236 $args{CodeCost} = $args{Config}->config("code_cost" , 1 ) unless defined $args{CodeCost};
319 38 100       202 $args{IterCost} = $args{Config}->config("iter_cost" , 2 ) unless defined $args{IterCost};
320 38 100       196 $args{RepeatCost} = $args{Config}->config("repeat_cost", 1 ) unless defined $args{RepeatCost};
321 38 100       182 $args{StackCost} = $args{Config}->config("stack_cost" , 1 ) unless defined $args{StackCost};
322 38 100       176 $args{ThreadCost} = $args{Config}->config("thread_cost", 10 ) unless defined $args{ThreadCost};
323              
324 38 100       200 croak $usage unless exists $args{Blueprint};
325 37 100       109 croak $usage unless exists $args{Physics};
326 36 100       131 croak $usage unless defined $args{Color};
327              
328 35         54 my $codelen = 1;
329 35         147 foreach my $d ($args{Blueprint}->size->get_all_components) {
330 76         336 $codelen *= $d;
331             }
332 35 100       130 croak "CodeCost must be greater than 0!" unless $args{CodeCost} > 0;
333 34 100       98 croak "IterCost must be greater than 0!" unless $args{IterCost} > 0;
334 33 100       94 croak "RepeatCost must be greater than 0!" unless $args{RepeatCost} > 0;
335 32 100       114 croak "StackCost must be greater than 0!" unless $args{StackCost} > 0;
336 31 100       87 croak "ThreadCost must be greater than 0!" unless $args{ThreadCost} > 0;
337 30         66 $args{Tokens} -= ($codelen * $args{CodeCost});
338 30 100       81 croak "Tokens must exceed the code size!" unless $args{Tokens} > 0;
339 29 100       134 croak "Code must be freeform! (no newlines)"
340             if $args{Blueprint}->code =~ /\n/;
341              
342 28         230 my $self = bless({}, $package);
343 28         91 $$self{blueprint} = $args{Blueprint};
344 28 100       96 $$self{boardsize} = $args{BoardSize} if exists $args{BoardSize};
345 28         97 $$self{code} = $$self{blueprint}->code;
346 28         167 $$self{codecost} = $args{CodeCost};
347 28         106 $$self{codesize} = $$self{blueprint}->size;
348 28         145 $$self{config} = $args{Config};
349 28         89 $$self{dims} = $$self{codesize}->get_dims();
350 28         81 $$self{itercost} = $args{IterCost};
351 28         61 $$self{repeatcost} = $args{RepeatCost};
352 28         64 $$self{stackcost} = $args{StackCost};
353 28         54 $$self{threadcost} = $args{ThreadCost};
354 28         56 $$self{tokens} = $args{Tokens};
355 28 100       81 if(exists($$self{boardsize})) {
356 23 100       131 $$self{dims} = $$self{boardsize}->get_dims()
357             if($$self{dims} < $$self{boardsize}->get_dims());
358             }
359 28 100       120 if($$self{codesize}->get_dims() < $$self{dims}) {
360             # upgrade codesize (keep it hypercubical)
361 17         232 $$self{codesize} = Language::Befunge::Vector->new(
362             $$self{codesize}->get_all_components(),
363 17         84 map { $$self{codesize}->get_component(0) }
364             (1..$$self{dims}-$$self{codesize}->get_dims())
365             );
366             }
367 28 100       94 if(exists($$self{boardsize})) {
368 23 100       97 if($$self{boardsize}->get_dims() < $$self{dims}) {
369             # upgrade boardsize
370 2         11 $$self{boardsize} = Language::Befunge::Vector->new(
371             $$self{boardsize}->get_all_components(),
372 1         8 map { 1 } (1..$$self{dims}-$$self{boardsize}->get_dims())
373             );
374             }
375             }
376              
377 28         66 $$self{color} = $args{Color};
378 28 100       86 croak "Color must be greater than 0" unless $$self{color} > 0;
379 27         58 $$self{physics} = $args{Physics};
380 27 100       103 croak "Physics must be a reference" unless ref($$self{physics});
381            
382             # set up our corral to be twice the size of our code or our board, whichever
383             # is bigger.
384 26         139 my $maxpos = Language::Befunge::Vector->new_zeroes($$self{dims});
385 26         88 foreach my $dim (0..$$self{dims}-1) {
386 57 100 100     389 if(!exists($$self{boardsize})
387             ||($$self{codesize}->get_component($dim) > $$self{boardsize}->get_component($dim))) {
388 35         161 $maxpos->set_component($dim, $$self{codesize}->get_component($dim));
389             } else {
390 22         89 $maxpos->set_component($dim, $$self{boardsize}->get_component($dim));
391             }
392             }
393 26         659 my $minpos = Language::Befunge::Vector->new_zeroes($$self{dims}) - $maxpos;
394 26         448 my $maxlen = 0;
395 26         77 foreach my $d (0..$$self{dims}-1) {
396 57         178 my $this = $maxpos->get_component($d) - $minpos->get_component($d);
397 57 100       178 $maxlen = $this if $this > $maxlen;
398             }
399 26         68 $$self{maxsize} = $maxpos;
400 26         77 $$self{minsize} = $minpos;
401 26         42 $$self{maxlen} = $maxlen;
402              
403 26         393 my $interp = Language::Befunge::Interpreter->new({
404             dims => $$self{dims},
405             storage => 'Language::Befunge::Storage::Generic::Vec'
406             });
407 26         82910 $$self{interp} = $interp;
408 26         55 $$self{codeoffset} = $minpos;
409 26         87 my $cachename = "storagecache-".$$self{dims};
410 26 100 100     150 if(exists($$self{blueprint}{cache})
411             && exists($$self{blueprint}{cache}{$cachename})) {
412 3         19 $$interp{storage} = $$self{blueprint}{cache}{$cachename}->_copy;
413             } else {
414 23 100       71 if($$self{dims} > 1) {
415             # split code into lines, pages, etc as necessary.
416 22         28 my @lines;
417 22         82 my $meas = $$self{codesize}->get_component(0);
418 22         42 my $dims = $$self{dims};
419 22         63 my @terms = ("", "\n", "\f");
420 22         75 push(@terms, "\0" x ($_-2)) for(3..$dims);
421              
422 22         283 push(@lines, substr($$self{code}, 0, $meas, "")) while length $$self{code};
423 22         55 foreach my $dim (0..$dims-1) {
424 46         68 my $offs = 1;
425 46         94 $offs *= $meas for (1..$dim-1);
426 46         118 for(my $i = $offs; $i <= scalar @lines; $i += $offs) {
427 220         556 $lines[$i-1] .= $terms[$dim];
428             }
429             }
430 22         89 $$self{code} = join("", @lines);
431             }
432              
433 23         167 $interp->get_storage->store($$self{code}, $$self{codeoffset});
434             # assign our corral size to the befunge space
435 23         4163 $interp->get_storage->expand($$self{minsize});
436 23         76 $interp->get_storage->expand($$self{maxsize});
437             # save off a copy of this befunge space for later reuse
438 23 100       143 $$self{blueprint}{cache} = {} unless exists $$self{blueprint}{cache};
439 23         104 $$self{blueprint}{cache}{$cachename} = $interp->get_storage->_copy;
440             }
441 26         442 my $storage = $interp->get_storage;
442 26         59 $$storage{maxsize} = $$self{maxsize};
443 26         54 $$storage{minsize} = $$self{minsize};
444             # store a copy of the Critter in the storage, so _expand (below) can adjust
445             # the remaining tokens.
446 26         70 $$storage{_ai_critter} = $self;
447 26         108 weaken($$storage{_ai_critter});
448             # store a copy of the Critter in the interp, so various command callbacks
449             # (below) can adjust the remaining tokens.
450 26         164 $$interp{_ai_critter} = $self;
451 26         67 weaken($$interp{_ai_critter});
452              
453 26         93 $interp->get_ops->{'{'} = \&AI::Evolve::Befunge::Critter::_block_open;
454 26         101 $interp->get_ops->{'j'} = \&AI::Evolve::Befunge::Critter::_op_flow_jump_to_wrap;
455 26         62 $interp->get_ops->{'k'} = \&AI::Evolve::Befunge::Critter::_op_flow_repeat_wrap;
456 26         67 $interp->get_ops->{'t'} = \&AI::Evolve::Befunge::Critter::_op_spawn_ip_wrap;
457              
458 26         72 my @invalid_meths = (',','.','&','~','i','o','=','(',')',map { chr } (ord('A')..ord('Z')));
  676         1381  
459 26         950 $$self{interp}{ops}{$_} = $$self{interp}{ops}{r} foreach @invalid_meths;
460              
461 26 100       98 if(exists($args{Commands})) {
462 23         34 foreach my $command (sort keys %{$args{Commands}}) {
  23         118  
463 42         76 my $cb = $args{Commands}{$command};
464 42         108 $$self{interp}{ops}{$command} = $cb;
465             }
466             }
467              
468              
469 26         49 my @params;
470             my @vectors;
471 26 100       155 push(@vectors, $$self{boardsize}) if exists $$self{boardsize};
472 26         53 push(@vectors, $$self{maxsize}, $$self{codesize});
473 26         45 foreach my $vec (@vectors) {
474 75         192 push(@params, $vec->get_all_components());
475 75         263 push(@params, 1) for($vec->get_dims()+1..$$self{dims});
476             }
477 26         119 push(@params, $$self{threadcost}, $$self{stackcost}, $$self{repeatcost},
478             $$self{itercost}, $$self{tokens}, $$self{dims});
479 26 100       112 push(@params, $self->physics->token) if defined $self->physics->token;
480              
481 26         677 $$self{interp}->set_params([@params]);
482              
483 26         376 return $self;
484             }
485              
486              
487             =head1 METHODS
488              
489             =head2 invoke
490              
491             my $rv = $critter->invoke($board);
492             my $rv = $critter->invoke();
493              
494             Run through a life cycle. If a board is specified, the board state
495             is copied into the appropriate place before execution begins.
496              
497             This should be run within an "eval"; if the critter causes an
498             exception, it will kill this function. It is commonly invoked by
499             L (see below), which handles exceptions properly.
500              
501             =cut
502              
503             sub invoke {
504 31     31 1 457 my ($self, $board) = @_;
505 31         75 delete($$self{move});
506 31 100       98 $self->populate($board) if defined $board;
507 31         107 my $rv = Result->new(name => $self->blueprint->name);
508 31         185 my $initial_ip = Language::Befunge::IP->new($$self{dims});
509 31         2534 $initial_ip->set_position($$self{codeoffset});
510 31         101 my $interp = $self->interp;
511 31         144 push(@{$initial_ip->get_toss}, @{$interp->get_params});
  31         78  
  31         190  
512 31         122 $interp->set_ips([$initial_ip]);
513 31         143 while($self->tokens > 0) {
514 670         3938 my $ip = shift @{$interp->get_ips()};
  670         1507  
515 670 100       1715 unless(defined($ip)) {
516 131         145 my @ips = @{$interp->get_newips};
  131         406  
517 131 100       303 last unless scalar @ips;
518 116         140 $ip = shift @ips;
519 116         424 $interp->set_ips([@ips]);
520             }
521 655 100       1699 unless(defined $$ip{_ai_critter}) {
522 30         56 $$ip{_ai_critter} = $self;
523 30         83 weaken($$ip{_ai_critter});
524             }
525 655 50       1806 last unless $self->spend($self->itercost);
526 655         1355 $interp->set_curip($ip);
527 655         1605 $interp->process_ip();
528 652 100       234107 if(defined($$self{move})) {
529 10         46 debug("move made: " . $$self{move} . "\n");
530 10         50 $rv->choice( $$self{move} );
531 10         140 return $rv;
532             }
533             }
534 18         101 debug("play timeout\n");
535 18         56 return $rv;
536             }
537              
538              
539             =head2 move
540              
541             my $rv = $critter->move($board, $score);
542              
543             Similar to invoke(), above. This function wraps invoke() in an
544             eval block, updates a scoreboard afterwards, and creates a "dead"
545             return value if the eval failed.
546              
547             =cut
548              
549             sub move {
550 29     29 1 1002 my ($self, $board) = @_;
551 29         44 my $rv;
552 29         46 local $@ = '';
553 29         56 eval {
554 29         791 $rv = $self->invoke($board);
555             };
556 29 100       4187 if($@ ne '') {
557 3         24 debug("eval error $@\n");
558 3         15 $rv = Result->new(name => $self->blueprint->name, died => 1);
559 3         8 my $reason = $@;
560 3         12 chomp $reason;
561 3         15 $rv->fate($reason);
562             }
563 29         187 $rv->tokens($self->tokens);
564 29         392 return $rv;
565             }
566              
567              
568             =head2 populate
569              
570             $critter->populate($board);
571              
572             Writes the board game state into the Befunge universe.
573              
574             =cut
575              
576             sub populate {
577 14     14 1 23 my ($self, $board) = @_;
578 14         65 my $storage = $$self{interp}->get_storage;
579 14         52 $storage->store($board->as_string);
580 14         666 $$self{interp}{_ai_board} = $board;
581 14         53 weaken($$self{interp}{_ai_board});
582             }
583              
584              
585             =head2 spend
586              
587             return unless $critter->spend($tokens * $cost);
588              
589             Attempts to spend a certain amount of the critter's tokens. Returns
590             true on success, false on failure.
591              
592             =cut
593              
594             sub spend {
595 953     953 1 4805 my ($self, $cost) = @_;
596 953         1299 $cost = int($cost);
597 953         2360 my $tokens = $self->tokens - $cost;
598             #debug("spend: cost=$cost resulting tokens=$tokens\n");
599 953 100       5349 return 0 if $tokens < 0;
600 947         4577 $self->tokens($tokens);
601 947         14935 return 1;
602             }
603              
604              
605             # sandboxing stuff
606             {
607 5     5   16049 no warnings 'redefine';
  5         13  
  5         339  
608              
609             # override Storage->expand() to impose bounds checking
610             my $_lbsgv_expand;
611 5     5   955 BEGIN { $_lbsgv_expand = \&Language::Befunge::Storage::Generic::Vec::expand; };
612             sub _expand {
613 124     124   5583 my ($storage, $v) = @_;
614 124 100       323 if(exists($$storage{maxsize})) {
615 38         63 my $min = $$storage{minsize};
616 38         55 my $max = $$storage{maxsize};
617 38 100       728 die "$v is out of bounds [$min,$max]!\n"
618             unless $v->bounds_check($min, $max);
619             }
620 116         1172 my $rv = &$_lbsgv_expand(@_);
621 116         4015 return $rv;
622             }
623             # redundant assignment avoids a "possible typo" warning
624             *Language::Befunge::Storage::Generic::Vec::XS::expand = \&_expand;
625             *Language::Befunge::Storage::Generic::Vec::XS::expand = \&_expand;
626             *Language::Befunge::Storage::Generic::Vec::expand = \&_expand;
627              
628             # override IP->spush() to impose stack size checking
629             my $_lbip_spush;
630 5     5   3199 BEGIN { $_lbip_spush = \&Language::Befunge::IP::spush; };
631             sub _spush {
632 288     288   19903 my ($ip, @newvals) = @_;
633 288         496 my $critter = $$ip{_ai_critter};
634 288 100       715 return $ip->dir_reverse unless $critter->spend($critter->stackcost * scalar @newvals);
635 287         738 my $rv = &$_lbip_spush(@_);
636 287         1951 return $rv;
637             }
638             *Language::Befunge::IP::spush = \&_spush;
639              
640             # override IP->ss_create() to impose stack count checking
641             sub _block_open {
642 2     2   85 my ($interp) = @_;
643 2         6 my $ip = $interp->get_curip;
644 2         4 my $critter = $$ip{_ai_critter};
645 2         7 my $count = $ip->svalue(1);
646 2 100       15 return $ip->dir_reverse unless $critter->spend($critter->stackcost * $count);
647 1         5 return Language::Befunge::Ops::block_open(@_);
648             }
649              
650             # override op_flow_jump_to to impose skip count checking
651             sub _op_flow_jump_to_wrap {
652 2     2   88 my ($interp) = @_;
653 2         6 my $ip = $interp->get_curip;
654 2         4 my $critter = $$interp{_ai_critter};
655 2         8 my $count = $ip->svalue(1);
656 2 100       17 return $ip->dir_reverse unless $critter->spend($critter->repeatcost * abs($count));
657 1         4 return Language::Befunge::Ops::flow_jump_to(@_);
658             }
659              
660             # override op_flow_repeat to impose loop count checking
661             sub _op_flow_repeat_wrap {
662 4     4   218 my ($interp) = @_;
663 4         13 my $ip = $interp->get_curip;
664 4         8 my $critter = $$interp{_ai_critter};
665 4         18 my $count = $ip->svalue(1);
666 4 100       45 return $ip->dir_reverse unless $critter->spend($critter->repeatcost * abs($count));
667 2         8 return Language::Befunge::Ops::flow_repeat(@_);
668             }
669              
670             # override op_spawn_ip to impose thread count checking
671             sub _op_spawn_ip_wrap {
672 2     2   119 my ($interp) = @_;
673 2         6 my $ip = $interp->get_curip;
674 2         3 my $critter = $$interp{_ai_critter};
675 2         4 my $cost = 0;$critter->threadcost;
  2         8  
676 2         12 foreach my $stack ($ip->get_toss(), @{$ip->get_ss}) {
  2         8  
677 2         6 $cost += scalar @$stack;
678             }
679 2         6 $cost *= $critter->stackcost;
680 2         14 $cost += $critter->threadcost;
681 2 100       12 return $ip->dir_reverse unless $critter->spend($cost);
682             # This is a hack; Storable can't deep copy our data structure.
683             # It will get re-added to both parent and child, next time around.
684 1         2 delete($$ip{_ai_critter});
685 1         6 return Language::Befunge::Ops::spawn_ip(@_);
686             }
687             }
688              
689             1;