mirror of
https://codeberg.org/scip/rpnc.git
synced 2025-12-18 13:01:08 +01:00
498 lines
10 KiB
Perl
Executable File
498 lines
10 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
|
|
use Term::ReadLine;
|
|
use Data::Dumper;
|
|
use Getopt::Long;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
my (@stack, @substack, @backup, @subbackup, @hist, @register);
|
|
my $term = Term::ReadLine->new('rpn calc');
|
|
my $debug = 0;
|
|
my $showstack = 1;
|
|
my $tty = 1;
|
|
my $VERSION = '1.05';
|
|
my $sub = 0;
|
|
my $maxstack = 10;
|
|
my $maxreg = 5;
|
|
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 (<STDIN>) {
|
|
chomp;
|
|
push @stack, split /\s\s*/;
|
|
}
|
|
print calc($op);
|
|
exit;
|
|
}
|
|
|
|
# management commands, always lower case letters or words
|
|
my %commands = (
|
|
# stack commands
|
|
s => sub { dumpstack(); },
|
|
sa => sub { dumpstack(1); },
|
|
sc => sub { clearstack(); },
|
|
scx => sub { clearstack(1); dumpstack(); },
|
|
sr => sub { reversestack(); },
|
|
srt => sub { rotatestack(); },
|
|
# collector
|
|
'(' => sub { $sub = 1 },
|
|
')' => sub { $sub = 0 },
|
|
# register stuff
|
|
r => sub { last_to_reg(); dumpstack(); },
|
|
rcx => sub { clearreg(1); dumpstack(); },
|
|
rc => sub { clearreg(); },
|
|
# main
|
|
'?' => sub { help(); },
|
|
u => sub { undo(); dumpstack(); },
|
|
h => sub { showhist(); },
|
|
q => sub { exit; },
|
|
# toggles
|
|
td => sub { $debug ^= 1; },
|
|
ts => sub { $showstack ^= 1; },
|
|
);
|
|
|
|
# executed 1:1, or aliased
|
|
my %alias = qw(^ ** x ^ < << > >> + + - - / / * * & & | |);
|
|
|
|
# hand coded functions
|
|
my %func = (
|
|
'%' => sub {
|
|
if (scalar @_ == 2) {
|
|
my ($a, $b) = @_;
|
|
return "($a / 100) * $b";
|
|
}
|
|
else {
|
|
print "percent only possible with 2 values\n";
|
|
undo();
|
|
return 0;
|
|
}
|
|
},
|
|
'%d' => sub {
|
|
# percentual difference
|
|
if (scalar @_ == 2) {
|
|
my ($a, $b) = @_;
|
|
return "(($a - $b) / $b) * 100"
|
|
}
|
|
else {
|
|
print "percent only possible with 2 values\n";
|
|
undo();
|
|
return 0;
|
|
}
|
|
},
|
|
'v' => sub {
|
|
if (scalar @_ == 2) {
|
|
my ($a, $b) = @_;
|
|
return "$a ** (1 / $b)";
|
|
}
|
|
else {
|
|
my $a = pop @_;
|
|
return "$a ** (1 / 2)";
|
|
}
|
|
},
|
|
'pr' => sub {
|
|
# parallel resistance, maybe add ~/.rpncrc support
|
|
# where to add such custom functions...
|
|
return "1 / (" . join(' + ', map { "1 / $_"} @_) . ")";
|
|
},
|
|
'm' => sub {
|
|
# median
|
|
if (scalar @_ >= 2) {
|
|
my $c = $#_;
|
|
if (scalar @_ % 2 == 0) {
|
|
# even
|
|
return "((sort qw(@_))[$c / 2] + (sort qw(@_))[($c / 2) + 1]) / 2";
|
|
}
|
|
else {
|
|
# uneven
|
|
return "(sort qw(@_))[$c / 2]";
|
|
}
|
|
}
|
|
else {
|
|
print "median only possible with 2 or more values\n";
|
|
undo(); return 0;
|
|
}
|
|
},
|
|
'a' => sub {
|
|
# average
|
|
return "(" . join(' + ', @_) . ") / " . scalar @_;
|
|
}
|
|
);
|
|
|
|
# math constants, always upper case letters, usable via eval{}
|
|
use constant PI => 3.141592653589793;
|
|
use constant V2 => 1.414213562373095;
|
|
use constant V3 => 1.732050807568877;
|
|
|
|
my $OUT = $term->OUT || \*STDOUT;
|
|
while ( defined ($_ = $term->readline(prompt())) ) {
|
|
foreach my $tok (split /\s+/) {
|
|
if ($tok =~ /^-?[A-Z\.\d]+?$/) {
|
|
# number
|
|
if ($tok =~ /^R(\d+?)/) {
|
|
my $r = getreg($1);
|
|
if ($r) {
|
|
pushstack($r);
|
|
}
|
|
else {
|
|
print "invalid register index!\n";
|
|
next;
|
|
}
|
|
}
|
|
else {
|
|
pushstack($tok);
|
|
}
|
|
dumpstack();
|
|
}
|
|
else {
|
|
if (exists $commands{$tok}) {
|
|
cmd($tok);
|
|
}
|
|
else {
|
|
print calc($tok);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub cmd {
|
|
my $c = shift;
|
|
|
|
if (exists $commands{$c}) {
|
|
my $sub = $commands{$c};
|
|
&$sub;
|
|
}
|
|
else {
|
|
print "unknown command '$c'!\n";
|
|
}
|
|
}
|
|
|
|
sub showhist {
|
|
foreach my $entry (@hist) {
|
|
printf "History: %10s = %s\n", $entry->[0], $entry->[1];
|
|
}
|
|
}
|
|
|
|
sub clearstack {
|
|
my $one = shift;
|
|
|
|
backup();
|
|
|
|
if ($sub) {
|
|
if ($one) {
|
|
pop @substack;
|
|
}
|
|
else {
|
|
@substack = ();
|
|
}
|
|
}
|
|
else {
|
|
if ($one) {
|
|
pop @stack;
|
|
}
|
|
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 $max = shift;
|
|
my $x = ' ';
|
|
my $prefix = 'stack';
|
|
my @all;
|
|
|
|
if ($sub) {
|
|
@all = @substack;
|
|
$prefix = 'collectorstack';
|
|
}
|
|
else {
|
|
@all = @stack;
|
|
}
|
|
|
|
my $abs = scalar @all;
|
|
if (! $max && $abs > $maxstack) {
|
|
my $min = $max - ($max * 2);
|
|
@all = @all[$min .. -1];
|
|
printf "%s [..]\n", $prefix;
|
|
}
|
|
|
|
if (@register) {
|
|
my $p = scalar @register;
|
|
foreach my $n (@register) {
|
|
printf "register R%d: %s\n", $p--, $n;
|
|
}
|
|
}
|
|
print "\n";
|
|
|
|
my $p = scalar @all;
|
|
foreach my $n (@all) {
|
|
$x = 'X' if($p == 1);
|
|
printf "%s %s %4d: %s\n", $prefix, $x, $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 getreg {
|
|
my $n = shift;
|
|
print scalar @register;
|
|
if (scalar @register <= $n) {
|
|
return $register[$n-1];
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
sub last_to_reg {
|
|
my $n;
|
|
if ($sub) {
|
|
if (@substack) {
|
|
$n = $substack[-1];
|
|
}
|
|
}
|
|
else {
|
|
if (@stack) {
|
|
$n = $stack[-1];
|
|
}
|
|
}
|
|
|
|
if ($n) {
|
|
if (scalar @register == $maxreg) {
|
|
shift @register;
|
|
}
|
|
push @register, $n;
|
|
}
|
|
}
|
|
|
|
sub clearreg {
|
|
my $one = shift;
|
|
|
|
if ($one) {
|
|
pop @register;
|
|
}
|
|
else {
|
|
@register = ();
|
|
}
|
|
}
|
|
|
|
sub prompt {
|
|
my $count;
|
|
my $prompt;
|
|
|
|
if ($sub) {
|
|
$count = scalar @substack;
|
|
$prompt = '%--(';
|
|
}
|
|
else {
|
|
$count = scalar @stack;
|
|
$prompt = '%';
|
|
}
|
|
|
|
return sprintf "%3d %s ", $count, $prompt;
|
|
}
|
|
|
|
sub calc {
|
|
my $op = shift;
|
|
my $res;
|
|
my $code;
|
|
|
|
my @last = getlast();
|
|
|
|
if (@last) {
|
|
if (exists $alias{$op}) {
|
|
$op = $alias{$op};
|
|
$code = join(" $op ", @last);
|
|
}
|
|
elsif (exists $func{$op}) {
|
|
my $sub = $func{$op};
|
|
$code = &$sub(@last);
|
|
return unless $code;
|
|
}
|
|
else {
|
|
print "syntax error or unknown command!\n";
|
|
undo();
|
|
return;
|
|
}
|
|
|
|
# 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();
|
|
push @hist, [$res, $code];
|
|
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] [<operator>]
|
|
|
|
If <operator> is provided, read numbers from STDIN,
|
|
otherwise runs interactively.
|
|
|
|
Configure: Available math operators:
|
|
td toggle debugging (-d) ( enter collect mode
|
|
ts toggle display of stack (-n) ) leave collect mode
|
|
+ add
|
|
Stack Management: - substract
|
|
s show the stack / divide
|
|
sa show the whole stack * multiply
|
|
scx clear X (last stack element) ^ expotentiate
|
|
sc clear stack % percent
|
|
sr reverse the stack %d percentual difference
|
|
srt rotate the stack & bitwise AND
|
|
| bitwise OR
|
|
Register Management: x bitwise XOR
|
|
r put X to register v pull root (2nd if stack==1)
|
|
rcx clear X (last register element) m median
|
|
rc clear register a average
|
|
|
|
Various Commands Constants: PI V2 V3
|
|
u undo last operation
|
|
h show history of past operations Using register:
|
|
q finish (C-d works as well) enter R + index, e.g. R1
|
|
? print help
|
|
~;
|
|
}
|