#!/usr/bin/perl -w
# calculator to calculate command line values in order (no BODMAS)
#
# copyright Paul Grosse (c) 2004
#
# take command line as a series of commands with no spaces ie
# calc 3*8.21^1.23/6+3
# which would effectively be...
# (((3 * 8.21) ^1.23) / 6) + 3
# and give...
# 11.5772876747159

($progress, $step, $steps) = (0, "", "abcdefghijklmnopqrstuvwxyz");

if ($#ARGV >= 0 ) {
  if ($#ARGV == 0) {
    if ($ARGV[0] =~ /^-h|--help/) {
      &printhelp;
      exit (0);
    };
  };
  foreach $x (@ARGV){
    $str = $x = lc($x);
    # break it up, bit by bit and calculate it
    # get a value from the string
    $error = 0;
    $n = &getval;
    if ($error != 0) {
      # jump out of loop and finish
      print "Error in $x...\n     $str\n\n";
      exit($error);
    };
    while (length($str) > 0) {
      # get a symbol
      if ($str =~ s"^(\+|-|\*|/|\^)"" ) {
        $fn = $1;
      } else {
        # jump out of loop and finish
        print "Error in $x...\n      $str\n\n";
        exit(2);
      };
      # get a value
      $m = &getval;
      if ($error != 0) {
        # jump out of loop and finish
        print "Error in $x...\n      $str\n\n";
        exit($error);
      };
      if      ($fn eq "+") {
        $n = $n + $m;
      } elsif ($fn eq "-") {
        $n = $n - $m;
      } elsif ($fn eq "*") {
        $n = $n * $m;
      } elsif ($fn eq "/") {
        $n = $n / $m;
      } elsif ($fn eq "^") {
        $n = $n ** $m;
      };
    };
    if ($error == 0) {
      # increment logging system
      {
        if (length ($steps) >0 ) {
          # still some left so let's take the first
          $keyed = 1;
          $steps =~ s/(\w)//;
          $keyi = $1;
          $step .= $keyi;
          $progress++;
          $args{$keyi} = $n;
        }else {
          $keyed = 0;
        };
      }
      # print out result
      print "$x = $n";
      if ($#ARGV > 0 ) {
        if ($keyed == 1) {
          print "  ($keyi)";
        };
      };
      print "\n";
    } else {
      # jump out of loop and finish
      print "Error in $x...\n     $str\n\n";
      exit(1);
    };
  };
} else {
  &printhelp;
};
exit (0);
###################################
## subs
###################################
sub getval {
  # strip a value from the beginning of $str and return it
  # look for letters if there are any in $step
  if (length($step) > 0) {
    # previous steps exist so look for a letter at the beginning
    if ($str =~ s/^([$step])//) {
      # got one
      my $key = $1;
      my $r = $args{$key};
      return $r;
    }
  };
  # must start with a digit. grab it
  {
    if ($str =~ s/^(\d+)//) {
      $v = $1;
      # look for a decimal point
      if ($str =~ s/^(\.)//) {
        # if found, get the rest of the number
        if ($str =~ s/^(\d+)//) {
          # concatenate the string
          $v .= ".".$1;
          return $v;
        };
      } else {
        # nothing after the decimal point
        return $v;
      };
    } else {
      $error = 1;
    };
  };
}

sub printhelp {
  # print the help page
  print "\n      ###############################################\n";
  print "      # Calc - simple command line calculator v 0.1 #\n";
  print "      #    Copyright (c) Paul Grosse 2004           #\n";
  print "      ###############################################\n\n";
  print "Use - calculates results from command line with each calculation\n";
  print "  occupying a contiguous block. Numbers and commands are used in\n";
  print "  the order that they appear on the command line - no BODMAS.\n";
  print "This program is aimed at simple use so I have not made it too\n";
  print "  extensive in its use - exponentials are not supported. It is\n";
  print "  really intended just for a quick calculation on a command line\n";
  print "  without having to load anything too large and complicated.\n";
  print "A single calculation can be entered by using a single block\n";
  print "  like so...\n\n";
  print "calc 3*4.7/9.8+3^2\n\n";
  print "  or multiple calculations can be given on one command line\n";
  print "  like so...\n\n";
  print "calc 3*4.7/9.8+3^2 0-4.1^3.6\n\n";
  print "Permitted characters in a calculation are:\n";
  print "  digits - either integer or floating point;\n";
  print "  +, -, *, / ^ for plus, minus, multiply, divide and power.\n";
  print "To get a negative number, for example, -3, use 0-3 as you would\n";
  print "  with a calculator.\n";
  print "To get a floating point number less than 1, always start with\n";
  print "  a zero so, don\'t type .3, use 0.3 instead.\n\n";
  print "In addition to these, you can, if you are using multiple calculations\n";
  print "  on one command line, refer to the result of a previous calculation\n";
  print "  by using a letter starting with 'a' as the first result so...\n";
  print "For example - If you wanted to find the total energy of 50 grammes\n";
  print "  travelling at 7,500m\/s, you would use the equation...\n\n";
  print "                  2\n";
  print "    E =        m c\n";
  print "        --------------\n";
  print "            (      2 )\n";
  print "            (     v  )\n";
  print "        sqrt(1 - --- )\n";
  print "            (      2 )\n";
  print "            (     c  )\n\n";
  print "You can break this up any way you like but one way is effectively\n";
  print "  to say...\n\n";
  print "    a\' = 300,000,000 ^ 2\n";
  print "    b\' = 7,500 ^ 2 \/ a\'\n";
  print "    c\' = 1 - b\' ^ 0.5\n";
  print "  Your answer = a\' * 0.05 / c\'\n\n";
  print "You put this on the command line like so...\n\n";
  print "calc 300000000^2 7500^2\/a 1-b^0.5 a*0.05\/c\n\n";
  print "You are therefore limited to references to the first 26 calculations\n";
  print "  but you wouldn\'t want a command line that looked like that, would\n";
  print "  you? It would be defeating the object of the exercise.\n\n";
  print "Have fun...\n\n";
}
