File Coverage

blib/lib/Iterator/Simple.pm
Criterion Covered Total %
statement 170 188 90.4
branch 75 92 81.5
condition 8 19 42.1
subroutine 38 42 90.4
pod 16 16 100.0
total 307 357 85.9


line stmt bran cond sub pod time code
1             package Iterator::Simple;
2              
3 11     11   640643 use strict;
  11         90  
  11         267  
4              
5 11     11   48 use Carp;
  11         14  
  11         624  
6             #use UNIVERSAL qw(isa);
7 11     11   58 use Scalar::Util qw(blessed reftype);
  11         24  
  11         390  
8 11     11   11521 use overload;
  11         9008  
  11         50  
9 11     11   551 use base qw(Exporter);
  11         16  
  11         1397  
10 11     11   66 use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS);
  11         18  
  11         757  
11              
12 11     11   60 use constant ITERATOR_CLASS => 'Iterator::Simple::Iterator';
  11         18  
  11         18550  
13             $VERSION = '0.07';
14              
15             $EXPORT_TAGS{basic} = [qw(iterator iter list is_iterator)];
16             $EXPORT_TAGS{utils} = [qw(
17             ifilter iflatten ichain izip ienumerate
18             islice ihead iskip imap igrep iarray
19             is_iterable is_listable
20             )];
21              
22             push @EXPORT_OK, @{$EXPORT_TAGS{basic}}, @{$EXPORT_TAGS{utils}};
23             $EXPORT_TAGS{all} = [@EXPORT_OK];
24              
25 52     52 1 1858 sub iterator(&) { ITERATOR_CLASS->new($_[0]);}
26              
27             # name: iter
28             # synopsis: iter($object);
29             # description:
30             # autodetect object type and turn it into iterator
31             # param: object: object to turn into iterator
32             # return: iterator
33             sub iter {
34 59 100   59 1 4172 if(not @_) {
35 1     1   4 return iterator { return };
  1         2  
36             }
37 58         82 my($self) = @_;
38 58 100       133 if(blessed $self) {
39 17 100       73 if($self->isa(ITERATOR_CLASS)) {
40 15         32 return $self;
41             }
42 2         3 my $method;
43 2 100       12 if($method = $self->can('__iter__')) {
44 1         3 return $method->($self);
45             }
46 1 50 33     4 if($method = overload::Method($self, '<>') || $self->can('next')) {
47 1     4   48 return ITERATOR_CLASS->new(sub { $method->($self) });
  4         44  
48             }
49 0 0       0 if($method = overload::Method($self, '&{}')) {
50 0         0 return ITERATOR_CLASS->new($method->($self));
51             }
52 0 0       0 if($method = overload::Method($self,'@{}')) {
53 0         0 return iarray($method->($self));
54             }
55             }
56 41 100       90 if(ref($self) eq 'ARRAY') {
57 38         64 return iarray($self);
58             }
59 3 100       7 if(ref($self) eq 'CODE') {
60 1         3 return ITERATOR_CLASS->new($self);
61             }
62 2 50       10 if(reftype($self) eq 'GLOB') {
63 2     9   9 return ITERATOR_CLASS->new(sub { scalar <$self> });
  9         37  
64             }
65              
66 0   0     0 croak sprintf "'%s' object is not iterable", (ref($self)||'SCALAR');
67             }
68              
69             # name: is_iterable
70             # synopsis: iter($object);
71             # description:
72             # returns given object is iterable or not.
73             # param: object
74             # return: iterator
75             sub is_iterable {
76 0     0 1 0 my($self) = @_;
77             return not not (
78 0   0     0 (blessed($self) and (
79             $self->isa(ITERATOR_CLASS)
80             or $self->can('__iter__')
81             or $self->can('next')
82             or overload::Method($self, '<>')
83             or overload::Method($self, '&{}')
84             or overload::Method($self,'@{}')
85             ))
86             or ref($self) eq 'ARRAY'
87             or ref($self) eq 'CODE'
88             or reftype($self) eq 'GLOB'
89             );
90             }
91              
92             # name: is_iterator
93             # synopsis: is_iterator($object);
94             # description:
95             # reports Iterator::Simpler iterator object or not;
96             # param: object: some object;
97             # return: bool
98             sub is_iterator {
99 3 50   3 1 612 blessed($_[0]) and $_[0]->isa(ITERATOR_CLASS);
100             }
101              
102             # name: list
103             # synopsis: list($object)
104             # description:
105             # autodetect object type and turn it into array reference
106             # param: object: object to turn into array
107             # return: array reference
108             sub list {
109 42 100   42 1 3792 if(not @_) {
110 1         4 return [];
111             }
112 41         59 my($self) = @_;
113 41 100       94 if(ref($self) eq 'ARRAY') {
114 1         5 return $self;
115             }
116 40 100       117 if(reftype($self) eq 'GLOB') {
117 1         15 return [<$self>];
118             }
119 39 50       100 if(blessed $self) {
120 39 100       139 if($self->isa(ITERATOR_CLASS)) {
121 35         42 my(@list, $val);
122 35         160 push @list, $val while defined($val = $self->());
123 35         240 return \@list;
124             }
125 4         6 my $method;
126 4 100       9 if($method = overload::Method($self,'@{}')) {
127 1         47 return $method->($self);
128             }
129 3 100       101 if($method = $self->can('__iter__')) {
130 1         2 my(@list, $val);
131 1         3 my $iter = $method->($self);
132 1         2 push @list, $val while defined($val = $iter->());
133 1         21 return \@list;
134             }
135 2 50 66     4 if($method = overload::Method($self, '<>') || $self->can('next')) {
136 2         59 my(@list, $val);
137 2         5 push @list, $val while defined($val = $method->($self));
138 2         106 return \@list;
139             }
140             }
141 0   0     0 croak sprintf "'%s' object could not be converted to array ref", (ref($self)||'SCALAR');
142             }
143              
144             # name: ifilter
145             # synopsis: ifilter $iterable, sub { CODE };
146             # description:
147             # filters another iterable object.
148             # if filter code yields another iterator, iterate it until it
149             # exhausted. if filter code yields undefined value, ignores it.
150             # param: source: source iterable object
151             # param: code: transformation code
152             # return: transformed iterator
153             sub ifilter {
154 7     7 1 580 my($src, $code) = @_;
155 7         12 $src = iter($src);
156 7 50 66     26 if(ref($code) ne 'CODE' and ! overload::Method($code, '&{}')) {
157 0         0 croak 'Second argument to ifilter must be callable.';
158             }
159              
160 7         56 my $buf;
161              
162             ref($src)->new(sub {
163 100     100   102 my $rv;
164 100 100       141 if($buf) {
165 53 100       57 return $rv if defined($rv = $buf->());
166 5         15 undef $buf;
167             }
168 52         64 while(defined(local $_ = $src->())) {
169 51 100       91 next unless defined($rv = $code->());
170 45 100       158 return $rv unless eval {$rv->isa(ITERATOR_CLASS)};
  45         223  
171 5         7 $buf = $rv;
172 5 50       7 return $rv if defined($rv = $buf->());
173 0         0 undef $buf;
174             }
175 7         12 return;
176 7         26 });
177             }
178              
179             # name: imap
180             # synopsis: imap { CODE } $iterable;
181             # description:
182             # simplified version of ifilter, no skip, no inflate.
183             # param: code: transformation code;
184             # param: source: source iterable object
185             # return: transformed iterator;
186             sub imap(&$) {
187 3     3 1 14 my($code, $src) = @_;
188 3         6 $src = iter($src);
189             ref($src)->new(sub {
190 31     31   85 local $_ = $src->();
191 31 100       49 return if not defined $_;
192 28         34 return $code->();
193 3         11 });
194             }
195              
196             # name: igrep
197             # synopsis: igrep { CODE } $iterable;
198             # description:
199             # iterator filter iterator
200             # param: code: filter condition
201             # param: source: source iterable object
202             # return: filtered iterator
203             sub igrep(&$) {
204 3     3 1 78 my($code, $src) = @_;
205 3         7 $src = iter($src);
206             ref($src)->new(sub {
207 23     23   86 while(defined(my $rv = $src->())) {
208 28         51 local $_ = $rv;
209 28 100       34 return $rv if $code->();
210             }
211 3         8 return;
212 3         12 });
213             }
214              
215             # name: iflatten
216             # synopsys: iflatten $iterable;
217             # description:
218             # if source iterator yields another iterator, iterate it first.
219             # param: source: source iterable object
220             # return: flatten iterator
221             sub iflatten {
222 3     3 1 12 my($src) = @_;
223 3         5 $src = iter($src);
224              
225 3         3 my $buf;
226             ref($src)->new(sub {
227 20     20   22 my $rv;
228 20 100       29 if($buf) {
229 6 100       8 return $rv if defined($rv = $buf->());
230 3         4 undef $buf;
231             }
232 17         16 while(1){
233 17         20 $rv = $src->();
234 17 100       28 return if not defined $rv;
235 14 100       15 return $rv unless eval {$rv->isa(ITERATOR_CLASS)};
  14         71  
236 3         13 $buf = $rv;
237 3 50       6 return $rv if defined($rv = $buf->());
238 0         0 undef $buf;
239             }
240 3         13 });
241             }
242              
243             # name: ichain
244             # synopsis: ichain($iterable1, $iterable2,...)
245             # description:
246             # iterate one or more iterater one by one.
247             # param: iteraters: one or more iterable object
248             # return: chained iterator
249             sub ichain {
250 3     3 1 11 my @srcs = map { iter($_) } @_;
  7         11  
251 3 50       11 if(@srcs == 1) {
252 0         0 return $srcs[0];
253             }
254             ref($srcs[0])->new(sub{
255 25     25   43 while(@srcs) {
256 29         40 my $rv = $srcs[0]->();
257 29 100       72 return $rv if defined $rv;
258 7         27 shift @srcs;
259             }
260 3         8 return;
261 3         13 });
262             }
263              
264             # name: ienumerate
265             # sysopsis: ienumerate($iterable);
266             # description:
267             # returns an iterator which yields $souce value with its index.
268             # param: iterable: source iterator
269             # return: iterator
270             sub ienumerate {
271 3     3 1 853 my($src) = @_;
272 3         9 $src = iter($src);
273 3         5 my $idx = 0;
274            
275             ref($src)->new(sub{
276 11     11   16 my $rv = $src->();
277 11 100       23 return if not defined $rv;
278 8         25 return [$idx++, $rv];
279 3         13 });
280             }
281              
282             # name: izip
283             # synopsis: izip($iterable, ...)
284             # description:
285             # this function returns an iterator yields array reference,
286             # where i-th array contains i-th element from each of the argument iterables.
287             # param: iterables: list of iterables;
288             # return: zipped iterator;
289             sub izip {
290 2     2 1 974 my @srcs = map { iter($_) } @_;
  4         7  
291            
292             ref($srcs[0])->new(sub{
293 8     8   9 my @rv;
294 8         10 for my $src (@srcs) {
295 14         19 my $rv = $src->();
296 14 100       24 return if not defined $rv;
297 12         19 push @rv, $rv;
298             }
299 6         15 return \@rv;
300 2         12 });
301             }
302              
303             # name: islice
304             # synopsis: isplice($iterable, $start, $end, $step);
305             # description:
306             # this function returns an iterator,
307             # param: iterable: source iterable object
308             # param: start: how many first values are skipped
309             # param: end: last index of source iterator
310             # param: step: step
311             # return: sliced iterator
312             sub islice {
313 8     8 1 1986 my($src, $next, $end, $step) = @_;
314 8 50       21 $next = defined $next ? int($next) : 0;
315 8 100       15 $end = defined $end ? int($end) : -1;
316 8 100       13 $step = defined $step ? int($step) : 1;
317 8 50       15 if($next == $end) {
318 0         0 $next = -1;
319             }
320              
321 8         15 $src = iter($src);
322 8         10 my $idx = 0;
323              
324             ref($src)->new(sub{
325 46 100   46   69 return if $next < 0;
326 40         46 my $rv;
327 40         44 while(defined($rv = $src->())) {
328 71 100       105 if($idx++ == $next) {
329 38         40 $next += $step;
330 38 100 100     80 if($end > 0 and $next >= $end) {
331 6         7 $next = -1;
332             }
333 38         92 return $rv;
334             }
335             }
336 2         4 return;
337 8         29 });
338             }
339              
340 1     1 1 414 sub ihead {islice($_[1], 0, $_[0]);}
341 1     1 1 490 sub iskip {islice($_[1], $_[0]);}
342              
343             # name: iarray
344             # synopsis: iarray $array_ref;
345             # description:
346             # creates iterator from array reference
347             # param: array_ref: source array reference
348             # return: iterator
349             sub iarray {
350 43     43 1 1097 my($ary) = @_;
351 43 50       94 if(ref($ary) ne 'ARRAY') {
352 0         0 croak 'Argument to iarray must be ARRAY reference';
353             }
354 43         51 my $idx = 0;
355              
356             iterator {
357 312 100   312   456 return if $idx == @$ary;
358 278         473 return $ary->[$idx++];
359 43         172 };
360             }
361              
362             # class Iterator::Simple::Iterator is underlying Iterator object.
363             # It is just a blessed subroutine reference.
364             {
365             package Iterator::Simple::Iterator;
366              
367 11     11   74 use Carp;
  11         21  
  11         553  
368             use overload (
369 11         43 '<>' => 'next',
370             '|' => 'filter',
371             fallback => 1,
372 11     11   55 );
  11         19  
373              
374             sub new {
375 88 50   88   172 if(ref($_[1]) ne 'CODE') {
376 0         0 croak 'Parameter to iterator constructor must be code reference.';
377             }
378 88         339 bless $_[1], $_[0];
379             }
380              
381 20     20   83 sub next { goto shift }
382              
383 0     0     sub __iter__ { $_[0] }
384              
385             *filter = \&Iterator::Simple::ifilter;
386             *flatten = \&Iterator::Simple::iflatten;
387             *chain = \&Iterator::Simple::ichain;
388             *zip = \&Iterator::Simple::izip;
389             *enumerate = \&Iterator::Simple::ienumerate;
390             *slice = \&Iterator::Simple::islice;
391 0     0     sub head { Iterator::Simple::ihead($_[1], $_[0]); }
392 0     0     sub skip { Iterator::Simple::iskip($_[1], $_[0]); }
393             }
394              
395             1;
396             __END__