1 module source.manager; 2 3 import source.context; 4 import source.location; 5 import source.name; 6 7 struct Source { 8 private: 9 FileID _file; 10 Context context; 11 12 @property 13 ref sourceManager() inout { 14 return context.sourceManager; 15 } 16 17 public: 18 alias file this; 19 @property file() const { 20 return _file; 21 } 22 23 string getContent() { 24 return sourceManager.getContent(this); 25 } 26 27 string getSlice(Location loc) { 28 return getContent()[getOffset(loc.start) .. getOffset(loc.stop)]; 29 } 30 31 FullName getFileName() { 32 return sourceManager.getFileName(this).getFullName(context); 33 } 34 35 FullName getDirectory() in { 36 assert(isFile()); 37 } do { 38 return sourceManager.getDirectory(this).getFullName(context); 39 } 40 41 FullLocation getImportLocation() { 42 return sourceManager 43 .getImportLocation(this) 44 .getFullLocation(context); 45 } 46 47 package: 48 uint getOffset(Position p) in { 49 assert(p.getFullPosition(context).getSource() == this); 50 } do { 51 return p.offset - sourceManager.getOffset(this); 52 } 53 } 54 55 struct SourceManager { 56 private: 57 SourceEntries files = SourceEntries(1); 58 SourceEntries mixins = SourceEntries(int.min); 59 60 // Make it non copyable. 61 @disable this(this); 62 63 public: 64 Position registerFile( 65 Location location, 66 Name filename, 67 Name directory, 68 string content, 69 ) out(result) { 70 assert(result.isFile()); 71 } do { 72 return files.registerFile(location, filename, directory, content); 73 } 74 75 Position registerMixin(Location location, string content) out(result) { 76 assert(result.isMixin()); 77 } do { 78 return mixins.registerMixin(location, content); 79 } 80 81 FileID getFileID(Position p) out(result) { 82 assert(p.isMixin() == result.isMixin()); 83 } do { 84 return p.isFile() 85 ? files.getFileID(p) 86 : mixins.getFileID(p); 87 } 88 89 uint getLineNumber(Position p) { 90 auto e = &getSourceEntry(p); 91 return e.getLineNumber(p.offset - e.base.offset); 92 } 93 94 uint getColumn(Position p) { 95 auto e = &getSourceEntry(p); 96 auto o = p.offset - e.base.offset; 97 return o - e.getLineOffset(o); 98 } 99 100 package: 101 static get() { 102 return SourceManager(); 103 } 104 105 private: 106 string getContent(FileID f) { 107 return getSourceEntry(f).content; 108 } 109 110 uint getOffset(FileID f) { 111 return getSourceEntry(f).base.offset; 112 } 113 114 Name getFileName(FileID f) { 115 return getSourceEntry(f).filename; 116 } 117 118 Name getDirectory(FileID f) in { 119 assert(f.isFile()); 120 } do { 121 return getSourceEntry(f).directory; 122 } 123 124 Location getImportLocation(FileID f) { 125 return getSourceEntry(f).location; 126 } 127 128 ref SourceEntry getSourceEntry(Position p) { 129 return getSourceEntry(getFileID(p)); 130 } 131 132 ref SourceEntry getSourceEntry(FileID f) { 133 return f.isFile() 134 ? files.sourceEntries[f] 135 : mixins.sourceEntries[f]; 136 } 137 } 138 139 struct FileID { 140 private: 141 import std.bitmanip; 142 mixin(bitfields!( 143 bool, "_mixin", 1, 144 uint, "_index", uint.sizeof * 8 - 1, 145 )); 146 147 this(uint id, bool isMixin) { 148 this._index = id; 149 this._mixin = isMixin; 150 } 151 152 public: 153 alias id this; 154 @property id() const { 155 return _index; 156 } 157 158 bool isFile() const { 159 return !_mixin; 160 } 161 162 bool isMixin() const { 163 return _mixin; 164 } 165 166 Source getSource(Context c) { 167 return Source(this, c); 168 } 169 } 170 171 unittest { 172 uint i = 1; 173 auto f = *cast(FileID*) &i; 174 175 assert(f.isMixin()); 176 assert(f.id == 0); 177 } 178 179 private: 180 181 struct SourceEntries { 182 SourceEntry[] sourceEntries; 183 Position nextSourcePos; 184 FileID lastFileID; 185 186 this(uint base) { 187 nextSourcePos = Position(base); 188 lastFileID = FileID(0, nextSourcePos.isMixin()); 189 } 190 191 Position registerFile( 192 Location location, 193 Name filename, 194 Name directory, 195 string content, 196 ) in { 197 assert(nextSourcePos.isFile()); 198 } do { 199 auto base = nextSourcePos; 200 nextSourcePos = nextSourcePos 201 .getWithOffset(cast(uint) content.length); 202 sourceEntries ~= 203 SourceEntry(base, location, filename, directory, content); 204 return base; 205 } 206 207 Position registerMixin(Location location, string content) in { 208 assert(nextSourcePos.isMixin()); 209 } do { 210 auto base = nextSourcePos; 211 nextSourcePos = nextSourcePos 212 .getWithOffset(cast(uint) content.length); 213 sourceEntries ~= SourceEntry(base, location, content); 214 return base; 215 } 216 217 bool isPositionInFileID(Position p, FileID fileID) { 218 auto offset = p.offset; 219 if (offset < sourceEntries[fileID].offset) { 220 return false; 221 } 222 223 return (fileID + 1 == sourceEntries.length) 224 ? (offset < nextSourcePos.offset) 225 : (offset < sourceEntries[fileID + 1].offset); 226 } 227 228 FileID getFileID(Position p) in { 229 assert(p.isMixin() == nextSourcePos.isMixin()); 230 assert(p.offset < nextSourcePos.offset); 231 } do { 232 // It is common to query the same file many time, 233 // so we have a one entry cache for it. 234 if (!isPositionInFileID(p, lastFileID)) { 235 import source.util.lookup; 236 lastFileID = FileID(lookup!(e => e.offset, 7)( 237 sourceEntries, 238 p.offset, 239 lastFileID, 240 ), p.isMixin()); 241 } 242 243 return lastFileID; 244 } 245 } 246 247 struct SourceEntry { 248 private: 249 Position base; 250 alias base this; 251 252 uint lastLineLookup; 253 immutable(uint)[] lines; 254 255 Location location; 256 string _content; 257 258 Name _filename; 259 Name _directory; 260 261 ulong pad; 262 263 // Make sure this is compact enough to fit in a cache line. 264 static assert(SourceEntry.sizeof == 8 * size_t.sizeof); 265 266 public: 267 @property 268 string content() const { 269 return _content; 270 } 271 272 @property 273 auto filename() const in { 274 assert(base.isFile()); 275 } do { 276 return _filename; 277 } 278 279 @property 280 auto directory() const in { 281 assert(base.isFile()); 282 } do { 283 return _directory; 284 } 285 286 private: 287 this(Position base, Location location, string content) in { 288 assert(base.isMixin()); 289 } do { 290 this.base = base; 291 this.location = location; 292 _content = content; 293 } 294 295 this( 296 Position base, 297 Location location, 298 Name filename, 299 Name directory, 300 string content, 301 ) in { 302 assert(base.isFile()); 303 } do { 304 this.base = base; 305 this.location = location; 306 _content = content; 307 _filename = filename; 308 _directory = directory; 309 } 310 311 uint getLineNumber(uint index) { 312 if (!lines) { 313 lines = getLines(content); 314 } 315 316 // It is common to query the same file many time, 317 // so we have a one entry cache for it. 318 if (!isIndexInLine(index, lastLineLookup)) { 319 import source.util.lookup; 320 lastLineLookup = lookup!(l => l, 15)( 321 lines, 322 index, 323 lastLineLookup, 324 ); 325 } 326 327 return lastLineLookup + 1; 328 } 329 330 uint getLineOffset(uint index) out(result) { 331 assert(result <= index); 332 } do { 333 return lines[getLineNumber(index) - 1]; 334 } 335 336 bool isIndexInLine(uint index, uint line) { 337 if (index < lines[line]) { 338 return false; 339 } 340 341 return (line + 1 == lines.length) 342 ? (index < content.length) 343 : (index < lines[line + 1]); 344 } 345 } 346 347 // XXX: This need to be vectorized 348 immutable(uint)[] getLines(string content) { 349 immutable(uint)[] ret = []; 350 351 uint p = 0; 352 uint i = 0; 353 char c = content[i]; 354 while (true) { 355 while (c != '\n' && c != '\r' && c != '\0') { 356 c = content[++i]; 357 } 358 359 if (c == '\0') { 360 ret ~= p; 361 return ret; 362 } 363 364 auto match = c; 365 c = content[++i]; 366 367 // \r\n is a special case 368 if (match == '\r' && c == '\n') { 369 c = content[++i]; 370 } 371 372 ret ~= p; 373 p = i; 374 } 375 }