Arjen Markus 2010-03-22: Every so often I seem to reinvent a calculator program, largely because none of them really do the job comfortably. The infix package solves the hardest problem out-of-the-box: parsing the arithmetic expressions and turning them into something that can be evaluated. So, using that package, here is a new attempt ... far from complete, but it does do the job.
Lars H: Rather than calling the infix command (which will cache the compiled form of all expressions it evaluates), you may prefer to call the underlying infix::core::compile command. Then instead of
set rc [catch {
set R [infix $variables $cmd]
} msg]
#I would say something like
set rc [catch {
set R [namespace eval ::values [infix::core::compile ::values $cmd]]
} msg]The "infix variables" will then be entries of the ::values::V array.
# calc.tcl --
# Use the infix package as a basis for a desktop calculator program
# like "bc"
#
# To do:
# - Define functions
# - Introduce bigfloat package
#
package require infix
# values --
# Namespace to run the commands in, stores the variables
#
namespace eval ::values {
variable R ""
::infix::core::setup base numconst expr::ops expr::fun expr::ternary
set ::variables "R <-> ::values::R"
}
# runCommand --
# Run the infix command, keeping track of variables
#
# Arguments:
# cmd Command to run
#
# Result:
# None
#
# Side effects:
# Prints the answer (stored in R) and sets any variables
#
proc ::values::runCommand {cmd} {
global variables
variable R
set var ""
if { [regexp {([a-zA-Z_][a-zA-Z_0-9]*) *:?=} $cmd ==> var] } {
if { [lsearch $variables $var] < 0 } {
variable $var
lappend variables $var -> ::values::$var
}
}
set rc [catch {
set R [infix $variables $cmd]
} msg]
if { $rc != 0 } {
puts [lindex [split $::errorInfo \n] 0]
} else {
lset variables end-1 <->
puts $R
}
}
# main --
# Read the command, evaluate and print
#
while {1} {
puts -nonewline "> "
flush stdout
gets stdin line
set cmd ""
while { [string index [string trim $line] end] == "\\" } {
append cmd " [string range [string trim $line] 0 end-1]"
gets stdin line
}
append cmd " $line"
switch -glob -- $cmd {
" quit" -
" exit" {
exit
}
"scale *" {
set newscale [lindex $cmd 1]
if { [string is -strict integer $newscale] } {
set scale newscale
} else {
puts "Scale must be an integer"
}
}
default {
::values::runCommand $cmd
}
}
}