1 
2 //          Copyright Tim Schendekehl 2023.
3 // Distributed under the Boost Software License, Version 1.0.
4 //    (See accompanying file LICENSE_1_0.txt or copy at
5 //          https://www.boost.org/LICENSE_1_0.txt)
6 
7 module dparsergen.core.parseexception;
8 import std.algorithm;
9 import std.conv;
10 
11 /**
12 Flags for toString of ParseException.
13 */
14 enum ExceptionStringFlags
15 {
16     none = 0,
17     noBacktrace = 1,
18     noLocation = 2,
19 }
20 
21 /**
22 Base class for exceptions of parser and lexer.
23 */
24 class ParseException : Exception
25 {
26     /**
27     Construct exception.
28     */
29     this(string msg, string file = __FILE__, size_t line = __LINE__)
30     {
31         super(msg, file, line);
32     }
33 
34     /**
35     Generate string for this exception.
36     */
37     alias toString = Exception.toString;
38 
39     /// ditto
40     void toString(string inputString, scope void delegate(in char[]) sink,
41             ExceptionStringFlags flags = ExceptionStringFlags.none) const
42     {
43         if (flags & ExceptionStringFlags.noBacktrace)
44             sink(text("Parse Error: ", msg, "\n"));
45         else
46             toString(sink);
47     }
48 
49     /**
50     Get message for this exception.
51     */
52     string simpleMsg() const
53     {
54         return msg;
55     }
56 
57     /**
58     Get exception with maximum end location for exceptions representing
59     multiple exceptions.
60     */
61     const(ParseException) maxEndException() const
62     {
63         return this;
64     }
65 
66     bool allowBacktrack() const
67     {
68         return msg.startsWith("unexpected") || msg.startsWith("EOF");
69     }
70 
71     bool laterEnd(const ParseException other) const
72     {
73         return false;
74     }
75 }
76 
77 /**
78 Exception type used by parser and lexer.
79 */
80 class SingleParseException(Location) : ParseException
81 {
82     /**
83     Start location for error.
84     */
85     Location markStart;
86 
87     /**
88     End location for error.
89     */
90     Location markEnd;
91 
92     /**
93     Construct exception.
94     */
95     this(string msg, Location markStart, Location markEnd,
96             string file = __FILE__, size_t line = __LINE__)
97     {
98         this.markStart = markStart;
99         this.markEnd = markEnd;
100         assert(markStart <= markEnd);
101         super(msg, file, line);
102     }
103 
104     override void toString(scope void delegate(in char[]) sink) const
105     {
106         assert(markStart <= markEnd);
107         sink("Parse Error(");
108         static if (__traits(hasMember, Location, "toPrettyString"))
109             sink(markStart.toPrettyString);
110         else
111             sink(text(markStart));
112         sink("): ");
113         sink(msg);
114         sink("\n");
115         super.toString(sink);
116     }
117 
118     override void toString(string inputString, scope void delegate(in char[]) sink,
119             ExceptionStringFlags flags = ExceptionStringFlags.none) const
120     {
121         assert(markStart <= markEnd);
122 
123         if ((flags & ExceptionStringFlags.noLocation) == 0)
124         {
125             sink("Parse Error(");
126             static if (__traits(hasMember, Location, "toPrettyString"))
127                 sink(markStart.toPrettyString);
128             else
129                 sink(text(markStart));
130             sink("): ");
131         }
132 
133         sink(msg);
134         sink("\n");
135 
136         if ((flags & ExceptionStringFlags.noBacktrace) == 0)
137             super.toString(sink);
138     }
139 
140     override const(ParseException) maxEndException() const
141     {
142         return this;
143     }
144 
145     override bool laterEnd(const ParseException other) const
146     {
147         auto singleOther = cast(const(SingleParseException)) other;
148         if (singleOther is null)
149             return false;
150         return markEnd > singleOther.markEnd;
151     }
152 }
153 
154 /**
155 Exception used by backtracking in the parser.
156 */
157 class BacktrackParseException : ParseException
158 {
159     ParseException[] nextErrors;
160     string[] prods;
161 
162     /**
163     Construct exception.
164     */
165     this(string msg, string[] prods, ParseException[] nextErrors,
166             string file = __FILE__, size_t line = __LINE__)
167     {
168         this.nextErrors = nextErrors;
169         this.prods = prods;
170         super(msg, file, line);
171     }
172 
173     override void toString(scope void delegate(in char[]) sink) const
174     {
175         foreach (i, e; nextErrors)
176         {
177             sink(prods[i]);
178             sink("\n");
179             if (e !is null)
180                 e.toString(sink);
181             else
182                 sink("null???");
183             sink("\n");
184         }
185         super.toString(sink);
186     }
187 
188     override void toString(string inputString, scope void delegate(in char[]) sink,
189             ExceptionStringFlags flags = ExceptionStringFlags.none) const
190     {
191         foreach (i, e; nextErrors)
192         {
193             sink(prods[i]);
194             sink("\n");
195             if (e !is null)
196                 e.toString(inputString, sink, flags);
197             else
198                 sink("null???");
199             sink("\n");
200         }
201         super.toString(sink);
202     }
203 
204     override const(ParseException) maxEndException() const
205     {
206         import std.typecons;
207 
208         Rebindable!(const(ParseException)) max;
209         foreach (i, e; nextErrors)
210         {
211             auto e2 = e.maxEndException();
212             if (max is null || e2.laterEnd(max))
213             {
214                 max = e2;
215             }
216         }
217         return max;
218     }
219 
220     override string simpleMsg() const
221     {
222         return maxEndException().msg;
223     }
224 
225     override bool allowBacktrack() const
226     {
227         foreach (e; nextErrors)
228         {
229             if (!e.allowBacktrack())
230                 return false;
231         }
232         return true;
233     }
234 }
235 
236 /**
237 Exception used to combine multiple other exceptions in GLR parser.
238 */
239 class MultiParseException : ParseException
240 {
241     ParseException[] nextErrors;
242     string[] prods;
243 
244     /**
245     Construct exception.
246     */
247     this(string msg, ParseException[] nextErrors, string file = __FILE__, size_t line = __LINE__)
248     {
249         assert(nextErrors.length);
250         this.nextErrors = nextErrors;
251         super(msg, file, line);
252     }
253 
254     override void toString(scope void delegate(in char[]) sink) const
255     {
256         sink("MultiParseException\n");
257         foreach (i, e; nextErrors)
258         {
259             if (e !is null)
260                 e.toString(sink);
261             else
262                 sink("null???");
263             sink("\n");
264         }
265         //super.toString(sink);
266     }
267 
268     override void toString(string inputString, scope void delegate(in char[]) sink,
269             ExceptionStringFlags flags = ExceptionStringFlags.none) const
270     {
271         sink("MultiParseException\n");
272         foreach (i, e; nextErrors)
273         {
274             if (e !is null)
275                 e.toString(inputString, sink, flags);
276             else
277                 sink("null???");
278             sink("\n");
279         }
280         //super.toString(sink);
281     }
282 
283     override const(ParseException) maxEndException() const
284     {
285         import std.typecons;
286 
287         Rebindable!(const(ParseException)) max;
288         foreach (i, e; nextErrors)
289         {
290             auto e2 = e.maxEndException();
291             if (max is null || e2.laterEnd(max))
292             {
293                 max = e2;
294             }
295         }
296         return max;
297     }
298 
299     override string simpleMsg() const
300     {
301         string r;
302         r ~= "MultiParseException: ";
303         foreach (i, e; nextErrors)
304         {
305             if (i > 0)
306                 r ~= " | ";
307             if (e !is null)
308                 r ~= e.simpleMsg;
309             else
310                 r ~= "null???";
311         }
312         return r;
313     }
314 }