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 }