Skip to content

Commit 4f6da13

Browse files
authored
Merge pull request #30 from Sympatron/master
Add support for padding in snprintf
2 parents 150a689 + ceba619 commit 4f6da13

File tree

3 files changed

+379
-231
lines changed

3 files changed

+379
-231
lines changed

.github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ jobs:
103103
- uses: actions-rs/toolchain@v1
104104
with:
105105
profile: minimal
106-
toolchain: 1.75.0 # clippy is too much of a moving target at the moment
106+
toolchain: 1.77.0 # clippy is too much of a moving target at the moment
107107
override: true
108108
components: clippy
109109
- uses: actions-rs/clippy-check@v1

src/snprintf.c

+139-132
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
static void write_output(
3030
char out, char* restrict str, size_t max, size_t* written );
3131

32+
static void write_padding(
33+
char* restrict str, size_t size, size_t* written, size_t len, unsigned long width, unsigned long precision, bool zero_pad, bool is_negative );
34+
3235
static char upcase(char c);
3336

3437
/* ======================================================================== *
@@ -84,7 +87,7 @@ extern unsigned long int strtoul(const char* str, char** endptr, int base);
8487
* - c (char)
8588
* - s (null-terminated string)
8689
* - % (literal percent sign)
87-
* - qualifiers: l, ll, z
90+
* - qualifiers: l, ll, z, width, (non-string) precision, left-space-pad, zero-pad
8891
*
8992
* Does not support:
9093
*
@@ -96,7 +99,7 @@ extern unsigned long int strtoul(const char* str, char** endptr, int base);
9699
* - f (decimal floating point)
97100
* - g (the shorter of %e and %f)
98101
* - G (the shorter of %E and %f)
99-
* - qualifiers: L, width, (non-string) precision, -, +, space-pad, zero-pad, etc
102+
* - qualifiers: L, -, +, right-pad, etc
100103
*
101104
* @param str the output buffer to write to
102105
* @param size the size of the output buffer
@@ -113,6 +116,8 @@ int vsnprintf(
113116
int is_long = 0;
114117
bool is_size_t = false;
115118
unsigned long precision = -1;
119+
unsigned long width = 0;
120+
bool zero_pad = false;
116121

117122
while ( *fmt )
118123
{
@@ -138,173 +143,93 @@ int vsnprintf(
138143
is_escape = true;
139144
break;
140145
case 'u':
141-
if ( is_size_t )
142146
{
143-
// Render %zu
144147
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
145-
size_t su = va_arg( ap, size_t );
146-
utoa( su, s, sizeof(s), 10 );
147-
for ( const char* p = s; *p != '\0'; p++ )
148+
unsigned long long ll = 0;
149+
if ( is_size_t )
148150
{
149-
write_output( *p, str, size, &written );
151+
// Render %zu
152+
ll = va_arg( ap, size_t );
150153
}
151-
}
152-
else if ( is_long == 2 )
153-
{
154-
// Render %lu
155-
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
156-
unsigned long long ll = va_arg( ap, unsigned long long );
157-
utoa( ll, s, sizeof(s), 10 );
158-
for ( const char* p = s; *p != '\0'; p++ )
154+
else if ( is_long == 2 )
159155
{
160-
write_output( *p, str, size, &written );
156+
// Render %llu
157+
ll = va_arg( ap, unsigned long long );
161158
}
162-
}
163-
else if ( is_long == 1 )
164-
{
165-
// Render %lu
166-
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
167-
unsigned long l = va_arg( ap, unsigned long );
168-
utoa( l, s, sizeof(s), 10 );
169-
for ( const char* p = s; *p != '\0'; p++ )
159+
else if ( is_long == 1 )
170160
{
171-
write_output( *p, str, size, &written );
161+
// Render %lu
162+
ll = va_arg( ap, unsigned long );
172163
}
173-
}
174-
else
175-
{
176-
// Render %u
177-
unsigned int i = va_arg( ap, unsigned int );
178-
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
179-
utoa( i, s, sizeof(s), 10 );
180-
for ( const char* p = s; *p != '\0'; p++ )
164+
else
181165
{
182-
write_output( *p, str, size, &written );
166+
// Render %u
167+
ll = va_arg( ap, unsigned int );
183168
}
184-
}
185-
break;
186-
case 'x':
187-
if ( is_size_t )
188-
{
189-
// Render %zu
190-
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
191-
size_t su = va_arg( ap, size_t );
192-
utoa( su, s, sizeof(s), 16 );
169+
utoa( ll, s, sizeof(s), 10 );
170+
write_padding( str, size, &written, strlen(s), width, precision, zero_pad, false );
193171
for ( const char* p = s; *p != '\0'; p++ )
194172
{
195173
write_output( *p, str, size, &written );
196174
}
197175
}
198-
else if ( is_long == 2 )
176+
break;
177+
case 'x':
178+
case 'X':
179+
// Render %x and %X
199180
{
200-
// Render %llu
181+
unsigned long long ll;
201182
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
202-
unsigned long long ll = va_arg( ap, unsigned long long );
203-
utoa( ll, s, sizeof(s), 16 );
204-
for ( const char* p = s; *p != '\0'; p++ )
183+
if ( is_size_t )
205184
{
206-
write_output( *p, str, size, &written );
185+
ll = va_arg( ap, size_t );
207186
}
208-
}
209-
else if ( is_long == 1 )
210-
{
211-
// Render %lu
212-
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
213-
unsigned long l = va_arg( ap, unsigned long );
214-
utoa( l, s, sizeof(s), 16 );
215-
for ( const char* p = s; *p != '\0'; p++ )
187+
else if ( is_long == 2 )
216188
{
217-
write_output( *p, str, size, &written );
189+
ll = va_arg( ap, unsigned long long );
218190
}
219-
}
220-
else
221-
{
222-
// Render %u
223-
unsigned int i = va_arg( ap, unsigned int );
224-
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
225-
utoa( i, s, sizeof(s), 16 );
226-
for ( const char* p = s; *p != '\0'; p++ )
191+
else if ( is_long == 1 )
227192
{
228-
write_output( *p, str, size, &written );
193+
ll = va_arg( ap, unsigned long );
229194
}
230-
}
231-
break;
232-
case 'X':
233-
if ( is_size_t )
234-
{
235-
// Render %zu
236-
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
237-
size_t su = va_arg( ap, size_t );
238-
utoa( su, s, sizeof(s), 16 );
239-
for ( const char* p = s; *p != '\0'; p++ )
195+
else
240196
{
241-
write_output( upcase(*p), str, size, &written );
197+
ll = va_arg( ap, unsigned int );
242198
}
243-
}
244-
else if ( is_long == 2 )
245-
{
246-
// Render %llu
247-
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
248-
unsigned long long ll = va_arg( ap, unsigned long long );
249199
utoa( ll, s, sizeof(s), 16 );
250-
for ( const char* p = s; *p != '\0'; p++ )
200+
write_padding( str, size, &written, strlen(s), width, precision, zero_pad, false );
201+
for (const char* p = s; *p != '\0'; p++)
251202
{
252-
write_output( upcase(*p), str, size, &written );
253-
}
254-
}
255-
else if ( is_long == 1 )
256-
{
257-
// Render %lu
258-
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
259-
unsigned long l = va_arg( ap, unsigned long );
260-
utoa( l, s, sizeof(s), 16 );
261-
for ( const char* p = s; *p != '\0'; p++ )
262-
{
263-
write_output( upcase(*p), str, size, &written );
264-
}
265-
}
266-
else
267-
{
268-
// Render %u
269-
unsigned int i = va_arg( ap, unsigned int );
270-
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
271-
utoa( i, s, sizeof(s), 16 );
272-
for ( const char* p = s; *p != '\0'; p++ )
273-
{
274-
write_output( upcase(*p), str, size, &written );
203+
char output_char = (*fmt == 'X') ? upcase(*p) : *p;
204+
write_output( output_char, str, size, &written );
275205
}
276206
}
277207
break;
278208
case 'i':
279209
case 'd':
280-
if ( is_long == 2 )
281210
{
282-
// Render %ld
283211
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
284-
signed long long ll = va_arg( ap, signed long long );
285-
itoa( ll, s, sizeof(s), 10 );
286-
for ( const char* p = s; *p != '\0'; p++ )
212+
signed long long ll = 0;
213+
unsigned long long ull = 0;
214+
if ( is_long == 2 )
287215
{
288-
write_output( *p, str, size, &written );
216+
// Render %lld
217+
ll = va_arg( ap, signed long long );
289218
}
290-
}
291-
else if ( is_long == 1 )
292-
{
293-
// Render %ld
294-
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
295-
signed long l = va_arg( ap, signed long );
296-
itoa( l, s, sizeof(s), 10 );
297-
for ( const char* p = s; *p != '\0'; p++ )
219+
else if ( is_long == 1 )
298220
{
299-
write_output( *p, str, size, &written );
221+
// Render %ld
222+
ll = va_arg( ap, signed long );
300223
}
301-
}
302-
else
303-
{
304-
// Render %d
305-
signed int i = va_arg( ap, signed int );
306-
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
307-
itoa( i, s, sizeof(s), 10 );
224+
else
225+
{
226+
// Render %d
227+
ll = va_arg( ap, signed int );
228+
}
229+
bool is_negative = ll < 0;
230+
ull = is_negative ? -ll : ll;
231+
utoa( ull, s, sizeof(s), 10 );
232+
write_padding( str, size, &written, strlen(s), width, precision, zero_pad, is_negative );
308233
for ( const char* p = s; *p != '\0'; p++ )
309234
{
310235
write_output( *p, str, size, &written );
@@ -323,6 +248,12 @@ int vsnprintf(
323248
{
324249
const char *s = va_arg( ap, const char* );
325250
unsigned long count = precision;
251+
252+
size_t len = strlen(s);
253+
if (precision != (unsigned long)-1 && precision < len) {
254+
len = precision;
255+
}
256+
write_padding( str, size, &written, len, width, precision, false, false );
326257

327258
while (count > 0 && *s != '\0')
328259
{
@@ -336,7 +267,7 @@ int vsnprintf(
336267
}
337268
break;
338269
case '.':
339-
// Render a precision specifier
270+
// Parse a precision specifier
340271
{
341272
// Next up is either a number or a '*' that signifies that the number is in the arguments list
342273
char next = *++fmt;
@@ -356,6 +287,27 @@ int vsnprintf(
356287
is_escape = true;
357288
}
358289
break;
290+
case '0':
291+
// Parse zero padding specifier
292+
zero_pad = true;
293+
fmt++;
294+
/* fall through */
295+
case '1':
296+
case '2':
297+
case '3':
298+
case '4':
299+
case '5':
300+
case '6':
301+
case '7':
302+
case '8':
303+
case '9':
304+
// Parse padding specifier
305+
width = strtoul(fmt, (char**) &fmt, 10);
306+
// Strtoul sets the fmt pointer to the char after the number,
307+
// however the code expects the char before that.
308+
fmt--;
309+
is_escape = true;
310+
break;
359311
case '%':
360312
write_output( '%', str, size, &written );
361313
break;
@@ -378,6 +330,9 @@ int vsnprintf(
378330
is_escape = true;
379331
is_long = 0;
380332
is_size_t = false;
333+
zero_pad = false;
334+
width = 0;
335+
precision = -1;
381336
break;
382337
default:
383338
write_output( *fmt, str, size, &written );
@@ -451,6 +406,58 @@ static void write_output(
451406
}
452407
}
453408

