#!/usr/bin/perl -w
# RPN Calculator 0.0.1
#
# This perl script implements a crude, but powerful, multi-stack Reverse Polish
# Notation calculator with a ReadLine terminal interface.  Any number of stacks
# may be created by the user, each one may have any number of variables defined.
# 
# Elements on each stack may be copied or moved to other stacks... values stored
# on the top of a stack may also be copied to variables for temporary storage.
#
# A wide variety of operators is provided, including
# Single-Instruction-Multiple-Data variants, that operate on a range of
# elements.
#
# Copyright (C) 2009, Stuart Longland <redhatter@gentoo.org>
# 
# 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 version 2.
# 
# 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.

use strict;
use Math::Trig;
use Term::ReadLine;

our $rl;
our %stacks;
our $stack;

# Recognised SI prefixes... taken from Wikipedia
my %siprefix = (
	y	=> 10**(-24),
	z	=> 10**(-21),
	a	=> 10**(-18),
	f	=> 10**(-15),
	p	=> 10**(-12),
	n	=> 10**(-9),
	u	=> 10**(-6),
	m	=> 10**(-3),
	c	=> 10**(-2),
	d	=> 10**(-1),
	Y	=> 10**(24),
	Z	=> 10**(21),
	E	=> 10**(18),
	P	=> 10**(15),
	T	=> 10**(12),
	G	=> 10**(9),
	M	=> 10**(6),
	k	=> 10**(3),
	h	=> 10**(2),
	da	=> 10**(1),
	''	=> 1
);

# Engineering notation prefixes to use
my %engprefix = (
	-8	=>	'y',
	-7	=>	'z',
	-6	=>	'a',
	-5	=>	'f',
	-4	=>	'p',
	-3	=>	'n',
	-2	=>	'u',
	-1	=>	'm',
	0	=>	'',
	1	=>	'k',
	2	=>	'M',
	3	=>	'G',
	4	=>	'T',
	5	=>	'P',
	6	=>	'E',
	7	=>	'Z',
	8	=>	'Y'
);

