mirror of
				git://git.code.sf.net/p/zsh/code
				synced 2025-11-04 07:21:06 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			263 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
			
		
		
	
	
			263 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
#!/bin/zsh -i
 | 
						|
#
 | 
						|
# Zsh calculator.  Understands most ordinary arithmetic expressions.
 | 
						|
# Line editing and history are available. A blank line or `q' quits.
 | 
						|
#
 | 
						|
# Runs as a script or a function.  If used as a function, the history
 | 
						|
# is remembered for reuse in a later call (and also currently in the
 | 
						|
# shell's own history).  There are various problems using this as a
 | 
						|
# script, so a function is recommended.
 | 
						|
#
 | 
						|
# The prompt shows a number for the current line.  The corresponding
 | 
						|
# result can be referred to with $<line-no>, e.g.
 | 
						|
#   1> 32 + 10
 | 
						|
#   42
 | 
						|
#   2> $1 ** 2
 | 
						|
#   1764
 | 
						|
# The set of remembered numbers is primed with anything given on the
 | 
						|
# command line.  For example,
 | 
						|
#   zcalc '2 * 16'
 | 
						|
#   1> 32                     # printed by function
 | 
						|
#   2> $1 + 2                 # typed by user
 | 
						|
#   34
 | 
						|
#   3> 
 | 
						|
# Here, 32 is stored as $1.  This works in the obvious way for any
 | 
						|
# number of arguments.
 | 
						|
#
 | 
						|
# If the mathfunc library is available, probably understands most system
 | 
						|
# mathematical functions.  The left parenthesis must be adjacent to the
 | 
						|
# end of the function name, to distinguish from shell parameters
 | 
						|
# (translation: to prevent the maintainers from having to write proper
 | 
						|
# lookahead parsing).  For example,
 | 
						|
#   1> sqrt(2)
 | 
						|
#   1.4142135623730951
 | 
						|
# is right, but `sqrt (2)' will give you an error.
 | 
						|
#
 | 
						|
# You can do things with parameters like
 | 
						|
#   1> pi = 4.0 * atan(1)
 | 
						|
# too.  These go into global parameters, so be careful.  You can declare
 | 
						|
# local variables, however:
 | 
						|
#   1> local pi
 | 
						|
# but note this can't appear on the same line as a calculation.  Don't
 | 
						|
# use the variables listed in the `local' and `integer' lines below
 | 
						|
# (translation: I can't be bothered to provide a sandbox).
 | 
						|
#
 | 
						|
# You can declare or delete math functions (implemented via zmathfuncdef):
 | 
						|
#   1> function cube $1 * $1 * $1
 | 
						|
# This has a single compulsory argument.  Note the function takes care of
 | 
						|
# the punctuation.  To delete the function, put nothing (at all) after
 | 
						|
# the function name:
 | 
						|
#   1> function cube
 | 
						|
#
 | 
						|
# Some constants are already available: (case sensitive as always):
 | 
						|
#   PI     pi, i.e. 3.1415926545897931
 | 
						|
#   E      e, i.e. 2.7182818284590455
 | 
						|
#
 | 
						|
# You can also change the output base.
 | 
						|
#   1> [#16]
 | 
						|
#   1>
 | 
						|
# Changes the default output to hexadecimal with numbers preceded by `16#'.
 | 
						|
# Note the line isn't remembered.
 | 
						|
#   2> [##16]
 | 
						|
#   2>
 | 
						|
# Change the default output base to hexadecimal with no prefix.
 | 
						|
#   3> [#]
 | 
						|
# Reset the default output base.
 | 
						|
#
 | 
						|
# This is based on the builtin feature that you can change the output base
 | 
						|
# of a given expression.  For example,
 | 
						|
#   1> [##16]  32 + 20 / 2
 | 
						|
#   2A
 | 
						|
#   2> 
 | 
						|
# prints the result of the calculation in hexadecimal.
 | 
						|
#
 | 
						|
# You can't change the default input base, but the shell allows any small
 | 
						|
# integer as a base:
 | 
						|
#   1> 2#1111
 | 
						|
#   15
 | 
						|
#   2> [##13] 13#6 * 13#9
 | 
						|
