Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: a calculator for zsh-4.x



Dear Clifford,

I think your snippet is very clever and provides a quick and
easy way to get a good calculator.

My own solution to the need of a calculator is less elegant
but seems to be a more general and flexible solution. It is
extendable (if you know how to tamper with the perl code)
and has many more capabilities.  It also can be used
anywhere perl is available, not necessarily under zsh.  

I have been using this calculator for a while and it seems
to be quite all right (I won't guarantee there are no bugs).
It took me a while to get it into this shape, but if any one
wants to try it out, it won't require much effort.

To enable the calculator
------------------------

   1. Cut out the perl code attached below, save it in
      a file, e.g. called "pcal.s" (or any other name you
      like) somewhere that your $PATH can access.

   2. chmod +x pcal.s (to make the file executable).

   3. Put "alias pcal='noglob pcal.s' in your .zshrc.

      This line makes it possible to type commands such as

         pcal 3*7
	 
      without using quotes around 3*7.


DISCLAIMER
----------

   The program was written strictly for personal use and is
   definitely not of industrial production grade.  My
   real-work load simply won't allow me to spend too much
   time on such "frivolous" activities :(. There is not much
   error checking or error recovery for user input. So use
   the program at your OWN RISK :)  

   Also the code includes more stuffs than are really needed
   right now - I put those in there with the intention of
   future extension - I simply never could find time for that.


You can use the calculator in two modes:

Non-interactive
---------------

   pcal 3 * 7                        # spaces are optional
   pcal 3 * (sin(3.14159/4) + 1.2)
   pcal dh 12345                     # convert dec -> hex
   pcal hd 123ab                     # convert hex -> dec
   pcal h 123a + abc                 # compute in hex
   pcal b 110111 * 1111              # compute in binary

When used in this mode, the answer is not saved in
a variable as nicely as Clifford's zcal function.  If you
really want it that way, you have to do a little more work.
Instead of defining pcal an alias to pcal.s as before, you 
can define (put these lines in .zshrc)

   pcal.s2() { 
      if [[ -n "$*" ]]
      then
	_x=`echo $* | sed "s/ans/$ans/g"`
	ans=`noglob pcal.s $_x`; print ans = $ans
      else
	 pcal.s2
      fi
   }
   alias pcal='noglob pcal.s2'

But if you need the answer for further computation, you may
as well use the interactive mode.


Interactive  - just type
-----------

   pcal

You will see a prompt

   pcal> 

Then you can type in any math expression or any expression
allowable in the non-Interactive mode.  In addition, you can
assign values or results of expressions to variables 

   * You have to use the perl convention of starting
   * a variable name with $ - sorry, if you are not a perl
   * fan, you may not like this.  If you are as lazy as
   * I am, you may type ; instead of $.

   pcal> $a = 123 + 5
   '0 = 128

   pcal> ;a + 789
   '1 = 917
   
   pcal> ;a + '1
   '2 = 1045

   pcal> h abc * a

If you assign a value to a variable, you can use the
variable in subsequent calculations.  If you do not assign
an expression to a variable, you can still use the answer in
subsequent calculations using the symbol '0, '1, etc.

Use any one of theses commands to quit: q  x  quit  exit

There are other features hidden in the code.  Explore as you
want to.  Let me know if you find any bugs.

One thing you may object about using such a perl script
instead of directly using zsh functions is that once you get
into the interactive mode you lose the capability of command
line editing.  This is true but if you are a more advanced
perl user who can add command line capability in your perl
script, e.g. using the CPAN ReadLine module.  

mk kwong

********************************************************************

Code Snippet:


#!/usr/bin/env perl
# ----------------------------------------------------------------------
# perl calculator - paste me into a file called e.g. pc.s in your $PATH
#                   chmod +x pc.s
#                   put "alias pc='noglob pc.s' in .zshrc
# ----------------------------------------------------------------------

sub isint { $_[0] =~ /^\s*[+-]?\d*\s*$/; }
sub isfloat { $_[0] =~ /^\s*[+-]?(\d*\.\d+)([eE]+[+-]*\d+)*\s*$/; }
sub isefloat { my ($n) = &abs($_[0]); 
  &isfloat($n) && ($n > 1e8 || $n < 1e-8); }
sub isstr { !&isint($_[0]) && !&isfloat($_[0]); }
sub round { for (@_) { $_ = sprintf("%.0f",$_); } @_; }
sub roundf { my ($p) = &isefloat($_[0])? "e" : "f"; 
  sprintf("%.$_[1]$p",$_[0]); }
sub floor { for $n (@_) { if ($n =~ /\./) {
    if ($n >= 0 || $n =~ /\.0*$/) { $n =~ s/\.\d*//; }
    else { $n =~ s/\.\d*//; $n--; } } } 
  @_; }
sub ceil { for $n (@_) { ($n) = &floor(-$n); $n = -$n;} @_; }
sub nsort { return sort {$a <=> $b} @_; }

sub pr { 
## In my own environment, I have command line editing capability
## REPLACE by subroutine to enable command line editing, e.g. using
## the CPAN modules of Term::ReadLine and Term::ReadLine::Gnu
  print $prompt; $_ = <>;
}

####################################################################

($_p = $0) =~ s/.*\///;
$_argvs = join(" ",@ARGV);
if (@ARGV) {
   $non_int = 1;                  #// non-interactive
   $_ = join(" ",@ARGV);
   proc();
   exit;
}
$prompt ||= "pcal> ";
$quit = '(q|x|quit|exit)';

####################################################################

sub loop {
pr();          # $_nopr = 1 means no initial prompt
while (1) {
  if (/^$quit$/) { exit; }
  exec "$_p $_argvs" if /^\.{1,}$/;                                    # ... new version of program
  proc();
  pr();
} }

####################################################################
 
$_debug = 1;
$_OFMT = "%.6g";
loop();

####################################################################
 
sub proc { $_a = $_s = 0;
$_db && db('=xxx,');
  if (/^db$/) { $_db = !$_db; return; }                            # toggle debug
  if (/^fmt\s*(\d+)$/) { $_OFMT = "%.$1g"; return; }               # float output format
  if (/^,\s*(.*)/) { system($1); return; }                         # , shell
  if (/^\.+$/) { system "pe"; exit; }                              # . rerun
  if (/^\. \s*(.*)/) { eval($1); return; }                         # . perl
  s/\`\`/\$_ans[0]/; s/\`(\d+)/\$_ans[$1]/g;                       # ``  => $_ans
  s/;(\w)/\$\1/g;                                                  # ;a  => $a
$_db && db('=yyy,');
  if (/^\.(\w+)$/) { $_ = "print join(\", \",\@$1)";               # .a => print @a
     eval; print ",\n"; return; }
  if (/^\/(\w+)$/) { eval "\%_a = \%$1";                           # /a => print %a
     for $_i (sort(keys(%_a))) { print "$_i => $_a{$_i}, "; }
     print "\n"; return; }
  math();
}

sub math {                   #// math mode
   s/\'\'/$_ans[$_nans]/g;
   s/\'(\d+)/$_ans[$1]/g;
   s/\'([a-zA-Z])/\$$1/g;
   if (s/^hd\s*//) { print " " x 4;                  #// hex to dec
      if (/[^\w\s]/) { s/(\w+)/h2d($1)/ge; print eval, "\n"; }
      else { for $_x (split(/\s+/)) { $_x and print h2d($_x),"  "; } print "\n"; } return; }
   if (s/^dh\s*//) { print " " x 4;                  #// dec to hex
      if (/[^\w\s]/) { print d2h(eval), "\n"; }
      else { for $_x (split(/\s+/)) { $_x and print d2h($_x),"  "; } print "\n"; } return; }
   if (s/^bd\s*//) { print " " x 4;                  #// bin to dec
      if (/[^\w\s]/) { s/(\w+)/b2d($1)/ge; print eval, "\n"; }
      else { for $_x (split(/\s+/)) { $_x and print b2d($_x),"  "; } print "\n"; } return; }
   if (s/^db\s*//) { print " " x 4;                  #// dec to bin
      if (/[^\w\s]/) { print d2b(eval), "\n"; }
      else { for $_x (split(/\s+/)) { $_x and print d2b($_x),"  "; } print "\n"; } return; }
   if (s/^hb\s*//) { print " " x 4;                  #// hex to bin
      if (/[^\w\s]/) { s/(\w+)/h2d($1)/ge; print d2b(eval), "\n"; }
      else { for $_x (split(/\s+/)) { $_x and print d2b(h2d($_x)),"  "; } print "\n"; } return; }
   if (s/^bh\s*//) { print " " x 4;                  #// bin to hex
      if (/[^\w\s]/) { s/(\w+)/b2d($1)/ge; print d2h(eval), "\n"; }
      else { for $_x (split(/\s+/)) { $_x and print d2h(b2d($_x)),"  "; } print "\n"; } return; }
   if (s/^h\s+//) {                 #// hex calculations
      s/(\w+)/h2d($1)/ge; 
      $_ans = d2h(eval); 
      push(@_ans,$_ans); $_nans = $#_ans;
      $non_int or print "'$_nans = "; print $_ans, "\n";
      $non_int or print "\n";
      return; }
   if (s/^b\s+//) {                 #// bin calculations
      s/(\w+)/b2d($1)/ge; 
      $_ans = d2b(eval); 
      push(@_ans,$_ans); $_nans = $#_ans;
      $non_int or print "'$_nans = "; print $_ans, "\n";
      $non_int or print "\n";
      return; }
   if (/;\s*$/) { eval; return; }
   eval "\$_ans = $_";
   push(@_ans,$_ans); $_nans = $#_ans;
   $non_int or print "'$_nans = "; print $_ans, "\n";
   $non_int or print "\n";
}

sub d2h($) { local($a,$b,$c) = $_[0]; while ($a > 0) { $c = $a % 16; $a = ($a-$c)/16;
    if ($c == 10) { $c = "a"; } elsif ($c == 11) { $c = "b"; } 
    elsif ($c == 12) { $c = "c"; } elsif ($c == 13) { $c = "d"; } 
    elsif ($c == 14) { $c = "e"; } elsif ($c == 15) { $c = "f"; } 
    $b = "$c$b" } return "$b"; }
sub h2d($) { hex($_[0]); }
sub d2b($) { local($a,$b,$c) = $_[0]; while ($a > 0) { $c = $a % 2; $a = ($a-$c)/2;
    $b = "$c$b" } return "$b"; }
sub b2d($) { local($a,$b) = $_[0]; while ($a =~ s/.//) { $b = 2*$b + $&; } return $b; }

# -----------------------------------------------------------------end

 > 
> Dear zsh-users:
> 
> In my excitement over zsh-4.x, I bring to you some sample calculator
> functions to enhance your zsh experience. However, I haven't searched this
> mailing list for similar subjects at all, and I am ignorant of this mailing
> list past posts. Perhaps I'm duplicating suggestions here, but I hope this
> is helpful.
> 
> I don't know about you, but I reach for zsh when I want to calculate
> addition in hexadecimal (like whenever I have read a linux ksymoops
> message). With the following .zshrc snippet, my shell becomes a
> convenient-to-type calculator:
> 
> Code Snippet:
> # -----------------------------------------------------------------begin
> # calculator - paste me into your .zshrc
> # ----------------------------------------------------------------------
> 
> # Here are some quick calculators that output in integer
> # hexadecimal, decimal, and binary.
> zcalc ()  { print $(( ans = ${1:-ans} )) }
> zcalch () { print $(( [#16] ans = ${1:-ans} )) }
> zcalcd () { print $(( [#10] ans = ${1:-ans} )) }
> zcalco () { print $(( [#8] ans = ${1:-ans} )) }
> zcalcb () { print $(( [#2] ans = ${1:-ans} )) }
> 
> # A key binding that will allow you to quickly get into zcalc
> bindkey -s '\C-xd' "zcalc \'"
> 
> [... omitted]



Messages sorted by: Reverse Date, Date, Thread, Author