1 /** 2 * This module defines some utility functions for DMD. 3 * 4 * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved 5 * Authors: $(LINK2 http://www.digitalmars.com, Walter Bright) 6 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/utils.d, _utils.d) 8 * Documentation: https://dlang.org/phobos/dmd_utils.html 9 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/utils.d 10 */ 11 12 module dmd.utils; 13 14 import core.stdc.string; 15 import dmd.errors; 16 import dmd.globals; 17 import dmd.root.file; 18 import dmd.root.filename; 19 import dmd.root.outbuffer; 20 import dmd.root.string; 21 22 23 /** 24 * Normalize path by turning forward slashes into backslashes 25 * 26 * Params: 27 * src = Source path, using unix-style ('/') path separators 28 * 29 * Returns: 30 * A newly-allocated string with '/' turned into backslashes 31 */ 32 const(char)* toWinPath(const(char)* src) 33 { 34 if (src is null) 35 return null; 36 char* result = strdup(src); 37 char* p = result; 38 while (*p != '\0') 39 { 40 if (*p == '/') 41 *p = '\\'; 42 p++; 43 } 44 return result; 45 } 46 47 48 /** 49 * Reads a file, terminate the program on error 50 * 51 * Params: 52 * loc = The line number information from where the call originates 53 * filename = Path to file 54 */ 55 FileBuffer readFile(Loc loc, const(char)* filename) 56 { 57 return readFile(loc, filename.toDString()); 58 } 59 60 /// Ditto 61 FileBuffer readFile(Loc loc, const(char)[] filename) 62 { 63 auto result = File.read(filename); 64 if (!result.success) 65 { 66 error(loc, "Error reading file `%.*s`", cast(int)filename.length, filename.ptr); 67 fatal(); 68 } 69 return FileBuffer(result.extractSlice()); 70 } 71 72 73 /** 74 * Writes a file, terminate the program on error 75 * 76 * Params: 77 * loc = The line number information from where the call originates 78 * filename = Path to file 79 * data = Full content of the file to be written 80 */ 81 extern (D) void writeFile(Loc loc, const(char)[] filename, const void[] data) 82 { 83 ensurePathToNameExists(Loc.initial, filename); 84 if (!File.update(filename, data)) 85 { 86 error(loc, "Error writing file '%*.s'", cast(int) filename.length, filename.ptr); 87 fatal(); 88 } 89 } 90 91 92 /** 93 * Ensure the root path (the path minus the name) of the provided path 94 * exists, and terminate the process if it doesn't. 95 * 96 * Params: 97 * loc = The line number information from where the call originates 98 * name = a path to check (the name is stripped) 99 */ 100 void ensurePathToNameExists(Loc loc, const(char)[] name) 101 { 102 const char[] pt = FileName.path(name); 103 if (pt.length) 104 { 105 if (!FileName.ensurePathExists(pt)) 106 { 107 error(loc, "cannot create directory %*.s", cast(int) pt.length, pt.ptr); 108 fatal(); 109 } 110 } 111 FileName.free(pt.ptr); 112 } 113 114 115 /** 116 * Takes a path, and escapes '(', ')' and backslashes 117 * 118 * Params: 119 * buf = Buffer to write the escaped path to 120 * fname = Path to escape 121 */ 122 void escapePath(OutBuffer* buf, const(char)* fname) 123 { 124 while (1) 125 { 126 switch (*fname) 127 { 128 case 0: 129 return; 130 case '(': 131 case ')': 132 case '\\': 133 buf.writeByte('\\'); 134 goto default; 135 default: 136 buf.writeByte(*fname); 137 break; 138 } 139 fname++; 140 } 141 } 142 143 /** 144 * Takes a path, and make it compatible with GNU Makefile format. 145 * 146 * GNU make uses a weird quoting scheme for white space. 147 * A space or tab preceded by 2N+1 backslashes represents N backslashes followed by space; 148 * a space or tab preceded by 2N backslashes represents N backslashes at the end of a file name; 149 * and backslashes in other contexts should not be doubled. 150 * 151 * Params: 152 * buf = Buffer to write the escaped path to 153 * fname = Path to escape 154 */ 155 void writeEscapedMakePath(ref OutBuffer buf, const(char)* fname) 156 { 157 uint slashes; 158 159 while (*fname) 160 { 161 switch (*fname) 162 { 163 case '\\': 164 slashes++; 165 break; 166 case '$': 167 buf.writeByte('$'); 168 goto default; 169 case ' ': 170 case '\t': 171 while (slashes--) 172 buf.writeByte('\\'); 173 goto case; 174 case '#': 175 buf.writeByte('\\'); 176 goto default; 177 case ':': 178 // ':' not escaped on Windows because it can 179 // create problems with absolute paths (e.g. C:\Project) 180 version (Windows) {} 181 else 182 { 183 buf.writeByte('\\'); 184 } 185 goto default; 186 default: 187 slashes = 0; 188 break; 189 } 190 191 buf.writeByte(*fname); 192 fname++; 193 } 194 } 195 196 /// 197 unittest 198 { 199 version (Windows) 200 { 201 enum input = `C:\My Project\file#4$.ext`; 202 enum expected = `C:\My\ Project\file\#4$$.ext`; 203 } 204 else 205 { 206 enum input = `/foo\bar/weird$.:name#\ with spaces.ext`; 207 enum expected = `/foo\bar/weird$$.\:name\#\\\ with\ spaces.ext`; 208 } 209 210 OutBuffer buf; 211 buf.writeEscapedMakePath(input); 212 assert(buf[] == expected); 213 } 214 215 /** 216 * Convert string to integer. 217 * 218 * Params: 219 * T = Type of integer to parse 220 * val = Variable to store the result in 221 * p = slice to start of string digits 222 * max = max allowable value (inclusive), defaults to `T.max` 223 * 224 * Returns: 225 * `false` on error, `true` on success 226 */ 227 bool parseDigits(T)(ref T val, const(char)[] p, const T max = T.max) 228 @safe pure @nogc nothrow 229 { 230 import core.checkedint : mulu, addu, muls, adds; 231 232 // mul* / add* doesn't support types < int 233 static if (T.sizeof < int.sizeof) 234 { 235 int value; 236 alias add = adds; 237 alias mul = muls; 238 } 239 // unsigned 240 else static if (T.min == 0) 241 { 242 T value; 243 alias add = addu; 244 alias mul = mulu; 245 } 246 else 247 { 248 T value; 249 alias add = adds; 250 alias mul = muls; 251 } 252 253 bool overflow; 254 foreach (char c; p) 255 { 256 if (c > '9' || c < '0') 257 return false; 258 value = mul(value, 10, overflow); 259 value = add(value, uint(c - '0'), overflow); 260 } 261 // If it overflows, value must be > to `max` (since `max` is `T`) 262 val = cast(T) value; 263 return !overflow && value <= max; 264 } 265 266 /// 267 @safe pure nothrow @nogc unittest 268 { 269 byte b; 270 ubyte ub; 271 short s; 272 ushort us; 273 int i; 274 uint ui; 275 long l; 276 ulong ul; 277 278 assert(b.parseDigits("42") && b == 42); 279 assert(ub.parseDigits("42") && ub == 42); 280 281 assert(s.parseDigits("420") && s == 420); 282 assert(us.parseDigits("42000") && us == 42_000); 283 284 assert(i.parseDigits("420000") && i == 420_000); 285 assert(ui.parseDigits("420000") && ui == 420_000); 286 287 assert(l.parseDigits("42000000000") && l == 42_000_000_000); 288 assert(ul.parseDigits("82000000000") && ul == 82_000_000_000); 289 290 assert(!b.parseDigits(ubyte.max.stringof)); 291 assert(!b.parseDigits("WYSIWYG")); 292 assert(!b.parseDigits("-42")); 293 assert(!b.parseDigits("200")); 294 assert(ub.parseDigits("200") && ub == 200); 295 assert(i.parseDigits(int.max.stringof) && i == int.max); 296 assert(i.parseDigits("420", 500) && i == 420); 297 assert(!i.parseDigits("420", 400)); 298 }