| /* |
| * Copyright (c) 2021-2022 Amlogic, Inc. All rights reserved. |
| * |
| * SPDX-License-Identifier: MIT |
| */ |
| |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include "aml_vsnprintf.h" |
| |
| #include "aml_strnlen.h" |
| #include "aml_isdigit.h" |
| #include <stdarg.h> |
| #include <stddef.h> |
| |
| #if CONFIG_LIBC_AML_FLOAT_PRINT |
| #define HAS_FLOAT 1 |
| #else |
| #define HAS_FLOAT 0 |
| #endif |
| |
| #define ZEROPAD (1 << 0) /* Pad with zero */ |
| #define SIGN (1 << 1) /* Unsigned/signed long */ |
| #define PLUS (1 << 2) /* Show plus */ |
| #define SPACE (1 << 3) /* Spacer */ |
| #define LEFT (1 << 4) /* Left justified */ |
| #define HEX_PREP (1 << 5) /* 0x */ |
| #define UPPERCASE (1 << 6) /* 'ABCDEF' */ |
| |
| static char *digits = "0123456789abcdefghijklmnopqrstuvwxyz"; |
| static char *upper_digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; |
| |
| static 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, int precision, int type) |
| { |
| char c, sign, tmp[66]; |
| char *dig = digits; |
| int i; |
| |
| if (type & UPPERCASE) |
| dig = upper_digits; |
| if (type & LEFT) |
| type &= ~ZEROPAD; |
| if (base < 2 || base > 36) |
| return 0; |
| |
| c = (type & ZEROPAD) ? '0' : ' '; |
| sign = 0; |
| if (type & SIGN) { |
| if (num < 0) { |
| sign = '-'; |
| num = -num; |
| size--; |
| } else if (type & PLUS) { |
| sign = '+'; |
| size--; |
| } else if (type & SPACE) { |
| sign = ' '; |
| size--; |
| } |
| } |
| |
| if (type & HEX_PREP) { |
| if (base == 16) |
| size -= 2; |
| else if (base == 8) |
| size--; |
| } |
| |
| i = 0; |
| |
| if (num == 0) |
| tmp[i++] = '0'; |
| else { |
| while (num != 0) { |
| tmp[i++] = dig[((unsigned long)num) % (unsigned int)base]; |
| num = ((unsigned long)num) / (unsigned int)base; |
| } |
| } |
| |
| if (i > precision) |
| precision = i; |
| size -= precision; |
| if (!(type & (ZEROPAD | LEFT))) |
| while (size-- > 0) |
| *str++ = ' '; |
| if (sign) |
| *str++ = sign; |
| |
| if (type & HEX_PREP) { |
| if (base == 8) |
| *str++ = '0'; |
| else if (base == 16) { |
| *str++ = '0'; |
| *str++ = digits[33]; |
| } |
| } |
| |
| if (!(type & 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, int type) |
| { |
| char tmp[24]; |
| char *dig = digits; |
| int i, len; |
| |
| if (type & 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 (!(type & 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, int type) |
| { |
| 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 (!(type & LEFT)) |
| while (len < size--) |
| *str++ = ' '; |
| for (i = 0; i < len; ++i) |
| *str++ = tmp[i]; |
| while (len < size--) |
| *str++ = ' '; |
| |
| return str; |
| } |
| |
| char *ecvtbuf(double arg, int ndigits, int *decpt, int *sign, char *buf); |
| char *fcvtbuf(double arg, int ndigits, int *decpt, int *sign, char *buf); |
| static void ee_bufcpy(char *d, char *s, int count); |
| |
| void ee_bufcpy(char *pd, char *ps, int count) |
| { |
| char *pe = ps + count; |
| |
| while (ps != pe) |
| *pd++ = *ps++; |
| } |
| |
| static void parse_float(double value, char *buffer, char fmt, int precision) |
| { |
| int decpt, sign, exp, pos; |
| char *digits = NULL; |
| char cvtbuf[80]; |
| int capexp = 0; |
| int magnitude; |
| |
| if (fmt == 'G' || fmt == 'E') { |
| capexp = 1; |
| fmt += 'a' - 'A'; |
| } |
| |
| if (fmt == 'g') { |
| digits = ecvtbuf(value, precision, &decpt, &sign, cvtbuf); |
| magnitude = decpt - 1; |
| if (magnitude < -4 || magnitude > precision - 1) { |
| fmt = 'e'; |
| precision -= 1; |
| } else { |
| fmt = 'f'; |
| precision -= decpt; |
| } |
| } |
| |
| if (fmt == 'e') { |
| digits = ecvtbuf(value, precision + 1, &decpt, &sign, cvtbuf); |
| |
| if (sign) |
| *buffer++ = '-'; |
| *buffer++ = *digits; |
| if (precision > 0) |
| *buffer++ = '.'; |
| ee_bufcpy(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] = (exp % 10) + '0'; |
| exp = exp / 10; |
| buffer[1] = (exp % 10) + '0'; |
| exp = exp / 10; |
| buffer[0] = (exp % 10) + '0'; |
| buffer += 3; |
| } else if (fmt == 'f') { |
| digits = fcvtbuf(value, precision, &decpt, &sign, cvtbuf); |
| 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 decimal_point(char *buffer) |
| { |
| while (*buffer) { |
| if (*buffer == '.') |
| return; |
| if (*buffer == 'e' || *buffer == 'E') |
| break; |
| buffer++; |
| } |
| |
| if (*buffer) { |
| int n = strnlen(buffer, 256); |
| |
| 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) |
| *++buffer = 0; |
| } |
| } |
| |
| static char *flt(char *str, double num, int size, int precision, char fmt, int flags) |
| { |
| char tmp[80]; |
| char c, sign; |
| int n, i; |
| |
| // Left align means no zero padding |
| if (flags & LEFT) |
| flags &= ~ZEROPAD; |
| |
| // Determine padding and sign char |
| c = (flags & ZEROPAD) ? '0' : ' '; |
| sign = 0; |
| if (flags & SIGN) { |
| if (num < 0.0) { |
| sign = '-'; |
| num = -num; |
| size--; |
| } else if (flags & PLUS) { |
| sign = '+'; |
| size--; |
| } else if (flags & SPACE) { |
| sign = ' '; |
| size--; |
| } |
| } |
| |
| // Compute the precision value |
| if (precision < 0) |
| precision = 6; // Default precision: 6 |
| |
| // Convert floating point number to text |
| parse_float(num, tmp, fmt, precision); |
| |
| if ((flags & HEX_PREP) && precision == 0) |
| decimal_point(tmp); |
| if (fmt == 'g' && !(flags & HEX_PREP)) |
| cropzeros(tmp); |
| |
| n = strnlen(tmp, 256); |
| |
| // Output number with alignment and padding |
| size -= n; |
| if (!(flags & (ZEROPAD | LEFT))) |
| while (size-- > 0) |
| *str++ = ' '; |
| if (sign) |
| *str++ = sign; |
| if (!(flags & LEFT)) |
| while (size-- > 0) |
| *str++ = c; |
| for (i = 0; i < n; i++) |
| *str++ = tmp[i]; |
| while (size-- > 0) |
| *str++ = ' '; |
| |
| return str; |
| } |
| |
| int vsnprintf(char *buf, size_t size, const char *fmt, va_list args) |
| { |
| int len; |
| unsigned long num; |
| int i, base; |
| char *str; |
| char *s; |
| |
| int flags; // Flags to number() |
| |
| int field_width; // Width of output field |
| int precision; // Min. # of digits for integers; max number of chars for from string |
| int qualifier; // 'h', 'l', or 'L' for integer fields |
| |
| for (str = buf; *fmt; fmt++) { |
| if (*fmt != '%') { |
| *str++ = *fmt; |
| continue; |
| } |
| |
| // Process flags |
| flags = 0; |
| repeat: |
| fmt++; // This also skips first '%' |
| switch (*fmt) { |
| case '-': |
| flags |= LEFT; |
| goto repeat; |
| case '+': |
| flags |= PLUS; |
| goto repeat; |
| case ' ': |
| flags |= SPACE; |
| goto repeat; |
| case '#': |
| flags |= HEX_PREP; |
| goto repeat; |
| case '0': |
| flags |= ZEROPAD; |
| goto repeat; |
| } |
| |
| // Get field width |
| field_width = -1; |
| if (isdigit(*fmt)) |
| field_width = skip_atoi(&fmt); |
| else if (*fmt == '*') { |
| fmt++; |
| field_width = va_arg(args, int); |
| if (field_width < 0) { |
| field_width = -field_width; |
| flags |= LEFT; |
| } |
| } |
| |
| // Get the precision |
| 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 == 'l' || *fmt == 'L') { |
| qualifier = *fmt; |
| fmt++; |
| } |
| #if CONFIG_LIBC_AML_LONG_LONG_PRINT |
| if (((*(fmt-1) == 'l') && (*fmt == 'l')) |
| || ((*(fmt - 1) == 'L') && (*fmt == 'L'))) { |
| qualifier = 'j'; |
| fmt++; |
| } |
| #endif |
| |
| // Default base |
| base = 10; |
| |
| switch (*fmt) { |
| case 'c': |
| if (!(flags & LEFT)) |
| while (--field_width > 0) |
| *str++ = ' '; |
| *str++ = (unsigned char)va_arg(args, int); |
| while (--field_width > 0) |
| *str++ = ' '; |
| continue; |
| |
| case 's': |
| s = va_arg(args, char *); |
| if (!s) |
| s = "<NULL>"; |
| len = strnlen(s, size - (str-buf) - 1); |
| if (!(flags & LEFT)) |
| while (len < field_width--) |
| *str++ = ' '; |
| for (i = 0; i < len; ++i) |
| *str++ = *s++; |
| while (len < field_width--) |
| *str++ = ' '; |
| continue; |
| |
| case 'p': |
| if (field_width == -1) { |
| field_width = 2 * sizeof(void *); |
| flags |= ZEROPAD; |
| } |
| str = number(str, (unsigned long)va_arg(args, void *), 16, field_width, |
| precision, flags); |
| continue; |
| |
| case 'A': |
| flags |= UPPERCASE; |
| |
| case 'a': |
| if (qualifier == 'l') |
| str = eaddr(str, va_arg(args, unsigned char *), field_width, |
| precision, flags); |
| else |
| str = iaddr(str, 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 |= UPPERCASE; |
| |
| case 'x': |
| base = 16; |
| break; |
| |
| case 'd': |
| case 'i': |
| flags |= SIGN; |
| |
| case 'u': |
| break; |
| |
| #if HAS_FLOAT |
| case 'f': |
| str = flt(str, va_arg(args, double), field_width, precision, *fmt, |
| flags | SIGN); |
| continue; |
| |
| #endif |
| |
| default: |
| if (*fmt != '%') |
| *str++ = '%'; |
| if (*fmt) |
| *str++ = *fmt; |
| else |
| --fmt; |
| continue; |
| } |
| |
| if (qualifier == 'l') |
| num = va_arg(args, unsigned long); |
| #if CONFIG_LIBC_AML_LONG_LONG_PRINT |
| else if (qualifier == 'j') |
| if (flags & SIGN) |
| num = va_arg(args, signed long long); |
| else |
| num = va_arg(args, unsigned long long); |
| #endif |
| else if (flags & SIGN) |
| num = va_arg(args, int); |
| else |
| num = va_arg(args, unsigned int); |
| |
| str = number(str, num, base, field_width, precision, flags); |
| } |
| |
| *str = '\0'; |
| return str - buf; |
| } |