File Coverage

blib/lib/Math/Calc/Units/Compute.pm
Criterion Covered Total %
statement 80 83 96.3
branch 30 32 93.7
condition 10 14 71.4
subroutine 20 21 95.2
pod 0 13 0.0
total 140 163 85.8


line stmt bran cond sub pod time code
1             package Math::Calc::Units::Compute;
2 1     1   4 use base 'Exporter';
  1         2  
  1         127  
3 1     1   5 use vars qw(@EXPORT_OK);
  1         2  
  1         65  
4             @EXPORT_OK = qw(compute
5             plus minus mult divide power
6             unit_mult unit_divide unit_power
7             construct);
8 1     1   644 use strict;
  1         2  
  1         44  
9              
10 1     1   607 use Math::Calc::Units::Convert qw(reduce);
  1         3  
  1         64  
11 1     1   601 use Math::Calc::Units::Rank qw(render_unit);
  1         3  
  1         147  
12 1     1   6 use Math::Calc::Units::Convert::Base;
  1         2  
  1         1422  
13             require Math::Calc::Units::Grammar;
14              
15             sub equivalent {
16 26     26 0 34 my ($u, $v) = @_;
17 26         100 return Math::Calc::Units::Convert::Base->same($u, $v);
18             }
19              
20             sub is_unit {
21 16     16 0 24 my ($x, $unit) = @_;
22 16         46 return equivalent($x, { $unit => 1 });
23             }
24              
25             # All these assume the values are in canonical units.
26             sub plus {
27 5     5 0 26 my ($u, $v) = @_;
28 5         53 $u = reduce($u);
29 5         13 $v = reduce($v);
30              
31 5 100 66     21 if (equivalent($u->[1], $v->[1])) {
    100 33        
    50          
32 3         24 return [ $u->[0] + $v->[0], $u->[1] ];
33             } elsif (is_unit($u->[1], 'timestamp') && is_unit($v->[1], 'sec')) {
34 1         7 return [ $u->[0] + $v->[0], $u->[1] ];
35             } elsif (is_unit($u->[1], 'sec') && is_unit($v->[1], 'timestamp')) {
36 0         0 return [ $u->[0] + $v->[0], $v->[1] ];
37             }
38              
39 1         10 die "Unable to add incompatible units `".render_unit($u->[1])."' and `".render_unit($v->[1])."'";
40             }
41              
42             sub minus {
43 6     6 0 35 my ($u, $v) = @_;
44 6         21 $u = reduce($u);
45 6         16 $v = reduce($v);
46              
47 6 100 100     20 if (is_unit($u->[1], 'timestamp') && is_unit($v->[1], 'timestamp')) {
    100 66        
    100          
48 1         7 return [ $u->[0] - $v->[0], { sec => 1 } ];
49             } elsif (equivalent($u->[1], $v->[1])) {
50 3         17 return [ $u->[0] - $v->[0], $u->[1] ];
51             } elsif (is_unit($u->[1], 'timestamp') && is_unit($v->[1], 'sec')) {
52 1         7 return [ $u->[0] - $v->[0], $u->[1] ];
53             }
54              
55 1         8 die "Unable to subtract incompatible units `".render_unit($u->[1])."' and `".render_unit($v->[1])."'";
56             }
57              
58             sub mult {
59 11     11 0 55 my ($u, $v) = @_;
60 11         49 return [ $u->[0] * $v->[0], unit_mult($u->[1], $v->[1]) ];
61             }
62              
63             sub divide {
64 24     24 0 110 my ($u, $v) = @_;
65 24         109 return [ $u->[0] / $v->[0], unit_divide($u->[1], $v->[1]) ];
66             }
67              
68             sub power {
69 7     7 0 30 my ($u, $v) = @_;
70 7 100       9 die "Can only raise to unit-less powers" if keys %{ $v->[1] };
  7         90  
71 6         22 $u = reduce($u);
72 6 100       8 if (keys %{ $u->[1] } != 0) {
  6         23  
73 5         9 my $power = $v->[0];
74 5 100       73 die "Can only raise a value with units to an integral power"
75             if abs($power - int($power)) > 1e-20;
76 4         15 return [ $u->[0] ** $power, unit_power($u->[1], $power) ];
77             }
78 1         24 return [ $u->[0] ** $v->[0], {} ];
79             }
80              
81             sub unit_mult {
82 35     35 0 56 my ($u, $v, $mult) = @_;
83 35   100     111 $mult ||= 1;
84 35         138 while (my ($unit, $vp) = each %$v) {
85 34         73 $u->{$unit} += $vp * $mult;
86 34 100       234 delete $u->{$unit} if $u->{$unit} == 0; # Keep zeroes out!
87             }
88 35         138 return $u;
89             }
90              
91             sub unit_divide {
92 24     24 0 36 my ($u, $v) = @_;
93 24         61 return unit_mult($u, $v, -1);
94             }
95              
96             sub unit_power {
97 4     4 0 8 my ($u, $power) = @_;
98 4 50       9 return {} if $power == 0;
99 4         18 $u->{$_} *= $power foreach (keys %$u);
100 4         18 return $u;
101             }
102              
103             sub construct {
104 7     7 0 28 my $s = shift;
105 7         41 my ($constructor, $args) = $s =~ /^(\w+)\((.*)\)/;
106 7         23 return Math::Calc::Units::Convert::construct($constructor, $args);
107             }
108              
109             package Math::Calc::Units::Compute;
110              
111             # Poor-man's tokenizer
112             sub tokenize {
113 109     109 0 140 my $data = shift;
114 109         1121 my @tokens = $data =~ m{\s*
115             (
116             \w+\([^\(\)]*\) # constructed (eg date(2001...))
117             |[\d.]+ # Numbers
118             |\w+ # Words
119             |\*\* # Exponentiation (**)
120             |[-+*/()@] # Operators
121             )}xg;
122 109 100       233 my @types = map { /\w\(/ ? 'CONSTRUCT'
  334 100       1585  
    100          
123             :( /\d/ ? 'NUMBER'
124             :( /\w/ ? 'WORD'
125             :( $_))) } @tokens;
126 109         334 return \@tokens, \@types;
127             }
128              
129             # compute : string ->
130             #
131             # If the first character of the string is '#', this will attempt to avoid
132             # canonicalization as much as possible.
133             #
134             sub compute {
135 109     109 0 165 my $expr = shift;
136 109         310 my $canonicalize = $expr !~ /^\#/;
137 109         218 my ($vals, $types) = tokenize($expr);
138             my $lexer = sub {
139             # print "TOK($vals->[0]) TYPE($types->[0])\n" if @$vals;
140 442 100   442   1797 return shift(@$types), shift(@$vals) if (@$types);
141 108         356 return ('', undef);
142 109         498 };
143              
144 109         427 my $parser = new Math::Calc::Units::Grammar;
145              
146             my $v =
147             $parser->YYParse(yylex => $lexer,
148             yyerror => sub {
149 0     0   0 my $parser = shift;
150 0         0 die "Error: expected ".join(" ", $parser->YYExpect)." got `".$parser->YYCurtok."', rest=".join(" ", @$types)."\nfrom ".join(" ", @$vals)."\n";
151             },
152 109         1456 yydebug => 0); # 0x1f);
153 105 100       787 return $canonicalize ? reduce($v) : $v;
154             };
155              
156             1;