## ********************************************************
## 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-)

