Skip to content

Commit 6aa46e5

Browse files
committed
Unparsing: rework error message handling
Use the diagnostics data structures defined in Langkit_Support to hold error messages for the loading of unparsing configuration. We previously used exception messages, which have several limitations: length limit (exception messages are truncated) and no possibility to have multiple error messages. Using actual diagnostics also allows to have slocs associated to error messages, which will be useful once the unparsing configuration transition from JSON to a proper DSL (GNATCOLL.JSON does not gives slocs for JSON data). This commit also adds the Langkit_Support.Diagnostics.Print procedure as a convenient helper to print lists of diagnostics encoded in UTF-8.
1 parent 341de08 commit 6aa46e5

File tree

7 files changed

+164
-66
lines changed

7 files changed

+164
-66
lines changed

langkit/support/langkit_support-diagnostics.adb

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,34 @@
33
-- SPDX-License-Identifier: Apache-2.0
44
--
55

6+
with Ada.Text_IO; use Ada.Text_IO;
7+
68
package body Langkit_Support.Diagnostics is
79

10+
function Sloc_Prefix (Sloc_Range : Source_Location_Range) return String;
11+
-- If ``Sloc_Range`` is not null, return a "X:Y: " prefix with the
12+
-- corresponding start line/column numbers.
13+
14+
-----------------
15+
-- Sloc_Prefix --
16+
-----------------
17+
18+
function Sloc_Prefix (Sloc_Range : Source_Location_Range) return String is
19+
Sloc : constant Source_Location := Start_Sloc (Sloc_Range);
20+
begin
21+
return
22+
(if Sloc = No_Source_Location
23+
then ""
24+
else Image (Sloc) & ": ");
25+
end Sloc_Prefix;
26+
827
----------------------
928
-- To_Pretty_String --
1029
----------------------
1130

1231
function To_Pretty_String (D : Diagnostic) return String is
13-
Sloc : constant Source_Location := Start_Sloc (D.Sloc_Range);
14-
Sloc_Prefix : constant String :=
15-
(if Sloc = No_Source_Location
16-
then ""
17-
else Image (Sloc) & ": ");
1832
begin
19-
return Sloc_Prefix & Image (To_Text (D.Message));
33+
return Sloc_Prefix (D.Sloc_Range) & Image (To_Text (D.Message));
2034
end To_Pretty_String;
2135

2236
------------
@@ -58,4 +72,37 @@ package body Langkit_Support.Diagnostics is
5872
Append (Diagnostics, Sloc_Range, To_Text (Msg));
5973
end Append;
6074

75+
-----------
76+
-- Print --
77+
-----------
78+
79+
procedure Print
80+
(Diagnostics : Diagnostics_Vectors.Vector;
81+
Prefix : String := "error: ";
82+
Indent : Natural := 2) is
83+
begin
84+
for D of Diagnostics loop
85+
Put (Sloc_Prefix (D.Sloc_Range));
86+
Put (Prefix);
87+
declare
88+
Empty_Line : Boolean := False;
89+
Msg : constant String := To_UTF8 (To_Text (D.Message));
90+
begin
91+
for C of Msg loop
92+
if C = ASCII.LF then
93+
New_Line;
94+
Empty_Line := True;
95+
else
96+
if Empty_Line then
97+
Put ((1 .. Indent => ' '));
98+
Empty_Line := False;
99+
end if;
100+
Put (C);
101+
end if;
102+
end loop;
103+
end;
104+
New_Line;
105+
end loop;
106+
end Print;
107+
61108
end Langkit_Support.Diagnostics;

langkit/support/langkit_support-diagnostics.ads

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,12 @@ package Langkit_Support.Diagnostics is
5252
Exc : Ada.Exceptions.Exception_Occurrence);
5353
-- Shortcut to append an exception message to a vector
5454

55+
procedure Print
56+
(Diagnostics : Diagnostics_Vectors.Vector;
57+
Prefix : String := "error: ";
58+
Indent : Natural := 2);
59+
-- Print all diagnostics in ``Diagnostics`` on the standard output, with
60+
-- ``Prefix`` before each message. For multi-line messages, insert
61+
-- ``Indent`` spaces before each line except the first one.
62+
5563
end Langkit_Support.Diagnostics;

