release 1.10.6
[descalc.git] / dct.pl
diff --git a/dct.pl b/dct.pl
index a671e52787a1dc635ad3d075c61f00535c119f4f..b11ca5724c8adba43ae9c850f1c6072fffddd279 100755 (executable)
--- a/dct.pl
+++ b/dct.pl
@@ -2,17 +2,17 @@
 
 # DCT - desktop calculator thingy
 
-# reverse polish notition calculator using curses
+# simple modular reverse polish notition calculator
 # by Shiar <shiar.org>
 
-our $VERSION = 1.009;
-
 use strict;
 use warnings;
 use utf8;
 
+use Data::Dumper;
 use Term::ReadKey;
-use Curses;
+
+our $VERSION = "1.10.6";
 
 use vars qw(@stack %val %var %set %alias %action %hook);
 
@@ -27,47 +27,39 @@ use vars qw(@stack %val %var %set %alias %action %hook);
        width    => 42,  # limit value precision, stetch menu
 ); # %set
 
-%alias = (' '=>'enter', "\004"=>'quit', 'q'=>'quit');  # rudimentary default key bindings
+%alias = (' '=>"enter", "\004"=>"quit");  # rudimentary default key bindings
 
 %action = (
-       "chs"   => [1, sub { -$_[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
-
-       "enter" => [0, sub {
+       "enter" => [ 0, sub {
                local $_ = defined $val{i} ? $val{i} : $stack[0];
                undef %val;
                return defined $_ ? $_ : ();
        }], # duplication
 
-       "swap"  => [2, sub { reverse @_ }], # swap x<->y
-       "undo"  => [-1, sub {
-               ($var{undo}, @stack) = ([@stack], @{ $var{undo} });
-       }], # undo/redo
-       "stack" => [-1, sub {
+       "chs"   => [ 1, sub { -$_[0] }], # negative
+
+       "drop"  => [ 1, sub { defined $val{i} ? '' : () }], # drop
+       "back"  => [ 1, sub { () }], # drop essentially
+       "clear" => [ 0, sub { @stack = (); undef %val; () }], # clear all
+
+       "swap"  => [ 2, sub { reverse @_ }], # swap x<->y
+       "stack" => [-2, sub {
                $var{stackpos} = 0 unless $var{stackpos};  # initialize
                $var{stackpos} %= @stack;  # cycle
                $val{i} = $stack[$var{stackpos}++];
        }], # stack
 
-       "version" => [-1, sub { error("Desktop Calculator Thingy $VERSION by Shiar"); () }], # version
+       "sto"   => [ 1, sub { $var{a} = $_[0] }], # copy
+       '?'     => [ 1, sub { $var{a} = $_[0] }], # assign
 
-       "sto"   => [1, sub { $var{a} = $_[0] }], # copy
-       '?'     => [1, sub { $var{a} = $_[0] }], # assign
+       "version" => [-2, sub {
+               error("Desktop Calculator Thingy $VERSION by Shiar"); ()
+       }], # version
 ); # %action
 
 
 sub error($) {
-       attron(A_REVERSE);
-       addstr(0, 0, shift);
-       attroff(A_REVERSE);
-       clrtoeol;
-       refresh;
-
-       ReadKey; # wait for confirm
-       1 while defined ReadKey(-1); # clear key buffer
+       $_->($_[0]) for @{$hook{showerror}};
 } # error
 
 sub showval($$);
@@ -110,46 +102,41 @@ sub showval($$) {
 } # showval
 
 sub showstack() {
-       for (0..@stack-1) {
-               addstr($set{height}-$_, 1, "$_: ".showval($stack[$_], $set{base}));
-               clrtoeol;
-       } # show stack
-       clrtoeol($set{height}-@stack, 1);
+       $_->() for @{$hook{showstack}};
 } # showstack
 
 
-my @modules;
-eval 'require $_' ? push @modules, $_
-: print STDERR "error loading $_\n".(join "", map "\t$_\n", split /\n/, $@)
-       for glob "*.pm";
+my %modules;
+for my $module (sort glob "*.pm") {
+       next unless $module =~ /^\d{2}_(\w+)\.pm$/;  # filename 00_name.pm
+       next if defined $modules{$1};  # such module already loaded
+       defined ($_ = do $module)
+       ? (ref $_ and $modules{$1} = $_)  # return value means no errors
+       : print STDERR $@, "error loading $module\n\n";
+} # load modules
+
+printf STDERR "DCT %s by Shiar (%s)\n", $VERSION,
+       join "; ", map {"$_ $modules{$_}{version}"} keys %modules;
 
-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;
-$_->() for @{ $hook{refresh} };
-showstack();
-addstr($set{height}+1, 0, "> ");  # prompt
-
-LOOP:
-while (1) {
-       addstr($set{height}+1, 2, showval($val{i}, $set{base}));
-       for my $cmd (@{ $hook{showentry} }) {
-               addstr($_) if $_ = $cmd->();
-       } # showentry functions
-       addstr($val{alpha}) if exists $val{alpha};
-       clrtoeol;
-       refresh;
+END { ReadMode 0; } # restore terminal on quit
+
+$_->() for @{$hook{init}};
+my $redraw = 1;
+
+LOOP: while (1) {
+       if ($redraw) {
+               $_->() for @{$hook{refresh}};
+               showstack();
+               $redraw = 0;
+       } # refresh
+
+       {
+               my $entry = showval($val{i}, $set{base});
+               $entry .= $_ for map $_->(), @{$hook{postentry}};
+               $entry .= $val{alpha} if exists $val{alpha};
+               $_->($entry) for @{$hook{showentry}};
+       } # show entry
 
        my $key = ReadKey;
        if ($key eq chr 27) {
@@ -158,14 +145,17 @@ while (1) {
        $_ = $alias{$key} || $key; #if exists $alias{$key};  # command shortkeys
        $_ = delete $val{alpha} if $_ eq "enter" and exists $val{alpha};  # use manual command
 
-       for my $cmd (@{ $hook{precmd} }) {
+       for my $cmd (@{$hook{precmd}}) {
                next LOOP if $cmd->();
        } # precmd functions
 
        last if $_ eq 'quit';
-       goto DRAW if $_ eq 'refresh';
 
-       if (exists $val{alpha} or /^\033?[A-Z]$/) {
+       if ($_ eq 'refresh') {
+               $redraw++;
+       } # refresh
+
+       elsif (/^\033?[A-Z]$/ or exists $val{alpha}) {
                if (defined $val{i}) {
                        unshift @stack, $val{i};
                        undef %val;
@@ -183,11 +173,13 @@ while (1) {
                } # add character
        } # manual command entry
 
-       elsif (/^\d$/) {
+       elsif (/^[\da-f]$/) {
+               m/^[a-z]$/ and $_ = ord($_)-87;  # digit>9
                $val{i} = 0 unless defined $val{i};
                $_ = -$_ if $val{i}<0;  # substract from negative value
-               $val{i} = ($val{frac} and $val{frac} *= 10) ? $val{i}+$_/$val{frac}
-                       : $val{i}*10+$_;
+               $val{i} = ($val{frac} and $val{frac} *= 10)
+                       ? $val{i}+$_/$val{frac}  # add digit to fraction
+                       : $val{i}*$set{base}+$_;  # add digit to integer part
        } # digit
        elsif ($_ eq '.') {
                $val{i} = 0 unless defined $val{i};
@@ -201,69 +193,80 @@ while (1) {
                $val{i} = -$val{i};
        } # change sign
        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
+               $val{i} = ($val{frac} and $val{frac} = int $val{frac}/10)
+                       ? int($val{i}*$val{frac})/$val{frac}  # backspace fraction digit
+                       : int $val{i}/$set{base}  # backspace digit in integer part
        } # backspace
 
        elsif (exists $action{$_}) {
-               my ($type, $cmd) = @{ $action{$_} };
-               unshift @stack, $action{enter}[1]->()
-                       if $type>0 and defined $val{i};  # auto enter
+               my ($type, $cmd) = @{$action{$_}};
+               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");
-                       goto DRAW;
+                       $redraw++;
+                       next;
                } # insufficient arguments
 
-               if ($type>=0) {
-                       $var{undo} = [@stack]; # if $_ ne 'undo';
-                       unshift @stack, $cmd->(splice @stack, 0, $type);
-                       showstack();
-               } # stack-modifying operation
-               else {
-                       $cmd->();
-               } # harmless
+               $_->($type) for @{$hook{preaction}};
+
+               # put return value(s) of stack-modifying operations (type>=0) at stack
+               $type<0 ? $cmd->() : unshift @stack, $cmd->(splice @stack, 0, $type);
+
+               showstack() if $type>=-1;
        } # some operation
 
        else {
-               error("unrecognised command: ".join(' ', map ord, split //, $_));
-               goto DRAW; # screen messed up
+               error(
+                       "unrecognised command: "  # show string or character codes
+                       . (m/^\w*$/ ? qq{"$_"} : join ' ', map ord, split //, $_)
+               );
+               $redraw++;  # 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
+1.01 040618     - start (curses, some basic commands)
+1.02 040620     - function keys select command/submenu from (sub)menu
+                - backspace to undo last digit
+1.03 040625     - values displayable in arbitrary base
+                - can enter fractions (.) and negative values (_)
+1.04 0408041445 - error dialog (don't mess up screen)
+                - manual command input using capital letters
+                - ^L redraws screen
+     0409092200 - overhaul in stack handling
+1.05 0409101945 - 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 0409152310 - menu contents in module
+                - new commands: a?(sin|cos|tan)h, inv, !, rand
+                - x and v shortkeys
+1.07 0409242350 - 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 0409262210 - 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 0409270057 - all key aliases moved to module DCT::Bindings
+     0409291215 - number of menu items depends on screen width
+     0410112130 - hooks allowing for extra code at reload, showentry, and precmd
+           2150 - all menu related functions moved to menu.pm
+           2205 - unit conversion out of main program (entirely into unitconv.pm)
+     0410120150 - backspace becomes "back" (soft drop, like old "drop")
+                - normal drop command (alt+bs) removes input/stack value at once
+           0213 - $val{frac} default undefined instead of 0
+1.10 0410120245 - fixed backspace with undef fraction
+     0410130020 - altered stack not redrawn after undo
+     0410132200 - digits added/removed to/from integer part in correct number base
+     0410142145 - allow modules to not load but without error
+                - display welcome at startup, also showing version and modules
+     0410150000 - preaction hook; undo functionality moved to module
+                - only first module run of multiple with the same name
+           0015 - invalid commands shown as strings instead of character codes
 =cut