Template::Direct::Maths - Handle a mathimatical query
Index
Code Index:
NAME

Template::Direct::Maths - Handle a mathimatical query
DESCRIPTION

Provide support for doing simple calculations (SIMPLE ONLY!)
METHODS

$class->new( $index, $line )
Create a new instance object.
$maths->tagName( )
$maths->singleTag( )
$maths->compile( )
Modifies a template with the data calculated.
$maths->parseStatement( $s, $data )
Return an array structure of values to calculate.
$maths->calculate( $statement )
Return a result based on calulating the statement.
AUTHOR

Martin Owens - Copyright 2008, AGPL
package Template::Direct::Maths;
use base Template::Direct::Base;
use Template::Direct;
use strict;
use warnings;
use Carp;
my %rules = (
'+' => 3,
'-' => 3,
'*' => 2,
'/' => 2,
'%' => 1,
'^' => 0,
);
sub new {
my ($class, $index, $line, %p) = @_;
my $self = $class->SUPER::new(%p);
$self->{'startTag'} = $index;
my ($s, $p) = split(/=/, $line);
$self->{'Statement'} = $s;
$self->{'Print'} = $p;
return $self;
}
sub tagName { 'maths' }
sub singleTag { 1 }
sub compile {
my ($self, $data, $template, %p) = @_;
my $statement = $self->parseStatement( $self->{'Statement'}, $data );
my $result = $self->calculate( $statement );
if($self->{'Print'}) {
$result = sprintf($self->{'Print'}, $result);
}
$self->setTagSection($template, $self->{'startTag'}, $result);
}
sub parseStatement {
my ($self, $s, $data) = @_;
my $statement = [];
#Split into raw tokens
my @raws = split(/\s+/, $s);
my @depths;
my $current = $statement;
foreach my $raw (@raws) {
if($raw =~ s/^\(//) {
# New level
my $new = [];
push @{$current}, $new;
push @depths, $current if $current;
$current = $new;
}
my $end = $raw =~ s/\)$// ? 1 : 0;
if($raw ne '') {
# Add sane tokens only, remove all unexpected charicters.
my $sane = $raw;
#$sane =~ s/[^\w\$_\{\}\<\>\|\&\=\!\@]//g;
# Get datum if required, replace this token with real value
if($sane =~ /^\$(.+)$/) {
$sane = $data->getDatum($1, forceString => 1);
}
# Set 0 when required
$sane = 0 if not $sane;
# Push this token onto the current stack.
push @{$current}, $sane if defined($sane) and scalar($sane.'') ne '';
}
$current = pop @depths if $end and @depths;
}
return $statement;
}
sub calculate {
my ($self, $s) = @_;
my $len = @{$s};
# Return Directly
return $s->[0] if $len == 1;
if($len > 3 and not (($len-1) % 2)) {
# Sort out the preceidence order and combine.
my @p;
# Take each operator index from the stack
for(my $i=1;$i<$len;$i+=2) {
push @p, $i;
}
foreach my $i (sort { $rules{$s->[$a]} <=> $rules{$s->[$b]} } @p) {
# Remove 3 tokens, calculate them and
# Put the result back on the stack
splice(@{$s}, $i-1, 0, [ splice(@{$s}, $i-1, 3) ] );
return $self->calculate( $s );
}
} elsif($len == 3) {
# Calculate trinary
my ($a, $o, $b) = @{$s};
$a = $self->calculate( $a ) if ref($a) eq 'ARRAY';
$b = $self->calculate( $b ) if ref($b) eq 'ARRAY';
return $a + $b if $o eq '+';
return $a - $b if $o eq '-';
return $a * $b if $o eq '*';
return $a / $b if $o eq '/';
return $a % $b if $o eq '%';
return $a ** $b if $o eq '^';
warn "\nUnknown operator '$o' in statement: ".$self->{'statement'}."\n";
return 0;
}
warn "Calculation broken for ".$self->{'Statement'}." : $len (".(($len-1) % 2).")\n";
return 0;
}
1;