## Version 2 of A bc-like calculator

Updated 2005-12-08 03:23:54

Arjen Markus (3 november 2004) The script below may not appeal to anyone who is used to handheld calculators and their software implementations. It is a script that mimicks the interface of the UNIX program bc. Rather than punching in the buttons on a small keypad, you can just type expressions like "1+2.3" and then press the Enter key to get the result.

This version allows you to define functions too, so that you do not have to repeat the whole expression when you need to fill in various values. For instance:

If you need to compute sin(2x) for a bunch of values for x, just do:

``` >> f(x)=sin(2*x)
>>f(1.)
>>f(2)
>>1/(1+f(3))```

etc.

(Oh, it does not work yet with functions of more than one argument and there are numerous other improvements possible).

``` # calc2.tcl --
#    Script to emulate a calculator, allows the on-the-spot
#    evaluation of expressions as well the definition of
#    functions
#
#    Allow function definitions:
#    >> f(x) = x*x + 2
#    >> g(x) = 1/(x+1)
#    >> f(4)*g(5)
#    3.0
#
# Author: Arjen Markus ([email protected])
#
# Notes:
#    - Nice interface to Tk (distinguish from Tcl)
#    - Run-time check for recursion
#    - On-line help
#

# Functions --
#    Namespace for the user-defined macros
#
namespace eval ::Functions {
}

# Calculator --
#    Namespace for the public commands
#
namespace eval ::Calculator {
variable hexmode    0
variable hexprompt  "(HEX)>> "
variable decprompt  ">> "
variable prompt     \$decprompt
}

# Standard functions --
#    Define the standard functions
#
# Arguments:
#    arglist     List of variables
#    expression  Expression to be evaluated
#    values      List of actual values
# Return value:
#    Value of the expression
#
namespace eval ::Functions {
#
# Note: no braces! - sin(1.0+2.0) would not work then
#
set prototype {
proc ::Functions::<name> { arg } {
expr <name>(\$arg)
}}
foreach f {exp sin cos tan log abs atan acos asin} {
eval [string map [list "<name>" \$f] \$prototype]
}

set prototype {
proc ::Functions::<name> { arg1 arg2 } {
expr <name>(\$arg1,\$arg2)
}}
foreach f {atan2 hypot} {
eval [string map [list "<name>" \$f] \$prototype]
}

#
# _id_
#
proc ::Functions::_id_ {arg} {
expr \$arg
}
#
# min, max
#
proc ::Functions::min {arg1 arg2} {
expr (\$arg1)<(\$arg2)? (\$arg1) : (\$arg2)
}
proc ::Functions::max {arg1 arg2} {
expr (\$arg1)<(\$arg2)? (\$arg1) : (\$arg2)
}
}

# HandleCommand --
#    Identify the type of command and handle accordingly
#
# Arguments:
#    command     Command that must be handled
# Return value:
#    {} if the command is a definition or the value of the expression.
# Side effects:
#    Definitions are handled directly
#
proc ::Calculator::HandleCommand { command } {
set new_command [string map { " " "" "\t" "" } \$command]

#
# Definitions take the form "name(x)=some expression"
#
if { [regexp {^[A-Za-z_][A-Za-z0-9]*\(.+\)=} \$new_command] } {
HandleDefinition \$new_command
return ""
} else {
switch -- \$command {
"?" - "help" {
ShowHelp
}
"hex" {
set ::Calculator::hexmode 1
set ::Calculator::prompt  \$::Calculator::hexprompt
}
"dec" {
set ::Calculator::hexmode 0
set ::Calculator::prompt  \$::Calculator::decprompt
}
default {
Evaluate \$new_command
}
}
}
}

# Evaluate --
#    Evaluate the expression
#
# Arguments:
#    command     Command that must be evaluated
# Return value:
#    The value of the expression.
#
proc ::Calculator::Evaluate { command } {
variable hexmode
if { \$hexmode == 0 } {
regsub -all {([a-zA-Z][a-zA-Z_0-9]*)\(} \$command {[\1 } command
regsub -all {[^ ]\(} \$command {[_id_ } command
set command [string map { ")" "]" } \$command]

namespace eval ::Functions [list expr \$command]

} else {
# TODO: big-endian/little-endian?
scan \$command %x intv
binary scan [binary format i \$intv] f realv
return "Integer:\t\$intv\nFloat:  \t\$realv"
}
}

# HandleDefinition --
#    Define the macro based on the given command
#
# Arguments:
#    command     Command that represents a definition
# Return value:
#    The value of the expression.
#
proc ::Calculator::HandleDefinition { command } {

regexp {(^[A-Za-z_][A-Za-z0-9]*)\(([^)])\)=(.*)} \$command \
matched fname arg body
puts "\$matched"

regsub -all "\\\m(\$arg)\\\M" \$body "(\\$\$arg)" body
proc ::Functions::\$fname \$arg "expr \$body"
return
}

# ShowHelp --
#    Show basic help information
#
# Arguments:
#    None
# Return value:
#    None
#
proc ::Calculator::ShowHelp { } {
puts "
Calculator commands:
?/help     This overview
hex        Go into hex mode - convert hexadecimal numbers
into decimal ones
dec        Go into ordinary mode
quit       Leave the calculator

Calculations:

Defining functions:
f(a)=sin(a)/a
defines a function that takes one argument,
so:
and:
"
}

# main code --
#    In a loop, read the expressions and evaluate them
#
puts "Calculator:
Example (define a function):

>> f(a)=a*a
>> f(3)
9
>>1.0+2.0+3.0
6.0
(Use quit to exit the program, ? or help for online help)"

while { 1 } {
puts -nonewline \$::Calculator::prompt
flush stdout
gets stdin line

if { \$line == "quit" } {
break
} else {
if { [ catch {
puts [::Calculator::HandleCommand \$line]
} message ] != 0 } {
puts "Error: [lindex [split \$message "\n"] 0]"
}
}
}```