File Coverage

blib/lib/Enum/Declare/Set.pm
Criterion Covered Total %
statement 133 134 99.2
branch 30 32 93.7
condition 10 17 58.8
subroutine 30 30 100.0
pod 0 18 0.0
total 203 231 87.8


line stmt bran cond sub pod time code
1             package Enum::Declare::Set;
2              
3 11     11   57 use strict;
  11         19  
  11         379  
4 11     11   40 use warnings;
  11         14  
  11         472  
5 11     11   65 use Carp qw/croak/;
  11         14  
  11         546  
6 11     11   85 use Scalar::Util qw/looks_like_number/;
  11         39  
  11         496  
7 11     11   45 use Object::Proto;
  11         18  
  11         1427  
8              
9             use overload
10             '""' => \&_stringify,
11             '==' => \&equals,
12 1     1   4 '!=' => sub { !$_[0]->equals($_[1]) },
13 3     3   90 'bool' => sub { !$_[0]->is_empty },
14 11     11   5621 fallback => 1;
  11         18025  
  11         122  
15              
16             object 'Enum::Declare::Set',
17             'meta:required',
18             'name:Str:default()',
19             'frozen:Bool:default(0)',
20             'bits:Str:default()',
21             'init_values:ArrayRef:default([]):arg(values)';
22              
23             sub BUILD {
24 45     45 0 3436 my ($self) = @_;
25 45         162 my $count = $self->meta->count;
26 45         68 my $bits = '';
27 45 50       175 vec($bits, $count - 1, 1) = 0 if $count;
28              
29 45         73 for my $val (@{$self->init_values}) {
  45         105  
30 6         13 my $idx = _val_to_index($self, $val);
31 6         14 vec($bits, $idx, 1) = 1;
32             }
33              
34 45         5478 $self->bits($bits);
35             }
36              
37             sub has {
38 46     46 0 173061 my ($self, $val) = @_;
39 46         62 my $idx = eval { $self->_val_to_index($val) };
  46         107  
40 46 100       99 return 0 unless defined $idx;
41 44 100       268 return vec($self->bits, $idx, 1) ? 1 : 0;
42             }
43              
44             sub add {
45 34     34 0 175738 my ($self, @vals) = @_;
46 34         83 $self->_assert_mutable;
47 33         64 my $bits = $self->bits;
48 33         68 for my $val (@vals) {
49 53         100 my $idx = $self->_val_to_index($val);
50 52         149 vec($bits, $idx, 1) = 1;
51             }
52 32         86 $self->bits($bits);
53 32         89 return $self;
54             }
55              
56             sub remove {
57 4     4 0 3945 my ($self, @vals) = @_;
58 4         15 $self->_assert_mutable;
59 3         10 my $bits = $self->bits;
60 3         9 for my $val (@vals) {
61 3         9 my $idx = $self->_val_to_index($val);
62 3         17 vec($bits, $idx, 1) = 0;
63             }
64 3         14 $self->bits($bits);
65 3         10 return $self;
66             }
67              
68             sub toggle {
69 3     3 0 734 my ($self, @vals) = @_;
70 3         10 $self->_assert_mutable;
71 2         7 my $bits = $self->bits;
72 2         7 for my $val (@vals) {
73 2         6 my $idx = $self->_val_to_index($val);
74 2 100       14 vec($bits, $idx, 1) = vec($bits, $idx, 1) ? 0 : 1;
75             }
76 2         9 $self->bits($bits);
77 2         5 return $self;
78             }
79              
80             sub members {
81 1     1 0 4 my ($self) = @_;
82 1         7 my $names = $self->meta->names;
83 1         6 my $n2v = $self->meta->name2val;
84 1         4 my $bits = $self->bits;
85 1         2 my @out;
86 1         5 for my $i (0 .. $#$names) {
87 3 100       15 push @out, $n2v->{$names->[$i]} if vec($bits, $i, 1);
88             }
89 1         8 return @out;
90             }
91              
92             sub names {
93 5     5 0 14 my ($self) = @_;
94 5         20 my $all_names = $self->meta->names;
95 5         10 my $bits = $self->bits;
96 5         8 my @out;
97 5         15 for my $i (0 .. $#$all_names) {
98 15 100       38 push @out, $all_names->[$i] if vec($bits, $i, 1);
99             }
100 5         45 return @out;
101             }
102              
103             sub count {
104 17     17 0 46 my ($self) = @_;
105 17         23 my $n = 0;
106 17         62 my $total = $self->meta->count;
107 17         37 my $bits = $self->bits;
108 17         46 for my $i (0 .. $total - 1) {
109 52 100       104 $n++ if vec($bits, $i, 1);
110             }
111 17         125 return $n;
112             }
113              
114             sub is_empty {
115 5     5 0 8 my ($self) = @_;
116 5 100       12 return $self->count == 0 ? 1 : 0;
117             }
118              
119             sub clone {
120 34     34 0 59384 my ($self) = @_;
121 34         304 my $new = Enum::Declare::Set->new(
122             meta => $self->meta,
123             name => $self->name,
124             );
125 34         101 $new->bits($self->bits);
126 34         64 return $new;
127             }
128              
129             # Set algebra — return new mutable Set
130              
131             sub union {
132 2     2 0 14 my ($self, $other) = @_;
133 2         47 $self->_assert_same_enum($other);
134 1         4 my $new = $self->clone;
135 1         8 $new->bits($self->bits | $other->bits);
136 1         3 return $new;
137             }
138              
139             sub intersection {
140 1     1 0 10 my ($self, $other) = @_;
141 1         4 $self->_assert_same_enum($other);
142 1         4 my $new = $self->clone;
143 1         11 $new->bits($self->bits & $other->bits);
144 1         3 return $new;
145             }
146              
147             sub difference {
148 1     1 0 9 my ($self, $other) = @_;
149 1         6 $self->_assert_same_enum($other);
150 1         4 my $new = $self->clone;
151 1         9 $new->bits($self->bits & ~$other->bits);
152 1         4 return $new;
153             }
154              
155             sub symmetric_difference {
156 1     1 0 7 my ($self, $other) = @_;
157 1         4 $self->_assert_same_enum($other);
158 1         2 my $new = $self->clone;
159 1         6 $new->bits($self->bits ^ $other->bits);
160 1         1 return $new;
161             }
162              
163             # Comparisons
164              
165             sub is_subset {
166 3     3 0 10 my ($self, $other) = @_;
167 3         9 $self->_assert_same_enum($other);
168 3 100       24 return ($self->bits & $other->bits) eq $self->bits ? 1 : 0;
169             }
170              
171             sub is_superset {
172 1     1 0 3 my ($self, $other) = @_;
173 1         4 $self->_assert_same_enum($other);
174 1         2 return $other->is_subset($self);
175             }
176              
177             sub is_disjoint {
178 2     2 0 6 my ($self, $other) = @_;
179 2         5 $self->_assert_same_enum($other);
180 2         6 my $inter = $self->bits & $other->bits;
181 2 100       11 return $inter eq ("\0" x length($inter)) ? 1 : 0;
182             }
183              
184             sub equals {
185 4     4 0 13 my ($self, $other) = @_;
186 4 50 33     25 return 0 unless ref($other) && $other->isa(__PACKAGE__);
187 4         9 $self->_assert_same_enum($other);
188 4 100       21 return $self->bits eq $other->bits ? 1 : 0;
189             }
190              
191             sub _val_to_index {
192 110     110   166 my ($self, $val) = @_;
193 110         278 my $v2n = $self->meta->val2name;
194             croak("Invalid enum value '$val' for " . $self->meta->enum_name)
195 110 100       777 unless exists $v2n->{$val};
196 107         258 my $names = $self->meta->names;
197 107         237 for my $i (0 .. $#$names) {
198             return $i if $self->meta->name2val->{$names->[$i]} eq $val
199             || (looks_like_number($val)
200             && looks_like_number($self->meta->name2val->{$names->[$i]})
201 203 100 66     1399 && $self->meta->name2val->{$names->[$i]} == $val);
      66        
      66        
202             }
203 0         0 croak("Value '$val' not found in " . $self->meta->enum_name);
204             }
205              
206             sub _assert_mutable {
207 41     41   72 my ($self) = @_;
208 41 100       580 croak("cannot modify a frozen set") if $self->frozen;
209             }
210              
211             sub _assert_same_enum {
212 15     15   23 my ($self, $other) = @_;
213 15 100 66     241 croak("sets belong to different enums")
214             unless $self->meta->enum_name eq $other->meta->enum_name
215             && $self->meta->package eq $other->meta->package;
216             }
217              
218             # Stringification
219              
220             sub _stringify {
221 2     2   11 my ($self) = @_;
222 2   50     7 my $label = $self->name || 'Set';
223 2         6 return $label . '(' . join(', ', $self->names) . ')';
224             }
225              
226             1;