!standard A.18.10(0) 10-06-02 AI05-0136-1/07 !standard A.18.17(0) !standard A.18.25(0) !class Amendment 09-02-04 !status Amendment 201Z 10-01-15 !status WG9 Approved 10-06-18 !status ARG Approved 9-0-3 09-11-07 !status work item 09-02-04 !status received 09-01-27 !priority Medium !difficulty Medium !subject Multiway tree container !summary Multiway tree containers are added. !problem One of the most important advantages of using predefined containers over hand-building data structures is that the memory management is handled by the container. This is especially an advantage when the elements are indefinite types such as class-wide types. However, Ada 2005 does not provide any way to create a tree structure out of existing containers without requiring explicit memory management. Consider representing a multiway tree using only the existing containers. Clearly, the list of child nodes of an element can easily be represented as a list container. That list would have to be part of the element as inserted in the tree - which of course is also the type of the elements in the list. Thus, the container has to appear in its own elements; implementing that necesarily includes an incomplete type and an access type (which could be anonymous). (If Ada did not require that, then the implementation of the containers would be significantly constrained; in particular, bounded forms would be incompatible with direct inclusion in the elements). The use of an access type here necessarily requires the use of allocators and thus hand-written memory management. Indeed, it is probably harder to use one of the existing container in this way than it is to write the operations directly. This situation cries out for a solution, and the easiest one is to provide one or more appropriate containers. !proposal Add a multiway tree container. A multiway tree allows multiple child elements for each parent element. (Such a tree is sometimes known as a parent-child-sibling tree.) A multiway tree is the most general tree; many problems map directly into it (see examples for a number of such cases). !wording Add the multiway tree to the list of containers defined in A.18(4.j). A.18.10 The Package Containers.Multiway_Trees [Editor's Note: The full ARG decided to use a natural insertion that requires renumbering all of the indefinite containers.] The language-defined generic package Containers.Multiway_Trees provides private types Tree and Cursor, and a set of operations for each type. A multiway tree container is well-suited to represent nested structures. A multiway tree container object manages a tree of internal *nodes*, each of which contains an element and pointers to the parent, first child, last child, next (successor) sibling, and previous (predecessor) sibling internal nodes. A cursor designates a particular node within a tree (and by extension the element contained in that node, if any). A cursor keeps designating the same node (and element) as long as the node is part of the container, even if the node is moved within the container. A *subtree* is a particular node (which *roots the subtree*) and all of its child nodes (including all of the children of the child nodes, recursively). There is a special node, the *root*, which is always present and has no associated element value. The root node provides a place to add nodes to an otherwise empty tree and represents the bottom of the tree. A node that has no children is called a *leaf node*. The *ancestors* of a node are the parent node, the parent of the parent node, and so on until a node with no parent is reached. Similarly, the *descendants* of a node are the child nodes, the children of each child node, and so on. The nodes of a subtree could be visited in several different orders. For a *depth-first order*, the last step of visiting a node is to visit the nodes of its child list in order, recursively. AARM Ramification: For the depth-first order, when each child node is visited, the child list of the child node is visited before the next sibling of the child node is visited. Static Semantics The generic library package Containers.Multiway_Trees has the following declaration: generic type Element_Type is private; with function "=" (Left, Right : Element_Type) return Boolean is <>; package Ada.Containers.Multiway_Trees is pragma Preelaborate(Multiway_Trees); pragma Remote_Types(Multiway_Trees); [Editor's note: Remote_Types to be consistent with AI05-0084-1.] type Tree is tagged private; pragma Preelaborable_Initialization(Tree); type Cursor is private; pragma Preelaborable_Initialization(Cursor); Empty_Tree : constant Tree; No_Element : constant Cursor; function Equal_Subtree (Left_Position : Cursor; Right_Position: Cursor) return Boolean; function "=" (Left, Right : Tree) return Boolean; function Is_Empty (Container : Tree) return Boolean; function Node_Count (Container : Tree) return Count_Type; function Subtree_Node_Count (Position : Cursor) return Count_Type; function Depth (Position : Cursor) return Count_Type; function Is_Root (Position : Cursor) return Boolean; function Is_Leaf (Position : Cursor) return Boolean; function Root (Container : Tree) return Cursor; procedure Clear (Container : in out Tree); function Element (Position : Cursor) return Element_Type; procedure Replace_Element (Container : in out Tree; Position : in Cursor; New_Item : in Element_Type); procedure Query_Element (Position : in Cursor; Process : not null access procedure (Element : in Element_Type)); procedure Update_Element (Container : in out Tree; Position : in Cursor; Process : not null access procedure (Element : in out Element_Type)); procedure Assign (Target : in out Tree; Source : in Tree); function Copy (Source : Tree) return Tree; procedure Move (Target : in out Tree; Source : in out Tree); procedure Delete_Leaf (Container : in out Tree; Position : in out Cursor); procedure Delete_Subtree (Container : in out Tree; Position : in out Cursor); procedure Swap (Container : in out Tree; I, J : in Cursor); function Find (Container : Tree; Item : Element_Type) return Cursor; function Find_In_Subtree (Container : Tree; Item : Element_Type; Position : Cursor) return Cursor; function Ancestor_Find (Container : Tree; Item : Element_Type; Position : Cursor) return Cursor; function Contains (Container : Tree; Item : Element_Type) return Boolean; function Has_Element (Position : Cursor) return Boolean; procedure Iterate (Container : in Tree; Process : not null access procedure (Position : in Cursor)); procedure Iterate_Subtree (Position : in Cursor; Process : not null access procedure (Position : in Cursor)); function Child_Count (Parent : Cursor) return Count_Type; function Child_Depth (Parent, Child : Cursor) return Count_Type; procedure Insert_Child (Container : in out Tree; Parent : in Cursor; Before : in Cursor; New_Item : in Element_Type; Count : in Count_Type := 1); procedure Insert_Child (Container : in out Tree; Parent : in Cursor; Before : in Cursor; New_Item : in Element_Type; Position : out Cursor; Count : in Count_Type := 1); procedure Insert_Child (Container : in out Tree; Parent : in Cursor; Before : in Cursor; Position : out Cursor; Count : in Count_Type := 1); procedure Prepend_Child (Container : in out Tree; Parent : in Cursor; New_Item : in Element_Type; Count : in Count_Type := 1); procedure Append_Child (Container : in out Tree; Parent : in Cursor; New_Item : in Element_Type; Count : in Count_Type := 1); procedure Delete_Children (Container : in out Tree; Parent : in Cursor); procedure Copy_Subtree (Target : in out Tree; Parent : in Cursor; Before : in Cursor; Source : in Cursor); procedure Splice_Subtree (Target : in out Tree; Parent : in Cursor; Before : in Cursor; Source : in out Tree; Position : in out Cursor); procedure Splice_Subtree (Container: in out Tree; Parent : in Cursor; Before : in Cursor; Position : in Cursor); procedure Splice_Children (Target : in out Tree; Target_Parent : in Cursor; Before : in Cursor; Source : in out Tree; Source_Parent : in Cursor); procedure Splice_Children (Container : in out Tree; Target_Parent : in Cursor; Before : in Cursor; Source_Parent : in Cursor); function Parent (Position : Cursor) return Cursor; function First_Child (Parent : Cursor) return Cursor; function First_Child_Element (Parent : Cursor) return Element_Type; function Last_Child (Parent : Cursor) return Cursor; function Last_Child_Element (Parent : Cursor) return Element_Type; function Next_Sibling (Position : Cursor) return Cursor; function Previous_Sibling (Position : Cursor) return Cursor; procedure Next_Sibling (Position : in out Cursor); procedure Previous_Sibling (Position : in out Cursor); procedure Iterate_Children (Container : in Tree; Parent : in Cursor; Process : not null access procedure (Position : in Cursor)); procedure Reverse_Iterate_Children (Container : in Tree; Parent : in Cursor; Process : not null access procedure (Position : in Cursor)); private ... -- not specified by the language end Ada.Containers.Multiway_Trees; The actual function for the generic formal function "=" on Element_Type values is expected to define a reflexive and symmetric relationship and return the same result value each time it is called with a particular pair of values. If it behaves in some other manner, the functions Find, Reverse_Find, Equal_Subtree, and "=" on tree values return an unspecified value. The exact arguments and number of calls of this generic formal function by the functions Find, Reverse_Find, Equal_Subtree, and "=" on tree values are unspecified. The type Tree is used to represent trees. The type Tree needs finalization (see 7.6). Empty_Tree represents the empty Tree object. It contains only the root node (Count (Empty_Tree) returns 1). If an object of type Tree is not otherwise initialized, it is initialized to the same value as Empty_Tree. No_Element represents a cursor that designates no element. If an object of type Cursor is not otherwise initialized, it is initialized to the same value as No_Element. The predefined "=" operator for type Cursor returns True if both cursors are No_Element, or designate the same element in the same container. Execution of the default implementation of the Input, Output, Read, or Write attribute of type Cursor raises Program_Error. Tree'Write writes Tree.Count-1 elements to the stream. Tree'Read reads Tree.Count-1 elements from the stream. Some operations of this generic package have access-to-subprogram parameters. To ensure such operations are well-defined, they guard against certain actions by the designated subprogram. In particular, some operations check for "tampering with cursors" of a container because they depend on the set of elements of the container remaining constant, and others check for "tampering with elements" of a container because they depend on elements of the container not being replaced. A subprogram is said to tamper with cursors of a tree object T if: * it inserts or deletes elements of T, that is, it calls the Clear, Delete_Leaf, Insert_Child, or Delete_Children procedures with T as a parameter; or AARM To be honest: Operations which are defined to be equivalent to a call on one of these operations also are included. Similarly, operations which call one of these as part of their definition are included. * it reorders the elements of T, that is, it calls the Splice_Subtree or Splice_Children procedures; or * it finalizes T; or * it calls Assign with T as the Target parameter; or * it calls the Move procedure with T as a parameter. AARM Reason: Swap copies elements rather than reordering them, so it doesn't tamper with cursors. A subprogram is said to tamper with elements of a tree object T if: * it tampers with cursors of T; or * it replaces one or more elements of T, that is, it calls the Replace_Element or Swap procedures with T as a parameter. AARM Reason: Complete replacement of an element can cause its memory to be deallocated while another operation is holding onto a reference to it. That can't be allowed. However, a simple modification of (part of) an element is not a problem, so Update_Element does not cause a problem. function Equal_Subtree (Left_Position : Cursor; Right_Position: Cursor) return Boolean; If Left_Position or Right_Position equals No_Element, propagates Constraint_Error. If the number of child nodes of the element designated by Left_Position is different than the number of child nodes of the element designated by Right_Position, the function returns False. If Left_Position designates a root node and Right_Position does not, the function returns False. If Right_Position designates a root node and Left_Position does not, the function returns False. Unless both cursors designate a root node, the elements are compared using the generic formal equality operator. If the result is False, the function returns False. Otherwise, it calls Equal_Subtree on a cursor designating each child element of the element designated by Left_Position and a cursor designated the corresponding child element of the element designated by Right_Position. If any such call returns False, the function returns False; otherwise it returns True. Any exception raised during the evaluation of element equality is propagated. [Editor's note: I needed this to define "=" on Trees, as a recursive definition is the only one that makes sense.] AARM Ramification: Left_Position and Right_Position do not need to be from the same tree. AARM Implementation Note: This wording describes the canonical semantics. However, the order and number of calls on the formal equality function is unspecified for all of the operations that use it in this package, so an implementation can call it as many or as few times as it needs to get the correct answer. Similarly, a global rule (see the introduction of Annex A) says that language-defined routines are not affected by overriding of other language-defined routines. This means that no reasonable program can tell how many times Equal_Subtree is called, and thus an implementation can call it as many or as few times as it needs to get the correct answer. Specifically, there is no requirement to call the formal equality or Equal_Subtree additional times once the answer has been determined. function "=" (Left, Right : Tree) return Boolean; If Left and Right denote the same tree object, then the function returns True. Otherwise, it calls Equal_Subtree with a cursor designating the root node of Left and Right; the result is returned. Any exception raised during the evaluation of Equal_Subtree is propagated. AARM Implementation Note: Similar considerations apply here as apply to Equal_Subtree. The actual number of calls performed is unspecified. function Node_Count (Container : Tree) return Count_Type; Node_Count returns the number of nodes in Container. AARM Ramification: Since all tree objects have a root node, this can never return a value of 0. Node_Count (Some_Tree) should always equal Subtree_Node_Count (Root (Some_Tree)). function Subtree_Node_Count (Position : Cursor) return Count_Type; If Position is No_Element, Subtree_Count returns 0; otherwise, Subtree_Node_Count returns the number of nodes in the subtree that is rooted by Position. function Is_Empty (Container : Tree) return Boolean; Equivalent to Count (Container) = 1. AARM Ramification: An empty tree contains just the root node. function Depth (Position : Cursor) return Count_Type; If Position equals No_Element, Depth returns 0; otherwise Depth returns the number of ancestor nodes of the node designated by Position (including the node itself). AARM Ramification: Depth (Root (Some_Tree)) = 1. function Is_Root (Position : Cursor) return Boolean; Is_Root returns True if the Position designates the root node of the tree; and returns False otherwise. function Is_Leaf (Position : Cursor) return Boolean; Is_Leaf returns True if Position designates a node that does not have any child nodes; and returns False otherwise. AARM Ramification: Is_Leaf returns False if passed No_Element, since No_Element does not designate a node. Is_Leaf can be passed a cursor that designates the root node; Is_Leaf will return True if passed the root node of an empty tree. function Root (Container : Tree) return Cursor; Root returns a cursor that designates the root node of Container. AARM Ramification: There is always a root node, even in an empty container, so this function never returns No_Element. procedure Clear (Container : in out Tree); Removes all the elements from Container. AARM Ramification: The root node is not removed; all trees have a root node. function Element (Position : Cursor) return Element_Type; If Position equals No_Element, then Constraint_Error is propagated; if Position designates the root node of a tree, then Program_Error is propagated. Otherwise, Element returns the element designated by Position. AARM Ramification: The root node does not contain an element, so that value cannot be read or written. procedure Replace_Element (Container : in out Tree; Position : in Cursor; New_Item : in Element_Type); If Position equals No_Element, then Constraint_Error is propagated; if Position does not designate an element in Container (including if it designates the root node), then Program_Error is propagated. Otherwise Replace_Element assigns the value New_Item to the element designated by Position. procedure Query_Element (Position : in Cursor; Process : not null access procedure (Element : in Element_Type)); If Position equals No_Element, then Constraint_Error is propagated; if Position designates the root node of a tree, then Program_Error is propagated. Otherwise, Query_Element calls Process.all with the element designated by Position as the argument. Program_Error is propagated if Process.all tampers with the elements of Container. Any exception raised by Process.all is propagated. procedure Update_Element (Container : in out Tree; Position : in Cursor; Process : not null access procedure (Element : in out Element_Type)); If Position equals No_Element, then Constraint_Error is propagated; if Position does not designate an element in Container (including if it designates the root node), then Program_Error is propagated. Otherwise Update_Element calls Process.all with the element designated by Position as the argument. Program_Error is propagated if Process.all tampers with the elements of Container. Any exception raised by Process.all is propagated. If Element_Type is unconstrained and definite, then the actual Element parameter of Process.all shall be unconstrained. procedure Assign (Target : in out Tree; Source : in Tree); If Target denotes the same object as Source, the operation has no effect. Otherwise, it calls Clear (Target), and then each element of Source is assigned to a corresponding element in Target. AARM To Be Honest: The "corresponding element in Target" has a parent element that corresponds to the parent element of the Source element, and has child elements that correspond to the child elements of the Source element. function Copy (Source : Tree) return Tree; Returns a tree with the same structure as Source and whose elements are initialized from the corresponding elements of Source. procedure Move (Target : in out Tree; Source : in out Tree); If Target denotes the same object as Source, then Move has no effect. Otherwise, Move first calls Clear (Target). Then, the nodes other than the root node in Source are moved to Target (in the same positions). After Move completes, Count(Target) is the number of nodes originally in Source, and Count(Source) is 1. procedure Delete_Leaf (Container : in out Tree; Position : in out Cursor); If Position equals No_Element, then Constraint_Error is propagated; if Position does not designate an element in Container (including if it designates the root node), then Program_Error is propagated. If the element designated by position has any child elements, then Constraint_Error is propagated. Otherwise Delete_Leaf removes (from Container) the element designated by Position. Finally, Position is set to No_Element. AARM Ramification: The check on Position checks that the cursor does not belong to some other Container. This check implies that a reference to the container is included in the cursor value. This wording is not meant to require detection of dangling cursors; such cursors are defined to be invalid, which means that execution is erroneous, and any result is allowed (including not raising an exception). The root node cannot be deleted. End AARM Ramification. procedure Delete_Subtree (Container : in out Tree; Position : in out Cursor); If Position equals No_Element, then Constraint_Error is propagated. If Position does not designate an element in Container (including if it designates the root node), then Program_Error is propagated. Otherwise Delete removes (from Container) the subtree designated by Position (that is, the node designated by Position and all of the descendant nodes of that node), and Position is set to No_Element. AARM Ramification: The root node cannot be deleted. To delete the entire contents of the tree, call Clear(Container). procedure Swap (Container : in out Tree; I, J : in Cursor); If either I or J equals No_Element, then Constraint_Error is propagated. If either I or J do not designate an element in Container (including if either designates the root node), then Program_Error is propagated. Otherwise, Swap exchanges the values of the elements designated by I and J. AARM Ramification: After a call to Swap, I designates the element value previously designated by J, and J designates the element value previously designated by I. The position of the elements do not change; for instance, the parent node and the first child node of I are unchanged by the operation. The root nodes do not contain element values, so they cannot be swapped End AARM Ramification. AARM To be honest: The implementation is not required to actually copy the elements if it can do the swap some other way. But it is allowed to copy the elements if needed. function Find (Container : Tree; Item : Element_Type) return Cursor; Find searches the elements of Container for an element equal to Item (using the generic formal equality operator). The search starts at the root node. The search checks the tree in a depth-first order. If no equal element is found, then Find returns No_Element. Otherwise, it returns a cursor designating the first equal element encountered. function Find_In_Subtree (Container : Tree; Item : Element_Type; Position : Cursor) return Cursor; If Position equals No_Element, then Constraint_Error is propagated; if Position does not designate a node in Container, then Program_Error is propagated. Find_In_Subtree searches a subtree of the elements of Container for an element equal to Item (using the generic formal equality operator). The search starts at the element designated by Position. The search checks the subtree rooted by Position in a depth-first order. If no equal element is found, then Find returns No_Element. Otherwise, it returns a cursor designating the first equal element encountered. AARM Ramification: Find_In_Subtree does not check any siblings of the element designated by Position. The root node does not contain an element, and therefore it can never be returned, but it can be explicitly passed to Position. [Editor's note: The subtle difference in wording for belonging to the correct container is needed as a cursor designating the root node is allowed here, and it does not "designate an element" (anywhere), but it does "designate a node" in Container.] function Ancestor_Find (Container : Tree; Item : Element_Type; Position : Cursor) return Cursor; If Position equals No_Element, then Constraint_Error is propagated; if Position does not designate an element in Container (including if it designates the root node), then Program_Error is propagated. Otherwise, Ancestor_Find searches for an element equal to Item (using the generic formal equality operator). The search starts at the element designated by Position, and checks each ancestor proceeding toward the root of the subtree. If no equal element is found, then Ancestor_Find returns No_Element. Otherwise, it returns a cursor designating the first equal element encountered. function Contains (Container : Tree; Item : Element_Type) return Boolean; Equivalent to Find (Container, Item) /= No_Element. function Has_Element (Position : Cursor) return Boolean; Returns True if Position designates an element, and returns False otherwise. Redundant[In particular, Has_Element returns False if the cursor designates the root node or equals No_Element.] AARM To Be Honest: This function might not detect cursors that designate deleted elements; such cursors are invalid (see below) and the result of Has_Element for an invalid cursor is unspecified (but not erroneous). procedure Iterate (Container : in Tree; Process : not null access procedure (Position : in Cursor)); Iterate calls Process.all with a cursor that designates each element in Container, starting with the root node and proceeding in a depth-first order. Program_Error is propagated if Process.all tampers with the cursors of Container. Any exception raised by Process.all is propagated. AARM Ramification: Process is not called with the root node, which does not have an associated element. [Editor's note: It is a bit weird to skip the root node, but it seems better that every cursor has an associated element. The alternative that the root node is returned would require most Process implementations to test Is_Root explicitly before doing any element operations; that would be a tripping hazard (very easy to forget).] AARM Implementation Note: The purpose of the tamper with cursors check is to prevent erroneous execution from the Position parameter of Process.all becoming invalid. This check takes place when the operations that tamper with the cursors of the container are called. The check cannot be made later (say in the body of Iterate), because that could cause the Position cursor to be invalid and potentially cause execution to become erroneous -- defeating the purpose of the check. See Iterate for vectors (A.18.2) for a suggested implementation of the check. End AARM Implementation Note. procedure Iterate_Subtree (Position : in Cursor; Process : not null access procedure (Position : in Cursor)); If Position equals No_Element, then Constraint_Error is propagated. Iterate calls Process.all with a cursor that designates each element in the subtree rooted by the node designated by Position, starting with the node designated by Position and proceeding in a depth-first order. Program_Error is propagated if Process.all tampers with the cursors of Container. Any exception raised by Process.all is propagated. AARM Ramification: Position can be passed a cursor designating the root node; in that case, Process is not called with the root node, which does not have an associated element. function Child_Count (Parent : Cursor) return Count_Type; Child_Count returns the number of child nodes of the node designated by Parent. function Child_Depth (Parent, Child : Cursor) return Count_Type; If Child or Parent is equal to No_Element, then Constraint_Error is propagated. Otherwise, Child_Depth returns the number of ancestor nodes of Child (including Child itself), up to but not including Parent; in this case Program_Error is propagated if Parent is not an ancestor of Child. AARM Ramification: Program_Error is propagated if Parent and Child are nodes in different containers. Child_Depth (Root (Some_Tree), Child) + 1 = Depth (Child) as the root is not counted. End AARM Ramification. procedure Insert_Child (Container : in out Tree; Parent : in Cursor; Before : in Cursor; New_Item : in Element_Type; Count : in Count_Type := 1); If Parent equals No_Element, then Constraint_Error is propagated. If Parent does not designate a node in Container, then Program_Error is propagated. If Before is not equal to No_Element, and does not designate a node in Container, then Program_Error is propagated. If Before is not equal to No_Element, and Container.Parent (Before) is not equal to Parent, then Constraint_Error is propagated. Otherwise, Insert_Child allocates Count nodes containing copies of New_Item and inserts them as children of Parent. If Parent already has child nodes then the new nodes are inserted prior to the node designated by Before, or, if Before equals No_Element, the new nodes are inserted after the last existing child node of Parent. Any exception raised during allocation of internal storage is propagated, and Container is not modified. procedure Insert_Child (Container : in out Tree; Parent : in Cursor; Before : in Cursor; New_Item : in Element_Type; Position : out Cursor; Count : in Count_Type := 1); If Parent equals No_Element, then Constraint_Error is propagated. If Parent does not designate a node in Container, then Program_Error is propagated. If Before is not equal to No_Element, and does not designate a node in Container, then Program_Error is propagated. If Before is not equal to No_Element, and Container.Parent (Before) is not equal to Parent, then Constraint_Error is propagated. Otherwise, Insert_Child allocates Count nodes containing copies of New_Item and inserts them as children of Parent. If Parent already has child nodes then the new nodes are inserted prior to the node designated by Before, or, if Before equals No_Element, the new nodes are inserted after the last existing child node of Parent. Position designates the first newly-inserted node. Any exception raised during allocation of internal storage is propagated, and Container is not modified. procedure Insert_Child (Container : in out Tree; Parent : in Cursor; Before : in Cursor; Position : out Cursor; Count : in Count_Type := 1); If Parent equals No_Element, then Constraint_Error is propagated. If Parent does not designate a node in Container, then Program_Error is propagated. If Before is not equal to No_Element, and does not designate a node in Container, then Program_Error is propagated. If Before is not equal to No_Element, and Container.Parent (Before) is not equal to Parent, then Constraint_Error is propagated. Otherwise, Insert_Child allocates Count nodes, the elements contained in the new nodes are initialized by default (see 3.3.1), and the new nodes are inserted as children of Parent. If Parent already has child nodes then the new nodes are inserted prior to the node designated by Before, or, if Before equals No_Element, the new nodes are inserted after the last existing child node of Parent. Position designates the first newly-inserted node. Any exception raised during allocation of internal storage is propagated, and Container is not modified. procedure Prepend_Child (Container : in out Tree; Parent : in Cursor; New_Item : in Element_Type; Count : in Count_Type := 1); Equivalent to Insert_Child (Container, Parent, First_Child (Container, Parent), New_Item, Count). procedure Append_Child (Container : in out Tree; Parent : in Cursor; New_Item : in Element_Type; Count : in Count_Type := 1); Equivalent to Insert_Child (Container, Parent, Last_Child (Container, Parent), New_Item, Count). procedure Delete_Children (Container : in out Tree; Parent : in Cursor); If Parent equals No_Element, then Constraint_Error is propagated. If Parent does not designate a node in Container, Program_Error is propagated. Otherwise, Delete_Children removes (from Container) all of the child elements of Parent along with their dependent elements. AARM Discussion: This routine deletes all of the child subtrees of Parent at once. Use Delete_Subtree to delete an individual subtree. [Editor's note: We don't provide a Delete_Leaf_Children (which would only delete leaf nodes), even though we do provide such a routine for the entire tree, as it would require pretesting the nodes to verify that they are leaves before doing any deletions, eliminating any performance advantage.] procedure Copy_Subtree (Target : in out Tree; Parent : in Cursor; Before : in Cursor; Source : in Cursor); If Parent equals No_Element, then Constraint_Error is propagated. If Parent does not designate a node in Target, then Program_Error is propagated. If Before is not equal to No_Element, and does not designate a node in Target, then Program_Error is propagated. If Before is not equal to No_Element, and Target.Parent (Before) is not equal to Parent, then Constraint_Error is propagated. If Source designates a root node, then Constraint_Error is propagated. If Source is equal to No_Element, then the operation has no effect. Otherwise, the subtree rooted by Source (which can be from any tree; it does not have to be a subtree of Target) is copied (new nodes are allocated to create a new subtree with the same structure as the Source subtree, with each element initialized from the corresponding element of the Source subtree) and inserted into Target as a child of Parent. If Parent already has child nodes then the new nodes are inserted prior to the node designated by Before, or, if Before equals No_Element, the new nodes are inserted after the last existing child node of Parent. The parent of the newly created subtree is set to Parent, and the overall count of Target is incremented by Subtree_Count (Source). Any exception raised during allocation of internal storage is propagated, and Container is not modified. AARM Discussion: We only need one routine here, as the source object is not modified, so we can use the same routine for both copying within and between containers. AARM Ramification: We do not allow copying a subtree that includes a root node, as that would require inserting a node with no value in the middle of the target tree. To copy an entire tree to another tree object, use Copy. [Editor's note: Normally, we would call the Source parameter "Position"; and Target would be "Container" but that seems to imply that the same container is required for the Source parameter (which it is not).] procedure Splice_Subtree (Target : in out Tree; Parent : in Cursor; Before : in Cursor; Source : in out Tree; Position : in out Cursor); If Parent equals No_Element, then Constraint_Error is propagated. If Parent does not designate a node in Target, then Program_Error is propagated. If Before is not equal to No_Element, and does not designate a node in Target, then Program_Error is propagated. If Before is not equal to No_Element, and Target.Parent (Before) is not equal to Parent, then Constraint_Error is propagated. If Position equals No_Element, Constraint_Error is propagated. If Position does not designate a node in Source or designates a root node, then Program_Error is propagated. If Source denotes the same object as Target, then if Position designates an ancestor of Parent or is equal to Parent, Constraint_Error is propagated; else the subtree rooted by the element designated by Position is moved to be a child of Parent. If Parent already has child nodes then the moved nodes are inserted prior to the node designated by Before, or, if Before equals No_Element, the moved nodes are inserted after the last existing child node of Parent. In each of these cases, Position and the count of Target are unchanged, and the parent of the element designated by Position is set to Parent. AARM Reason: We can't allow moving the subtree of Position to a descendant node of the subtree, as the descendant node will be part of the subtree being moved. The result would be a circularly linked tree, or one with inaccessible nodes. Thus we have to check Position against Parent, even though such a check is O(Depth(Source)). End AARM Reason. Otherwise (if Source does not denote the same object as Target), the subtree designated by Position is removed from Source and moved to Target. The subtree is inserted as a child of Parent. If Parent already has child nodes then the moved nodes are inserted prior to the node designated by Before, or, if Before equals No_Element, the moved nodes are inserted after the last existing child node of Parent. In each of these cases, the count of Target is incremented by Subtree_Count (Position), and the count of Source is decremented by Subtree_Count (Position), Position is updated to represent an element in Target. AARM Ramification: If Source is the same as Target, and Position = Before, or Next_Sibling(Position) = Before, Splice has no effect, as the subtree does not have to move to meet the postcondition. We do not allow splicing a subtree that includes a root node, as that would require inserting a node with no value in the middle of the target tree. Splice the children of the root node instead. For this reason there is no operation to splice an entire tree, as that would necessarily involve splicing a root node. End AARM Ramification. procedure Splice_Subtree (Container: in out Tree; Parent : in Cursor; Before : in Cursor; Position : in Cursor); If Parent equals No_Element, then Constraint_Error is propagated. If Parent does not designate a node in Container, then Program_Error is propagated. If Before is not equal to No_Element, and does not designate a node in Container, then Program_Error is propagated. If Before is not equal to No_Element, and Target.Parent (Before) is not equal to Parent, then Constraint_Error is propagated. If Position equals No_Element or designates a root node, Constraint_Error is propagated. If Position does not designate a node in Container, then Program_Error is propagated. If Position equals Before, there is no effect. If Position designates an ancestor of Parent or is equal to Parent, Constraint_Error is propagated. Otherwise, the subtree rooted by the element designated by Position is moved to be a child of Parent. If Parent already has child nodes then the moved nodes are inserted prior to the node designated by Before, or, if Before equals No_Element, the moved nodes are inserted after the last existing child node of Parent. The parent of the element designated by Position is set to Parent. AARM Reason: We can't allow moving the subtree of Position to a descendant node of the subtree, as the descendant node will be part of the subtree being moved. procedure Splice_Children (Target : in out Tree; Target_Parent : in Cursor; Before : in Cursor; Source : in out Tree; Source_Parent : in Cursor); If Target_Parent equals No_Element, then Constraint_Error is propagated. If Target_Parent does not designate a node in Target, then Program_Error is propagated. If Before is not equal to No_Element, and does not designate an element in Target, then Program_Error is propagated. If Source_Parent equals No_Element, then Constraint_Error is propagated. If Source_Parent does not designate a node in Source, then Program_Error is propagated. If Before is not equal to No_Element, and Target.Parent (Before) is not equal to Target_Parent, then Constraint_Error is propagated. If Source denotes the same object as Target, then: * if Target_Parent equals Source_Parent there is no effect; else * if Source_Parent is an ancestor of Target_Parent, then Constraint_Error is propagated; else * the child elements (and their descendants) of Source_Parent are moved to be child elements of Target_Parent. If Target_Parent already has child elements then the moved elements are inserted prior to the node designated by Before, or, if Before equals No_Element, the moved elements are inserted after the last existing child node of Target_Parent. The parent of each moved child element is set to Target_Parent. AARM Reason: We can't allow moving the children of Source_Parent to a descendant node, as the descendant node will be part of one of the subtrees being moved. Otherwise (if Source does not denote the same object as Target), the child elements (and their descendants) of Source_Parent are removed from Source and moved to Target. The child elements are inserted as children of Target_Parent. If Target_Parent already has child elements then the moved elements are inserted prior to the node designated by Before, or, if Before equals No_Element, the moved elements are inserted after the last existing child node of Target_Parent. In each of these cases, the overall count of Target is incremented by Subtree_Count (Source_Parent)-1, and overall count of Source is decremented by Subtree_Count (Source_Parent)-1. AARM Ramification: The node designated by Source_Parent is not moved, thus we never need to update Source_Parent. Move (Target, Source) could be written Splice_Children (Target, Target.Root, No_Element, Source, Source.Root); End AARM Ramification. procedure Splice_Children (Container : in out Tree; Target_Parent : in Cursor; Before : in Cursor; Source_Parent : in Cursor); If Target_Parent equals No_Element, then Constraint_Error is propagated. If Target_Parent does not designate a node in Container, then Program_Error is propagated. If Before is not equal to No_Element, and does not designate an element in Container, then Program_Error is propagated. If Source_Parent equals No_Element, then Constraint_Error is propagated. If Source_Parent does not designate a node in Container, then Program_Error is propagated. If Before is not equal to No_Element, and Container.Parent (Before) is not equal to Target_Parent, then Constraint_Error is propagated. If Target_Parent equals Source_Parent there is no effect. If Source_Parent is an ancestor of Target_Parent, then Constraint_Error is propagated. Otherwise the child elements (and their descendants) of Source_Parent are moved to be child elements of Target_Parent. If Target_Parent already has child elements then the moved elements are inserted prior to the node designated by Before, or, if Before equals No_Element, the moved elements are inserted after the last existing child node of Target_Parent. The parent of each moved child element is set to Target_Parent. function Parent (Position : Cursor) return Cursor; If Position is equal to No_Element or designates a root node, No_Element is returned. Otherwise, a cursor designating the parent node of the node designated by Position is returned. function First_Child (Parent : Cursor) return Cursor; If Parent is equal to No_Element, then Constraint_Error is propagated. Otherwise, First_Child returns a cursor designating the first child node of the node designated by Parent; if there is no such node, No_Element is returned. function First_Child_Element (Parent : Cursor) return Element_Type; Equivalent to Element (First_Child (Parent)). function Last_Child (Parent : Cursor) return Cursor; If Parent is equal to No_Element, then Constraint_Error is propagated. Otherwise, Last_Child returns a cursor designating the last child node of the node designated by Parent; if there is no such node, No_Element is returned. function Last_Child_Element (Parent : Cursor) return Element_Type; Equivalent to Element (Last_Child (Parent)). function Next_Sibling (Position : Cursor) return Cursor; If Position equals No_Element or designates the last child node of its parent, then Next_Sibling returns the value No_Element. Otherwise, it returns a cursor that designates the successor (with the same parent) of the node designated by Position. function Previous_Sibling (Position : Cursor) return Cursor; If Position equals No_Element or designates the first child node of its parent, then Previous_Sibling returns the value No_Element. Otherwise, it returns a cursor that designates the predecessor (with the same parent) of the node designated by Position. procedure Next_Sibling (Position : in out Cursor); Equivalent to Position := Next_Sibling (Position); procedure Previous_Sibling (Position : in out Cursor); Equivalent to Position := Previous_Sibling (Position); procedure Iterate_Children (Container : in Tree; Parent : in Cursor; Process : not null access procedure (Position : in Cursor)); If Parent equals No_Element, then Constraint_Error is propagated. If Parent does not designate a node in Container, then Program_Error is propagated. Iterate_Children calls Process.all with a cursor that designates each child node of Parent, starting with the first child node and moving the cursor as per the function Next_Sibling. Program_Error is propagated if Process.all tampers with the cursors of Container. Any exception raised by Process.all is propagated. procedure Reverse_Iterate_Children (Container : in Tree; Parent : in Cursor; Process : not null access procedure (Position : in Cursor)); If Parent equals No_Element, then Constraint_Error is propagated. If Parent does not designate a node in Container, then Program_Error is propagated. Reverse_Iterate_Children calls Process.all with a cursor that designates each child node of Parent, starting with the last child node and moving the cursor as per the function Previous_Sibling. Program_Error is propagated if Process.all tampers with the cursors of Container. Any exception raised by Process.all is propagated. Bounded (Run-Time) Errors It is a bounded error for the actual function associated with a generic formal subprogram, when called as part of an operation of this package, to tamper with elements of any Tree parameter to the operation. Either Program_Error is raised, or the operation works as defined on the value of the Tree either prior to, or subsequent to, some or all of the modifications to the Tree. It is a bounded error to call any subprogram declared in the visible part of Containers.Multiway_Trees when the associated container has been finalized. If the operation takes Container as an in out parameter, then it raises Constraint_Error or Program_Error. Otherwise, the operation either proceeds as it would for an empty container, or it raises Constraint_Error or Program_Error. Erroneous Execution A Cursor value is invalid if any of the following have occurred since it was created: * The tree that contains the element it designates has been finalized; * The tree that contains the element it designates has been used as the Source or Target of a call to Move; * The tree that contains the element it designates has been used as the Target of a call to Assign or the target of an assignment_statement; * The element it designates has been removed from the tree that previously contained the element. AARM Discussion: We talk about which tree the element was removed from in order to handle splicing nodes from one tree to another. The node still exists, but any cursors that designate it in the original tree are now invalid. This bullet covers removals caused by calls to Clear, Delete_Leaf, Delete_Subtree, Delete_Children, Splice_Children, and Splice_Subtree. The result of "=" or Has_Element is unspecified if it is called with an invalid cursor parameter. Execution is erroneous if any other subprogram declared in Containers.Multiway_Trees is called with an invalid cursor parameter. AARM Discussion: The list above is intended to be exhaustive. In other cases, a cursor value continues to designate its original element (or the root node). For instance, cursor values survive the insertion and deletion of other nodes. While it is possible to check for these cases, in many cases the overhead necessary to make the check is substantial in time or space. Implementations are encouraged to check for as many of these cases as possible and raise Program_Error if detected. Implementation Requirements No storage associated with a multiway tree object shall be lost upon assignment or scope exit. The execution of an assignment_statement for a tree shall have the effect of copying the elements from the source tree object to the target tree object. AARM Implementation Note: An assignment of a Tree is a "deep" copy; that is the elements are copied as well as the data structures. We say "effect of" in order to allow the implementation to avoid copying elements immediately if it wishes. For instance, an implementation that avoided copying until one of the containers is modified would be allowed. Implementation Advice Containers.Multiway_Trees should be implemented similarly to a multiway tree. In particular, if N is the overall number of nodes for a particular tree, then the worst-case time complexity of Element, Parent, First_Child, Last_Child, Insert_Child with Count=1, and Delete should be O(log N). AARM Reason: We do not mean to overly constrain implementation strategies here. However, it is important for portability that the performance of large containers has roughly the same factors on different implementations. If a program is moved to an implementation that takes O(N) time to access elements, that program could be unusable when the trees are large. We allow O(log N) access because the proportionality constant and caching effects are likely to be larger than the log factor, and we don't want to discourage innovative implementations. Move should not copy elements, and should minimize copying of internal data structures. AARM Implementation Note: Usually that can be accomplished simply by moving the pointer(s) to the internal data structures from the Source container to the Target container. If an exception is propagated from a tree operation, no storage should be lost, nor any elements removed from a tree unless specified by the operation. AARM Reason: This is important so that programs can recover from errors. But we don't want to require heroic efforts, so we just require documentation of cases where this can't be accomplished. A.18.17 The Package Containers.Indefinite_Multiway_Trees The language-defined generic package Containers.Indefinite_Multiway_Trees provides a multiway tree with the same operations as the package Containers.Multiway_Trees (see A.18.10), with the difference that the generic formal Element_Type is indefinite. Static Semantics The declaration of the generic library package Containers.Indefinite_Multiway_Trees has the same contents and semantics as Containers.Multiway_Trees except: * The generic formal Element_Type is indefinite. * The procedure with the profile: procedure Insert_Child (Container : in out Tree; Parent : in Cursor; Before : in Cursor; Position : out Cursor; Count : in Count_Type := 1); is omitted. AARM Discussion: This procedure is omitted because there is no way to create a default-initialized object of an indefinite type. We considered having this routine insert an empty element similar to the empty elements of a vector, but rejected this possibility because the semantics are fairly complex and very different from the existing definite container. That would make it more error-prone to convert a container from a definite type to an indefinite type; by omitting the routine completely, any problems will be diagnosed by the compiler. The actual Element parameter of access subprogram Process of Update_Element may be constrained even if Element_Type is unconstrained. A.18.25 The Package Containers.Bounded_Multiway_Trees The language-defined generic package Containers.Bounded_Multiway_Trees provides a private type Tree and a set of operations. It provides the same operations as the package Containers.Multiway_Trees (see A.18.10), with the difference that the maximum storage is bounded. Static Semantics The declaration of the generic library package Containers.Bounded_Multiway_Trees has the same contents and semantics as Containers.Multiway_Trees except: * pragma Preelaborate is replaced with pragma Pure. AARM implementation note: Package Containers.Bounded_Multiway_Trees cannot depend on package Ada.Finalization (because that package has Preelaborate categorization). * The type Tree is declared with a discriminant that specifies the capacity (maximum number of elements) as follows: type Tree (Capacity : Count_Type) is tagged private; * The type Tree needs finalization if and only if type Element_Type needs finalization. * The allocation of internal storage includes a check that the capacity is not exceeded, and Capacity_Error is raised if this check fails. * In procedure Assign, if Source length is greater than Target capacity, then Capacity_Error is propagated. * Function Copy is declared as follows: function Copy (Source : Tree; Capacity : Count_Type := 0) return Tree; If Capacity is 0, then the tree capacity is the count of Source; if Capacity is equal to or greater than Source.Count, the tree capacity equals the value of the Capacity parameter; otherwise, the operation propagates Capacity_Error. * In the five-parameter procedure Splice_Subtree, if Source is not the same object as Target, and if the sum of Target.Count and Subtree_Count (Position) is greater than Target.Capacity, then Splice_Subtree propagates Capacity_Error. * In the five-parameter procedure Splice_Children, if Source is not the same object as Target, and if the sum of Target.Count and Subtree_Count (Source_Parent)-1 is greater than Target.Capacity, then Splice_Children propagates Capacity_Error. Bounded Errors It is a bounded error to use a bounded tree if it was the target of an assignment_statement whose source was in the middle of an operation that disallows tampering with cursors [Redundant: or elements]. Either Program_Error is raised, or the operation proceeds as defined. Implementation Advice Bounded tree objects should be implemented without implicit pointers or dynamic allocation. The implementation advice for procedure Move to minimize copying does not apply. !discussion The author has found that almost all of his large projects contained a least one multiway tree, as it is an ideal structure for representing nesting. Flatter structures such as the existing containers do not handle nesting well. The name "multiway tree" seems to be the most common one for this data structure in the literature. (It is sometimes written "multi-way tree".) We do not provide an indexing method in this tree; this is purely a structuring data structure. If indexing is needed, a set or map container (already provided by the Ada Containers library) is probably more appropriate. Model: The basic model is that every element in the tree has an associated doubly-linked list of children. Thus we provide almost all of the operations of the list container, each with an added Parent parameter. We did not change the basic names of the operations as they are already familiar to programmers. We added "_Child" to each name in order to describe clearly what is being accessed. We considered leaving the names the same (without the "_Child" suffix), but that seemed confusing, as it would be easy to believe that First always represents the first node of the root of the tree. Having "_Child" as part of the names also opens up the possibility of operations not having the "_Child" suffix. These would naturally operate on the entire tree. We've taken advantage of this to define operations like Find and Iterate over the entire tree. We've added the suffix "_Subtree" to a few operations to make it clear that they operate on entire subtrees, as opposed to a single element. While this isn't strictly necessary, it should enhance readability of code. Elements are stored in internal nodes (as in the linked list containers). There is a built-in root node in each container. The root node does not contain an element value (in order to avoid complications with uninitialized values in the indefinite container forms), and it cannot be deleted. A function Root is provided to retrieve a cursor to the root node. The provision of the root node eliminates many special cases, as it is always the case that the parent cursor of an element containing node is a real cursor containing an (implicit) reference to the container. However, the root node cannot participate in any operations that require element values, including copying or splicing of subtrees. The effect is that the root node contains a list of child subtrees which are really the interesting trees. Thus the children of the root node can be thought of as a forest of trees. This organization allows bottom-up tree creation where subtrees are stashed as separate subtrees of the root until their parent is created (at which time Slice_Children or Slice_Subtree would be used to make it a subtree of the appropriate parent). Without this capability, bottom up tree creation would require the creation of many container objects (each with overhead) and potentially would require copying of the subtrees and elements from one container to another with each step (this is certainly the case for a bounded tree container). We considered using No_Element to represent the root. This however required passing the container to a number of operations like First_Child, and also caused additional wording complications. Note that we added the Assign and Copy routines here as proposed in AI05-0001-1. If we decide not to do that for the other containers, they should be removed here as well. Similarly, we define a bounded form here; in the unlikely event that we decide not to adopt AI05-0001-1, it should be removed here. Operations that were dropped from the list container are: * Delete_First and Delete_Last: These seem unlikely to occur in the context of a tree (as opposed to a sequence like a list), and they're very easy to write yourself if necessary. [Editor's Note: Dropping these are all Matt's idea. :-) Since the names aren't the same anyway (they'd be Delete_First_Child and Delete_Last_Child) the consistency argument doesn't seem to hold much water.] * Swap_Links: Swapping within a single sibling list doesn't seem very useful. Swapping such that the parents get changed on the nodes can be a nightmare to describe and implement correctly: consider swapping two nodes where one is the parent of the other, and additional children are also involved. Exactly which elements end up where?? We do provide Swap, which just swaps the elements and leaves the nodes alone; that doesn't have definitional issues. * Reverse_Find: This is a strange operation, which would be hard to define sensibly. Rather, we provide Ancestor_Find, which has a clear definition. * Reverse_Elements: This seems like a strange operation operating on a single list of children. It makes no sense on the entire container. So we don't provide it. * Sorting operations: It seems weird to sort just a single list of child elements. And it also doesn't make any sense on the entire container. More on the valueless root model: Since the top-level subtrees can be thought of as a forest of trees, it would be easy to build an implementation of the XML DOM directly using one of these tree containers. The XML DOM includes the capability of inserting elements without including them in the main tree. Such "orphan" elements could be modeled as children of the root node, with the first child representing the main tree. We hope that the valueless root model provides the right balance between the orphan model used by the XML DOM and a "classic" single-rooted tree. It appears to keep special cases to a minimum, while not straying too far from the typical terminology of trees. A pure single-roots model seems cleaner on first reflection, but in practice it does not simplify either the wording nor the use of the container. In addition, it can make some operations much more complex. We want the tree container to allow operations as naturally as possible, extra complications are more likely to cause users to abandon the container. If we defined the tree using traditional node insertion, then we would have to have rules for an empty tree and in addition we would need rules to prevent inserting a second root (this would be necessary for at least the various Inserts, Splices, Copy_Subtree). But this is somewhat silly, given that the first operation on every tree object would have to be to insert the one-and-only root. The alternative to allowing the insertion of exactly one root node is to have a root node that is always present, but has an optional value. In this case, inserting any node at the root is always illegal (and can easily be prevented by simply not allowing the Parent parameter to be No_Element). But then, we would have to define the idea of an uninitialized element for just this one node, and we would probably want a way to "unset" the value of this node. Another alternative is to have the root node always present, and have it be default initialized. This would not have as much wording complication, but it would have some annoying effects: * An empty tree would contain one valid element (which could be read); * The value of this extra node would often need to be ignored (if a forest of subtrees is being used) or replaced - this would add many additional opportunities for errors; * The indefinite container form could not use this solution, as there is no default initial value for an indefinite object. The only solution for the last problem would be to revert to the previous alternative for the indefinite forms, making the various forms annoyingly different. In addition, single rooted trees with values have a serious usage problem. That occurs because replacing the root is impossible. The effect is that a tree transformation that needs to change the root is complicated, and a general purpose transformation usually will have to have a special case just to handle the root. One obvious example is the bottom-up tree construction noted earlier. In order to do that in a single-rooted tree, we would have to either use many tree objects (each with associated overhead, and in the case of bounded trees, each Splice_Subtree would have to copy the nodes and elements as well), or we would have to use dummy nodes to construct onto which have to be ignored in later operations. (They couldn't be deleted, because the root node of a single-rooted tree cannot be deleted. You might be able to swap them with the real root node [if that is allowed], but deleting the dummy after that would require relinking all of the child nodes of the real root before the dummy could be deleted, a complex operation.) One example problem is that the dummy nodes would get in the way if/when trees are combined. For instance, consider the expression trees in an Ada compiler. The default expressions of a procedure are likely to be stored in the symbol table; if constructed bottom-up (as they would be using a parser generator like YACC) they would have to have a dummy node at the top if the tree is single rooted. When the default expression is later spliced into a call tree, the dummy node would end up in the middle of the call tree. Care would have to be taken with every operation to avoid this happening. When the root node does not have a value, it cannot be spliced or copied into any tree, so the problems with excess dummy nodes do not occur. Even more annoying would be the complications for doing tree transformations. Consider again the expression trees in an Ada compiler. One common transformation is to change a call to "/=" to not "=". (This transformation has to wait until the compiler knows that the "/=" has a Boolean type, as otherwise it could just be an ordinary function call.) This means that a "not" node has to be inserted before the "=" node. This is easily done with the current package: procedure Change_Not_Equals (Expr : in out Tree; Position : in Cursor) is My_Not : Cursor; begin Insert_Child (Expr, Parent => Parent (Position), Before => No_Element, New_Item => (Kind => A_Not), Position => My_Not); Splice_Subtree (Expr, Parent => My_Not, Before => No_Element, Position => Position); Replace_Element (Position, New_Item => (Kind => Equals)); end Change_Not_Equals; Since the current package does not allow the root node to have a value, Position cannot designate the root node (the root cannot designate a call element, obviously). Thus Insert_Child will add a node somewhere in the tree, but it won't try to add a second root. More generally, tree transformations only involve nodes with values, so they will not (directly) involve the root node. However, this does not work for a single-rooted package allowing values if the Position happens to be at the root (as it would be for the condition of an if statement that consists of a single "/="). That's because inserting a second root is never allowed (else the tree would not be single rooted!). Thus the initial Insert_Child would fail. Instead, the code would have to resort to using unnatural operations like Swap and would have to move all of the children of the root node manually: Insert_Child (Expr, Parent => Position, Before => No_Element, New_Item => (Kind => A_Not), Position => My_Not); Swap (Position, My_Not); Splice_Subtree (Expr, Parent => My_Not, Before => No_Element, Position => First_Child (Position)); Splice_Subtree (Expr, Parent => My_Not, Before => No_Element, Position => First_Child (Position)); Replace_Element (My_Not, New_Item => (Kind => Equals)); This is more complex and much less natural than the initial code. (It would be much more complex if the number of children were not fixed as in this example - imagine a similar transformation on any function call.) It also ends up with the original cursor pointing to a different node than the original code (which might be significant). Iterators: We considered having additional iterators. One could imagine wanting to visit the child nodes before their parents, as well as a straight breadth-first iteration. One could even imagine in-order iteration. One possible way to (re)structure the overall iterator is to have two process routines, one that is called when a node is first visited, and the second after the children are visited. (That would allow a more direct mapping of the HTML example below.) This would look something like: procedure Iterate (Container : in Tree; Process_Before_Children : access procedure (Position : in Cursor); Process_After_Children : access procedure (Position : in Cursor)); (Note that these subprograms are allowed to be null, so that if one or the other isn't needed, it can be skipped.) The biggest objection to such an iterator is that it can't be mapped into for-loop-like syntax as proposed in AI05-0139-2. One also could have a separate Breadth-First iterator. However, since that order requires two trips through the child list (one to visit the child nodes, and one to visit the children of those nodes, it is rather inefficient. It also doesn't make sense for debugging/output applications, so it wasn't clear that it was very useful. Similarly, one could define an In-Order iterator. However, that really only makes sense for binary trees with exactly two children, while a multiway tree can have more or less children. (The interpretation of a node with one child could depend on the node: contrast a "unary minus" node with a "First attribute" node.) So it seems likely that such an iterator would be confusing and not necessarily provide the intended semantics. Moreover, creating iterators by hand is relatively easy to do using cursors. So we don't provide additional iterators. Advantages of the multiway tree container over hand-created trees: (1) Complete memory management, including for indefinite elements. (2) Avoidance of linking mistakes during operations like insertions and deletions. (3) More safety than hand-built structures -- tampering checks and possible dangling pointer (cursor) detection. (4) Completely linked (doubly-linked sibling lists; complete parent and child pointers), so walking in any direction in the tree is always O(1). (5) Built-in iterators. (6) Container users get streaming for free; streaming of a tree is fairly hard to do correctly. Disadvantages of the multiway tree container: (1) Not as familiar as some other containers; may not be used as much for that reason. (Authors can help alleviate this situation somewhat.) (2) Not as general as a general graph structure. But a general graph structure is much harder to describe, it is harder to manage memory for, and is not as commonly needed as a tree. (3) More memory management options are available for hand-created code. But that also provides a lot more opportunities for bugs. !examples (1) Representing HTML: (XML is similar) type Root_HTML_Element is tagged private; function Init_Element return Root_HTML_Element; procedure Output_Prefix (Element : Root_Element_Type); procedure Output_Suffix (Element : Root_Element_Type); type Text is Root_HTML_Element with record The_Text : Unbounded_String; end record; procedure Output_Prefix (Element : Bold_Element_Type); -- Writes The_Text procedure Output_Suffix (Element : Bold_Element_Type); -- Writes nothing. type Bold_HTML_Element is new Root_HTML_Element with null record; procedure Output_Prefix (Element : Bold_Element_Type); -- Writes procedure Output_Suffix (Element : Bold_Element_Type); -- Writes -- Similar overridings of Output_Prefix and Output_Suffix for each type; -- we won't show them here. type Italic_HTML_Element is new Root_HTML_Element with null record; type Div_HTML_Element is new Root_HTML_Element with record Class : Class_Name; Style : Style_String; -- other attributes. end record; type Span_HTML_Element is new Root_HTML_Element with record Class : Class_Name; Style : Style_String; -- other attributes. end record; type Header_HTML_Element is new Root_HTML_Element with null record; type Body_HTML_Element is new Root_HTML_Element with null record; -- and so on. package HTML_Tree is new Ada.Containers.Multiway_Trees (Root_HTML_Element'Class); -- The following HTML code: