|
| 1 | +// replace dmd.location for faster line/file lookup and incremental updates |
| 2 | + |
| 3 | +module dmd.location; |
| 4 | + |
| 5 | +import core.stdc.stdio; |
| 6 | + |
| 7 | +import dmd.common.outbuffer; |
| 8 | +import dmd.root.array; |
| 9 | +import dmd.root.filename; |
| 10 | +import dmd.root.rmem : xarraydup; |
| 11 | +import dmd.root.string: toDString; |
| 12 | +import dmd.root.stringtable; |
| 13 | + |
| 14 | +/// How code locations are formatted for diagnostic reporting |
| 15 | +enum MessageStyle : ubyte |
| 16 | +{ |
| 17 | + digitalmars, /// filename.d(line): message |
| 18 | + gnu, /// filename.d:line: message, see https://www.gnu.org/prep/standards/html_node/Errors.html |
| 19 | + sarif /// JSON SARIF output, see https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html |
| 20 | +} |
| 21 | +/** |
| 22 | +A source code location |
| 23 | +
|
| 24 | +Used for error messages, `__FILE__` and `__LINE__` tokens, `__traits(getLocation, XXX)`, |
| 25 | +debug info etc. |
| 26 | +*/ |
| 27 | +struct Loc |
| 28 | +{ |
| 29 | + private ulong data = 0; // bitfield of file, line and column |
| 30 | + |
| 31 | + static immutable Loc initial; /// use for default initialization of Loc's |
| 32 | + |
| 33 | + extern (C++) __gshared bool showColumns; |
| 34 | + extern (C++) __gshared MessageStyle messageStyle; |
| 35 | + |
| 36 | +nothrow: |
| 37 | + /******************************* |
| 38 | + * Configure how display is done |
| 39 | + * Params: |
| 40 | + * showColumns = when to display columns |
| 41 | + * messageStyle = digitalmars or gnu style messages |
| 42 | + */ |
| 43 | + extern (C++) static void set(bool showColumns, MessageStyle messageStyle) |
| 44 | + { |
| 45 | + this.showColumns = showColumns; |
| 46 | + this.messageStyle = messageStyle; |
| 47 | + } |
| 48 | + |
| 49 | + /// Returns: a Loc that simply holds a filename, with no line / column info |
| 50 | + extern (C++) static Loc singleFilename(const char* filename) |
| 51 | + { |
| 52 | + return singleFilename(filename.toDString); |
| 53 | + } |
| 54 | + |
| 55 | + /// Returns: a Loc that simply holds a filename, with no line / column info |
| 56 | + static Loc singleFilename(const(char)[] filename) |
| 57 | + { |
| 58 | + ulong fileIndex = toLocFileIndex(filename); |
| 59 | + return Loc((fileIndex << 48) | 1); // default to charnum 1 |
| 60 | + } |
| 61 | + |
| 62 | + /// utf8 code unit index relative to start of line, starting from 1 |
| 63 | + extern (C++) uint charnum() const @nogc @safe |
| 64 | + { |
| 65 | + return data & 0xffff; |
| 66 | + } |
| 67 | + |
| 68 | + /// line number, starting from 1 |
| 69 | + extern (C++) uint linnum() const @nogc @trusted |
| 70 | + { |
| 71 | + return (data >> 16) & 0xffff_ffff; |
| 72 | + } |
| 73 | + |
| 74 | + /*** |
| 75 | + * Returns: filename for this location, null if none |
| 76 | + */ |
| 77 | + extern (C++) const(char)* filename() const @nogc |
| 78 | + { |
| 79 | + return locFileName[data >> 48].ptr; |
| 80 | + } |
| 81 | + |
| 82 | + /// Advance this location to the first column of the next line |
| 83 | + void nextLine() @safe pure @nogc |
| 84 | + { |
| 85 | + data = (data & ~0xffffL) + 0x10001; |
| 86 | + } |
| 87 | + |
| 88 | + bool isValid() const pure @safe |
| 89 | + { |
| 90 | + return data != 0; |
| 91 | + } |
| 92 | + |
| 93 | + extern (C++) const(char)* toChars(bool showColumns = Loc.showColumns, |
| 94 | + MessageStyle messageStyle = Loc.messageStyle) const nothrow |
| 95 | + { |
| 96 | + return SourceLoc(this).toChars(showColumns, messageStyle); |
| 97 | + } |
| 98 | + /** |
| 99 | + * Checks for equivalence by comparing the filename contents (not the pointer) and character location. |
| 100 | + * |
| 101 | + * Note: |
| 102 | + * - Uses case-insensitive comparison on Windows |
| 103 | + * - Ignores `charnum` if `Columns` is false. |
| 104 | + */ |
| 105 | + extern (C++) bool equals(Loc loc) const |
| 106 | + { |
| 107 | + auto this_data = showColumns ? data : data & ~0xffff; |
| 108 | + auto loc_data = showColumns ? loc.data : loc.data & ~0xffff; |
| 109 | + return this_data == loc_data; |
| 110 | + } |
| 111 | + |
| 112 | + /** |
| 113 | + * `opEquals()` / `toHash()` for AA key usage |
| 114 | + * |
| 115 | + * Compare filename contents (case-sensitively on Windows too), not |
| 116 | + * the pointer - a static foreach loop repeatedly mixing in a mixin |
| 117 | + * may lead to multiple equivalent filenames (`foo.d-mixin-<line>`), |
| 118 | + * e.g., for test/runnable/test18880.d. |
| 119 | + */ |
| 120 | + extern (D) bool opEquals(ref const(Loc) loc) const @trusted nothrow @nogc |
| 121 | + { |
| 122 | + return this.data == loc.data; |
| 123 | + } |
| 124 | + |
| 125 | + /// ditto |
| 126 | + extern (D) size_t toHash() const @trusted nothrow |
| 127 | + { |
| 128 | + return hashOf(this.data); |
| 129 | + } |
| 130 | +} |
| 131 | + |
| 132 | +/** |
| 133 | + * Format a source location for error messages |
| 134 | + * |
| 135 | + * Params: |
| 136 | + * buf = buffer to write string into |
| 137 | + * loc = source location to write |
| 138 | + * showColumns = include column number in message |
| 139 | + * messageStyle = select error message format |
| 140 | + */ |
| 141 | +void writeSourceLoc(ref OutBuffer buf, SourceLoc loc, bool showColumns, MessageStyle messageStyle) nothrow |
| 142 | +{ |
| 143 | + auto filename = loc.filename; |
| 144 | + if (filename is null) |
| 145 | + return; |
| 146 | + buf.writestring(loc.filename); |
| 147 | + if (loc.linnum == 0) |
| 148 | + return; |
| 149 | + |
| 150 | + final switch (messageStyle) |
| 151 | + { |
| 152 | + case MessageStyle.digitalmars: |
| 153 | + buf.writeByte('('); |
| 154 | + buf.print(loc.linnum); |
| 155 | + if (showColumns && loc.charnum) |
| 156 | + { |
| 157 | + buf.writeByte(','); |
| 158 | + buf.print(loc.charnum); |
| 159 | + } |
| 160 | + buf.writeByte(')'); |
| 161 | + break; |
| 162 | + case MessageStyle.gnu: // https://www.gnu.org/prep/standards/html_node/Errors.html |
| 163 | + buf.writeByte(':'); |
| 164 | + buf.print(loc.linnum); |
| 165 | + if (showColumns && loc.charnum) |
| 166 | + { |
| 167 | + buf.writeByte(':'); |
| 168 | + buf.print(loc.charnum); |
| 169 | + } |
| 170 | + break; |
| 171 | + case MessageStyle.sarif: // https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html |
| 172 | + // No formatting needed here for SARIF |
| 173 | + break; |
| 174 | + } |
| 175 | +} |
| 176 | + |
| 177 | +// Global string table to make file names comparable via `is` |
| 178 | +private __gshared StringTable!(size_t) locFileNameIndex; |
| 179 | +private __gshared const(char)[][] locFileName; |
| 180 | + |
| 181 | +size_t toLocFileIndex(const(char)[] fname) nothrow @trusted |
| 182 | +{ |
| 183 | + if (locFileName.length == 0) |
| 184 | + { |
| 185 | + locFileNameIndex.reset(); |
| 186 | + locFileName ~= null; |
| 187 | + locFileNameIndex.insert("", 0); // for loc.initial |
| 188 | + } |
| 189 | + if (auto p = locFileNameIndex.lookup(fname)) |
| 190 | + return p.value; |
| 191 | + size_t idx = locFileName.length; |
| 192 | + locFileName ~= fname.xarraydup; |
| 193 | + locFileNameIndex.insert(fname, idx); |
| 194 | + return idx; |
| 195 | +} |
| 196 | + |
| 197 | +const(char)[] toLocFilename(const(char)[] fname) nothrow |
| 198 | +{ |
| 199 | + return locFileName[toLocFileIndex(fname)]; |
| 200 | +} |
| 201 | + |
| 202 | +const(char)[] toLocFilename(const(char)* fname) nothrow |
| 203 | +{ |
| 204 | + return toLocFilename(fname.toDString); |
| 205 | +} |
| 206 | + |
| 207 | +void location_init() |
| 208 | +{ |
| 209 | + locFileName = null; |
| 210 | + locFileNameIndex.reset(); |
| 211 | +} |
| 212 | + |
| 213 | +struct SourceLoc |
| 214 | +{ |
| 215 | + Loc loc; |
| 216 | + |
| 217 | + //alias loc this; |
| 218 | + |
| 219 | + // aliases for backwards compatibility |
| 220 | + alias linnum = loc.linnum; |
| 221 | + alias line = loc.linnum; |
| 222 | + alias charnum = loc.charnum; |
| 223 | + alias column = loc.charnum; |
| 224 | + |
| 225 | + this(Loc oloc) nothrow @safe |
| 226 | + { |
| 227 | + loc = oloc; |
| 228 | + } |
| 229 | + |
| 230 | + this(const(char)[] filename, uint line, uint column, |
| 231 | + uint fileOffset = 0, const(char)[] fileContent = null) nothrow @safe |
| 232 | + { |
| 233 | + if (column > 0xffff) |
| 234 | + column = 0xffff; |
| 235 | + ulong fileIndex = toLocFileIndex(filename); |
| 236 | + loc.data = (fileIndex << 48) | (line << 16) | column; |
| 237 | + } |
| 238 | + |
| 239 | + void filename(const(char)[] fname) nothrow |
| 240 | + { |
| 241 | + ulong fileIndex = toLocFileIndex(fname); |
| 242 | + loc.data = (loc.data & ((1L << 48) - 1)) | (fileIndex << 48); |
| 243 | + } |
| 244 | + const(char)[] filename() const nothrow @nogc |
| 245 | + { |
| 246 | + return loc.filename.toDString; |
| 247 | + } |
| 248 | + uint xline() const nothrow |
| 249 | + { |
| 250 | + return loc.linnum(); |
| 251 | + } |
| 252 | + uint xcolumn() const nothrow |
| 253 | + { |
| 254 | + return loc.charnum(); |
| 255 | + } |
| 256 | + const(char)[] fileContent() const nothrow |
| 257 | + { |
| 258 | + return null; // only for error messages with context |
| 259 | + } |
| 260 | + uint fileOffset() const nothrow |
| 261 | + { |
| 262 | + return 0; // only for error messages with context |
| 263 | + } |
| 264 | + |
| 265 | + bool opEquals(SourceLoc other) const nothrow |
| 266 | + { |
| 267 | + return loc == other.loc; |
| 268 | + } |
| 269 | + |
| 270 | + extern (C++) const(char)* toChars(bool showColumns = Loc.showColumns, |
| 271 | + MessageStyle messageStyle = Loc.messageStyle) const nothrow |
| 272 | + { |
| 273 | + OutBuffer buf; |
| 274 | + writeSourceLoc(buf, this, showColumns, messageStyle); |
| 275 | + return buf.extractChars(); |
| 276 | + } |
| 277 | +} |
| 278 | + |
| 279 | +struct BaseLoc |
| 280 | +{ |
| 281 | + SourceLoc loc; |
| 282 | + |
| 283 | + uint startLine; |
| 284 | + uint startOffset; |
| 285 | + uint lastLineOffset; |
| 286 | + BaseLoc[] substitutions; /// Substitutions from #line / #file directives |
| 287 | + |
| 288 | + alias loc this; |
| 289 | + |
| 290 | +nothrow: |
| 291 | + this(const(char)[] filename, uint startLine) |
| 292 | + { |
| 293 | + this.loc = SourceLoc(filename, 1, 1); |
| 294 | + this.startLine = startLine; |
| 295 | + } |
| 296 | + |
| 297 | + Loc getLoc(uint offset) @nogc |
| 298 | + { |
| 299 | + Loc nloc; |
| 300 | + nloc.data = loc.loc.data + offset - lastLineOffset; // add char offset |
| 301 | + return nloc; |
| 302 | + } |
| 303 | + |
| 304 | + /** |
| 305 | + * Register a new file/line mapping from #file and #line directives |
| 306 | + * Params: |
| 307 | + * offset = byte offset in the source file at which the substitution starts |
| 308 | + * filename = new filename from this point on (null = unchanged) |
| 309 | + * line = line number from this point on |
| 310 | + */ |
| 311 | + void addSubstitution(uint offset, const(char)* filename, uint line) @system |
| 312 | + { |
| 313 | + auto fname = filename.toDString; |
| 314 | + if (substitutions.length == 0) |
| 315 | + substitutions ~= BaseLoc(this.filename, 0); |
| 316 | + |
| 317 | + if (fname.length == 0) |
| 318 | + fname = substitutions[$ - 1].filename; |
| 319 | + substitutions ~= BaseLoc(fname, startLine + line); // cast(int) (line - lines.length + startLine - 2)); |
| 320 | + } |
| 321 | + |
| 322 | + /// Returns: `loc` modified by substitutions from #file / #line directives |
| 323 | + SourceLoc substitute(SourceLoc loc) |
| 324 | + { |
| 325 | + if (substitutions.length == 0) |
| 326 | + return loc; |
| 327 | + |
| 328 | + const i = 0; // todo: getSubstitutionIndex(loc.fileOffset); |
| 329 | + if (substitutions[i].filename.length > 0) |
| 330 | + loc.filename = substitutions[i].filename; |
| 331 | + return SourceLoc(loc.filename, loc.line + substitutions[i].startLine, loc.column); |
| 332 | + } |
| 333 | + void newLine(uint offset) @safe |
| 334 | + { |
| 335 | + lastLineOffset = offset; |
| 336 | + loc.loc.nextLine(); |
| 337 | + } |
| 338 | +} |
| 339 | + |
| 340 | +BaseLoc* newBaseLoc(const(char)* filename, const(char)[] fileContent) nothrow |
| 341 | +{ |
| 342 | + return new BaseLoc(filename.toDString, 0); |
| 343 | +} |
| 344 | + |
| 345 | +// for a language server, lowered expression should not reuse the original source location |
| 346 | +// as internal names might get exposed to the user |
| 347 | +ref const(Loc) loweredLoc(return ref const Loc loc) |
| 348 | +{ |
| 349 | + version(LanguageServer) |
| 350 | + return Loc.initial; |
| 351 | + else |
| 352 | + return loc; |
| 353 | +} |
0 commit comments