File Coverage

blib/lib/Template/Direct/Maths.pm
Criterion Covered Total %
statement 75 80 93.7
branch 33 40 82.5
condition 5 9 55.5
subroutine 10 11 90.9
pod 6 6 100.0
total 129 146 88.3


line stmt bran cond sub pod time code
1             package Template::Direct::Maths;
2              
3 2     2   12 use base Template::Direct::Base;
  2         4  
  2         157  
4 2     2   13 use Template::Direct;
  2         4  
  2         38  
5              
6 2     2   10 use strict;
  2         3  
  2         83  
7 2     2   12 use warnings;
  2         2  
  2         76  
8              
9             =head1 NAME
10              
11             Template::Direct::Maths - Handle a mathimatical query
12              
13             =head1 DESCRIPTION
14              
15             Provide support for doing simple calculations (SIMPLE ONLY!)
16              
17             =head1 METHODS
18              
19             =cut
20              
21 2     2   18 use Carp;
  2         3  
  2         2835  
22              
23             my %rules = (
24             '+' => 3,
25             '-' => 3,
26             '*' => 2,
27             '/' => 2,
28             '%' => 1,
29             '^' => 0,
30             );
31              
32             =head2 I<$class>->new( $index, $line )
33              
34             Create a new instance object.
35              
36             =cut
37             sub new {
38 7     7 1 26 my ($class, $index, $line, %p) = @_;
39 7         28 my $self = $class->SUPER::new(%p);
40 7         21 $self->{'startTag'} = $index;
41 7         19 my ($s, $p) = split(/=/, $line);
42 7         12 $self->{'Statement'} = $s;
43 7         13 $self->{'Print'} = $p;
44 7         21 return $self;
45             }
46              
47             =head2 I<$maths>->tagName( )
48              
49             Returns 'maths'
50              
51             =cut
52 0     0 1 0 sub tagName { 'maths' }
53              
54             =head2 I<$maths>->singleTag( )
55              
56             Returns true
57              
58             =cut
59 7     7 1 20 sub singleTag { 1 }
60              
61             =head2 I<$maths>->compile( )
62              
63             Modifies a template with the data calculated.
64              
65             =cut
66             sub compile {
67 7     7 1 14 my ($self, $data, $template, %p) = @_;
68              
69 7         22 my $statement = $self->parseStatement( $self->{'Statement'}, $data );
70 7         18 my $result = $self->calculate( $statement );
71              
72 7 100       22 if($self->{'Print'}) {
73 2         24 $result = sprintf($self->{'Print'}, $result);
74             }
75              
76 7         32 $self->setTagSection($template, $self->{'startTag'}, $result);
77             }
78              
79             =head2 I<$maths>->parseStatement( $s, $data )
80              
81             Return an array structure of values to calculate.
82              
83             =cut
84             sub parseStatement {
85 7     7 1 12 my ($self, $s, $data) = @_;
86 7         12 my $statement = [];
87              
88             #Split into raw tokens
89 7         27 my @raws = split(/\s+/, $s);
90 7         10 my @depths;
91 7         8 my $current = $statement;
92              
93 7         11 foreach my $raw (@raws) {
94              
95 37 100       80 if($raw =~ s/^\(//) {
96             # New level
97 1         2 my $new = [];
98 1         3 push @{$current}, $new;
  1         3  
99 1 50       4 push @depths, $current if $current;
100 1         2 $current = $new;
101             }
102              
103 37 100       70 my $end = $raw =~ s/\)$// ? 1 : 0;
104              
105 37 50       66 if($raw ne '') {
106             # Add sane tokens only, remove all unexpected charicters.
107 37         40 my $sane = $raw;
108             #$sane =~ s/[^\w\$_\{\}\<\>\|\&\=\!\@]//g;
109              
110             # Get datum if required, replace this token with real value
111 37 100       70 if($sane =~ /^\$(.+)$/) {
112 3         11 $sane = $data->getDatum($1, forceString => 1);
113             }
114              
115             # Set 0 when required
116 37 50       72 $sane = 0 if not $sane;
117              
118             # Push this token onto the current stack.
119 37 50 33     153 push @{$current}, $sane if defined($sane) and scalar($sane.'') ne '';
  37         71  
120             }
121              
122 37 100 66     101 $current = pop @depths if $end and @depths;
123             }
124              
125 7         21 return $statement;
126             }
127              
128             =head2 I<$maths>->calculate( $statement )
129              
130             Return a result based on calulating the statement.
131              
132             =cut
133             sub calculate {
134 22     22 1 30 my ($self, $s) = @_;
135 22         19 my $len = @{$s};
  22         31  
136              
137             # Return Directly
138 22 50       50 return $s->[0] if $len == 1;
139              
140 22 100 66     84 if($len > 3 and not (($len-1) % 2)) {
    50          
141             # Sort out the preceidence order and combine.
142 7         7 my @p;
143             # Take each operator index from the stack
144 7         21 for(my $i=1;$i<$len;$i+=2) {
145 18         38 push @p, $i;
146             }
147 7         19 foreach my $i (sort { $rules{$s->[$a]} <=> $rules{$s->[$b]} } @p) {
  12         31  
148             # Remove 3 tokens, calculate them and
149             # Put the result back on the stack
150 7         8 splice(@{$s}, $i-1, 0, [ splice(@{$s}, $i-1, 3) ] );
  7         10  
  7         38  
151 7         51 return $self->calculate( $s );
152             }
153             } elsif($len == 3) {
154             # Calculate trinary
155 15         13 my ($a, $o, $b) = @{$s};
  15         32  
156 15 100       45 $a = $self->calculate( $a ) if ref($a) eq 'ARRAY';
157 15 100       44 $b = $self->calculate( $b ) if ref($b) eq 'ARRAY';
158 15 100       37 return $a + $b if $o eq '+';
159 11 100       26 return $a - $b if $o eq '-';
160 10 100       28 return $a * $b if $o eq '*';
161 5 100       18 return $a / $b if $o eq '/';
162 2 100       8 return $a % $b if $o eq '%';
163 1 50       6 return $a ** $b if $o eq '^';
164 0           warn "\nUnknown operator '$o' in statement: ".$self->{'statement'}."\n";
165 0           return 0;
166             }
167              
168 0           warn "Calculation broken for ".$self->{'Statement'}." : $len (".(($len-1) % 2).")\n";
169 0           return 0;
170             }
171              
172             =head1 AUTHOR
173              
174             Martin Owens - Copyright 2008, AGPL
175              
176             =cut
177             1;