-- Abstract : -- -- Utilities for navigating syntax trees produced by an LR parser. -- -- Design : -- -- It would be safer if Cursor contained a pointer to Iterator; then -- Copy and Splice could just take Cursor arguments. But that -- requires mode 'aliased in' for First, Last, which is not -- conformant with Ada.Iterator_Interfaces. -- -- Copyright (C) 2019, 2020, 2022 Stephen Leake All Rights Reserved. -- -- This library is free software; you can redistribute it and/or modify it -- under terms of the GNU General Public License as published by the Free -- Software Foundation; either version 3, or (at your option) any later -- version. This library is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- -- TABILITY or FITNESS FOR A PARTICULAR PURPOSE. -- As a special exception under Section 7 of GPL version 3, you are granted -- additional permissions described in the GCC Runtime Library Exception, -- version 3.1, as published by the Free Software Foundation. pragma License (Modified_GPL); with Ada.Iterator_Interfaces; with SAL.Gen_Unconstrained_Array_Image_Aux; package WisiToken.Syntax_Trees.LR_Utils is procedure Raise_Programmer_Error (Label : in String; Tree : in WisiToken.Syntax_Trees.Tree; Node : in WisiToken.Syntax_Trees.Node_Access); pragma No_Return (Raise_Programmer_Error); ---------- -- List functions -- -- A list has one of the following grammar forms: -- -- list : list element | element ; -- list : element | list element ; -- -- list : list separator element | element ; -- list : element | list separator element ; -- -- In the syntax tree, this looks like: -- -- list: Root -- | list -- | | list -- | | | element: First -- | | separator? -- | | element: 2 -- | separator? -- | element: 3 -- separator? -- element: Last type Constant_List (<>) is tagged private with Constant_Indexing => List_Constant_Ref, Default_Iterator => Iterate_Constant, Iterator_Element => Valid_Node_Access; function Tree (Container : in Constant_List) return Tree_Constant_Reference with Pre => not Container.Is_Invalid; function Is_Invalid (Container : in Constant_List) return Boolean; function Is_Empty (Container : in Constant_List) return Boolean; -- Returns True if Container is invalid, or if Container is empty function Root (Container : in Constant_List) return Node_Access with Pre => not Container.Is_Invalid; function List_ID (Container : in Constant_List) return Token_ID with Pre => not Container.Is_Invalid; function Element_ID (Container : in Constant_List) return Token_ID with Pre => not Container.Is_Invalid; function Count (Container : in Constant_List) return Ada.Containers.Count_Type with Pre => not Container.Is_Invalid; function Contains (Container : in Constant_List; Node : in Valid_Node_Access) return Boolean with Pre => not Container.Is_Invalid; type Cursor is private; No_Element : constant Cursor; function To_Cursor (Container : in Constant_List; Node : in Valid_Node_Access) return Cursor with Pre => (not Container.Is_Invalid) and then (Container.Contains (Node) and Container.Tree.ID (Node) = Container.Element_ID); function Contains (Container : in Constant_List; Item : in Cursor) return Boolean with Pre => not Container.Is_Invalid; function Has_Element (Cursor : in LR_Utils.Cursor) return Boolean; function Element (Cursor : in LR_Utils.Cursor) return Node_Access; -- Invalid_Node_Access if not Has_Element (Cursor). package Iterator_Interfaces is new Ada.Iterator_Interfaces (Cursor, Has_Element); type Iterator (Container : not null access constant Constant_List'Class) is new Iterator_Interfaces.Reversible_Iterator with null record; function First (Container : in Constant_List) return Cursor; function Last (Container : in Constant_List) return Cursor; overriding function First (Iter : in Iterator) return Cursor is (Iter.Container.First); overriding function Last (Iter : in Iterator) return Cursor is (Iter.Container.Last); overriding function Next (Iter : in Iterator; Position : Cursor) return Cursor; overriding function Previous (Iter : in Iterator; Position : Cursor) return Cursor; function List_Constant_Ref (Container : aliased in Constant_List'Class; Position : in Cursor) return Valid_Node_Access with Pre => Has_Element (Position); type Constant_Iterator (Container : not null access constant Constant_List) is new Iterator_Interfaces.Reversible_Iterator with null record; overriding function First (Iter : in Constant_Iterator) return Cursor is (Iter.Container.First); overriding function Last (Iter : in Constant_Iterator) return Cursor is (Iter.Container.Last); overriding function Next (Iter : in Constant_Iterator; Position : Cursor) return Cursor; overriding function Previous (Iter : in Constant_Iterator; Position : Cursor) return Cursor; function Iterate_Constant (Container : aliased in Constant_List'Class) return Constant_Iterator is (Iterator_Interfaces.Reversible_Iterator with Container'Access); type Find_Equal is access function (Target : in String; List : in Constant_List'Class; Node : in Valid_Node_Access) return Boolean; -- Function called by Find to compare Target to Node. Target, List -- are the Find arguments; Node is an element of List. Return True if -- Node matches Target. function Find (Container : in Constant_List; Target : in Valid_Node_Access) return Cursor with Pre => not Container.Is_Invalid and Container.Tree.ID (Target) = Container.Element_ID; function Find (Container : in Constant_List; Target : in String; Equal : in Find_Equal) return Cursor with Pre => not Container.Is_Invalid; type List (<>) is new Constant_List with private with Default_Iterator => Iterate, Iterator_Element => Valid_Node_Access; function Separator_ID (Container : in List) return Token_ID with Pre => not Container.Is_Invalid; function Iterate (Container : aliased in List'Class) return Iterator is (Iterator_Interfaces.Reversible_Iterator with Container'Access); package Creators is -- Nested package so these are not primitive, and don't have to be -- overridden for List. function Create_List (Tree : aliased in out WisiToken.Syntax_Trees.Tree; Root : in Valid_Node_Access; List_ID : in WisiToken.Token_ID; Element_ID : in WisiToken.Token_ID; Separator_ID : in WisiToken.Token_ID) return List with Pre => Tree.Editable and (Tree.Is_Nonterm (Root) and then Tree.Has_Children (Root)) and Tree.ID (Root) = List_ID; -- If there is no separator, set Separator_ID = WisiToken.Invalid_Token_ID -- The list cannot be empty; use Empty_List for an empty list. function Create_List (Tree : aliased in out WisiToken.Syntax_Trees.Tree; Root : in Valid_Node_Access; List_ID : in WisiToken.Token_ID; Element_ID : in WisiToken.Token_ID) return Constant_List with Pre => (Tree.Is_Nonterm (Root) and then Tree.Has_Children (Root)) and Tree.ID (Root) = List_ID; -- The separator is only need when adding new elements. function Create_List (Container : in Constant_List; Tree : aliased in out WisiToken.Syntax_Trees.Tree; Root : in Valid_Node_Access) return Constant_List with Pre => (Container.Tree.Is_Nonterm (Root) and then Container.Tree.Has_Children (Root)) and Container.Tree.ID (Root) = Container.List_ID; -- Same as Create_List, get all other params from Container. -- Need Tree for non-constant view. function Create_List (Container : in out List; Root : in Valid_Node_Access) return List with Pre => (Container.Tree.Is_Nonterm (Root) and then Container.Tree.Has_Children (Root)) and Container.Tree.ID (Root) = Container.List_ID; -- Same as Create_List, get all other params from Container. function Create_From_Element (Tree : aliased in out WisiToken.Syntax_Trees.Tree; Element : in Valid_Node_Access; List_ID : in WisiToken.Token_ID; Element_ID : in WisiToken.Token_ID; Separator_ID : in WisiToken.Token_ID) return List with Pre => Tree.Editable and Tree.ID (Tree.Parent (Element)) = List_ID and Tree.ID (Element) = Element_ID and Tree.ID (Tree.Parent (Element)) = List_ID; -- Same as Create_List, but it first finds the root as an ancestor of -- Element. function Create_From_Element (Container : in out List; Element : in Valid_Node_Access) return List with Pre => Container.Tree.ID (Container.Tree.Parent (Element)) = Container.List_ID and Container.Tree.ID (Element) = Container.Element_ID and Container.Tree.ID (Container.Tree.Parent (Element)) = Container.List_ID; -- Same as Create_From_Element, get all other params from Container. function Create_From_Element (Tree : aliased in out WisiToken.Syntax_Trees.Tree; Element : in Valid_Node_Access; List_ID : in WisiToken.Token_ID; Element_ID : in WisiToken.Token_ID) return Constant_List with Pre => Tree.ID (Tree.Parent (Element)) = List_ID and Tree.ID (Element) = Element_ID and Tree.ID (Tree.Parent (Element)) = List_ID; -- Same as Create_List, but it first finds the root as an ancestor of -- Element. function Invalid_List (Tree : aliased in out WisiToken.Syntax_Trees.Tree) return List; function Invalid_List (Tree : aliased in out WisiToken.Syntax_Trees.Tree) return Constant_List; -- First, Last return empty cursor, count returns 0, all other -- operations fail a precondition check. -- -- Useful when the result should never be used, but must be present, -- as in a conditional expression. function Empty_List (Tree : aliased in out WisiToken.Syntax_Trees.Tree; List_ID : in WisiToken.Token_ID; Multi_Element_RHS : in Natural; Element_ID : in WisiToken.Token_ID; Separator_ID : in WisiToken.Token_ID) return List with Pre => Tree.Editable; -- Result Root returns Invalid_Node_Access; First, Last return empty -- cursor, count returns 0; Append works correctly. function Empty_List (Container : in out List) return List; -- Same as Empty_List, get all other params from Container. end Creators; function Compatible (A, B : in Constant_List'Class) return Boolean; -- True if A and B are not invalid, and all components are the same -- except Root. procedure Append (Container : in out List; New_Element : in Valid_Node_Access) with Pre => not Container.Is_Invalid and then Container.Tree.ID (New_Element) = Container.Element_ID; -- Append New_Item to Container, including Container.Separator_ID if -- it is not Invalid_Token_Index. -- -- If Container was Empty, or if Container.Root has no parent in -- Tree, the modified list has no parent. Otherwise, the parent of -- Container.Root is updated to hold the new Container.Root. procedure Prepend (Container : in out List; New_Element : in Valid_Node_Access) with Pre => not Container.Is_Invalid and then Container.Tree.ID (New_Element) = Container.Element_ID; -- Prepend New_Item to Container, including Container.Separator_ID if -- it is not Invalid_Token_Index. -- -- Container.Root parent is updated. procedure Insert (Container : in out List; New_Element : in Valid_Node_Access; After : in Cursor) with Pre => not Container.Is_Invalid and then (Container.Tree.ID (New_Element) = Container.Element_ID and (After = No_Element or else Container.Contains (After))); -- Insert New_Item into Container after Ater, including -- Container.Separator_ID if it is not Invalid_Token_Index. -- -- If After is No_Element, calls Prepend. -- -- If Container was Empty, or if Container.Root has no parent, the -- modified list has no parent. Otherwise, if After is -- Container.Last, the parent of Container.Root is updated to hold -- the new Container.Root. procedure Copy (Source_List : in Constant_List'Class; Source_First : in Cursor := No_Element; Source_Last : in Cursor := No_Element; Dest_List : in out List'Class; User_Data : in User_Data_Access_Constant) with Pre => Compatible (Source_List, Dest_List); -- Deep copy slice of Source_List, appending to Dest_List. -- -- If First = No_Element, copy from List.First. -- If Last = No_Element, copy thru List.Last. procedure Delete (Container : in out List; Item : in out Cursor) with Pre => Container.Contains (Item); -- Delete Item from Container. Parent of Container.Root is updated -- appropriately. Cursor is set to No_Element. type Skip_Label is (Nested, Skip); type Skip_Item (Label : Skip_Label := Skip) is -- The default values must be valid to allow Skip_Info to be default -- initialized. record Element : Node_Access := null; case Label is when Nested => -- Element is an element in the list currently being copied -- containing a nested list with an element to skip (given by Element -- in the next Skip_Item). The nested list is defined by: List_Root : Valid_Node_Access; List_ID : Token_ID; Element_ID : Token_ID; Separator_ID : Token_ID; Multi_Element_RHS : Natural; when Skip => -- Element is the element in the current list to skip. null; end case; end record; subtype Nested_Skip_Item is Skip_Item (Nested); function Image (Item : in Skip_Item; Descriptor : in WisiToken.Descriptor) return String is ("(" & Item.Label'Image & ", " & Trimmed_Image (Get_Node_Index (Item.Element)) & (case Item.Label is when Nested => "," & Trimmed_Image (Get_Node_Index (Item.List_Root)) & ", " & Image (Item.List_ID, Descriptor), when Skip => "") & ")"); type Skip_Array is array (Positive_Index_Type range <>) of Skip_Item; type Skip_Info (Skip_Last : SAL.Base_Peek_Type) is record -- Skip_Last may be Positive_Index_Type'First - 1 to indicate an -- empty or invalid skip list. Start_List_Root : Node_Access := Invalid_Node_Access; Start_List_ID : Token_ID := Invalid_Token_ID; Start_Element_ID : Token_ID := Invalid_Token_ID; Start_Separator_ID : Token_ID := Invalid_Token_ID; Start_Multi_Element_RHS : Natural := 0; Skips : Skip_Array (Positive_Index_Type'First .. Skip_Last); end record; function Image is new SAL.Gen_Unconstrained_Array_Image_Aux (Positive_Index_Type, Skip_Item, Skip_Array, WisiToken.Descriptor, Image); function Image (Item : in Skip_Info; Descriptor : in WisiToken.Descriptor) return String is ("(" & (if Item.Start_List_ID = Invalid_Token_ID then "" else Trimmed_Image (Get_Node_Index (Item.Start_List_Root)) & ", " & Image (Item.Start_List_ID, Descriptor) & ", " & Image (Item.Skips, Descriptor)) & ")"); function Valid_Skip_List (Tree : aliased in out Syntax_Trees.Tree; Skip_List : in Skip_Array) return Boolean; -- The last element must be Skip, preceding elements must all be -- Nested. The Element in each array element must have ID = preceding -- Element_ID. The net result of all skips must not be empty, unless -- there is only one item (Skip); Start_List_Root may contain only -- that. function Copy_Skip_Nested (Skip_List : in Skip_Info; Tree : aliased in out Syntax_Trees.Tree; User_Data : in User_Data_Access_Constant) return Node_Access with Pre => Tree.Editable and (Skip_List.Start_List_ID /= Invalid_Token_ID and then (Valid_Skip_List (Tree, Skip_List.Skips) and Skip_List.Start_List_ID /= Skip_List.Start_Element_ID)); -- Copy list rooted at Skip_List.Start_List, skipping one element as -- indicated by Skip_List.Skip. Return root of copied list. -- -- Result is Invalid_Node_Access (indicating an empty list) if -- Skip_List has only one item (Skip), and Skip_List.Start_List_Root -- has only that item. -- -- Raises SAL.Programmer_Error if skip item described by Skip_List is -- not found. function List_Root (Tree : in Syntax_Trees.Tree; Node : in Valid_Node_Access; List_ID : in Token_ID) return Valid_Node_Access with Pre => Tree.ID (Node) = List_ID; private type Cursor is record Node : Node_Access; end record; No_Element : constant Cursor := (Node => Invalid_Node_Access); type Constant_List (Tree : not null access WisiToken.Syntax_Trees.Tree) is tagged -- We'd prefer to have Tree be 'constant' here, but then it would -- also be constant in List, where we _don't_ want that. An -- alternative design would be to not derive List from Constant_List; -- then we would would have to duplicate all operations. Another -- alternative design would be to derive Constant_List from List, and -- tighten the constraint on Tree in Constant_List. But that doesn't -- work either. Since most applications using LR_Utils are editing -- the tree anyway, we live with requiring variable access. -- -- The current design also means we can't specify Variable_Indexing -- on List. record Root : WisiToken.Syntax_Trees.Node_Access; List_ID : WisiToken.Token_ID; Element_ID : WisiToken.Token_ID; end record; type List is new Constant_List with record One_Element_RHS : Natural; Multi_Element_RHS : Natural; Separator_ID : WisiToken.Token_ID; end record; function Tree (Container : in Constant_List) return Tree_Constant_Reference is (Element => Container.Tree); function Is_Invalid (Container : in Constant_List) return Boolean is (Container.List_ID = Invalid_Token_ID); function Is_Empty (Container : in Constant_List) return Boolean is (Container.Root = Invalid_Node_Access); function Root (Container : in Constant_List) return Node_Access is (Container.Root); function List_ID (Container : in Constant_List) return Token_ID is (Container.List_ID); function Element_ID (Container : in Constant_List) return Token_ID is (Container.Element_ID); function Has_Element (Cursor : in LR_Utils.Cursor) return Boolean is (Cursor.Node /= Invalid_Node_Access); function Element (Cursor : in LR_Utils.Cursor) return Node_Access is (Cursor.Node); function Separator_ID (Container : in List) return Token_ID is (Container.Separator_ID); function Compatible (A, B : in Constant_List'Class) return Boolean is (A.Tree = B.Tree and A.List_ID /= Invalid_Token_ID and B.List_ID /= Invalid_Token_ID and A.List_ID = B.List_ID and A.Element_ID = B.Element_ID); end WisiToken.Syntax_Trees.LR_Utils;