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