import torrus 1.0.9
[freeside.git] / torrus / perllib / Torrus / RPN.pm
diff --git a/torrus/perllib/Torrus/RPN.pm b/torrus/perllib/Torrus/RPN.pm
new file mode 100644 (file)
index 0000000..20fe15a
--- /dev/null
@@ -0,0 +1,213 @@
+#
+#    Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc.
+#    Copyright (C) 2002  Stanislav Sinyagin
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program; if not, write to the Free Software
+#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+
+# $Id: RPN.pm,v 1.1 2010-12-27 00:03:40 ivan Exp $
+# Stanislav Sinyagin <ssinyagin@yahoo.com>
+
+# a simple little RPN calculator -- implements the same operations
+# that RRDTool does.
+
+# This file is based on Cricket's RPM.pm
+
+package Torrus::RPN;
+
+use strict;
+
+use Torrus::Log;
+use Math::BigFloat;
+
+# Each RPN operator is defined by an array reference with the
+# following  elements: <number of args>, <subroutine>, <accepts undef>
+
+my $operators = {
+    '+' => [ 2, sub{ $_[0] + $_[1]; } ],
+    '-' => [ 2, sub{ $_[0] - $_[1]; } ],
+    '*' => [ 2, sub{ $_[0] * $_[1]; } ],
+    '/' => [ 2, sub{ $_[0] / $_[1]; } ],
+    '%' => [ 2, sub{ $_[0] % $_[1]; } ],
+    'MOD' => [ 2, sub{ $_[0] % $_[1]; } ],
+    'SIN' => [ 1, sub{ sin($_[0]->bsstr()); } ],
+    'COS' => [ 1, sub{ cos($_[0]->bsstr()); } ],
+    'LOG' => [ 1, sub{ log($_[0]); } ],
+    'EXP' => [ 1, sub{ $_[0]->exponent() } ],
+    'FLOOR' => [ 1, sub{ $_[0]->bfloor(); } ],
+    'CEIL' => [ 1, sub{ $_[0]->bceil(); } ],
+    'LT' => [ 2, sub{ ($_[0] <  $_[1]) ? 1:0; } ],
+    'LE' => [ 2, sub{ ($_[0] <= $_[1]) ? 1:0; } ],
+    'GT' => [ 2, sub{ ($_[0] >  $_[1]) ? 1:0; } ],
+    'GE' => [ 2, sub{ ($_[0] >= $_[1]) ? 1:0; } ],
+    'EQ' => [ 2, sub{ ($_[0] == $_[1]) ? 1:0; } ],
+    'IF' => [ 3, sub{ defined($_[0]) ? ($_[0] ? $_[1] : $_[2]) : undef; }, 1],
+    'MIN' => [ 2, sub{ ($_[0] <  $_[1]) ? $_[0] : $_[1]; } ],
+    'MAX' => [ 2, sub{ ($_[0] >  $_[1]) ? $_[0] : $_[1]; } ],
+    'UN'   => [ 1, sub{ defined($_[0]) ? $_[0]->is_nan() : 1; }, 1 ],
+    'UNKN' => [ 0, sub{ undef; } ],
+    # Operators not defined in RRDtool graph
+    'NE'  => [ 2, sub{ ($_[0] != $_[1]) ? 1:0; } ],
+    'AND' => [ 2, sub{ ($_[0] and $_[1]) ? 1:0; } ],
+    'OR'  => [ 2, sub{ ($_[0] or $_[1]) ? 1:0; } ],
+    'NOT' => [ 1, sub{ (not $_[0]) ? 1:0; } ],
+    'ABS' => [ 1, sub{ abs($_[0]); } ],
+    'NOW' => [ 0, sub{ time(); } ],
+    'DUP' => [ 1, sub{ ($_[0], $_[0]);}, 1 ],
+    'EXC' => [ 2, sub{ ($_[1], $_[0]); }, 1 ],
+    'NUM' => [ 1, sub{ defined($_[0]) ? $_[0] : 0; }, 1 ],
+    'INF' => [ 0, sub{ Math::BigFloat->binf(); } ],
+    'NEGINF' => [ 0, sub{ Math::BigFloat->binf('-'); } ]
+    };
+
+
+sub new
+{
+    my $type = shift;
+    my $self = {};
+    bless( $self, $type );
+    $self->{'stack'} = [];
+    return $self;
+}
+
+
+sub operator
+{
+    my $self = shift;
+    my $op = shift;
+
+    my $n_args           = $operators->{$op}->[0];
+    my $action           = $operators->{$op}->[1];
+    my $acceptsUndefined = $operators->{$op}->[2];
+    my @args = ();
+    my $allDefined = 1;
+    for( my $i = 0; $i < $n_args; $i++ )
+    {
+        my $arg = $self->popStack();
+        if( defined( $arg ) or $acceptsUndefined )
+        {
+            push( @args, $arg );
+        }
+        else
+        {
+            $allDefined = 0;
+        }
+    }
+    $self->pushStack( $allDefined ? &{$action}(reverse @args) : undef );
+}
+
+
+sub popStack
+{
+    my $self = shift;
+
+    my $ret;
+    if( scalar( @{$self->{'stack'}} ) == 0 )
+    {
+        Warn("Stack underflow");
+    }
+    else
+    {
+        $ret = pop( @{$self->{'stack'}} );
+    }
+    return $ret;
+}
+
+
+sub pushStack
+{
+    my $self = shift;
+    my @items = @_;
+
+    push( @{$self->{'stack'}}, @items );
+}
+
+
+sub translate
+{
+    my $self = shift;
+    my $string = shift;
+    my $callback = shift;
+
+    # Debug("Translating RPN: $string");
+    my $item;
+    my @new_items;
+    foreach $item ( split( /,/, $string ) )
+    {
+        if( $item =~ /^\{([^\}]*)\}$/ )
+        {
+            my $noderef = $1;
+            my $timeoffset;
+            if( $noderef =~ s/\(([^\)]+)\)// )
+            {
+               $timeoffset = $1;
+            }
+            my $value = &{$callback}( $noderef, $timeoffset );
+            $value = 'UNKN' unless defined( $value );
+            # Debug("$item translated into $value");
+            $item = $value;
+        }
+        elsif( $item eq 'MOD' )
+        {
+            # In Torrus parameter value, percent sign is reserved for
+            # parameter expansion. Rrdtool understands % only.
+            $item = '%';
+        }
+        push( @new_items, $item );
+    }
+
+    $string = join( ',', @new_items );
+    # Debug("RPN translated: $string");
+    return $string;
+}
+
+
+sub run
+{
+    my $self = shift;
+    my $string = shift;
+    my $callback = shift;
+
+    # Debug("Input RPN: $string");
+
+    if( index( $string, '{' ) >= 0 )
+    {
+        $string = $self->translate( $string, $callback );
+    }
+
+    my $item;
+    foreach $item ( split( /,/, $string ) )
+    {
+        if( ref( $operators->{$item} ) )
+        {
+            $self->operator($item);
+        }
+        else
+        {
+            $self->pushStack( Math::BigFloat->new($item) );
+        }
+    }
+    
+    my $retval = $self->popStack();
+    # Debug("RPN result: $retval");
+    return $retval;
+}
+
+1;
+
+# Local Variables:
+# mode: perl
+# indent-tabs-mode: nil
+# perl-indent-level: 4
+# End: