File Coverage

blib/lib/Data/Dimensions.pm
Criterion Covered Total %
statement 129 189 68.2
branch 28 44 63.6
condition 7 19 36.8
subroutine 29 52 55.7
pod 2 17 11.7
total 195 321 60.7


line stmt bran cond sub pod time code
1             package Data::Dimensions;
2              
3 5     5   229326 use Data::Dimensions::Map;
  5         18  
  5         244  
4 5     5   11591 use Data::Dimensions::SickTie;
  5         14  
  5         204  
5 5     5   36 use strict;
  5         11  
  5         206  
6 5     5   3114 use vars qw($VERSION @ISA @HANDLER);
  5         10  
  5         831  
7             @ISA = qw();
8             $VERSION = '0.05';
9              
10             # use me baby
11             sub import {
12 5     5   58 my ($class, @stuff) = @_;
13 5         6841 foreach (@stuff) {
14 4 100       16 if ($_ eq 'extended') {
15 2         13 push_handler(\&Data::Dimensions::Map::parse_other);
16             }
17 4 100       9123 if ($_ eq '&units') {
18 2         14 my ($pack) = caller;
19             {
20 5     5   34 no strict 'refs';
  5         9  
  5         16698  
  2         6  
21 2         5 *{$pack . '::units'} = \&units;
  2         18  
22             }
23             }
24             }
25             }
26              
27             # These are the vaguely public methods
28             sub set :lvalue {
29 19     19 0 420 my $self = shift;
30 19 50       70 if (@_) {
31 0         0 $self->natural(@_);
32 0         0 return $self;
33             }
34             # I can't stop using this as it exists now
35 19         23 my $foo;
36 19         114 tie $foo, 'Data::Dimensions::SickTie', $self, @_;
37 19         131 $foo;
38             }
39              
40             # For, er, symmetry, yes.
41             sub get {
42 0     0 0 0 my $self = shift;
43 0         0 return $self->natural;
44             }
45              
46             sub new {
47 29     29 0 5110 my ($class, $units, $value) = @_;
48 29   66     125 $class = ref($class) || $class;
49 29         46 my $self = {};
50 29         73 @{$self}{qw(units scale)} = basicate_units($units);
  29         80  
51 29 100       162 $self->{base} = $value * $self->{scale}
52             if defined ($value);
53 29         101 return bless $self, $class;
54             }
55              
56             sub units {
57 5     5 1 67 Data::Dimensions->new(@_);
58             }
59              
60             #### tie() now works? for perl 5.14 so
61             sub TIESCALAR {
62 0 0   0   0 if ($] < 5.014) {
63 0         0 croak("Cannot use tie with Data::Dimensions in Perl < 5.14.0, use set \$foo = ... instead");
64             }
65 0         0 my $class = shift;
66              
67 0   0     0 my $units = shift || {};
68 0   0     0 my $val = shift || undef; # because
69 0         0 $class->new($units, $val);
70             }
71             sub FETCH {
72 0     0   0 return $_[0];
73             }
74             sub STORE {
75 0     0   0 my ($self, $val) = @_;
76 0 0 0     0 if (!ref($val) || !UNIVERSAL::isa($val, 'Data::Dimensions')) { # make a new me from a 'value'
77 0         0 $self->natural($val);
78             }
79             else {
80 0 0       0 $self->_moan("Storing value with incorrect units")
81             unless $self->same_units($val);
82 0         0 $self->base($val->base);
83             }
84             }
85              
86              
87              
88             ##### Not so public methods
89             sub natural {
90 23     23 1 33 my $self = shift;
91 23 100       48 if (@_) {
92 13         366 $self->{base} = $_[0] * $self->{scale};
93             }
94             else {
95 10         55 return ($self->{base} / $self->{scale});
96             }
97             }
98              
99             sub base {
100 18     18 0 30 my $self = shift;
101 18 100       53 if (@_) {
102 8         36 $self->{base} = $_[0];
103             }
104             else {
105 10         46 return $self->{base};
106             }
107             }
108              
109             sub same_units {
110 12     12 0 24 my ($self, $other) = @_;
111 12         31 my ($ou, $tu) = ($self->{units}, $other->{units});
112 12         61 my %temp = (%$ou, %$tu);
113              
114             {
115 12         21 local $^W = 0; # look Ma! No warnings.
  12         45  
116 12         34 foreach (keys %temp) {
117 21 100       107 return 0 if $ou->{$_} != $tu->{$_};
118             }
119             }
120 9         49 return 1;
121             }
122              
123             sub no_units {
124 2     2 0 2 my $self = shift;
125 2         4 my $ou = $self->{units};
126 2         3 foreach (keys %{$ou}) {
  2         7  
127 0 0       0 return 0 if $ou->{$_};
128             }
129 2         7 return 1;
130             }
131              
132             ## debug and death
133             sub _dump {
134 0     0   0 my $self = shift;
135 0         0 print overload::StrVal($self);
136 0         0 print " base: ", $self->{base}, " scale:", $self->{scale}, "\n";
137 0         0 foreach (keys %{$self->{units}}) {
  0         0  
138 0         0 print " $_ => ", $self->{units}->{$_}, "\n";
139             }
140             }
141              
142             sub _moan {
143 3     3   6 my $i = 0;
144 3         33 while ((caller($i))[0] =~ /Data.*Dimensions/) {
145 4         24 $i++;
146             }
147 3         13 my ($pack, $file, $line) = caller($i);
148 3         38 die($_[1] ." at $file line $line\n");
149             }
150              
151             ##### Overloading gubbins
152             # We use _guard to make sure both arguments are Data::Dimensions objects
153             # and to reverse arguments before they get any further
154             # as this simplifies code in the overloading routines
155              
156             sub _guard {
157 7     7   15 my ($one, $two, $r) = @_;
158 7 100 66     54 if ($r) {
    100          
159 1         5 $two = $one->new({}, $two);
160 1         4 ($one, $two) = ($two, $one);
161             }
162             elsif (!(ref($two) && UNIVERSAL::isa($two, 'Data::Dimensions'))) {
163 2         8 $two = $one->new({}, $two);
164             }
165 7         42 return ($one, $two);
166             }
167              
168             use overload
169             # these must be between objects with the same units
170 3     2   38 '+' => sub {u_arith(sub {$_[0] + $_[1]}, @_)},
  2         9  
