blob: 5be4a196345ee5c4a61a9803292b7abe9eb40adf [file] [log] [blame]
/**
* This module defines some utility functions for DMD.
*
* Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
* Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
* 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/utils.d, _utils.d)
* Documentation: https://dlang.org/phobos/dmd_utils.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/utils.d
*/
module dmd.utils;
import core.stdc.string;
import dmd.errors;
import dmd.globals;
import dmd.root.file;
import dmd.root.filename;
import dmd.common.outbuffer;
import dmd.root.string;
nothrow:
/**
* Normalize path by turning forward slashes into backslashes
*
* Params:
* src = Source path, using unix-style ('/') path separators
*
* Returns:
* A newly-allocated string with '/' turned into backslashes
*/
const(char)* toWinPath(const(char)* src)
{
if (src is null)
return null;
char* result = strdup(src);
char* p = result;
while (*p != '\0')
{
if (*p == '/')
*p = '\\';
p++;
}
return result;
}
/**
* Reads a file, terminate the program on error
*
* Params:
* loc = The line number information from where the call originates
* filename = Path to file
*/
Buffer readFile(Loc loc, const(char)* filename)
{
return readFile(loc, filename.toDString());
}
/// Ditto
Buffer readFile(Loc loc, const(char)[] filename)
{
auto result = File.read(filename);
if (!result.success)
{
error(loc, "error reading file `%.*s`", cast(int)filename.length, filename.ptr);
fatal();
}
return Buffer(result.extractSlice());
}
/**
* Writes a file, terminate the program on error
*
* Params:
* loc = The line number information from where the call originates
* filename = Path to file
* data = Full content of the file to be written
*/
extern (D) void writeFile(Loc loc, const(char)[] filename, const void[] data)
{
ensurePathToNameExists(Loc.initial, filename);
if (!File.update(filename, data))
{
error(loc, "error writing file '%.*s'", cast(int) filename.length, filename.ptr);
fatal();
}
}
/**
* Ensure the root path (the path minus the name) of the provided path
* exists, and terminate the process if it doesn't.
*
* Params:
* loc = The line number information from where the call originates
* name = a path to check (the name is stripped)
*/
void ensurePathToNameExists(Loc loc, const(char)[] name)
{
const char[] pt = FileName.path(name);
if (pt.length)
{
if (!FileName.ensurePathExists(pt))
{
error(loc, "cannot create directory %*.s", cast(int) pt.length, pt.ptr);
fatal();
}
}
FileName.free(pt.ptr);
}
/**
* Takes a path, and escapes '(', ')' and backslashes
*
* Params:
* buf = Buffer to write the escaped path to
* fname = Path to escape
*/
void escapePath(OutBuffer* buf, const(char)* fname)
{
while (1)
{
switch (*fname)
{
case 0:
return;
case '(':
case ')':
case '\\':
buf.writeByte('\\');
goto default;
default:
buf.writeByte(*fname);
break;
}
fname++;
}
}
/**
* Takes a path, and make it compatible with GNU Makefile format.
*
* GNU make uses a weird quoting scheme for white space.
* A space or tab preceded by 2N+1 backslashes represents N backslashes followed by space;
* a space or tab preceded by 2N backslashes represents N backslashes at the end of a file name;
* and backslashes in other contexts should not be doubled.
*
* Params:
* buf = Buffer to write the escaped path to
* fname = Path to escape
*/
void writeEscapedMakePath(ref OutBuffer buf, const(char)* fname)
{
uint slashes;
while (*fname)
{
switch (*fname)
{
case '\\':
slashes++;
break;
case '$':
buf.writeByte('$');
goto default;
case ' ':
case '\t':
while (slashes--)
buf.writeByte('\\');
goto case;
case '#':
buf.writeByte('\\');
goto default;
case ':':
// ':' not escaped on Windows because it can
// create problems with absolute paths (e.g. C:\Project)
version (Windows) {}
else
{
buf.writeByte('\\');
}
goto default;
default:
slashes = 0;
break;
}
buf.writeByte(*fname);
fname++;
}
}
///
unittest
{
version (Windows)
{
enum input = `C:\My Project\file#4$.ext`;
enum expected = `C:\My\ Project\file\#4$$.ext`;
}
else
{
enum input = `/foo\bar/weird$.:name#\ with spaces.ext`;
enum expected = `/foo\bar/weird$$.\:name\#\\\ with\ spaces.ext`;
}
OutBuffer buf;
buf.writeEscapedMakePath(input);
assert(buf[] == expected);
}
/**
* Convert string to integer.
*
* Params:
* T = Type of integer to parse
* val = Variable to store the result in
* p = slice to start of string digits
* max = max allowable value (inclusive), defaults to `T.max`
*
* Returns:
* `false` on error, `true` on success
*/
bool parseDigits(T)(ref T val, const(char)[] p, const T max = T.max)
@safe pure @nogc nothrow
{
import core.checkedint : mulu, addu, muls, adds;
// mul* / add* doesn't support types < int
static if (T.sizeof < int.sizeof)
{
int value;
alias add = adds;
alias mul = muls;
}
// unsigned
else static if (T.min == 0)
{
T value;
alias add = addu;
alias mul = mulu;
}
else
{
T value;
alias add = adds;
alias mul = muls;
}
bool overflow;
foreach (char c; p)
{
if (c > '9' || c < '0')
return false;
value = mul(value, 10, overflow);
value = add(value, uint(c - '0'), overflow);
}
// If it overflows, value must be > to `max` (since `max` is `T`)
val = cast(T) value;
return !overflow && value <= max;
}
///
@safe pure nothrow @nogc unittest
{
byte b;
ubyte ub;
short s;
ushort us;
int i;
uint ui;
long l;
ulong ul;
assert(b.parseDigits("42") && b == 42);
assert(ub.parseDigits("42") && ub == 42);
assert(s.parseDigits("420") && s == 420);
assert(us.parseDigits("42000") && us == 42_000);
assert(i.parseDigits("420000") && i == 420_000);
assert(ui.parseDigits("420000") && ui == 420_000);
assert(l.parseDigits("42000000000") && l == 42_000_000_000);
assert(ul.parseDigits("82000000000") && ul == 82_000_000_000);
assert(!b.parseDigits(ubyte.max.stringof));
assert(!b.parseDigits("WYSIWYG"));
assert(!b.parseDigits("-42"));
assert(!b.parseDigits("200"));
assert(ub.parseDigits("200") && ub == 200);
assert(i.parseDigits(int.max.stringof) && i == int.max);
assert(i.parseDigits("420", 500) && i == 420);
assert(!i.parseDigits("420", 400));
}