File Coverage

blib/lib/Iterator/Flex/Base.pm
Criterion Covered Total %
statement 293 312 93.9
branch 55 74 74.3
condition 29 44 65.9
subroutine 49 52 94.2
pod 16 21 76.1
total 442 503 87.8


line stmt bran cond sub pod time code
1             package Iterator::Flex::Base;
2              
3             # ABSTRACT: Iterator object
4              
5 55     55   481533 use v5.28;
  55         858  
6 55     55   324 use strict;
  55         302  
  55         1080  
7 55     55   348 use warnings;
  55         131  
  55         2572  
8              
9 55     55   2336 use experimental qw( signatures postderef declared_refs );
  55         10418  
  55         272  
10              
11             our $VERSION = '0.34';
12              
13 55     55   14835 use Ref::Util;
  55         18761  
  55         2247  
14 55     55   241 use Scalar::Util;
  55         107  
  55         1751  
15 55     55   209 use List::Util;
  55         140  
  55         2276  
16 55     55   6398 use Role::Tiny ();
  55         63661  
  55         1667  
17 55     55   5634 use Role::Tiny::With ();
  55         3238  
  55         720  
18 55     55   4225 use Module::Runtime ();
  55         14659  
  55         1772  
19              
20 55         18525 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 55     55   7692 );
  55         117  
32              
33 55     55   21775 use namespace::clean;
  55         621504  
  55         314  
34              
35             use overload
36 180     180   180 '<>' => sub ( $self, $, $ ) { &{$self}() },
  180         272  
  180         27189  
  180         181  
  180         184  
