1/**
2 * This module defines some utility functions for DMD.
3 *
4 * Copyright:   Copyright (C) 1999-2022 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
12module dmd.utils;
13
14import core.stdc.string;
15import dmd.errors;
16import dmd.globals;
17import dmd.root.file;
18import dmd.root.filename;
19import dmd.common.outbuffer;
20import dmd.root.string;
21
22nothrow:
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 */
33const(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 */
56Buffer readFile(Loc loc, const(char)* filename)
57{
58    return readFile(loc, filename.toDString());
59}
60
61/// Ditto
62Buffer 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 */
82extern (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 */
101void 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 */
123void 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 */
156void 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///
198unittest
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 */
228bool 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}
300