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.location;
8 import dparsergen.core.utils;
9 import std.algorithm;
10 import std.conv;
11 
12 /**
13 Flags specifying at compile time what should be stored in a location.
14 */
15 enum LocationTypeFlags
16 {
17     /// No flags.
18     none = 0,
19     /// This is the difference between two locations.
20     diff = 1,
21     /// The number of bytes should be stored.
22     bytes = 2,
23     /// The number of lines should be stored.
24     lines = 4,
25     /// The offset of bytes from the current line beginning should be stored.
26     lineOffset = 8
27 }
28 
29 /**
30 Implementation of location in source file, which can store different
31 data based on compile time flags.
32 
33 Params:
34     flags = Configuration of available fields.
35     T = Type used for numbers in the location.
36 */
37 struct LocationImpl(LocationTypeFlags flags, T = int)
38 {
39     static assert(flags & LocationTypeFlags.bytes, "Location without byte positon not implemented");
40 
41     /**
42     Number of bytes.
43     */
44     static if (flags & LocationTypeFlags.bytes)
45         T bytePos;
46 
47     /**
48     Number of lines
49     */
50     static if (flags & LocationTypeFlags.lines)
51         T line;
52 
53     /**
54     Number of bytes since beginning of line.
55     */
56     static if (flags & LocationTypeFlags.lineOffset)
57         T offset;
58 
59     /**
60     Create location.
61     */
62     this(AliasSeqIf!((flags & LocationTypeFlags.bytes) != 0, T) bytePos,
63             AliasSeqIf!((flags & LocationTypeFlags.lines) != 0, T) line,
64             AliasSeqIf!((flags & LocationTypeFlags.lineOffset) != 0, T) offset)
65     {
66         static if (flags & LocationTypeFlags.bytes)
67             this.bytePos = bytePos[0];
68         static if (flags & LocationTypeFlags.lines)
69             this.line = line[0];
70         static if (flags & LocationTypeFlags.lineOffset)
71             this.offset = offset[0];
72     }
73 
74     /**
75     Maximum value.
76     */
77     enum max = () {
78         LocationImpl r;
79         static if (flags & LocationTypeFlags.bytes)
80             r.bytePos = T.max;
81         static if (flags & LocationTypeFlags.lines)
82             r.line = T.max;
83         static if (flags & LocationTypeFlags.lineOffset)
84             r.offset = T.max;
85         return r;
86     }();
87 
88     /**
89     Special invalid value.
90     */
91     enum invalid = () {
92         LocationImpl r;
93         static if (flags & LocationTypeFlags.bytes)
94             r.bytePos = T.min;
95         static if (flags & LocationTypeFlags.lines)
96             r.line = T.min;
97         static if (flags & LocationTypeFlags.lineOffset)
98             r.offset = T.min;
99         return r;
100     }();
101 
102     /**
103     Location for beginning of file or zero difference.
104     */
105     enum zero = () {
106         LocationImpl r;
107         static if (flags & LocationTypeFlags.bytes)
108             r.bytePos = 0;
109         static if (flags & LocationTypeFlags.lines)
110             r.line = 0;
111         static if (flags & LocationTypeFlags.lineOffset)
112             r.offset = 0;
113         return r;
114     }();
115 
116     /**
117     Same type, but as absolute difference from beginning.
118     */
119     alias LocationAbs = LocationImpl!(flags & ~LocationTypeFlags.diff, T);
120 
121     /**
122     Same type, but as difference between locations.
123     */
124     alias LocationDiff = LocationImpl!(flags | LocationTypeFlags.diff, T);
125 
126     /**
127     Check if the location is valid.
128     */
129     bool isValid() const
130     {
131         static if (flags & LocationTypeFlags.bytes)
132             if (bytePos == T.min)
133                 return false;
134         static if (flags & LocationTypeFlags.lines)
135             if (line == T.min)
136                 return false;
137         static if (flags & LocationTypeFlags.lineOffset)
138             if (offset == T.min)
139                 return false;
140         return true;
141     }
142 
143     /**
144     Substract absolute locations.
145     */
146     LocationDiff opBinary(string op)(const LocationImpl rhs) const
147             if (op == "-" && !(flags & LocationTypeFlags.diff))
148     {
149         if (!isValid || !rhs.isValid)
150             return LocationDiff.invalid;
151         LocationDiff r;
152         static if (flags & LocationTypeFlags.bytes)
153         {
154             r.bytePos = bytePos - rhs.bytePos;
155         }
156         static if (flags & LocationTypeFlags.lines)
157         {
158             r.line = line - rhs.line;
159         }
160         static if (flags & LocationTypeFlags.lineOffset)
161         {
162             if (r.line == 0)
163             {
164                 r.offset = offset - rhs.offset;
165             }
166             else
167                 r.offset = offset;
168         }
169         return r;
170     }
171 
172     /**
173     Substract difference from absolute location.
174     */
175     LocationImpl opBinary(string op)(const LocationDiff rhs) const
176             if (op == "-" && !(flags & LocationTypeFlags.diff))
177     {
178         if (!isValid || !rhs.isValid)
179             return invalid;
180         LocationImpl r;
181         static if (flags & LocationTypeFlags.bytes)
182         {
183             assert(bytePos >= rhs.bytePos);
184             r.bytePos = bytePos - rhs.bytePos;
185         }
186         static if (flags & LocationTypeFlags.lines)
187         {
188             assert(line >= rhs.line);
189             r.line = line - rhs.line;
190         }
191         static if (flags & LocationTypeFlags.lineOffset)
192         {
193             if (rhs.line == 0)
194             {
195                 assert(offset >= rhs.offset);
196                 r.offset = offset - rhs.offset;
197             }
198             else
199             {
200                 assert(false);
201                 r.offset = size_t.max;
202             }
203         }
204         return r;
205     }
206 
207     /**
208     Add difference to absolute location.
209     */
210     LocationImpl opBinary(string op)(const LocationDiff rhs) const if (op == "+")
211     {
212         if (!isValid || !rhs.isValid)
213             return invalid;
214         LocationImpl r;
215         static if (flags & LocationTypeFlags.bytes)
216             r.bytePos = bytePos + rhs.bytePos;
217         static if (flags & LocationTypeFlags.lines)
218             r.line = line + rhs.line;
219         static if (flags & LocationTypeFlags.lineOffset)
220         {
221             if (rhs.line == 0)
222                 r.offset = offset + rhs.offset;
223             else
224                 r.offset = rhs.offset;
225         }
226         return r;
227     }
228 
229     // Dangerous, because this may be rvalue.
230     // see https://issues.dlang.org/show_bug.cgi?id=15231
231     /+void opOpAssign(string op)(const LocationDiff rhs) if (op == "+")
232     {
233         static if (flags & LocationTypeFlags.bytes)
234             bytePos += rhs.bytePos;
235         static if (flags & LocationTypeFlags.lines)
236             line += rhs.line;
237         static if (flags & LocationTypeFlags.lineOffset)
238         {
239             if (rhs.line == 0)
240                 offset += rhs.offset;
241             else
242                 offset = rhs.offset;
243         }
244     }+/
245     /*void opOpAssign(string op)(const LocationImpl rhs) if (op == "-")
246     {
247         static if (flags & LocationTypeFlags.bytes)
248             bytePos -= rhs.bytePos;
249     }*/
250 
251     /**
252     Compare locations.
253     */
254     int opCmp(const LocationImpl rhs) const
255     {
256         static if (flags & LocationTypeFlags.bytes)
257         {
258             int r1;
259             if (bytePos < rhs.bytePos)
260                 r1 = -1;
261             else if (bytePos > rhs.bytePos)
262                 r1 = 1;
263             else
264                 r1 = 0;
265         }
266 
267         static if (flags & LocationTypeFlags.bytes)
268             return r1;
269     }
270 
271     /**
272     Add location difference from string.
273     */
274     void advance(string str)
275     {
276         if (!isValid)
277             return;
278         static if (flags & LocationTypeFlags.bytes)
279             bytePos += str.length;
280         foreach (char c; str)
281         {
282             static if (flags & LocationTypeFlags.lineOffset)
283                 offset++;
284             if (c == '\n')
285             {
286                 static if (flags & LocationTypeFlags.lines)
287                     line++;
288                 static if (flags & LocationTypeFlags.lineOffset)
289                     offset = 0;
290             }
291         }
292     }
293 
294     /**
295     Calculate location difference from string.
296     */
297     static LocationImpl fromStr(string str)
298     {
299         LocationImpl r;
300         r.advance(str);
301         return r;
302     }
303 
304     /**
305     Represent location as string.
306     */
307     string toString() const
308     {
309         string r = "Location";
310         static if (flags & LocationTypeFlags.diff)
311             r ~= "Diff";
312         r ~= "(";
313         static if (flags & LocationTypeFlags.bytes)
314             r ~= text("byte=", bytePos, ", ");
315         static if (flags & LocationTypeFlags.lines)
316             r ~= text("line=", line, ", ");
317         static if (flags & LocationTypeFlags.lineOffset)
318             r ~= text("offset=", offset, ", ");
319         if (r.endsWith(", "))
320             r = r[0 .. $ - 2];
321         r ~= ")";
322         return r;
323     }
324 
325     /**
326     Represent location as string.
327     */
328     string toPrettyString() const
329     {
330         static if ((flags & LocationTypeFlags.lines) && (flags & LocationTypeFlags.lineOffset))
331             return text(line + 1, ":", offset);
332         else static if (flags & LocationTypeFlags.lines)
333             return text(line + 1);
334         else static if (flags & LocationTypeFlags.bytes)
335             return text(bytePos + 1);
336         else
337             return "noloc";
338     }
339 }
340 
341 /**
342 Location storing only byte positions.
343 */
344 alias LocationBytes = LocationImpl!(LocationTypeFlags.bytes);
345 
346 /**
347 Location storing bytes, lines and offsets from the line beginnings.
348 */
349 alias LocationAll = LocationImpl!(LocationTypeFlags.bytes | LocationTypeFlags.lines | LocationTypeFlags.lineOffset);
350 
351 /**
352 Store offset of tree from parent tree and length for tree.
353 */
354 struct LocationRangeStartDiffLength(Location)
355 {
356     alias LocationDiff = typeof(Location.init - Location.init);
357 
358     /**
359     Offset of start location from parent tree.
360     */
361     LocationDiff startFromParent;
362 
363     /**
364     Length for this tree.
365     */
366     LocationDiff inputLength;
367 }
368 
369 /**
370 Store start and length for tree.
371 */
372 struct LocationRangeStartLength(Location)
373 {
374     alias LocationDiff = typeof(Location.init - Location.init);
375 
376     /**
377     Start location for this tree.
378     */
379     Location start;
380 
381     /**
382     Length for this tree.
383     */
384     LocationDiff inputLength;
385 
386     /**
387     End location for this tree.
388     */
389     Location end() const
390     {
391         return start + inputLength;
392     }
393 
394     /**
395     Set start and end location.
396     */
397     void setStartEnd(Location start, Location end)
398     {
399         this.start = start;
400         if (end == Location.invalid || start > end)
401             this.inputLength = LocationDiff();
402         else
403             this.inputLength = end - start;
404     }
405 }
406 
407 /**
408 Store start and end locations for tree.
409 */
410 struct LocationRangeStartEnd(Location)
411 {
412     alias LocationDiff = typeof(Location.init - Location.init);
413 
414     /**
415     Start location for this tree.
416     */
417     Location start;
418 
419     /**
420     End location for this tree.
421     */
422     Location end;
423 
424     /**
425     Length for this tree.
426     */
427     LocationDiff inputLength() const
428     {
429         return end - start;
430     }
431 
432     /// ditto
433     void inputLength(LocationDiff n)
434     {
435         end = start + n;
436     }
437 
438     /**
439     Set start and end location.
440     */
441     void setStartEnd(Location start, Location end)
442     {
443         this.start = start;
444         this.end = end;
445     }
446 }
447 
448 /**
449 Check if the location range stores the start as the offset from the parent tree.
450 */
451 template isLocationRangeStartDiffLength(alias LocationRange)
452 {
453     enum isLocationRangeStartDiffLength = __traits(hasMember, LocationRange, "startFromParent");
454 }