From 35e543c1a73501c8fecbf89a647f58dbdd4377bb Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Sun, 17 Feb 2019 11:36:33 +0100 Subject: [PATCH] realease 1.00 - renamed commandline tool to rpnc - added better README - added collector mode - added V command - added R command --- README | 30 ----- README.md | 182 ++++++++++++++++++++++++++++ rpn | 252 --------------------------------------- rpnc | 350 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 532 insertions(+), 282 deletions(-) delete mode 100644 README create mode 100644 README.md delete mode 100755 rpn create mode 100755 rpnc diff --git a/README b/README deleted file mode 100644 index 13e1874..0000000 --- a/README +++ /dev/null @@ -1,30 +0,0 @@ - -Reverse Polish Notation Calculator, version 1.00. -Copyleft (L) 2019 - Thomas von Dein. -Licensed under the terms of the GPL 3.0. - -Commandline: rpn [-d] [] - -If is provided, read numbers from STDIN, -otherwise runs interactively. - -Available commands: -c clear stack -s show the stack -d toggle debugging (current setting: 0) -r reverse the stack (w/ reg if stack==1) -u undo last operation -q finish -? print help - -Available operators: -+ add -- substract -/ divide -* multiply -^ expotentiate -% percent -& bitwise AND -| bitwise OR -x bitwise XOR - diff --git a/README.md b/README.md new file mode 100644 index 0000000..21aa082 --- /dev/null +++ b/README.md @@ -0,0 +1,182 @@ +## Reverse Polish Notation Calculator for the commandline + +This is a small commandline calculator which takes its input in +[https://en.wikipedia.org/wiki/Reverse_Polish_notation](reverse polish notation) +form. + +It has an unlimited stack, supports various stack manipulation +commands, can be used interactively or via a pipe and has a collector +mode. It doesn't have any other dependencies than Perl. + +## Usage + +Calculate the summary resistance of parallel resistors with 220, 330 +and 440 Ohm using the following formula: + + 1 / (1/R1 + 1/R2 + 1/R3) + +Here's the sample session: + + 0 % 1 + stack 1: 1 + + 1 % 1 + stack 2: 1 + stack 1: 1 + + 2 % 220 + stack 3: 1 + stack 2: 1 + stack 1: 220 + + 3 % / + stack 2: 1 + stack 1: 0.00454545454545455 + + => 0.00454545454545455 + + 2 % 1 + stack 3: 1 + stack 2: 0.00454545454545455 + stack 1: 1 + + 3 % 330 + stack 4: 1 + stack 3: 0.00454545454545455 + stack 2: 1 + stack 1: 330 + + 4 % / + stack 3: 1 + stack 2: 0.00454545454545455 + stack 1: 0.00303030303030303 + + => 0.00303030303030303 + + 3 % 1 + stack 4: 1 + stack 3: 0.00454545454545455 + stack 2: 0.00303030303030303 + stack 1: 1 + + 4 % 440 + stack 5: 1 + stack 4: 0.00454545454545455 + stack 3: 0.00303030303030303 + stack 2: 1 + stack 1: 440 + + 5 % / + stack 4: 1 + stack 3: 0.00454545454545455 + stack 2: 0.00303030303030303 + stack 1: 0.00227272727272727 + + => 0.00227272727272727 + + 4 % + + stack 3: 1 + stack 2: 0.00454545454545455 + stack 1: 0.0053030303030303 + + => 0.0053030303030303 + + 3 % + + stack 2: 1 + stack 1: 0.00984848484848485 + + => 0.00984848484848485 + + 2 % / + stack 1: 101.538461538462 + + => 101.538461538462 + +The *%* character denotes the interactive prompt. What we basically entered was: + + 1 1 220 / 1 330 / 1 440 / + + / + +Which translates to: + + 1 ((1 / 220) + (1 / 330) + (1 / 440)) + +So, you're entering the numbers and operators as you would do on +paper. To learn more, refer to the Wikipedia page linked above. + +## Collector mode + +Beside traditional RPN you can also enter a special mode, called +*collector mode* by entering the ( command. The collector +mode has its own stack (a sub stack) which is independed of the +primary stack. Inside this mode you can use all operators, however +they work on *ALL* items on the sub stack. + +So, let's compare. If you had in normal RPN mode the following stack: + + 3 + 5 + 6 + +and then entering the + operator, the calculator would pop +5 and 6 from the stack, add them and push the result 11 back to the +stack. + +However, if you are in collector mode with this stack, then all the +items would be added, the sub stack would be cleared and the result 14 +would be added to the primary stack. + +You will leave the collector mode after an operator has been +executed. But you can also just leave the collector mode with the +command ) leaving the sub stack intact. That is, upon +re-entering collector mode at a later time, you'll find the unaltered +sub stack of before. + +## Undo + +Every operation which modifies the stack can be reversed by entering +the u command. There's only one level of undo and no redo. + +## Using STDIN via a PIPE + +If the commandline includes any operator, commands will be read from +STDIN, the result will be printed to STDOUT wihout any decoration and +the program will exit. Commands can be separated by whitespace or +newline. + +Examples: + + echo "2 2" | rpnc + + (echo 2; echo 2) | rpnc + + +Both commands will print 4 to STDOUT. + +## Complete list of all supported commands: + +* c clear stack +* s show the stack +* d toggle debugging (current setting: 0) +* r reverse the stack +* R rotate the stack +* ( enter collect mode +* ) leave collect mode +* u undo last operation +* q finish (C-d works as well) +* ? print help + +## Supported mathematical operators: + +* + add +* - substract +* / divide +* * multiply +* ^ expotentiate +* % percent +* & bitwise AND +* | bitwise OR +* x bitwise XOR +* V pull root (2nd if stack==1) + +## Copyleft + +Copyleft (L) 2019 - Thomas von Dein. +Licensed under the terms of the GPL 3.0. diff --git a/rpn b/rpn deleted file mode 100755 index 0e2e12e..0000000 --- a/rpn +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/perl - -use Term::ReadLine; -use Data::Dumper; - -use strict; -use warnings; - -my (@stack, @register, @rbackup, @sbackup); -my $prompt = "% "; -my $term = Term::ReadLine->new('rpn calc'); -my $debug = 0; -my $tty = 1; -my $VERSION = '1.00'; -my $op; - -my ($arg1, $arg2) = @ARGV; -if ($arg1 eq '-d') { - $debug = 1; - $op = $arg2; -} -elsif ($arg1 eq '-h') { - help(); - exit; -} -elsif ($arg1) { - $op = $arg1; -} - -if ($op) { - $tty = 0; - while () { - chomp; - push @stack, split /\s\s*/; - } - print calc($op); - exit; -} - -my %commands = ( - q => sub { exit; }, - '?' => sub { help(); }, - s => sub { dumpstack(); }, - c => sub { @stack = @register = (); }, - d => sub { - if ($debug) { - $debug = 0; - } - else { - $debug = 1; - } - }, - u => sub { undo(); dumpstack(); }, - r => sub { - backup(); - if (scalar @stack == 1 && @register) { - @register = @stack; - @stack = @rbackup; # == @register - } - else { - @stack = reverse(@stack); - } - dumpstack(); - } - ); - -my $OUT = $term->OUT || \*STDOUT; -while ( defined ($_ = $term->readline($prompt)) ) { - if (/^-?[\.\d]+?$/) { - # number - pushstack($_); - dumpstack(); - } - else { - if (/^[a-z\?]+?$/) { - cmd($_); - } - elsif (/^[<>x\%\|\&\^+\-\*\/]$/) { - # calc - print calc($_); - } - } -} - -sub cmd { - my $c = shift; - - if (exists $commands{$_}) { - my $sub = $commands{$_}; - &$sub; - } - else { - print "unknown command!\n"; - } -} - -sub pushstack { - my $num = shift; - if ($num) { - if ($num =~ /^\./) { - $num = '0' . $num; - } - push @stack, $num; - } -} - -sub dumpstack { - my $p = 1; - foreach my $n (@register) { - printf "r %04d - %s\n", $p++, $n; - } - foreach my $n (@stack) { - printf "s %04d - %s\n", $p++, $n; - } - print "\n"; -} - -sub calc { - my $op = shift; - my $res; - my $code; - - my @last = getlast(); - - if (@last) { - # map operators - if ($op eq '^') { - $op = '**'; - } - elsif ($op eq 'x') { - $op = '^'; - } - elsif ($op eq '<') { - $op = '<<'; - } - elsif ($op eq '>') { - $op = '>>'; - } - - # direct ops - if ($op eq '%') { - if (scalar @last == 2) { - my ($a, $b) = @last; - $code = "\$res = ($a / 100) * $b"; - } - else { - print "percent only possible with 2 values\n"; - undo(); - return; - } - } - else { - # rpn op - $code = "\$res = " . join(" $op ", @last); - } - - # execute - eval $code; - - if ($@) { - # error, reset stack - die $@; - } - else { - push @register, $res; - if ($debug) { - print "DEBUG: $code\n"; - } - if ($tty) { - return "=> $res\n\n"; - } - else { - return "$res\n"; - } - } - } - else { - return; - } -} - -sub undo { - @stack = @sbackup, @register = @rbackup; -} - -sub backup { - @sbackup = @stack; - @rbackup = @register; -} - - -sub getlast { - my @all = (); - - if (@stack) { - if (scalar @stack == 1 && @register) { - @all = (@register, @stack); - @register = @stack = (); - } - elsif (scalar @stack == 1) { - return (); - } - else { - @all = @stack; - @stack = (); - } - - return @all; - } - else { - if (@register) { - @all = @register; - @register = (); - } - } - - return @all; -} - -sub help { - print qq~ -Reverse Polish Notation Calculator, version $VERSION. -Copyleft (L) 2019 - Thomas von Dein. -Licensed under the terms of the GPL 3.0. - -Commandline: rpn [-d] [] - -If is provided, read numbers from STDIN, -otherwise runs interactively. - -Available commands: -c clear stack -s show the stack -d toggle debugging (current setting: $debug) -r reverse the stack (w/ reg if stack==1) -u undo last operation -q finish -? print help - -Available operators: -+ add -- substract -/ divide -* multiply -^ expotentiate -% percent -& bitwise AND -| bitwise OR -x bitwise XOR - -~; -} diff --git a/rpnc b/rpnc new file mode 100755 index 0000000..9c2e4bf --- /dev/null +++ b/rpnc @@ -0,0 +1,350 @@ +#!/usr/bin/perl + +use Term::ReadLine; +use Data::Dumper; +use Getopt::Long; + +use strict; +use warnings; + +my (@stack, @substack, @backup, @subbackup); +my $term = Term::ReadLine->new('rpn calc'); +my $debug = 0; +my $showstack = 1; +my $tty = 1; +my $VERSION = '1.00'; +my $sub = 0; +my $op; + +my ($o_h, $o_v, $o_s); +Getopt::Long::Configure( qw(no_ignore_case)); +if (! GetOptions ( + "version|v" => \$o_v, + "help|h" => \$o_h, + "debug|d" => \$debug, + "nostack|n" => \$o_s + ) ) { + help(); + exit; +} + +if ($o_v) { + print "$0 version $VERSION\n"; + exit; +} + +if ($o_h) { + help(); + exit; +} + +if ($o_s) { + $showstack = 0; +} + +$op = shift; + +if ($op) { + $tty = 0; + while () { + chomp; + push @stack, split /\s\s*/; + } + print calc($op); + exit; +} + +my %commands = ( + q => sub { exit; }, + '?' => sub { help(); }, + s => sub { dumpstack(); }, + c => sub { clearstack(); }, + d => sub { + if ($debug) { + $debug = 0; + } + else { + $debug = 1; + } + }, + u => sub { undo(); dumpstack(); }, + r => sub { reversestack(); }, + R => sub { rotatestack(); }, + '(' => sub { $sub = 1 }, + ')' => sub { $sub = 0 }, + ); + +my $OUT = $term->OUT || \*STDOUT; +while ( defined ($_ = $term->readline(prompt())) ) { + foreach my $tok (split /\s+/) { + if ($tok =~ /^-?[\.\d]+?$/) { + # number + pushstack($tok); + dumpstack(); + } + else { + if ($tok =~ /^[\(\)csdrRuq\?]+?$/) { + cmd($tok); + } + elsif ($tok =~ /^[V<>x\%\|\&\^+\-\*\/]$/) { + print calc($tok); + } + } + } +} + +sub cmd { + my $c = shift; + + if (exists $commands{$c}) { + my $sub = $commands{$c}; + &$sub; + } + else { + print "unknown command '$c'!\n"; + } +} + +sub clearstack { + if ($sub) { + @substack = (); + } + else { + @stack = (); + } +} + +sub reversestack { + backup(); + if ($sub) { + @substack = reverse @substack; + } + else { + @stack = reverse @stack; + } + dumpstack(); +} + +sub rotatestack { + backup(); + if ($sub) { + my $f = shift @substack; + @substack = (@substack, $f); + } + else { + my $f = shift @stack; + @stack = (@stack, $f); + } + dumpstack(); +} + +sub pushstack { + my $num = shift; + if ($num) { + if ($num =~ /^\./) { + $num = '0' . $num; + } + if ($sub) { + push @substack, $num; + } + else { + push @stack, $num; + } + } +} + +sub dumpstack { + return unless $showstack; + + my $prefix = 'stack'; + my @all; + + if ($sub) { + @all = @substack; + } + else { + @all = @stack; + } + + my $p = scalar @all; + + foreach my $n (@all) { + printf "%s %4d: %s\n", $prefix, $p--, $n; + } + + print "\n"; +} + +sub undo { + if ($sub) { + @substack = @subbackup; + } + else { + @stack = @backup; + } +} + +sub backup { + if ($sub) { + @subbackup = @substack; + } + else { + @backup = @stack; + } +} + + +sub getlast { + my @all = (); + + backup(); + + if ($sub) { + @all = @substack; + @substack = (); + } + else { + if (@stack) { + if (scalar @stack == 1) { + @all = pop @stack; + } + else { + @all = reverse (pop @stack, pop @stack); + } + } + } + + return @all; +} + +sub prompt { + my $count; + my $prompt; + + if ($sub) { + $count = scalar @substack; + $prompt = '%--('; + } + else { + $count = scalar @stack; + $prompt = '%'; + } + + return sprintf "%2d %s ", $count, $prompt; +} + +sub calc { + my $op = shift; + my $res; + my $code; + + my @last = getlast(); + + if (@last) { + # map operators + if ($op eq '^') { + $op = '**'; + } + elsif ($op eq 'x') { + $op = '^'; + } + elsif ($op eq '<') { + $op = '<<'; + } + elsif ($op eq '>') { + $op = '>>'; + } + + # direct and special ops + if ($op eq '%') { + if (scalar @last == 2) { + my ($a, $b) = @last; + $code = "($a / 100) * $b"; + } + else { + print "percent only possible with 2 values\n"; + undo(); + return; + } + } + elsif ($op eq 'V') { + if (scalar @last == 2) { + my ($a, $b) = @last; + $code = "$a ** (1 / $b)"; + } + else { + my $a = pop @last; + $code = "$a ** (1 / 2)"; + } + } + else { + # rpn op + $code = join(" $op ", @last); + } + + # execute + eval "\$res = $code"; + + if ($@) { + # error, reset stack + print "Syntax error: $@, resetting stack\n"; + undo(); + } + else { + push @stack, $res; + $sub = 0; + if ($debug) { + print "DEBUG: $code = $res\n"; + } + if ($tty) { + dumpstack(); + return "=> $res\n\n"; + } + else { + return "$res\n"; + } + } + } + else { + return; + } +} + + +sub help { + print qq~ +Reverse Polish Notation Calculator, version $VERSION. +Copyleft (L) 2019 - Thomas von Dein. +Licensed under the terms of the GPL 3.0. + +Commandline: rpn [-d] [] + +If is provided, read numbers from STDIN, +otherwise runs interactively. + +Available commands: +c clear stack +s show the stack +d toggle debugging (current setting: $debug) +r reverse the stack +R rotate the stack +( enter collect mode +) leave collect mode +u undo last operation +q finish (C-d works as well) +? print help + +Available operators: ++ add +- substract +/ divide +* multiply +^ expotentiate +% percent +& bitwise AND +| bitwise OR +x bitwise XOR +V pull root (2nd if stack==1) + +~; +}