File Coverage

blib/lib/Acme/Chef/Recipe.pm
Criterion Covered Total %
statement 158 163 96.9
branch 48 66 72.7
condition 3 6 50.0
subroutine 15 15 100.0
pod 9 9 100.0
total 233 259 89.9


line stmt bran cond sub pod time code
1              
2             package Acme::Chef::Recipe;
3              
4 7     7   56 use strict;
  7         14  
  7         354  
5 7     7   37 use warnings;
  7         10  
  7         170  
6              
7 7     7   34 use Carp;
  7         11  
  7         516  
8              
9 7     7   3515 use Acme::Chef::Ingredient;
  7         15  
  7         287  
10 7     7   12221 use Acme::Chef::Container;
  7         162  
  7         294  
11              
12             =head1 NAME
13              
14             Acme::Chef::Recipe - Internal module used by Acme::Chef
15              
16             =head1 SYNOPSIS
17              
18             use Acme::Chef;
19              
20             =head1 DESCRIPTION
21              
22             Please see L;
23              
24             =head2 METHODS
25              
26             This is list of methods in this package.
27              
28             =over 2
29              
30             =cut
31              
32 7     7   43 use vars qw/$VERSION %Grammars @GrammarOrder %Commands/;
  7         12  
  7         50241  
33             $VERSION = '1.00';
34              
35             @GrammarOrder = qw(
36             take_from add_dry put fold add remove combine divide
37             liquify_contents liquify stir_time stir_ingredient
38             mix clean pour refrigerate set_aside serve_with
39             until_verbed verb
40             );
41              
42             { # scope of grammar definition
43              
44             my $ord = qr/([1-9]\d*)(?:st|nd|rd|th)/;
45             my $ord_noncap = qr/[1-9]\d*(?:st|nd|rd|th)/;
46             my $ingr_noncap = qr/[\-\w][\- \w]*/;
47             my $ingr = qr/($ingr_noncap)/;
48             my $verb = qr/([\-\w]+)/;
49              
50             %Grammars = (
51              
52             put => sub {
53             my $recipe = shift;
54             local $_ = shift;
55             my $regex;
56             if (/ into (?:the )?(?:$ord )?mixing bowl$/) {
57             $regex = qr/^Put (?:the )?$ingr into (?:the )?(?:$ord )?mixing bowl$/;
58             } else {
59             $regex = qr/^Put (?:the )?$ingr$/;
60             }
61             /$regex/ or return();
62             $recipe->require_bowl($2||1);
63             $recipe->require_ingredient($1, 'put');
64             return 'put', $1, ($2||1);
65             },
66              
67             take_from => sub {
68             my $recipe = shift;
69             local $_ = shift;
70             /^Take $ingr from refrigerator$/ or return();
71             $recipe->require_ingredient($1);
72             return 'take_from', $1;
73             },
74              
75             fold => sub {
76             my $recipe = shift;
77             local $_ = shift;
78             /^Fold (?:the )?$ingr into (?:the )?(?:$ord )?mixing bowl$/ or return();
79             $recipe->require_bowl($2||1);
80             $recipe->require_ingredient($1, 'fold');
81             return 'fold', $1, ($2||1);
82             },
83              
84             add => sub {
85             my $recipe = shift;
86             local $_ = shift;
87             my $regex;
88             if (/ into (?:the )?(?:$ord )?mixing bowl$/) {
89             $regex = qr/^Add (?:the )?$ingr into (?:the )?(?:$ord )?mixing bowl$/;
90             } else {
91             $regex = qr/^Add (?:the )?$ingr()$/;
92             }
93             /$regex/ or return();
94             $recipe->require_bowl($2||1);
95             $recipe->require_ingredient($1, 'add');
96             return 'add', $1, ($2||1);
97             },
98              
99             remove => sub {
100             my $recipe = shift;
101             local $_ = shift;
102             my $regex;
103             if (/ from (?:the )?(?:$ord )?mixing bowl$/) {
104             $regex = qr/^Remove (?:the )?$ingr from (?:the )?(?:$ord )?mixing bowl$/;
105             } else {
106             $regex = qr/^Remove (?:the )?$ingr()$/;
107             }
108             /$regex/ or return();
109             $recipe->require_bowl($2||1);
110             $recipe->require_ingredient($1, 'remove');
111             return 'remove', $1, ($2||1);
112             },
113              
114             combine => sub {
115             my $recipe = shift;
116             local $_ = shift;
117             my $regex;
118             if (/ into (?:the )?(?:$ord )?mixing bowl$/) {
119             $regex = qr/^Combine (?:the )?$ingr into (?:the )?(?:$ord )?mixing bowl$/;
120             } else {
121             $regex = qr/^Combine (?:the )?$ingr()$/;
122             }
123             /$regex/ or return();
124             $recipe->require_bowl($2||1);
125             $recipe->require_ingredient($1, 'combine');
126             return 'combine', $1, ($2||1);
127             },
128              
129             divide => sub {
130             my $recipe = shift;
131             local $_ = shift;
132             my $regex;
133             if (/ into (?:the )?(?:$ord )?mixing bowl$/) {
134             $regex = qr/^Divide (?:the )?$ingr into (?:the )?(?:$ord )?mixing bowl$/;
135             } else {
136             $regex = qr/^Divide(?: the)?$ingr()$/;
137             }
138             /$regex/ or return();
139             $recipe->require_bowl($2||1);
140             $recipe->require_ingredient($1, 'divide');
141             return 'divide', $1, ($2||1);
142             },
143              
144             add_dry => sub {
145             my $recipe = shift;
146             local $_ = shift;
147             /^Add (?:the )?dry ingredients(?: into (?:the )?(?:$ord )?mixing bowl)?$/ or return();
148             $recipe->require_bowl($1||1);
149             return 'add_dry', ($1||1);
150             },
151              
152             liquify_contents => sub {
153             my $recipe = shift;
154             local $_ = shift;
155             /^Liquify (?:the )?contents of (?:the )?(?:$ord )?mixing bowl$/ or return();
156             $recipe->require_bowl($1||1);
157             return 'liquify_contents', ($1||1);
158             },
159              
160             liquify => sub {
161             my $recipe = shift;
162             local $_ = shift;
163             /^Liquify (?:the )?$ingr$/ or return();
164             $recipe->require_ingredient($1, 'liquify');
165             return 'liquify', $1;
166             },
167              
168             stir_time => sub {
169             my $recipe = shift;
170             local $_ = shift;
171             /^Stir (?:(?:the )?(?:$ord )?mixing bowl )?for (\d+) minutes?$/ or return();
172             $recipe->require_bowl($1||1);
173             return 'stir_time', $2, ($1||1);
174             },
175              
176             stir_ingredient => sub {
177             my $recipe = shift;
178             local $_ = shift;
179             /^Stir $ingr into (?:the )?(?:$ord )?mixing bowl$/ or return();
180             $recipe->require_bowl($2||1);
181             $recipe->require_ingredient($1, 'stir_ingredient');
182             return 'stir_ingredient', $1, ($2||1);
183             },
184              
185             mix => sub {
186             my $recipe = shift;
187             local $_ = shift;
188             /^Mix (?:the (?:$ord )?mixing bowl )well$/ or return();
189             $recipe->require_bowl($1||1);
190             return 'mix', ($1||1);
191             },
192              
193             clean => sub {
194             my $recipe = shift;
195             local $_ = shift;
196             /^Clean (?:the )?(?:$ord )?mixing bowl$/ or return();
197             $recipe->require_bowl($1||1);
198             return 'clean', ($1||1);
199             },
200              
201             pour => sub {
202             my $recipe = shift;
203             local $_ = shift;
204             /^Pour contents of (?:the )?((?:[1-9]\d*(?:st|nd|rd|th) )?)mixing bowl into (?:the )?((?:[1-9]\d*(?:st|nd|rd|th) )?)baking dish$/ or return();
205             my $m = $1 || 1;
206             my $b = $2 || 1;
207             $m =~ s/\D//g;
208             $b =~ s/\D//g;
209             $recipe->require_bowl($m);
210             $recipe->require_dish($b);
211             return 'pour', $m, $b;
212             },
213              
214             refrigerate => sub {
215             my $recipe = shift;
216             local $_ = shift;
217             /^Refrigerate(?: for (\d+) hours?)?$/ or return();
218             return 'refrigerate', (defined $1 ? $1 : 0);
219             },
220              
221             set_aside => sub {
222             my $recipe = shift;
223             local $_ = shift;
224             /^Set aside$/ or return();
225             return 'set_aside';
226             },
227              
228             serve_with => sub {
229             my $recipe = shift;
230             local $_ = shift;
231             /^Serve with $ingr$/ or return();
232             # $ingr is a recipe name here
233             return 'serve_with', lc($1);
234             },
235              
236             verb => sub {
237             my $recipe = shift;
238             local $_ = shift;
239             /^$verb (?:the )?$ingr$/ or return();
240             $recipe->require_ingredient($2, 'verb');
241             return 'verb', lc($1), $2;
242             },
243              
244             until_verbed => sub {
245             my $recipe = shift;
246             local $_ = shift;
247             /^$verb ((?:(?:the )?$ingr_noncap )?)until ${verb}ed$/ or return();
248             my $ing = (defined $2 ? $2 : '');
249             my $verbed = $3;
250             $verbed .= 'e' if not exists $recipe->{loops}{$verbed};
251             $ing =~ s/^the //;
252             $ing =~ s/ $//;
253             $recipe->require_ingredient($ing, 'until_verbed') if $ing ne '';
254             return 'until_verbed', $verbed, $ing;
255             },
256              
257             );
258              
259             }
260              
261             %Commands = (
262             put => sub {
263             my $recipe = shift;
264             my $data = shift;
265             $recipe -> {bowls} -> [$data -> [2] - 1]
266             ->
267             put( $recipe -> {ingredients} -> {$data -> [1]} );
268             return 1;
269             },
270            
271             take_from => sub {
272             my $recipe = shift;
273             my $data = shift;
274             local $/ = "\n";
275             my $value;
276             while (1) {
277             $value = ;
278             last if $value =~ /^\s*\.?\d+/;
279             }
280             $recipe -> {ingredients} -> {$data -> [1]}
281             -> value($value+0);
282             },
283              
284             fold => sub {
285             my $recipe = shift;
286             my $data = shift;
287             $recipe -> {bowls} -> [$data -> [2] - 1]
288             ->
289             fold( $recipe -> {ingredients} -> {$data -> [1]} );
290             return 1;
291             },
292              
293             add => sub {
294             my $recipe = shift;
295             my $data = shift;
296             $recipe -> {bowls} -> [$data -> [2] - 1]
297             ->
298             add( $recipe -> {ingredients} -> {$data -> [1]} );
299             return 1;
300             },
301              
302             remove => sub {
303             my $recipe = shift;
304             my $data = shift;
305             $recipe -> {bowls} -> [$data -> [2] - 1]
306             ->
307             remove( $recipe -> {ingredients} -> {$data -> [1]} );
308             return 1;
309             },
310              
311             combine => sub {
312             my $recipe = shift;
313             my $data = shift;
314             $recipe -> {bowls} -> [$data -> [2] - 1]
315             ->
316             combine( $recipe -> {ingredients} -> {$data -> [1]} );
317             return 1;
318             },
319              
320             divide => sub {
321             my $recipe = shift;
322             my $data = shift;
323             $recipe -> {bowls} -> [$data -> [2] - 1]
324             ->
325             divide( $recipe -> {ingredients} -> {$data -> [1]} );
326             return 1;
327             },
328              
329             add_dry => sub {
330             my $recipe = shift;
331             my $data = shift;
332             $recipe -> {bowls} -> [$data -> [1] - 1]
333             ->
334             put_sum(
335             grep { $_->type() eq 'dry' }
336             values %{ $recipe -> {ingredients} }
337             );
338             return 1;
339             },
340              
341             liquify => sub {
342             my $recipe = shift;
343             my $data = shift;
344             $recipe -> {ingredients} -> {$data -> [1]} -> liquify();
345             return 1;
346             },
347              
348             liquify_contents => sub {
349             my $recipe = shift;
350             my $data = shift;
351             $recipe -> {bowls} -> [$data -> [1] - 1] -> liquify_contents();
352             return 1;
353             },
354              
355             stir_time => sub {
356             my $recipe = shift;
357             my $data = shift;
358             $recipe -> {bowls} -> [$data -> [2] - 1]
359             ->
360             stir_time( $data -> [1] );
361             return 1;
362             },
363              
364             stir_ingredient => sub {
365             my $recipe = shift;
366             my $data = shift;
367             $recipe -> {bowls} -> [$data -> [2] - 1]
368             ->
369             stir_ingredient( $recipe -> {ingredients} -> {$data -> [1]} );
370             return 1;
371             },
372              
373             mix => sub {
374             my $recipe = shift;
375             my $data = shift;
376             $recipe -> {bowls} -> [$data -> [1] - 1] -> mix();
377             return 1;
378             },
379              
380             clean => sub {
381             my $recipe = shift;
382             my $data = shift;
383             $recipe -> {bowls} -> [$data -> [1] - 1] -> clean();
384             return 1;
385             },
386              
387             pour => sub {
388             my $recipe = shift;
389             my $data = shift;
390             my @stuff = $recipe -> {bowls} -> [$data -> [1] - 1] -> pour();
391              
392             $recipe -> {dishes} -> [$data -> [2] - 1] -> put( $_ ) foreach @stuff;
393              
394             return 1;
395             },
396              
397             refrigerate => sub {
398             my $recipe = shift;
399             my $data = shift;
400             my $serves = $recipe->{serves};
401             my $hours = $data->[1];
402             $serves ||= 0;
403             $hours ||= 0;
404             $recipe->{serves} = $hours if $serves < $hours;
405             return 'halt';
406             },
407              
408             set_aside => sub {
409             my $recipe = shift;
410             my $data = shift;
411              
412             return 'break';
413             },
414              
415             serve_with => sub {
416             my $recipe = shift;
417             my $data = shift;
418              
419             my $rec_recipe = $data->[1];
420              
421             return "recurse.$rec_recipe" ;
422             },
423              
424             verb => sub {
425             my $recipe = shift;
426             my $data = shift;
427              
428             my $verb = $data->[1];
429             my $ingr = $data->[2];
430             return "loop.$verb.$ingr";
431             },
432              
433             until_verbed => sub {
434             my $recipe = shift;
435             my $data = shift;
436              
437             my $verb = $data->[1];
438              
439             if ( exists $recipe->{ingredients}->{$data->[2]} ) {
440             my $ingr = $recipe->{ingredients}->{$data->[2]};
441             $ingr->value( $ingr->value() - 1 );
442             }
443              
444             return "endloop.$verb";
445             },
446              
447             );
448              
449             =item new
450              
451             Acme::Chef::Recipe constructor. Arguments are interpreted as key/value pairs
452             and used as object attributes.
453              
454             =cut
455              
456              
457             sub new {
458 96     96 1 134 my $proto = shift;
459 96   66     279 my $class = ref $proto || $proto;
460              
461 96         133 my $self = {};
462              
463 96 100       210 if (ref $proto) {
464 89         715 %$self = %$proto;
465              
466 89         165 $self->{bowls} = [ map { $_->new() } @{$self -> {bowls }} ];
  119         294  
  89         205  
467 89         133 $self->{dishes} = [ map { $_->new() } @{$self -> {dishes}} ];
  62         143  
  89         169  
468 84         283 $self->{loops} = { map { ( $_, $self->{loops}{$_} ) }
  89         195  
469 89         134 keys %{$self->{loops}} };
470              
471 89 50       235 if ( $self->{compiled} ) {
472 305         817 $self->{ingredients} = { map {
473 89         213 (
474             $_,
475             $self -> {ingredients} -> {$_} -> new()
476             )
477 89         96 } keys %{ $self->{ingredients} }
478             };
479             }
480             }
481              
482 96         288 my %args = @_;
483              
484 96         1501 %$self = (
485             compiled => 0,
486             name => '',
487             comments => '',
488             ingredients => '',
489             cooking_time => '',
490             temperature => '',
491             method => '',
492             serves => '',
493             output => '',
494             loops => {},
495             bowls => [],
496             dishes => [],
497             %$self,
498             %args,
499             );
500              
501 96         611 bless $self => $class;
502 96         250 return $self;
503             }
504              
505              
506             =item execute
507              
508             Executes the recipe (program). First argument should be a reference to a
509             hash of sous-recipes.
510              
511             =cut
512              
513              
514             sub execute {
515 53     53 1 72 my $self = shift;
516              
517 53         65 my $recipes = shift;
518              
519 53 50       136 $self->compile() unless $self->{compiled};
520              
521 53         62 my @loop_stack;
522              
523 53         55 my $max_pos = $#{$self->{method}};
  53         99  
524 53         72 my $exec_pos = 0;
525 53         59 while (1) {
526              
527 2528         3724 my $next_method = $self->{method}->[$exec_pos];
528             # print ' ' x scalar(@loop_stack), join(',', @$next_method),"\n";
529              
530 2528         5060 my $return = $Commands{$next_method->[0]}->($self, $next_method);
531              
532 2528 100       4914 last if $return eq 'halt';
533              
534 2520 100       9110 if ( $return =~ /^recurse\.([\-\w][\-\w ]*)/ ) {
    100          
    100          
    50          
535 36 50       95 exists $recipes->{$1}
536             or croak "Invalid recipe '$1' specified for recursion.";
537              
538 36         65 my $clone = $self->new();
539 36         142 my $sous_recipe = $recipes->{$1}->new(
540             bowls => $clone->{bowls},
541             dishes => $clone->{dishes},
542             );
543              
544 36         76 my $sous_done = $sous_recipe->execute( $recipes );
545              
546 36         83 $self->output( $sous_done->output() );
547              
548 36         85 $self -> {bowls} -> [0]
549             -> put( $sous_done -> first_bowl() -> new() -> pour() );
550              
551             } elsif ( $return =~ /^loop\.([^\.]+)\.([^\.]+)/ ) {
552 624         899 my $verb = $1;
553 624         757 my $ingr = $2;
554              
555 624         944 push @loop_stack, $verb;
556              
557 624 100       1778 if ( not $self -> {ingredients} -> {$ingr} -> value() ) {
558 45         58 pop @loop_stack;
559 45         113 $exec_pos = $self -> {loops} -> {$verb} -> {end};
560             }
561              
562             } elsif ( $return =~ /^endloop\.([^\.]+)/ ) {
563 576         895 my $verb = $1;
564              
565 576         1203 $exec_pos = $self -> {loops} -> {$verb} -> {start} - 1;
566              
567             } elsif ( $return =~ /^break/ ) {
568 0         0 my $verb = pop @loop_stack;
569 0         0 $exec_pos = $self -> {loops} -> {$verb} -> {end};
570             }
571              
572 2520         2701 $exec_pos++;
573 2520 100       4636 last if $exec_pos > $max_pos;
574             }
575              
576 53 100       124 if ( $self->{serves} ) {
577 17         67 foreach my $serve ( 0..($self->{serves}-1) ) {
578 26 50       38 last if $serve > $#{$self->{dishes}};
  26         76  
579 26         110 my $string = $self->{dishes}->[$serve]->print();
580 26         78 $self->{output} .= $string;
581             }
582             }
583              
584 53         185 return $self;
585             }
586              
587             =item first_bowl
588              
589             Returns the first bowl of the recipe.
590              
591             =cut
592              
593             sub first_bowl {
594 36     36 1 36 my $self = shift;
595 36         106 return $self->{bowls}->[0];
596             }
597              
598             =item require_ingredient
599              
600             First argument must be an ingredient object. Second may be a string indicating
601             the location of the requirement. Throws a fatal error if the ingredient is not
602             present.
603              
604             =cut
605              
606             sub require_ingredient {
607 144     144 1 173 my $self = shift;
608 144         232 my $ingredient = shift;
609 144         252 my $sub = shift;
610              
611 144 0 33     669 (defined $ingredient and exists $self->{ingredients}{$ingredient})
    0          
    50          
612             or croak "Unknown ingredient '".(defined$ingredient?$ingredient:'').
613             "' required for recipe '$self->{name}'".
614             (defined $sub?" in '$sub'":'').".";
615              
616 144         215 return $self;
617             }
618              
619             =item output
620              
621             Mutator for the Recipe output.
622              
623             =cut
624              
625             sub output {
626 89     89 1 95 my $self = shift;
627              
628 89 100       190 $self->{output} .= shift if @_;
629              
630 89         1187 return $self->{output};
631             }
632              
633             =item require_bowl
634              
635             First argument must be a number of bowls. Additional bowls are added to the
636             recipe if it currently has less than this number of bowls.
637              
638             =cut
639              
640             sub require_bowl {
641 166     166 1 240 my $self = shift;
642 166         205 my $no = shift;
643              
644 166 100       165 return if @{$self->{bowls}} >= $no;
  166         533  
645              
646 17         24 while (@{$self->{bowls}} < $no) {
  34         100  
647 17         24 push @{$self->{bowls}}, Acme::Chef::Container->new();
  17         116  
648             }
649              
650 17         27 return $self;
651             }
652              
653              
654             =item require_dish
655              
656             First argument must be a number of dishes. Additional dishes are added to the
657             recipe if it currently has less than this number of dishes.
658              
659             =cut
660              
661             sub require_dish {
662 16     16 1 27 my $self = shift;
663 16         22 my $no = shift;
664              
665 16 100       20 return if @{$self->{dishes}} >= $no;
  16         54  
666              
667 9         15 while (@{$self->{dishes}} < $no) {
  18         59  
668 9         17 push @{$self->{dishes}}, Acme::Chef::Container->new();
  9         39  
669             }
670              
671 9         24 return $self;
672             }
673              
674             =item recipe_name
675              
676             Mutator for the recipe name.
677              
678             =cut
679              
680             sub recipe_name {
681 13     13 1 21 my $self = shift;
682              
683 13 50       39 $self->{name} = shift if @_;
684              
685 13         97 return $self->{name};
686             }
687              
688              
689             =item compile
690              
691             Tries to compile the recipe. Returns 0 on error or if the recipe was
692             already compiled. Returns the compiled recipe if the compilation succeeded.
693              
694             =cut
695              
696             sub compile {
697 7     7 1 16 my $self = shift;
698              
699 7 50       51 return 0 if $self->{compiled};
700              
701 7         44 my @ingredients = split /\n/, $self->{ingredients};
702              
703 7         18 shift @ingredients; # remove header line
704              
705 7 50       23 @ingredients or croak "Failed compiling recipe. No ingredients specified.";
706              
707 7         14 my %ingredients;
708 7         13 my $ingredient_no = 0;
709              
710 7         18 foreach (@ingredients) {
711 44         48 $ingredient_no++;
712              
713 44         45 my $value;
714 44 100       199 if (s/^[ ]*(\d+)[ ]//) {
715 43         84 $value = $1;
716             } else {
717 1         1 $value = undef;
718             }
719              
720 44         59 my $measure_type = '';
721 44         105 foreach my $type ( keys %Acme::Chef::Ingredient::MeasureTypes ) {
722 88 50       1160 if ( s/^\Q$type\E[ ]// ) {
723 0         0 $measure_type = $type;
724 0         0 last;
725             }
726             }
727              
728 44         73 my $measure = '';
729 44         160 foreach my $meas ( keys %Acme::Chef::Ingredient::Measures ) {
730 538 100       995 next if $meas eq '';
731              
732 506 100       5146 if ( s/^\Q$meas\E[ ]// ) {
733 16         27 $measure = $meas;
734 16         28 last;
735             }
736             }
737              
738             /[ ]*([\-\w][\- \w]*)[ ]*$/
739 44 50       243 or croak "Invalid ingredient specification (ingredient no. $ingredient_no, name).";
740              
741 44         87 my $ingredient_name = $1;
742              
743 44         172 my $ingredient = Acme::Chef::Ingredient->new(
744             name => $ingredient_name,
745             value => $value,
746             measure => $measure,
747             measure_type => $measure_type,
748             );
749              
750 44         180 $ingredients{$ingredient_name} = $ingredient;
751             }
752              
753 7         140 $self->{ingredients} = \%ingredients;
754              
755 7         560 $self->{method} =~ s/\s+/ /g;
756              
757 7         470 my @steps = split /\s*\.\s*/, $self->{method};
758              
759 7         68 shift @steps; # remove "Method."
760              
761 7         15 my $step_no = 0;
762 7         50 foreach my $step (@steps) {
763 183         198 $step_no++;
764              
765 183         263 foreach my $grammar (@GrammarOrder) {
766 1307         2764 my @res = $Grammars{$grammar}->($self, $step);
767 1307 100       3102 @res or next;
768              
769 183 100       516 if ( $res[0] eq 'verb' ) {
    100          
770 5         10 my $verb = $res[1];
771 5         9 my $ingr = $res[2];
772              
773 5         32 $self->{loops}->{$verb} = {start => ($step_no-1), test => $ingr};
774             } elsif ( $res[0] eq 'until_verbed' ) {
775 5         10 my $verb = $res[1];
776 5 50       19 exists $self->{loops}->{$verb}
777             or croak "Loop end without loop start '$verb'.";
778              
779 5         15 $self->{loops}->{$verb}->{end} = $step_no - 1;
780             }
781              
782 183         489 $step = [@res];
783 183         336 last;
784             }
785              
786 183 50       464 croak "Invalid method step (step no. $step_no): '$step'."
787             if not ref $step eq 'ARRAY';
788             }
789              
790 7 50       19 if ( grep { not exists $self->{loops}{$_}{end} } keys %{$self->{loops}} ) {
  5         28  
  7         35  
791 0         0 croak "Not all loop starting points have matching ends.";
792             }
793              
794 7         28 $self->{method} = \@steps;
795              
796 7         17 $self->{compiled} = 1;
797              
798 7         119 return $self;
799             }
800              
801              
802             __END__