langkit/support/langkit_support-generic_api-unparsing.adb

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
with Ada.Command_Line;
77
with Ada.Containers.Hashed_Maps;
88
with Ada.Containers.Vectors;
9-
with Ada.Exceptions; use Ada.Exceptions;
109
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
1110
with Ada.Text_IO; use Ada.Text_IO;
1211
with Ada.Text_IO.Unbounded_IO; use Ada.Text_IO.Unbounded_IO;
@@ -28,6 +27,7 @@ use Langkit_Support.Internal.Descriptor;
2827
with Langkit_Support.Internal.Unparsing;
2928
use Langkit_Support.Internal.Unparsing;
3029
with Langkit_Support.Prettier_Utils; use Langkit_Support.Prettier_Utils;
30+
with Langkit_Support.Slocs; use Langkit_Support.Slocs;
3131
with Langkit_Support.Symbols; use Langkit_Support.Symbols;
3232

3333
package body Langkit_Support.Generic_API.Unparsing is
@@ -480,7 +480,10 @@ package body Langkit_Support.Generic_API.Unparsing is
480480
---------------------------
481481

482482
function Load_Unparsing_Config
483-
(Language : Language_Id; Filename : String) return Unparsing_Configuration
483+
(Language : Language_Id;
484+
Filename : String;
485+
Diagnostics : in out Diagnostics_Vectors.Vector)
486+
return Unparsing_Configuration
484487
is
485488
-- Create a map so that we can lookup nodes/fields by name
486489

@@ -618,6 +621,10 @@ package body Langkit_Support.Generic_API.Unparsing is
618621
-- Assuming that JSON is the "fields" configuration for Node, parse its
619622
-- field configurations and set Configs accordingly.
620623

624+
procedure Abort_Parsing (Message : String) with No_Return;
625+
-- Append an item to ``Diagnostics`` and raise an Invalid_Input
626+
-- exception.
627+
621628
Result : constant Unparsing_Configuration_Access :=
622629
new Unparsing_Configuration_Record;
623630
Pool : Document_Pool renames Result.Pool;
@@ -630,7 +637,7 @@ package body Langkit_Support.Generic_API.Unparsing is
630637
T : constant Type_Ref := Map.Lookup_Type (To_Symbol (Name));
631638
begin
632639
if T = No_Type_Ref or else not Is_Node_Type (T) then
633-
raise Invalid_Input with "invalid node name: " & Name;
640+
Abort_Parsing ("invalid node name: " & Name);
634641
end if;
635642
return To_Index (T);
636643
end To_Type_Index;
@@ -646,14 +653,14 @@ package body Langkit_Support.Generic_API.Unparsing is
646653
Map.Lookup_Struct_Member (Node, To_Symbol (Name));
647654
begin
648655
if M = No_Struct_Member_Ref then
649-
raise Invalid_Input with
650-
"invalid field for " & Node_Type_Image (Node) & ": " & Name;
656+
Abort_Parsing
657+
("invalid field for " & Node_Type_Image (Node) & ": " & Name);
651658
elsif not Is_Field (M) then
652-
raise Invalid_Input with
653-
Name & " is not a syntax field for " & Node_Type_Image (Node);
659+
Abort_Parsing
660+
(Name & " is not a syntax field for " & Node_Type_Image (Node));
654661
elsif Is_Null_For (M, Node) then
655-
raise Invalid_Input with
656-
Name & " is a null field for " & Node_Type_Image (Node);
662+
Abort_Parsing
663+
(Name & " is a null field for " & Node_Type_Image (Node));
657664
else
658665
return To_Index (M);
659666
end if;
@@ -1168,7 +1175,7 @@ package body Langkit_Support.Generic_API.Unparsing is
11681175
when Field_Template =>
11691176
"template for " & Field_Image (Context.Field, Context.Node));
11701177
begin
1171-
raise Invalid_Input with Prefix & ": " & Message;
1178+
Abort_Parsing (Prefix & ": " & Message);
11721179
end Abort_Parsing;
11731180

