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 }