lib/std/fmt.rad 8.3 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: Copy {
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: Copy {
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: Copy {
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
/// Write a u32 at the end of the buffer and return its start offset.
48
export fn formatU32(val: u32, buffer: &mut [u8]) -> u32 {
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 offset of the first written byte.
67
    return i;
68
}
69
70
/// Write a i32 at the end of the buffer and return its start offset.
71
export fn formatI32(val: i32, buffer: &mut [u8]) -> u32 {
72
    assert buffer.len >= I32_STR_LEN;
73
74
    let neg: bool = val < 0;
75
    let mut x: u32 = -val as u32 if neg else val as u32;
76
    let mut i: u32 = buffer.len;
77
    // Handle the zero case separately to ensure a single '0' is written.
78
    if x == 0 {
79
        set i -= 1;
80
        set buffer[i] = '0';
81
    } else {
82
        // Write digits backwards from the end of the buffer.
83
        while x <> 0 {
84
            set i -= 1;
85
            set buffer[i] = '0' + (x % 10) as u8;
86
            set x /= 10;
87
        }
88
        // Add the negative sign if needed.
89
        if neg {
90
            set i -= 1;
91
            set buffer[i] = '-';
92
        }
93
    }
94
    return i;
95
}
96
97
/// Write a u64 at the end of the buffer and return its start offset.
98
export fn formatU64(val: u64, buffer: &mut [u8]) -> u32 {
99
    assert buffer.len >= U64_STR_LEN;
100
101
    let mut x: u64 = val;
102
    let mut i: u32 = buffer.len;
103
104
    if x == 0 {
105
        set i -= 1;
106
        set buffer[i] = '0';
107
    } else {
108
        while x <> 0 {
109
            set i -= 1;
110
            set buffer[i] = ('0' + (x % 10) as u8);
111
            set x /= 10;
112
        }
113
    }
114
    return i;
115
}
116
117
/// Write a i64 at the end of the buffer and return its start offset.
118
export fn formatI64(val: i64, buffer: &mut [u8]) -> u32 {
119
    assert buffer.len >= I64_STR_LEN;
120
121
    let neg: bool = val < 0;
122
    let mut x: u64 = -val as u64 if neg else val as u64;
123
    let mut i: u32 = buffer.len;
124
    if x == 0 {
125
        set i -= 1;
126
        set buffer[i] = '0';
127
    } else {
128
        while x <> 0 {
129
            set i -= 1;
130
            set buffer[i] = '0' + (x % 10) as u8;
131
            set x /= 10;
132
        }
133
        if neg {
134
            set i -= 1;
135
            set buffer[i] = '-';
136
        }
137
    }
138
    return i;
139
}
140
141
/// Write a i8 at the end of the buffer and return its start offset.
142
export fn formatI8(val: i8, buffer: &mut [u8]) -> u32 {
143
    return formatI32(val as i32, buffer);
144
}
145
146
/// Write a i16 at the end of the buffer and return its start offset.
147
export fn formatI16(val: i16, buffer: &mut [u8]) -> u32 {
148
    return formatI32(val as i32, buffer);
149
}
150
151
/// Write a u8 at the end of the buffer and return its start offset.
152
export fn formatU8(val: u8, buffer: &mut [u8]) -> u32 {
153
    return formatU32(val as u32, buffer);
154
}
155
156
/// Write a u16 at the end of the buffer and return its start offset.
157
export fn formatU16(val: u16, buffer: &mut [u8]) -> u32 {
158
    return formatU32(val as u32, buffer);
159
}
160
161
/// Write a bool at the end of the buffer and return its start offset.
162
export fn formatBool(val: bool, buffer: &mut [u8]) -> u32 {
163
    assert buffer.len >= BOOL_STR_LEN;
164
    if val {
165
        let start = buffer.len - 4;
166
        try! mem::copy(&mut buffer[start..], "true");
167
        return start;
168
    } else {
169
        let start = buffer.len - 5;
170
        try! mem::copy(&mut buffer[start..], "false");
171
        return start;
172
    }
173
}
174
175
/// Convert a single ASCII digit into its numeric value for the given radix.
176
export fn digitFromAscii(ch: u8, radix: u32) -> ?u32 {
177
    assert radix >= 2 and radix <= 36;
178
179
    // Default to an out-of-range value so non-digits fall through to `nil`.
180
    let mut value: u32 = 36;
181
182
    if ch >= '0' and ch <= '9' {
183
        set value = (ch - '0') as u32;
184
    } else if radix > 10 {
185
        // Mask to convert ASCII letters to uppercase.
186
        let upper = ch & 0xDF;
187
        if upper >= 'A' and upper <= 'Z' {
188
            set value = (upper - 'A') as u32 + 10;
189
        }
190
    }
191
    if value < radix {
192
        return value;
193
    }
194
    return nil;
195
}
196
197
/// Decode a single-byte ASCII escape.
198
export fn decodeAsciiEscape(ch: u8) -> u8 {
199
    match ch {
200
        case 'n'  => return '\n',
201
        case 't'  => return '\t',
202
        case 'r'  => return '\r',
203
        case '\\' => return '\\',
204
        case '"'  => return '"',
205
        case '\'' => return '\'',
206
        case '0'  => return 0,
207
        else      => return ch,
208
    }
209
}
210
211
/// Parse an unsigned integer literal (binary, decimal, or hexadecimal).
212
export fn parseInt(text: *[u8]) -> IntLiteral throws (ParseError) {
213
    if text.len == 0 {
214
        throw ParseError::Invalid;
215
    }
216
217
    let mut start: u32 = 0;
218
    let mut radix: u32 = 10;
219
    let mut radixType = Radix::Decimal;
220
    if start + 1 < text.len and text[start] == '0' {
221
        let prefix = text[start + 1];
222
        if prefix == 'x' or prefix == 'X' {
223
            set radix = 16;
224
            set radixType = Radix::Hex;
225
            set start += 2;
226
        } else if prefix == 'b' or prefix == 'B' {
227
            set radix = 2;
228
            set radixType = Radix::Binary;
229
            set start += 2;
230
        }
231
        if start >= text.len {
232
            throw ParseError::Invalid;
233
        }
234
    }
235
    let mut value: u64 = 0;
236
    let radix64: u64 = radix as u64;
237
    for i in start..text.len {
238
        let ch = text[i];
239
        let digit = digitFromAscii(ch, radix) else {
240
            throw ParseError::InvalidDigit;
241
        };
242
        if value > (U64_MAX / radix64) {
243
            throw ParseError::Overflow;
244
        }
245
        set value *= radix64;
246
247
        if value > U64_MAX - (digit as u64) {
248
            throw ParseError::Overflow;
249
        }
250
        set value += (digit as u64);
251
    }
252
    return IntLiteral { text, magnitude: value, radix: radixType };
253
}
254
255
/// Process escape sequences in a raw string, writing the result into `dst`.
256
/// Returns the number of bytes written.
257
export fn unescapeString(raw: *[u8], dst: &mut [u8]) -> u32 {
258
    let mut i: u32 = 0;
259
    let mut j: u32 = 0;
260
261
    while i < raw.len {
262
        if raw[i] == '\\' and i + 1 < raw.len {
263
            set dst[j] = decodeAsciiEscape(raw[i + 1]);
264
            set i += 2;
265
        } else {
266
            set dst[j] = raw[i];
267
            set i += 1;
268
        }
269
        set j += 1;
270
    }
271
    return j;
272
}
273
274
/// Parse a single-byte character literal, including the single quotes.
275
export fn parseChar(text: *[u8]) -> u8 throws (ParseError) {
276
    if text.len < 2 {
277
        throw ParseError::Invalid;
278
    }
279
    let raw = &text[1..text.len - 1];
280
    if raw.len == 0 {
281
        throw ParseError::Invalid;
282
    }
283
    if raw[0] == '\\' {
284
        if raw.len <> 2 {
285
            throw ParseError::Invalid;
286
        }
287
        return decodeAsciiEscape(raw[1]);
288
    }
289
    if raw.len <> 1 {
290
        throw ParseError::Invalid;
291
    }
292
    return raw[0];
293
}