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=PRINTF_FULL Full ANSI printf formatter
22 * -D CONFIG_PRINTF=PRINTF_NOFLOAT Exclude support for floats
23 * -D CONFIG_PRINTF=PRINTF_REDUCED Simplified formatter (see below)
24 * -D CONFIG_PRINTF=PRINTF_NOMODIFIERS Exclude 'l' and 'h' modifiers in reduced version
25 * -D CONFIG_PRINTF=PRINTF_DISABLED No formatter at all
28 * Code size on AVR4 with GCC 3.4.1 (-O2):
29 * PRINTF_FULL 2912byte (0xB60)
30 * PRINTF_NOFLOAT 1684byte (0x694)
31 * PRINTF_REDUCED 924byte (0x39C)
32 * PRINTF_NOMODIFIERS 416byte (0x1A0)
34 * Code/data size in words on DSP56K with CodeWarrior 6.0:
36 * PRINTF_NOFLOAT 795/45
37 * PRINTF_REDUCED 482/0
38 * PRINTF_NOMODIFIERS 301/0
40 * The reduced version of formatter is suitable when program size is critical
41 * rather than formatting power. This routine uses less than 20 bytes of
42 * stack space which makes it practical even in systems with less than 256
45 * The only formatting specifiers supported by the reduced formatter are:
47 * %% %c %s %d %o %x %X and %hd %ho %hx %hX %ld %lo %lx %lX
50 * It means that real variables are not supported as well as field
51 * width and precision arguments.
56 *#* Revision 1.9 2004/09/14 21:06:23 bernie
59 *#* Revision 1.8 2004/08/25 14:12:09 rasky
60 *#* Aggiornato il comment block dei log RCS
62 *#* Revision 1.7 2004/08/04 15:53:47 rasky
63 *#* Nuove opzioni di configurazione per formatted_write e ridotto maggiormente l'utilizzo dellos tack
65 *#* Revision 1.6 2004/07/30 14:34:10 rasky
66 *#* Vari fix per documentazione e commenti
67 *#* Aggiunte PP_CATn e STATIC_ASSERT
69 *#* Revision 1.5 2004/07/29 22:57:09 bernie
70 *#* Switch to new-style config handling.
72 *#* Revision 1.4 2004/07/21 00:20:20 bernie
73 *#* Allow completely disabling printf()-like formatter.
75 *#* Revision 1.3 2004/07/18 22:00:15 bernie
76 *#* Reorganize configuration parameters to match DevLib's convention.
78 *#* Revision 1.2 2004/06/03 11:27:09 bernie
79 *#* Add dual-license information.
81 *#* Revision 1.1 2004/05/23 15:43:16 bernie
82 *#* Import mware modules.
87 #include <compiler.h> /* progmem macros */
88 #include <config.h> /* CONFIG_ macros */
89 #include <debug.h> /* ASSERT */
91 #ifndef CONFIG_PRINTF_N_FORMATTER
92 /*! Enable arcane %n formatter */
93 #define CONFIG_PRINTF_N_FORMATTER 0
96 #ifndef CONFIG_PRINTF_OCTAL_FORMATTER
97 /*! Enable %o formatter */
98 #define CONFIG_PRINTF_OCTAL_FORMATTER 0
101 // True if we must keep a count of the number of characters we print
102 #define CONFIG_PRINTF_COUNT_CHARS (CONFIG_PRINTF_RETURN_COUNT || CONFIG_PRINTF_N_FORMATTER)
106 #if CONFIG_PRINTF > PRINTF_NOFLOAT
108 #endif /* CONFIG_PRINTF > PRINTF_NOFLOAT */
110 #if CONFIG_PRINTF > PRINTF_NOFLOAT
111 /*bernie: save some memory, who cares about floats with lots of decimals? */
112 /*#define FRMWRI_BUFSIZE 134*/
113 #error 134 is too much, the code must be fixed to have a lower precision limit
116 * Conservative estimate. Should be (probably) 12 (which is the size necessary
117 * to represent (2^32-1) in octal plus the sign bit.
119 #define FRMWRI_BUFSIZE 16
122 #ifndef MEM_ATTRIBUTE
123 #define MEM_ATTRIBUTE
126 #if CONFIG_PRINTF > PRINTF_NOMODIFIERS
127 #define IS_SHORT (h_modifier || (sizeof(int) == 2 && !l_modifier))
129 #define IS_SHORT (sizeof(int) == 2)
130 #endif /* CONFIG_PRINTF > PRINTF_NOMODIFIERS */
133 #if CONFIG_PRINTF > PRINTF_NOFLOAT
135 static char *float_conversion(MEM_ATTRIBUTE long double value,
136 MEM_ATTRIBUTE short nr_of_digits,
137 MEM_ATTRIBUTE char *buf,
138 MEM_ATTRIBUTE char format_flag,
139 MEM_ATTRIBUTE char g_flag,
140 MEM_ATTRIBUTE bool alternate_flag)
142 MEM_ATTRIBUTE char *cp;
143 MEM_ATTRIBUTE char *buf_pointer;
144 MEM_ATTRIBUTE short n, i, dec_point_pos, integral_10_log;
151 while (value >= 1e11) /* To speed up things a bit */
154 integral_10_log += 10;
162 else if (value) /* Not just 0.0 */
164 while (value <= 1e-10) /* To speed up things a bit */
167 integral_10_log -= 10;
177 if (integral_10_log < nr_of_digits && integral_10_log >= -4)
180 nr_of_digits -= integral_10_log;
184 /* %#G - No removal of trailing zeros */
187 /* %G - Removal of trailing zeros */
188 alternate_flag = true;
198 /* Less than one... */
199 if (integral_10_log < 0)
201 *buf_pointer++ = '0';
202 if ((n = nr_of_digits) || alternate_flag)
203 *buf_pointer++ = '.';
205 while (--i > integral_10_log && nr_of_digits)
207 *buf_pointer++ = '0';
210 if (integral_10_log < (-n - 1))
211 /* Nothing more to do */
217 dec_point_pos = - integral_10_log;
222 while (i <= nr_of_digits )
224 value -= (long double)(n = (short)value); /* n=Digit value=Remainder */
225 value *= 10; /* Prepare for next shot */
226 *buf_pointer++ = n + '0';
227 if ( ! i++ && (nr_of_digits || alternate_flag))
228 *buf_pointer++ = '.';
231 /* Rounding possible */
235 cp = buf_pointer - 1;
240 if ( (*cp += n) == ('9' + 1) )
248 } while (cp-- > buf);
257 if (*(cp - 1) == '.')
282 /* %G - Remove trailing zeros */
285 while (*(buf_pointer - 1) == '0')
287 if (*(buf_pointer - 1) == '.')
294 *buf_pointer++ = format_flag;
295 if (integral_10_log < 0)
297 *buf_pointer++ = '-';
298 integral_10_log = -integral_10_log;
301 *buf_pointer++ = '+';
307 *buf_pointer++ = (integral_10_log % 10) + '0';
308 integral_10_log /= 10;
309 } while ( integral_10_log || n < 2 );
310 for ( i = n ; n > 0 ; n-- )
311 *(buf_pointer - 11 - i + n) = *(buf_pointer - n);
314 return (buf_pointer);
317 #endif /* CONFIG_PRINTF > PRINTF_NOFLOAT */
320 * This routine forms the core and entry of the formatter.
322 * The conversion performed conforms to the ANSI specification for "printf".
325 PGM_FUNC(_formatted_write)(const char * PGM_ATTR format,
326 void put_one_char(char, void *),
327 void *secret_pointer,
330 #if CONFIG_PRINTF > PRINTF_REDUCED
331 MEM_ATTRIBUTE static char bad_conversion[] = "???";
332 MEM_ATTRIBUTE static char null_pointer[] = "<NULL>";
334 MEM_ATTRIBUTE int precision;
336 #if CONFIG_PRINTF_COUNT_CHARS
337 MEM_ATTRIBUTE int nr_of_chars;
339 MEM_ATTRIBUTE int field_width;
340 MEM_ATTRIBUTE char format_flag;
341 enum PLUS_SPACE_FLAGS {
342 PSF_NONE, PSF_PLUS, PSF_MINUS
346 #if CONFIG_PRINTF_OCTAL_FORMATTER
351 MEM_ATTRIBUTE enum PLUS_SPACE_FLAGS plus_space_flag : 2;
352 #if CONFIG_PRINTF_OCTAL_FORMATTER
353 MEM_ATTRIBUTE enum DIV_FACTOR div_factor : 2;
355 MEM_ATTRIBUTE enum DIV_FACTOR div_factor : 1;
357 MEM_ATTRIBUTE bool left_adjust : 1;
358 MEM_ATTRIBUTE bool l_L_modifier : 1;
359 MEM_ATTRIBUTE bool h_modifier : 1;
360 MEM_ATTRIBUTE bool alternate_flag : 1;
361 MEM_ATTRIBUTE bool nonzero_value : 1;
362 MEM_ATTRIBUTE bool zeropad : 1;
364 MEM_ATTRIBUTE unsigned long ulong;
366 #if CONFIG_PRINTF > PRINTF_NOFLOAT
367 MEM_ATTRIBUTE long double fvalue;
370 MEM_ATTRIBUTE char *buf_pointer;
371 MEM_ATTRIBUTE char *ptr;
372 MEM_ATTRIBUTE const char *hex;
373 MEM_ATTRIBUTE char buf[FRMWRI_BUFSIZE];
375 #if CONFIG_PRINTF_COUNT_CHARS
378 for (;;) /* Until full format string read */
380 while ((format_flag = PGM_READ_CHAR(format++)) != '%') /* Until '%' or '\0' */
383 #if CONFIG_PRINTF_RETURN_COUNT
384 return (nr_of_chars);
388 put_one_char(format_flag, secret_pointer);
389 #if CONFIG_PRINTF_COUNT_CHARS
393 if (PGM_READ_CHAR(format) == '%') /* %% prints as % */
396 put_one_char('%', secret_pointer);
397 #if CONFIG_PRINTF_COUNT_CHARS
403 flags.left_adjust = false;
404 flags.alternate_flag = false;
405 flags.plus_space_flag = PSF_NONE;
406 flags.zeropad = false;
407 ptr = buf_pointer = &buf[0];
408 hex = "0123456789ABCDEF";
410 /* check for leading '-', '+', ' ','#' or '0' flags */
413 switch (PGM_READ_CHAR(format))
416 if (flags.plus_space_flag)
419 flags.plus_space_flag = PSF_PLUS;
422 flags.left_adjust = true;
425 flags.alternate_flag = true;
428 flags.zeropad = true;
436 /* Optional field width (may be '*') */
437 if (PGM_READ_CHAR(format) == '*')
439 field_width = va_arg(ap, int);
442 field_width = -field_width;
443 flags.left_adjust = true;
450 while (PGM_READ_CHAR(format) >= '0' && PGM_READ_CHAR(format) <= '9')
451 field_width = field_width * 10 + (PGM_READ_CHAR(format++) - '0');
454 if (flags.left_adjust)
455 flags.zeropad = false;
457 /* Optional precision (or '*') */
458 if (PGM_READ_CHAR(format) == '.')
460 if (PGM_READ_CHAR(++format) == '*')
462 precision = va_arg(ap, int);
468 while (PGM_READ_CHAR(format) >= '0' && PGM_READ_CHAR(format) <= '9')
469 precision = precision * 10 + (PGM_READ_CHAR(format++) - '0');
475 /* At this point, "left_adjust" is nonzero if there was
476 * a sign, "zeropad" is 1 if there was a leading zero
477 * and 0 otherwise, "field_width" and "precision"
478 * contain numbers corresponding to the digit strings
479 * before and after the decimal point, respectively,
480 * and "plus_space_flag" is either 0 (no flag) or
481 * contains a plus or space character. If there was no
482 * decimal point, "precision" will be -1.
485 flags.l_L_modifier = false;
486 flags.h_modifier = false;
488 /* Optional 'l','L' r 'h' modifier? */
489 switch (PGM_READ_CHAR(format))
493 flags.l_L_modifier = true;
497 flags.h_modifier = true;
503 * At exit from the following switch, we will emit
504 * the characters starting at "buf_pointer" and
507 switch (format_flag = PGM_READ_CHAR(format++))
509 #if CONFIG_PRINTF_N_FORMATTER
511 if (sizeof(short) != sizeof(int))
513 if (sizeof(int) != sizeof(long))
516 *va_arg(ap, short *) = nr_of_chars;
517 else if (flags.l_L_modifier)
518 *va_arg(ap, long *) = nr_of_chars;
520 *va_arg(ap, int *) = nr_of_chars;
525 *va_arg(ap, short *) = nr_of_chars;
527 *va_arg(ap, int *) = nr_of_chars;
532 if (flags.l_L_modifier)
533 *va_arg(ap, long *) = nr_of_chars;
535 *va_arg(ap, int *) = nr_of_chars;
540 buf[0] = va_arg(ap, int);
545 if ( !(buf_pointer = va_arg(ap, char *)) )
546 buf_pointer = null_pointer;
549 for (n=0; *buf_pointer++ && n < precision; n++)
555 #if CONFIG_PRINTF_OCTAL_FORMATTER
557 if (flags.alternate_flag && !precision)
561 hex = "0123456789abcdef";
565 if (format_flag == 'p')
566 #if defined(__AVR__) || defined(__I196__) /* 16bit pointers */
567 ulong = (unsigned long)(unsigned short)va_arg(ap, char *);
568 #else /* 32bit pointers */
569 ulong = (unsigned long)va_arg(ap, char *);
570 #endif /* 32bit pointers */
571 else if (sizeof(short) == sizeof(int))
572 ulong = flags.l_L_modifier ?
573 va_arg(ap, unsigned long) : (unsigned long)va_arg(ap, unsigned int);
575 ulong = flags.h_modifier ?
576 (unsigned long)(unsigned short) va_arg(ap, int)
577 : (unsigned long)va_arg(ap, int);
579 #if CONFIG_PRINTF_OCTAL_FORMATTER
580 (format_flag == 'o') ? DIV_OCT :
582 (format_flag == 'u') ? DIV_DEC : DIV_HEX;
583 flags.plus_space_flag = PSF_NONE;
584 goto INTEGRAL_CONVERSION;
588 if (sizeof(short) == sizeof(long))
590 if ( (long)(ulong = va_arg(ap, unsigned long)) < 0)
592 flags.plus_space_flag = PSF_MINUS;
593 ulong = (unsigned long)(-((signed long)ulong));
596 else if (sizeof(short) == sizeof(int))
598 if ( (long)(ulong = flags.l_L_modifier ?
599 va_arg(ap,unsigned long) : (unsigned long)va_arg(ap,int)) < 0)
601 flags.plus_space_flag = PSF_MINUS;
602 ulong = (unsigned long)(-((signed long)ulong));
607 if ( (signed long)(ulong = (unsigned long) (flags.h_modifier ?
608 (short) va_arg(ap, int) : va_arg(ap,int))) < 0)
610 flags.plus_space_flag = PSF_MINUS;
611 ulong = (unsigned long)(-((signed long)ulong));
614 flags.div_factor = DIV_DEC;
616 /* Now convert to digits */
618 ptr = buf_pointer = &buf[FRMWRI_BUFSIZE - 1];
619 flags.nonzero_value = (ulong != 0);
621 /* No char if zero and zero precision */
622 if (precision != 0 || flags.nonzero_value)
624 switch (flags.div_factor)
628 *--buf_pointer = hex[ulong % 10];
634 *--buf_pointer = hex[ulong % 16];
637 #if CONFIG_PRINTF_OCTAL_FORMATTER
640 *--buf_pointer = hex[ulong % 8];
647 /* "precision" takes precedence */
650 precision = field_width - (flags.plus_space_flag != PSF_NONE);
651 while (precision > (int)(ptr - buf_pointer))
652 *--buf_pointer = '0';
654 if (flags.alternate_flag && flags.nonzero_value)
656 if (format_flag == 'x' || format_flag == 'X')
658 *--buf_pointer = format_flag;
659 *--buf_pointer = '0';
661 #if CONFIG_PRINTF_OCTAL_FORMATTER
662 else if ((format_flag == 'o') && (*buf_pointer != '0'))
664 *--buf_pointer = '0';
668 ASSERT(buf_pointer >= buf);
671 #if CONFIG_PRINTF > PRINTF_NOFLOAT
680 goto FLOATING_CONVERSION;
691 if (sizeof(double) != sizeof(long double))
693 if ( (fvalue = flags.l_L_modifier ?
694 va_arg(ap,long double) : va_arg(ap,double)) < 0)
696 flags.plus_space_flag = PSF_MINUS;
700 else if ( (fvalue = va_arg(ap,long double)) < 0)
702 flags.plus_space_flag = PSF_MINUS;
705 ptr = float_conversion (fvalue,
707 buf_pointer += field_width,
710 flags.alternate_flag);
713 precision = field_width - (flags.plus_space_flag != PSF_NONE);
714 while (precision > ptr - buf_pointer)
715 *--buf_pointer = '0';
719 #else /* CONFIG_PRINTF <= PRINTF_NOFLOAT */
725 ptr = buf_pointer = bad_conversion;
729 #endif /* CONFIG_PRINTF <= PRINTF_NOFLOAT */
731 case '\0': /* Really bad place to find NUL in */
735 /* Undefined conversion! */
736 ptr = buf_pointer = bad_conversion;
743 * This part emittes the formatted string to "put_one_char".
746 /* If field_width == 0 then nothing should be written. */
747 precision = ptr - buf_pointer;
749 if ( precision > field_width)
755 n = field_width - precision - (flags.plus_space_flag != PSF_NONE);
758 /* emit any leading pad characters */
759 if (!flags.left_adjust)
762 put_one_char(' ', secret_pointer);
763 #if CONFIG_PRINTF_COUNT_CHARS
768 /* emit flag characters (if any) */
769 if (flags.plus_space_flag)
771 put_one_char(flags.plus_space_flag == PSF_PLUS ? '+' : '-', secret_pointer);
772 #if CONFIG_PRINTF_COUNT_CHARS
777 /* emit the string itself */
778 while (--precision >= 0)
780 put_one_char(*buf_pointer++, secret_pointer);
781 #if CONFIG_PRINTF_COUNT_CHARS
786 /* emit trailing space characters */
787 if (flags.left_adjust)
790 put_one_char(' ', secret_pointer);
791 #if CONFIG_PRINTF_COUNT_CHARS
797 #else /* PRINTF_REDUCED starts here */
799 #if CONFIG_PRINTF > PRINTF_NOMODIFIERS
800 char l_modifier, h_modifier;
801 unsigned long u_val, div_val;
803 unsigned int u_val, div_val;
804 #endif /* CONFIG_PRINTF > PRINTF_NOMODIFIERS */
807 unsigned int nr_of_chars, base;
812 for (;;) /* Until full format string read */
814 while ((format_flag = PGM_READ_CHAR(format++)) != '%') /* Until '%' or '\0' */
817 return (nr_of_chars);
818 put_one_char(format_flag, secret_pointer);
822 #if CONFIG_PRINTF > PRINTF_NOMODIFIERS
823 /*=================================*/
824 /* Optional 'l' or 'h' modifiers ? */
825 /*=================================*/
826 l_modifier = h_modifier = 0;
827 switch (PGM_READ_CHAR(format))
839 #endif /* CONFIG_PRINTF > PRINTF_NOMODIFIERS */
841 switch (format_flag = PGM_READ_CHAR(format++))
844 format_flag = va_arg(ap, int);
846 put_one_char(format_flag, secret_pointer);
851 ptr = va_arg(ap, char *);
852 while ((format_flag = *ptr++))
854 put_one_char(format_flag, secret_pointer);
864 div_val = 0x40000000;
865 goto CONVERSION_LOOP;
872 div_val = 1000000000;
873 goto CONVERSION_LOOP;
881 div_val = 0x10000000;
884 #if CONFIG_PRINTF > PRINTF_NOMODIFIERS
886 u_val = (format_flag == 'd') ?
887 (short)va_arg(ap, int) : (unsigned short)va_arg(ap, int);
889 u_val = va_arg(ap, long);
891 u_val = (format_flag == 'd') ?
892 va_arg(ap,int) : va_arg(ap,unsigned int);
893 #else /* CONFIG_PRINTF > PRINTF_NOMODIFIERS */
894 u_val = va_arg(ap,int);
895 #endif /* CONFIG_PRINTF > PRINTF_NOMODIFIERS */
896 if (format_flag == 'd')
898 if (((int)u_val) < 0)
901 put_one_char('-', secret_pointer);
905 while (div_val > 1 && div_val > u_val)
911 outChar = (u_val / div_val) + '0';
914 if (format_flag == 'x')
915 outChar += 'a'-'9'-1;
917 outChar += 'A'-'9'-1;
919 put_one_char(outChar, secret_pointer);
926 } /* end switch(format_flag...) */
928 #endif /* CONFIG_PRINTF > PRINTF_REDUCED */
931 #endif /* CONFIG_PRINTF */