37             fallback => 0,
38 1128     1128   1724 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 55     55   64744 ;
  55         105  
  55         662  
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 294     294 0 215652 sub new ( $class, $state = undef, $general = {} ) {
  294         344  
  294         333  
  294         393  
  294         306  
56 294         676 return $class->new_from_state( $state, $general );
57             }
58              
59 315     315 0 345 sub new_from_state ( $class, $state, $general ) {
  315         341  
  315         343  
  315         315  
  315         330  
60 315         734 return $class->new_from_attrs( $class->construct( $state ), $general );
61             }
62              
63 407     407 0 537 sub new_from_attrs ( $class, $in_ipar = {}, $in_gpar = {} ) { ## no critic (ExcessComplexity)
  407         519  
  407         517  
  407         476  
  407         425  
64              
65 407         1484 my %ipar = $in_ipar->%*;
66 407         927 my %gpar = $in_gpar->%*;
67              
68 407         1086 $class->_validate_interface_pars( \%ipar );
69 407         1042 $class->_validate_signal_pars( \%gpar );
70              
71 407   100     1357 my @roles = ( delete( $ipar{ +_ROLES } ) // [] )->@*;
72              
73 407   100     1799 $gpar{ +ERROR } //= [THROW];
74             $gpar{ +ERROR } = [ $gpar{ +ERROR } ]
75 407 50       738 unless Ref::Util::is_arrayref( $gpar{ +ERROR } );
76              
77 407 50       852 if ( $gpar{ +ERROR }[0] eq THROW ) {
78 407         602 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 407   100     1084 my $exhaustion_action = $gpar{ +EXHAUSTION } // [ ( +RETURN ) => undef ];
85              
86             my @exhaustion_action
87 407 100       959 = Ref::Util::is_arrayref( $exhaustion_action )
88             ? ( $exhaustion_action->@* )
89             : ( $exhaustion_action );
90              
91 407         654 $gpar{ +EXHAUSTION } = \@exhaustion_action;
92              
93 407 100       737 if ( $exhaustion_action[0] eq RETURN ) {
    50          
94 303         421 push @roles, 'Exhaustion::Return';
95             }
96             elsif ( $exhaustion_action[0] eq THROW ) {
97              
98 104 100 66     258 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 407 100       850 if ( defined( my $par = $ipar{ +METHODS } ) ) {
108              
109 17         1688 require Iterator::Flex::Method;
110              
111 17 50       46 throw_failure( parameter => q{value for methods parameter must be a hash reference} )
112             unless Ref::Util::is_hashref( $par );
113              
114 17         46 for my $name ( keys $par->%* ) {
115              
116 29         53 my $code = $par->{$name};
117              
118 29 50       65 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         48 my $role = eval { Iterator::Flex::Method::Maker( $name, name => $name ) };
  29         96  
123              
124 29 100       1553 if ( $@ ne q{} ) {
125 20         1540 my $error = $@;
126 20 50 33     161 die $error
127             unless Ref::Util::is_blessed_ref( $error )
128             && $error->isa( 'Iterator::Flex::Failure::RoleExists' );
129 20         407 $role = $error->payload;
130             }
131              
132 29         167 push @roles, q{+} . $role; # need '+', as these are fully qualified role module names.
133             }
134             }
135              
136 407         633 @roles = map { load_role( $_, $class->_role_namespaces ) } @roles;
  1391         20017  
137 407         7516 $class = Role::Tiny->create_class_with_roles( $class, @roles );
138              
139 407 50       140692 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 407 50       1008 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 407   66     5046 $ipar{ +_NAME } //= $class;
149              
150 407         3360 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 407 50       1140 if exists $REGISTRY{ refaddr $self };
155              
156 407         915 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 407         527 my @ipar;
161 407         1055 for my $key ( keys %ipar ) {
162 3108         4706 $ipar[ $RegIterationIndexMap{$key} ] = $ipar{$key};
163             Scalar::Util::weaken $ipar[ $RegIterationIndexMap{$key} ]
164             if Ref::Util::is_ref( $ipar{$key} )
165 3108 100 100     7204 && Scalar::Util::isweak( $ipar{$key} );
166             }
167 407         866 my @gpar;
168 407         802 for my $key ( keys %gpar ) {
169 907         1481 $gpar[ $RegGeneralParameterIndexMap{$key} ] = $gpar{$key};
170             Scalar::Util::weaken $gpar[ $RegGeneralParameterIndexMap{$key} ]
171             if Ref::Util::is_ref( $gpar{$key} )
172 907 50 66     2127 && Scalar::Util::isweak( $gpar{$key} );
173             }
174              
175 407         723 $regentry->[REG_ITERATOR] = \@ipar;
176 407         526 $regentry->[REG_GENERAL] = \@gpar;
177              
178 407         1150 $self->_clear_state;
179              
180 407         4245 return $self;
181             }
182              
183 407     407   442 sub _validate_interface_pars ( $class, $pars ) {
  407         456  
  407         427  
  407         442  
184 407         717 state %InterfaceParameters = {}->%{ +INTERFACE_PARAMETER_VALUES };
185              
186 407         971 my @bad = grep { !exists $InterfaceParameters{$_} } keys $pars->%*;
  3014         3951  
187              
188 407 50       822 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 407 50 33     888 if defined $pars->{_ROLES} && !Ref::Util::is_arrayref( $pars->{ +_ROLES } );
193              
194 407 100       805 if ( defined( my $par = $pars->{ +_DEPENDS } ) ) {
195 101 100       325 $pars->{ +_DEPENDS } = $par = [$par] unless Ref::Util::is_arrayref( $par );
196             throw_failure( parameter => "dependency #$_ is not an iterator object" )
197 101 50   141   492 unless List::Util::all { $class->_is_iterator( $_ ) } $par->@*;
  141         389  
198             }
199              
200 407         911 return;
201             }
202              
203 407     407   444 sub _validate_signal_pars ( $class, $pars ) {
  407         442  
  407         416  
  407         404  
204 407         556 state %SignalParameters = {}->%{ +SIGNAL_PARAMETER_VALUES };
205 407         666 my @bad = grep { !exists $SignalParameters{$_} } keys $pars->%*;
  263         385  
206              
207 407 50       715 throw_failure( parameter => "unknown signal parameters: @{[ join ', ', @bad ]}" )
  0         0  
208             if @bad;
209             }
210              
211              
212 404     404   212134 sub DESTROY ( $self ) {
  404         524  
  404         479  
213              
214 404 50       759 if ( defined $self ) {
215 404         5733 delete $REGISTRY{ refaddr $self };
216             }
217             }
218              
219 2     2   3 sub _name ( $self ) {
  2         4  
  2         2  
220 2         16 $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 141     141   151 sub _is_iterator ( $, $obj ) {
  141         156  
  141         157  
248 141   33     705 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 22 sub may ( $self, $meth ) {
  16         15  
  16         21  
  16         14  
280              
281 16         26 my \@attributes = $REGISTRY{ refaddr $self }[REG_ITERATOR];
282 16   100     34 my $may = $attributes[REG_ITER_MAY_METHOD] //= {};
283              
284             return $may->{"_may_$meth"}
285             //= defined $attributes[REG_ITER__DEPENDS]
286 3     3   6 ? !List::Util::first { !$_->may( $meth ) } $attributes[REG_ITER__DEPENDS]->@*
287 16 100 33     89 : 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   209587 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 1825     1825   3635 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   329 sub _add_roles ( $class, @roles ) {
  73         142  
  73         189  
  73         100  
345             Role::Tiny->apply_roles_to_package( $class,
346 73         152 map { load_role( $_, $class->_role_namespaces ) } @roles );
  421         4171  
347             }
348              
349 198     198   337 sub _apply_method_to_depends ( $self, $meth ) {
  198         214  
  198         234  
  198         233  
350              
351 198 100       644 if ( defined( my $depends = $REGISTRY{ refaddr $self }[REG_ITERATOR][REG_ITER__DEPENDS] ) ) {
352             # first check if dependencies have method
353 63     84   334 my $cant = List::Util::first { !$_->can( $meth ) } $depends->@*;
  84         301  
354 63 100       269 throw_failure( Unsupported => "dependency: @{[ $cant->_name ]} does not have a '$meth' method" )
  1         4  
355             if $cant;
356              
357             # now apply the method
358 62         203 $_->$meth foreach $depends->@*;
359             }
360             }
361              
362              
363              
364              
365              
366              
367              
368              
369              
370 1213     1213 1 28154 sub is_exhausted ( $self ) {
  1213         1183  
  1213         1130  
371 1213         2122 $self->get_state == IterState_EXHAUSTED;
372             }
373              
374              
375              
376              
377              
378              
379              
380              
381              
382              
383              
384              
385              
386              
387 573     573 0 654 sub set_exhausted ( $self ) {
  573         569  
  573         585  
388 573         1047 $self->set_state( IterState_EXHAUSTED );
389             }
390              
391              
392              
393              
394              
395              
396              
397              
398              
399              
400              
401              
402              
403              
404 627     627   640 sub _clear_state ( $self ) {
  627         646  
  627         641  
405 627         1476 $self->set_state( IterState_CLEAR );
406             }
407              
408              
409              
410              
411              
412              
413              
414              
415              
416 2     2 1 1640 sub is_error ( $self ) {
  2         3  
  2         3  
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 3 sub set_error ( $self ) {
  2         2  
  2         2  
434 2         5 $self->set_state( IterState_ERROR );
435             }
436              
437              
438              
439              
440              
441              
442              
443              
444              
445 2     2 1 3 sub throws_on_exhaustion( $self ) {
  2         2  
  2         3  
446 2         8 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 2 sub returns_on_exhaustion( $self ) {
  1         2  
  1         1  
459 1         4 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 2 sub buffer ( $self, $n = 0, $pars = {} ) {
  1         1  
  1         2  
  1         1  
  1         2  
472 1         387 require Iterator::Flex::Buffer;
473 1         5 Iterator::Flex::Buffer->new( $self, $n, $pars );
474             }
475              
476              
477              
478              
479              
480              
481              
482              
483              
484              
485 1     1 1 2 sub cache ( $self, $pars = {} ) {
  1         2  
  1         2  
  1         1  
486 1         418 require Iterator::Flex::Cache;
487 1         4 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         1  
  1         3  
  1         1  
505 1         400 require Iterator::Flex::Cat;
506 1         4 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 7     7 1 11 sub chunk ( $self, $pars = {} ) {
  7         7  
  7         10  
  7         6  
524 7         798 require Iterator::Flex::Chunk;
525 7         20 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 117     117 1 9668 sub drain ( $self, $n = undef ) {
  117         161  
  117         142  
  117         168  
548              
549 117 100 66     556 throw_failure( parameter => '$n is not a positive integer' )
      100        
550             if defined $n && !( Scalar::Util::looks_like_number( $n ) && int( $n ) == $n && $n > 0 );
551              
552 116         156 my @values;
553              
554             eval {
555 116 100       207 if ( $n ) {
556 41         86 while ( $n-- ) {
557 148         255 push @values, $self->next;
558 148 100       226 if ( $self->is_exhausted ) {
559 1         2 pop @values;
560 1         2 last;
561             }
562             }
563             }
564             else {
565 75         110 while ( 1 ) {
566 505         892 push @values, $self->next;
567 502 100       748 if ( $self->is_exhausted ) {
568 72         109 pop @values;
569 72         124 last;
570             }
571             }
572             }
573 113         244 1;
574 116 100       183 } or do {
575 3 100 66     590 die $@
576             unless Ref::Util::is_blessed_ref( $@ )
577             && $@->isa( 'Iterator::Flex::Failure::Exhausted' );
578             };
579              
580 114         414 return \@values;
581             }
582              
583              
584              
585              
586              
587              
588              
589              
590              
591 1     1 1 2 sub flatten ( $self, $pars = {} ) {
  1         1  
  1         2  
  1         2  
592 1         424 require Iterator::Flex::Flatten;
593 1         4 return Iterator::Flex::Flatten->new( $self, $pars );
594             }
595              
596             sub iflatten;
597             *iflatten = \*flatten;
598              
599              
600              
601              
602              
603              
604              
605              
606              
607              
608 2     2 1 2 sub foreach ( $self, $code ) { ## no critic (BuiltinHomonyms)
  2         3  
  2         2  
  2         1  
609              
610 2 100       10 if ( $self->throws_on_exhaustion ) {
    50          
611             eval {
612 1         1 local $_; ## no critic (InitializationForLocalVars)
613 1         2 while ( 1 ) { $_ = $self->(); $code->() }
  4         9  
  3         5  
614 0         0 1;
615 1 50       2 } or do {
616 1 50 33     57 die $@
617             unless Ref::Util::is_blessed_ref( $@ )
618             && $@->isa( 'Iterator::Flex::Failure::Exhausted' );
619             };
620             }
621             elsif ( $self->returns_on_exhaustion ) {
622              
623             # optimize for when sentinel is the undefined value
624 1 50       3 if ( !defined $self->sentinel ) {
625 1         2 local $_; ## no critic (InitializationForLocalVars)
626 1         3 $code->() while defined( $_ = $self->() );
627             }
628              
629             # yeah, this is too slow. should adapt the logic in Wrap::Return.
630             else {
631 0         0 local $_; ## no critic (InitializationForLocalVars)
632 0         0 $_ = $self->();
633 0         0 until ( $self->is_exhausted ) { ## no critic (UntilBlock)
634 0         0 $code->();
635 0         0 $_ = $self->();
636             }
637             }
638             }
639             }
640              
641              
642              
643              
644              
645              
646              
647              
648              
649              
650 9     9 1 16 sub gather ( $self, $code, $pars = {} ) {
  9         66  
  9         11  
  9         12  
  9         9  
651 9         879 require Iterator::Flex::Gather;
652 9         29 Iterator::Flex::Gather->new( $code, $self, $pars );
653             }
654              
655             sub igather;
656             *igather = \&gather;
657              
658              
659              
660              
661              
662              
663              
664              
665              
666 1     1 1 2 sub grep ( $self, $code, $pars = {} ) { ## no critic (BuiltinHomonyms)
  1         2  
  1         1  
  1         2  
  1         1  
667              
668 1         400 require Iterator::Flex::Grep;
669 1         4 Iterator::Flex::Grep->new( $code, $self, $pars );
670             }
671              
672             sub igrep;
673             *igrep = \&grep;
674              
675              
676              
677              
678              
679              
680              
681              
682              
683 3     3 1 7 sub map ( $self, $code, $pars = {} ) { ## no critic (BuiltinHomonyms)
  3         5  
  3         4  
  3         4  
  3         3  
684              
685 3         764 require Iterator::Flex::Map;
686 3         14 Iterator::Flex::Map->new( $code, $self, $pars );
687             }
688              
689             sub imap;
690             *imap = \↦
691              
692              
693              
694              
695              
696              
697              
698              
699              
700 1     1 1 3 sub take ( $self, $n, $pars = {} ) {
  1         2  
  1         1  
  1         2  
  1         2  
701 1         404 require Iterator::Flex::Take;
702 1         4 Iterator::Flex::Take->new( $self, $n, $pars );
703             }
704              
705             1;
706              
707             #
708             # This file is part of Iterator-Flex
709             #
710             # This software is Copyright (c) 2018 by Smithsonian Astrophysical Observatory.
711             #
712             # This is free software, licensed under:
713             #
714             # The GNU General Public License, Version 3, June 2007
715             #
716              
717             __END__