1 /**
2  * Encapsulates file/line/column locations.
3  *
4  * Copyright:   Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
5  * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
6  * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/location.d, _location.d)
8  * Documentation:  https://dlang.org/phobos/dmd_location.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/location.d
10  */
11 
12 module dmd.location;
13 
14 import core.stdc.stdio;
15 
16 import dmd.common.outbuffer;
17 import dmd.root.array;
18 import dmd.root.filename;
19 
20 version (DMDLIB)
21 {
22     version = LocOffset;
23 }
24 
25 /// How code locations are formatted for diagnostic reporting
26 enum MessageStyle : ubyte
27 {
28     digitalmars,  /// filename.d(line): message
29     gnu,          /// filename.d:line: message, see https://www.gnu.org/prep/standards/html_node/Errors.html
30 }
31 
32 /**
33 A source code location
34 
35 Used for error messages, `__FILE__` and `__LINE__` tokens, `__traits(getLocation, XXX)`,
36 debug info etc.
37 */
38 struct Loc
39 {
40     private uint _linnum;
41     private uint _charnum;
42     private uint fileIndex; // index into filenames[], starting from 1 (0 means no filename)
43     version (LocOffset)
44         uint fileOffset; /// utf8 code unit index relative to start of file, starting from 0
45 
46     static immutable Loc initial; /// use for default initialization of const ref Loc's
47 
48     extern (C++) __gshared bool showColumns;
49     extern (C++) __gshared MessageStyle messageStyle;
50 
51     __gshared Array!(const(char)*) filenames;
52 
53 nothrow:
54 
55     /*******************************
56      * Configure how display is done
57      * Params:
58      *  showColumns = when to display columns
59      *  messageStyle = digitalmars or gnu style messages
60      */
61     extern (C++) static void set(bool showColumns, MessageStyle messageStyle)
62     {
63         this.showColumns = showColumns;
64         this.messageStyle = messageStyle;
65     }
66 
67     extern (C++) this(const(char)* filename, uint linnum, uint charnum) @safe
68     {
69         this._linnum = linnum;
70         this._charnum = charnum;
71         this.filename = filename;
72     }
73 
74     /// utf8 code unit index relative to start of line, starting from 1
75     extern (C++) uint charnum() const @nogc @safe
76     {
77         return _charnum;
78     }
79 
80     /// ditto
81     extern (C++) uint charnum(uint num) @nogc @safe
82     {
83         return _charnum = num;
84     }
85 
86     /// line number, starting from 1
87     extern (C++) uint linnum() const @nogc @safe
88     {
89         return _linnum;
90     }
91 
92     /// ditto
93     extern (C++) uint linnum(uint num) @nogc @safe
94     {
95         return _linnum = num;
96     }
97 
98     /***
99      * Returns: filename for this location, null if none
100      */
101     extern (C++) const(char)* filename() const @nogc
102     {
103         return fileIndex ? filenames[fileIndex - 1] : null;
104     }
105 
106     /***
107      * Set file name for this location
108      * Params:
109      *   name = file name for location, null for no file name
110      */
111     extern (C++) void filename(const(char)* name) @trusted
112     {
113         if (name)
114         {
115             //printf("setting %s\n", name);
116             filenames.push(name);
117             fileIndex = cast(uint)filenames.length;
118             if (!fileIndex)
119             {
120                 import dmd.globals : global;
121                 import dmd.errors : error, fatal;
122 
123                 global.gag = 0; // ensure error message gets printed
124                 error(Loc.initial, "internal compiler error: file name index overflow!");
125                 fatal();
126             }
127         }
128         else
129             fileIndex = 0;
130     }
131 
132     extern (C++) const(char)* toChars(
133         bool showColumns = Loc.showColumns,
134         MessageStyle messageStyle = Loc.messageStyle) const nothrow
135     {
136         OutBuffer buf;
137         if (fileIndex)
138         {
139             buf.writestring(filename);
140         }
141         if (linnum)
142         {
143             final switch (messageStyle)
144             {
145                 case MessageStyle.digitalmars:
146                     buf.writeByte('(');
147                     buf.print(linnum);
148                     if (showColumns && charnum)
149                     {
150                         buf.writeByte(',');
151                         buf.print(charnum);
152                     }
153                     buf.writeByte(')');
154                     break;
155                 case MessageStyle.gnu: // https://www.gnu.org/prep/standards/html_node/Errors.html
156                     buf.writeByte(':');
157                     buf.print(linnum);
158                     if (showColumns && charnum)
159                     {
160                         buf.writeByte(':');
161                         buf.print(charnum);
162                     }
163                     break;
164             }
165         }
166         return buf.extractChars();
167     }
168 
169     /**
170      * Checks for equivalence by comparing the filename contents (not the pointer) and character location.
171      *
172      * Note:
173      *  - Uses case-insensitive comparison on Windows
174      *  - Ignores `charnum` if `Columns` is false.
175      */
176     extern (C++) bool equals(ref const(Loc) loc) const
177     {
178         return (!showColumns || charnum == loc.charnum) &&
179                linnum == loc.linnum &&
180                FileName.equals(filename, loc.filename);
181     }
182 
183     /**
184      * `opEquals()` / `toHash()` for AA key usage
185      *
186      * Compare filename contents (case-sensitively on Windows too), not
187      * the pointer - a static foreach loop repeatedly mixing in a mixin
188      * may lead to multiple equivalent filenames (`foo.d-mixin-<line>`),
189      * e.g., for test/runnable/test18880.d.
190      */
191     extern (D) bool opEquals(ref const(Loc) loc) const @trusted nothrow @nogc
192     {
193         import core.stdc.string : strcmp;
194 
195         return charnum == loc.charnum &&
196                linnum == loc.linnum &&
197                (filename == loc.filename ||
198                 (filename && loc.filename && strcmp(filename, loc.filename) == 0));
199     }
200 
201     /// ditto
202     extern (D) size_t toHash() const @trusted nothrow
203     {
204         import dmd.root.string : toDString;
205 
206         auto hash = hashOf(linnum);
207         hash = hashOf(charnum, hash);
208         hash = hashOf(filename.toDString, hash);
209         return hash;
210     }
211 
212     /******************
213      * Returns:
214      *   true if Loc has been set to other than the default initialization
215      */
216     bool isValid() const pure @safe
217     {
218         return fileIndex != 0;
219     }
220 }