2929static 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+
3235static 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