Version 38 of Variable Extensions (varx)

Updated 2016-11-09 23:57:07 by Napier

Variable Extensions (varx)

Napier (Dash Automation) - 11/09/2016

This page is dedicated to providing you guys with some of the variable-based procedures that we use heavily within our scripts. Each procedure in the package below are meant to provide convenient syntax for commonly required patterns that we run into when creating out scripts. We have put a fairly strong effort towards benchmarking with the development of each of these as we run our scripts on Systems that have 1/100th the processing power that most scripts would run on. I absolutely welcome your feedback, criticisms, and additions.

I will try to get the completely documentation and examples ready for you guys so that you can better understand how some of these procedures operate and how they may be able to provide the same level of convenience they do to us within almost every proc we write!

Some Notes

There are a few procedures which we utilize as dependencies within these scripts. They are simple and are mostly a means to allow us to do some inline documentation of examples and tests. As I can find the time I will absolutely attempt to convert them to the more standard approach taken by the community (or I welcome others to contribute if you feel so inclined). Below are the utility procedures that may be used throughout that should be copied over. They are great utilities mostly found within this wiki in other places.

proc callback args { ::tailcall ::namespace code $args }
proc rrange { min max } { ::try {::expr {int(rand()*($max-$min+1)+$min)}} }
proc bench { cmd } {
  set times [ list \
    [rrange 15000 99999] [rrange 10000 30000] [rrange 90000 99999] \
    [rrange 30000 80000] [rrange 10000 20000] [rrange 50000 90000]
  ]
  set result {}
  foreach n $times { lappend result $n [ time $cmd $n ] }
  puts "
    --------------------------------------------------------
      Benchmark Statistics
      
      Host:      [info hostname] [info nameofexecutable] [info script]
      Version:   [info patchlevel]
      Command:   $cmd
      
      Times Run        Results
  "
  dict for { n stats } $result {
    puts "      $n        $stats"
  }
  puts "
    --------------------------------------------------------
  "
}

Summary of Commands

::var::variables varName ...?varName? ...

# Summary:
#  ::var::variables is a convenience command which takes a list of namespace variable 
#  names and instantiates them within your procedure exactly like calling the variable 
#  command multiple times for each variable.
# Examples:
% {
  namespace eval foo {
    variable bar 1
    variable baz 2
    variable qux 3
  }
  proc ::foo::example {} {
    ::var::variables bar baz qux
    puts $bar        ; # % 1
    set qux foo
    # ... Do Fun Stuffs
  }
  ::foo::example
  puts $::foo::qux   ; # % foo
}

::var::define varName ...?varName? ...

# Summary:
#  ::var::define allows you to provide a list of variable names which should be
#  created within your procedure.  If the variable does not already exist it will 
#  create it with an empty value (set $var {}).  It returns the args that are 
#  passed to it.
# Examples:
% {
  proc ::foo::example {} {
    ::var::define foo bar baz
    puts $foo        ; # % {}
    puts $bar        ; # % {}
    puts $baz        ; # % {}
    # ... Do Fun Stuffs
  }
  ::foo::example
}

::var::const varName varValue

#############################################################################
# Summary:
#  ::var::const allows you to define a variable which can not be modified once 
#  an initial value has been set on it.  This can be useful in cases where you 
#  wish to build static values without worry another procedure or part of your 
#  current procedure may change the value.
#  This command returns the value that was set upon the const variable and thus 
#  may also be utilized as an identity procedure.
# Examples:
% {
  puts [ ::var::const foo bar ]   ; # % bar
  proc ::foo::example {} {
    global foo
    set foo baz
    puts $foo        ; # % bar
    # ... Do Fun Stuffs
  }
  set foo baz
  puts $foo          ; # % bar
  ::foo::example
}

::var::sets varName varValue ...?varName? ...varValue ...

#############################################################################
# Summary:
#  ::var::sets is a procedure which makes it easy to set the values of multiple
#  variables within a single line.  It is extremely useful when establishing default 
#  values as well as for making your procedures smaller and easier to read and maintain.
#  
#  In addition, this procedure will return a dict which contains the variables that were 
#  set and their values as the keys and values.
#############################################################################

# Examples:
% {  
  set dict [ ::var::sets foo bar baz qux ]
  puts $foo  ; % bar
  puts $baz  ; % qux
  puts $dict ; % {foo bar baz qux}
}
% {
  namespace eval foo {
    variable bar v1
    variable baz v2
  }
  proc ::foo::example {} {
    ::var::pipe variables foo baz | sets foo n1 baz n2 myVar test
    puts $myVar    ;  # % test
    # ... Do Fun Stuffs
  }
  ::foo::example
  puts $::foo::bar   ; # % n1
  puts $::foo::baz   ; # % n2
}
# --------------------------------------------------------
  # Benchmark Tests (C9.com Virtual Machine i386 Ubuntu )
  # --------------------------------------------------------
  #  23292  { 1.2550231839258115 microseconds per iteration } 
  #  187150 { 1.303157894736842  microseconds per iteration } 
  #  14484  { 1.2782380557856945 microseconds per iteration } 
  #  193029 { 1.2582617119707402 microseconds per iteration } 
  #  22163  { 1.2419798763705274 microseconds per iteration } 
  #  85368  { 1.2508551213569488 microseconds per iteration }
  # --------------------------------------------------------
