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   72970 use 5.008001;
  4         29  
4 4     4   49 use strict;
  4         16  
  4         88  
5 4     4   19 use warnings;
  4         7  
  4         226  
6              
7             BEGIN {
8 4     4   14 $Type::Tiny::Bitfield::AUTHORITY = 'cpan:TOBYINK';
9 4         337 $Type::Tiny::Bitfield::VERSION = '2.003_000';
10             }
11              
12             $Type::Tiny::Bitfield::VERSION =~ tr/_//d;
13              
14 14     14   83 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  14         68  
15              
16 4     4   1015 use Exporter::Tiny 1.004001 ();
  4         9395  
  4         93  
17 4     4   1522 use Type::Tiny ();
  4         43  
  4         95  
18 4     4   1582 use Types::Common::Numeric qw( +PositiveOrZeroInt );
  4         14  
  4         49  
19 4     4   352 use Eval::TypeTiny qw( eval_closure );
  4         9  
  4         30  
20              
21             our @ISA = qw( Type::Tiny Exporter::Tiny );
22              
23             __PACKAGE__->_install_overloads(
24             q[+] => 'new_combined',
25             );
26              
27 42     42   180 sub _is_power_of_two { not $_[0] & $_[0]-1 }
28              
29             sub _exporter_fail {
30 6     6   14238 my ( $class, $type_name, $args, $globals ) = @_;
31 6         18 my $caller = $globals->{into};
32 6         26 my %values = %$args;
33 6   66     49 /^[-]/ && delete( $values{$_} ) for keys %values;
34 6         22 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     89 unless( ref($caller) or $caller eq '-lexical' or $globals->{'lexical'} );
    50 33        
43 4         8 return map +( $_->{name} => $_->{code} ), @{ $type->exportables };
  4         11  
44             }
45              
46             sub new {
47 27     27 1 1097 my $proto = shift;
48            
49 27 50       122 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       68 if exists $opts{constraint};
56             _croak
57             "Bitfield type constraints cannot have a inlining coderef passed to the constructor"
58 25 100       58 if exists $opts{inlined};
59             _croak "Need to supply hashref of values"
60 24 100       68 unless exists $opts{values};
61            
62 23         77 $opts{parent} = PositiveOrZeroInt;
63            
64 23         103 for my $key ( keys %{ $opts{values} } ) {
  23         82  
65 46 100       243 _croak "Not an all-caps name in a bitfield: $key"
66             unless $key =~ /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*/
67             }
68 21         46 my $ALL = 0;
69 21         36 my %already = ();
70 21         35 for my $value ( values %{ $opts{values} } ) {
  21         48  
71 42 100 66     249 _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 40 100       134 if $already{$value}++;
75 39         79 $ALL |= ( 0 + $value );
76             }
77 18         35 $opts{ALL} = $ALL;
78            
79             $opts{constraint} = sub {
80 92     92   361 not shift() & ~$ALL;
81 18         73 };
82            
83 18 50 66     107 if ( defined $opts{coercion}
      66        
84             and !ref $opts{coercion}
85             and 1 eq $opts{coercion} ) {
86 12         25 delete $opts{coercion};
87             $opts{_build_coercion} = sub {
88 11     11   56 require Types::Standard;
89 11         19 my $c = shift;
90 11         30 my $t = $c->type_constraint;
91 11         44 $c->add_type_coercions(
92             Types::Standard::Str(),
93             $t->_stringy_coercion,
94             );
95 12         60 };
96             } #/ if ( defined $opts{coercion...})
97            
98 18         91 return $proto->SUPER::new( %opts );
99             } #/ sub new
100              
101             sub new_combined {
102 10     10 0 26 my ( $self, $other, $swap ) = @_;
103            
104 10 100 33     64 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         10 for my $k ( keys %{ $self->values } ) {
  6         12  
113             _croak "Conflicting value: $k"
114 9 100       18 if exists $other->values->{$k};
115             }
116            
117 5         10 my %all_values = ( %{ $self->values }, %{ $other->values } );
  5         13  
  5         10  
118 5 100 100     21 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 63     63 1 252 $_[0]{values};
129             }
130              
131             sub _lockdown {
132 18     18   42 my ( $self, $callback ) = @_;
133 18         58 $callback->( $self->{values} );
134             }
135              
136             sub exportables {
137 5     5 1 12 my ( $self, $base_name ) = @_;
138 5 50       30 if ( not $self->is_anon ) {
139 5   33     22 $base_name ||= $self->name;
140             }
141            
142 5         29 my $exportables = $self->SUPER::exportables( $base_name );
143            
144 5         27 require Eval::TypeTiny;
145 5         30 require B;
146            
147 5         10 for my $key ( keys %{ $self->values } ) {
  5         17  
148 17         45 my $value = $self->values->{$key};
149 17         131 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         17 my $weak = $self;
160 5         29 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   5 code => sub { $weak->to_string( @_ ) },
166 5         38 };
167            
168 5         84 return $exportables;
169             }
170              
171             sub constant_names {
172 1     1 1 234 my $self = shift;
173 4         28 return map { $_->{name} }
174 9         13 grep { my $tags = $_->{tags}; grep $_ eq 'constants', @$tags; }
  9         18  
175 1 50       5 @{ $self->exportables || [] };
  1         6  
176             }
177              
178             sub can_be_inlined {
179 126     126 1 3895 !!1;
180             }
181              
182             sub inline_check {
183 221     221 1 489 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         695 );
191             }
192              
193             sub _stringy_coercion {
194 12     12   61 my ( $self, $varname ) = @_;
195 12   100     57 $varname ||= '$_';
196 12         17 my %vals = %{ $self->values };
  12         30  
197 12         38 my $pfx = uc( "$self" );
198 12         35 my $pfxl = length $pfx;
199             my $hash = sprintf(
200             '( %s )',
201             join(
202             q{, },
203 12         170 map sprintf( '%s => %d', B::perlstring($_), $vals{$_} ),
204             sort keys %vals,
205             ),
206             );
207 12         106 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 728 my ( $self, $str ) = @_;
212 1   33     16 $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 804 my ( $self, $int ) = @_;
221 6 100       23 $self->check( $int ) or return undef;
222 4         33 my %values = %{ $self->values };
  4         13  
223 4   100     26 $self->{all_names} ||= [ sort { $values{$a} <=> $values{$b} } keys %values ];
  4         12  
224 4         6 $int += 0;
225 4         7 my @names;
226 4         7 for my $n ( @{ $self->{all_names} } ) {
  4         9  
227 16 100       39 push @names, $n if $int & $values{$n};
228             }
229 4         34 return join q{|}, @names;
230             }
231              
232             sub AUTOLOAD {
233 5     5   3044 our $AUTOLOAD;
234 5         9 my $self = shift;
235 5         41 my ( $m ) = ( $AUTOLOAD =~ /::(\w+)$/ );
236 5 50       19 return if $m eq 'DESTROY';
237 5 100 66     98 if ( ref $self and exists $self->{values}{$m} ) {
238 4         38 return 0 + $self->{values}{$m};
239             }
240 1         14 local $Type::Tiny::AUTOLOAD = $AUTOLOAD;
241 1         9 return $self->SUPER::AUTOLOAD( @_ );
242             }
243              
244             sub can {
245 502     502 1 1103 my ( $self, $m ) = ( shift, @_ );
246 502 100 66     2044 if ( ref $self and exists $self->{values}{$m} ) {
247 2     1   26 return sub () { 0 + $self->{values}{$m} };
  1         10  
248             }
249 500         1254 return $self->SUPER::can( @_ );
250             }
251              
252             1;
253              
254             __END__
255              
256             =pod
257              
258             =encoding utf-8
259              
260             =head1 NAME
261              
262             Type::Tiny::Bitfield - bitfield/bitflag type constraints
263              
264             =head1 SYNOPSIS
265              
266             Using Type::Tiny::Bitfield's export feature:
267              
268             package LightSource {
269             use Moo;
270            
271             use Type::Tiny::Bitfield LedSet => {
272             RED => 1,
273             GREEN => 2,
274             BLUE => 4,
275             };
276            
277             has leds => ( is => 'ro', isa => LedSet, default => 0, coerce => 1 );
278            
279             sub new_red {
280             my $class = shift;
281             return $class->new( leds => LEDSET_RED );
282             }
283            
284             sub new_green {
285             my $class = shift;
286             return $class->new( leds => LEDSET_GREEN );
287             }
288            
289             sub new_yellow {
290             my $class = shift;
291             return $class->new( leds => LEDSET_RED | LEDSET_GREEN );
292             }
293             }
294              
295             Using Type::Tiny::Bitfield's object-oriented interface:
296              
297             package LightSource {
298             use Moo;
299             use Type::Tiny::Bitfield;
300            
301             my $LedSet = Type::Tiny::Bitfield->new(
302             name => 'LedSet',
303             values => {
304             RED => 1,
305             GREEN => 2,
306             BLUE => 4,
307             },
308             coercion => 1,
309             );
310            
311             has leds => ( is => 'ro', isa => $LedSet, default => 0, coerce => 1 );
312            
313             sub new_red {
314             my $class = shift;
315             return $class->new( leds => $LedSet->RED );
316             }
317            
318             sub new_green {
319             my $class = shift;
320             return $class->new( leds => $LedSet->GREEN );
321             }
322            
323             sub new_yellow {
324             my $class = shift;
325             return $class->new( leds => $LedSet->coerce('red|green') );
326             }
327             }
328              
329             =head1 STATUS
330              
331             This module is covered by the
332             L<Type-Tiny stability policy|Type::Tiny::Manual::Policies/"STABILITY">.
333              
334             =head1 DESCRIPTION
335              
336             Bitfield type constraints.
337              
338             This package inherits from L<Type::Tiny>; see that for most documentation.
339             Major differences are listed below:
340              
341             =head2 Attributes
342              
343             =over
344              
345             =item C<values>
346              
347             Hashref of bits allowed in the bitfield. Keys must be UPPER_SNAKE_CASE strings.
348             Values must be positive integers which are powers of two. The same number
349             cannot be used multiple times.
350              
351             =item C<constraint>
352              
353             Unlike Type::Tiny, you I<cannot> pass a constraint coderef to the constructor.
354             Instead rely on the default.
355              
356             =item C<inlined>
357              
358             Unlike Type::Tiny, you I<cannot> pass an inlining coderef to the constructor.
359             Instead rely on the default.
360              
361             =item C<parent>
362              
363             Parent is always B<Types::Common::Numeric::PositiveOrZeroInt>, and cannot be
364             passed to the constructor.
365              
366             =item C<coercion>
367              
368             If C<< coercion => 1 >> is passed to the constructor, the type will have an
369             automatic coercion from B<Str>. Types built by the C<import> method will
370             always have C<< coercion => 1 >>.
371              
372             In the SYNOPSIS example, the coercion from B<Str> will accept strings like:
373              
374             "RED"
375             "red"
376             "Red Green"
377             "Red+Blue"
378             "blue | GREEN"
379             "LEDSET_RED + LeDsEt_green"
380              
381             =back
382              
383             =head2 Methods
384              
385             This class uses C<AUTOLOAD> to allow the names of each bit in the bitfield
386             to be used as methods. These method names will always be UPPER_SNAKE_CASE.
387              
388             For example, in the synopsis, C<< LedSet->GREEN >> would return 2.
389              
390             Other methods it provides:
391              
392             =over
393              
394             =item C<< from_string( $str ) >>
395              
396             Provides the standard coercion from a string, even if this type constraint
397             doesn't have a coercion.
398              
399             =item C<< to_string( $int ) >>
400              
401             Does the reverse coercion.
402              
403             =item C<< constant_names() >>
404              
405             This is a convenience to allow for:
406              
407             use base 'Exporter::Tiny';
408             push our @EXPORT_OK, LineStyle->constant_names;
409              
410             =back
411              
412             =head2 Exports
413              
414             Type::Tiny::Bitfield can be used as an exporter.
415              
416             use Type::Tiny::Bitfield LedSet => {
417             RED => 1,
418             GREEN => 2,
419             BLUE => 4,
420             };
421              
422             This will export the following functions into your namespace:
423              
424             =over
425              
426             =item C<< LedSet >>
427              
428             =item C<< is_LedSet( $value ) >>
429              
430             =item C<< assert_LedSet( $value ) >>
431              
432             =item C<< to_LedSet( $string ) >>
433              
434             =item C<< LedSet_to_Str( $value ) >>
435              
436             =item C<< LEDSET_RED >>
437              
438             =item C<< LEDSET_GREEN >>
439              
440             =item C<< LEDSET_BLUE >>
441              
442             =back
443              
444             Multiple bitfield types can be exported at once:
445              
446             use Type::Tiny::Enum (
447             LedSet => { RED => 1, GREEN => 2, BLUE => 4 },
448             LedPattern => { FLASHING => 1 },
449             );
450              
451             =head2 Overloading
452              
453             It is possible to combine two Bitfield types using the C<< + >> operator.
454              
455             use Type::Tiny::Enum (
456             LedSet => { RED => 1, GREEN => 2, BLUE => 4 },
457             LedPattern => { FLASHING => 8 },
458             );
459            
460             has leds => (
461             is => 'ro',
462             isa => LedSet + LedPattern,
463             default => 0,
464             coerce => 1
465             );
466              
467             This will allow values like "11" (LEDSET_RED|LEDSET_GREEN|LEDPATTERN_FLASHING).
468              
469             An exception will be thrown if any of the names in the two types being combined
470             conflict.
471              
472             =head1 BUGS
473              
474             Please report any bugs to
475             L<https://github.com/tobyink/p5-type-tiny/issues>.
476              
477             =head1 SEE ALSO
478              
479             L<Type::Tiny::Manual>.
480              
481             L<Type::Tiny>.
482              
483             =head1 AUTHOR
484              
485             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
486              
487             =head1 COPYRIGHT AND LICENCE
488              
489             This software is copyright (c) 2023 by Toby Inkster.
490              
491             This is free software; you can redistribute it and/or modify it under
492             the same terms as the Perl 5 programming language system itself.
493              
494             =head1 DISCLAIMER OF WARRANTIES
495              
496             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
497             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
498             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.