| ------------------------------------------------------------------------------ |
| -- -- |
| -- GNAT RUN-TIME COMPONENTS -- |
| -- -- |
| -- ADA.STRINGS.TEXT_OUTPUT.UTILS -- |
| -- -- |
| -- B o d y -- |
| -- -- |
| -- Copyright (C) 2020-2021, Free Software Foundation, Inc. -- |
| -- -- |
| -- GNAT is free software; you can redistribute it and/or modify it under -- |
| -- terms of the GNU General Public License as published by the Free Soft- -- |
| -- ware Foundation; either version 3, or (at your option) any later ver- -- |
| -- sion. GNAT is distributed in the hope that it will be useful, but WITH- -- |
| -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -- |
| -- 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. -- |
| -- -- |
| -- You should have received a copy of the GNU General Public License and -- |
| -- a copy of the GCC Runtime Library Exception along with this program; -- |
| -- see the files COPYING3 and COPYING.RUNTIME respectively. If not, see -- |
| -- <http://www.gnu.org/licenses/>. -- |
| -- -- |
| -- GNAT was originally developed by the GNAT team at New York University. -- |
| -- Extensive contributions were provided by Ada Core Technologies Inc. -- |
| -- -- |
| ------------------------------------------------------------------------------ |
| |
| with Ada.Strings.UTF_Encoding.Wide_Wide_Strings; |
| |
| package body Ada.Strings.Text_Output.Utils is |
| |
| procedure Put_Octet (S : in out Sink'Class; Item : Character) with Inline; |
| -- Send a single octet to the current Chunk |
| |
| procedure Adjust_Column (S : in out Sink'Class) with Inline; |
| -- Adjust the column for a non-NL character. |
| |
| procedure Put_UTF_8_Outline (S : in out Sink'Class; Item : UTF_8); |
| -- Out-of-line portion of Put_UTF_8. This exists solely to make Put_UTF_8 |
| -- small enough to reasonably inline it. |
| |
| procedure Full (S : in out Sink'Class) is |
| begin |
| pragma Assert (S.Last = S.Chunk_Length); |
| Full_Method (S); |
| pragma Assert (S.Last = 0); |
| end Full; |
| |
| procedure Flush (S : in out Sink'Class) is |
| begin |
| Flush_Method (S); |
| end Flush; |
| |
| procedure Put_Octet (S : in out Sink'Class; Item : Character) is |
| begin |
| S.Last := S.Last + 1; |
| S.Cur_Chunk.Chars (S.Last) := Item; |
| pragma Assert (S.Chunk_Length = S.Cur_Chunk.Chars'Length); |
| if S.Last = S.Chunk_Length then |
| Full (S); |
| end if; |
| end Put_Octet; |
| |
| procedure Adjust_Column (S : in out Sink'Class) is |
| begin |
| -- If we're in the first column, indent. This is handled here, rather |
| -- than when we see NL, because we don't want spaces in a blank line. |
| -- The character we're about to put is not NL; NL is handled in |
| -- New_Line. So after indenting, we simply increment the Column. |
| |
| if S.Column = 1 then |
| Tab_To_Column (S, S.Indentation + 1); |
| end if; |
| S.Column := S.Column + 1; |
| end Adjust_Column; |
| |
| procedure Put_7bit (S : in out Sink'Class; Item : Character_7) is |
| begin |
| Adjust_Column (S); |
| Put_Octet (S, Item); |
| end Put_7bit; |
| |
| procedure Put_7bit_NL (S : in out Sink'Class; Item : Character_7) is |
| begin |
| if Item = NL then |
| New_Line (S); |
| else |
| Put_7bit (S, Item); |
| end if; |
| end Put_7bit_NL; |
| |
| procedure Put_Character (S : in out Sink'Class; Item : Character) is |
| begin |
| if Character'Pos (Item) < 2**7 then |
| Put_7bit_NL (S, Item); |
| else |
| Put_Wide_Wide_Character (S, To_Wide_Wide (Item)); |
| end if; |
| end Put_Character; |
| |
| procedure Put_Wide_Character |
| (S : in out Sink'Class; Item : Wide_Character) is |
| begin |
| if Wide_Character'Pos (Item) < 2**7 then |
| Put_7bit_NL (S, From_Wide (Item)); |
| else |
| Put_Wide_Wide_Character (S, To_Wide_Wide (Item)); |
| end if; |
| end Put_Wide_Character; |
| |
| procedure Put_Wide_Wide_Character |
| (S : in out Sink'Class; Item : Wide_Wide_Character) is |
| begin |
| if Wide_Wide_Character'Pos (Item) < 2**7 then |
| Put_7bit_NL (S, From_Wide_Wide (Item)); |
| else |
| S.All_7_Bits := False; |
| if Wide_Wide_Character'Pos (Item) >= 2**8 then |
| S.All_8_Bits := False; |
| end if; |
| declare |
| Temp : constant UTF_8_Lines := |
| UTF_Encoding.Wide_Wide_Strings.Encode ((1 => Item)); |
| begin |
| for X of Temp loop |
| pragma Assert (X /= NL); |
| Adjust_Column (S); |
| Put_Octet (S, X); |
| end loop; |
| end; |
| end if; |
| end Put_Wide_Wide_Character; |
| |
| procedure Put_UTF_8_Outline (S : in out Sink'Class; Item : UTF_8) is |
| begin |
| if S.Last + Item'Length = S.Chunk_Length then |
| -- Item fits exactly in current chunk |
| |
| S.Cur_Chunk.Chars (S.Last + 1 .. S.Last + Item'Length) := Item; |
| S.Last := S.Last + Item'Length; |
| S.Column := S.Column + Item'Length; |
| Full (S); |
| -- ???Seems like maybe we shouldn't call Full until we have MORE |
| -- characters. But then we can't pass Chunk_Length => 1 to |
| -- Create_File to get unbuffered output. |
| else |
| -- We get here only if Item doesn't fit in the current chunk, which |
| -- should be fairly rare. We split Item into Left and Right, where |
| -- Left exactly fills the current chunk, and recurse on Left and |
| -- Right. Right will fit into the next chunk unless it's very long, |
| -- so another level of recursion will be extremely rare. |
| |
| declare |
| Left_Length : constant Natural := S.Chunk_Length - S.Last; |
| Right_First : constant Natural := Item'First + Left_Length; |
| Left : UTF_8 renames Item (Item'First .. Right_First - 1); |
| Right : UTF_8 renames Item (Right_First .. Item'Last); |
| pragma Assert (Left & Right = Item); |
| begin |
| Put_UTF_8 (S, Left); -- This will call Full. |
| Put_UTF_8 (S, Right); -- This might call Full, but probably not. |
| end; |
| end if; |
| end Put_UTF_8_Outline; |
| |
| procedure Put_UTF_8 (S : in out Sink'Class; Item : UTF_8) is |
| begin |
| Adjust_Column (S); |
| |
| if S.Last + Item'Length < S.Chunk_Length then |
| -- Item fits in current chunk |
| |
| S.Cur_Chunk.Chars (S.Last + 1 .. S.Last + Item'Length) := Item; |
| S.Last := S.Last + Item'Length; |
| S.Column := S.Column + Item'Length; |
| else |
| Put_UTF_8_Outline (S, Item); |
| end if; |
| end Put_UTF_8; |
| |
| procedure Put_UTF_8_Lines (S : in out Sink'Class; Item : UTF_8_Lines) is |
| Line_Start, Index : Integer := Item'First; |
| -- Needs to be Integer, because Item'First might be negative for empty |
| -- Items. |
| begin |
| while Index <= Item'Last loop |
| if Item (Index) = NL then |
| if Index > Line_Start then |
| Put_UTF_8 (S, Item (Line_Start .. Index - 1)); |
| end if; |
| New_Line (S); |
| Line_Start := Index + 1; |
| end if; |
| |
| Index := Index + 1; |
| end loop; |
| |
| if Index > Line_Start then |
| Put_UTF_8 (S, Item (Line_Start .. Index - 1)); |
| end if; |
| end Put_UTF_8_Lines; |
| |
| procedure Put_String (S : in out Sink'Class; Item : String) is |
| begin |
| for X of Item loop |
| Put_Character (S, X); |
| end loop; |
| end Put_String; |
| |
| procedure Put_Wide_String (S : in out Sink'Class; Item : Wide_String) is |
| begin |
| for X of Item loop |
| Put_Wide_Character (S, X); |
| end loop; |
| end Put_Wide_String; |
| |
| procedure Put_Wide_Wide_String |
| (S : in out Sink'Class; Item : Wide_Wide_String) is |
| begin |
| for X of Item loop |
| Put_Wide_Wide_Character (S, X); |
| end loop; |
| end Put_Wide_Wide_String; |
| |
| procedure New_Line (S : in out Sink'Class) is |
| begin |
| S.Column := 1; |
| Put_Octet (S, NL); |
| end New_Line; |
| |
| function Column (S : Sink'Class) return Positive is (S.Column); |
| |
| procedure Tab_To_Column (S : in out Sink'Class; Column : Positive) is |
| begin |
| if S.Column < Column then |
| for X in 1 .. Column - S.Column loop |
| Put_Octet (S, ' '); |
| end loop; |
| S.Column := Column; |
| end if; |
| end Tab_To_Column; |
| |
| procedure Set_Indentation (S : in out Sink'Class; Amount : Natural) is |
| begin |
| S.Indentation := Amount; |
| end Set_Indentation; |
| |
| function Indentation (S : Sink'Class) return Natural is (S.Indentation); |
| |
| procedure Indent |
| (S : in out Sink'Class; Amount : Optional_Indentation := Default) |
| is |
| By : constant Natural := |
| (if Amount = Default then S.Indent_Amount else Amount); |
| begin |
| Set_Indentation (S, Indentation (S) + By); |
| end Indent; |
| |
| procedure Outdent |
| (S : in out Sink'Class; Amount : Optional_Indentation := Default) |
| is |
| By : constant Natural := |
| (if Amount = Default then S.Indent_Amount else Amount); |
| begin |
| Set_Indentation (S, Indentation (S) - By); |
| end Outdent; |
| |
| end Ada.Strings.Text_Output.Utils; |