/** * 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)); }