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 }