Updated 2011-06-21 11:40:58 by RLE
 # exprlib.tcl --
 #
 #       explode the functionality of [expr] into prefix-style procs.
 #
 #       e.g. from: set x [expr {42*14}]
 #            to:   set x [* 42 14]
 #
 # author: Glenn Jackman
 #         http://www.purl.org/net/glennj/
 #
 # references
 #       http://groups.google.com/groups?threadm=19b0c689.0107082126.67e6cf9a%40posting.google.com
 #       http://wiki.tcl.tk/expr
 #
 # Performance penalty: Using this library incurs significant penalties for
 # mathematically significant applications.  Consider:
 #
 #       % time {set pi [expr {atan(1)*4}]} 10000
 #       16 microseconds per iteration
 #       % package require exprlib; namespace import ::exprlib::*
 #       % time {set pi [* [atan 1] 4]} 10000
 #       267 microseconds per iteration

 package provide exprlib 0.1
 package require Tcl 8.0

 namespace eval ::exprlib {
     # unary and binary operators
     namespace export ! ~ + - * / % << >> < > <= >= == != eq ne & ^ | && ||

     # additional english operators
     namespace export and or not

     # math functions available since tcl 8.0
     namespace export abs double int round srand

     # functions added in tcl 8.4
     if {$::tcl_version >= 8.4} {
         namespace export wide
     }
     # functions added in tcl 8.3
     if {$::tcl_version >= 8.3} {
         namespace export acos asin atan atan2 ceil cos cosh exp
         namespace export floor fmod hypot log log10 pow sin sinh
         namespace export sqrt tan tanh
     }

     # rand has been augmented to accept lower and upper bounds
     namespace export rand

     # assignment operators
     namespace export += -= *= /= %= &= |= ^= <<= >>= &&= ||=
 }

 # unary operators
 #       !       logical not
 #       ~       bitwise not
 foreach op {! ~} {
     proc ::exprlib::$op arg "expr { $op \$arg }"
 }

 # "stretch" these binary operators
 #       +       add, also unary plus
 #       *       multiply
 #       &&      logical and
 #       ||      logical or
 foreach op {+ * && ||} {
     proc ::exprlib::$op args "expr \[join \$args $op]"
 }

 # - is special because it can be unary or binary
 #       -       subtract, unary minus
 proc ::exprlib::- {arg1 {arg2 ""}} {
     expr { ($arg2 eq "") ? (- $arg1) : ($arg1 - $arg2) }
 }

 # true binary operators
 #       /       divide
 #       %       remainder
 #       <<      left shift
 #       >>      right shift
 #       <       boolean less than
 #       >       boolean greater than
 #       <=      boolean less than or equal to
 #       >=      boolean greater than or equal to
 #       ==      boolean equal
 #       !=      boolean not equal
 #       eq      string equal
 #       ne      string not equal
 #       &       bitwise and
 #       ^       bitwise xor
 #       |       bitwise or
 foreach op {/ % << >> < > <= >= == != & ^ |} {
     proc ::exprlib::$op {arg1 arg2} "expr { \$arg1 $op \$arg2 }"
 }

 if {$::tcl_version >= 8.4} {
     foreach op {eq ne} {
         proc ::exprlib::$op {arg1 arg2} "expr { \$arg1 $op \$arg2 }"
     }
 } else {
     proc ::exprlib::eq {arg1 arg2} {
         expr {[string compare $arg1 $arg2] == 0}
     }
     proc ::exprlib::ne {arg1 arg2} {
         expr {[string compare $arg1 $arg2] != 0}
     }
 }

 # english operators
 #       and     logical and
 #       or      logical or
 #       not     logical not
 foreach {srcCmd targetCmd} {&& and || or ! not} {
     proc ::exprlib::$targetCmd args "eval ::exprlib::$srcCmd \$args"
 }

 ################################################################################
 # math functions
 #
 # with tcl 8.4 we can introspect the math functions with [info functions]
 # but that does not tell us the number of arguments each takes.  We'd be
 # exposed to vague error messages "too many/few arguments for math function"

 foreach func { abs double int round srand } {
     proc ::exprlib::$func arg "expr { ${func}(\$arg) }"
 }
 if {$::tcl_version >= 8.4} {
     proc ::exprlib::wide arg {expr { wide($arg) }}
 }
 if {$::tcl_version >= 8.3} {
     foreach func { acos asin atan ceil cos cosh exp floor
                    log log10 sin sinh sqrt tan tanh } {
         proc ::exprlib::$func arg "expr { ${func}(\$arg) }"
     }
     foreach func { atan2 fmod hypot pow } {
         proc ::exprlib::$func {arg1 arg2} "expr { ${func}(\$arg1,\$arg2) }"
     }
 }

 # return a pseudo-random number in the range [lowerBound, upperBound)
 proc ::exprlib::rand {{lowerBound 0} {upperBound 1}} {
     expr { (rand() * ($upperBound - $lowerBound)) + $lowerBound }
 }

 ################################################################################
 # assignment operators

 #       ++      autoincrement
 proc ::exprlib::++ varname {
     upvar 1 $varname var
     if {[info exists var]} {incr var} {set var 1}
 }

 #       --      autodecrement
 proc ::exprlib::-- varname {
     upvar 1 $varname var
     if {[info exists var]} {incr var -1} {set var -1}
 }

 #       +=      add
 proc ::exprlib::+= {varname value} {
     upvar 1 $varname var
     if {[info exists var]} {incr var $value} {set var $value}
 }

 #       -=      subtract
 proc ::exprlib::-= {varname value} {
     upvar 1 $varname var
     if {[info exists var]} {incr var [- $value]} {set var [- $value]}
 }

 #       *=      multiply
 #       /=      divide
 #       %=      modulus
 #       <<=     left shift
 #       >>=     right shift
 #       &=      bitwise and
 #       |=      bitwise or
 #       ^=      bitwise xor
 foreach op {*= /= %= <<= >>= &= |= ^=} {
     proc ::exprlib::$op {varname value} "
         upvar 1 \$varname var
         set var \[[string range $op 0 [expr {[string length $op]-2}]] \$var \$value]
     "
 }

 # the perl &&= and ||= operators
 #       &&=     assign to var if the current value of var is true
 #       ||=     assign to var if the current value of var is false or unset
 # (These may be somewhat less useful in Tcl, as Perl has a different
 # notion of truth values.)
 proc ::exprlib::&&= {varname value} {
     upvar 1 $varname var
     if {[info exists var]} {
         if {$var} {set var $value} {set var}
     }
     # return nothing if var is unset?
 }
 proc ::exprlib::||= {varname value} {
     upvar 1 $varname var
     if {[info exists var] && $var} {set var} {set var $value}
 }

 ################################################################################
 proc ::exprlib::_introspect {} {
     foreach p [lsort [info procs [namespace current]::*]] {
         puts "proc $p [list [info args $p]] [list [info body $p]]"
     }
 }