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