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 } } }