File Coverage

blib/lib/Iterator/Flex/Base.pm
Criterion Covered Total %
statement 286 310 92.2
branch 50 72 69.4
condition 23 38 60.5
subroutine 49 52 94.2
pod 16 21 76.1
total 424 493 86.0


line stmt bran cond sub pod time code
1             package Iterator::Flex::Base;
2              
3             # ABSTRACT: Iterator object
4              
5 54     54   677959 use v5.28;
  54         320  
6 54     54   383 use strict;
  54         377  
  54         1775  
7 54     54   344 use warnings;
  54         217  
  54         3346  
8              
9 54     54   2947 use experimental qw( signatures postderef declared_refs );
  54         12520  
  54         429  
10              
11             our $VERSION = '0.33';
12              
13 54     54   20920 use Ref::Util;
  54         22894  
  54         6895  
14 54     54   390 use Scalar::Util;
  54         118  
  54         2518  
15 54     54   360 use List::Util;
  54         108  
  54         4420  
16 54     54   9281 use Role::Tiny ();
  54         88472  
  54         1312  
17 54     54   7322 use Role::Tiny::With ();
  54         4323  
  54         1156  
18 54     54   6568 use Module::Runtime ();
  54         19341  
  54         3262  
19              
20 54         24945 use Iterator::Flex::Utils qw (
21             :default
22             :ExhaustionActions
23             :GeneralParameters
24             :RegistryIndices
25             :IterAttrs
26             :IterStates
27             :InterfaceParameters
28             :SignalParameters
29             load_role
30             throw_failure
31 54     54   10069 );
  54         129  
32              
33 54     54   27416 use namespace::clean;
  54         805957  
  54         411  
34              
35             use overload
36 180     180   237 '<>' => sub ( $self, $, $ ) { &{$self}() },
  180         360  
  180         37938  
  180         265  
  180         196  
37             fallback => 0,
38 1120     1120   2718 bool => sub { 1 },
39              
40             # these are required for the perldb to not barf
41             # see https://github.com/Perl/perl5/issues/23486
42 0     0   0 eq => sub { 0 },
43 0     0   0 q{""} => sub { q{} },
44 54     54   91903 ;
  54         159  
  54         904  
