/* * The Minimal snprintf() implementation * * Copyright (c) 2013,2014 Michal Ludvig * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the auhor nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * ---- * * This is a minimal snprintf() implementation optimised * for embedded systems with a very limited program memory. * mini_snprintf() doesn't support _all_ the formatting * the glibc does but on the other hand is a lot smaller. * Here are some numbers from my STM32 project (.bin file size): * no snprintf(): 10768 bytes * mini snprintf(): 11420 bytes (+ 652 bytes) * glibc snprintf(): 34860 bytes (+24092 bytes) * Wasting nearly 24kB of memory just for snprintf() on * a chip with 32kB flash is crazy. Use mini_snprintf() instead. * */ #include "mini-printf.h" static int mini_strlen(const char *s) { int len = 0; while (s[len] != '\0') len++; return len; } static int mini_itoa(long value, unsigned int radix, int uppercase, int unsig, char *buffer) { char *pbuffer = buffer; int negative = 0; int i, len; /* No support for unusual radixes. */ if (radix > 16) return 0; if (value < 0 && !unsig) { negative = 1; value = -value; } /* This builds the string back to front ... */ do { int digit = value % radix; *(pbuffer++) = (digit < 10 ? '0' + digit : (uppercase ? 'A' : 'a') + digit - 10); value /= radix; } while (value > 0); if (negative) *(pbuffer++) = '-'; *(pbuffer) = '\0'; /* ... now we reverse it (could do it recursively but will * conserve the stack space) */ len = (pbuffer - buffer); for (i = 0; i < len / 2; i++) { char j = buffer[i]; buffer[i] = buffer[len-i-1]; buffer[len-i-1] = j; } return len; } static int mini_pad(char* ptr, int len, char pad_char, int pad_to, char *buffer) { int i; int overflow = 0; char * pbuffer = buffer; if(pad_to == 0) pad_to = len; if(len > pad_to) { len = pad_to; overflow = 1; } for(i = pad_to - len; i > 0; i --) { *(pbuffer++) = pad_char; } for(i = len; i > 0; i --) { *(pbuffer++) = *(ptr++); } len = pbuffer - buffer; if(overflow) { for (i = 0; i < 3 && pbuffer > buffer; i ++) { *(pbuffer-- - 1) = '*'; } } return len; } struct mini_buff { char *buffer, *pbuffer; unsigned int buffer_len; }; static int _puts(char *s, int len, void *buf) { if(!buf) return len; struct mini_buff *b = buf; char * p0 = b->buffer; int i; /* Copy to buffer */ for (i = 0; i < len; i++) { if(b->pbuffer == b->buffer + b->buffer_len - 1) { break; } *(b->pbuffer ++) = s[i]; } *(b->pbuffer) = 0; return b->pbuffer - p0; } #ifdef MINI_PRINTF_ENABLE_OBJECTS static int (*mini_handler) (void* data, void* obj, int ch, int lhint, char** bf) = 0; static void (*mini_handler_freeor)(void* data, void*) = 0; static void * mini_handler_data = 0; void mini_printf_set_handler( void* data, int (*handler)(void* data, void* obj, int ch, int len_hint, char** buf), void (*freeor)(void* data, void* buf)) { mini_handler = handler; mini_handler_freeor = freeor; mini_handler_data = data; } #endif int mini_vsnprintf(char *buffer, unsigned int buffer_len, const char *fmt, va_list va) { struct mini_buff b; b.buffer = buffer; b.pbuffer = buffer; b.buffer_len = buffer_len; if(buffer_len == 0) buffer = (void*) 0; int n = mini_vpprintf(_puts, (buffer != (void*)0)?&b:(void*)0, fmt, va); if(buffer == (void*) 0) { return n; } return b.pbuffer - b.buffer; } int mini_vpprintf(int (*puts)(char* s, int len, void* buf), void* buf, const char *fmt, va_list va) { char bf[24]; char bf2[24]; char ch; #ifdef MINI_PRINTF_ENABLE_OBJECTS void* obj; #endif if(puts == (void*)0) { /* run puts in counting mode. */ puts = _puts; buf = (void*)0; } int n = 0; while ((ch=*(fmt++))) { int len; if (ch!='%') { len = 1; len = puts(&ch, len, buf); } else { char pad_char = ' '; int pad_to = 0; char l = 0; char *ptr; ch=*(fmt++); /* Zero padding requested */ if (ch == '0') pad_char = '0'; while (ch >= '0' && ch <= '9') { pad_to = pad_to * 10 + (ch - '0'); ch=*(fmt++); } if(pad_to > (signed int) sizeof(bf)) { pad_to = sizeof(bf); } if (ch == 'l') { l = 1; ch=*(fmt++); } switch (ch) { case 0: goto end; case 'u': case 'd': if(l) { len = mini_itoa(va_arg(va, unsigned long), 10, 0, (ch=='u'), bf2); } else { if(ch == 'u') { len = mini_itoa((unsigned long) va_arg(va, unsigned int), 10, 0, 1, bf2); } else { len = mini_itoa((long) va_arg(va, int), 10, 0, 0, bf2); } } len = mini_pad(bf2, len, pad_char, pad_to, bf); len = puts(bf, len, buf); break; case 'p': case 'x': case 'X': if(l) { len = mini_itoa(va_arg(va, unsigned long), 16, (ch=='X'), 1, bf2); } else { len = mini_itoa((unsigned long) va_arg(va, unsigned int), 16, (ch=='X'), 1, bf2); } len = mini_pad(bf2, len, pad_char, pad_to, bf); len = puts(bf, len, buf); break; case 'c' : ch = (char)(va_arg(va, int)); len = mini_pad(&ch, 1, pad_char, pad_to, bf); len = puts(bf, len, buf); break; case 's' : ptr = va_arg(va, char*); len = mini_strlen(ptr); if (pad_to > 0) { len = mini_pad(ptr, len, pad_char, pad_to, bf); len = puts(bf, len, buf); } else { len = puts(ptr, len, buf); } break; #ifdef MINI_PRINTF_ENABLE_OBJECTS case 'O' : /* Object by content (e.g. str) */ case 'R' : /* Object by representation (e.g. repr)*/ obj = va_arg(va, void*); len = mini_handler(mini_handler_data, obj, ch, pad_to, &ptr); if (pad_to > 0) { len = mini_pad(ptr, len, pad_char, pad_to, bf); len = puts(bf, len, buf); } else { len = puts(ptr, len, buf); } mini_handler_freeor(mini_handler_data, ptr); break; #endif default: len = 1; len = puts(&ch, len, buf); break; } } n = n + len; } end: return n; } int mini_snprintf(char* buffer, unsigned int buffer_len, const char *fmt, ...) { int ret; va_list va; va_start(va, fmt); ret = mini_vsnprintf(buffer, buffer_len, fmt, va); va_end(va); return ret; } int mini_pprintf(int (*puts)(char*s, int len, void* buf), void* buf, const char *fmt, ...) { int ret; va_list va; va_start(va, fmt); ret = mini_vpprintf(puts, buf, fmt, va); va_end(va); return ret; }