4 * Copyright 2003, 2004 Develer S.r.l. (http://www.develer.com/)
5 * This file is part of DevLib - See devlib/README for information.
10 * \brief Basic "printf", "sprintf" and "fprintf" formatter.
12 * This module is 100% reentrant and can be adapted to user-defined routines
13 * that needs formatters with special properties like different output
14 * channels or new format specifiers.
16 * To reduce size in applications not using real numbers or long integers
17 * the formatter may be compiled to exclude certain parts. This is
18 * controlled by giving a -D option a compilation time:
21 * -D CONFIG_PRINTF_NOFLOAT Exclude support for floats
22 * -D CONFIG_PRINTF_REDUCED Simplified formatter (see below)
23 * -D CONFIG_PRINTF_NOMODIFIERS Exclude 'l' and 'h' modifiers in reduced version
26 * The reduced version of formatter is suitable when program size is critical
27 * rather than formatting power. This routine uses less than 20 bytes of
28 * stack space which makes it practical even in systems with less than 256
31 * The only formatting specifiers supported by the reduced formatter are:
33 * %% %c %s %d %o %x %X and %hd %ho %hx %hX %ld %lo %lx %lX
36 * It means that real variables are not supported as well as field
37 * width and precision arguments.
39 * The last eight (h and l modifiers) can easily be removed
40 * by defining the \c CONFIG_PRINTF_NOMODIFIERS macro.
45 * Revision 1.4 2004/07/21 00:20:20 bernie
46 * Allow completely disabling printf()-like formatter.
48 * Revision 1.3 2004/07/18 22:00:15 bernie
49 * Reorganize configuration parameters to match DevLib's convention.
51 * Revision 1.2 2004/06/03 11:27:09 bernie
52 * Add dual-license information.
54 * Revision 1.1 2004/05/23 15:43:16 bernie
55 * Import mware modules.
59 #ifndef CONFIG_PRINTF_DISABLED
62 #include <compiler.h> /* progmem macros */
63 #include <config.h> /* CONFIG_ macros */
65 #ifndef CONFIG_PRINTF_NOFLOAT
67 #endif /* CONFIG_PRINTF_NOFLOAT */
69 #ifndef FRMWRI_BUFSIZE
70 /*bernie: save some memory, who cares about floats with lots of decimals? */
71 /*#define FRMWRI_BUFSIZE 134*/
72 #define FRMWRI_BUFSIZE 32
79 #ifndef CONFIG_PRINTF_NOMODIFIERS
80 #define IS_SHORT (h_modifier || (sizeof(int) == 2 && !l_modifier))
82 #define IS_SHORT (sizeof(int) == 2)
86 #ifndef CONFIG_PRINTF_NOFLOAT
88 static char *float_conversion(MEM_ATTRIBUTE long double value,
89 MEM_ATTRIBUTE short nr_of_digits,
90 MEM_ATTRIBUTE char *buf,
91 MEM_ATTRIBUTE char format_flag,
92 MEM_ATTRIBUTE char g_flag,
93 MEM_ATTRIBUTE char alternate_flag)
95 MEM_ATTRIBUTE char *cp;
96 MEM_ATTRIBUTE char *buf_pointer;
97 MEM_ATTRIBUTE short n, i, dec_point_pos, integral_10_log;
104 while (value >= 1e11) /* To speed up things a bit */
107 integral_10_log += 10;
115 else if (value) /* Not just 0.0 */
117 while (value <= 1e-10) /* To speed up things a bit */
120 integral_10_log -= 10;
130 if (integral_10_log < nr_of_digits && integral_10_log >= -4)
133 nr_of_digits -= integral_10_log;
137 /* %#G - No removal of trailing zeros */
140 /* %G - Removal of trailing zeros */
151 /* Less than one... */
152 if (integral_10_log < 0)
154 *buf_pointer++ = '0';
155 if ((n = nr_of_digits) || alternate_flag)
156 *buf_pointer++ = '.';
158 while (--i > integral_10_log && nr_of_digits)
160 *buf_pointer++ = '0';
163 if (integral_10_log < (-n - 1))
164 /* Nothing more to do */
170 dec_point_pos = - integral_10_log;
175 while (i <= nr_of_digits )
177 value -= (long double)(n = (short)value); /* n=Digit value=Remainder */
178 value *= 10; /* Prepare for next shot */
179 *buf_pointer++ = n + '0';
180 if ( ! i++ && (nr_of_digits || alternate_flag))
181 *buf_pointer++ = '.';
184 /* Rounding possible */
188 cp = buf_pointer - 1;
193 if ( (*cp += n) == ('9' + 1) )
201 } while (cp-- > buf);
210 if (*(cp - 1) == '.')
235 /* %G - Remove trailing zeros */
238 while (*(buf_pointer - 1) == '0')
240 if (*(buf_pointer - 1) == '.')
247 *buf_pointer++ = format_flag;
248 if (integral_10_log < 0)
250 *buf_pointer++ = '-';
251 integral_10_log = -integral_10_log;
254 *buf_pointer++ = '+';
260 *buf_pointer++ = (integral_10_log % 10) + '0';
261 integral_10_log /= 10;
262 } while ( integral_10_log || n < 2 );
263 for ( i = n ; n > 0 ; n-- )
264 *(buf_pointer - 11 - i + n) = *(buf_pointer - n);
267 return (buf_pointer);
270 #endif /* !CONFIG_PRINTF_NOFLOAT */
273 * This routine forms the core and entry of the formatter.
275 * The conversion performed conforms to the ANSI specification for "printf".
278 PGM_FUNC(_formatted_write)(const char * PGM_ATTR format,
279 void put_one_char(char, void *),
280 void *secret_pointer,
283 MEM_ATTRIBUTE static char bad_conversion[] = "???";
284 MEM_ATTRIBUTE static char null_pointer[] = "<NULL>";
286 #ifndef CONFIG_PRINTF_REDUCED
287 MEM_ATTRIBUTE char format_flag;
288 MEM_ATTRIBUTE int precision;
290 MEM_ATTRIBUTE int field_width, nr_of_chars;
291 MEM_ATTRIBUTE char plus_space_flag, left_adjust, l_L_modifier;
292 MEM_ATTRIBUTE char h_modifier, alternate_flag;
293 MEM_ATTRIBUTE char nonzero_value;
294 MEM_ATTRIBUTE unsigned long ulong, div_factor;
296 #ifndef CONFIG_PRINTF_NOFLOAT
297 MEM_ATTRIBUTE long double fvalue;
300 MEM_ATTRIBUTE char *buf_pointer;
301 MEM_ATTRIBUTE char *ptr;
302 MEM_ATTRIBUTE const char *hex;
303 MEM_ATTRIBUTE char zeropad;
304 MEM_ATTRIBUTE char buf[FRMWRI_BUFSIZE];
307 for (;;) /* Until full format string read */
309 while ((format_flag = PGM_READ_CHAR(format++)) != '%') /* Until '%' or '\0' */
312 return (nr_of_chars);
313 put_one_char (format_flag, secret_pointer);
316 if (PGM_READ_CHAR(format) == '%') /* %% prints as % */
319 put_one_char('%', secret_pointer);
324 plus_space_flag = left_adjust = alternate_flag = zeropad = 0;
325 ptr = buf_pointer = &buf[0];
326 hex = "0123456789ABCDEF";
328 /* check for leading '-', '+', ' ','#' or '0' flags */
331 switch (PGM_READ_CHAR(format))
337 plus_space_flag = PGM_READ_CHAR(format);
354 /* Optional field width (may be '*') */
355 if (PGM_READ_CHAR(format) == '*')
357 field_width = va_arg(ap, int);
360 field_width = -field_width;
368 while (PGM_READ_CHAR(format) >= '0' && PGM_READ_CHAR(format) <= '9')
369 field_width = field_width * 10 + (PGM_READ_CHAR(format++) - '0');
375 /* Optional precision (or '*') */
376 if (PGM_READ_CHAR(format) == '.')
378 if (PGM_READ_CHAR(++format) == '*')
380 precision = va_arg(ap, int);
386 while (PGM_READ_CHAR(format) >= '0' && PGM_READ_CHAR(format) <= '9')
387 precision = precision * 10 + (PGM_READ_CHAR(format++) - '0');
393 /* At this point, "left_adjust" is nonzero if there was
394 * a sign, "zeropad" is 1 if there was a leading zero
395 * and 0 otherwise, "field_width" and "precision"
396 * contain numbers corresponding to the digit strings
397 * before and after the decimal point, respectively,
398 * and "plus_space_flag" is either 0 (no flag) or
399 * contains a plus or space character. If there was no
400 * decimal point, "precision" will be -1.
403 l_L_modifier = h_modifier = 0;
405 /* Optional 'l','L' r 'h' modifier? */
406 switch (PGM_READ_CHAR(format))
420 * At exit from the following switch, we will emit
421 * the characters starting at "buf_pointer" and
424 switch (format_flag = PGM_READ_CHAR(format++))
428 if (sizeof(short) != sizeof(int))
430 if (sizeof(int) != sizeof(long))
433 *va_arg(ap, short *) = nr_of_chars;
434 else if (l_L_modifier)
435 *va_arg(ap, long *) = nr_of_chars;
437 *va_arg(ap, int *) = nr_of_chars;
442 *va_arg(ap, short *) = nr_of_chars;
444 *va_arg(ap, int *) = nr_of_chars;
450 *va_arg(ap, long *) = nr_of_chars;
452 *va_arg(ap, int *) = nr_of_chars;
457 buf[0] = va_arg(ap, int);
462 if ( !(buf_pointer = va_arg(ap, char *)) )
463 buf_pointer = null_pointer;
466 for (n=0; *buf_pointer++ && n < precision; n++)
473 if (alternate_flag && !precision)
476 hex = "0123456789abcdef";
480 if (format_flag == 'p')
481 #if defined(__AVR__) || defined(__I196__) /* 16bit pointers */
482 ulong = (unsigned long)(unsigned short)va_arg(ap, char *);
483 #else /* 32bit pointers */
484 ulong = (unsigned long)va_arg(ap, char *);
485 #endif /* 32bit pointers */
486 else if (sizeof(short) == sizeof(int))
487 ulong = l_L_modifier ?
488 va_arg(ap, unsigned long) : (unsigned long)va_arg(ap, unsigned int);
491 (unsigned long)(unsigned short) va_arg(ap, int)
492 : (unsigned long)va_arg(ap, int);
493 div_factor = (format_flag == 'o') ?
494 8 : (format_flag == 'u') ? 10 : 16;
496 goto INTEGRAL_CONVERSION;
500 if (sizeof(short) == sizeof(long))
502 if ( (long)(ulong = va_arg(ap, unsigned long)) < 0)
504 plus_space_flag = '-';
505 ulong = (unsigned long)(-((signed long)ulong));
508 else if (sizeof(short) == sizeof(int))
510 if ( (long)(ulong = l_L_modifier ?
511 va_arg(ap,unsigned long) : (unsigned long)va_arg(ap,int)) < 0)
513 plus_space_flag = '-';
514 ulong = (unsigned long)(-((signed long)ulong));
519 if ( (signed long)(ulong = (unsigned long) (h_modifier ?
520 (short) va_arg(ap, int) : va_arg(ap,int))) < 0)
522 plus_space_flag = '-';
523 ulong = (unsigned long)(-((signed long)ulong));
528 /* Now convert to digits */
530 ptr = buf_pointer = &buf[FRMWRI_BUFSIZE - 1];
531 nonzero_value = (ulong != 0);
533 /* No char if zero and zero precision */
534 if (precision != 0 || nonzero_value)
536 *--buf_pointer = hex[ulong % div_factor];
537 while (ulong /= div_factor);
539 /* "precision" takes precedence */
542 precision = field_width - (plus_space_flag != 0);
543 while (precision > (int)(ptr - buf_pointer))
544 *--buf_pointer = '0';
546 if (alternate_flag && nonzero_value)
548 if (format_flag == 'x' || format_flag == 'X')
550 *--buf_pointer = format_flag;
551 *--buf_pointer = '0';
553 else if ((format_flag == 'o') && (*buf_pointer != '0'))
555 *--buf_pointer = '0';
560 #ifndef CONFIG_PRINTF_NOFLOAT
569 goto FLOATING_CONVERSION;
580 if (sizeof(double) != sizeof(long double))
582 if ( (fvalue = l_L_modifier ?
583 va_arg(ap,long double) : va_arg(ap,double)) < 0)
585 plus_space_flag = '-';
589 else if ( (fvalue = va_arg(ap,long double)) < 0)
591 plus_space_flag = '-';
594 ptr = float_conversion (fvalue,
596 buf_pointer += field_width,
602 precision = field_width - (plus_space_flag != 0);
603 while (precision > ptr - buf_pointer)
604 *--buf_pointer = '0';
608 #else /* CONFIG_PRINTF_NOFLOAT */
614 ptr = buf_pointer = bad_conversion;
618 #endif /* CONFIG_PRINTF_NOFLOAT */
620 case '\0': /* Really bad place to find NUL in */
624 /* Undefined conversion! */
625 ptr = buf_pointer = bad_conversion;
632 * This part emittes the formatted string to "put_one_char".
635 /* If field_width == 0 then nothing should be written. */
636 precision = ptr - buf_pointer;
638 if ( precision > field_width)
644 n = field_width - precision - (plus_space_flag != 0);
647 /* emit any leading pad characters */
651 put_one_char(' ', secret_pointer);
655 /* emit flag characters (if any) */
658 put_one_char(plus_space_flag, secret_pointer);
662 /* emit the string itself */
663 while (--precision >= 0)
665 put_one_char(*buf_pointer++, secret_pointer);
669 /* emit trailing space characters */
673 put_one_char(' ', secret_pointer);
678 #else /* CONFIG_PRINTF_REDUCED starts here */
680 #ifndef CONFIG_PRINTF_NOMODIFIERS
681 char l_modifier, h_modifier;
682 unsigned long u_val, div_val;
684 unsigned int u_val, div_val;
685 #endif /* CONFIG_PRINTF_NOMODIFIERS */
687 char l_modifier, h_modifier;
689 unsigned int nr_of_chars, base;
694 for (;;) /* Until full format string read */
696 while ((format_flag = PGM_READ_CHAR(format++)) != '%') /* Until '%' or '\0' */
699 return (nr_of_chars);
700 put_one_char (format_flag, secret_pointer);
704 #ifndef CONFIG_PRINTF_NOMODIFIERS
705 /*=================================*/
706 /* Optional 'l' or 'h' modifiers ? */
707 /*=================================*/
708 l_modifier = h_modifier = 0;
709 switch (PGM_READ_CHAR(format))
721 #endif /* !CONFIG_PRINTF_NOMODIFIERS */
723 switch (format_flag = PGM_READ_CHAR(format++))
726 format_flag = va_arg(ap, int);
728 put_one_char (format_flag, secret_pointer);
733 ptr = va_arg(ap, char *);
734 while (format_flag = *ptr++)
736 put_one_char (format_flag, secret_pointer);
746 div_val = 0x40000000;
747 goto CONVERSION_LOOP;
754 div_val = 1000000000;
755 goto CONVERSION_LOOP;
763 div_val = 0x10000000;
766 #ifndef CONFIG_PRINTF_NOMODIFIERS
768 u_val = (format_flag == 'd') ?
769 (short)va_arg(ap, int) : (unsigned short)va_arg(ap, int);
771 u_val = va_arg(ap, long);
773 u_val = (format_flag == 'd') ?
774 va_arg(ap,int) : va_arg(ap,unsigned int);
775 #else /* CONFIG_PRINTF_NOMODIFIERS */
776 u_val = va_arg(ap,int);
777 #endif /* CONFIG_PRINTF_NOMODIFIERS */
778 if (format_flag == 'd')
780 if (((int)u_val) < 0)
783 put_one_char ('-', secret_pointer);
787 while (div_val > 1 && div_val > u_val)
793 outChar = (u_val / div_val) + '0';
795 if (format_flag == 'x')
796 outChar += 'a'-'9'-1;
798 outChar += 'A'-'9'-1;
799 put_one_char (outChar, secret_pointer);
806 } /* end switch(format_flag...) */
808 #endif /* CONFIG_PRINTF_REDUCED */
811 #endif /* CONFIG_PRINTF_DISABLED */