#include <string.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdint.h>
static void write_output(
char out, char* restrict str, size_t max, size_t* written );
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 );
static char upcase(char c);
#define MAXIMUM_NUMBER_LENGTH 21
extern int32_t itoa(int64_t i, char* s, size_t s_len, uint8_t radix);
extern int32_t utoa(uint64_t i, char* s, size_t s_len, uint8_t radix);
extern unsigned long int strtoul(const char* str, char** endptr, int base);
int vsnprintf(
char* restrict str, size_t size, const char* fmt, va_list ap )
{
size_t written = 0;
bool is_escape = false;
int is_long = 0;
bool is_size_t = false;
unsigned long precision = -1;
unsigned long width = 0;
bool zero_pad = false;
while ( *fmt )
{
if ( is_escape )
{
is_escape = false;
switch ( *fmt )
{
case 'z':
if ( is_long || is_size_t ) {
return -1;
}
is_size_t = true;
is_escape = true;
break;
case 'l':
is_long++;
if ( is_long >= 3 ) {
return -1;
}
is_escape = true;
break;
case 'u':
{
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
unsigned long long ll = 0;
if ( is_size_t )
{
ll = va_arg( ap, size_t );
}
else if ( is_long == 2 )
{
ll = va_arg( ap, unsigned long long );
}
else if ( is_long == 1 )
{
ll = va_arg( ap, unsigned long );
}
else
{
ll = va_arg( ap, unsigned int );
}
utoa( ll, s, sizeof(s), 10 );
write_padding( str, size, &written, strlen(s), width, precision, zero_pad, false );
for ( const char* p = s; *p != '\0'; p++ )
{
write_output( *p, str, size, &written );
}
}
break;
case 'x':
case 'X':
{
unsigned long long ll;
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
if ( is_size_t )
{
ll = va_arg( ap, size_t );
}
else if ( is_long == 2 )
{
ll = va_arg( ap, unsigned long long );
}
else if ( is_long == 1 )
{
ll = va_arg( ap, unsigned long );
}
else
{
ll = va_arg( ap, unsigned int );
}
utoa( ll, s, sizeof(s), 16 );
write_padding( str, size, &written, strlen(s), width, precision, zero_pad, false );
for (const char* p = s; *p != '\0'; p++)
{
char output_char = (*fmt == 'X') ? upcase(*p) : *p;
write_output( output_char, str, size, &written );
}
}
break;
case 'i':
case 'd':
{
char s[MAXIMUM_NUMBER_LENGTH] = { 0 };
signed long long ll = 0;
unsigned long long ull = 0;
if ( is_long == 2 )
{
ll = va_arg( ap, signed long long );
}
else if ( is_long == 1 )
{
ll = va_arg( ap, signed long );
}
else
{
ll = va_arg( ap, signed int );
}
bool is_negative = ll < 0;
ull = is_negative ? -ll : ll;
utoa( ull, s, sizeof(s), 10 );
write_padding( str, size, &written, strlen(s), width, precision, zero_pad, is_negative );
for ( const char* p = s; *p != '\0'; p++ )
{
write_output( *p, str, size, &written );
}
}
break;
case 'c':
{
char c = (char) va_arg( ap, int );
write_output( c, str, size, &written );
}
break;
case 's':
{
const char *s = va_arg( ap, const char* );
unsigned long count = precision;
size_t len = strlen(s);
if (precision != (unsigned long)-1 && precision < len) {
len = precision;
}
write_padding( str, size, &written, len, width, precision, false, false );
while (count > 0 && *s != '\0')
{
write_output(*s, str, size, &written);
s++;
if (precision != (unsigned long)-1) {
count--;
}
}
}
break;
case '.':
{
char next = *++fmt;
if (next == '*')
{
precision = va_arg( ap, int );
}
else
{
precision = strtoul(fmt, (char**) &fmt, 10);
fmt--;
}
is_escape = true;
}
break;
case '0':
zero_pad = true;
fmt++;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
width = strtoul(fmt, (char**) &fmt, 10);
fmt--;
is_escape = true;
break;
case '%':
write_output( '%', str, size, &written );
break;
default:
break;
}
fmt++;
if (!is_escape) {
precision = -1;
}
}
else
{
switch ( *fmt )
{
case '%':
is_escape = true;
is_long = 0;
is_size_t = false;
zero_pad = false;
width = 0;
precision = -1;
break;
default:
write_output( *fmt, str, size, &written );
break;
}
fmt++;
}
}
if ( written < size )
{
str[written] = '\0';
}
return (int) written;
}
int snprintf( char* restrict str, size_t size, const char* restrict fmt, ... )
{
va_list ap;
va_start( ap, fmt );
int result = vsnprintf( str, size, fmt, ap );
va_end( ap );
return result;
}
static void write_output(
char out, char* restrict str, size_t size, size_t* written )
{
if ( *written < size )
{
str[*written] = out;
*written = *written + 1;
}
else
{
*written = *written + 1;
}
}
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) {
if ( is_negative )
{
len++;
}
unsigned long pad_len = width > len ? width - len : 0;
unsigned long zero_pad_len = 0;
if ( precision != 0 && precision != (unsigned long)-1 )
{
if ( is_negative )
{
zero_pad_len = precision >= len ? precision - len + 1 : 0;
}
else
{
zero_pad_len = precision >= len ? precision - len : 0;
}
}
else if ( zero_pad && precision == (unsigned long)-1 )
{
zero_pad_len = pad_len;
}
pad_len = (zero_pad_len > pad_len) ? 0 : pad_len - zero_pad_len;
for (unsigned long i = 0; i < pad_len; i++)
{
write_output( ' ', str, size, written );
}
if (is_negative)
{
write_output( '-', str, size, written );
}
for (unsigned long i = 0; i < zero_pad_len; i++)
{
write_output( '0', str, size, written );
}
}
static char upcase(char c) {
if (( c >= 'a' ) && ( c <= 'z' ))
{
return (c - 'a') + 'A';
}
else
{
return c;
}
}