File Coverage

blib/lib/Type/Tiny/Bitfield.pm
Criterion Covered Total %
statement 135 136 99.2
branch 36 44 81.8
condition 29 46 63.0
subroutine 28 28 100.0
pod 9 10 90.0
total 237 264 89.7


line stmt bran cond sub pod time code
1             package Type::Tiny::Bitfield;
2              
3 4     4   68432 use 5.008001;
  4         27  
4 4     4   23 use strict;
  4         9  
  4         114  
5 4     4   57 use warnings;
  4         11  
  4         187  
6              
7             BEGIN {
8 4     4   14 $Type::Tiny::Bitfield::AUTHORITY = 'cpan:TOBYINK';
9 4         330 $Type::Tiny::Bitfield::VERSION = '2.004000';
10             }
11              
12             $Type::Tiny::Bitfield::VERSION =~ tr/_//d;
13              
14 14     14   81 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  14         60  
15              
16 4     4   999 use Exporter::Tiny 1.004001 ();
  4         8903  
  4         96  
17 4     4   1367 use Type::Tiny ();
  4         10  
  4         102  
18 4     4   1894 use Types::Common::Numeric qw( +PositiveOrZeroInt );
  4         15  
  4         38  
19 4     4   336 use Eval::TypeTiny qw( eval_closure );
  4         11  
  4         1252  