# All commands recognised by the shell
my %commands = (
	# Basic commands
	'quit'	=>	{	func => \&cmd_quit,
				doc => "Quit RPNCalc",
				args=>[]
			},
	'reset'	=>	{	func => \&cmd_reset,
				doc => "Reset RPNCalc",
				args=>[] },
	'help'	=>	{	func => \&cmd_help,
				doc => "Get Help",
				args=>[
					{	name =>'function',
						type =>'string',
						optional =>1,
						doc =>"Function to display "
							."help for" } ]
			},
	
	# Stack commands
	'pop'	=>	{	func => \&cmd_pop,
				doc => "Pop top of stack.",
				args => [
					{	name =>'stack',
						type =>'string',
						optional =>1,
						doc =>"Stack to push popped "
							."value to (or 'this' "
							."stack).  Value is "
							."discarded if this "
							."argument is "
							."omitted." },
					{	name =>'var',
						type =>'string',
						optional =>1,
						doc =>"Stack variable to place "
							."value in, otherwise "
							."store on top of "
							."stack." } ]
			},
	'dup'	=>	{	func => \&cmd_dup,
				doc => "Duplicate top of stack.",
				args => [
					{	name =>'stack',
						type =>'string',
						optional =>1,
						doc =>"Stack to push popped "
							."value to (or 'this'"
							." stack).  Value is"
							." duplicated on "
							."current stack if "
							."this argument is "
							."omitted." },
					{	name =>'var',
						type =>'string',
						optional =>1,
						doc =>"Stack variable to place "
							."value in, otherwise "
							."store on top of "
							."stack." } ]
			},
	'cp'	=>	{	func => \&cmd_cp,
				doc => 'Copy elements (to another stack)',
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to copy' },
					{	name =>'stack',
						type =>'string',
						optional =>1,
						doc =>'Stack to push the elements onto.' } ]
			},
	'mv'	=>	{	func => \&cmd_mv, doc => 'Move elements to another stack',
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to move' },
					{	name =>'stack',
						type =>'string',
						optional =>1,
						doc =>'Stack to push the elements onto.' } ]
			},
	'swap'	=>	{	func => \&cmd_swap, doc => 'Reverse the order of elements',
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>"Number of elements to reverse on top of stack." } ]
			},
	'new'	=>	{	func => \&cmd_new, doc => "Create new stack.",
				args => [
					{	name =>'name',
						type =>'string',
						optional =>0,
						doc =>"Name of stack" },
					{	name=>'comment',
						type =>'string',
						optional =>1,
						doc =>"Comment to give to stack.  Enclose multiple "
						."words with \"quotes\" or use escape\\ characters." } ]
			},
	'rm'	=>	{	func => \&cmd_rm,
				doc =>"Delete stack.",
				args => [
					{	name =>'name',
						type =>'string',
						optional =>0,
						doc =>"Name of stack" } ]
			},
	'ls'	=>	{	func => \&cmd_ls, doc => "List stack contents.",
				args => [
					{	name =>'name',
						type =>'string',
						optional =>1,
						doc =>"Name of stack to list." } ]
			},
	'switch' =>	{	func => \&cmd_switch, doc => "Switch to alternate stack",
				args => [
					{	name =>'switch',
						type =>'string',
						optional =>1,
						doc =>"Name of stack to switch to" } ]
			},

	# Variable commands
	'vars'	=>	{	func => \&cmd_vars,
				doc =>"List variables",
				args => [
					{	name =>'name',
						type =>'string',
						optional =>1,
						doc =>'Name of stack to list variables from' } ]
			},
	'rmvar'	=>	{	func => \&cmd_rmvar,
				doc =>"Delete variable",
				args => [
					{	name =>'name',
						type =>'string',
						optional =>0,
						doc =>'Variable name' } ]
			},

	# Arithmetic operations
	'neg'	=>	{	func => \&cmd_neg,
				doc =>"Negate stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to negate' } ]
			},
	'inv'	=>	{	func => \&cmd_inv,
				doc =>"Invert stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to invert' } ]
			},
	'add'	=>	{	func => \&cmd_add,
				doc =>"Add stack elements together",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to pop and add' } ]
			},
	'madd'	=>	{	func => \&cmd_madd,
				doc =>"Add stack elements by top of stack",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>0,
						doc =>'Number of elements to add' },
					{	name =>'stack',
						type =>'string',
						optional =>1,
						doc =>'Stack to manipulate.' } ]
			},
	'sub'	=>	{	func => \&cmd_sub,
				doc =>"Subtract stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to pop and subtract' } ]
			},
	'msub'	=>	{	func => \&cmd_madd,
				doc =>"Subtract stack elements by top of stack",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>0,
						doc =>'Number of elements to subtract' },
					{	name =>'stack',
						type =>'string',
						optional =>1,
						doc =>'Stack to manipulate.' } ]
			},
	'mul'	=>	{	func => \&cmd_mul,
				doc =>"Multiply stack elements together",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to pop and multiply' } ]
			},
	'mmul'	=>	{	func => \&cmd_mmul,
				doc =>"Multiply stack elements by top of stack",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>0,
						doc =>'Number of elements to multiply' },
					{	name =>'stack',
						type =>'string',
						optional =>1,
						doc =>'Stack to manipulate.' } ]
			},
	'div'	=>	{	func => \&cmd_div,
				doc =>"Divide stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to pop and divide' } ]
			},
	'mdiv'	=>	{	func => \&cmd_mdiv,
				doc =>"Divide stack elements by top of stack",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>0,
						doc =>'Number of elements to divide' },
					{	name =>'stack',
						type =>'string',
						optional =>1,
						doc =>'Stack to manipulate.' } ]
			},

	# Power functions
	'pow'	=>	{	func => \&cmd_pow,
				doc =>"Raise stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to pop and raise' } ]
			},
	'mpow'	=>	{	func => \&cmd_mpow,
				doc =>"Raise stack elements by top of stack",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to raise' },
					{	name =>'stack',
						type =>'string',
						optional =>1,
						doc =>'Stack to manipulate.' } ]
			},
	'root'	=>	{	func => \&cmd_root,
				doc =>"Lower stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to pop and lower' },
					{	name =>'stack',
						type =>'string',
						optional =>1,
						doc =>'Stack to manipulate.' } ]
			},
	'mroot'	=>	{	func => \&cmd_mroot,
				doc =>"Lower stack elements by top of stack",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to lower' },
					{	name =>'stack',
						type =>'string',
						optional =>1,
						doc =>'Stack to manipulate.' } ]
			},

	# Logarithmic functions
	'ln'	=>	{	func => \&cmd_ln,
				doc =>"Compute the natural logarithm of elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to compute' } ]
			},
	'log'	=>	{	func => \&cmd_log,
				doc =>"Compute the base 10 logarithm of elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to compute' } ]
			},
	'mlog'	=>	{	func => \&cmd_mlog,
				doc =>"Compute the logarithm of elements (base = top of stack)",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to compute' },
					{	name =>'stack',
						type =>'string',
						optional =>1,
						doc =>'Stack to manipulate.' } ]
			},
	'euler'	=>	{	func => \&cmd_euler,
				doc =>"Raise Euler's number to element values",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to raise' } ]
			},
	'exp'	=>	{	func => \&cmd_exp,
				doc =>"Raise 10 to the power of element values",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements to compute' } ]
			},

	# Trigonometric functions
	'deg'	=>	{	func => \&cmd_deg,
				doc =>"Convert stack elements from radians to degrees",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'rad'	=>	{	func => \&cmd_deg,
				doc =>"Convert stack elements from degrees to radians",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'sin'	=>	{	func => \&cmd_sin,
				doc =>"Calculate sine of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'asin'	=>	{	func => \&cmd_asin,
				doc =>"Calculate arcsine of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'sec'	=>	{	func => \&cmd_sec,
				doc =>"Calculate secant of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'asec'	=>	{	func => \&cmd_asec,
				doc =>"Calculate arcus secant of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'cos'	=>	{	func => \&cmd_cos,
				doc =>"Calculate cosine of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'acos'	=>	{	func => \&cmd_acos,
				doc =>"Calculate arccosine of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'csc'	=>	{	func => \&cmd_csc,
				doc =>"Calculate cosecant of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'acsc'	=>	{	func => \&cmd_acsc,
				doc =>"Calculate arcus cosecant of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'tan'	=>	{	func => \&cmd_tan,
				doc =>"Calculate tangent of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'atan'	=>	{	func => \&cmd_atan,
				doc =>"Calculate arctangent of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'cot'	=>	{	func => \&cmd_cot,
				doc =>"Calculate cotangent of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'acot'	=>	{	func => \&cmd_acot,
				doc =>"Calculate arcus cotangent of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'pi'	=>	{	func => \&cmd_pi,
				doc =>"Push (N*PI)/D onto stack",
				args => [
				{	name =>'N',
					doc =>'Numerator to multiply PI by',
					type =>'int',
					optional =>1 },
				{	name =>'D',
					doc =>'Denominator to divide PI by',
					type =>'int',
					optional =>1 } ]
			},

	# Hyperbolic functions
	'sinh'	=>	{	func => \&cmd_sinh,
				doc =>"Calculate hyperbolic sine of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'asinh'	=>	{	func => \&cmd_asinh,
				doc =>"Calculate hyperbolic arcsine of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'sech'	=>	{	func => \&cmd_sech,
				doc =>"Calculate hyperbolic secant of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'asech'	=>	{	func => \&cmd_asech,
				doc =>"Calculate hyperbolic arcus secant of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'cosh'	=>	{	func => \&cmd_cosh,
				doc =>"Calculate hyperbolic cosine of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'acosh'	=>	{	func => \&cmd_acos,
				doc =>"Calculate hyperbolic arccosine of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'csch'	=>	{	func => \&cmd_csch,
				doc =>"Calculate hyperbolic cosecant of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'acsch'	=>	{	func => \&cmd_acsch,
				doc =>"Calculate hyperbolic arcus cosecant of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'tanh'	=>	{	func => \&cmd_tanh,
				doc =>"Calculate hyperbolic tangent of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'atanh'	=>	{	func => \&cmd_atanh,
				doc =>"Calculate hyperbolic arctangent of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'coth'	=>	{	func => \&cmd_coth,
				doc =>"Calculate hyperbolic cotangent of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			},
	'acoth'	=>	{	func => \&cmd_acoth,
				doc =>"Calculate hyperbolic arcus cotangent of stack elements",
				args => [
					{	name =>'num',
						type =>'int',
						optional =>1,
						doc =>'Number of elements' } ]
			}
);

sub cmd_quit {
	print "\nLeaving\n";
	exit;
}

sub cmd_reset {
	%stacks = (
		main => {
			name => 'main',
			comment => 'Main Stack',
			permanent => 1,
			stackdata => [],
			variables => {}
		}
	);
	$stack = $stacks{main};
	return @_;
}

sub cmd_help {
	my $fn = shift;
	if ( defined $fn ) {
		if ( not defined $commands{$fn} ) {
			print "No such function: $fn\n";
			return undef;
		}
		my $fnp = $commands{$fn};
		print "$fn";
		foreach my $arg ( @{$fnp->{args}} ) {
			if ( $arg->{optional} ) {
				print " [".$arg->{name}."]";
			} else {
				print " <".$arg->{name}.">";
			}
		}
		print "\t".$fnp->{doc}."\n";
		foreach my $arg ( @{$fnp->{args}} ) {
			print "\t".$arg->{name}." (".$arg->{type}."): ".$arg->{doc}."\n";
		}
	} else {
		print "Commands: ".join(", ", sort keys %commands)."\n";
	}
	return @_;
}

sub cmd_pop {
	my $stack_name = shift;
	my $to_stack;
	my $var = shift;

	if ( defined $stack_name ) {
		if ( $stack_name eq 'this' ) {
			$to_stack = $stack;
		} else {
			$to_stack = $stacks{$stack_name};
			if ( not defined $to_stack ) {
				return undef;
			}
		}
	} else {
		$stack_name = $stack->{name};
	}

	my $val = shift( @{$stack->{stackdata}} );
	if ( not defined $val ) {
		return undef;
	}

	if ( defined $to_stack ) {
		if ( defined $var ) {
			$to_stack->{variables}->{$var} = $val;
			print "$stack_name.$var => ".engnotation($val)."\n";
		} else {
			unshift @{$to_stack->{stackdata}}, $val;
			print $stack_name."[0] => ".engnotation($val)."\n";
		}
	}
	return @_;
}

sub cmd_dup {
	my $stack_name = shift;
	my $to_stack = $stack;
	my $var = shift;

	if ( defined $stack_name ) {
		if ( $stack_name eq 'this' ) {
			$to_stack = $stack;
		} else {
			$to_stack = $stacks{$stack_name};
			if ( not defined $to_stack ) {
				return undef;
			}
		}
	}

	my $val = $stack->{stackdata}->[0];
	if ( not defined $val ) {
		return undef;
	}

	if ( defined $var ) {
		$to_stack->{variables}->{$var} = $val;
		print "$stack_name.$var => ".engnotation($val)."\n";
	} else {
		unshift @{$to_stack->{stackdata}}, $var;
		print $stack_name."[0] => ".engnotation($val)."\n";
	}
	return @_;
}

sub cmd_mv {
	my $num = shift;
	my $stack_name = shift;
	
	if ( not defined $num ) {
		$num = 1;
	}
	if ( not defined $stack_name ) {
		return @_;
	}
	my $s = $stacks{$stack_name};
	if ( not defined $s ) {
		return @_;
	}
	
	my @vals = splice( @{$stack->{stackdata}}, 0, $num );
	unshift @{$s->{stackdata}}, @vals;
	return @_;
}

sub cmd_cp {
	my $num = shift;
	my $stack_name = shift;
	
	if ( not defined $num ) {
		$num = 1;
	}
	if ( not defined $stack_name ) {
		return @_;
	}
	my $s = $stacks{$stack_name};
	if ( not defined $s ) {
		return @_;
	}
	
	my @vals = splice( @{$stack->{stackdata}}, 0, $num );
	unshift @{$s->{stackdata}}, @vals;
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_swap {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}
	my @vals = reverse(splice( @{$stack->{stackdata}}, 0, $num ));
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_new {
	my $name	= shift;
	my $comment	= shift;
	
	if ( not defined $name ) {
		return undef;
	}

	if ( defined $stacks{$name} ) {
		return undef;
	}
	$stacks{$name} = {
		name => $name,
		comment => $comment,
		permanent => 0,
		stackdata => [],
		variables => {}
	};
	return @_;
}

sub cmd_rm {
	my $name	= shift;
	if ( defined $stacks{$name} ) {
		if ( not $stacks{$name}->{permanent} ) {
			if ( $stack = $stacks{$name} ) {
				$stack = $stacks{main};
			}
			delete $stacks{$name};
			return @_;
		}
	}
	return undef;
}

sub cmd_ls {
	my $name = shift;
	my $s = $stack;
	if ( defined $name ) {
		$s = $stacks{$name};
	}
	if ( not defined $s ) {
		return undef;
	}

	my $i = 0;
	foreach my $val ( @{$s->{stackdata}} ) {
		printf "%4d: %s\n", $i++, engnotation($val);
	}
	return @_;
}

sub cmd_switch {
	my $name = shift;
	if ( not defined $name ) {
		print "Stacks defined: ".join(", ", sort keys %stacks)."\n";
		print "Current Stack: ".$stack->{name}."\n";
		return undef;
	}
	if ( not defined $stacks{$name} ) {
		return undef;
	}
	print "Switched to stack $name\n";
	$stack = $stacks{$name};
	return @_;
}

sub cmd_vars {
	my $name = shift;
	my $s = $stack;
	if ( defined $name ) {
		$s = $stacks{$name};
	}
	if ( not defined $s ) {
		return undef;
	}

	print "Vars of stack ".$s->{name}."\n";
	foreach my $var ( sort keys %{$s->{variables}} ) {
		printf "\t%s\t= %f\n", $var, $s->{variables}->{$var};
	}
	return @_;
}

sub cmd_rmvar {
	my $name = shift;
	if ( not defined $name ) {
		return undef;
	}

	if ( not defined $stack->{variables}->{$name} ) {
		return undef;
	}

	delete $stack->{variables}->{$name};
	print "Variable $name deleted\n";
	return @_;
}

sub cmd_neg {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, -$val;
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_inv {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, 1.0/$val;
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_add {
	my $num = shift;
	if ( not defined $num ) {
		$num = 2;
	}

	my @vals = splice( @{$stack->{stackdata}}, 0, $num );
	my $res = shift(@vals);
	foreach my $v (@vals) {
		$res += $v;
	}
	unshift @{$stack->{stackdata}}, $res;
	return @_;
}

sub cmd_sub {
	my $num = shift;
	if ( not defined $num ) {
		$num = 2;
	}

	my @vals = splice( @{$stack->{stackdata}}, 0, $num );
	my $res = shift(@vals);
	foreach my $v (@vals) {
		$res -= $v;
	}
	unshift @{$stack->{stackdata}}, $res;
	return @_;
}

sub cmd_mul {
	my $num = shift;
	if ( not defined $num ) {
		$num = 2;
	}

	my @vals = splice( @{$stack->{stackdata}}, 0, $num );
	my $res = shift(@vals);
	foreach my $v (@vals) {
		$res *= $v;
	}
	unshift @{$stack->{stackdata}}, $res;
	return @_;
}

sub cmd_div {
	my $num = shift;
	if ( not defined $num ) {
		$num = 2;
	}

	my @vals = splice( @{$stack->{stackdata}}, 0, $num );
	my $res = shift(@vals);
	foreach my $v (@vals) {
		$res /= $v;
	}
	unshift @{$stack->{stackdata}}, $res;
	return @_;
}

sub cmd_pow {
	my $num = shift;
	if ( not defined $num ) {
		$num = 2;
	}

	my @vals = splice( @{$stack->{stackdata}}, 0, $num );
	my $res = shift(@vals);
	foreach my $v (@vals) {
		$res **= $v;
	}
	unshift @{$stack->{stackdata}}, $res;
	return @_;
}

sub cmd_root {
	my $num = shift;
	if ( not defined $num ) {
		$num = 2;
	}

	my @vals = splice( @{$stack->{stackdata}}, 0, $num );
	my $res = shift(@vals);
	foreach my $v (@vals) {
		$res **= (1.0/$v);
	}
	unshift @{$stack->{stackdata}}, $res;
	return @_;
}

sub cmd_madd {
	my $num = shift;
	my $name = shift;
	my $s = $stack;

	if ( not defined $num ) {
		$num = 1;
	}

	if ( defined $name ) {
		$s = $stacks{$name};
	}
	if ( not defined $s ) {
		return undef;
	}
	
	my $amount = shift( @{$stack->{stackdata}} );
	my @vals = ();
	foreach my $val ( splice( @{$s->{stackdata}}, 0, $num ) ) {
		push @vals, $val + $amount;
	}
	unshift @{$s->{stackdata}}, @vals;
	return @_;
}

sub cmd_msub {
	my $num = shift;
	my $name = shift;
	my $s = $stack;

	if ( not defined $num ) {
		$num = 1;
	}

	if ( defined $name ) {
		$s = $stacks{$name};
	}
	if ( not defined $s ) {
		return undef;
	}
	
	my $amount = shift( @{$stack->{stackdata}} );
	my @vals = ();
	foreach my $val ( splice( @{$s->{stackdata}}, 0, $num ) ) {
		push @vals, $val - $amount;
	}
	unshift @{$s->{stackdata}}, @vals;
	return @_;
}

sub cmd_mmul {
	my $num = shift;
	my $name = shift;
	my $s = $stack;

	if ( not defined $num ) {
		$num = 1;
	}

	if ( defined $name ) {
		$s = $stacks{$name};
	}
	if ( not defined $s ) {
		return undef;
	}
	
	my $amount = shift( @{$stack->{stackdata}} );
	my @vals = ();
	foreach my $val ( splice( @{$s->{stackdata}}, 0, $num ) ) {
		push @vals, $val * $amount;
	}
	unshift @{$s->{stackdata}}, @vals;
	return @_;
}

sub cmd_mdiv {
	my $num = shift;
	my $name = shift;
	my $s = $stack;

	if ( not defined $num ) {
		$num = 1;
	}

	if ( defined $name ) {
		$s = $stacks{$name};
	}
	if ( not defined $s ) {
		return undef;
	}
	
	my $amount = shift( @{$stack->{stackdata}} );
	my @vals = ();
	foreach my $val ( splice( @{$s->{stackdata}}, 0, $num ) ) {
		push @vals, $val / $amount;
	}
	unshift @{$s->{stackdata}}, @vals;
	return @_;
}

sub cmd_mpow {
	my $num = shift;
	my $name = shift;
	my $s = $stack;

	if ( not defined $num ) {
		$num = 1;
	}

	if ( defined $name ) {
		$s = $stacks{$name};
	}
	if ( not defined $s ) {
		return undef;
	}
	
	my $amount = shift( @{$stack->{stackdata}} );
	my @vals = ();
	foreach my $val ( splice( @{$s->{stackdata}}, 0, $num ) ) {
		push @vals, $val ** $amount;
	}
	unshift @{$s->{stackdata}}, @vals;
	return @_;
}

sub cmd_mroot {
	my $num = shift;
	my $name = shift;
	my $s = $stack;

	if ( not defined $num ) {
		$num = 1;
	}

	if ( defined $name ) {
		$s = $stacks{$name};
	}
	if ( not defined $s ) {
		return undef;
	}
	
	my $amount = shift( @{$stack->{stackdata}} );
	my @vals = ();
	foreach my $val ( splice( @{$s->{stackdata}}, 0, $num ) ) {
		push @vals, $val ** (1.0/$amount);
	}
	unshift @{$s->{stackdata}}, @vals;
	return @_;
}

sub cmd_euler {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, exp($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_exp {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, 10**$val;
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_ln {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, log($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_log {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, log($val)/log(10);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_mlog {
	my $num = shift;
	my $name = shift;
	my $s = $stack;

	if ( not defined $num ) {
		$num = 1;
	}

	if ( defined $name ) {
		$s = $stacks{$name};
	}
	if ( not defined $s ) {
		return undef;
	}
	
	my $amount = shift( @{$stack->{stackdata}} );
	my @vals = ();
	foreach my $val ( splice( @{$s->{stackdata}}, 0, $num ) ) {
		push @vals, log($val)/log($amount);
	}
	unshift @{$s->{stackdata}}, @vals;
	return @_;
}

sub cmd_deg {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, (180.0*$val)/Math::Trig::pi;
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_rad {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, (Math::Trig::pi*$val)/180.0;
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_pi {
	my $n = shift;
	my $d = shift;

	if ( not defined $n ) {
		$n = 1;
	}

	if ( not defined $d ) {
		$d = 1;
	}

	unshift @{$stack->{stackdata}}, ($n*Math::Trig::pi)/$d;
	return @_;
}

sub cmd_sin {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, sin($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_asin {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::asin($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_sec {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::sec($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_asec {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::asec($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_cos {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, cos($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_acos {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::acos($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_csc {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::csc($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_acsc {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::acsc($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_tan {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::tan($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_atan {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::atan($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_cot {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::cot($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_acot {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::acot($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_sinh {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::sinh($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_asinh {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::asinh($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_sech {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::sech($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_asech {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::asech($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_cosh {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::cosh($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_acosh {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::acosh($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_csch {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::csch($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_acsch {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::acsch($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_tanh {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::tanh($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_atanh {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::atanh($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_coth {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::coth($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub cmd_acoth {
	my $num = shift;
	if ( not defined $num ) {
		$num = 1;
	}

	my @vals = ();
	foreach my $val (splice( @{$stack->{stackdata}}, 0, $num )) {
		push @vals, Math::Trig::acoth($val);
	}
	unshift @{$stack->{stackdata}}, @vals;
	return @_;
}

sub engnotation {
	my $val = shift;
	if ( not defined $val ) { $val = 0; }
	if ( $val eq 0 ) { return sprintf("%f", $val); }

	my $exp;
	if ( $val < 0 ) {
		$exp = int(log(-$val)/(3*log(10)));
	} else {
		$exp = int(log($val)/(3*log(10)));
	}
	if ( $exp eq 0 ) { return sprintf("%f", $val); }

	if ( $exp < -8 ) {
		$exp = -8;
	} elsif ( $exp > 8 ) {
		$exp = 8;
	}
	my $sym = $engprefix{$exp};
	my $mag = $siprefix{$sym};
	my $mantissa = $val / $mag;
	return sprintf("%f%s", $mantissa, $sym);
}

sub autocomplete {
	my ($text, $line, $start, $end) = @_;
	
	my @possibilities = ();
	if ( $text eq '' ) {
		push @possibilities, keys %stacks;
		push @possibilities, keys %commands;
	}
	if ( $text =~ /^(\S*)\.(.*)$/ ) {
		my $sn = $1;
		my $partvar = $2;
		my $s = $stack;
		if ( ($sn ne 'this') and ($sn ne '') ) {
			$s = $stacks{$sn};
			if ( not defined $s ) {
				return ();
			}
		}

		# Complete a variable
		foreach my $var ( keys %{$s->{variables}} ) {
			if ( $var =~ /^$partvar/ ) {
				push @possibilities, "$sn.$var";
			}
		}
	} else {
		# Command or stack.var reference
		foreach my $sn ( keys %stacks ) {
			if ( $sn =~ /^$text/ ) {
				push @possibilities, "$sn.";
			}
		}
		foreach my $cn ( keys %commands ) {
			if ( $cn =~ /^$text/ ) {
				push @possibilities, $cn;
			}
		}
	}

	return @possibilities;
}

# Start of program
$rl = new Term::ReadLine 'rpncalc';
$rl->Attribs->{attempted_completion_function} = \&autocomplete;

cmd_reset();

while(1) {
	my $line = $rl->readline("> ");
	if ( not defined $line ) {
		cmd_quit();
	}

	# Strip whitespace
	$line =~ s/^\s*\(.*\)\s*$/$1/g;

	# Extract the arguments given
	my @parts = ();
	my $append = '';
	my $appendstr = '';
	foreach my $rpart (split /\s/, $line) {
		my $part = $rpart;
		if ( $append eq "'" ) {
			if ($rpart =~ /(.*[^\\]?)'$/) {
				$part = $1;
				$append = '';
			}
			$part =~ s/\\[\\']/$1/g;
			$appendstr .= $part;
			if ( not defined $append ) {
				push @parts, $appendstr;
			}
		} elsif ( $append eq '"' ) {
			if ($rpart =~ /(.*[^\\]?)"$/) {
				$part = $1;
				$append = '';
			}
			$part =~ s/\\[\\"]/$1/g;
			$appendstr .= $part;
			if ( not defined $append ) {
				push @parts, $appendstr;
			}
		} elsif ( $append eq "\\" ) {
			if ($rpart =~ /^(['"])(.*)$/ ) {
				$part = $2;
				$append = $1;
			} elsif ( $rpart =~ /^(.*)\\$/ ) {
				$part = $1;
				$append = '\\';
			} else {
				$append = '';
			}

			if ( $append ne '' ) {
				$appendstr .= $part;
			} else {
				push @parts, $part;
			}
		} else {
			if ($rpart =~ /^(['"])(.*)$/ ) {
				$part = $2;
				$append = $1;
			} elsif ( $rpart =~ /^(.*)\\$/ ) {
				$part = $1;
				$append = '\\';
			}

			if ( $append ne '' ) {
				$appendstr = $part;
			} else {
				push @parts, $part;
			}
		}
	}

	while ($#parts ge 0) {
		# Look up the command
		my $cmd = shift(@parts);
		if ( not defined $cmd ) { next; }

		# If command is numeric, push it onto the stack as a number.
		if ( $cmd =~ /^[\-\+]?[0-9]*\.?[0-9]*$/ ) {
			if ( $cmd =~ /^\+(.*)$/ ) {
				$cmd = $1;
			}
			unshift @{$stack->{stackdata}}, $cmd;
		# If command is in scientific notation, evaluate and push onto stack
		} elsif ( $cmd =~ /^([\-\+]?[0-9]*\.?[0-9]*)[eE]([\-\+]?[0-9]+)?$/ ) {
			my $mantissa = $1;
			my $exponent = $2;
			
			if ( $mantissa =~ /^\+(.*)$/ ) {
				$mantissa = $1;
			}
			if ( $exponent =~ /^\+(.*)$/ ) {
				$exponent = $1;
			}

			my $val = $mantissa * ( 10 ** $exponent );
			
			unshift @{$stack->{stackdata}}, $val;
		# If command is in engineering/SI notation, evaluate and push onto stack
		} elsif ( $cmd =~ /^([\-\+]?[0-9]*\.?[0-9]*)(y|z|a|f|p|n|u|m|c|d|da|h|k|M|G|T|P|E|Z|Y)$/ ) {
			my $mantissa = $1;
			my $exponent = $2;
			
			if ( $mantissa =~ /^\+(.*)$/ ) {
				$mantissa = $1;
			}

			my $val = $mantissa * $siprefix{$exponent};
			
			unshift @{$stack->{stackdata}}, $val;
		# If command is in fact a variable reference: .foo, stack.foo, this.foo
		} elsif ( $cmd =~ /^(\S*)\.(\S+)$/ ) {
			my $sn = $1;
			my $vn = $2;
			my $s = $stack;

			if ( ( $sn ne '') and ( $sn ne 'this' ) ) {
				$s = $stacks{$sn};
				if ( not defined $s ) {
					next;
				}
			}
			
			if ( not defined $s->{variables}->{$vn} ) {
				next;
			}
			unshift @{$stack->{stackdata}}, $s->{variables}->{$vn};
		# Okay... command must be a command then...
		} elsif ( defined $commands{$cmd} ) {
			# Execute the command
			@parts = &{$commands{$cmd}->{func}}(@parts);
		# Nope... no idea what this is
		} else {
			print "Command $cmd unknown\n";
		}
	}
	if ( $#{$stack->{stackdata}} ge 0 ) {
		printf "TOS: %s\n", engnotation($stack->{stackdata}->[0]);
	}
}
