3 # DCT - desktop calculator thingy
5 # simple modular reverse polish notition calculator
15 our $VERSION = "1.10.6";
17 use vars qw(@stack %val %var %set %alias %action %hook);
20 base => 10, # decimal; set using commands bin/oct/dec/hex/base
21 numb => 0, # fixed scientific engineering
22 card => 1, # degrees radians grades
23 coord => 0, # cartesian polar spherical
24 complex => 0, # real complex
26 height => 4, # stack depth (lines of stack plus one)
27 width => 42, # limit value precision, stetch menu
30 %alias = (' '=>"enter", "\004"=>"quit"); # rudimentary default key bindings
34 local $_ = defined $val{i} ? $val{i} : $stack[0];
36 return defined $_ ? $_ : ();
39 "chs" => [ 1, sub { -$_[0] }], # negative
41 "drop" => [ 1, sub { defined $val{i} ? '' : () }], # drop
42 "back" => [ 1, sub { () }], # drop essentially
43 "clear" => [ 0, sub { @stack = (); undef %val; () }], # clear all
45 "swap" => [ 2, sub { reverse @_ }], # swap x<->y
47 $var{stackpos} = 0 unless $var{stackpos}; # initialize
48 $var{stackpos} %= @stack; # cycle
49 $val{i} = $stack[$var{stackpos}++];
52 "sto" => [ 1, sub { $var{a} = $_[0] }], # copy
53 '?' => [ 1, sub { $var{a} = $_[0] }], # assign
55 "version" => [-2, sub {
56 error("Desktop Calculator Thingy $VERSION by Shiar"); ()
62 $_->($_[0]) for @{$hook{showerror}};
67 my ($val, $base) = @_;
68 return '' unless defined $val;
69 return $val if $base==10;
79 while ($int>$base**10) {
85 my $char = $int%$base;
86 $txt = ($char<10 ? $char : chr($char+55)).$txt;
90 $txt .= '.' if $frac>0;
91 for (my $i = 0; length $txt<$set{width}-2 && $frac>0; $i++) {
95 $txt .= $char<10 ? $char : chr($char+55);
98 $txt = "-".$txt if $sign;
99 $txt .= 'e'.showval($exp, $base) if $exp;
105 $_->() for @{$hook{showstack}};
110 for my $module (sort glob "*.pm") {
111 next unless $module =~ /^\d{2}_(\w+)\.pm$/; # filename 00_name.pm
112 next if defined $modules{$1}; # such module already loaded
113 defined ($_ = do $module)
114 ? (ref $_ and $modules{$1} = $_) # return value means no errors
115 : print STDERR $@, "error loading $module\n\n";
118 printf STDERR "DCT %s by Shiar (%s)\n", $VERSION,
119 join "; ", map {"$_ $modules{$_}{version}"} keys %modules;
121 ReadMode 3; # cbreak mode
122 END { ReadMode 0; } # restore terminal on quit
124 $_->() for @{$hook{init}};
129 $_->() for @{$hook{refresh}};
135 my $entry = showval($val{i}, $set{base});
136 $entry .= $_ for map $_->(), @{$hook{postentry}};
137 $entry .= $val{alpha} if exists $val{alpha};
138 $_->($entry) for @{$hook{showentry}};
142 if ($key eq chr 27) {
143 $key .= $_ while defined ($_ = ReadKey(-1)); # read additional keys
145 $_ = $alias{$key} || $key; #if exists $alias{$key}; # command shortkeys
146 $_ = delete $val{alpha} if $_ eq "enter" and exists $val{alpha}; # use manual command
148 for my $cmd (@{$hook{precmd}}) {
149 next LOOP if $cmd->();
152 last if $_ eq 'quit';
154 if ($_ eq 'refresh') {
158 elsif (/^\033?[A-Z]$/ or exists $val{alpha}) {
159 if (defined $val{i}) {
160 unshift @stack, $val{i};
163 } # enter present value
166 $val{alpha} = substr $val{alpha}, 0, -1 or delete $val{alpha};
168 elsif ($_ eq "drop") {
172 $val{alpha} .= $key =~ /^\033(.)/ ? uc $1 : lc $key;
174 } # manual command entry
176 elsif (/^[\da-f]$/) {
177 m/^[a-z]$/ and $_ = ord($_)-87; # digit>9
178 $val{i} = 0 unless defined $val{i};
179 $_ = -$_ if $val{i}<0; # substract from negative value
180 $val{i} = ($val{frac} and $val{frac} *= 10)
181 ? $val{i}+$_/$val{frac} # add digit to fraction
182 : $val{i}*$set{base}+$_; # add digit to integer part
185 $val{i} = 0 unless defined $val{i};
188 elsif ($_ eq "eex") {
189 $val{i} = 1 unless defined $val{i};
192 elsif ($_ eq "chs" and defined $val{i}) {
195 elsif ($_ eq "back" and defined $val{i}) {
196 $val{i} = ($val{frac} and $val{frac} = int $val{frac}/10)
197 ? int($val{i}*$val{frac})/$val{frac} # backspace fraction digit
198 : int $val{i}/$set{base} # backspace digit in integer part
201 elsif (exists $action{$_}) {
202 my ($type, $cmd) = @{$action{$_}};
203 unshift @stack, $action{enter}[1]->() if $type>0 and defined $val{i}; # auto enter
205 if ($type>0 and $type>@stack) {
206 error("insufficient stack arguments for operation");
209 } # insufficient arguments
211 $_->($type) for @{$hook{preaction}};
213 # put return value(s) of stack-modifying operations (type>=0) at stack
214 $type<0 ? $cmd->() : unshift @stack, $cmd->(splice @stack, 0, $type);
216 showstack() if $type>=-1;
221 "unrecognised command: " # show string or character codes
222 . (m/^\w*$/ ? qq{"$_"} : join ' ', map ord, split //, $_)
224 $redraw++; # screen messed up
230 1.01 040618 - start (curses, some basic commands)
231 1.02 040620 - function keys select command/submenu from (sub)menu
232 - backspace to undo last digit
233 1.03 040625 - values displayable in arbitrary base
234 - can enter fractions (.) and negative values (_)
235 1.04 0408041445 - error dialog (don't mess up screen)
236 - manual command input using capital letters
238 0409092200 - overhaul in stack handling
239 1.05 0409101945 - hp48-like drop (backspace but not editing value)
240 - error on insufficient arguments for command
241 - command backspacing
242 - some unit conversion (mostly lengths) from menu
243 - q for sq(rt) (formerly quit, now only ^D/quit)
244 1.06 0409152310 - menu contents in module
245 - new commands: a?(sin|cos|tan)h, inv, !, rand
247 1.07 0409242350 - numeric modifiers hardcoded instead of in action hash
248 - action undo: last stack alteration can be undone
249 - enter on no value repeats last val on stack
250 - new commands: sr/sr, shortkeys ( )
251 1.08 0409262210 - additional digits were not correctly applied to negative values
252 - negative numbers displayed correctly in different bases
255 - stack command (cursor up) cycles through values in stack
256 1.09 0409270057 - all key aliases moved to module DCT::Bindings
257 0409291215 - number of menu items depends on screen width
258 0410112130 - hooks allowing for extra code at reload, showentry, and precmd
259 2150 - all menu related functions moved to menu.pm
260 2205 - unit conversion out of main program (entirely into unitconv.pm)
261 0410120150 - backspace becomes "back" (soft drop, like old "drop")
262 - normal drop command (alt+bs) removes input/stack value at once
263 0213 - $val{frac} default undefined instead of 0
264 1.10 0410120245 - fixed backspace with undef fraction
265 0410130020 - altered stack not redrawn after undo
266 0410132200 - digits added/removed to/from integer part in correct number base
267 0410142145 - allow modules to not load but without error
268 - display welcome at startup, also showing version and modules
269 0410150000 - preaction hook; undo functionality moved to module
270 - only first module run of multiple with the same name
271 0015 - invalid commands shown as strings instead of character codes