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   296012 use strict;
  11         28  
  11         431  
4              
5 11     11   64 use Carp;
  11         23  
  11         1062  
6             #use UNIVERSAL qw(isa);
7 11     11   67 use Scalar::Util qw(blessed reftype);
  11         22  
  11         1247  
8 11     11   21722 use overload;
  11         13525  
  11         66  
9 11     11   564 use base qw(Exporter);
  11         23  
  11         1432  
10 11     11   97 use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS);
  11         41  
  11         855  
11              
12 11     11   71 use constant ITERATOR_CLASS => 'Iterator::Simple::Iterator';
  11         30  
  11         29079  
13             $VERSION = '0.06';
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 51     51 1 2322 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 58 100   58 1 6905 if(not @_) {
35 1     1   7 return iterator { return };
  1         4  
36             }
37 57         94 my($self) = @_;
38 57 100       205 if(blessed $self) {
39 17 100       120 if($self->isa(ITERATOR_CLASS)) {
40 15         48 return $self;
41             }
42 2         4 my $method;
43 2 100       28 if($method = $self->can('__iter__')) {
44 1         4 return $method->($self);
45             }
46 1 50 33     5 if($method = overload::Method($self, '<>') || $self->can('next')) {
47 1     4   224829 return ITERATOR_CLASS->new(sub { $method->($self) });
  4         69  
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 40 100       136 if(ref($self) eq 'ARRAY') {
57 37         109 return iarray($self);
58             }
59 3 100       14 if(ref($self) eq 'CODE') {
60 1         7 return ITERATOR_CLASS->new($self);
61             }
62 2 50       17 if(reftype($self) eq 'GLOB') {
63 2     9   13 return ITERATOR_CLASS->new(sub { scalar <$self> });
  9         57  
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 942 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 41 100   41 1 4726 if(not @_) {
110 1         3 return [];
111             }
112 40         107 my($self) = @_;
113 40 100       129 if(ref($self) eq 'ARRAY') {
114 1         6 return $self;
115             }
116 39 100       170 if(reftype($self) eq 'GLOB') {
117 1         22 return [<$self>];
118             }
119 38 50       170 if(blessed $self) {
120 38 100       206 if($self->isa(ITERATOR_CLASS)) {
121 34         48 my(@list, $val);
122 34         325 push @list, $val while defined($val = $self->());
123 34         317 return \@list;
124             }
125 4         7 my $method;
126 4 100       15 if($method = overload::Method($self,'@{}')) {
127 1         2199 return $method->($self);
128             }
129 3 100       113 if($method = $self->can('__iter__')) {
130 1         2 my(@list, $val);
131 1         12 my $iter = $method->($self);
132 1         4 push @list, $val while defined($val = $iter->());
133 1         27 return \@list;
134             }
135 2 50 66     5 if($method = overload::Method($self, '<>') || $self->can('next')) {
136 2         58 my(@list, $val);
137 2         6 push @list, $val while defined($val = $method->($self));
138 2         102 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 755 my($src, $code) = @_;
155 7         19 $src = iter($src);
156 7 50 66     39 if(ref($code) ne 'CODE' and ! overload::Method($code, '&{}')) {
157 0         0 croak 'Second argument to ifilter must be callable.';
158             }
159              
160 7         1685 my $buf;
161              
162             ref($src)->new(sub {
163 100     100   98 my $rv;
164 100 100       184 if($buf) {
165 53 100       73 return $rv if defined($rv = $buf->());
166 5         8 undef $buf;
167             }
168 52         100 while(defined(local $_ = $src->())) {
169 51 100       141 next unless defined($rv = $code->());
170 45 100       212 return $rv unless eval {$rv->isa(ITERATOR_CLASS)};
  45         407  
171 5         7 $buf = $rv;
172 5 50       12 return $rv if defined($rv = $buf->());
173 0         0 undef $buf;
174             }
175 7         16 return;
176 7         75 });
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 43 my($code, $src) = @_;
188 3         10 $src = iter($src);
189             ref($src)->new(sub {
190 31     31   202 local $_ = $src->();
191 31 100       61 return if not defined $_;
192 28         53 return $code->();
193 3         50 });
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 34 my($code, $src) = @_;
205 3         10 $src = iter($src);
206             ref($src)->new(sub {
207 23     23   144 while(defined(my $rv = $src->())) {
208 28         46 local $_ = $rv;
209 28 100       55 return $rv if $code->();
210             }
211 3         8 return;
212 3         27 });
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 18 my($src) = @_;
223 3         8 $src = iter($src);
224              
225 3         22 my $buf;
226             ref($src)->new(sub {
227 20     20   23 my $rv;
228 20 100       43 if($buf) {
229 6 100       12 return $rv if defined($rv = $buf->());
230 3         5 undef $buf;
231             }
232 17         23 while(1){
233 17         30 $rv = $src->();
234 17 100       41 return if not defined $rv;
235 14 100       17 return $rv unless eval {$rv->isa(ITERATOR_CLASS)};
  14         132  
236 3         6 $buf = $rv;
237 3 50       9 return $rv if defined($rv = $buf->());
238 0         0 undef $buf;
239             }
240 3         22 });
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 14 my @srcs = map { iter($_) } @_;
  7         36  
251 3 50       14 if(@srcs == 1) {
252 0         0 return $srcs[0];
253             }
254             ref($srcs[0])->new(sub{
255 25     25   61 while(@srcs) {
256 29         57 my $rv = $srcs[0]->();
257 29 100       128 return $rv if defined $rv;
258 7         63 shift @srcs;
259             }
260 3         10 return;
261 3         19 });
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 995 my($src) = @_;
272 3         34 $src = iter($src);
273 3         38 my $idx = 0;
274            
275             ref($src)->new(sub{
276 11     11   21 my $rv = $src->();
277 11 100       33 return if not defined $rv;
278 8         36 return [$idx++, $rv];
279 3         21 });
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 1244 my @srcs = map { iter($_) } @_;
  4         18  
291            
292             ref($srcs[0])->new(sub{
293 8     8   11 my @rv;
294 8         8 for my $src (@srcs) {
295 14         23 my $rv = $src->();
296 14 100       151 return if not defined $rv;
297 12         19 push @rv, $rv;
298             }
299 6         31 return \@rv;
300 2         13 });
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 7     7 1 2227 my($src, $next, $end, $step) = @_;
314 7 50       21 $next = defined $next ? int($next) : 0;
315 7 100       17 $end = defined $end ? int($end) : -1;
316 7 100       17 $step = defined $step ? int($step) : 1;
317 7 50       18 if($next == $end) {
318 0         0 $next = -1;
319             }
320              
321 7         19 $src = iter($src);
322 7         11 my $idx = 0;
323              
324             ref($src)->new(sub{
325 42 100   42   118 return if $next < 0;
326 37         34 my $rv;
327 37         54 while($rv = $src->()) {
328 68 100       143 if($idx++ == $next) {
329 35         32 $next += $step;
330 35 100 100     121 if($end > 0 and $next >= $end) {
331 5         6 $next = -1;
332             }
333 35         128 return $rv;
334             }
335             }
336 2         5 return;
337 7         38 });
338             }
339              
340 1     1 1 573 sub ihead {islice($_[1], 0, $_[0]);}
341 1     1 1 686 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 42     42 1 1434 my($ary) = @_;
351 42 50       149 if(ref($ary) ne 'ARRAY') {
352 0         0 croak 'Argument to iarray must be ARRAY reference';
353             }
354 42         56 my $idx = 0;
355              
356             iterator {
357 309 100   309   664 return if $idx == @$ary;
358 275         735 return $ary->[$idx++];
359 42         226 };
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   113 use Carp;
  11         20  
  11         945  
368             use overload (
369 11         64 '<>' => 'next',
370             '|' => 'filter',
371             fallback => 1,
372 11     11   62 );
  11         22  
373              
374             sub new {
375 86 50   86   284 if(ref($_[1]) ne 'CODE') {
376 0         0 croak 'Parameter to iterator constructor must be code reference.';
377             }
378 86         438 bless $_[1], $_[0];
379             }
380              
381 20     20   111 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__