blob: 72cc03fadb5a6300e45cd5d1b0fd6ba3d4dda07d [file] [log] [blame]
------------------------------------------------------------------------------
-- --
-- GNAT COMPILER COMPONENTS --
-- --
-- E R R O U T C . P R E T T Y _ E M I T T E R --
-- --
-- B o d y --
-- --
-- Copyright (C) 1992-2025, 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. See the GNU General Public License --
-- for more details. You should have received a copy of the GNU General --
-- Public License distributed with GNAT; see file COPYING3. If not, go to --
-- http://www.gnu.org/licenses for a complete copy of the license. --
-- --
-- GNAT was originally developed by the GNAT team at New York University. --
-- Extensive contributions were provided by Ada Core Technologies Inc. --
-- --
------------------------------------------------------------------------------
with Namet; use Namet;
with Opt; use Opt;
with Output; use Output;
with Sinput; use Sinput;
with GNAT.Lists; use GNAT.Lists;
package body Erroutc.Pretty_Emitter is
REGION_OFFSET : constant := 1;
-- Number of characters between the line bar and the region span
REGION_ARM_SIZE : constant := 2;
-- Number of characters on the region span arms
-- e.g. two for this case:
-- +--
-- |
-- +--
-- ^^
REGION_SIZE : constant := REGION_OFFSET + 1 + REGION_ARM_SIZE;
-- The total number of characters taken up by the region span characters
MAX_BAR_POS : constant := 7;
-- The maximum position of the line bar from the start of the line
procedure Destroy (Elem : in out Labeled_Span_Type);
pragma Inline (Destroy);
procedure Destroy (Elem : in out Labeled_Span_Type) is
begin
-- Diagnostic elements will be freed when all the diagnostics have been
-- emitted.
null;
end Destroy;
package Labeled_Span_Lists is new Doubly_Linked_Lists
(Element_Type => Labeled_Span_Type,
"=" => "=",
Destroy_Element => Destroy,
Check_Tampering => False);
subtype Labeled_Span_List is Labeled_Span_Lists.Doubly_Linked_List;
type Printable_Line is record
First : Source_Ptr;
-- The first character of the line
Last : Source_Ptr;
-- The last character of the line
Line_Nr : Pos;
-- The line number
Spans : Labeled_Span_List;
-- The spans applied on the line
end record;
procedure Destroy (Elem : in out Printable_Line);
pragma Inline (Destroy);
function Equals (L, R : Printable_Line) return Boolean is
(L.Line_Nr = R.Line_Nr);
package Lines_Lists is new Doubly_Linked_Lists
(Element_Type => Printable_Line,
"=" => Equals,
Destroy_Element => Destroy,
Check_Tampering => False);
subtype Lines_List is Lines_Lists.Doubly_Linked_List;
type File_Sections is record
File : String_Ptr;
-- Name of the file
Ptr : Source_Ptr;
-- Pointer to the Primary location in the file section that is printed
-- at the start of the file section. If there are none then the first
-- location in the section.
Lines : Lines_List;
-- Lines to be printed for the file
end record;
procedure Destroy (Elem : in out File_Sections);
pragma Inline (Destroy);
function Equals (L, R : File_Sections) return Boolean is
(L.File /= null and then R.File /= null and then L.File.all = R.File.all);
package File_Section_Lists is new Doubly_Linked_Lists
(Element_Type => File_Sections,
"=" => Equals,
Destroy_Element => Destroy,
Check_Tampering => False);
subtype File_Section_List is File_Section_Lists.Doubly_Linked_List;
function Create_File_Sections
(Locations : Labeled_Span_Id) return File_Section_List;
-- Create a list of file sections from the labeled spans that are to be
-- printed.
--
-- Each file section contains a list of lines that are to be printed for
-- the file and the spans that are applied to each of those lines.
procedure Create_File_Section
(Sections : in out File_Section_List; Loc : Labeled_Span_Type);
-- Create a new file section for the given labeled span.
procedure Add_Printable_Line
(Lines : Lines_List; Loc : Labeled_Span_Type; S_Ptr : Source_Ptr);
procedure Create_Printable_Line
(Lines : Lines_List; Loc : Labeled_Span_Type; S_Ptr : Source_Ptr);
-- Create a new printable line for the given labeled span and add it in the
-- correct position to the Lines list based on the line number.
function Get_Region_Span
(Spans : Labeled_Span_List) return Labeled_Span_Type;
function Has_Multiple_Labeled_Spans (L : Printable_Line) return Boolean;
procedure Write_Region_Delimiter (SGR_Code : String);
-- Write the arms signifying the start and end of a region span
-- e.g. +--
procedure Write_Region_Bar (SGR_Code : String);
-- Write the bar signifying the continuation of a region span
-- e.g. |
procedure Write_Region_Continuation (SGR_Code : String);
-- Write the continuation signifying the continuation of a region span
-- e.g. :
procedure Write_Region_Offset;
-- Write a number of whitespaces equal to the size of the region span
function Trimmed_Image (I : Natural) return String;
-- Removes the leading whitespace from the 'Image of a Natural number.
procedure Write_Span_Labels
(Loc : Labeled_Span_Type;
L : Printable_Line;
Line_Size : Integer;
Idx : String;
Within_Region_Span : Boolean;
SGR_Code : String;
Region_Span_SGR_Code : String);
procedure Write_File_Section
(Sec : File_Sections;
Write_File_Name : Boolean;
File_Name_Offset : Integer;
Include_Spans : Boolean;
SGR_Code : String := SGR_Note);
-- Prints the labled spans for a given File_Section.
--
-- --> <File_Section.File_Name>
-- <Labeled_Spans inside the file>
procedure Write_Labeled_Spans
(Locations : Labeled_Span_Id;
Write_File_Name : Boolean;
File_Name_Offset : Integer;
Include_Spans : Boolean := True;
SGR_Code : String := SGR_Note);
-- Pretty-prints all of the code regions indicated by the Locations. The
-- labeled spans in the Locations are grouped by file into File_Sections
-- and sorted by the file name of the Primary location followed by all
-- other locations sorted alphabetically.
procedure Write_Intersecting_Labels
(Intersecting_Labels : Labeled_Span_List; SGR_Code : String);
-- Prints the indices and their associated labels of intersecting labels.
--
-- Labeled spans that are insercting on the same line are printed without
-- labels. Instead the span pointer is replaced by an index number and in
-- the end all of the indices are printed with their associated labels.
--
--
-- 42 | [for I in V1.First_Index .. V1.Last_Index => V1(I), -6];
-- | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- | 1-
-- | 2-------------------------------------------
-- | 1: positional element
-- | 2: named element
function Get_Line_End
(Buf : Source_Buffer_Ptr; Loc : Source_Ptr) return Source_Ptr;
-- Get the source location for the end of the line (LF) in Buf for Loc. If
-- Loc is past the end of Buf already, return Buf'Last.
function Get_Line_Start
(Buf : Source_Buffer_Ptr; Loc : Source_Ptr) return Source_Ptr;
-- Get the source location for the start of the line in Buf for Loc
function Get_First_Line_Char
(Buf : Source_Buffer_Ptr; Loc : Source_Ptr) return Source_Ptr;
-- Get first non-space character in the line containing Loc
function Get_Last_Line_Char
(Buf : Source_Buffer_Ptr; Loc : Source_Ptr) return Source_Ptr;
-- Get last non line end [LF, CR] character in the line containing Loc
function Image (X : Positive; Width : Positive) return String;
-- Output number X over Width characters, with whitespace padding.
-- Only output the low-order Width digits of X, if X is larger than
-- Width digits.
procedure Write_Buffer
(Buf : Source_Buffer_Ptr; First : Source_Ptr; Last : Source_Ptr);
-- Output the characters from First to Last position in Buf, using
-- Write_Buffer_Char.
procedure Write_Buffer_Char (Buf : Source_Buffer_Ptr; Loc : Source_Ptr);
-- Output the characters at position Loc in Buf, translating ASCII.HT
-- in a suitable number of spaces so that the output is not modified
-- by starting in a different column that 1.
procedure Write_Line_Marker (Num : Pos; Width : Positive);
-- Attempts to write the line number within Width number of whitespaces
-- followed by a bar ':' symbol.
--
-- e.g ' 12 |'
--
-- This is usually used on source code lines that are marked by a span.
procedure Write_Empty_Bar_Line (Width : Integer);
-- Writes Width number of whitespaces and a bar '|' symbol.
--
-- e.g ' |'
--
-- This is usually used on lines where the label is going to printed.
procedure Write_Empty_Skip_Line (Width : Integer);
-- Writes Width number of whitespaces and a bar ':' symbol.
--
-- e.g ' :'
--
-- This is usually used between non-continous source lines that neec to be
-- printed.
procedure Write_Error_Msg_Line (E_Msg : Error_Msg_Object);
-- Write the error message line for the given diagnostic:
--
-- '['<Diag.Id>']' <Diag.Kind>: <Diag.Message> ['['<Diag.Switch>']']
function Should_Write_File_Name
(Sub_Diag : Error_Msg_Object; Diag : Error_Msg_Object) return Boolean;
-- If the sub-diagnostic and the main diagnostic only point to the same
-- file then there is no reason to add the file name to the sub-diagnostic.
function Should_Write_Spans
(Sub_Diag : Error_Msg_Object; Diag : Error_Msg_Object) return Boolean;
-- Old sub-diagnostics used to have the same location as the main
-- diagnostic in order to group them correctly. However in most cases
-- it was not meant to point to a location but rather add an additional
-- message to the original diagnostic.
--
-- If the sub-diagnostic and the main diagnostic have the same location
-- then we should avoid printing the spans.
procedure Print_Diagnostic (E : Error_Msg_Id);
-- Entry point for printing a primary diagnostic message.
procedure Print_Edit (Edit : Edit_Type; Offset : Integer);
-- Prints an edit object as follows:
--
-- --> <File_Name>
-- -<Line_Nr> <Old_Line>
-- +<Line_Nr> <New_Line>
procedure Print_Fix (Fix : Fix_Type; Offset : Integer);
-- Prints a fix object as follows
--
-- + Fix: <Fix.Description>
-- <Fix.Edits>
procedure Print_Sub_Diagnostic
(Sub_Diag : Error_Msg_Object; Diag : Error_Msg_Object; Offset : Integer);
function To_String (Sptr : Source_Ptr) return String;
-- Convert the source pointer to a string of the form: "file:line:column"
function To_File_Name (Sptr : Source_Ptr) return String;
-- Converts the file name of the Sptr to a string.
function Line_To_String (Sptr : Source_Ptr) return String;
-- Converts the logical line number of the Sptr to a string.
function Column_To_String (Sptr : Source_Ptr) return String;
-- Converts the column number of the Sptr to a string. Column values less
-- than 10 are prefixed with a 0.
-------------
-- Destroy --
-------------
procedure Destroy (Elem : in out Printable_Line) is
begin
Labeled_Span_Lists.Destroy (Elem.Spans);
end Destroy;
-------------
-- Destroy --
-------------
procedure Destroy (Elem : in out File_Sections) is
begin
Free (Elem.File);
Lines_Lists.Destroy (Elem.Lines);
end Destroy;
------------------
-- Get_Line_End --
------------------
function Get_Line_End
(Buf : Source_Buffer_Ptr; Loc : Source_Ptr) return Source_Ptr
is
Cur_Loc : Source_Ptr := Source_Ptr'Min (Loc, Buf'Last);
begin
while Cur_Loc < Buf'Last and then Buf (Cur_Loc) /= ASCII.LF loop
Cur_Loc := Cur_Loc + 1;
end loop;
return Cur_Loc;
end Get_Line_End;
--------------------
-- Get_Line_Start --
--------------------
function Get_Line_Start
(Buf : Source_Buffer_Ptr; Loc : Source_Ptr) return Source_Ptr
is
Cur_Loc : Source_Ptr := Loc;
begin
while Cur_Loc > Buf'First and then Buf (Cur_Loc - 1) /= ASCII.LF loop
Cur_Loc := Cur_Loc - 1;
end loop;
return Cur_Loc;
end Get_Line_Start;
-------------------------
-- Get_First_Line_Char --
-------------------------
function Get_First_Line_Char
(Buf : Source_Buffer_Ptr; Loc : Source_Ptr) return Source_Ptr
is
Cur_Loc : Source_Ptr := Get_Line_Start (Buf, Loc);
begin
while Cur_Loc < Buf'Last and then Buf (Cur_Loc) = ' ' loop
Cur_Loc := Cur_Loc + 1;
end loop;
return Cur_Loc;
end Get_First_Line_Char;
------------------------
-- Get_Last_Line_Char --
------------------------
function Get_Last_Line_Char
(Buf : Source_Buffer_Ptr; Loc : Source_Ptr) return Source_Ptr
is
Cur_Loc : Source_Ptr := Get_Line_End (Buf, Loc);
begin
while Cur_Loc > Buf'First
and then Buf (Cur_Loc) in ASCII.LF | ASCII.CR
loop
Cur_Loc := Cur_Loc - 1;
end loop;
return Cur_Loc;
end Get_Last_Line_Char;
-----------
-- Image --
-----------
function Image (X : Positive; Width : Positive) return String is
Str : String (1 .. Width);
Curr : Natural := X;
begin
for J in reverse 1 .. Width loop
if Curr > 0 then
Str (J) := Character'Val (Character'Pos ('0') + Curr mod 10);
Curr := Curr / 10;
else
Str (J) := ' ';
end if;
end loop;
return Str;
end Image;
--------------------------------
-- Has_Multiple_Labeled_Spans --
--------------------------------
function Has_Multiple_Labeled_Spans (L : Printable_Line) return Boolean is
Count : Natural := 0;
Loc : Labeled_Span_Type;
Loc_It : Labeled_Span_Lists.Iterator :=
Labeled_Span_Lists.Iterate (L.Spans);
begin
while Labeled_Span_Lists.Has_Next (Loc_It) loop
Labeled_Span_Lists.Next (Loc_It, Loc);
if Loc.Label /= null then
Count := Count + 1;
end if;
end loop;
return Count > 1;
end Has_Multiple_Labeled_Spans;
---------------------
-- Get_Region_Span --
---------------------
function Get_Region_Span
(Spans : Labeled_Span_List) return Labeled_Span_Type
is
Loc : Labeled_Span_Type;
Loc_It : Labeled_Span_Lists.Iterator :=
Labeled_Span_Lists.Iterate (Spans);
begin
while Labeled_Span_Lists.Has_Next (Loc_It) loop
Labeled_Span_Lists.Next (Loc_It, Loc);
if Loc.Is_Region then
return Loc;
end if;
end loop;
return No_Labeled_Span_Object;
end Get_Region_Span;
------------------
-- Write_Buffer --
------------------
procedure Write_Buffer
(Buf : Source_Buffer_Ptr; First : Source_Ptr; Last : Source_Ptr)
is
begin
for Loc in First .. Last loop
Write_Buffer_Char (Buf, Loc);
end loop;
end Write_Buffer;
-----------------------
-- Write_Buffer_Char --
-----------------------
procedure Write_Buffer_Char (Buf : Source_Buffer_Ptr; Loc : Source_Ptr) is
begin
-- If the character ASCII.HT is not the last one in the file,
-- output as many spaces as the character represents in the
-- original source file.
if Buf (Loc) = ASCII.HT and then Loc < Buf'Last then
for X in Get_Column_Number (Loc) .. Get_Column_Number (Loc + 1) - 1
loop
Write_Char (' ');
end loop;
-- Otherwise output the character itself
else
Write_Char (Buf (Loc));
end if;
end Write_Buffer_Char;
-----------------------
-- Write_Line_Marker --
-----------------------
procedure Write_Line_Marker (Num : Pos; Width : Positive) is
begin
Write_Str (Image (Positive (Num), Width => Width - 2));
Write_Str (" |");
end Write_Line_Marker;
--------------------------
-- Write_Empty_Bar_Line --
--------------------------
procedure Write_Empty_Bar_Line (Width : Integer) is
begin
Write_Str (String'(1 .. Width - 1 => ' '));
Write_Str ("|");
end Write_Empty_Bar_Line;
---------------------------
-- Write_Empty_Skip_Line --
---------------------------
procedure Write_Empty_Skip_Line (Width : Integer) is
begin
Write_Str (String'(1 .. Width - 1 => ' '));
Write_Str (":");
end Write_Empty_Skip_Line;
----------------------------
-- Write_Region_Delimiter --
----------------------------
procedure Write_Region_Delimiter (SGR_Code : String) is
begin
Write_Str (String'(1 .. REGION_OFFSET => ' '));
Write_Str (SGR_Code);
Write_Str ("+");
Write_Str (String'(1 .. REGION_ARM_SIZE => '-'));
Write_Str (SGR_Reset);
end Write_Region_Delimiter;
----------------------
-- Write_Region_Bar --
----------------------
procedure Write_Region_Bar (SGR_Code : String) is
begin
Write_Str (String'(1 .. REGION_OFFSET => ' '));
Write_Str (SGR_Code);
Write_Str ("|");
Write_Str (SGR_Reset);
Write_Str (String'(1 .. REGION_ARM_SIZE => ' '));
end Write_Region_Bar;
-------------------------------
-- Write_Region_Continuation --
-------------------------------
procedure Write_Region_Continuation (SGR_Code : String) is
begin
Write_Str (String'(1 .. REGION_OFFSET => ' '));
Write_Str (SGR_Code);
Write_Str (":");
Write_Str (SGR_Reset);
Write_Str (String'(1 .. REGION_ARM_SIZE => ' '));
end Write_Region_Continuation;
-------------------------
-- Write_Region_Offset --
-------------------------
procedure Write_Region_Offset is
begin
Write_Str (String'(1 .. REGION_SIZE => ' '));
end Write_Region_Offset;
------------------------
-- Add_Printable_Line --
------------------------
procedure Add_Printable_Line
(Lines : Lines_List;
Loc : Labeled_Span_Type;
S_Ptr : Source_Ptr)
is
L : Printable_Line;
L_It : Lines_Lists.Iterator;
Line_Ptr : constant Pos := Pos (Get_Physical_Line_Number (S_Ptr));
Line_Found : Boolean := False;
begin
L_It := Lines_Lists.Iterate (Lines);
while Lines_Lists.Has_Next (L_It) loop
Lines_Lists.Next (L_It, L);
if not Line_Found and then L.Line_Nr = Line_Ptr then
if not Labeled_Span_Lists.Contains (L.Spans, Loc) then
Labeled_Span_Lists.Append (L.Spans, Loc);
end if;
Line_Found := True;
end if;
end loop;
if not Line_Found then
Create_Printable_Line (Lines, Loc, S_Ptr);
end if;
end Add_Printable_Line;
---------------------------
-- Create_Printable_Line --
---------------------------
procedure Create_Printable_Line
(Lines : Lines_List; Loc : Labeled_Span_Type; S_Ptr : Source_Ptr)
is
Spans : constant Labeled_Span_List := Labeled_Span_Lists.Create;
Buf : constant Source_Buffer_Ptr :=
Source_Text (Get_Source_File_Index (S_Ptr));
Line_Nr : constant Pos := Pos (Get_Physical_Line_Number (S_Ptr));
New_Line : constant Printable_Line :=
(First => Get_Line_Start (Buf, S_Ptr),
Last => Get_Line_End (Buf, S_Ptr),
Line_Nr => Line_Nr,
Spans => Spans);
L : Printable_Line;
L_It : Lines_Lists.Iterator := Lines_Lists.Iterate (Lines);
Found_Greater_Line : Boolean := False;
Insert_Before_Line : Printable_Line;
begin
Labeled_Span_Lists.Append (Spans, Loc);
-- Insert the new line based on the line number
while Lines_Lists.Has_Next (L_It) loop
Lines_Lists.Next (L_It, L);
if not Found_Greater_Line and then L.Line_Nr > New_Line.Line_Nr then
Found_Greater_Line := True;
Insert_Before_Line := L;
Lines_Lists.Insert_Before (Lines, Insert_Before_Line, New_Line);
end if;
end loop;
-- Insert after all the lines have been iterated over to avoid the
-- mutation lock in GNAT.Lists.
if not Found_Greater_Line then
Lines_Lists.Append (Lines, New_Line);
end if;
end Create_Printable_Line;
-------------------------
-- Create_File_Section --
-------------------------
procedure Create_File_Section
(Sections : in out File_Section_List; Loc : Labeled_Span_Type)
is
Lines : constant Lines_List := Lines_Lists.Create;
-- Carret positions
Ptr : constant Source_Ptr := Loc.Span.Ptr;
Line_Ptr : constant Pos := Pos (Get_Physical_Line_Number (Ptr));
-- Span start positions
Fst : constant Source_Ptr := Loc.Span.First;
Line_Fst : constant Pos := Pos (Get_Physical_Line_Number (Fst));
-- Span end positions
Lst : constant Source_Ptr := Loc.Span.Last;
Line_Lst : constant Pos := Pos (Get_Physical_Line_Number (Lst));
begin
Create_Printable_Line (Lines, Loc, Fst);
if Line_Fst /= Line_Ptr then
Create_Printable_Line (Lines, Loc, Ptr);
end if;
if Line_Ptr /= Line_Lst then
Create_Printable_Line (Lines, Loc, Lst);
end if;
File_Section_Lists.Append
(Sections,
(File => new String'(To_File_Name (Loc.Span.Ptr)),
Ptr => Loc.Span.Ptr,
Lines => Lines));
end Create_File_Section;
--------------------------
-- Create_File_Sections --
--------------------------
function Create_File_Sections
(Locations : Labeled_Span_Id) return File_Section_List
is
Loc : Labeled_Span_Type;
Loc_It : Labeled_Span_Id := Locations;
Sections : File_Section_List := File_Section_Lists.Create;
Sec : File_Sections;
F_It : File_Section_Lists.Iterator;
File_Found : Boolean;
begin
while Loc_It /= No_Labeled_Span loop
Loc := Erroutc.Locations.Table (Loc_It);
File_Found := False;
F_It := File_Section_Lists.Iterate (Sections);
while File_Section_Lists.Has_Next (F_It) loop
File_Section_Lists.Next (F_It, Sec);
if Sec.File /= null
and then Sec.File.all = To_File_Name (Loc.Span.Ptr)
then
File_Found := True;
Add_Printable_Line (Sec.Lines, Loc, Loc.Span.First);
Add_Printable_Line (Sec.Lines, Loc, Loc.Span.Ptr);
Add_Printable_Line (Sec.Lines, Loc, Loc.Span.Last);
if Loc.Is_Primary then
Sec.Ptr := Loc.Span.Ptr;
end if;
end if;
end loop;
if not File_Found then
Create_File_Section (Sections, Loc);
end if;
Loc_It := Loc.Next;
end loop;
return Sections;
end Create_File_Sections;
-----------------------
-- Write_Span_Labels --
-----------------------
procedure Write_Span_Labels
(Loc : Labeled_Span_Type;
L : Printable_Line;
Line_Size : Integer;
Idx : String;
Within_Region_Span : Boolean;
SGR_Code : String;
Region_Span_SGR_Code : String)
is
Span_Char : constant Character := (if Loc.Is_Primary then '~' else '-');
Buf : constant Source_Buffer_Ptr :=
Source_Text (Get_Source_File_Index (L.First));
Col_L_Fst : constant Natural :=
Natural (Get_Column_Number (Get_First_Line_Char (Buf, L.First)));
Col_L_Lst : constant Natural :=
Natural (Get_Column_Number (Get_Last_Line_Char (Buf, L.Last)));
-- Carret positions
Ptr : constant Source_Ptr := Loc.Span.Ptr;
Line_Ptr : constant Pos := Pos (Get_Physical_Line_Number (Ptr));
Col_Ptr : constant Natural := Natural (Get_Column_Number (Ptr));
-- Span start positions
Fst : constant Source_Ptr := Loc.Span.First;
Line_Fst : constant Pos := Pos (Get_Physical_Line_Number (Fst));
Col_Fst : constant Natural := Natural (Get_Column_Number (Fst));
-- Span end positions
Lst : constant Source_Ptr := Loc.Span.Last;
Line_Lst : constant Pos := Pos (Get_Physical_Line_Number (Lst));
Col_Lst : constant Natural := Natural (Get_Column_Number (Lst));
-- Attributes for the span on the current line
Span_Sym : constant String := (if Idx = "" then "^" else Idx);
Span_Fst : constant Natural :=
(if Line_Fst = L.Line_Nr then Col_Fst else Col_L_Fst);
Span_Lst : constant Natural :=
(if Line_Lst = L.Line_Nr then Col_Lst else Col_L_Lst);
Span_Ptr_Fst : constant Natural :=
(if Line_Ptr = L.Line_Nr then Col_Ptr else Col_L_Fst);
Span_Ptr_Lst : constant Natural :=
(if Line_Ptr = L.Line_Nr then Span_Ptr_Fst + Span_Sym'Length
else Span_Fst);
begin
if not Loc.Is_Region then
Write_Empty_Bar_Line (Line_Size);
if Within_Region_Span then
Write_Region_Bar (Region_Span_SGR_Code);
else
Write_Region_Offset;
end if;
Write_Str (String'(1 .. Span_Fst - 1 => ' '));
Write_Str (SGR_Code);
if Line_Ptr = L.Line_Nr then
Write_Str (String'(Span_Fst .. Col_Ptr - 1 => Span_Char));
Write_Str (Span_Sym);
end if;
Write_Str (String'(Span_Ptr_Lst .. Span_Lst => Span_Char));
Write_Str (SGR_Reset);
Write_Eol;
-- Write the label under the line unless it is an intersecting span.
-- In this case omit the label which will be printed later along with
-- the index.
if Loc.Label /= null and then Idx = "" then
Write_Empty_Bar_Line (Line_Size);
if Within_Region_Span then
Write_Region_Bar (Region_Span_SGR_Code);
else
Write_Region_Offset;
end if;
Write_Str (String'(1 .. Span_Fst - 1 => ' '));
Write_Str (SGR_Code);
Write_Str (Loc.Label.all);
Write_Str (SGR_Reset);
Write_Eol;
end if;
else
if Line_Lst = L.Line_Nr then
Write_Empty_Bar_Line (Line_Size);
Write_Str (String'(1 .. REGION_OFFSET => ' '));
Write_Str (SGR_Code);
Write_Str (Loc.Label.all);
Write_Str (SGR_Reset);
Write_Eol;
end if;
end if;
end Write_Span_Labels;
-------------------
-- Trimmed_Image --
-------------------
function Trimmed_Image (I : Natural) return String is
Img_Raw : constant String := Natural'Image (I);
begin
return Img_Raw (Img_Raw'First + 1 .. Img_Raw'Last);
end Trimmed_Image;
-------------------------------
-- Write_Intersecting_Labels --
-------------------------------
procedure Write_Intersecting_Labels
(Intersecting_Labels : Labeled_Span_List; SGR_Code : String)
is
L : Labeled_Span_Type;
L_It : Labeled_Span_Lists.Iterator :=
Labeled_Span_Lists.Iterate (Intersecting_Labels);
Idx : Integer := 0;
begin
while Labeled_Span_Lists.Has_Next (L_It) loop
Labeled_Span_Lists.Next (L_It, L);
Idx := Idx + 1;
Write_Empty_Bar_Line (MAX_BAR_POS);
Write_Str (" ");
Write_Str ((if L.Is_Primary then SGR_Code else SGR_Note));
Write_Int (Int (Idx));
Write_Str (": ");
Write_Str (L.Label.all);
Write_Str (SGR_Reset);
Write_Eol;
end loop;
end Write_Intersecting_Labels;
------------------------
-- Write_File_Section --
------------------------
procedure Write_File_Section
(Sec : File_Sections; Write_File_Name : Boolean;
File_Name_Offset : Integer; Include_Spans : Boolean;
SGR_Code : String := SGR_Note)
is
use Lines_Lists;
function Get_SGR_Code (L : Labeled_Span_Type) return String is
(if L.Is_Primary then SGR_Code else SGR_Note);
L : Printable_Line;
L_It : Iterator := Iterate (Sec.Lines);
Multiple_Labeled_Spans : Boolean := False;
Idx : Integer := 0;
Intersecting_Labels : constant Labeled_Span_List :=
Labeled_Span_Lists.Create;
Prev_Line_Nr : Natural := 0;
Within_Region_Span : Boolean := False;
begin
if Write_File_Name then
-- offset the file start location for sub-diagnostics
Write_Str (String'(1 .. File_Name_Offset => ' '));
Write_Str ("--> " & To_String (Sec.Ptr));
Write_Eol;
end if;
-- Historically SPARK does not include spans in their info messages.
if not Include_Spans then
return;
end if;
while Has_Next (L_It) loop
Next (L_It, L);
declare
Line_Nr : constant Pos := L.Line_Nr;
Line_Str : constant String := Trimmed_Image (Natural (Line_Nr));
Line_Size : constant Integer :=
Integer'Max (Line_Str'Length, MAX_BAR_POS);
Loc : Labeled_Span_Type;
Loc_It : Labeled_Span_Lists.Iterator :=
Labeled_Span_Lists.Iterate (L.Spans);
Buf : constant Source_Buffer_Ptr :=
Source_Text (Get_Source_File_Index (L.First));
Region_Span : constant Labeled_Span_Type :=
Get_Region_Span (L.Spans);
Contains_Region_Span_Start : constant Boolean :=
Region_Span /= No_Labeled_Span_Object
and then Line_Nr =
Pos (Get_Physical_Line_Number (Region_Span.Span.First));
Contains_Region_Span_End : constant Boolean :=
Region_Span /= No_Labeled_Span_Object
and then Line_Nr =
Pos (Get_Physical_Line_Number (Region_Span.Span.Last));
Region_Span_Color : constant String :=
(if Region_Span /= No_Labeled_Span_Object then
Get_SGR_Code (Region_Span)
else SGR_Note);
begin
if not Multiple_Labeled_Spans then
Multiple_Labeled_Spans := Has_Multiple_Labeled_Spans (L);
end if;
-- Write an empty line with the continuation symbol if the line
-- numbers are not contiguous
if Prev_Line_Nr /= 0 and then Pos (Prev_Line_Nr + 1) /= Line_Nr
then
Write_Empty_Skip_Line (Line_Size);
if Within_Region_Span then
Write_Region_Continuation (Region_Span_Color);
end if;
Write_Eol;
end if;
if Contains_Region_Span_Start then
Within_Region_Span := True;
end if;
Write_Line_Marker (Line_Nr, Line_Size);
-- Write either the region span symbol or the same number of
-- whitespaces.
if Contains_Region_Span_Start or Contains_Region_Span_End then
Write_Region_Delimiter (Region_Span_Color);
elsif Within_Region_Span then
Write_Region_Bar (Region_Span_Color);
else
Write_Region_Offset;
end if;
-- Write the line itself
Write_Buffer (Buf => Buf, First => L.First, Last => L.Last);
-- Write all the spans for the line
while Labeled_Span_Lists.Has_Next (Loc_It) loop
Labeled_Span_Lists.Next (Loc_It, Loc);
if Multiple_Labeled_Spans and then Loc.Label /= null then
-- Collect all the spans with labels to print them at the
-- end.
Labeled_Span_Lists.Append (Intersecting_Labels, Loc);
Idx := Idx + 1;
Write_Span_Labels
(Loc => Loc,
L => L,
Line_Size => Line_Size,
Idx => Trimmed_Image (Idx),
Within_Region_Span => Within_Region_Span,
SGR_Code => Get_SGR_Code (Loc),
Region_Span_SGR_Code => Region_Span_Color);
else
Write_Span_Labels
(Loc => Loc,
L => L,
Line_Size => Line_Size,
Idx => "",
Within_Region_Span => Within_Region_Span,
SGR_Code => Get_SGR_Code (Loc),
Region_Span_SGR_Code => Region_Span_Color);
end if;
end loop;
if Contains_Region_Span_End then
Within_Region_Span := False;
end if;
Prev_Line_Nr := Natural (Line_Nr);
end;
end loop;
Write_Intersecting_Labels (Intersecting_Labels, SGR_Code);
end Write_File_Section;
-------------------------
-- Write_Labeled_Spans --
-------------------------
procedure Write_Labeled_Spans
(Locations : Labeled_Span_Id;
Write_File_Name : Boolean;
File_Name_Offset : Integer;
Include_Spans : Boolean := True;
SGR_Code : String := SGR_Note)
is
Sections : File_Section_List := Create_File_Sections (Locations);
Sec : File_Sections;
F_It : File_Section_Lists.Iterator :=
File_Section_Lists.Iterate (Sections);
begin
while File_Section_Lists.Has_Next (F_It) loop
File_Section_Lists.Next (F_It, Sec);
Write_File_Section
(Sec => Sec,
Write_File_Name => Write_File_Name,
File_Name_Offset => File_Name_Offset,
Include_Spans => Include_Spans,
SGR_Code => SGR_Code);
end loop;
File_Section_Lists.Destroy (Sections);
end Write_Labeled_Spans;
--------------------------
-- Write_Error_Msg_Line --
--------------------------
procedure Write_Error_Msg_Line (E_Msg : Error_Msg_Object) is
Switch_Str : constant String := Get_Doc_Switch (E_Msg);
SGR_Code : constant String := Get_SGR_Code (E_Msg);
begin
Write_Str (SGR_Code);
if not GNATprove_Mode or else E_Msg.Id /= No_Diagnostic_Id then
Write_Str ("[" & To_String (E_Msg.Id) & "]");
end if;
Write_Str (" " & Kind_To_String (E_Msg) & ": ");
Write_Str (SGR_Reset);
Write_Str (E_Msg.Text.all);
if Switch_Str /= "" then
Write_Str (" " & Switch_Str);
end if;
if E_Msg.Warn_Err = From_Pragma then
Write_Str (" " & Warn_As_Err_Tag);
end if;
Write_Eol;
end Write_Error_Msg_Line;
----------------------------
-- Should_Write_File_Name --
----------------------------
function Should_Write_File_Name
(Sub_Diag : Error_Msg_Object; Diag : Error_Msg_Object) return Boolean
is
Sub_Loc : constant Labeled_Span_Type :=
Locations.Table (Primary_Location (Sub_Diag));
Diag_Loc : constant Labeled_Span_Type :=
Locations.Table (Primary_Location (Diag));
function Has_Multiple_Files (Diag : Error_Msg_Object) return Boolean;
------------------------
-- Has_Multiple_Files --
------------------------
function Has_Multiple_Files (Diag : Error_Msg_Object) return Boolean is
First : constant Labeled_Span_Type :=
Locations.Table (Diag.Locations);
File : constant String := To_File_Name (First.Span.Ptr);
Loc_Id : Labeled_Span_Id := Diag.Locations;
Loc : Labeled_Span_Type;
begin
Loc_Id := Diag.Locations;
while Loc_Id /= No_Labeled_Span loop
Loc := Locations.Table (Loc_Id);
if To_File_Name (Loc.Span.Ptr) /= File then
return True;
end if;
Loc_Id := Loc.Next;
end loop;
return False;
end Has_Multiple_Files;
-- Start of processing for Should_Write_File_Name
begin
return
Has_Multiple_Files (Diag)
or else To_File_Name (Sub_Loc.Span.Ptr) /=
To_File_Name (Diag_Loc.Span.Ptr);
end Should_Write_File_Name;
------------------------
-- Should_Write_Spans --
------------------------
function Should_Write_Spans
(Sub_Diag : Error_Msg_Object; Diag : Error_Msg_Object) return Boolean
is
Sub_Loc : constant Labeled_Span_Id := Primary_Location (Sub_Diag);
Diag_Loc : constant Labeled_Span_Id := Primary_Location (Diag);
begin
return
Sub_Loc /= No_Labeled_Span and then Diag_Loc /= No_Labeled_Span
and then Locations.Table (Sub_Loc).Span.Ptr /=
Locations.Table (Diag_Loc).Span.Ptr;
end Should_Write_Spans;
----------------
-- Print_Edit --
----------------
procedure Print_Edit (Edit : Edit_Type; Offset : Integer) is
Buf : constant Source_Buffer_Ptr :=
Source_Text (Get_Source_File_Index (Edit.Span.Ptr));
Line_Nr : constant Pos := Pos (Get_Physical_Line_Number (Edit.Span.Ptr));
Line_Fst : constant Source_Ptr := Get_Line_Start (Buf, Edit.Span.First);
Line_Lst : constant Source_Ptr := Get_Line_End (Buf, Edit.Span.First);
begin
Write_Str (String'(1 .. Offset => ' '));
Write_Str ("--> " & To_File_Name (Edit.Span.Ptr));
Write_Eol;
-- write the original line
Write_Char ('-');
Write_Line_Marker (Line_Nr, MAX_BAR_POS - 1);
Write_Buffer (Buf => Buf, First => Line_Fst, Last => Line_Lst);
-- write the edited line
Write_Char ('+');
Write_Line_Marker (Line_Nr, MAX_BAR_POS - 1);
Write_Buffer
(Buf => Buf, First => Line_Fst, Last => Edit.Span.First - 1);
if Edit.Text /= null then
Write_Str (Edit.Text.all);
end if;
Write_Buffer (Buf => Buf, First => Edit.Span.Last + 1, Last => Line_Lst);
end Print_Edit;
---------------
-- Print_Fix --
---------------
procedure Print_Fix (Fix : Fix_Type; Offset : Integer) is
E : Edit_Id;
begin
Write_Str (String'(1 .. Offset => ' '));
Write_Str ("+ Fix: ");
if Fix.Description /= null then
Write_Str (Fix.Description.all);
end if;
Write_Eol;
E := Fix.Edits;
while E /= No_Edit loop
Print_Edit (Edits.Table (E), MAX_BAR_POS - 1);
E := Edits.Table (E).Next;
end loop;
end Print_Fix;
--------------------------
-- Print_Sub_Diagnostic --
--------------------------
procedure Print_Sub_Diagnostic
(Sub_Diag : Error_Msg_Object; Diag : Error_Msg_Object; Offset : Integer)
is
begin
Write_Str (String'(1 .. Offset => ' '));
Write_Str ("+ ");
Write_Str (Sub_Diag.Text.all);
Write_Eol;
if Should_Write_Spans (Sub_Diag, Diag) then
Write_Labeled_Spans
(Locations => Sub_Diag.Locations,
Write_File_Name => Should_Write_File_Name (Sub_Diag, Diag),
File_Name_Offset => Offset,
Include_Spans => not GNATprove_Mode or else Sub_Diag.Kind /= Info,
SGR_Code => SGR_Note);
end if;
end Print_Sub_Diagnostic;
----------------------
-- Print_Diagnostic --
----------------------
procedure Print_Diagnostic (E : Error_Msg_Id) is
E_Msg : constant Error_Msg_Object := Errors.Table (E);
E_Next_Id : Error_Msg_Id;
F : Fix_Id;
begin
-- Print the main diagnostic
Write_Error_Msg_Line (E_Msg);
-- Print diagnostic locations along with spans
Write_Labeled_Spans
(Locations => E_Msg.Locations,
Write_File_Name => True,
File_Name_Offset => 0,
Include_Spans => not GNATprove_Mode or else E_Msg.Kind /= Info,
SGR_Code => Get_SGR_Code (E_Msg));
-- Print subdiagnostics
E_Next_Id := E_Msg.Next;
while E_Next_Id /= No_Error_Msg
and then Errors.Table (E_Next_Id).Msg_Cont
loop
-- Print the subdiagnostic and offset the location of the file
-- name
Print_Sub_Diagnostic
(Errors.Table (E_Next_Id), E_Msg, MAX_BAR_POS - 1);
E_Next_Id := Errors.Table (E_Next_Id).Next;
end loop;
-- Print fixes
F := E_Msg.Fixes;
while F /= No_Fix loop
Print_Fix (Fixes.Table (F), MAX_BAR_POS - 1);
F := Fixes.Table (F).Next;
end loop;
-- Separate main diagnostics with a blank line
Write_Eol;
end Print_Diagnostic;
--------------------------
-- Print_Error_Messages --
--------------------------
procedure Print_Error_Messages is
E : Error_Msg_Id;
begin
Set_Standard_Error;
E := First_Error_Msg;
while E /= No_Error_Msg loop
if not Errors.Table (E).Deleted and then not Errors.Table (E).Msg_Cont
then
Print_Diagnostic (E);
end if;
E := Errors.Table (E).Next;
end loop;
Set_Standard_Output;
end Print_Error_Messages;
------------------
-- To_File_Name --
------------------
function To_File_Name (Sptr : Source_Ptr) return String is
Sfile : constant Source_File_Index := Get_Source_File_Index (Sptr);
Ref_Name : constant File_Name_Type :=
(if Full_Path_Name_For_Brief_Errors then Full_Ref_Name (Sfile)
else Reference_Name (Sfile));
begin
return Get_Name_String (Ref_Name);
end To_File_Name;
--------------------
-- Line_To_String --
--------------------
function Line_To_String (Sptr : Source_Ptr) return String is
Line : constant Logical_Line_Number := Get_Logical_Line_Number (Sptr);
Img_Raw : constant String := Int'Image (Int (Line));
begin
return Img_Raw (Img_Raw'First + 1 .. Img_Raw'Last);
end Line_To_String;
----------------------
-- Column_To_String --
----------------------
function Column_To_String (Sptr : Source_Ptr) return String is
Col : constant Column_Number := Get_Column_Number (Sptr);
Img_Raw : constant String := Int'Image (Int (Col));
begin
return
(if Col < 10 then "0" else "") &
Img_Raw (Img_Raw'First + 1 .. Img_Raw'Last);
end Column_To_String;
---------------
-- To_String --
---------------
function To_String (Sptr : Source_Ptr) return String is
begin
return
To_File_Name (Sptr) & ":" & Line_To_String (Sptr) & ":" &
Column_To_String (Sptr);
end To_String;
end Erroutc.Pretty_Emitter;