-- -- Copyright (C) 2014-2022, AdaCore -- SPDX-License-Identifier: Apache-2.0 -- -- .. note:: This unit is internal: only Langkit and Langkit-generated -- libraries are supposed to use it. -- -- This package provides helper on top of the Prettier_Ada library to build -- documents incremenally: create a document, inspect it, possibly modify it, -- and at the end produce the final Prettier_Ada document. with Ada.Containers.Hashed_Maps; with Ada.Containers.Vectors; with Ada.Strings.Unbounded; use Ada.Strings.Unbounded; with GNATCOLL.Traces; with Prettier_Ada.Documents; with Langkit_Support.Generic_API; use Langkit_Support.Generic_API; with Langkit_Support.Generic_API.Analysis; use Langkit_Support.Generic_API.Analysis; with Langkit_Support.Generic_API.Introspection; use Langkit_Support.Generic_API.Introspection; with Langkit_Support.Symbols; use Langkit_Support.Symbols; with Langkit_Support.Text; use Langkit_Support.Text; private package Langkit_Support.Prettier_Utils is package Prettier renames Prettier_Ada.Documents; use type Prettier.Symbol_Type; package Type_Vectors is new Ada.Containers.Vectors (Positive, Type_Ref); function Node_Matches (Node : Lk_Node; Types : Type_Vectors.Vector) return Boolean with Pre => not Node.Is_Null; -- Return whether ``Node`` matches at least one type in ``Types`` -- The Document_Type data structure serves two joint purposes: -- -- * Represent unparsing configuration templates: these contain pure -- formatting directives and Recurse items (i.e. anything but Token -- items). -- -- * Represent actual unparsing documents: tokens and formatting directives -- (i.e. anything but Recurse items). -- -- Formatting directives map the Prettier IR/commands as closely as -- possible, with exceptions (for instance there is no Token or Whitespace -- command in Prettier) that allow us to refine raw unparsing documents, -- for example insert necessary whitespaces/newlines between tokens. ----------------------- -- Template symbols -- ----------------------- type Template_Symbol is new Natural; subtype Some_Template_Symbol is Template_Symbol range 1 .. Template_Symbol'Last; No_Template_Symbol : constant Template_Symbol := 0; -- The following map type is used during templates parsing to validate the -- names used as symbols in JSON templates, and to turn them into their -- internal representation: ``Template_Symbol``. type Symbol_Info is record Source_Name : Unbounded_String; -- Name for this symbol as found in the unparsing configuration Template_Sym : Some_Template_Symbol; -- Unique identifier for this symbol Has_Definition : Boolean; -- Whether we have found one definition for this symbol Is_Referenced : Boolean; -- Whether we have found at least one reference to this symbol end record; package Symbol_Parsing_Maps is new Ada.Containers.Hashed_Maps (Key_Type => Symbol_Type, Element_Type => Symbol_Info, Hash => Hash, Equivalent_Keys => "="); Duplicate_Symbol_Definition : exception; function Declare_Symbol (Source_Name : Unbounded_String; Symbols : Symbol_Table; Symbol_Map : in out Symbol_Parsing_Maps.Map) return Some_Template_Symbol; -- Return the template symbol corresponding to ``Source_Name`` (creating it -- if needed) and mark it as being declared in ``Symbol_Map``. -- -- Raise a ``Duplicate_Symbol_Definition`` exception if that symbol was -- already marked as declared. function Reference_Symbol (Source_Name : Unbounded_String; Symbols : Symbol_Table; Symbol_Map : in out Symbol_Parsing_Maps.Map) return Some_Template_Symbol; -- Return the template symbol corresponding to ``Source_Name`` (creating it -- if needed) and mark it as being referenced in ``Symbol_Map``. function Extract_Definitions (Source : Symbol_Parsing_Maps.Map) return Symbol_Parsing_Maps.Map; -- Return a new map that contains only entries from ``Source`` that have -- the ``Has_Definition`` component set to true, resetting their -- ``Is_Referenced`` component to False. -- -- This is useful when creating the initial symbol map to parse templates -- for (A) node fields or (B) list separators from the map obtained after -- parsing the corresponding (C) node template: symbols defined in (C) can -- be referenced from both (A) and (B), but symbols referenced in (C) must -- be marked as referenced in (A) or (B) only if these templates do -- reference them. type Document_Record; type Document_Type is access all Document_Record; package Document_Vectors is new Ada.Containers.Vectors (Positive, Document_Type); type Matcher_Record is record Matched_Type : Type_Ref; Document : Document_Type; end record; package Matcher_Vectors is new Ada.Containers.Vectors (Index_Type => Positive, Element_Type => Matcher_Record); type Document_Kind is (Align, Break_Parent, Empty_Table_Separator, Expected_Line_Breaks, Expected_Whitespaces, Fill, Flush_Line_Breaks, Group, Hard_Line, Hard_Line_Without_Break_Parent, If_Break, If_Empty, If_Kind, Indent, Line, List, Literal_Line, Recurse, Recurse_Field, Recurse_Flatten, Recurse_Left, Recurse_Right, Soft_Line, Table, Table_Separator, Token, Trim, Whitespace); subtype Template_Document_Kind is Document_Kind with Static_Predicate => Template_Document_Kind not in Expected_Line_Breaks | Expected_Whitespaces | Table; subtype Instantiated_Template_Document_Kind is Document_Kind with Static_Predicate => Instantiated_Template_Document_Kind not in If_Empty | If_Kind | Recurse | Recurse_Field | Recurse_Flatten | Recurse_Left | Recurse_Right; subtype Final_Document_Kind is Instantiated_Template_Document_Kind with Static_Predicate => Final_Document_Kind not in Expected_Line_Breaks | Expected_Whitespaces | Flush_Line_Breaks; type Document_Record (Kind : Document_Kind := Document_Kind'First) is record case Kind is when Align => Align_Data : Prettier.Alignment_Data_Type; Align_Contents : Document_Type; when Break_Parent => null; when Empty_Table_Separator => null; when Expected_Line_Breaks => Expected_Line_Breaks_Count : Positive; when Expected_Whitespaces => Expected_Whitespaces_Count : Positive; when Fill => Fill_Document : Document_Type; when Flush_Line_Breaks => null; when Group => Group_Document : Document_Type; Group_Should_Break : Boolean; Group_Id : Template_Symbol; when Hard_Line => null; when Hard_Line_Without_Break_Parent => null; when If_Break => If_Break_Contents : Document_Type; If_Break_Flat_Contents : Document_Type; If_Break_Group_Id : Template_Symbol; when If_Empty => If_Empty_Then : Document_Type; If_Empty_Else : Document_Type; when If_Kind => If_Kind_Field : Struct_Member_Ref; If_Kind_Matchers : Matcher_Vectors.Vector; If_Kind_Default : Document_Type; If_Kind_Null : Document_Type; when Indent => Indent_Document : Document_Type; when Line => null; when List => List_Documents : Document_Vectors.Vector; when Literal_Line => null; when Recurse => null; when Recurse_Field => Recurse_Field_Ref : Struct_Member_Ref; -- Node field on which to recurse Recurse_Field_Position : Positive; -- 1-based index for this field in the list of fields for the -- owning node. -- -- This information is in theory redundant with the field -- reference, but using an index allows template instantantiation -- code to use an array rather than a map to store information -- related to fields: more simple and probably more efficient. when Recurse_Flatten => Recurse_Flatten_Types : Type_Vectors.Vector; when Recurse_Left | Recurse_Right => null; when Soft_Line => null; when Table => Table_Rows : Document_Vectors.Vector; Table_Must_Break : Boolean; when Table_Separator | Token => Token_Kind : Token_Kind_Ref; Token_Text : Unbounded_Text_Type; when Trim => null; when Whitespace => Whitespace_Length : Positive; end case; end record; function To_Prettier_Document (Document : Document_Type) return Prettier.Document_Type; -- Turn an unparsing document into an actual Prettier document -- Templates have different kinds depending on how they should be -- instantiated: -- -- * No_Template_Kind: Special value to designate the absence of template. -- -- * With_Recurse: Node or field template to instantiate with a single node -- document argument. For node templates, the argument must embed all the -- tokens for that node. For field templates, the argument must embed -- only the unparsing of the node that the field contains. -- -- * With_Recurse_Field: Node template to instantiate with one argument per -- field. -- -- * With_Text_Recurse: Field template to instantiate with a single node -- argument (the field). The argument must embed all the tokens for the -- field (pre/post tokens plus the unparsing of the node that the field -- contains). type Template_Kind is (No_Template_Kind, With_Recurse, With_Recurse_Field, With_Text_Recurse, Join_Template); subtype Some_Template_Kind is Template_Kind range Template_Kind'Succ (Template_Kind'First) .. Template_Kind'Last; type Template_Type (Kind : Template_Kind := No_Template_Kind) is record case Kind is when No_Template_Kind => null; when Some_Template_Kind => Root : Document_Type; -- Root node for this template Symbols : Symbol_Parsing_Maps.Map; -- Symbols that are referenced and defined in this template end case; end record; -- Template document extended with information about how to instantiate it No_Template : constant Template_Type := (Kind => No_Template_Kind); type Document_Pool is tagged private; -- Allocation pool for ``Document_Type`` nodes procedure Release (Self : in out Document_Pool); -- Free all the Document_Type nodes allocated in ``Self`` function Create_Align (Self : in out Document_Pool; Data : Prettier.Alignment_Data_Type; Contents : Document_Type) return Document_Type; -- Return an ``Align`` node function Create_Break_Parent (Self : in out Document_Pool) return Document_Type; -- Return a ``Break_Parent`` node function Create_Empty_Table_Separator (Self : in out Document_Pool) return Document_Type; -- Return an ``Empty_Table_Separator`` node function Create_Expected_Line_Breaks (Self : in out Document_Pool; Count : Positive) return Document_Type; -- Return an ``Expected_Line_Breaks`` node function Create_Expected_Whitespaces (Self : in out Document_Pool; Count : Positive) return Document_Type; -- Return an ``Expected_Whitespaces`` node function Create_Fill (Self : in out Document_Pool; Document : Document_Type) return Document_Type; -- Return a ``Fill`` node function Create_Flush_Line_Breaks (Self : in out Document_Pool) return Document_Type; -- Return a ``Flush_Line_Breaks`` node function Create_Group (Self : in out Document_Pool; Document : Document_Type; Should_Break : Boolean; Id : Template_Symbol) return Document_Type; -- Return a ``Group`` node function Create_Hard_Line (Self : in out Document_Pool) return Document_Type; -- Return a ``Hard_Line`` node function Create_Hard_Line_Without_Break_Parent (Self : in out Document_Pool) return Document_Type; -- Return a ``Hard_Line_Without_Break_Parent`` node function Create_If_Break (Self : in out Document_Pool; Contents : Document_Type; Flat_Contents : Document_Type := null; Group_Id : Template_Symbol := No_Template_Symbol) return Document_Type; -- Return an ``If_Break`` node function Create_If_Empty (Self : in out Document_Pool; Then_Contents : Document_Type; Else_Contents : Document_Type) return Document_Type; -- Return an ``If_Empty`` node function Create_If_Kind (Self : in out Document_Pool; If_Kind_Field : Struct_Member_Ref; If_Kind_Matchers : in out Matcher_Vectors.Vector; If_Kind_Default : Document_Type; If_Kind_Null : Document_Type) return Document_Type; -- Return an ``If_Kind`` node function Create_Indent (Self : in out Document_Pool; Document : Document_Type) return Document_Type; -- Return an ``Indent`` node function Create_Line (Self : in out Document_Pool) return Document_Type; -- Return a ``Line`` node function Create_List (Self : in out Document_Pool; Documents : in out Document_Vectors.Vector) return Document_Type; -- Transfer all nodes in ``Documents`` to a new ``List`` node and return -- that new node. function Create_Literal_Line (Self : in out Document_Pool) return Document_Type; -- Return a ``Literal_Line`` node function Create_Empty_List (Self : in out Document_Pool) return Document_Type; -- Return a new empty ``List`` node function Create_Recurse (Self : in out Document_Pool) return Document_Type; -- Return a ``Recurse`` node function Create_Recurse (Self : in out Document_Pool) return Template_Type; -- Return a ``Recurse`` node wrapped in a ``With_Recurse`` template function Create_Recurse_Field (Self : in out Document_Pool; Field : Struct_Member_Ref; Position : Positive) return Document_Type; -- Return a ``Recurse_Field`` node function Create_Recurse_Flatten (Self : in out Document_Pool; Types : in out Type_Vectors.Vector) return Document_Type; -- Return a ``Recurse_Flatten`` node function Create_Recurse_Left (Self : in out Document_Pool) return Document_Type; -- Return a ``Recurse_Left`` node function Create_Recurse_Right (Self : in out Document_Pool) return Document_Type; -- Return a ``Recurse_Right`` node function Create_Table (Self : in out Document_Pool; Rows : in out Document_Vectors.Vector; Must_Break : Boolean) return Document_Type; -- Return a ``Table`` node function Create_Table_Separator (Self : in out Document_Pool; Kind : Token_Kind_Ref; Text : Unbounded_Text_Type) return Document_Type; -- Return a ``Table_Separator`` node function Create_Soft_Line (Self : in out Document_Pool) return Document_Type; -- Return a ``Soft_Line`` node function Create_Token (Self : in out Document_Pool; Kind : Token_Kind_Ref; Text : Unbounded_Text_Type) return Document_Type; -- Return a ``Token`` node function Create_Trim (Self : in out Document_Pool) return Document_Type; -- Return a ``Trim`` node function Create_Whitespace (Self : in out Document_Pool; Length : Positive := 1) return Document_Type; -- Return a ``Whitespace`` node for the given length procedure Detect_Broken_Groups (Self : in out Document_Type; Max_Empty_Lines : Integer); -- Set the Group_Should_Break flag for all groups that can be statically -- proven to be broken. -- -- See ``Unparsing_Configuration_Record.Max_Empty_Lines`` for the semantics -- of ``Max_Empty_Lines``. procedure Dump (Document : Document_Type; Trace : GNATCOLL.Traces.Trace_Handle := null); -- Debug helper: dump a textual representation of ``Document`` on the given -- trace (do nothing if the trace is disabled), or on the standard output -- (if ``Trace`` is null). ------------- -- Spacing -- ------------- type Spacing_Kind is (None, Whitespaces, Line_Breaks); -- Spacing required between two tokens: -- -- * ``None``: no spacing required, the two tokens can be unparsed next to -- each other in the source buffer (spacing is permitted, but not -- necessary). -- -- * ``Whitespaces``: a given number of whitespaces is required after the -- first token. Note that one line break satisfies an arbitrary number of -- required whitespaces. -- -- * ``Line_Breaks``: a given number of line breaks is required right after -- the first token. Extra spacing is permitted after that line break. subtype Some_Spacing_Kind is Spacing_Kind range Whitespaces .. Line_Breaks; type Spacing_Type (Kind : Spacing_Kind := Spacing_Kind'First) is record case Kind is when None => null; when Whitespaces | Line_Breaks => Count : Positive; end case; end record; No_Spacing : constant Spacing_Type := (Kind => None); One_Whitespace_Spacing : constant Spacing_Type := (Kind => Whitespaces, Count => 1); One_Line_Break_Spacing : constant Spacing_Type := (Kind => Line_Breaks, Count => 1); -- There is a total order for all possible Spacing_Type values: -- -- * ``No_Spacing`` is the weakest spacing requirement. -- * ``One_Whitespace_Spacing`` is the second weakest. -- * ``(Whitespaces, 2)`` comes third. -- * ... -- * ``One_Line_Break_Spacing`` is stronger than all whitespaces -- requirements. -- * Then comes ``(Line_Breaks, 2)``. -- * ... and so on. function "<" (Left, Right : Spacing_Type) return Boolean; function "<=" (Left, Right : Spacing_Type) return Boolean is (Left < Right or else Left = Right); function Max_Spacing (Left, Right : Spacing_Type) return Spacing_Type is (if Left < Right then Right else Left); function Min_Spacing (Left, Right : Spacing_Type) return Spacing_Type is (if Left < Right then Left else Right); function Required_Spacing (Left, Right : Token_Kind_Ref) return Spacing_Type; -- Return the spacing that is required when unparsing a token of kind -- ``Right`` just after a token of kind ``Left`` to a source buffer. -- -- For convenience, ``Required_Spacing`` is allowed to be -- ``No_Token_Kind_Ref``: the result is always ``None`` in this case. The -- intended use case for this is when processing the first token to unparse -- to a source buffer: ``Left`` is ``No_Token_Kind_Ref`` (no token were -- unprase in the source buffer yet) and ``Right`` is the first token to -- unparse to the source buffer. function Required_Line_Breaks (Self : Spacing_Type; Max_Empty_Lines : Integer) return Natural; -- Return the number of line breaks that ``Self`` implies, within the limit -- implied by ``Max_Empty_Lines``. procedure Extend_Spacing (Self : in out Spacing_Type; Requirement : Spacing_Type); -- Shortcut for:: -- -- Self := Max_Spacing (Self, Requirement); procedure Insert_Required_Spacing (Pool : in out Document_Pool; Document : in out Document_Type; Max_Empty_Lines : Integer); -- Adjust the tree of nodes in ``Document`` so that formatting that -- unparsing document will leave the mandatory spacing between tokens (i.e. -- so that the formatted document can be re-parsed correctly). -- -- See ``Unparsing_Configuration_Record.Max_Empty_Lines`` for the semantics -- of ``Max_Empty_Lines``. private type Document_Pool is new Document_Vectors.Vector with null record; procedure Register (Self : in out Document_Pool; Document : Document_Type); -- Register ``Document`` as allocated by ``Self`` end Langkit_Support.Prettier_Utils;