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


Template-Direct documentation Contained in the Template-Direct distribution.

Index


Code Index:

NAME

Top

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

DESCRIPTION

Top

  Provide support for doing simple calculations (SIMPLE ONLY!)

METHODS

Top

$class->new( $index, $line )

  Create a new instance object.

$maths->tagName( )

  Returns 'maths'

$maths->singleTag( )

  Returns true

$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

Top

  Martin Owens - Copyright 2008, AGPL


Template-Direct documentation Contained in the Template-Direct distribution.
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;