#   42
 | 
						|
# and the standard C-like notation with a leading 0x for hexadecimal is
 | 
						|
# also understood.  However, leading 0 for octal is not understood --- it's
 | 
						|
# too confusing in a calculator.  Use 8#777 etc.
 | 
						|
#
 | 
						|
# Options: -#<base> is the same as a line containing just `[#<base>],
 | 
						|
# similarly -##<base>; they set the default output base, with and without
 | 
						|
# a base discriminator in front, respectively.
 | 
						|
#
 | 
						|
#
 | 
						|
# To do:
 | 
						|
# - separate zcalc history from shell history using arrays --- or allow
 | 
						|
#   zsh to switch internally to and from array-based history.
 | 
						|
 | 
						|
emulate -L zsh
 | 
						|
setopt extendedglob
 | 
						|
 | 
						|
# TODO: make local variables that shouldn't be visible in expressions
 | 
						|
# begin with _.
 | 
						|
local line ans base defbase forms match mbegin mend psvar optlist opt arg
 | 
						|
local compcontext="-zcalc-line-"
 | 
						|
integer num outdigits outform=1
 | 
						|
 | 
						|
# We use our own history file with an automatic pop on exit.
 | 
						|
history -ap "${ZDOTDIR:-$HOME}/.zcalc_history"
 | 
						|
 | 
						|
forms=( '%2$g' '%.*g' '%.*f' '%.*E' '')
 | 
						|
 | 
						|
zmodload -i zsh/mathfunc 2>/dev/null
 | 
						|
autoload -U zmathfuncdef
 | 
						|
 | 
						|
: ${ZCALCPROMPT="%1v> "}
 | 
						|
 | 
						|
# Supply some constants.
 | 
						|
float PI E
 | 
						|
(( PI = 4 * atan(1), E = exp(1) ))
 | 
						|
 | 
						|
# Process command line
 | 
						|