171 0     0   0 '-' => sub {u_arith(sub {$_[0] - $_[1]}, @_)},
  0         0  
172 0     0   0 '%' => sub {u_arith(sub {$_[0] % $_[1]}, @_)},
  0         0  
173 0     0   0 '&' => sub {u_arith(sub {$_[0] & $_[1]}, @_)},
  0         0  
174 0     0   0 '|' => sub {u_arith(sub {$_[0] | $_[1]}, @_)},
  0         0  
175 0     0   0 '^' => sub {u_arith(sub {$_[0] ^ $_[1]}, @_)},
  0         0  
176 7     7   107 '<=>' => sub {u_comp(sub {$_[0] <=> $_[1]}, @_)},
  7         46  
177 0     0   0 'cmp' => sub {u_comp(sub {$_[0] cmp $_[1]}, @_)},
  0         0  
178              
179             # these can propogate their units
180 1     1   6 '/' => sub {u_div(sub {$_[0] / $_[1]}, &_guard)},
  1         3  
181 4     4   33 '*' => sub {u_mul(sub {$_[0] * $_[1]}, &_guard)},
  4         12  
182              
183             # these need to be careful about basic/natural units
184 0     0   0 '++' => sub {$_[0]->natural($_[0]->natural + 1), shift},
185 0     0   0 '--' => sub {$_[0]->natural($_[0]->natural - 1), shift},
186              
187             # These need (some) args with NO units, and need natural units
188 2     2   1155 '**' => sub {u_exp(&_guard)},
189 0     0   0 'cos' => sub {u_nounit(sub {cos $_[0]}, $_[0])},
  0         0  
190 0     0   0 'sin' => sub {u_nounit(sub {sin $_[0]}, $_[0])},
  0         0  
191 0     0   0 'exp' => sub {u_nounit(sub {exp $_[0]}, $_[0])},
  0         0  
192 0     0   0 'log' => sub {u_nounit(sub {log $_[0]}, $_[0])},
  0         0  
193 0     0   0 'sqrt' => sub {u_exp(sub {$_[0] ** $_[1]}, _guard($_[0], 0.5, 0))},
  0         0  
194              
195             # These output, so need to use natural units
196 0     0   0 '0+' => sub {$_[0]->natural},
197 0     0   0 '""' => sub {$_[0]->natural},
198 0     0   0 'bool' => sub {$_[0]->{base}},
199              
200 5     5   33966 '=' => \&clone;
  5         6646  
  5         277  
