# queue.tcl -- # # Queue implementation for Tcl. # # Copyright (c) 1998-2000 by Ajuba Solutions. # Copyright (c) 2008-2010 Andreas Kupries # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # # RCS: @(#) $Id: queue_tcl.tcl,v 1.2 2010/03/24 06:13:00 andreas_kupries Exp $ namespace eval ::struct::queue { # counter is used to give a unique name for unnamed queues variable counter 0 # Only export one command, the one used to instantiate a new queue namespace export queue_tcl } # ::struct::queue::queue_tcl -- # # Create a new queue with a given name; if no name is given, use # queueX, where X is a number. # # Arguments: # name name of the queue; if null, generate one. # # Results: # name name of the queue created proc ::struct::queue::queue_tcl {args} { variable I::qat variable I::qret variable I::qadd variable counter switch -exact -- [llength [info level 0]] { 1 { # Missing name, generate one. incr counter set name "queue${counter}" } 2 { # Standard call. New empty queue. set name [lindex $args 0] } default { # Error. return -code error \ "wrong # args: should be \"queue ?name?\"" } } # FIRST, qualify the name. if {![string match "::*" $name]} { # Get caller's namespace; append :: if not global namespace. set ns [uplevel 1 [list namespace current]] if {"::" != $ns} { append ns "::" } set name "$ns$name" } if {[llength [info commands $name]]} { return -code error \ "command \"$name\" already exists, unable to create queue" } # Initialize the queue as empty set qat($name) 0 set qret($name) [list] set qadd($name) [list] # Create the command to manipulate the queue interp alias {} $name {} ::struct::queue::QueueProc $name return $name } ########################## # Private functions follow # ::struct::queue::QueueProc -- # # Command that processes all queue object commands. # # Arguments: # name name of the queue object to manipulate. # args command name and args for the command # # Results: # Varies based on command to perform if {[package vsatisfies [package provide Tcl] 8.5]} { # In 8.5+ we can do an ensemble for fast dispatch. proc ::struct::queue::QueueProc {name cmd args} { # Shuffle method to front and then simply run the ensemble. # Dispatch, argument checking, and error message generation # are all done in the C-level. I $cmd $name {*}$args } namespace eval ::struct::queue::I { namespace export clear destroy get peek \ put unget size namespace ensemble create } } else { # Before 8.5 we have to code our own dispatch, including error # checking. proc ::struct::queue::QueueProc {name cmd args} { # Do minimal args checks here if { [llength [info level 0]] == 2 } { return -code error "wrong # args: should be \"$name option ?arg arg ...?\"" } # Split the args into command and args components if { [llength [info commands ::struct::queue::I::$cmd]] == 0 } { set optlist [lsort [info commands ::struct::queue::I::*]] set xlist {} foreach p $optlist { set p [namespace tail $p] if {($p eq "K") || ($p eq "Shift") || ($p eq "Shift?")} continue lappend xlist $p } set optlist [linsert [join $xlist ", "] "end-1" "or"] return -code error \ "bad option \"$cmd\": must be $optlist" } uplevel 1 [linsert $args 0 ::struct::queue::I::$cmd $name] } } namespace eval ::struct::queue::I { # The arrays hold all of the queues which were made. variable qat ; # Index in qret of next element to return variable qret ; # List of elements waiting for return variable qadd ; # List of elements added and not yet reached for return. } # ::struct::queue::I::clear -- # # Clear a queue. # # Arguments: # name name of the queue object. # # Results: # None. proc ::struct::queue::I::clear {name} { variable qat variable qret variable qadd set qat($name) 0 set qret($name) [list] set qadd($name) [list] return } # ::struct::queue::I::destroy -- # # Destroy a queue object by removing it's storage space and # eliminating it's proc. # # Arguments: # name name of the queue object. # # Results: # None. proc ::struct::queue::I::destroy {name} { variable qat ; unset qat($name) variable qret ; unset qret($name) variable qadd ; unset qadd($name) interp alias {} $name {} return } # ::struct::queue::I::get -- # # Get an item from a queue. # # Arguments: # name name of the queue object. # count number of items to get; defaults to 1 # # Results: # item first count items from the queue; if there are not enough # items in the queue, throws an error. proc ::struct::queue::I::get {name {count 1}} { if { $count < 1 } { error "invalid item count $count" } elseif { $count > [size $name] } { error "insufficient items in queue to fill request" } Shift? $name variable qat ; upvar 0 qat($name) AT variable qret ; upvar 0 qret($name) RET variable qadd ; upvar 0 qadd($name) ADD if { $count == 1 } { # Handle this as a special case, so single item gets aren't # listified set item [lindex $RET $AT] incr AT Shift? $name return $item } # Otherwise, return a list of items if {$count > ([llength $RET] - $AT)} { # Need all of RET (from AT on) and parts of ADD, maybe all. set max [expr {$count - ([llength $RET] - $AT) - 1}] set result [concat [lrange $RET $AT end] [lrange $ADD 0 $max]] Shift $name set AT $max } else { # Request can be satisified from RET alone. set max [expr {$AT + $count - 1}] set result [lrange $RET $AT $max] set AT $max } incr AT Shift? $name return $result } # ::struct::queue::I::peek -- # # Retrieve the value of an item on the queue without removing it. # # Arguments: # name name of the queue object. # count number of items to peek; defaults to 1 # # Results: # items top count items from the queue; if there are not enough items # to fulfill the request, throws an error. proc ::struct::queue::I::peek {name {count 1}} { variable queues if { $count < 1 } { error "invalid item count $count" } elseif { $count > [size $name] } { error "insufficient items in queue to fill request" } Shift? $name variable qat ; upvar 0 qat($name) AT variable qret ; upvar 0 qret($name) RET variable qadd ; upvar 0 qadd($name) ADD if { $count == 1 } { # Handle this as a special case, so single item pops aren't # listified return [lindex $RET $AT] } # Otherwise, return a list of items if {$count > [llength $RET] - $AT} { # Need all of RET (from AT on) and parts of ADD, maybe all. set over [expr {$count - ([llength $RET] - $AT) - 1}] return [concat [lrange $RET $AT end] [lrange $ADD 0 $over]] } else { # Request can be satisified from RET alone. return [lrange $RET $AT [expr {$AT + $count - 1}]] } } # ::struct::queue::I::put -- # # Put an item into a queue. # # Arguments: # name name of the queue object # args items to put. # # Results: # None. proc ::struct::queue::I::put {name args} { variable qadd if { [llength $args] == 0 } { error "wrong # args: should be \"$name put item ?item ...?\"" } foreach item $args { lappend qadd($name) $item } return } # ::struct::queue::I::unget -- # # Put an item into a queue. At the _front_! # # Arguments: # name name of the queue object # item item to put at the front of the queue # # Results: # None. proc ::struct::queue::I::unget {name item} { variable qat ; upvar 0 qat($name) AT variable qret ; upvar 0 qret($name) RET if {![llength $RET]} { set RET [list $item] } elseif {$AT == 0} { set RET [linsert [K $RET [unset RET]] 0 $item] } else { # step back and modify return buffer incr AT -1 set RET [lreplace [K $RET [unset RET]] $AT $AT $item] } return } # ::struct::queue::I::size -- # # Return the number of objects on a queue. # # Arguments: # name name of the queue object. # # Results: # count number of items on the queue. proc ::struct::queue::I::size {name} { variable qat variable qret variable qadd return [expr { [llength $qret($name)] + [llength $qadd($name)] - $qat($name) }] } # ### ### ### ######### ######### ######### proc ::struct::queue::I::Shift? {name} { variable qat variable qret if {$qat($name) < [llength $qret($name)]} return Shift $name return } proc ::struct::queue::I::Shift {name} { variable qat variable qret variable qadd set qat($name) 0 set qret($name) $qadd($name) set qadd($name) [list] return } proc ::struct::queue::I::K {x y} { set x } # ### ### ### ######### ######### ######### ## Ready namespace eval ::struct { # Get 'queue::queue' into the general structure namespace for # pickup by the main management. namespace import -force queue::queue_tcl }