# tree.tcl --
#
# Implementation of a tree data structure for Tcl.
#
# Copyright (c) 1998-2000 by Ajuba Solutions.
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
# RCS: @(#) $Id: tree1.tcl,v 1.5 2005/10/04 17:15:05 andreas_kupries Exp $
package require Tcl 8.2
namespace eval ::struct {}
namespace eval ::struct::tree {
# Data storage in the tree module
# -------------------------------
#
# There's a lot of bits to keep track of for each tree:
# nodes
# node values
# node relationships
#
# It would quickly become unwieldy to try to keep these in arrays or lists
# within the tree namespace itself. Instead, each tree structure will get
# its own namespace. Each namespace contains:
# children array mapping nodes to their children list
# parent array mapping nodes to their parent node
# node:$node array mapping keys to values for the node $node
# counter is used to give a unique name for unnamed trees
variable counter 0
# Only export one command, the one used to instantiate a new tree
namespace export tree
}
# ::struct::tree::tree --
#
# Create a new tree with a given name; if no name is given, use
# treeX, where X is a number.
#
# Arguments:
# name Optional name of the tree; if null or not given, generate one.
#
# Results:
# name Name of the tree created
proc ::struct::tree::tree {{name ""}} {
variable counter
if {[llength [info level 0]] == 1} {
incr counter
set name "tree${counter}"
}
# FIRST, qualify the name.
if {![string match "::*" $name]} {
# Get caller's namespace; append :: if not global namespace.
set ns [uplevel 1 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 tree"
}
# Set up the namespace for the object,
# identical to the object command.
namespace eval $name {
# Set up root node's child list
variable children
set children(root) [list]
# Set root node's parent
variable parent
set parent(root) [list]
# Set up the node attribute mapping
variable attribute
array set attribute {}
# Set up a counter for use in creating unique node names
variable nextUnusedNode
set nextUnusedNode 1
# Set up a counter for use in creating node attribute arrays.
variable nextAttr
set nextAttr 0
}
# Create the command to manipulate the tree
interp alias {} ::$name {} ::struct::tree::TreeProc $name
return $name
}
##########################
# Private functions follow
# ::struct::tree::TreeProc --
#
# Command that processes all tree object commands.
#
# Arguments:
# name Name of the tree object to manipulate.
# cmd Subcommand to invoke.
# args Arguments for subcommand.
#
# Results:
# Varies based on command to perform
proc ::struct::tree::TreeProc {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
set sub _$cmd
if { [llength [info commands ::struct::tree::$sub]] == 0 } {
set optlist [lsort [info commands ::struct::tree::_*]]
set xlist {}
foreach p $optlist {
set p [namespace tail $p]
lappend xlist [string range $p 1 end]
}
set optlist [linsert [join $xlist ", "] "end-1" "or"]
return -code error \
"bad option \"$cmd\": must be $optlist"
}
return [uplevel 1 [linsert $args 0 ::struct::tree::$sub $name]]
}
# ::struct::tree::_children --
#
# Return the child list for a given node of a tree.
#
# Arguments:
# name Name of the tree object.
# node Node to look up.
#
# Results:
# children List of children for the node.
proc ::struct::tree::_children {name node} {
if { ![_exists $name $node] } {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
variable ${name}::children
return $children($node)
}
# ::struct::tree::_cut --
#
# Destroys the specified node of a tree, but not its children.
# These children are made into children of the parent of the
# destroyed node at the index of the destroyed node.
#
# Arguments:
# name Name of the tree object.
# node Node to look up and cut.
#
# Results:
# None.
proc ::struct::tree::_cut {name node} {
if { [string equal $node "root"] } {
# Can't delete the special root node
return -code error "cannot cut root node"
}
if { ![_exists $name $node] } {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
variable ${name}::parent
variable ${name}::children
# Locate our parent, children and our location in the parent
set parentNode $parent($node)
set childNodes $children($node)
set index [lsearch -exact $children($parentNode) $node]
# Excise this node from the parent list,
set newChildren [lreplace $children($parentNode) $index $index]
# Put each of the children of $node into the parent's children list,
# in the place of $node, and update the parent pointer of those nodes.
foreach child $childNodes {
set newChildren [linsert $newChildren $index $child]
set parent($child) $parentNode
incr index
}
set children($parentNode) $newChildren
KillNode $name $node
return
}
# ::struct::tree::_delete --
#
# Remove a node from a tree, including all of its values. Recursively
# removes the node's children.
#
# Arguments:
# name Name of the tree.
# node Node to delete.
#
# Results:
# None.
proc ::struct::tree::_delete {name node} {
if { [string equal $node "root"] } {
# Can't delete the special root node
return -code error "cannot delete root node"
}
if { ![_exists $name $node] } {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
variable ${name}::children
variable ${name}::parent
# Remove this node from its parent's children list
set parentNode $parent($node)
set index [lsearch -exact $children($parentNode) $node]
set children($parentNode) [lreplace $children($parentNode) $index $index]
# Yes, we could use the stack structure implemented in ::struct::stack,
# but it's slower than inlining it. Since we don't need a sophisticated
# stack, don't bother.
set st [list]
foreach child $children($node) {
lappend st $child
}
KillNode $name $node
while { [llength $st] > 0 } {
set node [lindex $st end]
set st [lreplace $st end end]
foreach child $children($node) {
lappend st $child
}
KillNode $name $node
}
return
}
# ::struct::tree::_depth --
#
# Return the depth (distance from the root node) of a given node.
#
# Arguments:
# name Name of the tree.
# node Node to find.
#
# Results:
# depth Number of steps from node to the root node.
proc ::struct::tree::_depth {name node} {
if { ![_exists $name $node] } {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
variable ${name}::parent
set depth 0
while { ![string equal $node "root"] } {
incr depth
set node $parent($node)
}
return $depth
}
# ::struct::tree::_destroy --
#
# Destroy a tree, including its associated command and data storage.
#
# Arguments:
# name Name of the tree to destroy.
#
# Results:
# None.
proc ::struct::tree::_destroy {name} {
namespace delete $name
interp alias {} ::$name {}
}
# ::struct::tree::_exists --
#
# Test for existance of a given node in a tree.
#
# Arguments:
# name Name of the tree to query.
# node Node to look for.
#
# Results:
# 1 if the node exists, 0 else.
proc ::struct::tree::_exists {name node} {
return [info exists ${name}::parent($node)]
}
# ::struct::tree::_get --
#
# Get a keyed value from a node in a tree.
#
# Arguments:
# name Name of the tree.
# node Node to query.
# flag Optional flag specifier; if present, must be "-key".
# key Optional key to lookup; defaults to data.
#
# Results:
# value Value associated with the key given.
proc ::struct::tree::_get {name node {flag -key} {key data}} {
if {![_exists $name $node]} {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
variable ${name}::attribute
if {![info exists attribute($node)]} {
# No attribute data for this node,
# except for the default key 'data'.
if {[string equal $key data]} {
return ""
}
return -code error "invalid key \"$key\" for node \"$node\""
}
upvar ${name}::$attribute($node) data
if {![info exists data($key)]} {
return -code error "invalid key \"$key\" for node \"$node\""
}
return $data($key)
}
# ::struct::tree::_getall --
#
# Get a serialized list of key/value pairs from a node in a tree.
#
# Arguments:
# name Name of the tree.
# node Node to query.
#
# Results:
# value A serialized list of key/value pairs.
proc ::struct::tree::_getall {name node args} {
if {![_exists $name $node]} {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
if {[llength $args]} {
return -code error "wrong # args: should be \"$name getall $node\""
}
variable ${name}::attribute
if {![info exists attribute($node)]} {
# Only default key is present, invisibly.
return {data {}}
}
upvar ${name}::$attribute($node) data
return [array get data]
}
# ::struct::tree::_keys --
#
# Get a list of keys from a node in a tree.
#
# Arguments:
# name Name of the tree.
# node Node to query.
#
# Results:
# value A serialized list of key/value pairs.
proc ::struct::tree::_keys {name node args} {
if {![_exists $name $node]} {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
if {[llength $args]} {
return -code error "wrong # args: should be \"$name keys $node\""
}
variable ${name}::attribute
if {![info exists attribute($node)]} {
# No attribute data for this node,
# except for the default key 'data'.
return {data}
}
upvar ${name}::$attribute($node) data
return [array names data]
}
# ::struct::tree::_keyexists --
#
# Test for existance of a given key for a node in a tree.
#
# Arguments:
# name Name of the tree.
# node Node to query.
# flag Optional flag specifier; if present, must be "-key".
# key Optional key to lookup; defaults to data.
#
# Results:
# 1 if the key exists, 0 else.
proc ::struct::tree::_keyexists {name node {flag -key} {key data}} {
if {![_exists $name $node]} {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
if {![string equal $flag "-key"]} {
return -code error "invalid option \"$flag\": should be -key"
}
variable ${name}::attribute
if {![info exists attribute($node)]} {
# No attribute data for this node,
# except for the default key 'data'.
return [string equal $key data]
}
upvar ${name}::$attribute($node) data
return [info exists data($key)]
}
# ::struct::tree::_index --
#
# Determine the index of node with in its parent's list of children.
#
# Arguments:
# name Name of the tree.
# node Node to look up.
#
# Results:
# index The index of the node in its parent
proc ::struct::tree::_index {name node} {
if { [string equal $node "root"] } {
# The special root node has no parent, thus no index in it either.
return -code error "cannot determine index of root node"
}
if { ![_exists $name $node] } {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
variable ${name}::children
variable ${name}::parent
# Locate the parent and ourself in its list of children
set parentNode $parent($node)
return [lsearch -exact $children($parentNode) $node]
}
# ::struct::tree::_insert --
#
# Add a node to a tree; if the node(s) specified already exist, they
# will be moved to the given location.
#
# Arguments:
# name Name of the tree.
# parentNode Parent to add the node to.
# index Index at which to insert.
# args Node(s) to insert. If none is given, the routine
# will insert a single node with a unique name.
#
# Results:
# nodes List of nodes inserted.
proc ::struct::tree::_insert {name parentNode index args} {
if { [llength $args] == 0 } {
# No node name was given; generate a unique one
set args [list [GenerateUniqueNodeName $name]]
}
if { ![_exists $name $parentNode] } {
return -code error "parent node \"$parentNode\" does not exist in tree \"$name\""
}
variable ${name}::parent
variable ${name}::children
# Make sure the index is numeric
if { ![string is integer $index] } {
# If the index is not numeric, make it numeric by lsearch'ing for
# the value at index, then incrementing index (because "end" means
# just past the end for inserts)
set val [lindex $children($parentNode) $index]
set index [expr {[lsearch -exact $children($parentNode) $val] + 1}]
}
foreach node $args {
if {[_exists $name $node] } {
# Move the node to its new home
if { [string equal $node "root"] } {
return -code error "cannot move root node"
}
# Cannot make a node its own descendant (I'm my own grandpaw...)
set ancestor $parentNode
while { ![string equal $ancestor "root"] } {
if { [string equal $ancestor $node] } {
return -code error "node \"$node\" cannot be its own descendant"
}
set ancestor $parent($ancestor)
}
# Remove this node from its parent's children list
set oldParent $parent($node)
set ind [lsearch -exact $children($oldParent) $node]
set children($oldParent) [lreplace $children($oldParent) $ind $ind]
# If the node is moving within its parent, and its old location
# was before the new location, decrement the new location, so that
# it gets put in the right spot
if { [string equal $oldParent $parentNode] && $ind < $index } {
incr index -1
}
} else {
# Set up the new node
set children($node) [list]
}
# Add this node to its parent's children list
set children($parentNode) [linsert $children($parentNode) $index $node]
# Update the parent pointer for this node
set parent($node) $parentNode
incr index
}
return $args
}
# ::struct::tree::_isleaf --
#
# Return whether the given node of a tree is a leaf or not.
#
# Arguments:
# name Name of the tree object.
# node Node to look up.
#
# Results:
# isleaf True if the node is a leaf; false otherwise.
proc ::struct::tree::_isleaf {name node} {
if { ![_exists $name $node] } {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
variable ${name}::children
return [expr {[llength $children($node)] == 0}]
}
# ::struct::tree::_move --
#
# Move a node (and all its subnodes) from where ever it is to a new
# location in the tree.
#
# Arguments:
# name Name of the tree
# parentNode Parent to add the node to.
# index Index at which to insert.
# node Node to move; the node must exist in the tree.
# args Additional nodes to move; these nodes must exist
# in the tree.
#
# Results:
# None.
proc ::struct::tree::_move {name parentNode index node args} {
set args [linsert $args 0 $node]
# Can only move a node to a real location in the tree
if { ![_exists $name $parentNode] } {
return -code error "parent node \"$parentNode\" does not exist in tree \"$name\""
}
variable ${name}::parent
variable ${name}::children
# Make sure the index is numeric
if { ![string is integer $index] } {
# If the index is not numeric, make it numeric by lsearch'ing for
# the value at index, then incrementing index (because "end" means
# just past the end for inserts)
set val [lindex $children($parentNode) $index]
set index [expr {[lsearch -exact $children($parentNode) $val] + 1}]
}
# Validate all nodes to move before trying to move any.
foreach node $args {
if { [string equal $node "root"] } {
return -code error "cannot move root node"
}
# Can only move real nodes
if { ![_exists $name $node] } {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
# Cannot move a node to be a descendant of itself
set ancestor $parentNode
while { ![string equal $ancestor "root"] } {
if { [string equal $ancestor $node] } {
return -code error "node \"$node\" cannot be its own descendant"
}
set ancestor $parent($ancestor)
}
}
# Remove all nodes from their current parent's children list
foreach node $args {
set oldParent $parent($node)
set ind [lsearch -exact $children($oldParent) $node]
set children($oldParent) [lreplace $children($oldParent) $ind $ind]
# Update the nodes parent value
set parent($node) $parentNode
}
# Add all nodes to their new parent's children list
set children($parentNode) \
[eval [list linsert $children($parentNode) $index] $args]
return
}
# ::struct::tree::_next --
#
# Return the right sibling for a given node of a tree.
#
# Arguments:
# name Name of the tree object.
# node Node to retrieve right sibling for.
#
# Results:
# sibling The right sibling for the node, or null if node was
# the rightmost child of its parent.
proc ::struct::tree::_next {name node} {
# The 'root' has no siblings.
if { [string equal $node "root"] } {
return {}
}
if { ![_exists $name $node] } {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
# Locate the parent and our place in its list of children.
variable ${name}::parent
variable ${name}::children
set parentNode $parent($node)
set index [lsearch -exact $children($parentNode) $node]
# Go to the node to the right and return its name.
return [lindex $children($parentNode) [incr index]]
}
# ::struct::tree::_numchildren --
#
# Return the number of immediate children for a given node of a tree.
#
# Arguments:
# name Name of the tree object.
# node Node to look up.
#
# Results:
# numchildren Number of immediate children for the node.
proc ::struct::tree::_numchildren {name node} {
if { ![_exists $name $node] } {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
variable ${name}::children
return [llength $children($node)]
}
# ::struct::tree::_parent --
#
# Return the name of the parent node of a node in a tree.
#
# Arguments:
# name Name of the tree.
# node Node to look up.
#
# Results:
# parent Parent of node $node
proc ::struct::tree::_parent {name node} {
if { ![_exists $name $node] } {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
# FRINK: nocheck
return [set ${name}::parent($node)]
}
# ::struct::tree::_previous --
#
# Return the left sibling for a given node of a tree.
#
# Arguments:
# name Name of the tree object.
# node Node to look up.
#
# Results:
# sibling The left sibling for the node, or null if node was
# the leftmost child of its parent.
proc ::struct::tree::_previous {name node} {
# The 'root' has no siblings.
if { [string equal $node "root"] } {
return {}
}
if { ![_exists $name $node] } {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
# Locate the parent and our place in its list of children.
variable ${name}::parent
variable ${name}::children
set parentNode $parent($node)
set index [lsearch -exact $children($parentNode) $node]
# Go to the node to the right and return its name.
return [lindex $children($parentNode) [incr index -1]]
}
# ::struct::tree::_serialize --
#
# Serialize a tree object (partially) into a transportable value.
#
# Arguments:
# name Name of the tree.
# node Root node of the serialized tree.
#
# Results:
# A list structure describing the part of the tree which was serialized.
proc ::struct::tree::_serialize {name {node root}} {
if {![_exists $name $node]} {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
Serialize $name $node tree attr
return [list $tree [array get attr]]
}
# ::struct::tree::_set --
#
# Set or get a value for a node in a tree.
#
# Arguments:
# name Name of the tree.
# node Node to modify or query.
# args Optional arguments specifying a key and a value. Format is
# ?-key key? ?value?
# If no key is specified, the key "data" is used.
#
# Results:
# val Value associated with the given key of the given node
proc ::struct::tree::_set {name node args} {
if {![_exists $name $node]} {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
if {[llength $args] > 3} {
return -code error "wrong # args: should be \"$name set [list $node] ?-key key?\
?value?\""
}
# Process the arguments ...
set key "data"
set haveValue 0
if {[llength $args] > 1} {
foreach {flag key} $args break
if {![string match "${flag}*" "-key"]} {
return -code error "invalid option \"$flag\": should be key"
}
if {[llength $args] == 3} {
set haveValue 1
set value [lindex $args end]
}
} elseif {[llength $args] == 1} {
set haveValue 1
set value [lindex $args end]
}
if {$haveValue} {
# Setting a value. This may have to create
# the attribute array for this particular
# node
variable ${name}::attribute
if {![info exists attribute($node)]} {
# No attribute data for this node,
# so create it as we need it.
GenAttributeStorage $name $node
}
upvar ${name}::$attribute($node) data
return [set data($key) $value]
} else {
# Getting a value
return [_get $name $node -key $key]
}
}
# ::struct::tree::_append --
#
# Append a value for a node in a tree.
#
# Arguments:
# name Name of the tree.
# node Node to modify or query.
# args Optional arguments specifying a key and a value. Format is
# ?-key key? ?value?
# If no key is specified, the key "data" is used.
#
# Results:
# val Value associated with the given key of the given node
proc ::struct::tree::_append {name node args} {
if {![_exists $name $node]} {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
if {
([llength $args] != 1) &&
([llength $args] != 3)
} {
return -code error "wrong # args: should be \"$name set [list $node] ?-key key?\
value\""
}
if {[llength $args] == 3} {
foreach {flag key} $args break
if {![string equal $flag "-key"]} {
return -code error "invalid option \"$flag\": should be -key"
}
} else {
set key "data"
}
set value [lindex $args end]
variable ${name}::attribute
if {![info exists attribute($node)]} {
# No attribute data for this node,
# so create it as we need it.
GenAttributeStorage $name $node
}
upvar ${name}::$attribute($node) data
return [append data($key) $value]
}
# ::struct::tree::_lappend --
#
# lappend a value for a node in a tree.
#
# Arguments:
# name Name of the tree.
# node Node to modify or query.
# args Optional arguments specifying a key and a value. Format is
# ?-key key? ?value?
# If no key is specified, the key "data" is used.
#
# Results:
# val Value associated with the given key of the given node
proc ::struct::tree::_lappend {name node args} {
if {![_exists $name $node]} {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
if {
([llength $args] != 1) &&
([llength $args] != 3)
} {
return -code error "wrong # args: should be \"$name lappend [list $node] ?-key key?\
value\""
}
if {[llength $args] == 3} {
foreach {flag key} $args break
if {![string equal $flag "-key"]} {
return -code error "invalid option \"$flag\": should be -key"
}
} else {
set key "data"
}
set value [lindex $args end]
variable ${name}::attribute
if {![info exists attribute($node)]} {
# No attribute data for this node,
# so create it as we need it.
GenAttributeStorage $name $node
}
upvar ${name}::$attribute($node) data
return [lappend data($key) $value]
}
# ::struct::tree::_size --
#
# Return the number of descendants of a given node. The default node
# is the special root node.
#
# Arguments:
# name Name of the tree.
# node Optional node to start counting from (default is root).
#
# Results:
# size Number of descendants of the node.
proc ::struct::tree::_size {name {node root}} {
if { ![_exists $name $node] } {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
# If the node is the root, we can do the cheap thing and just count the
# number of nodes (excluding the root node) that we have in the tree with
# array names
if { [string equal $node "root"] } {
set size [llength [array names ${name}::parent]]
return [expr {$size - 1}]
}
# Otherwise we have to do it the hard way and do a full tree search
variable ${name}::children
set size 0
set st [list ]
foreach child $children($node) {
lappend st $child
}
while { [llength $st] > 0 } {
set node [lindex $st end]
set st [lreplace $st end end]
incr size
foreach child $children($node) {
lappend st $child
}
}
return $size
}
# ::struct::tree::_splice --
#
# Add a node to a tree, making a range of children from the given
# parent children of the new node.
#
# Arguments:
# name Name of the tree.
# parentNode Parent to add the node to.
# from Index at which to insert.
# to Optional end of the range of children to replace.
# Defaults to 'end'.
# node Optional node name; if given, must be unique. If not
# given, a unique name will be generated.
#
# Results:
# node Name of the node added to the tree.
proc ::struct::tree::_splice {name parentNode from {to end} args} {
if { [llength $args] == 0 } {
# No node name given; generate a unique node name
set node [GenerateUniqueNodeName $name]
} else {
set node [lindex $args 0]
}
if { [_exists $name $node] } {
return -code error "node \"$node\" already exists in tree \"$name\""
}
variable ${name}::children
variable ${name}::parent
# Save the list of children that are moving
set moveChildren [lrange $children($parentNode) $from $to]
# Remove those children from the parent
set children($parentNode) [lreplace $children($parentNode) $from $to]
# Add the new node
_insert $name $parentNode $from $node
# Move the children
set children($node) $moveChildren
foreach child $moveChildren {
set parent($child) $node
}
return $node
}
# ::struct::tree::_swap --
#
# Swap two nodes in a tree.
#
# Arguments:
# name Name of the tree.
# node1 First node to swap.
# node2 Second node to swap.
#
# Results:
# None.
proc ::struct::tree::_swap {name node1 node2} {
# Can't swap the magic root node
if {[string equal $node1 "root"] || [string equal $node2 "root"]} {
return -code error "cannot swap root node"
}
# Can only swap two real nodes
if {![_exists $name $node1]} {
return -code error "node \"$node1\" does not exist in tree \"$name\""
}
if {![_exists $name $node2]} {
return -code error "node \"$node2\" does not exist in tree \"$name\""
}
# Can't swap a node with itself
if {[string equal $node1 $node2]} {
return -code error "cannot swap node \"$node1\" with itself"
}
# Swapping nodes means swapping their labels and values
variable ${name}::children
variable ${name}::parent
set parent1 $parent($node1)
set parent2 $parent($node2)
# Replace node1 with node2 in node1's parent's children list, and
# node2 with node1 in node2's parent's children list
set i1 [lsearch -exact $children($parent1) $node1]
set i2 [lsearch -exact $children($parent2) $node2]
set children($parent1) [lreplace $children($parent1) $i1 $i1 $node2]
set children($parent2) [lreplace $children($parent2) $i2 $i2 $node1]
# Make node1 the parent of node2's children, and vis versa
foreach child $children($node2) {
set parent($child) $node1
}
foreach child $children($node1) {
set parent($child) $node2
}
# Swap the children lists
set children1 $children($node1)
set children($node1) $children($node2)
set children($node2) $children1
if { [string equal $node1 $parent2] } {
set parent($node1) $node2
set parent($node2) $parent1
} elseif { [string equal $node2 $parent1] } {
set parent($node1) $parent2
set parent($node2) $node1
} else {
set parent($node1) $parent2
set parent($node2) $parent1
}
# Swap the values
# More complicated now with the possibility that nodes do not have
# attribute storage associated with them.
variable ${name}::attribute
if {
[set ia [info exists attribute($node1)]] ||
[set ib [info exists attribute($node2)]]
} {
# At least one of the nodes has attribute data. We simply swap
# the references to the arrays containing them. No need to
# copy the actual data around.
if {$ia && $ib} {
set tmp $attribute($node1)
set attribute($node1) $attribute($node2)
set attribute($node2) $tmp
} elseif {$ia} {
set attribute($node2) $attribute($node1)
unset attribute($node1)
} elseif {$ib} {
set attribute($node1) $attribute($node2)
unset attribute($node2)
} else {
return -code error "Impossible condition."
}
} ; # else: No attribute storage => Nothing to do {}
return
}
# ::struct::tree::_unset --
#
# Remove a keyed value from a node.
#
# Arguments:
# name Name of the tree.
# node Node to modify.
# args Optional additional args specifying which key to unset;
# if given, must be of the form "-key key". If not given,
# the key "data" is unset.
#
# Results:
# None.
proc ::struct::tree::_unset {name node {flag -key} {key data}} {
if {![_exists $name $node]} {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
if {![string match "${flag}*" "-key"]} {
return -code error "invalid option \"$flag\": should be \"$name unset\
[list $node] ?-key key?\""
}
variable ${name}::attribute
if {![info exists attribute($node)]} {
# No attribute data for this node,
# except for the default key 'data'.
GenAttributeStorage $name $node
}
upvar ${name}::$attribute($node) data
catch {unset data($key)}
return
}
# ::struct::tree::_walk --
#
# Walk a tree using a pre-order depth or breadth first
# search. Pre-order DFS is the default. At each node that is visited,
# a command will be called with the name of the tree and the node.
#
# Arguments:
# name Name of the tree.
# node Node at which to start.
# args Optional additional arguments specifying the type and order of
# the tree walk, and the command to execute at each node.
# Format is
# ?-type {bfs|dfs}? ?-order {pre|post|in|both}? -command cmd
#
# Results:
# None.
proc ::struct::tree::_walk {name node args} {
set usage "$name walk $node ?-type {bfs|dfs}? ?-order {pre|post|in|both}? -command cmd"
if {[llength $args] > 6 || [llength $args] < 2} {
return -code error "wrong # args: should be \"$usage\""
}
if { ![_exists $name $node] } {
return -code error "node \"$node\" does not exist in tree \"$name\""
}
# Set defaults
set type dfs
set order pre
set cmd ""
for {set i 0} {$i < [llength $args]} {incr i} {
set flag [lindex $args $i]
incr i
if { $i >= [llength $args] } {
return -code error "value for \"$flag\" missing: should be \"$usage\""
}
switch -glob -- $flag {
"-type" {
set type [string tolower [lindex $args $i]]
}
"-order" {
set order [string tolower [lindex $args $i]]
}
"-command" {
set cmd [lindex $args $i]
}
default {
return -code error "unknown option \"$flag\": should be \"$usage\""
}
}
}
# Make sure we have a command to run, otherwise what's the point?
if { [string equal $cmd ""] } {
return -code error "no command specified: should be \"$usage\""
}
# Validate that the given type is good
switch -exact -- $type {
"dfs" - "bfs" {
set type $type
}
default {
return -code error "invalid search type \"$type\": should be dfs, or bfs"
}
}
# Validate that the given order is good
switch -exact -- $order {
"pre" - "post" - "in" - "both" {
set order $order
}
default {
return -code error "invalid search order \"$order\":\
should be pre, post, both, or in"
}
}
if {[string equal $order "in"] && [string equal $type "bfs"]} {
return -code error "unable to do a ${order}-order breadth first walk"
}
# Do the walk
variable ${name}::children
set st [list ]
lappend st $node
# Compute some flags for the possible places of command evaluation
set leave [expr {[string equal $order post] || [string equal $order both]}]
set enter [expr {[string equal $order pre] || [string equal $order both]}]
set touch [string equal $order in]
if {$leave} {
set lvlabel leave
} elseif {$touch} {
# in-order does not provide a sense
# of nesting for the parent, hence
# no enter/leave, just 'visit'.
set lvlabel visit
}
if { [string equal $type "dfs"] } {
# Depth-first walk, several orders of visiting nodes
# (pre, post, both, in)
array set visited {}
while { [llength $st] > 0 } {
set node [lindex $st end]
if {[info exists visited($node)]} {
# Second time we are looking at this 'node'.
# Pop it, then evaluate the command (post, both, in).
set st [lreplace $st end end]
if {$leave || $touch} {
# Evaluate the command at this node
WalkCall $name $node $lvlabel $cmd
}
} else {
# First visit of this 'node'.
# Do *not* pop it from the stack so that we are able
# to visit again after its children
# Remember it.
set visited($node) .
if {$enter} {
# Evaluate the command at this node (pre, both)
WalkCall $name $node "enter" $cmd
}
# Add the children of this node to the stack.
# The exact behaviour depends on the chosen
# order. For pre, post, both-order we just
# have to add them in reverse-order so that
# they will be popped left-to-right. For in-order
# we have rearrange the stack so that the parent
# is revisited immediately after the first child.
# (but only if there is ore than one child,)
set clist $children($node)
set len [llength $clist]
if {$touch && ($len > 1)} {
# Pop node from stack, insert into list of children
set st [lreplace $st end end]
set clist [linsert $clist 1 $node]
incr len
}
for {set i [expr {$len - 1}]} {$i >= 0} {incr i -1} {
lappend st [lindex $clist $i]
}
}
}
} else {
# Breadth first walk (pre, post, both)
# No in-order possible. Already captured.
if {$leave} {
set backward $st
}
while { [llength $st] > 0 } {
set node [lindex $st 0]
set st [lreplace $st 0 0]
if {$enter} {
# Evaluate the command at this node
WalkCall $name $node "enter" $cmd
}
# Add this node's children
# And create a mirrored version in case of post/both order.
foreach child $children($node) {
lappend st $child
if {$leave} {
set backward [linsert $backward 0 $child]
}
}
}
if {$leave} {
foreach node $backward {
# Evaluate the command at this node
WalkCall $name $node "leave" $cmd
}
}
}
return
}
# ::struct::tree::WalkCall --
#
# Helper command to 'walk' handling the evaluation
# of the user-specified command. Information about
# the tree, node and current action are substituted
# into the command before it evaluation.
#
# Arguments:
# tree Tree we are walking
# node Node we are at.
# action The current action.
# cmd The command to call, already partially substituted.
#
# Results:
# None.
proc ::struct::tree::WalkCall {tree node action cmd} {
set subs [list %n [list $node] %a [list $action] %t [list $tree] %% %]
uplevel 2 [string map $subs $cmd]
return
}
# ::struct::tree::GenerateUniqueNodeName --
#
# Generate a unique node name for the given tree.
#
# Arguments:
# name Name of the tree to generate a unique node name for.
#
# Results:
# node Name of a node guaranteed to not exist in the tree.
proc ::struct::tree::GenerateUniqueNodeName {name} {
variable ${name}::nextUnusedNode
while {[_exists $name "node${nextUnusedNode}"]} {
incr nextUnusedNode
}
return "node${nextUnusedNode}"
}
# ::struct::tree::KillNode --
#
# Delete all data of a node.
#
# Arguments:
# name Name of the tree containing the node
# node Name of the node to delete.
#
# Results:
# none
proc ::struct::tree::KillNode {name node} {
variable ${name}::parent
variable ${name}::children
variable ${name}::attribute
# Remove all record of $node
unset parent($node)
unset children($node)
if {[info exists attribute($node)]} {
# FRINK: nocheck
unset ${name}::$attribute($node)
unset attribute($node)
}
return
}
# ::struct::tree::GenAttributeStorage --
#
# Create an array to store the attrributes of a node in.
#
# Arguments:
# name Name of the tree containing the node
# node Name of the node which got attributes.
#
# Results:
# none
proc ::struct::tree::GenAttributeStorage {name node} {
variable ${name}::nextAttr
variable ${name}::attribute
set attr "a[incr nextAttr]"
set attribute($node) $attr
upvar ${name}::$attr data
set data(data) ""
return
}
# ::struct::tree::Serialize --
#
# Serialize a tree object (partially) into a transportable value.
#
# Arguments:
# name Name of the tree.
# node Root node of the serialized tree.
#
# Results:
# None
proc ::struct::tree::Serialize {name node tvar avar} {
upvar 1 $tvar tree $avar attr
variable ${name}::children
variable ${name}::attribute
# Store attribute data
if {[info exists attribute($node)]} {
set attr($node) [array get ${name}::$attribute($node)]
} else {
set attr($node) {}
}
# Build tree structure as nested list.
set subtrees [list]
foreach c $children($node) {
Serialize $name $c sub attr
lappend subtrees $sub
}
set tree [list $node $subtrees]
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::struct {
# Get 'tree::tree' into the general structure namespace.
namespace import -force tree::tree
namespace export tree
}
package provide struct::tree 1.2.2