# PowerShell RPN Calculator

Monday, 12 October 2009

Adam Barr blogged bits and pieces of a PowerShell RPN calculator a few years ago: first the basics, and then some tweaks to clean it up. An RPN calculator, if you haven’t played with one before, flips the way you enter data. Rather than type “2 + 2”, you type “2 2 +”. RPN-style calculation supposedly has lots of great benefits. While I understand it and can do it, I wouldn’t say I “get it.”

Anyways.

One of the things he runs into with the last version is the PowerShell’s (version one) inability to dynamically invoke static methods:

… you should be able to write:

$add = [Decimal]::Add

$add.Invoke(2,3)

but it doesn't work. That's not exactly the same as getting the value of a static property, but maybe there's a related issue, or maybe I just can't figure out how to do it right.

Well, in version two it *does* work, and that makes the RPN calculator a whole lot cleaner.

Another interesting aspect about the latest implementation is that it has a lot of very similar code segments. It has tables for operations on the Double class that take one argument, operations on the Double class that take two arguments, operations on the **M**ath class that take one argument, and operations on the Math class that take two arguments. Afterward, there are four nearly identical blocks of code that perform the operation and store the result.

Note: This of course isn’t a slag on the implementation, this is just continuing the tinkering on an interesting concept.

We can simplify this in two ways:

- Don’t create hard-coded lists of operators. Instead, we’ll look at all methods in the Math and Double class to see if one matches. This approach gives us 30-something operators for free, and perhaps more in future versions of the .NET Framework.
- Don’t create hard-coded lists of
*arity*: the number of arguments consumed by an operator. Instead, we’ll look at the method overloads that match the operator name, and see how many arguments they consume.

Now, there are some subtleties to both points:

- The method names in the Decimal and Math classes get kind of long. You don’t want to have to write “2 3 Multiply” in an RPN calculator. To work around that, we’ll define a hashtable of shortcuts that simply map operators to their names.
- Some operators have multiple overloads. For example, the one-argument
*Round*method rounds a number to zero decimal points. The two-argument*Round*method rounds it to the specified number of decimal points.

By leveraging PowerShell’s built-in support for introspection and dynamic method invocation, we now have a script that is both much shorter, and much more powerful.

001

002

003

004

005

006

007

008

009

010

011

012

013

014

015

016

017

018

019

020

021

022

023

024

025

026

027

028

029

030

031

032

033

034

035

036

037

038

039

040

041

042

043

044

045

046

047

048

049

050

051

052

053

054

055

056

057

058

059

060

061

062

063

064

065

066

067

068

069

070

071

072

073

074

075

076

077

078

079

080

081

082

083 ##############################################################################

##

## rpn.ps1

##

##############################################################################<#

.SYNOPSIS

Evaluates a statement as an RPN calculator. Supports all operations from

System.Math and System.Decimal..EXAMPLE

rpn 2 2 +.EXAMPLE

rpn 2 3 + 1.3 / 2 Round2 Negate#>

$s = new-object System.Collections.Stack

$n = 0d$shortcuts = @{

"+" = "Add"; "-" = "Subtract"; "/" = "Divide"; "*" = "Multiply";

"%" = "Remainder"; "^" = "Pow"; "||" = "Abs"

}:ARGLOOP foreach ($a in $args) {

if($shortcuts[$a]) { $a = $shortcuts[$a] }## First, see if it's a number. If so, push it.

try { $s.Push( [Decimal] $a ); continue } catch {}## It's an operation. Extract the operation name

## (such as Floor, Round, etc.) It may also represent a

## specific operation (such as Round2 - Round to specified precision).

$argCountList = $a -replace "(\D+)(\d*)",'$2'

$op = $a.Substring(0, $a.Length - $argCountList.Length)## We support any static operations from the Decimal or Math classes

foreach($type in [Decimal],[Math])

{

if($definition = $type::$op)

{

## If they haven't specifically given the number of arguments,

## see how many this method supports. We go through each overload

## definition, and see how many commas it has.

if(-not $argCountList)

{

$argCountList = $definition.OverloadDefinitions |

Foreach-Object { ($_ -split ", ").Count } |

Sort-Object -Unique

}## Now, for each overload, see if we can call it.

foreach($argCount in $argCountList)

{

try

{

$methodArguments = $s.ToArray()[($argCount-1)..0]

$result = $type::$op.Invoke($methodArguments)## If we were able to call the method, pop all of its

## arguments off of the stack.

$null = 1..$argCount | % { $s.Pop() }## Then push the result

$s.Push($result)

continue ARGLOOP

}

catch

{

## If we catch an error, try with the next number of

## arguments

}

}

}

}

}$s

No. 1 — December 7th, 2015 at 3:53 am

An RPN stack can be useful, only if you have a running stack (and probably when you have stack manipulation functions like the most fine HP RPN calculators.) This script works very well, but instead of ignoring stack entry errors, it should simply choke and (simply) explain the error; as the HP calculators do.