X-Git-Url: http://git.shiar.net/descalc.git/blobdiff_plain/ce0d29d8a852b8bf7dcad717390a4ad8efb59263..b2aba05a22d1f4036008c2d12874ddc9eb98edc0:/dct.pl diff --git a/dct.pl b/dct.pl index f3cdcc9..a671e52 100755 --- a/dct.pl +++ b/dct.pl @@ -5,34 +5,7 @@ # reverse polish notition calculator using curses # by Shiar -# 1.01 06-18 - start (curses, some basic commands) -# 1.02 06-20 - function keys select command/submenu from (sub)menu -# - backspace to undo last digit -# 1.03 06-25 - values displayable in arbitrary base -# - can enter fractions (.) and negative values (_) -# 1.04 08-04 14:45 - error dialog (don't mess up screen) -# - manual command input using capital letters -# - ^L redraws screen -# pre 09-09 22:00 - overhaul in stack handling -# 1.05 09-10 19:45 - hp48-like drop (backspace but not editing value) -# - error on insufficient arguments for command -# - command backspacing -# - some unit conversion (mostly lengths) from menu -# - q for sq(rt) (formerly quit, now only ^D/quit) -# 1.06 09-15 23:10 - menu contents in module -# - new commands: a?(sin|cos|tan)h, inv, !, rand -# - x and v shortkeys -# 1.07 09-24 23:50 - numeric modifiers hardcoded instead of in action hash -# - action undo: last stack alteration can be undone -# - enter on no value repeats last val on stack -# - new commands: sr/sr, shortkeys ( ) -# 1.08 09-26 22:10 - additional digits were not correctly applied to negative values -# - negative numbers displayed correctly in different bases -# - second undo redoes -# - fixed % -# - stack command (cursor up) cycles through values in stack -# 09-27 00:57 - all key aliases moved to module DCT::Bindings -our $VERSION = 1.008; +our $VERSION = 1.009; use strict; use warnings; @@ -40,161 +13,50 @@ use utf8; use Term::ReadKey; use Curses; -use DCT::Menu 1.006; -use DCT::Bindings 1.008; -initscr; -ReadMode 3; # cbreak mode -END { - ReadMode 0; - endwin; -} # restore terminal on quit - -my %val = qw(i 0 frac 0); # i, frac -my @stack; -my %var; +use vars qw(@stack %val %var %set %alias %action %hook); -my %set = ( +%set = ( base => 10, # decimal; set using commands bin/oct/dec/hex/base numb => 0, # fixed scientific engineering card => 1, # degrees radians grades coord => 0, # cartesian polar spherical complex => 0, # real complex - height => $LINES<3 ? 4 : $LINES-3, # stack depth (lines of stack plus one) - width => $COLS || 42, # limit value precision, stetch menu - menushow => 12, # menu items to show simultaneously + height => 4, # stack depth (lines of stack plus one) + width => 42, # limit value precision, stetch menu ); # %set -#%alias = (' '=>'enter', "\004"=>'quit', 'q'=>'quit') unless %alias; # rudimentary defaults +%alias = (' '=>'enter', "\004"=>'quit', 'q'=>'quit'); # rudimentary default key bindings -my @menu = @{$menus[0]}; -my $menumin = 0; +%action = ( + "chs" => [1, sub { -$_[0] }], # negative -my %action = ( - 'more' => [-1, sub { - $menumin += $set{menushow}; - $menumin = 0 if $menumin>=$#menu; - showmenu(); - }], # tab - 'chs' => [0, sub {$stack[0] = -$stack[0]}], # negative + "drop" => [1, sub { defined $val{i} ? '' : () }], # drop + "back" => [1, sub { () }], # drop essentially + "clear" => [0, sub { @stack = (); undef %val; () }], # clear all #todo: if (val{i}) delete char after cursor - 'drop' => [0, sub {shift @stack}], # backspace - 'clear'=> [0, sub {@stack = (); %val = (i=>undef, frac=>0) }], # clear all #todo: if (val{i}) delete char after cursor - - 'enter'=> [0, sub { - unshift @stack, defined $val{i} ? $val{i} : $stack[0]; - %val = (i=>undef, frac=>0); + "enter" => [0, sub { + local $_ = defined $val{i} ? $val{i} : $stack[0]; + undef %val; + return defined $_ ? $_ : (); }], # duplication -# 'undo' => [0, sub {@stack = @{ $var{undo} }}], # undo - 'undo' => [0, sub {($var{undo}, @stack) = ([@stack], @{ $var{undo} }) }], # undo - 'swap' => [1, sub {@stack[0, 1] = @stack[1, 0]}], # swap x<->y - 'stack'=> [0, sub { + "swap" => [2, sub { reverse @_ }], # swap x<->y + "undo" => [-1, sub { + ($var{undo}, @stack) = ([@stack], @{ $var{undo} }); + }], # undo/redo + "stack" => [-1, sub { $var{stackpos} = 0 unless $var{stackpos}; # initialize $var{stackpos} %= @stack; # cycle $val{i} = $stack[$var{stackpos}++]; }], # stack - 'version' => [0, sub{error("Desktop Calculator Thingy $VERSION by Shiar")}], # version - - '=' => [1, sub {$var{a} = $stack[0]}], # copy - '?' => [1, sub {$var{a} = shift @stack}], # assign - - '+' => [2, sub {$stack[1] += shift @stack}], # addition - '-' => [2, sub {$stack[1] -= shift @stack}], # substraction - '*' => [2, sub {$stack[1] *= shift @stack}], # multiplication - '/' => [2, sub {$stack[1] /= shift @stack}], # division - 'mod' => [2, sub {$stack[1] %= shift @stack}], # modulo - - 'inv' => [1, sub {$stack[0] = 1 / $stack[0]}], # 1/x - 'sqrt' => [1, sub {$stack[0] = sqrt $stack[0]}], # square root - 'sq' => [1, sub {$stack[0] *= $stack[0]}], # squared - '^' => [2, sub {$stack[1] **= shift @stack}], # exponentiation - 'xroot'=> [2, sub {$stack[1] **= 1 / shift @stack}], # x-root of y - - 'log' => [1, sub {$stack[0] = log($stack[0]) / log(10)}], # logarithm - 'alog' => [1, sub {$stack[0] = 10 ** $stack[0]}], # 10^x - 'ln' => [1, sub {$stack[0] = log $stack[0]}], # natural logaritm - 'lnp1' => [1, sub {$stack[0] = log($stack[0]+1)}], # ln(x+1) - 'exp' => [1, sub {$stack[0] = exp($stack[0])}], # e^x - 'expm' => [1, sub {$stack[0] = exp($stack[0])-1}], # exp(x)-1 - - 'sin' => [1, sub {$stack[0] = sin $stack[0]}], # sine - 'asin' => [1, sub {$stack[0] = atan2($stack[0], sqrt(1 - $stack[0]*$stack[0]))}], # inverse sine - 'cos' => [1, sub {$stack[0] = cos $stack[0]}], # cosine - 'acos' => [1, sub {$stack[0] = atan2(sqrt(1 - $stack[0]*$stack[0]), $stack[0])}], # inverse cosine - 'tan' => [1, sub {$stack[0] = sin($stack[0]) / cos($stack[0])}], # tangent -# 'atan' => [1, sub {}], # arctangent - - 'sinh' => [1, sub {$stack[0] = ( exp($stack[0]) - exp(-$stack[0]) )/2}], # hyperbolic sine - 'cosh' => [1, sub {$stack[0] = ( exp($stack[0]) + exp(-$stack[0]) )/2}], # hyperbolic cosine - 'tanh' => [1, sub {$stack[0] = ( exp($stack[0]) - exp(-$stack[0]) )/( exp($stack[0]) + exp(-$stack[0]) )}], # hyperbolic tangent (sinh/cosh) - 'asinh'=> [1, sub {$stack[0] = log( sqrt($stack[0]**2+1)+$stack[0] )}], # inverse hyperbolic sine - 'acosh'=> [1, sub {$stack[0] = log( sqrt($stack[0]**2-1)+$stack[0] )}], # inverse hyperbolic cosine - 'atanh'=> [1, sub {$stack[0] = log( (1+$stack[0]) / (1-$stack[0]) )/2}], # inverse hyperbolic tangent - - '%' => [2, sub {$stack[0] = shift(@stack)/$stack[0]}], # percentage - '%ch' => [2, sub {$val{i} = 100*(shift(@stack)-$val{i})/$val{i}}], # percentage change - '%t' => [2, sub {$val{i} = 100*$val{i}/shift(@stack)}], # percentage total - - 'and' => [2, sub {$stack[1] &= shift @stack}], # bitwise and - 'or' => [2, sub {$stack[1] |= shift @stack}], # bitwise or - 'xor' => [2, sub {$stack[1] ^= shift @stack}], # bitwise xor - 'not' => [2, sub {$stack[0] = ~$stack[0]}], # bitwise not - 'sl' => [1, sub {$stack[0] *= 2}], # shift left - 'sr' => [1, sub {$stack[0] /= 2}], # shift right - - 'abs' => [1, sub {$stack[0] = abs $stack[0]}], # absolute #todo - 'sign' => [1, sub {$stack[0] = $stack[0] <=> 0}], # sign - 'ip' => [1, sub {$stack[0] = int $stack[0]}], # integer part - 'fp' => [1, sub {$stack[0] -= int $stack[0]}], # fractional part - - 'rnd' => [1, sub {local $_ = 10**shift @stack; $val{i} = int(($val{i}+.5)*$_)/$_}], # round - 'trnc' => [1, sub {local $_ = 10**shift @stack; $val{i} = int($val{i}*$_)/$_}], # truncate - 'floor'=> [1, sub {$stack[0] = int $stack[0]}], # floor - 'ceil' => [1, sub {$stack[0] = int $stack[0]+.9999}], # ceil - - 'min' => [2, sub {local $_ = shift @stack; $stack[0] = $_ if $_<$stack[0] }], # minimum - 'max' => [2, sub {local $_ = shift @stack; $stack[0] = $_ if $_>$stack[0] }], # maximum - - 'dec' => [0, sub {$set{base} = 10}], # decimal - 'bin' => [0, sub {$set{base} = 2}], # binary - 'oct' => [0, sub {$set{base} = 8}], # octal - 'hex' => [0, sub {$set{base} = 16}], # hexadecimal - 'base' => [1, sub {$set{base} = shift @stack}], # alphanumerical - - '!' => [1, sub {local $_ = $stack[0]; $stack[0] *= $_ while --$_>1}], # factor - 'rand' => [0, sub {unshift @stack, rand}], # random value <1 -); # %action + "version" => [-1, sub { error("Desktop Calculator Thingy $VERSION by Shiar"); () }], # version -my %unit; -{ -my $i = 0; -$unit{$_->[0]} = { name=>$_->[0], type=>$i, val=>$_->[1] } for map {$i++; @$_} ( - [ - ['m', 1], - ['cm', .01], - ['mm', .001], - ['km', 1000], - ['ft', .3048], - ['in', .0254], - ['yd', .9144], - ['mile', 1609.344], - ['nmile', 1852], - ['lyr', 9.46052840488e+15], - ['mil', 2.54e-5], - # _m _cm _mm _yd _ft _in _Mpc _pc _lyr _au _km _mi - # _nmi _miUS _chain _rd _fath _ftUS _Mil _μ _Å _fermi - ], # lengths - [ - ['m^3', 1], - ['cm^3', 1e-6], - ['ft^3', .028316846592], - ['in^3', 1.6387064e-5], - ], # volume -); -} # create unit table + "sto" => [1, sub { $var{a} = $_[0] }], # copy + '?' => [1, sub { $var{a} = $_[0] }], # assign +); # %action sub error($) { @@ -205,7 +67,7 @@ sub error($) { refresh; ReadKey; # wait for confirm - 1 while defined (ReadKey -1); # clear key buffer + 1 while defined ReadKey(-1); # clear key buffer } # error sub showval($$); @@ -226,6 +88,7 @@ sub showval($$) { $int /= $base; $exp++; } # exponent part + while ($int>=1) { my $char = $int%$base; $txt = ($char<10 ? $char : chr($char+55)).$txt; @@ -251,132 +114,156 @@ sub showstack() { addstr($set{height}-$_, 1, "$_: ".showval($stack[$_], $set{base})); clrtoeol; } # show stack - clrtoeol($set{height}-$#stack-1, 1); + clrtoeol($set{height}-@stack, 1); } # showstack -sub showmenu() { - clrtoeol($set{height}+2, 1); - my $nr = 0; - for (grep exists $menu[$_], $menumin+1..$menumin+$set{menushow}) { - my $sub = (my $s = $menu[$_]) =~ s/>\d+$//; - addstr($set{height}+2, $set{width}/$set{menushow}*($nr++), $_); - attron(A_REVERSE); - addstr($s); - attroff(A_REVERSE); - addch('>') if $sub; - } # display menu txts -} # showmenu + +my @modules; +eval 'require $_' ? push @modules, $_ +: print STDERR "error loading $_\n".(join "", map "\t$_\n", split /\n/, $@) + for glob "*.pm"; + +initscr; +ReadMode 3; # cbreak mode +END { + ReadMode 0; + endwin; +} # restore terminal on quit + +$set{height} = $LINES-2 if $LINES>=3; +$set{width} = $COLS if $COLS; +$_->() for @{ $hook{init} }; DRAW: clear; -showmenu(); +$_->() for @{ $hook{refresh} }; showstack(); addstr($set{height}+1, 0, "> "); # prompt +LOOP: while (1) { addstr($set{height}+1, 2, showval($val{i}, $set{base})); - addstr('_'.$val{unit}{name}) if exists $val{unit}; - addstr($val{bla}) if exists $val{bla}; + for my $cmd (@{ $hook{showentry} }) { + addstr($_) if $_ = $cmd->(); + } # showentry functions + addstr($val{alpha}) if exists $val{alpha}; clrtoeol; refresh; - $_ = ReadKey; - if ($_ eq chr 27) { - while (defined (my $key = ReadKey -1)) { - $_ .= $key; - } # read additional keys + my $key = ReadKey; + if ($key eq chr 27) { + $key .= $_ while defined ($_ = ReadKey(-1)); # read additional keys } # escape sequence + $_ = $alias{$key} || $key; #if exists $alias{$key}; # command shortkeys + $_ = delete $val{alpha} if $_ eq "enter" and exists $val{alpha}; # use manual command - exists $alias{$_} and $_ = $alias{$_}; # command shortkeys - if (exists $falias{$_}) { - unless ($_ = $menu[$falias{$_}]) { - error("* no such menu entry *"); - goto DRAW; - } - } # function key - - $_ = delete $val{bla} if exists $val{bla} and $_ eq 'enter'; + for my $cmd (@{ $hook{precmd} }) { + next LOOP if $cmd->(); + } # precmd functions - if ($_ eq 'quit') { - last; - } # quit - elsif ($_ eq 'refresh') { - goto DRAW; - } # refresh + last if $_ eq 'quit'; + goto DRAW if $_ eq 'refresh'; - elsif (exists $val{bla} or /^[A-Z]$/) { + if (exists $val{alpha} or /^\033?[A-Z]$/) { if (defined $val{i}) { unshift @stack, $val{i}; - %val = (i=>undef, frac=>0); + undef %val; showstack(); } # enter present value - if ($_ eq "drop") { - $val{bla} = substr $val{bla}, 0, -1 or delete $val{bla}; + + if ($_ eq "back") { + $val{alpha} = substr $val{alpha}, 0, -1 or delete $val{alpha}; } # backspace + elsif ($_ eq "drop") { + delete $val{alpha}; + } # drop else { - $val{bla} .= lc $_; + $val{alpha} .= $key =~ /^\033(.)/ ? uc $1 : lc $key; } # add character - } # manual command + } # manual command entry elsif (/^\d$/) { $val{i} = 0 unless defined $val{i}; - $_ = -$_ if $val{i}<0; - $val{i} = ($val{frac} *= 10) ? $val{i}+$_/$val{frac} : $val{i}*10+$_; - } + $_ = -$_ if $val{i}<0; # substract from negative value + $val{i} = ($val{frac} and $val{frac} *= 10) ? $val{i}+$_/$val{frac} + : $val{i}*10+$_; + } # digit elsif ($_ eq '.') { $val{i} = 0 unless defined $val{i}; $val{frac} = 1; } # decimal point - elsif ($_ eq 'eex') { + elsif ($_ eq "eex") { $val{i} = 1 unless defined $val{i}; #todo } # exponent - elsif ($_ eq 'chs' and defined $val{i}) { + elsif ($_ eq "chs" and defined $val{i}) { $val{i} = -$val{i}; } # change sign - elsif ($_ eq 'drop' and defined $val{i}) { + elsif ($_ eq "back" and defined $val{i}) { $val{i} = ($val{frac} = int $val{frac}/10) ? int($val{i}*$val{frac})/$val{frac} : int $val{i}/10 } # backspace - elsif (exists $action{$_} or /^\d$/) { + elsif (exists $action{$_}) { my ($type, $cmd) = @{ $action{$_} }; - if ($type>0 and defined $val{i}) { - unshift @stack, $val{i}; - %val = (i=>undef, frac=>0); - } # auto enter + unshift @stack, $action{enter}[1]->() + if $type>0 and defined $val{i}; # auto enter if ($type>0 and $type>@stack) { - error("* insufficient stack arguments for operation *"); + error("insufficient stack arguments for operation"); goto DRAW; } # insufficient arguments - $var{undo} = [@stack] if $type>=0 and $_ ne 'undo'; - $cmd->(); - showstack() if $type>=0; - } # some operation - elsif (/>(\d+)$/) { - @menu = @{ $menus[$1] }; - $menumin = 0; - showmenu(); - } # submenu - - elsif ($_ =~ /^_/) {{ - $_ = $unit{substr $_, 1} or next; - if (exists $val{unit} and $val{unit}{type}==$_->{type}) { - unshift @stack, $val{i} if defined $val{i}; - $stack[0] *= delete($val{unit})->{val} / $_->{val}; + if ($type>=0) { + $var{undo} = [@stack]; # if $_ ne 'undo'; + unshift @stack, $cmd->(splice @stack, 0, $type); showstack(); - %val = (i=>undef, frac=>0); - } # convert + } # stack-modifying operation else { - $val{unit} = $_; - } # set source unit - }} # conversion + $cmd->(); + } # harmless + } # some operation else { - error("* unrecognised command: ".join(' ', map ord, split //, $_)." *"); + error("unrecognised command: ".join(' ', map ord, split //, $_)); goto DRAW; # screen messed up } # error } # input loop +=cut +VERSION HISTORY +1.01 06-18 - start (curses, some basic commands) +1.02 06-20 - function keys select command/submenu from (sub)menu + - backspace to undo last digit +1.03 06-25 - values displayable in arbitrary base + - can enter fractions (.) and negative values (_) +1.04 08-04 14:45 - error dialog (don't mess up screen) + - manual command input using capital letters + - ^L redraws screen + pre 09-09 22:00 - overhaul in stack handling +1.05 09-10 19:45 - hp48-like drop (backspace but not editing value) + - error on insufficient arguments for command + - command backspacing + - some unit conversion (mostly lengths) from menu + - q for sq(rt) (formerly quit, now only ^D/quit) +1.06 09-15 23:10 - menu contents in module + - new commands: a?(sin|cos|tan)h, inv, !, rand + - x and v shortkeys +1.07 09-24 23:50 - numeric modifiers hardcoded instead of in action hash + - action undo: last stack alteration can be undone + - enter on no value repeats last val on stack + - new commands: sr/sr, shortkeys ( ) +1.08 09-26 22:10 - additional digits were not correctly applied to negative values + - negative numbers displayed correctly in different bases + - second undo redoes + - fixed % + - stack command (cursor up) cycles through values in stack +1.09 09-27 00:57 - all key aliases moved to module DCT::Bindings + 09-29 12:15 - number of menu items depends on screen width + 10-11 21:30 - hooks allowing for extra code at reload, showentry, and precmd + 21:50 - all menu related functions moved to menu.pm + 22:05 - unit conversion out of main program (entirely into unitconv.pm) + 10-12 01:50 - backspace becomes "back" (soft drop, like old "drop") + - normal drop command (alt+bs) removes input/stack value at once + 02:13 - $val{frac} default undefined instead of 0 +=cut