45              
46             # We separate constructor parameters into two categories:
47             #
48             # 1. those that are used to construct the iterator
49             # 2. those that specify what happens when the iterator signals exhaustion
50             #
51             # Category #2 may be expanded. Category #2 parameters are *not* passed
52             # to the iterator class construct* routines
53              
54              
55 274     274 0 385079 sub new ( $class, $state = undef, $general = {} ) {
  274         466  
  274         506  
  274         498  
  274         479  
56 274         1014 return $class->new_from_state( $state, $general );
57             }
58              
59 295     295 0 537 sub new_from_state ( $class, $state, $general ) {
  295         516  
  295         487  
  295         457  
  295         444  
60 295         1141 return $class->new_from_attrs( $class->construct( $state ), $general );
61             }
62              
63 385     385 0 801 sub new_from_attrs ( $class, $in_ipar = {}, $in_gpar = {} ) { ## no critic (ExcessComplexity)
  385         701  
  385         674  
  385         708  
  385         613  
64              
65 385         2006 my %ipar = $in_ipar->%*;
66 385         1300 my %gpar = $in_gpar->%*;
67              
68 385         1519 $class->_validate_interface_pars( \%ipar );
69 385         1408 $class->_validate_signal_pars( \%gpar );
70              
71 385   100     1779 my @roles = ( delete( $ipar{ +_ROLES } ) // [] )->@*;
72              
73 385   100     3982 $gpar{ +ERROR } //= [THROW];
74             $gpar{ +ERROR } = [ $gpar{ +ERROR } ]
75 385 50       1143 unless Ref::Util::is_arrayref( $gpar{ +ERROR } );
76              
77 385 50       1288 if ( $gpar{ +ERROR }[0] eq THROW ) {
78 385         860 push @roles, 'Error::Throw';
79             }
80             else {
81 0         0 throw_failure( q{unknown specification of iterator error signaling behavior:}, $gpar{ +ERROR }[0] );
82             }
83              
84 385   100     1534 my $exhaustion_action = $gpar{ +EXHAUSTION } // [ ( +RETURN ) => undef ];
85              
86             my @exhaustion_action
87 385 100       1283 = Ref::Util::is_arrayref( $exhaustion_action )
88             ? ( $exhaustion_action->@* )
89             : ( $exhaustion_action );
90              
91 385         921 $gpar{ +EXHAUSTION } = \@exhaustion_action;
92              
93 385 100       977 if ( $exhaustion_action[0] eq RETURN ) {
    50          
94 284         597 push @roles, 'Exhaustion::Return';
95             }
96             elsif ( $exhaustion_action[0] eq THROW ) {
97              
98 101 100 66     382 push @roles,
99             @exhaustion_action > 1 && $exhaustion_action[1] eq PASSTHROUGH
100             ? 'Exhaustion::PassthroughThrow'
101             : 'Exhaustion::Throw';
102             }
103             else {
104 0         0 throw_failure( parameter => "unknown exhaustion action: $exhaustion_action[0]" );
105             }
106              
107 385 100       1133 if ( defined( my $par = $ipar{ +METHODS } ) ) {
108              
109 17         2310 require Iterator::Flex::Method;
110              
111 17 50       62 throw_failure( parameter => q{value for methods parameter must be a hash reference} )
112             unless Ref::Util::is_hashref( $par );
113              
114 17         68 for my $name ( keys $par->%* ) {
115              
116 29         64 my $code = $par->{$name};
117              
118 29 50       75 throw_failure( parameter => "value for 'methods' parameter key '$name' must be a code reference" )
119             unless Ref::Util::is_coderef( $code );
120              
121             # create role for the method
122 29         54 my $role = eval { Iterator::Flex::Method::Maker( $name, name => $name ) };
  29         101  
123              
124 29 100       1866 if ( $@ ne q{} ) {
125 20         1854 my $error = $@;
126 20 50 33     173 die $error
127             unless Ref::Util::is_blessed_ref( $error )
128             && $error->isa( 'Iterator::Flex::Failure::RoleExists' );
129 20         496 $role = $error->payload;
130             }
131              
132 29         189 push @roles, q{+} . $role; # need '+', as these are fully qualified role module names.
133             }
134             }
135              
136 385         920 @roles = map { load_role( $_, $class->_role_namespaces ) } @roles;
  1331         29180  
137 385         10931 $class = Role::Tiny->create_class_with_roles( $class, @roles );
138              
139 385 50       212574 unless ( $class->can( '_construct_next' ) ) {
140 0         0 throw_failure(
141             class => "Constructed class '$class' does not provide the required _construct_next method\n" );
142             }
143              
144 385 50       1444 unless ( $class->does( 'Iterator::Flex::Role::State' ) ) {
145 0         0 throw_failure( class => "Constructed class '$class' does not provide a State role\n" );
146             }
147              
148 385   66     7154 $ipar{ +_NAME } //= $class;
149              
150 385         4599 my $self = bless $class->_construct_next( \%ipar, \%gpar ), $class;
151              
152             throw_failure(
153             parameter => q{attempt to register an iterator subroutine which has already been registered.} )
154 385 50       1579 if exists $REGISTRY{ refaddr $self };
155              
156 385         1269 my $regentry = $REGISTRY{ refaddr $self } = [];
157              
158             # convert to arrays. some of the parameter values are weak
159             # references so make sure we don't unweaken them
160 385         630 my @ipar;
161 385         1601 for my $key ( keys %ipar ) {
162 2919         6181 $ipar[ $RegIterationIndexMap{$key} ] = $ipar{$key};
163             Scalar::Util::weaken $ipar[ $RegIterationIndexMap{$key} ]
164             if Ref::Util::is_ref( $ipar{$key} )
165 2919 100 100     9829 && Scalar::Util::isweak( $ipar{$key} );
166             }
167 385         1371 my @gpar;
168 385         1301 for my $key ( keys %gpar ) {
169 861         2055 $gpar[ $RegGeneralParameterIndexMap{$key} ] = $gpar{$key};
170             Scalar::Util::weaken $gpar[ $RegGeneralParameterIndexMap{$key} ]
171             if Ref::Util::is_ref( $gpar{$key} )
172 861 50 66     6883 && Scalar::Util::isweak( $gpar{$key} );
173             }
174              
175 385         1111 $regentry->[REG_ITERATOR] = \@ipar;
176 385         723 $regentry->[REG_GENERAL] = \@gpar;
177              
178 385         1691 $self->_clear_state;
179              
180 385         6282 return $self;
181             }
182              
183 385     385   704 sub _validate_interface_pars ( $class, $pars ) {
  385         629  
  385         608  
  385         561  
184 385         842 state %InterfaceParameters = {}->%{ +INTERFACE_PARAMETER_VALUES };
185              
186 385         1591 my @bad = grep { !exists $InterfaceParameters{$_} } keys $pars->%*;
  2835         5220  
187              
188 385 50       1181 throw_failure( parameter => "unknown interface parameters: @{[ join ', ', @bad ]}" )
  0         0  
189             if @bad;
190              
191 0         0 throw_failure( parameter => "@{[ _ROLES ]} must be an arrayref" )
192 385 50 33     1253 if defined $pars->{_ROLES} && !Ref::Util::is_arrayref( $pars->{ +_ROLES } );
193              
194 385 100       1125 if ( defined( my $par = $pars->{ +_DEPENDS } ) ) {
195 98 100       432 $pars->{ +_DEPENDS } = $par = [$par] unless Ref::Util::is_arrayref( $par );
196             throw_failure( parameter => "dependency #$_ is not an iterator object" )
197 98 50   138   818 unless List::Util::all { $class->_is_iterator( $_ ) } $par->@*;
  138         599  
198             }
199              
200 385         1088 return;
201             }
202              
203 385     385   607 sub _validate_signal_pars ( $class, $pars ) {
  385         657  
  385         649  
  385         572  
204 385         729 state %SignalParameters = {}->%{ +SIGNAL_PARAMETER_VALUES };
205 385         953 my @bad = grep { !exists $SignalParameters{$_} } keys $pars->%*;
  258         533  
206              
207 385 50       996 throw_failure( parameter => "unknown signal parameters: @{[ join ', ', @bad ]}" )
  0         0  
208             if @bad;
209             }
210              
211              
212 382     382   338864 sub DESTROY ( $self ) {
  382         629  
  382         569  
213              
214 382 50       975 if ( defined $self ) {
215 382         7905 delete $REGISTRY{ refaddr $self };
216             }
217             }
218              
219 2     2   2 sub _name ( $self ) {
  2         5  
  2         4  
220 2         21 $REGISTRY{ refaddr $self }[REG_ITERATOR][REG_ITER__NAME];
221             }
222              
223              
224              
225              
226              
227              
228              
229              
230              
231              
232              
233              
234              
235              
236              
237              
238              
239              
240              
241              
242              
243              
244             # TODO: this is too restrictive. It should allow simple coderefs, or
245             # things with a next or __next__.
246              
247 138     138   238 sub _is_iterator ( $, $obj ) {
  138         239  
  138         201  
248 138   33     949 return Ref::Util::is_blessed_ref( $obj ) && $obj->isa( __PACKAGE__ );
249             }
250              
251              
252              
253              
254              
255              
256              
257              
258              
259              
260              
261              
262              
263 0     0   0 sub __iter__ ( $self ) {
  0         0  
  0         0  
264 0         0 return $REGISTRY{ refaddr $self }[REG_ITERATOR][REG_ITER_NEXT];
265             }
266              
267              
268              
269              
270              
271              
272              
273              
274              
275              
276              
277              
278              
279 16     16 1 29 sub may ( $self, $meth ) {
  16         19  
  16         37  
  16         17  
280              
281 16         37 my \@attributes = $REGISTRY{ refaddr $self }[REG_ITERATOR];
282 16   100     52 my $may = $attributes[REG_ITER_MAY_METHOD] //= {};
283              
284             return $may->{"_may_$meth"}
285             //= defined $attributes[REG_ITER__DEPENDS]
286 3     3   9 ? !List::Util::first { !$_->may( $meth ) } $attributes[REG_ITER__DEPENDS]->@*
287 16 100 33     116 : defined $attributes[ $RegIterationIndexMap{$meth} ];
288             }
289              
290              
291              
292              
293              
294              
295              
296              
297              
298              
299              
300              
301              
302              
303              
304              
305             sub _namespaces {
306 3     3   244777 return 'Iterator::Flex';
307             }
308              
309              
310              
311              
312              
313              
314              
315              
316              
317              
318              
319              
320              
321              
322              
323              
324             sub _role_namespaces {
325 1765     1765   5046 return 'Iterator::Flex::Role';
326             }
327              
328              
329              
330              
331              
332              
333              
334              
335              
336              
337              
338              
339              
340              
341              
342              
343              
344 73     73   367 sub _add_roles ( $class, @roles ) {
  73         192  
  73         241  
  73         161  
345             Role::Tiny->apply_roles_to_package( $class,
346 73         262 map { load_role( $_, $class->_role_namespaces ) } @roles );
  421         6869  
347             }
348              
349 198     198   330 sub _apply_method_to_depends ( $self, $meth ) {
  198         391  
  198         326  
  198         273  
350              
351 198 100       982 if ( defined( my $depends = $REGISTRY{ refaddr $self }[REG_ITERATOR][REG_ITER__DEPENDS] ) ) {
352             # first check if dependencies have method
353 63     84   544 my $cant = List::Util::first { !$_->can( $meth ) } $depends->@*;
  84         504  
354 63 100       365 throw_failure( Unsupported => "dependency: @{[ $cant->_name ]} does not have a '$meth' method" )
  1         6  
355             if $cant;
356              
357             # now apply the method
358 62         324 $_->$meth foreach $depends->@*;
359             }
360             }
361              
362              
363              
364              
365              
366              
367              
368              
369              
370 1206     1206 1 48116 sub is_exhausted ( $self ) {
  1206         1833  
  1206         1630  
371 1206         3127 $self->get_state == IterState_EXHAUSTED;
372             }
373              
374              
375              
376              
377              
378              
379              
380              
381              
382              
383              
384              
385              
386              
387 564     564 0 818 sub set_exhausted ( $self ) {
  564         874  
  564         804  
388 564         1648 $self->set_state( IterState_EXHAUSTED );
389             }
390              
391              
392              
393              
394              
395              
396              
397              
398              
399              
400              
401              
402              
403              
404 605     605   898 sub _clear_state ( $self ) {
  605         889  
  605         847  
405 605         2285 $self->set_state( IterState_CLEAR );
406             }
407              
408              
409              
410              
411              
412              
413              
414              
415              
416 2     2 1 1563 sub is_error ( $self ) {
  2         3  
  2         2  
417 2         7 $self->get_state == IterState_ERROR;
418             }
419              
420              
421              
422              
423              
424              
425              
426              
427              
428              
429              
430              
431              
432              
433 2     2 0 2 sub set_error ( $self ) {
  2         2  
  2         3  
434 2         4 $self->set_state( IterState_ERROR );
435             }
436              
437              
438              
439              
440              
441              
442              
443              
444              
445 1     1 1 2 sub throws_on_exhaustion( $self ) {
  1         2  
  1         2  
446 1         12 return $REGISTRY{ refaddr $self }[REG_GENERAL][REG_GP_EXHAUSTION][0] eq THROW;
447              
448             }
449              
450              
451              
452              
453              
454              
455              
456              
457              
458 1     1 1 3 sub returns_on_exhaustion( $self ) {
  1         3  
  1         2  
459 1         6 return $REGISTRY{ refaddr $self }[REG_GENERAL][REG_GP_EXHAUSTION][0] eq RETURN;
460              
461             }
462              
463              
464              
465              
466              
467              
468              
469              
470              
471 1     1 1 3 sub buffer ( $self, $n = 0, $pars = {} ) {
  1         3  
  1         3  
  1         3  
  1         3  
472 1         623 require Iterator::Flex::Buffer;
473 1         8 Iterator::Flex::Buffer->new( $self, $n, $pars );
474             }
475              
476              
477              
478              
479              
480              
481              
482              
483              
484              
485 1     1 1 3 sub cache ( $self, $pars = {} ) {
  1         3  
  1         2  
  1         2  
486 1         711 require Iterator::Flex::Cache;
487 1         9 Iterator::Flex::Cache->new( $self, $pars );
488             }
489              
490             sub icache;
491             *icache = \&cache;
492              
493              
494              
495              
496              
497              
498              
499              
500              
501              
502              
503              
504 1     1 1 3 sub cat ( $self, @args ) {
  1         3  
  1         2  
  1         3  
505 1         764 require Iterator::Flex::Cat;
506 1         9 Iterator::Flex::Cat->new( $self, @args );
507             }
508              
509             sub chain;
510             *chain = \&cat;
511              
512              
513              
514              
515              
516              
517              
518              
519              
520              
521              
522              
523 3     3 1 9 sub chunk ( $self, $pars = {} ) {
  3         8  
  3         7  
  3         6  
524 3         1391 require Iterator::Flex::Chunk;
525 3         19 Iterator::Flex::Chunk->new( $self, $pars );
526             }
527              
528             sub ichunk;
529             *ichunk = \&chunk;
530              
531             sub batch;
532             *batch = \&chunk;
533              
534              
535              
536              
537              
538              
539              
540              
541              
542              
543              
544              
545              
546              
547 115     115 1 14639 sub drain ( $self, $n = undef ) {
  115         223  
  115         199  
  115         202  
548              
549 115         204 my @values;
550              
551             eval {
552 115 100       347 if ( $n ) {
553 41         128 while ( $n-- ) {
554 148         437 push @values, $self->next;
555 148 100       419 if ( $self->is_exhausted ) {
556 1         3 pop @values;
557 1         4 last;
558             }
559             }
560             }
561             else {
562 74         188 while ( 1 ) {
563 501         1296 push @values, $self->next;
564 498 100       1178 if ( $self->is_exhausted ) {
565 71         150 pop @values;
566 71         152 last;
567             }
568             }
569             }
570 112         360 1;
571 115 100       222 } or do {
572 3 100 66     897 die $@
573             unless Ref::Util::is_blessed_ref( $@ )
574             && $@->isa( 'Iterator::Flex::Failure::Exhausted' );
575             };
576              
577 113         662 return \@values;
578             }
579              
580              
581              
582              
583              
584              
585              
586              
587              
588 1     1 1 4 sub flatten ( $self, $pars = {} ) {
  1         2  
  1         3  
  1         3  
589 1         651 require Iterator::Flex::Flatten;
590 1         7 return Iterator::Flex::Flatten->new( $self, $pars );
591             }
592              
593             sub iflatten;
594             *iflatten = \*flatten;
595              
596              
597              
598              
599              
600              
601              
602              
603              
604              
605 1     1 1 3 sub foreach ( $self, $code ) { ## no critic (BuiltinHomonyms)
  1         2  
  1         2  
  1         3  
606              
607 1 50       7 if ( $self->throws_on_exhaustion ) {
    50          
608             eval {
609 0         0 local $_; ## no critic (InitializationForLocalVars)
610 0         0 while ( $_ = $self->() ) { $code->() }
  0         0  
611 0         0 1;
612 0 0       0 } or do {
613 0 0 0     0 die $@
614             unless Ref::Util::is_blessed_ref( $@ )
615             && $@->isa( 'Iterator::Flex::Failure::Exhausted' );
616             };
617             }
618             elsif ( $self->returns_on_exhaustion ) {
619              
620             # optimize for when sentinel is the undefined value
621 1 50       5 if ( !defined $self->sentinel ) {
622 1         3 local $_; ## no critic (InitializationForLocalVars)
623 1         5 $code->() while defined( $_ = $self->() );
624             }
625              
626             # yeah, this is too slow. should adapt the logic in Wrap::Return.
627             else {
628 0         0 local $_; ## no critic (InitializationForLocalVars)
629 0         0 $_ = $self->();
630 0         0 until ( $self->is_exhausted ) { ## no critic (UntilBlock)
631 0         0 $code->();
632 0         0 $_ = $self->();
633             }
634             }
635             }
636             }
637              
638              
639              
640              
641              
642              
643              
644              
645              
646              
647 9     9 1 98 sub gather ( $self, $code, $pars = {} ) {
  9         21  
  9         16  
  9         24  
  9         18  
648 9         1361 require Iterator::Flex::Gather;
649 9         53 Iterator::Flex::Gather->new( $code, $self, $pars );
650             }
651              
652             sub igather;
653             *igather = \&gather;
654              
655              
656              
657              
658              
659              
660              
661              
662              
663 1     1 1 2 sub grep ( $self, $code, $pars = {} ) { ## no critic (BuiltinHomonyms)
  1         4  
  1         2  
  1         3  
  1         2  
664              
665 1         750 require Iterator::Flex::Grep;
666 1         7 Iterator::Flex::Grep->new( $code, $self, $pars );
667             }
668              
669             sub igrep;
670             *igrep = \&grep;
671              
672              
673              
674              
675              
676              
677              
678              
679              
680 3     3 1 8 sub map ( $self, $code, $pars = {} ) { ## no critic (BuiltinHomonyms)
  3         6  
  3         7  
  3         91  
  3         8  
681              
682 3         1250 require Iterator::Flex::Map;
683 3         23 Iterator::Flex::Map->new( $code, $self, $pars );
684             }
685              
686             sub imap;
687             *imap = \↦
688              
689              
690              
691              
692              
693              
694              
695              
696              
697 1     1 1 4 sub take ( $self, $n, $pars = {} ) {
  1         3  
  1         3  
  1         3  
  1         1  
698 1         816 require Iterator::Flex::Take;
699 1         6 Iterator::Flex::Take->new( $self, $n, $pars );
700             }
701              
702             1;
703              
704             #
705             # This file is part of Iterator-Flex
706             #
707             # This software is Copyright (c) 2018 by Smithsonian Astrophysical Observatory.
708             #
709             # This is free software, licensed under:
710             #
711             # The GNU General Public License, Version 3, June 2007
712             #
713              
714             __END__