| /** | |
| * Read a file from disk and store it in memory. | |
| * | |
| * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved | |
| * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) | |
| * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/file_manager.d, _file_manager.d) | |
| * Documentation: https://dlang.org/phobos/dmd_file_manager.html | |
| * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/file_manager.d | |
| */ | |
| module dmd.file_manager; | |
| import dmd.root.stringtable : StringTable; | |
| import dmd.root.file : File, Buffer; | |
| import dmd.root.filename : FileName; | |
| import dmd.root.string : toDString; | |
| import dmd.globals; | |
| import dmd.identifier; | |
| enum package_d = "package." ~ mars_ext; | |
| enum package_di = "package." ~ hdr_ext; | |
| final class FileManager | |
| { | |
| private StringTable!(const(ubyte)[]) files; | |
| /// | |
| public this () nothrow | |
| { | |
| this.files._init(); | |
| } | |
| nothrow: | |
| /******************************************** | |
| * Look for the source file if it's different from filename. | |
| * Look for .di, .d, directory, and along global.path. | |
| * Does not open the file. | |
| * Params: | |
| * filename = as supplied by the user | |
| * path = path to look for filename | |
| * Returns: | |
| * the found file name or | |
| * `null` if it is not different from filename. | |
| */ | |
| static const(char)[] lookForSourceFile(const char[] filename, const char*[] path) | |
| { | |
| //printf("lookForSourceFile(`%.*s`)\n", cast(int)filename.length, filename.ptr); | |
| /* Search along path[] for .di file, then .d file, then .i file, then .c file. | |
| */ | |
| const sdi = FileName.forceExt(filename, hdr_ext); | |
| if (FileName.exists(sdi) == 1) | |
| return sdi; | |
| scope(exit) FileName.free(sdi.ptr); | |
| const sd = FileName.forceExt(filename, mars_ext); | |
| // Special file name representing `stdin`, always assume its presence | |
| if (sd == "__stdin.d") | |
| return sd; | |
| if (FileName.exists(sd) == 1) | |
| return sd; | |
| scope(exit) FileName.free(sd.ptr); | |
| const si = FileName.forceExt(filename, i_ext); | |
| if (FileName.exists(si) == 1) | |
| return si; | |
| scope(exit) FileName.free(si.ptr); | |
| const sc = FileName.forceExt(filename, c_ext); | |
| if (FileName.exists(sc) == 1) | |
| return sc; | |
| scope(exit) FileName.free(sc.ptr); | |
| if (FileName.exists(filename) == 2) | |
| { | |
| /* The filename exists and it's a directory. | |
| * Therefore, the result should be: filename/package.d | |
| * iff filename/package.d is a file | |
| */ | |
| const ni = FileName.combine(filename, package_di); | |
| if (FileName.exists(ni) == 1) | |
| return ni; | |
| FileName.free(ni.ptr); | |
| const n = FileName.combine(filename, package_d); | |
| if (FileName.exists(n) == 1) | |
| return n; | |
| FileName.free(n.ptr); | |
| } | |
| if (FileName.absolute(filename)) | |
| return null; | |
| if (!path.length) | |
| return null; | |
| foreach (entry; path) | |
| { | |
| const p = entry.toDString(); | |
| const(char)[] n = FileName.combine(p, sdi); | |
| if (FileName.exists(n) == 1) { | |
| return n; | |
| } | |
| FileName.free(n.ptr); | |
| n = FileName.combine(p, sd); | |
| if (FileName.exists(n) == 1) { | |
| return n; | |
| } | |
| FileName.free(n.ptr); | |
| n = FileName.combine(p, si); | |
| if (FileName.exists(n) == 1) { | |
| return n; | |
| } | |
| FileName.free(n.ptr); | |
| n = FileName.combine(p, sc); | |
| if (FileName.exists(n) == 1) { | |
| return n; | |
| } | |
| FileName.free(n.ptr); | |
| const b = FileName.removeExt(filename); | |
| n = FileName.combine(p, b); | |
| FileName.free(b.ptr); | |
| if (FileName.exists(n) == 2) | |
| { | |
| const n2i = FileName.combine(n, package_di); | |
| if (FileName.exists(n2i) == 1) | |
| return n2i; | |
| FileName.free(n2i.ptr); | |
| const n2 = FileName.combine(n, package_d); | |
| if (FileName.exists(n2) == 1) { | |
| return n2; | |
| } | |
| FileName.free(n2.ptr); | |
| } | |
| FileName.free(n.ptr); | |
| } | |
| return null; | |
| } | |
| /** | |
| * Looks up the given filename from the internal file buffer table. | |
| * If the file does not already exist within the table, it will be read from the filesystem. | |
| * If it has been read before, | |
| * | |
| * Returns: the loaded source file if it was found in memory, | |
| * otherwise `null` | |
| */ | |
| const(ubyte)[] lookup(FileName filename) | |
| { | |
| const name = filename.toString; | |
| if (auto val = files.lookup(name)) | |
| return val.value; | |
| if (name == "__stdin.d") | |
| { | |
| auto buffer = readFromStdin().extractSlice(); | |
| if (this.files.insert(name, buffer) is null) | |
| assert(0, "stdin: Insert after lookup failure should never return `null`"); | |
| return buffer; | |
| } | |
| if (FileName.exists(name) != 1) | |
| return null; | |
| auto readResult = File.read(name); | |
| if (!readResult.success) | |
| return null; | |
| auto fb = readResult.extractSlice(); | |
| if (files.insert(name, fb) is null) | |
| assert(0, "Insert after lookup failure should never return `null`"); | |
| return fb; | |
| } | |
| /** | |
| * Looks up the given filename from the internal file buffer table, and returns the lines within the file. | |
| * If the file does not already exist within the table, it will be read from the filesystem. | |
| * If it has been read before, | |
| * | |
| * Returns: the loaded source file if it was found in memory, | |
| * otherwise `null` | |
| */ | |
| const(char)[][] getLines(FileName file) | |
| { | |
| const(char)[][] lines; | |
| if (const buffer = lookup(file)) | |
| { | |
| const slice = buffer; | |
| size_t start, end; | |
| for (auto i = 0; i < slice.length; i++) | |
| { | |
| const c = slice[i]; | |
| if (c == '\n' || c == '\r') | |
| { | |
| if (i != 0) | |
| { | |
| end = i; | |
| // Appending lines one at a time will certainly be slow | |
| lines ~= cast(const(char)[])slice[start .. end]; | |
| } | |
| // Check for Windows-style CRLF newlines | |
| if (c == '\r') | |
| { | |
| if (slice.length > i + 1 && slice[i + 1] == '\n') | |
| { | |
| // This is a CRLF sequence, skip over two characters | |
| start = i + 2; | |
| i++; | |
| } | |
| else | |
| { | |
| // Just a CR sequence | |
| start = i + 1; | |
| } | |
| } | |
| else | |
| { | |
| // The next line should start after the LF sequence | |
| start = i + 1; | |
| } | |
| } | |
| } | |
| if (slice[$ - 1] != '\r' && slice[$ - 1] != '\n') | |
| { | |
| end = slice.length; | |
| lines ~= cast(const(char)[])slice[start .. end]; | |
| } | |
| } | |
| return lines; | |
| } | |
| /** | |
| * Adds the contents of a file to the table. | |
| * Params: | |
| * filename = name of the file | |
| * buffer = contents of the file | |
| * Returns: | |
| * the buffer added, or null | |
| */ | |
| const(ubyte)[] add(FileName filename, const(ubyte)[] buffer) | |
| { | |
| auto val = files.insert(filename.toString, buffer); | |
| return val == null ? null : val.value; | |
| } | |
| } | |
| private Buffer readFromStdin() nothrow | |
| { | |
| import core.stdc.stdio; | |
| import dmd.errors; | |
| import dmd.root.rmem; | |
| enum bufIncrement = 128 * 1024; | |
| size_t pos = 0; | |
| size_t sz = bufIncrement; | |
| ubyte* buffer = null; | |
| for (;;) | |
| { | |
| buffer = cast(ubyte*)mem.xrealloc(buffer, sz + 4); // +2 for sentinel and +2 for lexer | |
| // Fill up buffer | |
| do | |
| { | |
| assert(sz > pos); | |
| size_t rlen = fread(buffer + pos, 1, sz - pos, stdin); | |
| pos += rlen; | |
| if (ferror(stdin)) | |
| { | |
| import core.stdc.errno; | |
| error(Loc.initial, "cannot read from stdin, errno = %d", errno); | |
| fatal(); | |
| } | |
| if (feof(stdin)) | |
| { | |
| // We're done | |
| assert(pos < sz + 2); | |
| buffer[pos .. pos + 4] = '\0'; | |
| return Buffer(buffer[0 .. pos]); | |
| } | |
| } while (pos < sz); | |
| // Buffer full, expand | |
| sz += bufIncrement; | |
| } | |
| assert(0); | |
| } |