11741181
--------------------------
@@ -1220,6 +1227,16 @@ package body Langkit_Support.Generic_API.Unparsing is
12201227
JSON.Map_JSON_Object (Process'Access);
12211228
end Load_Field_Configs;
12221229

1230+
-------------------
1231+
-- Abort_Parsing --
1232+
-------------------
1233+
1234+
procedure Abort_Parsing (Message : String) is
1235+
begin
1236+
Append (Diagnostics, No_Source_Location_Range, To_Text (Message));
1237+
raise Invalid_Input;
1238+
end Abort_Parsing;
1239+
12231240
use type GNAT.Strings.String_Access;
12241241

12251242
-- First, parse the JSON document
@@ -1229,16 +1246,27 @@ package body Langkit_Support.Generic_API.Unparsing is
12291246
JSON : JSON_Value;
12301247
begin
12311248
if JSON_Text = null then
1232-
raise Invalid_Input with "cannot read " & Filename;
1249+
Abort_Parsing ("cannot read " & Filename);
12331250
end if;
12341251
JSON_Result := Read (JSON_Text.all);
12351252
GNAT.Strings.Free (JSON_Text);
12361253

12371254
if JSON_Result.Success then
12381255
JSON := JSON_Result.Value;
12391256
else
1240-
raise Invalid_Input with
1241-
Filename & ":" & Format_Parsing_Error (JSON_Result.Error);
1257+
declare
1258+
Sloc : constant Source_Location :=
1259+
(Line_Number (JSON_Result.Error.Line),
1260+
Column_Number (JSON_Result.Error.Column));
1261+
Sloc_Range : constant Source_Location_Range :=
1262+
Make_Range (Sloc, Sloc);
1263+
begin
1264+
Append
1265+
(Diagnostics,
1266+
Sloc_Range,
1267+
To_Text (To_String (JSON_Result.Error.Message)));
1268+
raise Invalid_Input;
1269+
end;
12421270
end if;
12431271

12441272
-- Then load the unparsing configuration from it. Require a
@@ -1248,7 +1276,7 @@ package body Langkit_Support.Generic_API.Unparsing is
12481276
Result.Language := Language;
12491277

12501278
if not JSON.Has_Field ("node_configs") then
1251-
raise Invalid_Input with "missing ""node_configs"" key";
1279+
Abort_Parsing ("missing ""node_configs"" key");
12521280
end if;
12531281

12541282
declare
@@ -1288,9 +1316,9 @@ package body Langkit_Support.Generic_API.Unparsing is
12881316

12891317
if Value.Has_Field ("sep") then
12901318
if not Is_List_Node (Node) then
1291-
raise Invalid_Input with
1292-
Name & " is not a list node, invalid ""sep"""
1293-
& " configuration";
1319+
Abort_Parsing
1320+
(Name & " is not a list node, invalid ""sep"""
1321+
& " configuration");
12941322
end if;
12951323
declare
12961324
Context : Template_Parsing_Context :=
@@ -1395,8 +1423,9 @@ package body Langkit_Support.Generic_API.Unparsing is
13951423

13961424
exception
13971425
when Invalid_Input =>
1426+
pragma Assert (not Diagnostics.Is_Empty);
13981427
Destroy (Symbols);
1399-
raise;
1428+
return No_Unparsing_Configuration;
14001429
end Load_Unparsing_Config;
14011430

14021431
--------------------------
@@ -1854,14 +1883,16 @@ package body Langkit_Support.Generic_API.Unparsing is
18541883
-- Parse the configuration file and the source file to pretty-print.
18551884
-- Abort if there is a parsing failure.
18561885

1886+
declare
1887+
Diagnostics : Diagnostics_Vectors.Vector;
1888+
Filename : constant String := To_String (Config_Filename.Get);
18571889
begin
1858-
Config :=
1859-
Load_Unparsing_Config (Language, To_String (Config_Filename.Get));
1860-
exception
1861-
when Exc : Invalid_Input =>
1890+
Config := Load_Unparsing_Config (Language, Filename, Diagnostics);
1891+
if Config = No_Unparsing_Configuration then
18621892
Put_Line ("Error when loading the unparsing configuration:");
1863-
Put_Line (Exception_Message (Exc));
1893+
Print (Diagnostics);
18641894
return;
1895+
end if;
18651896
end;
18661897
Context := Create_Context (Language);
18671898
Unit := Context.Get_From_File

langkit/support/langkit_support-generic_api-unparsing.ads

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
-- with Ada.Text_IO; use Ada.Text_IO;
2121
-- with Ada.Text_IO.Unbounded_IO; use Ada.Text_IO.Unbounded_IO;
2222
--
23+
-- with Langkit_Support.Diagnostics; use Langkit_Support.Diagnostics;
2324
-- with Langkit_Support.Generic_API; use Langkit_Support.Generic_API;
2425
-- with Langkit_Support.Generic_API.Analysis;
2526
-- use Langkit_Support.Generic_API.Analysis;
@@ -38,8 +39,9 @@
3839
-- -- Load_Unparsing_Config function for more information about this
3940
-- -- file.
4041
--
41-
-- Config : constant Unparsing_Configuration :=
42-
-- Load_Unparsing_Config (Self_Id, "config.json");
42+
-- Diagnostics : Diagnostics_Vectors.Vector;
43+
-- Config : constant Unparsing_Configuration :=
44+
-- Load_Unparsing_Config (Self_Id, "config.json", Diagnostics);
4345
--
4446
-- -- Parse the source file to reformat
4547
--
@@ -48,7 +50,15 @@
4850
-- U : constant Lk_Unit :=
4951
-- Ctx.Get_From_File (Ada.Command_Line.Argument (1));
5052
-- begin
51-
-- -- If it has parsing errors, bail out
53+
-- -- If we were unable to load the unparsing configuration, bail out
54+
--
55+
-- if Config = No_Unparsing_Configuration then
56+
-- Put_Line ("Error when loading the unparsing configuration:");
57+
-- Print (Diagnostics);
58+
-- raise Program_Error;
59+
-- end if;
60+
--
61+
-- -- If the source file to reformat has parsing errors, bail out
5262
--
5363
-- if U.Has_Diagnostics then
5464
-- Put_Line ("Parsing errors:");
@@ -74,6 +84,7 @@ private with Ada.Finalization;
7484

7585
with Prettier_Ada.Documents;
7686

87+
with Langkit_Support.Diagnostics; use Langkit_Support.Diagnostics;
7788
with Langkit_Support.Generic_API.Analysis;
7889
use Langkit_Support.Generic_API.Analysis;
7990

@@ -83,12 +94,18 @@ package Langkit_Support.Generic_API.Unparsing is
8394
-- Configuration that customizes how source fragments are turned into a
8495
-- prettier document.
8596

97+
No_Unparsing_Configuration : constant Unparsing_Configuration;
98+
-- Special value to mean the absence of an unparsing configuration
99+
86100
function Load_Unparsing_Config
87-
(Language : Language_Id;
88-
Filename : String) return Unparsing_Configuration;
101+
(Language : Language_Id;
102+
Filename : String;
103+
Diagnostics : in out Diagnostics_Vectors.Vector)
104+
return Unparsing_Configuration;
89105
-- Read and parse the unparsing configuration for the given Language from
90-
-- Filename. Raise a Langkit_Support.Errors.Invalid_Input exception if an
91-
-- error occurs while reading the configuration file.
106+
-- Filename. Append error messages to ``Diagnostics`` and return
107+
-- ``No_Unparsing_Configuration`` if an error occurs while reading the
108+
-- configuration file.
92109
--
93110
-- The configuration is a JSON file that provides "document templates":
94111
-- patterns to generate Prettier documents:
@@ -286,4 +303,7 @@ private
286303
overriding procedure Adjust (Self : in out Unparsing_Configuration);
287304
overriding procedure Finalize (Self : in out Unparsing_Configuration);
288305

306+
No_Unparsing_Configuration : constant Unparsing_Configuration :=
307+
(Ada.Finalization.Controlled with Value => null);
308+
289309
end Langkit_Support.Generic_API.Unparsing;

testsuite/tests/ada_api/unparsing/commands.adb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ with GNATCOLL.JSON; use GNATCOLL.JSON;
66
with Prettier_Ada.Documents;
77
with Prettier_Ada.Documents.Json;
88

9+
with Langkit_Support.Diagnostics; use Langkit_Support.Diagnostics;
910
with Langkit_Support.Generic_API.Analysis;
1011
use Langkit_Support.Generic_API.Analysis;
1112
with Langkit_Support.Generic_API.Unparsing;
@@ -42,7 +43,15 @@ procedure Commands is
4243
raise Program_Error;
4344
end if;
4445

45-
Config := Load_Unparsing_Config (Self_Id, Filename);
46+
declare
47+
Diagnostics : Diagnostics_Vectors.Vector;
48+
begin
49+
Config := Load_Unparsing_Config (Self_Id, Filename, Diagnostics);
50+
if Config = No_Unparsing_Configuration then
51+
Print (Diagnostics);
52+
raise Program_Error;
53+
end if;
54+
end;
4655
Doc := Unparse_To_Prettier (Unit.Root, Config);
4756
JSON_Text := Prettier_Ada.Documents.Json.Serialize (Doc);
4857

0 commit comments

Comments
 (0)