20              
21             our @ISA = qw( Type::Tiny Exporter::Tiny );
22              
23             __PACKAGE__->_install_overloads(
24             q[+] => 'new_combined',
25             );
26              
27 41     41   170 sub _is_power_of_two { not $_[0] & $_[0]-1 }
28              
29             sub _exporter_fail {
30 6     6   14417 my ( $class, $type_name, $args, $globals ) = @_;
31 6         14 my $caller = $globals->{into};
32 6         31 my %values = %$args;
33 6   66     46 /^[-]/ && delete( $values{$_} ) for keys %values;
34 6         23 my $type = $class->new(
35             name => $type_name,
36             values => \%values,
37             coercion => 1,
38             );
39             $INC{'Type/Registry.pm'}
40             ? 'Type::Registry'->for_class( $caller )->add_type( $type, $type_name )
41             : ( $Type::Registry::DELAYED{$caller}{$type_name} = $type )
42 4 50 33     121 unless( ref($caller) or $caller eq '-lexical' or $globals->{'lexical'} );
    50 33        
43 4         9 return map +( $_->{name} => $_->{code} ), @{ $type->exportables };
  4         10  
44             }
45              
46             sub new {
47 27     27 1 1176 my $proto = shift;
48            
49 27 50       115 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  0         0  
50             _croak
51             "Bitfield type constraints cannot have a parent constraint passed to the constructor"
52 27 100       84 if exists $opts{parent};
53             _croak
54             "Bitfield type constraints cannot have a constraint coderef passed to the constructor"
55 26 100       66 if exists $opts{constraint};
56             _croak
57             "Bitfield type constraints cannot have a inlining coderef passed to the constructor"
58 25 100       56 if exists $opts{inlined};
59             _croak "Need to supply hashref of values"
60 24 100       63 unless exists $opts{values};
61            
62 23         67 $opts{parent} = PositiveOrZeroInt;
63            
64 23         103 for my $key ( keys %{ $opts{values} } ) {
  23         81  
65 46 100       213 _croak "Not an all-caps name in a bitfield: $key"
66             unless $key =~ /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*/
67             }
68 21         42 my $ALL = 0;
69 21         41 my %already = ();
70 21         31 for my $value ( values %{ $opts{values} } ) {
  21         53  
71 41 100 66     207 _croak "Not a positive power of 2 in a bitfield: $value"
72             unless is_PositiveOrZeroInt( $value ) && _is_power_of_two( $value );
73             _croak "Duplicate value in a bitfield: $value"
74 39 100       124 if $already{$value}++;
75 38         69 $ALL |= ( 0 + $value );
76             }
77 18         40 $opts{ALL} = $ALL;
78            
79             $opts{constraint} = sub {
80 92     92   379 not shift() & ~$ALL;
81 18         70 };
82            
83 18 50 66     115 if ( defined $opts{coercion}
      66        
84             and !ref $opts{coercion}
85             and 1 eq $opts{coercion} ) {
86 12         29 delete $opts{coercion};
87             $opts{_build_coercion} = sub {
88 11     11   57 require Types::Standard;
89 11         24 my $c = shift;
90 11         33 my $t = $c->type_constraint;
91 11         48 $c->add_type_coercions(
92             Types::Standard::Str(),
93             $t->_stringy_coercion,
94             );
95 12         41 };
96             } #/ if ( defined $opts{coercion...})
97            
98 18         95 return $proto->SUPER::new( %opts );
99             } #/ sub new
100              
101             sub new_combined {
102 10     10 0 30 my ( $self, $other, $swap ) = @_;
103            
104 10 100 33     59 Scalar::Util::blessed( $self )
      66        
      100        
105             && $self->isa( __PACKAGE__ )
106             && Scalar::Util::blessed( $other )
107             && $other->isa( __PACKAGE__ )
108             or _croak( "Bad overloaded operation" );
109            
110 6 50       19 ( $other, $self ) = ( $self, $other ) if $swap;
111            
112 6         11 for my $k ( keys %{ $self->values } ) {
  6         15  
113             _croak "Conflicting value: $k"
114 8 100       18 if exists $other->values->{$k};
115             }
116            
117 5         10 my %all_values = ( %{ $self->values }, %{ $other->values } );
  5         11  
  5         9  
118 5 100 100     36 return ref( $self )->new(
119             display_name => sprintf( '%s+%s', "$self", "$other" ),
120             values => \%all_values,
121             ( $self->has_coercion || $other->has_coercion )
122             ? ( coercion => 1 )
123             : (),
124             );
125             }
126              
127             sub values {
128 62     62 1 237 $_[0]{values};
129             }
130              
131             sub _lockdown {
132 18     18   45 my ( $self, $callback ) = @_;
133 18         54 $callback->( $self->{values} );
134             }
135              
136             sub exportables {
137 5     5 1 16 my ( $self, $base_name ) = @_;
138 5 50       33 if ( not $self->is_anon ) {
139 5   33     29 $base_name ||= $self->name;
140             }
141            
142 5         28 my $exportables = $self->SUPER::exportables( $base_name );
143            
144 5         27 require Eval::TypeTiny;
145 5         20 require B;
146            
147 5         1401 for my $key ( keys %{ $self->values } ) {
  5         19  
148 17         44 my $value = $self->values->{$key};
149 17         117 push @$exportables, {
150             name => uc( sprintf '%s_%s', $base_name, $key ),
151             tags => [ 'constants' ],
152             code => Eval::TypeTiny::eval_closure(
153             source => sprintf( 'sub () { %d }', $value ),
154             environment => {},
155             ),
156             };
157             }
158            
159 5         15 my $weak = $self;
160 5         22 require Scalar::Util;
161 5         21 Scalar::Util::weaken( $weak );
162             push @$exportables, {
163             name => sprintf( '%s_to_Str', $base_name ),
164             tags => [ 'from' ],
165 1     1   4 code => sub { $weak->to_string( @_ ) },
166 5         37 };
167            
168 5         86 return $exportables;
169             }
170              
171             sub constant_names {
172 1     1 1 172 my $self = shift;
173 4         30 return map { $_->{name} }
174 9         13 grep { my $tags = $_->{tags}; grep $_ eq 'constants', @$tags; }
  9         17  
175 1 50       3 @{ $self->exportables || [] };
  1         5  
176             }
177              
178             sub can_be_inlined {
179 126     126 1 4111 !!1;
180             }
181              
182             sub inline_check {
183 221     221 1 545 my ( $self, $var ) = @_;
184            
185             return sprintf(
186             '( %s and not %s & ~%d )',
187             PositiveOrZeroInt->inline_check( $var ),
188             $var,
189             $self->{ALL},
190 221         667 );
191             }
192              
193             sub _stringy_coercion {
194 12     12   63 my ( $self, $varname ) = @_;
195 12   100     71 $varname ||= '$_';
196 12         16 my %vals = %{ $self->values };
  12         31  
197 12         41 my $pfx = uc( "$self" );
198 12         28 my $pfxl = length $pfx;
199             my $hash = sprintf(
200             '( %s )',
201             join(
202             q{, },
203 12         173 map sprintf( '%s => %d', B::perlstring($_), $vals{$_} ),
204             sort keys %vals,
205             ),
206             );
207 12         99 return qq{do { my \$bits = 0; my \%lookup = $hash; for my \$tok ( grep /\\w/, split /[\\s|+]+/, uc( $varname ) ) { if ( substr( \$tok, 0, $pfxl) eq "$pfx" ) { \$tok = substr( \$tok, $pfxl ); \$tok =~ s/^_//; } if ( exists \$lookup{\$tok} ) { \$bits |= \$lookup{\$tok}; next; } require Carp; Carp::carp("Unknown token: \$tok"); } \$bits; }};
208             }
209              
210             sub from_string {
211 1     1 1 634 my ( $self, $str ) = @_;
212 1   33     15 $self->{from_string} ||= eval_closure(
213             environment => {},
214             source => sprintf( 'sub { my $STR = shift; %s }', $self->_stringy_coercion( '$STR' ) ),
215             );
216 1         22 $self->{from_string}->( $str );
217             }
218              
219             sub to_string {
220 6     6 1 780 my ( $self, $int ) = @_;
221 6 100       22 $self->check( $int ) or return undef;
222 4         28 my %values = %{ $self->values };
  4         11  
223 4   100     20 $self->{all_names} ||= [ sort { $values{$a} <=> $values{$b} } keys %values ];
  4         11  
224 4         8 $int += 0;
225 4         6 my @names;
226 4         8 for my $n ( @{ $self->{all_names} } ) {
  4         11  
227 16 100       36 push @names, $n if $int & $values{$n};
228             }
229 4         45 return join q{|}, @names;
230             }
231              
232             sub AUTOLOAD {
233 5     5   2924 our $AUTOLOAD;
234 5         11 my $self = shift;
235 5         37 my ( $m ) = ( $AUTOLOAD =~ /::(\w+)$/ );
236 5 50       21 return if $m eq 'DESTROY';
237 5 100 66     81 if ( ref $self and exists $self->{values}{$m} ) {
238 4         27 return 0 + $self->{values}{$m};
239             }
240 1         3 local $Type::Tiny::AUTOLOAD = $AUTOLOAD;
241 1         9 return $self->SUPER::AUTOLOAD( @_ );
242             }
243              
244             sub can {
245 502     502 1 1157 my ( $self, $m ) = ( shift, @_ );
246 502 100 66     2071 if ( ref $self and exists $self->{values}{$m} ) {
247 2     1   20 return sub () { 0 + $self->{values}{$m} };
  1         6  
248             }
249 500         1296 return $self->SUPER::can( @_ );
250             }
251              
252             1;
253              
254             __END__