1 /**
2 * Read a file from disk and store it in memory.
3 *
4 * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
5 * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/file_manager.d, _file_manager.d)
7 * Documentation: https://dlang.org/phobos/dmd_file_manager.html
8 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/file_manager.d
9 */
10
11 module dmd.file_manager;
12
13 import dmd.root.stringtable : StringTable;
14 import dmd.root.file : File, Buffer;
15 import dmd.root.filename : FileName;
16 import dmd.root.string : toDString;
17 import dmd.globals;
18 import dmd.identifier;
19
20 enum package_d = "package." ~ mars_ext;
21 enum package_di = "package." ~ hdr_ext;
22
23 final class FileManager
24 {
25 private StringTable!(const(ubyte)[]) files;
26
27 ///
this()28 public this () nothrow
29 {
30 this.files._init();
31 }
32
33 nothrow:
34 /********************************************
35 * Look for the source file if it's different from filename.
36 * Look for .di, .d, directory, and along global.path.
37 * Does not open the file.
38 * Params:
39 * filename = as supplied by the user
40 * path = path to look for filename
41 * Returns:
42 * the found file name or
43 * `null` if it is not different from filename.
44 */
lookForSourceFile(const char[]filename,const char * []path)45 static const(char)[] lookForSourceFile(const char[] filename, const char*[] path)
46 {
47 //printf("lookForSourceFile(`%.*s`)\n", cast(int)filename.length, filename.ptr);
48 /* Search along path[] for .di file, then .d file, then .i file, then .c file.
49 */
50 const sdi = FileName.forceExt(filename, hdr_ext);
51 if (FileName.exists(sdi) == 1)
52 return sdi;
53 scope(exit) FileName.free(sdi.ptr);
54
55 const sd = FileName.forceExt(filename, mars_ext);
56 // Special file name representing `stdin`, always assume its presence
57 if (sd == "__stdin.d")
58 return sd;
59 if (FileName.exists(sd) == 1)
60 return sd;
61 scope(exit) FileName.free(sd.ptr);
62
63 const si = FileName.forceExt(filename, i_ext);
64 if (FileName.exists(si) == 1)
65 return si;
66 scope(exit) FileName.free(si.ptr);
67
68 const sc = FileName.forceExt(filename, c_ext);
69 if (FileName.exists(sc) == 1)
70 return sc;
71 scope(exit) FileName.free(sc.ptr);
72
73 if (FileName.exists(filename) == 2)
74 {
75 /* The filename exists and it's a directory.
76 * Therefore, the result should be: filename/package.d
77 * iff filename/package.d is a file
78 */
79 const ni = FileName.combine(filename, package_di);
80 if (FileName.exists(ni) == 1)
81 return ni;
82 FileName.free(ni.ptr);
83
84 const n = FileName.combine(filename, package_d);
85 if (FileName.exists(n) == 1)
86 return n;
87 FileName.free(n.ptr);
88 }
89 if (FileName.absolute(filename))
90 return null;
91 if (!path.length)
92 return null;
93 foreach (entry; path)
94 {
95 const p = entry.toDString();
96
97 const(char)[] n = FileName.combine(p, sdi);
98 if (FileName.exists(n) == 1) {
99 return n;
100 }
101 FileName.free(n.ptr);
102
103 n = FileName.combine(p, sd);
104 if (FileName.exists(n) == 1) {
105 return n;
106 }
107 FileName.free(n.ptr);
108
109 n = FileName.combine(p, si);
110 if (FileName.exists(n) == 1) {
111 return n;
112 }
113 FileName.free(n.ptr);
114
115 n = FileName.combine(p, sc);
116 if (FileName.exists(n) == 1) {
117 return n;
118 }
119 FileName.free(n.ptr);
120
121 const b = FileName.removeExt(filename);
122 n = FileName.combine(p, b);
123 FileName.free(b.ptr);
124 if (FileName.exists(n) == 2)
125 {
126 const n2i = FileName.combine(n, package_di);
127 if (FileName.exists(n2i) == 1)
128 return n2i;
129 FileName.free(n2i.ptr);
130 const n2 = FileName.combine(n, package_d);
131 if (FileName.exists(n2) == 1) {
132 return n2;
133 }
134 FileName.free(n2.ptr);
135 }
136 FileName.free(n.ptr);
137 }
138 return null;
139 }
140
141 /**
142 * Looks up the given filename from the internal file buffer table.
143 * If the file does not already exist within the table, it will be read from the filesystem.
144 * If it has been read before,
145 *
146 * Returns: the loaded source file if it was found in memory,
147 * otherwise `null`
148 */
lookup(FileName filename)149 const(ubyte)[] lookup(FileName filename)
150 {
151 const name = filename.toString;
152 if (auto val = files.lookup(name))
153 return val.value;
154
155 if (name == "__stdin.d")
156 {
157 auto buffer = readFromStdin().extractSlice();
158 if (this.files.insert(name, buffer) is null)
159 assert(0, "stdin: Insert after lookup failure should never return `null`");
160 return buffer;
161 }
162
163 if (FileName.exists(name) != 1)
164 return null;
165
166 auto readResult = File.read(name);
167 if (!readResult.success)
168 return null;
169
170 auto fb = readResult.extractSlice();
171 if (files.insert(name, fb) is null)
172 assert(0, "Insert after lookup failure should never return `null`");
173
174 return fb;
175 }
176
177 /**
178 * Looks up the given filename from the internal file buffer table, and returns the lines within the file.
179 * If the file does not already exist within the table, it will be read from the filesystem.
180 * If it has been read before,
181 *
182 * Returns: the loaded source file if it was found in memory,
183 * otherwise `null`
184 */
getLines(FileName file)185 const(char)[][] getLines(FileName file)
186 {
187 const(char)[][] lines;
188 if (const buffer = lookup(file))
189 {
190 const slice = buffer;
191 size_t start, end;
192 for (auto i = 0; i < slice.length; i++)
193 {
194 const c = slice[i];
195 if (c == '\n' || c == '\r')
196 {
197 if (i != 0)
198 {
199 end = i;
200 // Appending lines one at a time will certainly be slow
201 lines ~= cast(const(char)[])slice[start .. end];
202 }
203 // Check for Windows-style CRLF newlines
204 if (c == '\r')
205 {
206 if (slice.length > i + 1 && slice[i + 1] == '\n')
207 {
208 // This is a CRLF sequence, skip over two characters
209 start = i + 2;
210 i++;
211 }
212 else
213 {
214 // Just a CR sequence
215 start = i + 1;
216 }
217 }
218 else
219 {
220 // The next line should start after the LF sequence
221 start = i + 1;
222 }
223 }
224 }
225
226 if (slice[$ - 1] != '\r' && slice[$ - 1] != '\n')
227 {
228 end = slice.length;
229 lines ~= cast(const(char)[])slice[start .. end];
230 }
231 }
232
233 return lines;
234 }
235
236 /**
237 * Adds the contents of a file to the table.
238 * Params:
239 * filename = name of the file
240 * buffer = contents of the file
241 * Returns:
242 * the buffer added, or null
243 */
add(FileName filename,const (ubyte)[]buffer)244 const(ubyte)[] add(FileName filename, const(ubyte)[] buffer)
245 {
246 auto val = files.insert(filename.toString, buffer);
247 return val == null ? null : val.value;
248 }
249 }
250
readFromStdin()251 private Buffer readFromStdin() nothrow
252 {
253 import core.stdc.stdio;
254 import dmd.errors;
255 import dmd.root.rmem;
256
257 enum bufIncrement = 128 * 1024;
258 size_t pos = 0;
259 size_t sz = bufIncrement;
260
261 ubyte* buffer = null;
262 for (;;)
263 {
264 buffer = cast(ubyte*)mem.xrealloc(buffer, sz + 4); // +2 for sentinel and +2 for lexer
265
266 // Fill up buffer
267 do
268 {
269 assert(sz > pos);
270 size_t rlen = fread(buffer + pos, 1, sz - pos, stdin);
271 pos += rlen;
272 if (ferror(stdin))
273 {
274 import core.stdc.errno;
275 error(Loc.initial, "cannot read from stdin, errno = %d", errno);
276 fatal();
277 }
278 if (feof(stdin))
279 {
280 // We're done
281 assert(pos < sz + 2);
282 buffer[pos .. pos + 4] = '\0';
283 return Buffer(buffer[0 .. pos]);
284 }
285 } while (pos < sz);
286
287 // Buffer full, expand
288 sz += bufIncrement;
289 }
290
291 assert(0);
292 }
293