## ******************************************************** ## stack.tcl version 2.0 ## ## Provides stack-like functions for tcl. ## ## Release Date: 00.04.07. ## ## In all of these functions, a Tcl list is handled like ## a stack. ## ## The caller wishing to use these functions without ## qualifiying the names with stack:: should include the ## line: ## namespace import stack::*. ## after requiring or sourcing this code. Possibly ## qualfying this with: ## namespace forget stack::errorTest. ## ## When speed is more important than exception handling ## the variable "stack::nodebug" can be set to "1" and ## things will go somewhat faster. ## ## When debugging IS enabled, the CALLING function must ## be caught to catch the uplevel'd exceptions. ## ******************************************************** ;#barecode package provide stack 1.0 namespace eval stack { variable nodebug 0 } ## ******************************************************** ## Name: stack::pop ## ## Description: ## Pop items from the "top" of the list/stack. ## "n" is the number of elements to pop. ## Popped items are removed from the list and ## returned. proc stack::pop { { stack "" } { n 1 } } { set s [ uplevel 1 [ list set $stack ] ] stack::errorTest decr n set data [ lrange $s 0 $n ] incr n set s [ lrange $s $n end ] uplevel 1 [ list set $stack $s ] set data } ## ******************************************************** ## ******************************************************** ## Name: stack::push ## ## Description: ## Push items onto the top of the list/stack. ## "args" is a special Tcl variable which collects all ## arguments to a proc which are not explicitly named. proc stack::push { { stack "" } { args "" } } { set s [ uplevel 1 [ list set $stack ] ] stack::errorTest uplevel 1 [ list set $stack [ concat $args $s ] ] } ## ******************************************************** ## ******************************************************** ## Name: stack::shift ## ## Description: ## Shift items onto bottom of list/stack. proc stack::shift { { stack "" } { args "" } } { set s [ uplevel 1 [ list set $stack ] ] stack::errorTest uplevel 1 [ list set $stack [ concat $s $args ] ] } ## ******************************************************** ## ******************************************************** ## Name: stack::unshift ## ## Description: ## Unshifts items from the bottom of the list/stack. ## Unshifted items are removed from the list and returned. proc stack::unshift { { stack "" } { n 1 } } { set s [ uplevel 1 [ list set $stack ] ] stack::errorTest set data [ lrange $s end-[ expr { $n - 1 } ] end ] uplevel 1 [ list set $stack [ lrange $s 0 end-$n ] ] set data } ## ******************************************************** ## ******************************************************** ## Name: stack::prune ## ## Description: ## Prunes a list/stack based on a regular expression. ## "n" here refers to the number of items to associate ## into a group for regexp processing. ## Useful for things like queues where a key is associated ## with a number of entries and you want to strip out all ## entries based on a key. ## Pruned values are removed from the list/stack and ## returned. proc stack::prune { { stack "" } { regex "" } { n 1 } } { set s [ uplevel 1 [ list set $stack ] ] stack::errorTest set twigs [ list ] set i 0 while { 1 } { ;## use -regexp in case items are lists themselves set i [ lsearch -regexp $s $regex ] if { $i < 0 } { break } set j [ expr {$i + $n - 1} ] set data [ lrange $s $i $j ] set twigs [ concat $twigs $data ] set s [ lreplace $s $i $j ] } uplevel 1 [ list set $stack $s ] set twigs } ## ******************************************************** ## ******************************************************** ## ## Name: stack::circB ## ## Description: ## Cause a stack to behave like a circular buffer, or ## like a "history" buffer. ## This function and stack::circF are complementary, ## enabling "forward" and "backward" circulation. proc stack::circB { { stack "" } { n 1 } } { set s [ uplevel 1 [ list set $stack ] ] stack::errorTest for { set data [ list ] } { $n > 0 } { decr n } { set data [ stack::unshift s ] stack::push s $data } uplevel 1 [ list set $stack $s ] } ## ******************************************************** ## ******************************************************** ## ## Name: stack::circF ## ## Description: ## Causes a stack to behave like a circular buffer, or ## like a "history" buffer. ## This function and stack::circB are complementary, ## enabling "forward" and "backward" circulation. proc stack::circF { { stack "" } { n 1 } } { set s [ uplevel 1 [ list set $stack ] ] stack::errorTest for { set data [ list ] } { $n > 0 } { decr n } { set data [ stack::pop s ] eval [ list stack::shift s ] $data } uplevel 1 [ list set $stack $s ] } ## ******************************************************** ## ******************************************************** ## ## Name: stack::flip ## ## Description: ## Reverses the order of elements in a stack or list. proc stack::flip { { stack "" } } { set s [ uplevel 1 [ list set $stack ] ] stack::errorTest set rev [ list ] set i 0 set length [ llength $s ] while { $i < $length } { set rev [ concat $rev [ lindex $s end-$i ] ] incr i } uplevel 1 [ list set $stack $rev ] } ## ******************************************************** ## ******************************************************** ## ## Name: stack::shuffle ## ## Description: ## Randomly reorder items in a list. proc stack::shuffle { { stack "" } } { set s [ uplevel 1 [ list set $stack ] ] stack::errorTest set deck [ list ] expr srand([ clock clicks ]) for { set length [ llength $s ] } { $length > 0 } { decr length } { set i [ expr {int ( rand() * $length )} ] lappend deck [lindex $s $i] set s [ lreplace $s $i $i ] } uplevel 1 [ list set $stack $deck ] } ## ******************************************************** ## ******************************************************** ## Name: stack::getItem ## ## Description: ## Retrieves an item from stack and returns a list of the ## index of the item and the item itself. proc stack::getItem { { stack "" } { regex "" } { n 1 } } { set s [ uplevel 1 [ list set $stack ] ] stack::errorTest set i [ lsearch -regexp $s $regex ] if { $i < 0 } { return [ list -1 [ list ] ] } set j [ expr {$i + $n - 1} ] set data [ lrange $s $i $j ] return [ list $i $data ] } ## ******************************************************** ## ******************************************************** ## Name: stack::updateItem ## ## Description: ## Replace an item from stack. ## Use getItem to locate item ## note that an lreplace on index -1 causes a push! proc stack::updateItem { { stack "" } { index -1 } { newitem "" } } { if { $index < 0 } { return } set s [ uplevel 1 [ list set $stack ] ] stack::errorTest set s [ lreplace $s $index $index $newitem ] uplevel 1 [ list set $stack $s ] } ## ******************************************************** ## ******************************************************** ## ## Name: stack::errorTest ## ## Description: ## Error tests for stack validity. Not rigorous. ## Tests done in level of caller. proc stack::errorTest {} { if { $stack::nodebug } { return {} } uplevel 1 { if { ! [ info exists stack ] } { return -code error "stack::errorTest called externally." } elseif { [ info exists n ] && [ regexp { [^0-9] } $n ] } { return -code error "Second argument must be integer." } elseif { [ info exists args ] && ! [ string length $args ] } { return -code error "Empty argument string passed." } elseif { ! [ string length $stack ] } { return -code error "No stack name given." } elseif { ! [ llength $s ] } { if { ! [ regexp {:push|:shift} [ stack::myName ] ] } { return -code error "Stack \"$stack\" exhausted." } } } } ## ******************************************************** ## ******************************************************** ## ## Name: stack::myName ## ## Description: ## Returns the name of the calling procedure. Does this ## by parsing level info. Level -1 is the immediate ## caller, level -2 is the caller of the caller, etc. proc stack::myName { { level "-1" } } { if { [ catch { set name [ lindex [ info level $level ] 0 ] } err ] } { if { [ info exists ::API ] } { set name $::API } else { set name unknown_caller } } set name } ## ******************************************************** ## ******************************************************** ## ## Name: decr ## ## Description: ## Decrement function, analog for incr. ## ## Parameters: ## ## Usage: ## ## Comments: ## Sign convention is correct relative to incr. proc decr { int { n 1 } } { if { [ catch { uplevel incr $int -$n } err ] } { return -code error "decr: $err" } } ## ******************************************************** ---- With regard to ''shuffle'' above, see [Shuffle a list]. Timing results are available too through that page. KBK: Thanks for the plug. 8-) ---- [Category Package]