lib/std/fmt.rad 8.1 KiB raw
1
//! Formatting utilities for converting values to strings.
2
use super::mem;
3
4
/// Maximum `u64` value.
5
export constant U64_MAX: u64 = 0xFFFFFFFFFFFFFFFF;
6
/// Maximum string length for a formatted u32 (eg. "4294967295").
7
export constant U32_STR_LEN: u32 = 10;
8
/// Maximum string length for a formatted i32 (eg. "-2147483648").
9
export constant I32_STR_LEN: u32 = U32_STR_LEN + 1;
10
/// Maximum string length for a formatted u64 (eg. "18446744073709551615").
11
export constant U64_STR_LEN: u32 = 20;
12
/// Maximum string length for a formatted i64 (eg. "-9223372036854775808").
13
export constant I64_STR_LEN: u32 = 20;
14
/// Maximum string length for a formatted bool (eg. "false").
15
export constant BOOL_STR_LEN: u32 = 5;
16
17
/// Radix/base of a parsed integer literal.
18
export union Radix {
19
    /// Binary literal (0b...).
20
    Binary,
21
    /// Decimal literal.
22
    Decimal,
23
    /// Hexadecimal literal (0x...).
24
    Hex,
25
}
26
27
/// Errors reported while parsing literal text.
28
export union ParseError {
29
    /// Literal text was empty or missing required digits.
30
    Invalid,
31
    /// Literal contained an invalid digit for its radix.
32
    InvalidDigit,
33
    /// Literal value exceeded the supported range.
34
    Overflow,
35
}
36
37
/// Parsed integer literal metadata.
38
export record IntLiteral {
39
    /// Raw characters that comprised the literal.
40
    text: *[u8],
41
    /// Magnitude parsed from the literal.
42
    magnitude: u64,
43
    /// Radix used by the literal.
44
    radix: Radix,
45
}
46
47
/// Format a u32 by writing it to the provided buffer.
48
export fn formatU32(val: u32, buffer: *mut [u8]) -> *[u8] {
49
    assert buffer.len >= U32_STR_LEN;
50
51
    let mut x: u32 = val;
52
    let mut i: u32 = buffer.len;
53
54
    // Handle the zero case separately to ensure a single '0' is written.
55
    if x == 0 {
56
        set i -= 1;
57
        set buffer[i] = '0';
58
    } else {
59
        // Write digits backwards from the end of the buffer.
60
        while x <> 0 {
61
            set i -= 1;
62
            set buffer[i] = ('0' + (x % 10) as u8);
63
            set x /= 10;
64
        }
65
    }
66
    // Return the slice from the start of the written number to
67
    // the end of the buffer.
68
    return &buffer[i..];
69
}
70
71
/// Format a i32 by writing it to the provided buffer.
72
export fn formatI32(val: i32, buffer: *mut [u8]) -> *[u8] {
73
    assert buffer.len >= I32_STR_LEN;
74
75
    let neg: bool = val < 0;
76
    let mut x: u32 = -val as u32 if neg else val as u32;
77
    let mut i: u32 = buffer.len;
78
    // Handle the zero case separately to ensure a single '0' is written.
79
    if x == 0 {
80
        set i -= 1;
81
        set buffer[i] = '0';
82
    } else {
83
        // Write digits backwards from the end of the buffer.
84
        while x <> 0 {
85
            set i -= 1;
86
            set buffer[i] = '0' + (x % 10) as u8;
87
            set x /= 10;
88
        }
89
        // Add the negative sign if needed.
90
        if neg {
91
            set i -= 1;
92
            set buffer[i] = '-';
93
        }
94
    }
95
    return &buffer[i..];
96
}
97
98
/// Format a u64 by writing it to the provided buffer.
99
export fn formatU64(val: u64, buffer: *mut [u8]) -> *[u8] {
100
    assert buffer.len >= U64_STR_LEN;
101
102
    let mut x: u64 = val;
103
    let mut i: u32 = buffer.len;
104
105
    if x == 0 {
106
        set i -= 1;
107
        set buffer[i] = '0';
108
    } else {
109
        while x <> 0 {
110
            set i -= 1;
111
            set buffer[i] = ('0' + (x % 10) as u8);
112
            set x /= 10;
113
        }
114
    }
115
    return &buffer[i..];
116
}
117
118
/// Format a i64 by writing it to the provided buffer.
119
export fn formatI64(val: i64, buffer: *mut [u8]) -> *[u8] {
120
    assert buffer.len >= I64_STR_LEN;
121
122
    let neg: bool = val < 0;
123
    let mut x: u64 = -val as u64 if neg else val as u64;
124
    let mut i: u32 = buffer.len;
125
    if x == 0 {
126
        set i -= 1;
127
        set buffer[i] = '0';
128
    } else {
129
        while x <> 0 {
130
            set i -= 1;
131
            set buffer[i] = '0' + (x % 10) as u8;
132
            set x /= 10;
133
        }
134
        if neg {
135
            set i -= 1;
136
            set buffer[i] = '-';
137
        }
138
    }
139
    return &buffer[i..];
140
}
141
142
/// Format a i8 by writing it to the provided buffer.
143
export fn formatI8(val: i8, buffer: *mut [u8]) -> *[u8] {
144
    return formatI32(val as i32, buffer);
145
}
146
147
/// Format a i16 by writing it to the provided buffer.
148
export fn formatI16(val: i16, buffer: *mut [u8]) -> *[u8] {
149
    return formatI32(val as i32, buffer);
150
}
151
152
/// Format a u8 by writing it to the provided buffer.
153
export fn formatU8(val: u8, buffer: *mut [u8]) -> *[u8] {
154
    return formatU32(val as u32, buffer);
155
}
156
157
/// Format a u16 by writing it to the provided buffer.
158
export fn formatU16(val: u16, buffer: *mut [u8]) -> *[u8] {
159
    return formatU32(val as u32, buffer);
160
}
161
162
/// Format a bool by writing it to the provided buffer.
163
export fn formatBool(val: bool, buffer: *mut [u8]) -> *[u8] {
164
    if val {
165
        try! mem::copy(buffer, "true");
166
        return &buffer[..4];
167
    } else {
168
        try! mem::copy(buffer, "false");
169
        return &buffer[..5];
170
    }
171
}
172
173
/// Convert a single ASCII digit into its numeric value for the given radix.
174
export fn digitFromAscii(ch: u8, radix: u32) -> ?u32 {
175
    assert radix >= 2 and radix <= 36;
176
177
    // Default to an out-of-range value so non-digits fall through to `nil`.
178
    let mut value: u32 = 36;
179
180
    if ch >= '0' and ch <= '9' {
181
        set value = (ch - '0') as u32;
182
    } else if radix > 10 {
183
        // Mask to convert ASCII letters to uppercase.
184
        let upper = ch & 0xDF;
185
        if upper >= 'A' and upper <= 'Z' {
186
            set value = (upper - 'A') as u32 + 10;
187
        }
188
    }
189
    if value < radix {
190
        return value;
191
    }
192
    return nil;
193
}
194
195
/// Decode a single-byte ASCII escape.
196
export fn decodeAsciiEscape(ch: u8) -> u8 {
197
    match ch {
198
        case 'n'  => return '\n',
199
        case 't'  => return '\t',
200
        case 'r'  => return '\r',
201
        case '\\' => return '\\',
202
        case '"'  => return '"',
203
        case '\'' => return '\'',
204
        case '0'  => return 0,
205
        else      => return ch,
206
    }
207
}
208
209
/// Parse an unsigned integer literal (binary, decimal, or hexadecimal).
210
export fn parseInt(text: *[u8]) -> IntLiteral throws (ParseError) {
211
    if text.len == 0 {
212
        throw ParseError::Invalid;
213
    }
214
215
    let mut start: u32 = 0;
216
    let mut radix: u32 = 10;
217
    let mut radixType = Radix::Decimal;
218
    if start + 1 < text.len and text[start] == '0' {
219
        let prefix = text[start + 1];
220
        if prefix == 'x' or prefix == 'X' {
221
            set radix = 16;
222
            set radixType = Radix::Hex;
223
            set start += 2;
224
        } else if prefix == 'b' or prefix == 'B' {
225
            set radix = 2;
226
            set radixType = Radix::Binary;
227
            set start += 2;
228
        }
229
        if start >= text.len {
230
            throw ParseError::Invalid;
231
        }
232
    }
233
    let mut value: u64 = 0;
234
    let radix64: u64 = radix as u64;
235
    for i in start..text.len {
236
        let ch = text[i];
237
        let digit = digitFromAscii(ch, radix) else {
238
            throw ParseError::InvalidDigit;
239
        };
240
        if value > (U64_MAX / radix64) {
241
            throw ParseError::Overflow;
242
        }
243
        set value *= radix64;
244
245
        if value > U64_MAX - (digit as u64) {
246
            throw ParseError::Overflow;
247
        }
248
        set value += (digit as u64);
249
    }
250
    return IntLiteral { text, magnitude: value, radix: radixType };
251
}
252
253
/// Process escape sequences in a raw string, writing the result into `dst`.
254
/// Returns the number of bytes written.
255
export fn unescapeString(raw: *[u8], dst: *mut [u8]) -> u32 {
256
    let mut i: u32 = 0;
257
    let mut j: u32 = 0;
258
259
    while i < raw.len {
260
        if raw[i] == '\\' and i + 1 < raw.len {
261
            set dst[j] = decodeAsciiEscape(raw[i + 1]);
262
            set i += 2;
263
        } else {
264
            set dst[j] = raw[i];
265
            set i += 1;
266
        }
267
        set j += 1;
268
    }
269
    return j;
270
}
271
272
/// Parse a single-byte character literal, including the single quotes.
273
export fn parseChar(text: *[u8]) -> u8 throws (ParseError) {
274
    if text.len < 2 {
275
        throw ParseError::Invalid;
276
    }
277
    let raw = &text[1..text.len - 1];
278
    if raw.len == 0 {
279
        throw ParseError::Invalid;
280
    }
281
    if raw[0] == '\\' {
282
        if raw.len <> 2 {
283
            throw ParseError::Invalid;
284
        }
285
        return decodeAsciiEscape(raw[1]);
286
    }
287
    if raw.len <> 1 {
288
        throw ParseError::Invalid;
289
    }
290
    return raw[0];
291
}