201             ;
202             sub clone {
203 0     0 0 0 my $new = $_[0]->new($_[0]->{units});
204 0         0 $new->{scale} = $_[0]->{scale};
205 0         0 $new->{base} = $_[0]->{base};
206 0         0 return $new;
207             }
208              
209             # Both args must have same units, return has same units
210             # try to keep result scaled as $one
211             sub u_arith {
212 3     3 0 9 my ($op, $one, $two, $r) = @_;
213 3         12 my $result = $one->new($one->{units});
214 3         9 $result->{scale} = $one->{scale};
215 3 50 33     36 if ($r) {
    50          
216 0         0 $result->natural(&$op, $two, $one->natural);
217             }
218             elsif (!(ref($two) && UNIVERSAL::isa($two, 'Data::Dimensions'))) {
219 0         0 $result->natural(&$op($one->natural, $two));
220             }
221             else {
222 3 100       12 $one->_moan("Mixing different types in arithmetic operation")
223             unless ($one->same_units($two));
224 2         9 $result->base(&$op($one->base, $two->base));
225             }
226 2         12 return $result;
227             }
228              
229             # Must have same units on each side, compare in base units
230             sub u_comp {
231 7     7 0 20 my ($op, $one, $two, $r) = @_;
232 7 50 66     55 if ($r) {
    100          
233 0         0 return &$op($two, $one->natural);
234             }
235             elsif (!(ref($two) && UNIVERSAL::isa($two, 'Data::Dimensions'))) {
236 6         22 return &$op($one->natural, $two);
237             }
238             else {
239 1 50       5 $one->_moan("Mixing different types in comparison operation")
240             unless $one->same_units($two);
241 1         7 return &$op($one->{base}, $two->{base});
242             }
243             }
244              
245             # Can have different units, must propogate units and scaling correctly
246             sub u_div {
247 1     1 0 2 my ($op, $one, $two) = @_;
248 1         5 my $result = $one->new($one->{units});
249 1         4 my ($ru, $tu) = ($result->{units}, $two->{units});
250 1         3 foreach (keys %$tu) {
251 1         4 $ru->{$_} -= $tu->{$_};
252             }
253 1         3 $result->{scale} = $one->{scale} / $two->{scale};
254 1         5 $result->{base} = &$op($one->{base}, $two->{base});
255 1         4 return $result;
256             }
257              
258             sub u_mul {
259 4     4 0 8 my ($op, $one, $two) = @_;
260 4         15 my $result = $one->new($one->{units});
261 4         13 my ($ru, $tu) = ($result->{units}, $two->{units});
262 4         11 foreach (keys %$tu) {
263 8         21 $ru->{$_} += $tu->{$_};
264             }
265 4         13 $result->{scale} = $one->{scale} * $two->{scale};
266 4         16 $result->{base} = &$op($one->{base}, $two->{base});
267 4         18 return $result;
268             }
269              
270             # a**b, b must not have units, a can and these must propogate
271             sub u_exp {
272 2     2 0 3 my ($one, $two) = @_;
273 2 50       7 $one->_moan("Cannot raise to exponent with units")
274             unless $two->no_units;
275 2         6 my $result = $one->new($one->{units});
276 2         3 my $ru = $result->{units};
277 2         6 my $expn = $two->natural;
278 2         6 foreach (keys %$ru) {
279 4         9 $ru->{$_} *= $expn;
280             }
281 2         6 $result->{scale} = $one->{scale} ** $expn;
282 2         4 $result->natural($one->natural ** $expn);
283 2         8 return $result;
284             }
285              
286             # for single arg functions, like cos()
287             sub u_nounit {
288 0     0 0 0 my ($op, $one) = @_;
289 0 0       0 $one->_moan("Value must have no units")
290             unless $one->no_units;
291 0         0 return &$op($one->natural);
292             }
293              
294             ##### Stuff to cope with turning natural units into basic units
295             # See also Data::Dimensions::Map
296             BEGIN {
297 5     5   5891 @HANDLER =( \&Data::Dimensions::Map::parse_SI);
298             }
299              
300             sub push_handler { # ok, ok, it shifts... you try foreach backwards
301 4     4 0 30 my $handler = shift;
302 4 100       26 $handler = UNIVERSAL::isa($handler, 'CODE') ? $handler : shift;
303 4         16 unshift @HANDLER, $handler;
304             }
305              
306             # Charge through appropriate handlers
307             sub basicate_units {
308 29     29 0 50 my ($hr) = @_;
309 29         39 my $scale = 1;
310             # prefixes
311 29         384 ($hr, $scale) = Data::Dimensions::Map::parse_prefix($hr, $scale);
312             # everything else
313 29         71 foreach (@HANDLER) {
314 50         181 ($hr, $scale) = &$_($hr, $scale);
315             }
316 29         69 return ($hr, $scale);
317             }
318              
319             1;
320             __END__