>> 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: # Improvements to be made: # - Nice interface to Tk (distinguish from Tcl) # - Run-time check for recursion # - On-line help # - Hexadecimal/decimal conversions # # 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: 1.0+3/4.0 gives the answer 1.75 sin(1) gives the answer 0.84147098... Defining functions: f(a)=sin(a)/a defines a function that takes one argument, so: f(1) gives the answer 0.84147098... and: f(2.0*3.0) gives the answer -0.0465692... 5*f(2) gives the answer 2.27324356... " } # 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]" } } }
AM (2 january 2008) I seem to be re-inventing this wheel every few years, but Tcl 8.5 makes it very easy:
# calculator.tcl -- # Basic programmable calculator # # Note: # It uses Tcl 8.5 characterstics to do the programmable bits # # Example: # (pi is a predefined constant) # f(a) = sin(2.0*a) # defines a function f that you can use like this: # pi+f(2.0) # to print the value of 3.1415... + sin(4.0) # # # We need Tcl 8.5, so be explicit about it # package require Tcl 8.5 # ReadLoop -- # Present a prompt and handle the command # # Arguments: # None # # Result: # None # # Side effects: # The command "q" or "quit" stops the program # proc ReadLoop {} { while {1} { puts -nonewline "(? for help) > " flush stdout set input [gets stdin] switch -re -- $input { "\\?" { PresentHelp } {^[A-Za-z ][A-Za-z _0-9()]*=[^=]} { HandleDefinition $input } "q" - "quit" { exit } default { puts [EvalExpression $input] } } } } # PresentHelp -- # Present help text # # Arguments: # None # # Result: # None # proc PresentHelp {} { puts " Simple calculator: >> 1+2 ==> 3 >> f(a) = sin(2.0*a) ==> function f defined >> f(1.0) 0.909297426826 >> pi = 3.14 ==> Type \"q\" or \"quit\"to stop the program" } # EvalExpression -- # Evaluate an expression # # Arguments: # expression Expression to be evaluated # # Result: # Value of the expression # proc EvalExpression {expression} { # # Prefix variable references with a $ # regsub -all {[A-Za-z][A-Za-z_0-9]*[^\(]} "$expression " {$\0} expression regsub -all {\$([A-Za-z][A-Za-z_0-9]*\()} "$expression" {\1} expression regsub -all {([0-9.])\$[eE]} "$expression" {\1e} expression if { [catch { set value [uplevel #0 expr [list $expression]] } msg] } { puts $msg #puts $::errorInfo return {} } return $value } # HandleDefinition -- # Define a variable or a function # # Arguments: # definition Definition to be handled # # Result: # None # proc HandleDefinition {definition} { # # Variable definition? # if { [regexp {\) *=} $definition] == 0 } { regexp {([A-Za-z][A-Za-z_0-9]*) *= *(.*)} $definition ==> varname value uplevel #0 set $varname [EvalExpression $value] } else { # # Function definition # regexp {([^=]+) *= *(.*)} $definition ==> function body set function [string map {" " "" ( " {" ) "} " , " "} $function] regsub -all {[A-Za-z][A-Za-z_0-9]*[^\(]} "$body " {$\0} body regsub -all {\$([A-Za-z][A-Za-z_0-9]*\()} "$body" {\1} body regsub -all {([0-9.])\$[eE]} "$body" {\1e} body uplevel #0 proc tcl::mathfunc::$function [list "expr {$body}"] } } # main -- # Get the thing going # set pi [expr {4.0*atan(1.0)}] ReadLoopI added an automatic result variable y, so that the result of any previous calculation can be reused. Moreover, I moved the help hint to display only upon start of the script. Little problem in Unix/Linux: cursor up does not return the previous readline content.AK: Have a look at Pure-tcl readline2 and linenoise. The issue is that you are using the stdin in cooked mode. You get the fully-edited line back on the [gets], but know nothing about cursor operations which were done. The aforementioned references are an (1) exposed read-loop in Tcl using stdin in raw-mode and handling all editing itself, making it possible to add a history, and (2) a C library with Tcl binding which does the same and exposes the blocks needed for a history. The Cmdr package and framework is user of the latter, i.e. linenoise.RJM: thanks, using package require tclreadline I modified the Readloop below and that works well:Further, I added a regsub in EvalExpression that interprets ^ as the pow() operator
# replace ^ by pow(...) constructs - at the cost of the xor operator regsub -all {(\(.+\)|([\w.]+))\^(\(.+\)|([\w.]+))} "$expression" {pow(\2,\3)} expression
proc ReadLoop {} { while {1} { set input [tclreadline::readline read "> "] switch -re -- $input { "\\?" { PresentHelp } {^[A-Za-z ][A-Za-z _0-9()]*=[^=]} { HandleDefinition $input } "q" - "quit" { exit } default { set ::y [EvalExpression $input] puts $::y } } } } puts "(? for help)"No need for complicated manipulations of the expressions and commands you type to get a user-defined function to work properly. Just make sure it is in the tcl::mathfunc namespace (relative to the current!) and [expr] takes care of the rest.