Files
MentOS/mentos/src/klib/vsprintf.c
T
2021-10-04 11:44:02 +02:00

676 lines
16 KiB
C

/// MentOS, The Mentoring Operating system project
/// @file vsprintf.c
/// @brief Print formatting routines.
/// @copyright (c) 2014-2021 This file is distributed under the MIT License.
/// See LICENSE.md for details.
#include "math.h"
#include "ctype.h"
#include "string.h"
#include "stdarg.h"
#include "stdbool.h"
#include "stdint.h"
#include "stdio.h"
#include "video.h"
#include "fcvt.h"
/// Size of the buffer used to call cvt functions.
#define CVTBUFSIZE 500
#define FLAGS_ZEROPAD (1U << 0U) ///< Fill zeros before the number.
#define FLAGS_LEFT (1U << 1U) ///< Left align the value.
#define FLAGS_PLUS (1U << 2U) ///< Print the plus sign.
#define FLAGS_SPACE (1U << 3U) ///< If positive add a space instead of the plus sign.
#define FLAGS_HASH (1U << 4U) ///< Preceed with 0x or 0X, %x or %X respectively.
#define FLAGS_UPPERCASE (1U << 5U) ///< Print uppercase.
#define FLAGS_SIGN (1U << 6U) ///< Print the sign.
/// The list of digits.
static char *_digits = "0123456789abcdefghijklmnopqrstuvwxyz";
/// The list of uppercase digits.
static char *_upper_digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/// @brief Returns the index of the first non-integer character.
static inline int skip_atoi(const char **s)
{
int i = 0;
while (isdigit(**s))
i = i * 10 + *((*s)++) - '0';
return i;
}
static char *number(char *str, long num, int base, int size, int32_t precision, unsigned flags)
{
char c, tmp[66] = { 0 };
char *dig = _digits;
if (flags & FLAGS_UPPERCASE) {
dig = _upper_digits;
}
if (flags & FLAGS_LEFT) {
flags &= ~FLAGS_ZEROPAD;
}
if (base < 2 || base > 36) {
return 0;
}
c = (flags & FLAGS_ZEROPAD) ? '0' : ' ';
// --------------------------------
// Set the sign.
// --------------------------------
char sign = 0;
if (flags & FLAGS_SIGN) {
if (num < 0) {
sign = '-';
num = -num;
size--;
} else if (flags & FLAGS_PLUS) {
sign = '+';
size--;
} else if (flags & FLAGS_SPACE) {
sign = ' ';
size--;
}
}
// Sice I've removed the sign (if negative), i can transform it to unsigned.
uint32_t uns_num = (uint32_t)num;
if (flags & FLAGS_HASH) {
if (base == 16) {
size -= 2;
} else if (base == 8) {
size--;
}
}
int32_t i = 0;
if (uns_num == 0) {
tmp[i++] = '0';
} else {
while (uns_num != 0) {
tmp[i++] = dig[((unsigned long)uns_num) % (unsigned)base];
uns_num = ((unsigned long)uns_num) / (unsigned)base;
}
}
if (i > precision) {
precision = i;
}
size -= precision;
if (!(flags & (FLAGS_ZEROPAD | FLAGS_LEFT))) {
while (size-- > 0)
*str++ = ' ';
}
if (sign) {
*str++ = sign;
}
if (flags & FLAGS_HASH) {
if (base == 8)
*str++ = '0';
else if (base == 16) {
*str++ = '0';
*str++ = _digits[33];
}
}
if (!(flags & FLAGS_LEFT)) {
while (size-- > 0) {
*str++ = c;
}
}
while (i < precision--) {
*str++ = '0';
}
while (i-- > 0) {
*str++ = tmp[i];
}
while (size-- > 0) {
*str++ = ' ';
}
return str;
}
static char *eaddr(char *str, unsigned char *addr, int size, int precision, unsigned flags)
{
(void)precision;
char tmp[24];
char *dig = _digits;
int i, len;
if (flags & FLAGS_UPPERCASE) {
dig = _upper_digits;
}
len = 0;
for (i = 0; i < 6; i++) {
if (i != 0) {
tmp[len++] = ':';
}
tmp[len++] = dig[addr[i] >> 4];
tmp[len++] = dig[addr[i] & 0x0F];
}
if (!(flags & FLAGS_LEFT)) {
while (len < size--) {
*str++ = ' ';
}
}
for (i = 0; i < len; ++i) {
*str++ = tmp[i];
}
while (len < size--) {
*str++ = ' ';
}
return str;
}
static char *iaddr(char *str, unsigned char *addr, int size, int precision, unsigned flags)
{
(void)precision;
char tmp[24];
int i, n, len;
len = 0;
for (i = 0; i < 4; i++) {
if (i != 0) {
tmp[len++] = '.';
}
n = addr[i];
if (n == 0) {
tmp[len++] = _digits[0];
} else {
if (n >= 100) {
tmp[len++] = _digits[n / 100];
n = n % 100;
tmp[len++] = _digits[n / 10];
n = n % 10;
} else if (n >= 10) {
tmp[len++] = _digits[n / 10];
n = n % 10;
}
tmp[len++] = _digits[n];
}
}
if (!(flags & FLAGS_LEFT)) {
while (len < size--) {
*str++ = ' ';
}
}
for (i = 0; i < len; ++i) {
*str++ = tmp[i];
}
while (len < size--) {
*str++ = ' ';
}
return str;
}
static void cfltcvt(double value, char *buffer, char fmt, int precision)
{
int decpt, sign, exp, pos;
char cvtbuf[CVTBUFSIZE];
char *digits = cvtbuf;
int capexp = 0;
int magnitude;
if (fmt == 'G' || fmt == 'E') {
capexp = 1;
fmt += 'a' - 'A';
}
if (fmt == 'g') {
ecvtbuf(value, precision, &decpt, &sign, cvtbuf, CVTBUFSIZE);
magnitude = decpt - 1;
if (magnitude < -4 || magnitude > precision - 1) {
fmt = 'e';
precision -= 1;
} else {
fmt = 'f';
precision -= decpt;
}
}
if (fmt == 'e') {
ecvtbuf(value, precision + 1, &decpt, &sign, cvtbuf, CVTBUFSIZE);
if (sign) {
*buffer++ = '-';
}
*buffer++ = *digits;
if (precision > 0) {
*buffer++ = '.';
}
memcpy(buffer, digits + 1, precision);
buffer += precision;
*buffer++ = capexp ? 'E' : 'e';
if (decpt == 0) {
if (value == 0.0) {
exp = 0;
} else {
exp = -1;
}
} else {
exp = decpt - 1;
}
if (exp < 0) {
*buffer++ = '-';
exp = -exp;
} else {
*buffer++ = '+';
}
buffer[2] = (char)((exp % 10) + '0');
exp = exp / 10;
buffer[1] = (char)((exp % 10) + '0');
exp = exp / 10;
buffer[0] = (char)((exp % 10) + '0');
buffer += 3;
} else if (fmt == 'f') {
fcvtbuf(value, precision, &decpt, &sign, cvtbuf, CVTBUFSIZE);
if (sign) {
*buffer++ = '-';
}
if (*digits) {
if (decpt <= 0) {
*buffer++ = '0';
*buffer++ = '.';
for (pos = 0; pos < -decpt; pos++) {
*buffer++ = '0';
}
while (*digits) {
*buffer++ = *digits++;
}
} else {
pos = 0;
while (*digits) {
if (pos++ == decpt) {
*buffer++ = '.';
}
*buffer++ = *digits++;
}
}
} else {
*buffer++ = '0';
if (precision > 0) {
*buffer++ = '.';
for (pos = 0; pos < precision; pos++) {
*buffer++ = '0';
}
}
}
}
*buffer = '\0';
}
static void forcdecpt(char *buffer)
{
while (*buffer) {
if (*buffer == '.') {
return;
}
if (*buffer == 'e' || *buffer == 'E') {
break;
}
buffer++;
}
if (*buffer) {
long n = (long)strlen(buffer);
while (n > 0) {
buffer[n + 1] = buffer[n];
n--;
}
*buffer = '.';
} else {
*buffer++ = '.';
*buffer = '\0';
}
}
static void cropzeros(char *buffer)
{
char *stop;
while (*buffer && *buffer != '.') {
buffer++;
}
if (*buffer++) {
while (*buffer && *buffer != 'e' && *buffer != 'E') {
buffer++;
}
stop = buffer--;
while (*buffer == '0') {
buffer--;
}
if (*buffer == '.') {
buffer--;
}
while ((*++buffer = *stop++)) {}
}
}
static char *flt(char *str, double num, int size, int precision, char fmt, unsigned flags)
{
char tmp[80];
char c, sign;
int n, i;
// Left align means no zero padding.
if (flags & FLAGS_LEFT)
flags &= ~FLAGS_ZEROPAD;
// Determine padding and sign char.
c = (flags & FLAGS_ZEROPAD) ? '0' : ' ';
sign = 0;
if (flags & FLAGS_SIGN) {
if (num < 0.0) {
sign = '-';
num = -num;
size--;
} else if (flags & FLAGS_PLUS) {
sign = '+';
size--;
} else if (flags & FLAGS_SPACE) {
sign = ' ';
size--;
}
}
// Compute the precision value.
if (precision < 0) {
// Default precision: 6.
precision = 6;
} else if (precision == 0 && fmt == 'g') {
// ANSI specified.
precision = 1;
}
// Convert floating point number to text.
cfltcvt(num, tmp, fmt, precision);
// '#' and precision == 0 means force a decimal point.
if ((flags & FLAGS_HASH) && precision == 0) {
forcdecpt(tmp);
}
// 'g' format means crop zero unless '#' given.
if (fmt == 'g' && !(flags & FLAGS_HASH)) {
cropzeros(tmp);
}
n = strlen(tmp);
// Output number with alignment and padding.
size -= n;
if (!(flags & (FLAGS_ZEROPAD | FLAGS_LEFT))) {
while (size-- > 0) {
*str++ = ' ';
}
}
if (sign) {
*str++ = sign;
}
if (!(flags & FLAGS_LEFT)) {
while (size-- > 0) {
*str++ = c;
}
}
for (i = 0; i < n; i++) {
*str++ = tmp[i];
}
while (size-- > 0) {
*str++ = ' ';
}
return str;
}
int vsprintf(char *str, const char *fmt, va_list args)
{
int base;
char *tmp;
char *s;
// Flags to number().
unsigned flags;
// 'h', 'l', or 'L' for integer fields.
char qualifier;
for (tmp = str; *fmt; fmt++) {
if (*fmt != '%') {
*tmp++ = *fmt;
continue;
}
// Process flags-
flags = 0;
repeat:
// This also skips first '%'.
fmt++;
switch (*fmt) {
case '-':
flags |= FLAGS_LEFT;
goto repeat;
case '+':
flags |= FLAGS_PLUS;
goto repeat;
case ' ':
flags |= FLAGS_SPACE;
goto repeat;
case '#':
flags |= FLAGS_HASH;
goto repeat;
case '0':
flags |= FLAGS_ZEROPAD;
goto repeat;
}
// Get the width of the output field.
int32_t field_width;
field_width = -1;
if (isdigit(*fmt)) {
field_width = skip_atoi(&fmt);
} else if (*fmt == '*') {
fmt++;
field_width = va_arg(args, int32_t);
if (field_width < 0) {
field_width = -field_width;
flags |= FLAGS_LEFT;
}
}
/* Get the precision, thus the minimum number of digits for
* integers; max number of chars for from string.
*/
int32_t precision = -1;
if (*fmt == '.') {
++fmt;
if (isdigit(*fmt)) {
precision = skip_atoi(&fmt);
} else if (*fmt == '*') {
++fmt;
precision = va_arg(args, int);
}
if (precision < 0) {
precision = 0;
}
}
// Get the conversion qualifier.
qualifier = -1;
if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {
qualifier = *fmt;
fmt++;
}
// Default base.
base = 10;
switch (*fmt) {
case 'c':
if (!(flags & FLAGS_LEFT)) {
while (--field_width > 0) {
*tmp++ = ' ';
}
}
*tmp++ = va_arg(args, char);
while (--field_width > 0) {
*tmp++ = ' ';
}
continue;
case 's':
s = va_arg(args, char *);
if (!s) {
s = "<NULL>";
}
int32_t len = (int32_t)strnlen(s, (uint32_t)precision);
if (!(flags & FLAGS_LEFT)) {
while (len < field_width--) {
*tmp++ = ' ';
}
}
int32_t it;
for (it = 0; it < len; ++it) {
*tmp++ = *s++;
}
while (len < field_width--) {
*tmp++ = ' ';
}
continue;
case 'p':
if (field_width == -1) {
field_width = 2 * sizeof(void *);
flags |= FLAGS_ZEROPAD;
}
tmp = number(tmp, (unsigned long)va_arg(args, void *), 16, field_width, precision, flags);
continue;
case 'n':
if (qualifier == 'l') {
long *ip = va_arg(args, long *);
*ip = (tmp - str);
} else {
int *ip = va_arg(args, int *);
*ip = (tmp - str);
}
continue;
case 'A':
flags |= FLAGS_UPPERCASE;
break;
case 'a':
if (qualifier == 'l')
tmp = eaddr(tmp, va_arg(args, unsigned char *), field_width, precision, flags);
else
tmp = iaddr(tmp, va_arg(args, unsigned char *), field_width, precision, flags);
continue;
// Integer number formats - set up the flags and "break".
case 'o':
base = 8;
break;
case 'X':
flags |= FLAGS_UPPERCASE;
break;
case 'x':
base = 16;
break;
case 'd':
case 'i':
flags |= FLAGS_SIGN;
case 'u':
break;
case 'E':
case 'G':
case 'e':
case 'f':
case 'g':
tmp = flt(tmp, va_arg(args, double), field_width, precision, *fmt, flags | FLAGS_SIGN);
continue;
default:
if (*fmt != '%')
*tmp++ = '%';
if (*fmt)
*tmp++ = *fmt;
else
--fmt;
continue;
}
if (flags & FLAGS_SIGN) {
long num;
if (qualifier == 'l') {
num = va_arg(args, long);
} else if (qualifier == 'h') {
num = va_arg(args, short);
} else {
num = va_arg(args, int);
}
tmp = number(tmp, num, base, field_width, precision, flags);
} else {
unsigned long num;
if (qualifier == 'l') {
num = va_arg(args, unsigned long);
} else if (qualifier == 'h') {
num = va_arg(args, unsigned short);
} else {
num = va_arg(args, unsigned int);
}
tmp = number(tmp, num, base, field_width, precision, flags);
}
}
*tmp = '\0';
return tmp - str;
}
int printf(const char *format, ...)
{
char buffer[4096];
va_list ap;
int len;
// Start variabile argument's list.
va_start(ap, format);
len = vsprintf(buffer, format, ap);
va_end(ap);
video_puts(buffer);
return len;
}
int sprintf(char *str, const char *fmt, ...)
{
va_list args;
int len;
va_start(args, fmt);
len = vsprintf(str, fmt, args);
va_end(args);
return len;
}