330 lines
11 KiB
C
330 lines
11 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
│vi: set net ft=c ts=8 sts=2 sw=2 fenc=utf-8 :vi│
|
|
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
│ @author (c) Marco Paland (info@paland.com) │
|
|
│ 2014-2019, PALANDesign Hannover, Germany │
|
|
│ │
|
|
│ @license The MIT License (MIT) │
|
|
│ │
|
|
│ Permission is hereby granted, free of charge, to any person obtaining a copy │
|
|
│ of this software and associated documentation files (the "Software"), to deal│
|
|
│ in the Software without restriction, including without limitation the rights │
|
|
│ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell │
|
|
│ copies of the Software, and to permit persons to whom the Software is │
|
|
│ furnished to do so, subject to the following conditions: │
|
|
│ │
|
|
│ The above copyright notice and this permission notice shall be included in │
|
|
│ all copies or substantial portions of the Software. │
|
|
│ │
|
|
│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR │
|
|
│ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, │
|
|
│ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE │
|
|
│ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER │
|
|
│ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,│
|
|
│ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN │
|
|
│ THE SOFTWARE. │
|
|
│ │
|
|
│ @brief Tiny printf, sprintf and (v)snprintf implementation, optimized for │
|
|
│ embedded systems with a very limited resources. These routines are │
|
|
│ thread safe and reentrant! Use this instead of the bloated │
|
|
│ standard/newlib printf cause these use malloc for printf (and may not │
|
|
│ be thread safe). │
|
|
│ │
|
|
│ @brief Modified by Justine Tunney to support three different types of │
|
|
│ UNICODE, 128-bit arithmetic, binary conversion, string escaping, │
|
|
│ AVX2 character scanning, and possibly a tinier footprint too, so │
|
|
│ long as extremely wild linker hacks aren't considered cheating. │
|
|
└─────────────────────────────────────────────────────────────────────────────*/
|
|
#include "libc/assert.h"
|
|
#include "libc/bits/bits.h"
|
|
#include "libc/bits/weaken.h"
|
|
#include "libc/conv/conv.h"
|
|
#include "libc/fmt/fmt.h"
|
|
#include "libc/fmt/paland.inc"
|
|
#include "libc/fmt/palandprintf.internal.h"
|
|
#include "libc/mem/mem.h"
|
|
#include "libc/runtime/internal.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/sysv/errfuns.h"
|
|
|
|
static int ppatoi(const char **str) {
|
|
int i;
|
|
for (i = 0; '0' <= **str && **str <= '9'; ++*str) {
|
|
i *= 10;
|
|
i += **str - '0';
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/**
|
|
* Implements {,v}{,s{,n},{,{,x}as},f,d}printf domain-specific language.
|
|
*
|
|
* Type Specifiers
|
|
*
|
|
* - `%s` char * (thompson-pike unicode)
|
|
* - `%ls` wchar_t * (32-bit unicode → thompson-pike unicode)
|
|
* - `%hs` char16_t * (16-bit unicode → thompson-pike unicode)
|
|
* - `%b` int (radix 2 binary)
|
|
* - `%o` int (radix 8 octal)
|
|
* - `%d` int (radix 10 decimal)
|
|
* - `%x` int (radix 16 hexadecimal)
|
|
* - `%X` int (radix 16 hexadecimal uppercase)
|
|
* - `%u` unsigned
|
|
* - `%f` double
|
|
* - `%Lf` long double
|
|
* - `%p` pointer (48-bit hexadecimal)
|
|
*
|
|
* Size Modifiers
|
|
*
|
|
* - `%hhd` char (8-bit)
|
|
* - `%hd` short (16-bit)
|
|
* - `%ld` long (64-bit)
|
|
* - `%lu` unsigned long (64-bit)
|
|
* - `%lx` unsigned long (64-bit hexadecimal)
|
|
* - `%jd` intmax_t (128-bit)
|
|
*
|
|
* Width Modifiers
|
|
*
|
|
* - `%08d` fixed columns w/ zero leftpadding
|
|
* - `%8d` fixed columns w/ space leftpadding
|
|
* - `%*s` variable column string (thompson-pike)
|
|
*
|
|
* Precision Modifiers
|
|
*
|
|
* - `%.8s` supplied byte length (obeys nul terminator)
|
|
* - `%.*s` supplied byte length argument (obeys nul terminator)
|
|
* - `%`.*s` supplied byte length argument c escaped (ignores nul terminator)
|
|
* - `%#.*s` supplied byte length argument visualized (ignores nul terminator)
|
|
* - `%.*hs` supplied char16_t length argument (obeys nul terminator)
|
|
* - `%.*ls` supplied wchar_t length argument (obeys nul terminator)
|
|
*
|
|
* Formatting Modifiers
|
|
*
|
|
* - `%,d` thousands separators
|
|
* - `%'s` escaped c string literal
|
|
* - `%`c` c escaped character
|
|
* - `%`'c` c escaped character quoted
|
|
* - `%`s` c escaped string
|
|
* - `%`'s` c escaped string quoted
|
|
* - `%`s` escaped double quoted c string literal
|
|
* - `%`c` escaped double quoted c character literal
|
|
* - `%+d` plus leftpad if positive (aligns w/ negatives)
|
|
* - `% d` space leftpad if positive (aligns w/ negatives)
|
|
* - `%#s` datum (radix 256 null-terminated ibm cp437)
|
|
* - `%#x` int (radix 16 hexadecimal w/ 0x prefix if not zero)
|
|
*
|
|
* @note implementation detail of printf(), snprintf(), etc.
|
|
* @see printf() for wordier documentation
|
|
*/
|
|
hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) {
|
|
void *p;
|
|
char qchar;
|
|
long double ldbl;
|
|
wchar_t charbuf[1];
|
|
const char *alphabet;
|
|
int (*out)(long, void *);
|
|
unsigned char signbit, log2base;
|
|
int w, flags, width, lasterr, precision;
|
|
|
|
lasterr = errno;
|
|
out = fn ? fn : (int (*)(int, void *))missingno;
|
|
|
|
while (*format) {
|
|
/* %[flags][width][.precision][length] */
|
|
if (*format != '%') {
|
|
/* no */
|
|
if (out(*format, arg) == -1) return -1;
|
|
format++;
|
|
continue;
|
|
} else {
|
|
/* yes, evaluate it */
|
|
format++;
|
|
}
|
|
|
|
/* evaluate flags */
|
|
flags = 0;
|
|
getflag:
|
|
switch (*format++) {
|
|
case '0':
|
|
flags |= FLAGS_ZEROPAD;
|
|
goto getflag;
|
|
case '-':
|
|
flags |= FLAGS_LEFT;
|
|
goto getflag;
|
|
case '+':
|
|
flags |= FLAGS_PLUS;
|
|
goto getflag;
|
|
case ' ':
|
|
flags |= FLAGS_SPACE;
|
|
goto getflag;
|
|
case '#':
|
|
flags |= FLAGS_HASH;
|
|
goto getflag;
|
|
case ',':
|
|
flags |= FLAGS_GROUPING;
|
|
goto getflag;
|
|
case '`':
|
|
flags |= FLAGS_REPR;
|
|
/* fallthrough */
|
|
case '\'':
|
|
flags |= FLAGS_QUOTE;
|
|
goto getflag;
|
|
default:
|
|
format--;
|
|
break;
|
|
}
|
|
|
|
/* evaluate width field */
|
|
width = 0;
|
|
if (isdigit(*format)) {
|
|
width = ppatoi(&format);
|
|
} else if (*format == '*') {
|
|
w = va_arg(va, int);
|
|
if (w < 0) {
|
|
flags |= FLAGS_LEFT; /* reverse padding */
|
|
width = -w;
|
|
} else {
|
|
width = w;
|
|
}
|
|
format++;
|
|
}
|
|
|
|
/* evaluate precision field */
|
|
precision = 0;
|
|
if (*format == '.') {
|
|
flags |= FLAGS_PRECISION;
|
|
format++;
|
|
if (isdigit(*format)) {
|
|
precision = ppatoi(&format);
|
|
} else if (*format == '*') {
|
|
precision = va_arg(va, int);
|
|
format++;
|
|
}
|
|
}
|
|
if (precision < 0) {
|
|
precision = 0;
|
|
}
|
|
|
|
/* evaluate length field */
|
|
signbit = 31;
|
|
switch (*format) {
|
|
case 'j': /* intmax_t */
|
|
format++;
|
|
signbit = sizeof(intmax_t) * 8 - 1;
|
|
break;
|
|
case 'l':
|
|
if (format[1] == 'l') format++;
|
|
/* fallthrough */
|
|
case 't': /* ptrdiff_t */
|
|
case 'z': /* size_t */
|
|
case 'Z': /* size_t */
|
|
case 'L': /* long double */
|
|
format++;
|
|
signbit = 63;
|
|
break;
|
|
case 'h':
|
|
format++;
|
|
if (*format == 'h') {
|
|
format++;
|
|
signbit = 7;
|
|
} else {
|
|
signbit = 15;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* evaluate specifier */
|
|
alphabet = "0123456789abcdef";
|
|
log2base = 0;
|
|
qchar = '"';
|
|
switch (*format++) {
|
|
case 'p':
|
|
flags |= FLAGS_ZEROPAD;
|
|
width = POINTER_XDIGITS;
|
|
log2base = 4;
|
|
signbit = 47;
|
|
goto DoNumber;
|
|
case 'X':
|
|
alphabet = "0123456789ABCDEF";
|
|
/* fallthrough */
|
|
case 'x':
|
|
log2base = 4;
|
|
goto DoNumber;
|
|
case 'b':
|
|
log2base = 1;
|
|
goto DoNumber;
|
|
case 'o':
|
|
log2base = 3;
|
|
goto DoNumber;
|
|
case 'd':
|
|
case 'i':
|
|
flags |= FLAGS_ISSIGNED;
|
|
/* fallthrough */
|
|
case 'u': {
|
|
flags &= ~FLAGS_HASH; /* no hash for dec format */
|
|
DoNumber:
|
|
if (!weaken(ntoa) ||
|
|
weaken(ntoa)(out, arg, va, signbit, log2base, precision, width,
|
|
flags, alphabet) == -1) {
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'f':
|
|
case 'F':
|
|
if (signbit == 63) {
|
|
ldbl = va_arg(va, long double);
|
|
} else {
|
|
ldbl = va_arg(va, double);
|
|
}
|
|
if (!weaken(ftoa) ||
|
|
weaken(ftoa)(out, arg, ldbl, precision, width, flags) == -1) {
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case 'c':
|
|
precision = 1;
|
|
flags |= FLAGS_PRECISION;
|
|
qchar = '\'';
|
|
p = charbuf;
|
|
charbuf[0] = va_arg(va, int); /* assume little endian */
|
|
goto showstr;
|
|
|
|
case 'm':
|
|
p = weaken(strerror) ? weaken(strerror)(lasterr) : "?";
|
|
signbit = 0;
|
|
goto showstr;
|
|
|
|
case 'r':
|
|
flags |= FLAGS_REPR;
|
|
/* fallthrough */
|
|
|
|
case 'q':
|
|
flags |= FLAGS_QUOTE;
|
|
/* fallthrough */
|
|
|
|
case 's':
|
|
p = va_arg(va, void *);
|
|
showstr:
|
|
if (!weaken(stoa) || weaken(stoa)(out, arg, p, flags, precision, width,
|
|
signbit, qchar) == -1) {
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case '%':
|
|
if (out('%', arg) == -1) return -1;
|
|
break;
|
|
|
|
default:
|
|
if (out(format[-1], arg) == -1) return -1;
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|