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 }