Tcl Library Source Code

tree1.tcl at [b430832e75]
Login

File modules/struct/tree1.tcl artifact a9a43ad85f part of check-in b430832e75


# 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