# 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);
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($$);
} # 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) {
$_ = $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;
} # 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};
$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