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