while [[ -n $1 && $1 = -(|[#-]*) ]]; do
 | 
						|
  optlist=${1[2,-1]}
 | 
						|
  shift
 | 
						|
  [[ $optlist = (|-) ]] && break
 | 
						|
  while [[ -n $optlist ]]; do
 | 
						|
    opt=${optlist[1]}
 | 
						|
    optlist=${optlist[2,-1]}
 | 
						|
    case $opt in
 | 
						|
      ('#') # Default base
 | 
						|
            if [[ -n $optlist ]]; then
 | 
						|
	       arg=$optlist
 | 
						|
	       optlist=
 | 
						|
	    elif [[ -n $1 ]]; then
 | 
						|
	       arg=$1
 | 
						|
	       shift
 | 
						|
	    else
 | 
						|
	       print "-# requires an argument" >&2
 | 
						|
	       return 1
 | 
						|
	    fi
 | 
						|
	    if [[ $arg != (|\#)[[:digit:]]## ]]; then
 | 
						|
	      print - "-# requires a decimal number as an argument" >&2
 | 
						|
	      return 1
 | 
						|
	    fi
 | 
						|
            defbase="[#${arg}]"
 | 
						|
	    ;;
 | 
						|
    esac
 | 
						|
  done
 | 
						|
done
 | 
						|
 | 
						|
for (( num = 1; num <= $#; num++ )); do
 | 
						|
  # Make sure all arguments have been evaluated.
 | 
						|
  # The `$' before the second argv forces string rather than numeric
 | 
						|
  # substitution.
 | 
						|
  (( argv[$num] = $argv[$num] ))
 | 
						|
  print "$num> $argv[$num]"
 | 
						|
done
 | 
						|
 | 
						|
psvar[1]=$num
 | 
						|
while vared -cehp "${(%)ZCALCPROMPT}" line; do
 | 
						|
  [[ -z $line ]] && break
 | 
						|
  # special cases
 | 
						|
  # Set default base if `[#16]' or `[##16]' etc. on its own.
 | 
						|
  # Unset it if `[#]' or `[##]'.
 | 
						|
  if [[ $line = (#b)[[:blank:]]#('[#'(\#|)(<->|)']')[[:blank:]]#(*) ]]; then
 | 
						|
    if [[ -z $match[4] ]]; then
 | 
						|
      if [[ -z $match[3] ]]; then
 | 
						|
	defbase=
 | 
						|
      else
 | 
						|
	defbase=$match[1]
 | 
						|
      fi
 | 
						|
      print -s -- $line
 | 
						|
      line=
 | 
						|
      continue
 | 
						|
    else
 | 
						|
      base=$match[1]
 | 
						|
    fi
 | 
						|
  else
 | 
						|
    base=$defbase
 | 
						|
  fi
 | 
						|
 | 
						|
  print -s -- $line
 | 
						|
 | 
						|
  line="${${line##[[:blank:]]#}%%[[:blank:]]#}"
 | 
						|
  case "$line" in
 | 
						|
    # Escapes begin with a colon
 | 
						|
    (:!*)
 | 
						|
    # shell escape
 | 
						|
    eval ${line##:\![[:blank:]]#}
 | 
						|
    line=
 | 
						|
    continue
 | 
						|
    ;;
 | 
						|
 | 
						|
    ((:|)q)
 | 
						|
    # Exit
 | 
						|
    return 0
 | 
						|
    ;;
 | 
						|
 | 
						|
    ((:|)norm) # restore output format to default
 | 
						|
      outform=1
 | 
						|
    ;;
 | 
						|
 | 
						|
    ((:|)sci[[:blank:]]#(#b)(<->)(#B))
 | 
						|
      outdigits=$match[1]
 | 
						|
      outform=2
 | 
						|
    ;;
 | 
						|
 | 
						|
    ((:|)fix[[:blank:]]#(#b)(<->)(#B))
 | 
						|
      outdigits=$match[1]
 | 
						|
      outform=3
 | 
						|
    ;;
 | 
						|
 | 
						|
    ((:|)eng[[:blank:]]#(#b)(<->)(#B))
 | 
						|
      outdigits=$match[1]
 | 
						|
      outform=4
 | 
						|
    ;;
 | 
						|
 | 
						|
    (:raw)
 | 
						|
    outform=5
 | 
						|
    ;;
 | 
						|
 | 
						|
    ((:|)local([[:blank:]]##*|))
 | 
						|
      eval $line
 | 
						|
      line=
 | 
						|
      continue
 | 
						|
    ;;
 | 
						|
 | 
						|
    ((:|)function[[:blank:]]##(#b)([^[:blank:]]##)(|[[:blank:]]##([^[:blank:]]*)))
 | 
						|
      zmathfuncdef $match[1] $match[3]
 | 
						|
      line=
 | 
						|
      continue
 | 
						|
    ;;
 | 
						|
 | 
						|
    (:*)
 | 
						|
    print "Unrecognised escape"
 | 
						|
    line=
 | 
						|
    continue
 | 
						|
    ;;
 | 
						|
 | 
						|
    (*)
 | 
						|
      # Latest value is stored as a string, because it might be floating
 | 
						|
      # point or integer --- we don't know till after the evaluation, and
 | 
						|
      # arrays always store scalars anyway.
 | 
						|
      #
 | 
						|
      # Since it's a string, we'd better make sure we know which
 | 
						|
      # base it's in, so don't change that until we actually print it.
 | 
						|
      eval "ans=\$(( $line ))"
 | 
						|
      # on error $ans is not set; let user re-edit line
 | 
						|
      [[ -n $ans ]] || continue
 | 
						|
      argv[num++]=$ans
 | 
						|
      psvar[1]=$num
 | 
						|
    ;;
 | 
						|
  esac
 | 
						|
  if [[ -n $base ]]; then
 | 
						|
    print -- $(( $base $ans ))
 | 
						|
  elif [[ $ans = *.* ]] || (( outdigits )); then
 | 
						|
    if [[ -z $forms[outform] ]]; then
 | 
						|
      print -- $(( $ans ))
 | 
						|
    else
 | 
						|
      printf "$forms[outform]\n" $outdigits $ans
 | 
						|
    fi
 | 
						|
  else
 | 
						|
    printf "%d\n" $ans
 | 
						|
  fi
 | 
						|
  line=
 | 
						|
done
 | 
						|
 | 
						|
return 0
 |