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