blob: a0e5d0519c8ec3c3ac935b9d171b2a74ac13c9b8 [file] [log] [blame]
/**
* Read a file from disk and store it in memory.
*
* Copyright: Copyright (C) 1999-2023 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, isDirSeparator;
import dmd.root.string : toDString;
import dmd.globals;
import dmd.identifier;
import dmd.location;
enum package_d = "package." ~ mars_ext;
enum package_di = "package." ~ hdr_ext;
/// Returns: whether a file with `name` is a special "package.d" module
bool isPackageFileName(scope FileName fileName) nothrow
{
return FileName.equals(fileName.name, package_d) || FileName.equals(fileName.name, package_di);
}
// A path stack that allows one to go up and down the path using directory
// separators. `cur` is the current path, `up` goes up one path, `down` goes
// down one path. if `up` or `down` return false, there are no further paths.
private struct PathStack
{
private const(char)[] path;
private size_t pos;
@safe @nogc nothrow pure:
this(const(char)[] p)
{
path = p;
pos = p.length;
}
const(char)[] cur()
{
return path[0 .. pos];
}
bool up()
{
if (pos == 0)
return false;
while (--pos != 0)
if (isDirSeparator(path[pos]))
return true;
return false;
}
bool down()
{
if (pos == path.length)
return false;
while (++pos != path.length)
if (isDirSeparator(path[pos]))
return true;
return false;
}
}
final class FileManager
{
private StringTable!(const(ubyte)[]) files;
private StringTable!(bool) packageStatus;
// check if the package path of the given path exists. The input path is
// expected to contain the full path to the module, so the parent
// directories of that path are checked.
private bool packageExists(const(char)[] p) nothrow
{
// step 1, look for the closest parent path that is cached
bool exists = true;
auto st = PathStack(p);
while (st.up) {
if (auto cached = packageStatus.lookup(st.cur)) {
exists = cached.value;
break;
}
}
// found a parent that is cached (or reached the end of the stack).
// step 2, traverse back up the stack storing either false if the
// parent doesn't exist, or the result of the `exists` call if it does.
while (st.down) {
if (!exists)
packageStatus.insert(st.cur, false);
else
exists = packageStatus.insert(st.cur, FileName.exists(st.cur) == 2).value;
}
// at this point, exists should be the answer.
return exists;
}
///
public this () nothrow
{
this.files._init();
this.packageStatus._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.
*/
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.
*/
// see if we should check for the module locally.
bool checkLocal = packageExists(filename);
const sdi = FileName.forceExt(filename, hdr_ext);
if (checkLocal && 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 (checkLocal && FileName.exists(sd) == 1)
return sd;
scope(exit) FileName.free(sd.ptr);
if (checkLocal)
{
auto cached = packageStatus.lookup(filename);
if (!cached)
cached = packageStatus.insert(filename, FileName.exists(filename) == 2);
if (cached.value)
{
/* 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 (!packageExists(n)) {
FileName.free(n.ptr);
continue; // no need to check for anything else.
}
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);
const b = FileName.removeExt(filename);
n = FileName.combine(p, b);
FileName.free(b.ptr);
scope(exit) FileName.free(n.ptr);
// also cache this if we are looking for package.d[i]
auto cached = packageStatus.lookup(n);
if (!cached) {
cached = packageStatus.insert(n, FileName.exists(n) == 2);
}
if (cached.value)
{
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);
}
}
/* ImportC: No D modules found, now search along path[] for .i file, then .c file.
*/
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);
foreach (entry; path)
{
const p = entry.toDString();
const(char)[] 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);
}
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);
}