-- -- Copyright (C) 2014-2022, AdaCore -- SPDX-License-Identifier: Apache-2.0 -- with Ada.Characters.Conversions; use Ada.Characters.Conversions; with Ada.Containers.Vectors; with Libadalang.Common; use Libadalang.Common; package body Libadalang.Doc_Utils is use XStrings; function Extract_Doc_From (Token : Token_Reference; Backwards : Boolean; Skip_White_Lines : Integer := -1) return Doc_Type; -- Extract documentation from comments starting at ``Token``. If -- ``Backwards`` is ``True``, then search for documentation backwards. -- Skip up to ``Skip_White_Lines`` white lines separating the first doc -- comment from ``Token``. If ``Skip_White_Lines`` is -1, skip any number -- of white lines. -- -- Will raise a ``Property_Error`` if the doc is incorrectly formatted. ---------------------- -- Extract_Doc_From -- ---------------------- function Extract_Doc_From (Token : Token_Reference; Backwards : Boolean; Skip_White_Lines : Integer := -1) return Doc_Type is Tok : Token_Reference := Token; T : XStrings.XString; LF : constant Wide_Wide_Character := To_Wide_Wide_Character (ASCII.LF); procedure Next_Token; -- Set Tok to the token after it (if Backwards is False) or to the token -- before it (if Backwards it True). procedure Next_Token is begin if Backwards then Tok := Previous (Tok); else Tok := Next (Tok); end if; end Next_Token; Ret : Doc_Type; K : Token_Kind; package XString_Vectors is new Ada.Containers.Vectors (Positive, XString); Doc_Vec : XString_Vectors.Vector; begin Next_Token; -- There is no next token: exit if Tok = No_Token then return Ret; end if; if Skip_White_Lines /= 0 and then Kind (Data (Tok)) = Ada_Whitespace then if Skip_White_Lines = -1 then -- If told to skip all white lines, go ahead Next_Token; elsif Skip_White_Lines > 0 then -- If told to skip a certain number of white lines, verify that -- the next token indeed contains said number of white lines. T := To_XString (Common.Text (Tok)); if T.Count (LF) = Skip_White_Lines then Next_Token; end if; end if; end if; -- No comment in the direction expected? There is no doc! if Tok = No_Token or else Kind (Data (Tok)) /= Ada_Comment then return Ret; end if; -- Process as many comments as possible from our starting point, -- until we find an empty line or anything else than a comment or -- a whitespace. while Tok /= No_Token loop K := Kind (Data (Tok)); case K is when Ada_Whitespace => T := To_XString (Common.Text (Tok)); exit when T.Count (LF) > 1; when Ada_Comment => T := To_XString (Common.Text (Tok)); -- Strip potential CR at the end of the line if not T.Is_Empty and then T.Get (T.Length) = Chars.CR then T := T.Slice (1, T.Length - 1); end if; -- Strip the "--" from the comment T := T.Slice (3, T.Length); -- If this is an annotation then if T.Starts_With ("%") then declare -- Try to split on the ":" X : constant XString_Array := T.Split (":"); K : constant Wide_Wide_String := X (1).Slice (2, X (1).Length).Trim.To_String; -- ^ Strip % prefix V : constant Wide_Wide_String := (if X'Length < 2 then raise Property_Error with "Incorrectly formatted docstring" else X (2).Trim.To_String); begin Ret.Annotations.Include (K, V); end; else Doc_Vec.Append (T); end if; when others => exit; end case; Next_Token; end loop; -- Reverse the Doc vector if lines were searched backwards if Backwards then Doc_Vec.Reverse_Elements; end if; -- Transform the doc vector into a string declare Last_Index : constant Natural := Doc_Vec.Last_Index; Offset : Positive := Positive'Last; -- Offset for the leftmost first non whitespace char in all the -- docstring. begin for I in Doc_Vec.First_Index .. Last_Index loop declare L : XString renames Doc_Vec (I); Trimmed : XString renames L.Trim; begin if Trimmed.Length > 0 then Offset := Positive'Min (Offset, L.Length - Trimmed.Length + 1); end if; end; end loop; for I in Doc_Vec.First_Index .. Last_Index loop declare L : XString renames Doc_Vec (I); begin -- Check that every character we're going to strip is a white -- space; else, raise an error. if not L.Is_Empty and then (Offset >= L.Length or else (for some C of L.Slice (1, Offset - 1) => not Is_Space (C))) then raise Property_Error with "Incorrectly formatted docstring"; end if; Ret.Doc.Append (L.Slice (Offset, L.Length)); if I /= Last_Index then Ret.Doc.Append (LF); end if; end; end loop; end; return Ret; end Extract_Doc_From; ----------------------- -- Get_Documentation -- ----------------------- function Get_Documentation (Decl : Basic_Decl) return Doc_Type is Doc : Doc_Type; begin if Decl.Kind = Ada_Generic_Package_Internal then return Get_Documentation (Decl.Parent.As_Basic_Decl); elsif Decl.Kind in Ada_Base_Package_Decl | Ada_Generic_Package_Decl then -- Documentation for packages is assumed to appear before the -- "package" keyword. Doc := Extract_Doc_From (Decl.Token_Start, Backwards => True, Skip_White_Lines => -1); -- If not found and the package is a library unit, search before the -- prelude. if Doc.Doc = Null_XString and then Decl.P_Is_Compilation_Unit_Root then Doc := Extract_Doc_From (Decl.Unit.Root.Token_Start, Backwards => True, Skip_White_Lines => -1); end if; return Doc; else -- Documentation for all other entities is assumed to appear after -- the node representing the entity. return Extract_Doc_From (Decl.Token_End, Backwards => False, Skip_White_Lines => 1); end if; end Get_Documentation; end Libadalang.Doc_Utils;