File Coverage

blib/lib/Number/Range/Regex/Iterator.pm
Criterion Covered Total %
statement 104 122 85.2
branch 58 70 82.8
condition n/a
subroutine 12 15 80.0
pod 9 9 100.0
total 183 216 84.7


line stmt bran cond sub pod time code
1             # Number::Range::Regex::Iterator
2             #
3             # Copyright 2012 Brian Szymanski. All rights reserved. This module is
4             # free software; you can redistribute it and/or modify it under the same
5             # terms as Perl itself.
6              
7             package Number::Range::Regex::Iterator;
8              
9 14     14   79 use strict;
  14         24  
  14         688  
10 14     14   77 use vars qw ( @ISA @EXPORT @EXPORT_OK $VERSION );
  14         27  
  14         1583  
11             eval { require warnings; }; #it's ok if we can't load warnings
12              
13             require Exporter;
14 14     14   82 use base 'Exporter';
  14         25  
  14         16767  
15             @ISA = qw( Exporter );
16              
17             $VERSION = '0.32';
18              
19             use overload bool => \&in_range,
20 14     14   411 '""' => sub { return $_[0] };
  14     0   30  
  14         188  
  0         0  
21              
22             # fields in %$self:
23             # ranges : arrayref of the subranges involved
24             # out_of_range : whether the iterator's current state is
25             # out of range (advanced beyond the last
26             # element or before the first one) or not.
27             # if true, will be either "underflow" or
28             # "overflow" accordingly
29             # number : the number currently pointed to
30             # rangenum : which subrange is the number in?
31             # rangepos_left : the offset between number and the leftmost
32             # element of this subrange
33             # rangepos_right : the offset between number and the rightmost
34             # element of this subrange
35              
36             sub new {
37 14     14 1 1873 my ($class, $range) = @_;
38              
39 14         92 my $self = bless { range => $range }, $class;
40              
41 14 50       102 if(!$range->isa('Number::Range::Regex::Range')) {
42 0         0 die "unknown arg: $range, usage: Iterator->new( \$range )";
43             }
44 14 100       66 if($range->is_empty) {
    100          
45 3         29 die "can't iterate over an empty range";
46             } elsif($range->isa('Number::Range::Regex::CompoundRange')) {
47 3         13 $self->{ranges} = $range->{ranges};
48             } else { #SimpleRange
49 8         85 $self->{ranges} = [ $range ];
50             }
51              
52 11 100       74 $self->first() if $self->{ranges}->[0]->has_lower_bound;
53              
54 11         42 return $self;
55             }
56              
57             sub size {
58 0     0 1 0 my ($self) = @_;
59 0 0       0 return undef if !$self->{ranges}->[0]->has_lower_bound;
60 0 0       0 return undef if !$self->{ranges}->[-1]->has_upper_bound;
61 0 0       0 return $self->{size} if defined $self->{size};
62 0         0 foreach my $sr ( @{$self->{ranges}} ) {
  0         0  
63 0         0 $self->{size} += $sr->{max} - $sr->{min} + 1;
64             }
65 0         0 return $self->{size};
66             }
67              
68             sub seek {
69 51     51 1 13274 my ($self, $number) = @_;
70 51         59 my $n;
71 51         76 for($n = 0 ; $n < @{$self->{ranges}}; $n++ ) {
  148         520  
72 124         193 my $sr = $self->{ranges}->[$n];
73 124 100       365 if($sr->contains($number)) {
74 27         50 $self->{number} = $number;
75 27         46 $self->{rangenum} = $n;
76 27         36 $self->{out_of_range} = 0;
77 27 100       69 if($sr->has_lower_bound) {
78 19         45 $self->{rangepos_left} = $number - $sr->{min};
79             }
80 27 100       86 if($sr->has_upper_bound) {
81 19         44 $self->{rangepos_right} = $sr->{max} - $number;
82             }
83 27         82 last;
84             }
85             }
86 51 100       67 if($n == @{$self->{ranges}}) {
  51         124  
87 24         98 die "can't seek() - range '".$self->{range}->to_string."' does not contain '$number'";
88             }
89 27         164 return $self;
90             }
91              
92             sub first {
93 25     25 1 4335 my ($self) = @_;
94 25         56 my $first_r = $self->{ranges}->[0];
95 25 100       72 die "can't first() an iterator with no lower bound" unless $first_r->has_lower_bound;
96 22         72 $self->{number} = $first_r->{min};
97 22         38 $self->{rangenum} = 0;
98 22 50       60 if($first_r->has_lower_bound) {
99 22         37 $self->{rangepos_left} = 0;
100 22 100       77 if($first_r->has_upper_bound) {
101 18         65 $self->{rangepos_right} = $first_r->{max} - $first_r->{min};
102             }
103             }
104 22         67 $self->{out_of_range} = 0;
105 22         236 return $self;
106             }
107              
108             sub last {
109 15     15 1 1653 my ($self) = @_;
110 15         53 my $last_r = $self->{ranges}->[-1];
111 15 100       51 die "can't last() an iterator with no upper bound" unless $last_r->has_upper_bound;
112 12         184 $self->{number} = $last_r->{max};
113 12         16 $self->{rangenum} = $#{$self->{ranges}};
  12         34  
114 12 50       49 if($last_r->has_upper_bound) {
115 12         23 $self->{rangepos_right} = 0;
116 12 100       34 if($last_r->has_lower_bound) {
117 10         31 $self->{rangepos_left} = $last_r->{max} - $last_r->{min};
118             }
119             }
120 12         37 $self->{out_of_range} = 0;
121 12         46 return $self;
122             }
123              
124             sub fetch {
125 96     96 1 4050 my ($self) = @_;
126 96 100       254 die "can't fetch() an iterator before positioning it using first/last/seek" if !defined $self->{number};
127 93 100       252 die "can't fetch() an out of range ($self->{out_of_range}) iterator" if $self->{out_of_range};
128 90         542 return $self->{number};
129             }
130              
131             sub next {
132 11289     11289 1 56125 my ($self) = @_;
133 11289 100       29566 die "can't next() an iterator before positioning it using first/last/seek" if !defined $self->{number};
134 11286 100       23773 die "can't next() an out of range ($self->{out_of_range}) iterator" if $self->{out_of_range};
135              
136             #_dbg($self, "pre-next: ");
137 11284         18091 my $this_r = $self->{ranges}->[ $self->{rangenum} ];
138 11284 100       45746 if( $this_r->has_upper_bound ? $self->{number} < $this_r->{max} : 1 ) {
    100          
139 11270 100       29981 $self->{rangepos_left}++ if defined $self->{rangepos_left};
140 11270 100       25352 $self->{rangepos_right}-- if defined $self->{rangepos_right};
141 11270         14110 $self->{number}++;
142             } else {
143 14         26 $self->{rangenum}++;
144 14 100       29 if($self->{rangenum} == @{$self->{ranges}}) {
  14         51  
145 5         11 $self->{out_of_range} = 'overflow';
146 5         17 return $self;
147             }
148 9         52 my $new_r = $self->{ranges}->[ $self->{rangenum} ];
149 9         15 $self->{rangepos_left} = 0;
150 9 50       29 if($new_r->has_upper_bound) { #min must be defined - this is the next one up
151 9         30 $self->{rangepos_right} = $new_r->{max} - $new_r->{min};
152             }
153 9         28 $self->{number} = $new_r->{min};
154             }
155             #_dbg($self, "post-next: ");
156 11279         30490 return $self;
157             }
158              
159             sub prev {
160 170     170 1 1765 my ($self) = @_;
161 170 100       391 die "can't prev() an iterator before positioning it using first/last/seek" if !defined $self->{number};
162 167 100       351 die "can't prev() an out of range ($self->{out_of_range}) iterator" if $self->{out_of_range};
163              
164             #_dbg($self, "pre-prev: ");
165 165         268 my $this_r = $self->{ranges}->[ $self->{rangenum} ];
166 165 100       8550 if( $this_r->has_lower_bound ? $self->{number} > $this_r->{min} : 1 ) {
    100          
167 157 100       382 $self->{rangepos_left}-- if defined $self->{rangepos_left};
168 157 100       379 $self->{rangepos_right}++ if defined $self->{rangepos_right};
169 157         207 $self->{number}--;
170             } else {
171 8         14 $self->{rangenum}--;
172 8 100       22 if($self->{rangenum} == -1) {
173 3         6 $self->{out_of_range} = 'underflow';
174 3         9 return $self;
175             }
176 5         623 my $new_r = $self->{ranges}->[ $self->{rangenum} ];
177 5         15 $self->{rangepos_left} = $new_r->{max} - $new_r->{min};
178 5         7 $self->{rangepos_right} = 0;
179 5         11 $self->{number} = $new_r->{max};
180             }
181             #_dbg($self, "post-prev: ");
182 162         508 return $self;
183             }
184              
185             sub in_range {
186 11544     11544 1 41766 my ($self) = @_;
187 11544         33548 return ! $self->{out_of_range};
188             }
189              
190             sub _dbg {
191 0     0     my ($self, $ident) = @_;
192 0           my $str = $ident;
193 0           for my $key ( qw ( number rangenum rangepos_left rangepos_right ) ) {
194 0           my $val = $self->{$key};
195 0 0         $val = "[undef]" unless defined $val;
196 0           $str .= " $key: $val,";
197             }
198 0           $str =~ s/,$//;
199 0           warn "$str\n";
200             }
201              
202             1;
203              
204             __END__