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 }