diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9cb9142 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,3 @@ +[*.c] +indent_style = space +indent_size = 4 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2c54e52 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.0) +project(tinyprintf C) + +macro(make_test t src PRINTF SPRINTF) + add_executable(${t} ${src} tinyprintf.c) + target_include_directories(${t} PUBLIC .) + target_compile_definitions(${t} PRIVATE + TINYPRINTF_OVERRIDE_LIBC=0 + TINYPRINTF_DEFINE_TFP_PRINTF=${PRINTF} + TINYPRINTF_DEFINE_TFP_SPRINTF=${SPRINTF}) +endmacro() + + +make_test(test-printf test/printf.c 1 0) +make_test(test-sprintf test/sprintf.c 0 1) +make_test(test-all test/all.c 1 1) diff --git a/test/all.c b/test/all.c new file mode 100644 index 0000000..f23419c --- /dev/null +++ b/test/all.c @@ -0,0 +1,106 @@ +#include +#include +#include + +#include "tinyprintf.h" + +char expected_buffer[1000]; +char actual_buffer[1000]; + +int passes = 0, failures = 0; + +void check(const char *expr, int expected_sz, int actual_sz) +{ + int size_pass = expected_sz == actual_sz; + int content_pass = strcmp(expected_buffer, actual_buffer) == 0; + int pass = size_pass && content_pass; + + if (pass) { + passes++; + printf("PASS printf(%s)\n", expr); + printf("Output %s", actual_buffer); + } else { + failures++; + printf("FAIL printf(%s)\n", expr); + + if (!content_pass) { + printf("libc %s\n", expected_buffer); + printf("tinyprintf %s\n", actual_buffer); + } + } + printf("\n"); +} + +#define TPRINTF(expr...) do { \ + int expected_sz = snprintf(expected_buffer, sizeof(expected_buffer), expr);\ + int actual_sz = tfp_snprintf(actual_buffer, sizeof(actual_buffer), expr);\ + check(#expr, expected_sz, actual_sz);\ +} while(0) + +void main() +{ + TPRINTF("d1=%016llx d2=%016lx d3=%02x d4=%02X 42=%03d", + (long long unsigned) 0xd1, (long unsigned) 0xd2, 0xd3, 0xd4, 42); + TPRINTF("d1=%04x d2=%06x d3=%08x %%100", 0xd1, 0xd2, 0xd3); + TPRINTF("|%-14s| |%-16s| d2=%2x |%-30s|", "str14", "str16", 0xd2, + "12345678901234567890123456789012345"); + TPRINTF("|%4s|", "string4"); + TPRINTF("|%-4s|", "string4"); + TPRINTF("42=%3d d1=%4.4x |%4s| d2=%8.8x", 42, 0xd1, "string4", 0xd2); + TPRINTF("42=%3d d1=%4.4x |%-4s| d2=%8.8x", 42, 0xd1, "string4", 0xd2); + TPRINTF("84=%d 21=%ds |%s| |%sOK| d1=%x d2=%#x", + 84, 21, "hello", "fine", 0xd1, 0xd2); + TPRINTF("42=% 3d d1=%4x |%10s| d2=%3.3x", 42, 0xd1, "string4", 0xd2); + + TPRINTF("%lld", LLONG_MIN); + TPRINTF("%lld", LLONG_MAX); + TPRINTF("%llu", ULLONG_MAX); + TPRINTF("%llx", LLONG_MIN); + TPRINTF("%llx", LLONG_MAX); + TPRINTF("%llx", ULLONG_MAX); + + TPRINTF("d1=%.1x", 0xd1); + TPRINTF("d1=%4.1x", 0xd1); + TPRINTF("d1=%4.x", 0xd1); + TPRINTF("d1=%4.4x", 0xd1); + TPRINTF("d1=%04x", 0xd1); + + { + char blah[256]; + TPRINTF("a=%zd", sizeof(blah)); + TPRINTF("a=%zu", sizeof(blah)); + TPRINTF("a=%zi", sizeof(blah)); + TPRINTF("a=0x%zx", sizeof(blah)); + } + + { + int in_stack; + TPRINTF("Adddress of main: %p", main); + TPRINTF("Adddress of stack variable: %p", &in_stack); + } + + { + char buf[] = "0123456789"; + TPRINTF("%*s", 5, &buf[5]); /* minimum length, too long string */ + TPRINTF("%.*s", 5, buf); /* maximum length, too long string */ + TPRINTF("%.*s", 5, &buf[8]); /* maximum length, too short string */ + TPRINTF("%*.*s", 5, 5, &buf[8]); /* minimum and maximum, too short string*/ + TPRINTF("%*.*s", 5, 1, &buf[8]); + TPRINTF("%*.*s", 5, 5, buf); + + TPRINTF("%-*d", 5, 123); + TPRINTF("%*d", 5, 123); + } + + printf("Sizeof\n"); + printf(" char %zd\n", sizeof(char)); + printf(" int %zd\n", sizeof(int)); + printf(" long %zd\n", sizeof(long)); + printf(" long long %zd\n", sizeof(long long)); + printf(" void* %zd\n", sizeof(void *)); + printf("\n"); + + printf("Summary\n"); + printf(" passes: %d\n", passes); + printf(" failures: %d\n", failures); +} diff --git a/test/printf.c b/test/printf.c index dd84a9b..333ef38 100644 --- a/test/printf.c +++ b/test/printf.c @@ -39,6 +39,7 @@ int main() TPRINTF("42=%3d d1=%4.4x |%-4s| d2=%8.8x\n", 42, 0xd1, "string4", 0xd2); TPRINTF("84=%d 21=%ds |%s| |%sOK| d1=%x d2=%#x\n", 84, 21, "hello", "fine", 0xd1, 0xd2); + TPRINTF("42=% 3d d1=%4x |%10s| d2=%3.3x\n", 42, 0xd1, "string4", 0xd2); TPRINTF("%lld\n", LLONG_MIN); TPRINTF("%lld\n", LLONG_MAX); diff --git a/tinyprintf.c b/tinyprintf.c index bb22700..0e10345 100644 --- a/tinyprintf.c +++ b/tinyprintf.c @@ -42,6 +42,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #endif +#include + #ifdef PRINTF_LONG_LONG_SUPPORT # define PRINTF_LONG_SUPPORT #endif @@ -70,14 +72,16 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * Implementation */ struct param { - char lz:1; /**< Leading zeros */ - char alt:1; /**< alternate form */ - char uc:1; /**< Upper case (for base16 only) */ - char align_left:1; /**< 0 == align right (default), 1 == align left */ - unsigned int width; /**< field width */ - char sign; /**< The sign to display (if any) */ - unsigned int base; /**< number base (e.g.: 8, 10, 16) */ - char *bf; /**< Buffer to output */ + char l:1; /**< Add leading character */ + char alt:1; /**< alternate form */ + char uc:1; /**< Upper case (for base16 only) */ + char align_left:1; /**< 0 == align right (default), 1 == align left */ + char lchr; /**< Leading character */ + unsigned int width; /**< field width */ + int precision; /**< field precision */ + char sign; /**< The sign to display (if any) */ + unsigned int base; /**< number base (e.g.: 8, 10, 16) */ + char *bf; /**< Buffer to output */ }; @@ -198,15 +202,15 @@ static char a2u(char ch, const char **src, int base, unsigned int *nump) return ch; } -static void putchw(void *putp, putcf putf, struct param *p) +static void putchw(void *putp, putcf putf, const struct param *p) { char ch; int n = p->width; char *bf = p->bf; + int precision = p->precision; /* Number of filling characters */ - while (*bf++ && n > 0) - n--; + while (precision-- > 0 && *bf++ && n-- > 0); if (p->sign) n--; if (p->alt && p->base == 16) @@ -215,7 +219,7 @@ static void putchw(void *putp, putcf putf, struct param *p) n--; /* Fill with space to align to the right, before alternate or sign */ - if (!p->lz && !p->align_left) { + if (!p->l && !p->align_left) { while (n-- > 0) putf(putp, ' '); } @@ -233,18 +237,19 @@ static void putchw(void *putp, putcf putf, struct param *p) } /* Fill with zeros, after alternate or sign */ - if (p->lz) { + if (p->l) { while (n-- > 0) - putf(putp, '0'); + putf(putp, p->lchr); } /* Put actual buffer */ bf = p->bf; - while ((ch = *bf++)) + precision = p->precision; + while (precision-- > 0 && (ch = *bf++)) putf(putp, ch); /* Fill with space to align to the left, after string */ - if (!p->lz && p->align_left) { + if (!p->l && p->align_left) { while (n-- > 0) putf(putp, ' '); } @@ -259,6 +264,7 @@ void tfp_format(void *putp, putcf putf, const char *fmt, va_list va) char bf[12]; /* int = 32b on some architectures */ #endif char ch; + int w; p.bf = bf; while ((ch = *(fmt++))) { @@ -269,9 +275,11 @@ void tfp_format(void *putp, putcf putf, const char *fmt, va_list va) char lng = 0; /* 1 for long, 2 for long long */ #endif /* Init parameter struct */ - p.lz = 0; + p.l = 0; + p.lchr = ' '; p.alt = 0; p.width = 0; + p.precision = INT_MAX; p.align_left = 0; p.sign = 0; @@ -282,11 +290,22 @@ void tfp_format(void *putp, putcf putf, const char *fmt, va_list va) p.align_left = 1; continue; case '0': - p.lz = 1; + case ' ': + p.l = 1; + p.lchr = ch; continue; case '#': p.alt = 1; continue; + case '*': + w = va_arg(va, int); + if (w < 0) { + p.align_left = 1; + p.width = (unsigned int) -w; + } else { + p.width = (unsigned int) w; + } + continue; default: break; } @@ -300,13 +319,25 @@ void tfp_format(void *putp, putcf putf, const char *fmt, va_list va) /* We accept 'x.y' format but don't support it completely: * we ignore the 'y' digit => this ignores 0-fill - * size and makes it == width (ie. 'x') */ + * size and makes it == width (ie. 'x'), but use + * its value to set fill character to '0' if greater than 1. + */ if (ch == '.') { - p.lz = 1; /* zero-padding */ - /* ignore actual 0-fill size: */ - do { - ch = *(fmt++); - } while ((ch >= '0') && (ch <= '9')); + p.l = 1; + ch = *(fmt++); + if (ch >= '0' && ch <= '9') { + unsigned int num; + ch = a2u(ch, &fmt, 10, &num); + if (num > 1) { + p.lchr = '0'; + } + } else if (ch == '*') { + ch = *(fmt++); + int precision = va_arg(va, int); + if (precision >= 0) { + p.precision = precision; + } + } } #ifdef PRINTF_SIZE_T_SUPPORT @@ -430,7 +461,7 @@ void init_printf(void *putp, putcf putf) stdout_putp = putp; } -void tfp_printf(char *fmt, ...) +void tfp_printf(const char *fmt, ...) { va_list va; va_start(va, fmt); diff --git a/tinyprintf.h b/tinyprintf.h index a769f4a..7ba9595 100644 --- a/tinyprintf.h +++ b/tinyprintf.h @@ -173,7 +173,7 @@ int tfp_sprintf(char *str, const char *fmt, ...) \ #if TINYPRINTF_DEFINE_TFP_PRINTF void init_printf(void *putp, putcf putf); -void tfp_printf(char *fmt, ...) _TFP_SPECIFY_PRINTF_FMT(1, 2); +void tfp_printf(const char *fmt, ...) _TFP_SPECIFY_PRINTF_FMT(1, 2); # if TINYPRINTF_OVERRIDE_LIBC # define printf tfp_printf # endif