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   53 use strict;
  11         15  
  11         334  
4 11     11   36 use warnings;
  11         12  
  11         455  
5 11     11   38 use Carp qw/croak/;
  11         13  
  11         484  
6 11     11   131 use Scalar::Util qw/looks_like_number/;
  11         43  
  11         548  
7 11     11   61 use Object::Proto;
  11         32  
  11         1396  
8              
9             use overload
10             '""' => \&_stringify,
11             '==' => \&equals,
12 1     1   6 '!=' => sub { !$_[0]->equals($_[1]) },
13 3     3   160 'bool' => sub { !$_[0]->is_empty },
14 11     11   5566 fallback => 1;
  11         17181  
  11         108  
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 3474 my ($self) = @_;
25 45         156 my $count = $self->meta->count;
26 45         74 my $bits = '';
27 45 50       201 vec($bits, $count - 1, 1) = 0 if $count;
28              
29 45         100 for my $val (@{$self->init_values}) {
  45         109  
30 6         11 my $idx = _val_to_index($self, $val);
31 6         14 vec($bits, $idx, 1) = 1;
32             }
33              
34 45         5597 $self->bits($bits);
35             }
36              
37             sub has {
38 46     46 0 173541 my ($self, $val) = @_;
39 46         114 my $idx = eval { $self->_val_to_index($val) };
  46         124  
40 46 100       162 return 0 unless defined $idx;
41 44 100       332 return vec($self->bits, $idx, 1) ? 1 : 0;
42             }
43              
44             sub add {
45 34     34 0 149996 my ($self, @vals) = @_;
46 34         104 $self->_assert_mutable;
47 33         77 my $bits = $self->bits;
48 33         80 for my $val (@vals) {
49 53         127 my $idx = $self->_val_to_index($val);
50 52         188 vec($bits, $idx, 1) = 1;
51             }
52 32         119 $self->bits($bits);
53 32         103 return $self;
54             }
55              
56             sub remove {
57 4     4 0 4217 my ($self, @vals) = @_;
58 4         15 $self->_assert_mutable;
59 3         9 my $bits = $self->bits;
60 3         9 for my $val (@vals) {
61 3         9 my $idx = $self->_val_to_index($val);
62 3         15 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 959 my ($self, @vals) = @_;
70 3         14 $self->_assert_mutable;
71 2         8 my $bits = $self->bits;
72 2         5 for my $val (@vals) {
73 2         7 my $idx = $self->_val_to_index($val);
74 2 100       14 vec($bits, $idx, 1) = vec($bits, $idx, 1) ? 0 : 1;
75             }
76 2         11 $self->bits($bits);
77 2         5 return $self;
78             }
79              
80             sub members {
81 1     1 0 4 my ($self) = @_;
82 1         6 my $names = $self->meta->names;
83 1         4 my $n2v = $self->meta->name2val;
84 1         4 my $bits = $self->bits;
85 1         3 my @out;
86 1         4 for my $i (0 .. $#$names) {
87 3 100       17 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 18 my ($self) = @_;
94 5         19 my $all_names = $self->meta->names;
95 5         14 my $bits = $self->bits;
96 5         10 my @out;
97 5         17 for my $i (0 .. $#$all_names) {
98 15 100       46 push @out, $all_names->[$i] if vec($bits, $i, 1);
99             }
100 5         54 return @out;
101             }
102              
103             sub count {
104 17     17 0 54 my ($self) = @_;
105 17         28 my $n = 0;
106 17         75 my $total = $self->meta->count;
107 17         47 my $bits = $self->bits;
108 17         52 for my $i (0 .. $total - 1) {
109 52 100       143 $n++ if vec($bits, $i, 1);
110             }
111 17         133 return $n;
112             }
113              
114             sub is_empty {
115 5     5 0 14 my ($self) = @_;
116 5 100       16 return $self->count == 0 ? 1 : 0;
117             }
118              
119             sub clone {
120 34     34 0 80628 my ($self) = @_;
121 34         370 my $new = Enum::Declare::Set->new(
122             meta => $self->meta,
123             name => $self->name,
124             );
125 34         129 $new->bits($self->bits);
126 34         78 return $new;
127             }
128              
129             # Set algebra — return new mutable Set
130              
131             sub union {
132 2     2 0 17 my ($self, $other) = @_;
133 2         40 $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 9 my ($self, $other) = @_;
141 1         4 $self->_assert_same_enum($other);
142 1         3 my $new = $self->clone;
143 1         7 $new->bits($self->bits & $other->bits);
144 1         4 return $new;
145             }
146              
147             sub difference {
148 1     1 0 10 my ($self, $other) = @_;
149 1         4 $self->_assert_same_enum($other);
150 1         3 my $new = $self->clone;
151 1         10 $new->bits($self->bits & ~$other->bits);
152 1         3 return $new;
153             }
154              
155             sub symmetric_difference {
156 1     1 0 8 my ($self, $other) = @_;
157 1         5 $self->_assert_same_enum($other);
158 1         3 my $new = $self->clone;
159 1         8 $new->bits($self->bits ^ $other->bits);
160 1         3 return $new;
161             }
162              
163             # Comparisons
164              
165             sub is_subset {
166 3     3 0 13 my ($self, $other) = @_;
167 3         12 $self->_assert_same_enum($other);
168 3 100       29 return ($self->bits & $other->bits) eq $self->bits ? 1 : 0;
169             }
170              
171             sub is_superset {
172 1     1 0 4 my ($self, $other) = @_;
173 1         5 $self->_assert_same_enum($other);
174 1         3 return $other->is_subset($self);
175             }
176              
177             sub is_disjoint {
178 2     2 0 10 my ($self, $other) = @_;
179 2         7 $self->_assert_same_enum($other);
180 2         10 my $inter = $self->bits & $other->bits;
181 2 100       14 return $inter eq ("\0" x length($inter)) ? 1 : 0;
182             }
183              
184             sub equals {
185 4     4 0 24 my ($self, $other) = @_;
186 4 50 33     37 return 0 unless ref($other) && $other->isa(__PACKAGE__);
187 4         13 $self->_assert_same_enum($other);
188 4 100       33 return $self->bits eq $other->bits ? 1 : 0;
189             }
190              
191             sub _val_to_index {
192 110     110   197 my ($self, $val) = @_;
193 110         363 my $v2n = $self->meta->val2name;
194             croak("Invalid enum value '$val' for " . $self->meta->enum_name)
195 110 100       890 unless exists $v2n->{$val};
196 107         281 my $names = $self->meta->names;
197 107         303 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     1645 && $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   90 my ($self) = @_;
208 41 100       645 croak("cannot modify a frozen set") if $self->frozen;
209             }
210              
211             sub _assert_same_enum {
212 15     15   30 my ($self, $other) = @_;
213 15 100 66     288 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   19 my ($self) = @_;
222 2   50     10 my $label = $self->name || 'Set';
223 2         8 return $label . '(' . join(', ', $self->names) . ')';
224             }
225              
226             1;