409+
/**
410+
* write_padding - Write padding to the output buffer.
411+
*
412+
* @param str the buffer to write to
413+
* @param size the total size of `str`
414+
* @param written pass in the number of characters in the buffer; is increased
415+
* by one regardless of whether we wrote to the buffer
416+
* @param len the length of the string to write
417+
* @param width the total width of the padding
418+
* @param precision the precision of the padding
419+
* @param zero_pad whether to zero pad the string
420+
* @param is_negative whether the number is negative
421+
*/
422+
static void write_padding(char* restrict str, size_t size, size_t* written, size_t len, unsigned long width, unsigned long precision, bool zero_pad, bool is_negative) {
423+
if ( is_negative )
424+
{
425+
len++;
426+
}
427+
unsigned long pad_len = width > len ? width - len : 0;
428+
unsigned long zero_pad_len = 0;
429+
if ( precision != 0 && precision != (unsigned long)-1 )
430+
{
431+
if ( is_negative )
432+
{
433+
zero_pad_len = precision >= len ? precision - len + 1 : 0;
434+
}
435+
else
436+
{
437+
zero_pad_len = precision >= len ? precision - len : 0;
438+
}
439+
}
440+
else if ( zero_pad && precision == (unsigned long)-1 )
441+
{
442+
zero_pad_len = pad_len;
443+
}
444+
// Apply whitespace padding if needed
445+
pad_len = (zero_pad_len > pad_len) ? 0 : pad_len - zero_pad_len;
446+
for (unsigned long i = 0; i < pad_len; i++)
447+
{
448+
write_output( ' ', str, size, written );
449+
}
450+
// Apply zero padding if needed
451+
if (is_negative)
452+
{
453+
write_output( '-', str, size, written );
454+
}
455+
for (unsigned long i = 0; i < zero_pad_len; i++)
456+
{
457+
write_output( '0', str, size, written );
458+
}
459+
}
460+
454461
/**
455462
* Converts 'a'..'z' to 'A'..'Z', leaving all other characters unchanged.
456463
*/

0 commit comments

Comments
 (0)