% {
  set times [ list [rrange 15000 100000] [rrange 100000 250000] [rrange 10000 15000]  [rrange 70000 200000] [rrange 15000 25000]  [rrange 50000 100000] ]
  set result {}
  foreach n $times {
    lappend result $n [ time { set dict [ ::var::sets myVar "this" another 0 test "that" ] } $n ]
  }
}

::var::switch varValue ...?globPattern? ...matchResponse ...

#############################################################################
# Summary:
#  ::var::switch is very similar to a regular switch command except that instead 
#  of executing a script when a match is found, we will simply return the value.
#  When using ::var::switch, in contrast to a regular switch command, the -glob
#  style matching is inferred.  You may optionally provide the -nocase parameter
#  if matching should be case-insensitive.
#  You may also define a response value of "-" to automatically match the next
#  response until a value value is matched.  This does provide the side effect
#  that returning a value of "-" becomes problematic.  In the case "-" never finds
#  a valid match down the line a response of "-" will be provided instead.
#  
# Examples:
% {
  proc ::foo::example { val } {
    return [ ::var::switch $val f* 1 b*r 2 *z 3 dog - q* 4 * 5 ]
  }
  puts [ ::foo::example foo ] ; # % 1
  puts [ ::foo::example bar ] ; # % 2
  puts [ ::foo::example baz ] ; # % 3
  puts [ ::foo::example qux ] ; # % 4
  puts [ ::foo::example dog ] ; # % 4
  puts [ ::foo::example ham ] ; # % 5
  puts [ ::foo::example ran ] ; # % 5
  puts [ ::foo::example taz ] ; # % 3
  puts [ ::foo::example fan ] ; # % 1
}

The Script

namespace eval var {}

proc ::var::callback args { ::tailcall ::namespace code $args }

proc ::var::variables args { ::foreach var $args { ::uplevel 1 [::list ::variable $var] } }

proc ::var::define args { 
  ::foreach var $args {
    ::upvar 1 $var v
    ::if { ! [::info exists v] } { ::set v {} }
  }
  ::return $args
}

proc ::var::sets args {
  ::foreach {var val} $args {
    ::upvar 1 $var ref 
    ::set ref $val
  }
  ::return $args
}

proc ::var::const {name value} {
  ::uplevel 1 [::list ::set $name $value]
  ::uplevel 1 [::list ::trace variable $name w "::set $name [::list $value];#"]
  ::return $value
}

proc ::var::switch args {
  ::set nocase 0; ::set next 0; ::set response {}
  ::foreach arg $args {
    ::set args [::lassign $args var]
    ::if { ! [::string match -* $arg] } { ::break }
    ::if { $arg eq "-nocase" } { ::set nocase 1 }
  }
  ::foreach { pattern response } $args {
    ::if { $next } { ::if { $response eq "-" } { ::continue } else { ::return $response } }
    ::if { $nocase ? [::string match -nocase $pattern $var] : [::string match $pattern $var]} {
      ::if { $response eq "-" } { ::set next 1; ::continue }
      ::return $response
    }
  }
  ::return $response
}

proc ::var::trace { var callback {value {}} } {
  ::upvar 1 $var current
  ::if {![::info exists current]} {::set current $value}
  ::uplevel 1 [::list ::trace add variable $var write [callback traceback $callback $current]]
}

proc ::var::traceback { callback prev var args } {
  ::upvar 1 $var value
  ::uplevel 1 [::list ::trace remove variable $var write [callback traceback $callback $prev ]]
  ::uplevel 1 [::list ::trace add    variable $var write [callback traceback $callback $value]]
  ::set a [::info args [::lindex $callback 0]]
  ::if { "args" in $a } {
    ::set args [::list $value $prev $var]
  } else {
    ::switch -- [::llength $a] {
      0 { ::set args {}                               }
      1 { ::set args [::list $value]                  }
      2 { ::set args [::list $value $prev]            }
      3 { ::set args [::list $value $prev $var]       }
      default { ::throw error {wrong # args: should be "", "value", "value prevValue", or "value prevValue varName"} }
    }
  }
  ::uplevel #0 [::list eval $callback $args]
}

proc ::var::untrace { var } {
  ::upvar 1 $var $var
  ::set info [::trace info variable $var]
  ::if { $info eq {} } { ::return }
  ::uplevel 1 [::list ::trace remove variable $var {*}[::lindex $info 0]]
}

# ::var::variables bar baz | sets bar n1 baz n2 myVar myValue
# ::var::pipe variables bar baz | sets bar n1 baz n2 myVar myValue
proc ::var::pipe args {
  ::set cmds [::split $args |]
  ::foreach cmd $cmds {
    ::uplevel 1 [::list try [::namespace current]::[::string trim $cmd]]
  }
}

proc ::var::alias { var alias } { ::uplevel 1 [::list ::upvar 0 $var $alias] }

proc ::var::empty { varName args } {
  ::upvar 1 $varName var
  ::tailcall ::if [::expr {![::info exists var] || $var eq {}}] {*}$args
}

proc var::null {varName args} {
  ::upvar 1 $varName var
  ::tailcall ::if [::expr {![::info exists var]}] {*}$args
}

proc var::false {varName args} {
  ::upvar 1 $varName var
  ::tailcall ::if [::expr {[::info exists var] && [::string is false -strict $var]}] {*}$args
}

proc var::true {varName args} {
  ::upvar 1 $varName var
  ::tailcall ::if [::expr {[::info exists var] && [::string is true -strict $var]}] {*}$args
}