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.10 2004/10/26 09:01:35 bernie
59 *#* Revision 1.9 2004/09/14 21:06:23 bernie
62 *#* Revision 1.8 2004/08/25 14:12:09 rasky
63 *#* Aggiornato il comment block dei log RCS
65 *#* Revision 1.7 2004/08/04 15:53:47 rasky
66 *#* Nuove opzioni di configurazione per formatted_write e ridotto maggiormente l'utilizzo dellos tack
68 *#* Revision 1.6 2004/07/30 14:34:10 rasky
69 *#* Vari fix per documentazione e commenti
70 *#* Aggiunte PP_CATn e STATIC_ASSERT
72 *#* Revision 1.5 2004/07/29 22:57:09 bernie
73 *#* Switch to new-style config handling.
75 *#* Revision 1.4 2004/07/21 00:20:20 bernie
76 *#* Allow completely disabling printf()-like formatter.
78 *#* Revision 1.3 2004/07/18 22:00:15 bernie
79 *#* Reorganize configuration parameters to match DevLib's convention.
81 *#* Revision 1.2 2004/06/03 11:27:09 bernie
82 *#* Add dual-license information.
84 *#* Revision 1.1 2004/05/23 15:43:16 bernie
85 *#* Import mware modules.
90 #include <compiler.h> /* progmem macros */
91 #include <config.h> /* CONFIG_ macros */
92 #include <debug.h> /* ASSERT */
94 #ifndef CONFIG_PRINTF_N_FORMATTER
95 /*! Enable arcane %n formatter */
96 #define CONFIG_PRINTF_N_FORMATTER 0
99 #ifndef CONFIG_PRINTF_OCTAL_FORMATTER
100 /*! Enable %o formatter */
101 #define CONFIG_PRINTF_OCTAL_FORMATTER 0
104 // True if we must keep a count of the number of characters we print
105 #define CONFIG_PRINTF_COUNT_CHARS (CONFIG_PRINTF_RETURN_COUNT || CONFIG_PRINTF_N_FORMATTER)
109 #if CONFIG_PRINTF > PRINTF_NOFLOAT
111 #endif /* CONFIG_PRINTF > PRINTF_NOFLOAT */
113 #if CONFIG_PRINTF > PRINTF_NOFLOAT
114 /*bernie: save some memory, who cares about floats with lots of decimals? */
115 /*#define FRMWRI_BUFSIZE 134*/
116 #error 134 is too much, the code must be fixed to have a lower precision limit
119 * Conservative estimate. Should be (probably) 12 (which is the size necessary
120 * to represent (2^32-1) in octal plus the sign bit.
122 #define FRMWRI_BUFSIZE 16
125 #ifndef MEM_ATTRIBUTE
126 #define MEM_ATTRIBUTE
129 #if CONFIG_PRINTF > PRINTF_NOMODIFIERS
130 #define IS_SHORT (h_modifier || (sizeof(int) == 2 && !l_modifier))
132 #define IS_SHORT (sizeof(int) == 2)
133 #endif /* CONFIG_PRINTF > PRINTF_NOMODIFIERS */
136 #if CONFIG_PRINTF > PRINTF_NOFLOAT
138 static char *float_conversion(MEM_ATTRIBUTE long double value,
139 MEM_ATTRIBUTE short nr_of_digits,
140 MEM_ATTRIBUTE char *buf,
141 MEM_ATTRIBUTE char format_flag,
142 MEM_ATTRIBUTE char g_flag,
143 MEM_ATTRIBUTE bool alternate_flag)
145 MEM_ATTRIBUTE char *cp;
146 MEM_ATTRIBUTE char *buf_pointer;
147 MEM_ATTRIBUTE short n, i, dec_point_pos, integral_10_log;
154 while (value >= 1e11) /* To speed up things a bit */
157 integral_10_log += 10;
165 else if (value) /* Not just 0.0 */
167 while (value <= 1e-10) /* To speed up things a bit */
170 integral_10_log -= 10;
180 if (integral_10_log < nr_of_digits && integral_10_log >= -4)
183 nr_of_digits -= integral_10_log;
187 /* %#G - No removal of trailing zeros */
190 /* %G - Removal of trailing zeros */
191 alternate_flag = true;
201 /* Less than one... */
202 if (integral_10_log < 0)
204 *buf_pointer++ = '0';
205 if ((n = nr_of_digits) || alternate_flag)
206 *buf_pointer++ = '.';
208 while (--i > integral_10_log && nr_of_digits)
210 *buf_pointer++ = '0';
213 if (integral_10_log < (-n - 1))
214 /* Nothing more to do */
220 dec_point_pos = - integral_10_log;
225 while (i <= nr_of_digits )
227 value -= (long double)(n = (short)value); /* n=Digit value=Remainder */
228 value *= 10; /* Prepare for next shot */
229 *buf_pointer++ = n + '0';
230 if ( ! i++ && (nr_of_digits || alternate_flag))
231 *buf_pointer++ = '.';
234 /* Rounding possible */
238 cp = buf_pointer - 1;
243 if ( (*cp += n) == ('9' + 1) )
251 } while (cp-- > buf);
260 if (*(cp - 1) == '.')
285 /* %G - Remove trailing zeros */
288 while (*(buf_pointer - 1) == '0')
290 if (*(buf_pointer - 1) == '.')
297 *buf_pointer++ = format_flag;
298 if (integral_10_log < 0)
300 *buf_pointer++ = '-';
301 integral_10_log = -integral_10_log;
304 *buf_pointer++ = '+';
310 *buf_pointer++ = (integral_10_log % 10) + '0';
311 integral_10_log /= 10;
312 } while ( integral_10_log || n < 2 );
313 for ( i = n ; n > 0 ; n-- )
314 *(buf_pointer - 11 - i + n) = *(buf_pointer - n);
317 return (buf_pointer);
320 #endif /* CONFIG_PRINTF > PRINTF_NOFLOAT */
323 * This routine forms the core and entry of the formatter.
325 * The conversion performed conforms to the ANSI specification for "printf".
328 PGM_FUNC(_formatted_write)(const char * PGM_ATTR format,
329 void put_one_char(char, void *),
330 void *secret_pointer,
333 #if CONFIG_PRINTF > PRINTF_REDUCED
334 MEM_ATTRIBUTE static char bad_conversion[] = "???";
335 MEM_ATTRIBUTE static char null_pointer[] = "<NULL>";
337 MEM_ATTRIBUTE int precision;
339 #if CONFIG_PRINTF_COUNT_CHARS
340 MEM_ATTRIBUTE int nr_of_chars;
342 MEM_ATTRIBUTE int field_width;
343 MEM_ATTRIBUTE char format_flag;
344 enum PLUS_SPACE_FLAGS {
345 PSF_NONE, PSF_PLUS, PSF_MINUS
349 #if CONFIG_PRINTF_OCTAL_FORMATTER
354 MEM_ATTRIBUTE enum PLUS_SPACE_FLAGS plus_space_flag : 2;
355 #if CONFIG_PRINTF_OCTAL_FORMATTER
356 MEM_ATTRIBUTE enum DIV_FACTOR div_factor : 2;
358 MEM_ATTRIBUTE enum DIV_FACTOR div_factor : 1;
360 MEM_ATTRIBUTE bool left_adjust : 1;
361 MEM_ATTRIBUTE bool l_L_modifier : 1;
362 MEM_ATTRIBUTE bool h_modifier : 1;
363 MEM_ATTRIBUTE bool alternate_flag : 1;
364 MEM_ATTRIBUTE bool nonzero_value : 1;
365 MEM_ATTRIBUTE bool zeropad : 1;
367 MEM_ATTRIBUTE unsigned long ulong;
369 #if CONFIG_PRINTF > PRINTF_NOFLOAT
370 MEM_ATTRIBUTE long double fvalue;
373 MEM_ATTRIBUTE char *buf_pointer;
374 MEM_ATTRIBUTE char *ptr;
375 MEM_ATTRIBUTE const char *hex;
376 MEM_ATTRIBUTE char buf[FRMWRI_BUFSIZE];
378 #if CONFIG_PRINTF_COUNT_CHARS
381 for (;;) /* Until full format string read */
383 while ((format_flag = PGM_READ_CHAR(format++)) != '%') /* Until '%' or '\0' */
386 #if CONFIG_PRINTF_RETURN_COUNT
387 return (nr_of_chars);
391 put_one_char(format_flag, secret_pointer);
392 #if CONFIG_PRINTF_COUNT_CHARS
396 if (PGM_READ_CHAR(format) == '%') /* %% prints as % */
399 put_one_char('%', secret_pointer);
400 #if CONFIG_PRINTF_COUNT_CHARS
406 flags.left_adjust = false;
407 flags.alternate_flag = false;
408 flags.plus_space_flag = PSF_NONE;
409 flags.zeropad = false;
410 ptr = buf_pointer = &buf[0];
411 hex = "0123456789ABCDEF";
413 /* check for leading '-', '+', ' ','#' or '0' flags */
416 switch (PGM_READ_CHAR(format))
419 if (flags.plus_space_flag)
422 flags.plus_space_flag = PSF_PLUS;
425 flags.left_adjust = true;
428 flags.alternate_flag = true;
431 flags.zeropad = true;
439 /* Optional field width (may be '*') */
440 if (PGM_READ_CHAR(format) == '*')
442 field_width = va_arg(ap, int);
445 field_width = -field_width;
446 flags.left_adjust = true;
453 while (PGM_READ_CHAR(format) >= '0' && PGM_READ_CHAR(format) <= '9')
454 field_width = field_width * 10 + (PGM_READ_CHAR(format++) - '0');
457 if (flags.left_adjust)
458 flags.zeropad = false;
460 /* Optional precision (or '*') */
461 if (PGM_READ_CHAR(format) == '.')
463 if (PGM_READ_CHAR(++format) == '*')
465 precision = va_arg(ap, int);
471 while (PGM_READ_CHAR(format) >= '0' && PGM_READ_CHAR(format) <= '9')
472 precision = precision * 10 + (PGM_READ_CHAR(format++) - '0');
478 /* At this point, "left_adjust" is nonzero if there was
479 * a sign, "zeropad" is 1 if there was a leading zero
480 * and 0 otherwise, "field_width" and "precision"
481 * contain numbers corresponding to the digit strings
482 * before and after the decimal point, respectively,
483 * and "plus_space_flag" is either 0 (no flag) or
484 * contains a plus or space character. If there was no
485 * decimal point, "precision" will be -1.
488 flags.l_L_modifier = false;
489 flags.h_modifier = false;
491 /* Optional 'l','L' r 'h' modifier? */
492 switch (PGM_READ_CHAR(format))
496 flags.l_L_modifier = true;
500 flags.h_modifier = true;
506 * At exit from the following switch, we will emit
507 * the characters starting at "buf_pointer" and
510 switch (format_flag = PGM_READ_CHAR(format++))
512 #if CONFIG_PRINTF_N_FORMATTER
514 if (sizeof(short) != sizeof(int))
516 if (sizeof(int) != sizeof(long))
519 *va_arg(ap, short *) = nr_of_chars;
520 else if (flags.l_L_modifier)
521 *va_arg(ap, long *) = nr_of_chars;
523 *va_arg(ap, int *) = nr_of_chars;
528 *va_arg(ap, short *) = nr_of_chars;
530 *va_arg(ap, int *) = nr_of_chars;
535 if (flags.l_L_modifier)
536 *va_arg(ap, long *) = nr_of_chars;
538 *va_arg(ap, int *) = nr_of_chars;
543 buf[0] = va_arg(ap, int);
548 if ( !(buf_pointer = va_arg(ap, char *)) )
549 buf_pointer = null_pointer;
552 for (n=0; *buf_pointer++ && n < precision; n++)
558 #if CONFIG_PRINTF_OCTAL_FORMATTER
560 if (flags.alternate_flag && !precision)
564 hex = "0123456789abcdef";
568 if (format_flag == 'p')
569 #if defined(__AVR__) || defined(__I196__) /* 16bit pointers */
570 ulong = (unsigned long)(unsigned short)va_arg(ap, char *);
571 #else /* 32bit pointers */
572 ulong = (unsigned long)va_arg(ap, char *);
573 #endif /* 32bit pointers */
574 else if (sizeof(short) == sizeof(int))
575 ulong = flags.l_L_modifier ?
576 va_arg(ap, unsigned long) : (unsigned long)va_arg(ap, unsigned int);
578 ulong = flags.h_modifier ?
579 (unsigned long)(unsigned short) va_arg(ap, int)
580 : (unsigned long)va_arg(ap, int);
582 #if CONFIG_PRINTF_OCTAL_FORMATTER
583 (format_flag == 'o') ? DIV_OCT :
585 (format_flag == 'u') ? DIV_DEC : DIV_HEX;
586 flags.plus_space_flag = PSF_NONE;
587 goto INTEGRAL_CONVERSION;
591 if (sizeof(short) == sizeof(long))
593 if ( (long)(ulong = va_arg(ap, unsigned long)) < 0)
595 flags.plus_space_flag = PSF_MINUS;
596 ulong = (unsigned long)(-((signed long)ulong));
599 else if (sizeof(short) == sizeof(int))
601 if ( (long)(ulong = flags.l_L_modifier ?
602 va_arg(ap,unsigned long) : (unsigned long)va_arg(ap,int)) < 0)
604 flags.plus_space_flag = PSF_MINUS;
605 ulong = (unsigned long)(-((signed long)ulong));
610 if ( (signed long)(ulong = (unsigned long) (flags.h_modifier ?
611 (short) va_arg(ap, int) : va_arg(ap,int))) < 0)
613 flags.plus_space_flag = PSF_MINUS;
614 ulong = (unsigned long)(-((signed long)ulong));
617 flags.div_factor = DIV_DEC;
619 /* Now convert to digits */
621 ptr = buf_pointer = &buf[FRMWRI_BUFSIZE - 1];
622 flags.nonzero_value = (ulong != 0);
624 /* No char if zero and zero precision */
625 if (precision != 0 || flags.nonzero_value)
627 switch (flags.div_factor)
631 *--buf_pointer = hex[ulong % 10];
637 *--buf_pointer = hex[ulong % 16];
640 #if CONFIG_PRINTF_OCTAL_FORMATTER
643 *--buf_pointer = hex[ulong % 8];
650 /* "precision" takes precedence */
653 precision = field_width - (flags.plus_space_flag != PSF_NONE);
654 while (precision > (int)(ptr - buf_pointer))
655 *--buf_pointer = '0';
657 if (flags.alternate_flag && flags.nonzero_value)
659 if (format_flag == 'x' || format_flag == 'X')
661 *--buf_pointer = format_flag;
662 *--buf_pointer = '0';
664 #if CONFIG_PRINTF_OCTAL_FORMATTER
665 else if ((format_flag == 'o') && (*buf_pointer != '0'))
667 *--buf_pointer = '0';
671 ASSERT(buf_pointer >= buf);
674 #if CONFIG_PRINTF > PRINTF_NOFLOAT
683 goto FLOATING_CONVERSION;
694 if (sizeof(double) != sizeof(long double))
696 if ( (fvalue = flags.l_L_modifier ?
697 va_arg(ap,long double) : va_arg(ap,double)) < 0)
699 flags.plus_space_flag = PSF_MINUS;
703 else if ( (fvalue = va_arg(ap,long double)) < 0)
705 flags.plus_space_flag = PSF_MINUS;
708 ptr = float_conversion (fvalue,
710 buf_pointer += field_width,
713 flags.alternate_flag);
716 precision = field_width - (flags.plus_space_flag != PSF_NONE);
717 while (precision > ptr - buf_pointer)
718 *--buf_pointer = '0';
722 #else /* CONFIG_PRINTF <= PRINTF_NOFLOAT */
728 ptr = buf_pointer = bad_conversion;
732 #endif /* CONFIG_PRINTF <= PRINTF_NOFLOAT */
734 case '\0': /* Really bad place to find NUL in */
738 /* Undefined conversion! */
739 ptr = buf_pointer = bad_conversion;
746 * This part emittes the formatted string to "put_one_char".
749 /* If field_width == 0 then nothing should be written. */
750 precision = ptr - buf_pointer;
752 if ( precision > field_width)
758 n = field_width - precision - (flags.plus_space_flag != PSF_NONE);
761 /* emit any leading pad characters */
762 if (!flags.left_adjust)
765 put_one_char(' ', secret_pointer);
766 #if CONFIG_PRINTF_COUNT_CHARS
771 /* emit flag characters (if any) */
772 if (flags.plus_space_flag)
774 put_one_char(flags.plus_space_flag == PSF_PLUS ? '+' : '-', secret_pointer);
775 #if CONFIG_PRINTF_COUNT_CHARS
780 /* emit the string itself */
781 while (--precision >= 0)
783 put_one_char(*buf_pointer++, secret_pointer);
784 #if CONFIG_PRINTF_COUNT_CHARS
789 /* emit trailing space characters */
790 if (flags.left_adjust)
793 put_one_char(' ', secret_pointer);
794 #if CONFIG_PRINTF_COUNT_CHARS
800 #else /* PRINTF_REDUCED starts here */
802 #if CONFIG_PRINTF > PRINTF_NOMODIFIERS
803 char l_modifier, h_modifier;
804 unsigned long u_val, div_val;
806 unsigned int u_val, div_val;
807 #endif /* CONFIG_PRINTF > PRINTF_NOMODIFIERS */
810 unsigned int nr_of_chars, base;
815 for (;;) /* Until full format string read */
817 while ((format_flag = PGM_READ_CHAR(format++)) != '%') /* Until '%' or '\0' */
820 return (nr_of_chars);
821 put_one_char(format_flag, secret_pointer);
825 #if CONFIG_PRINTF > PRINTF_NOMODIFIERS
826 /*=================================*/
827 /* Optional 'l' or 'h' modifiers ? */
828 /*=================================*/
829 l_modifier = h_modifier = 0;
830 switch (PGM_READ_CHAR(format))
842 #endif /* CONFIG_PRINTF > PRINTF_NOMODIFIERS */
844 switch (format_flag = PGM_READ_CHAR(format++))
847 format_flag = va_arg(ap, int);
849 put_one_char(format_flag, secret_pointer);
854 ptr = va_arg(ap, char *);
855 while ((format_flag = *ptr++))
857 put_one_char(format_flag, secret_pointer);
867 div_val = 0x40000000;
868 goto CONVERSION_LOOP;
875 div_val = 1000000000;
876 goto CONVERSION_LOOP;
884 div_val = 0x10000000;
887 #if CONFIG_PRINTF > PRINTF_NOMODIFIERS
889 u_val = (format_flag == 'd') ?
890 (short)va_arg(ap, int) : (unsigned short)va_arg(ap, int);
892 u_val = va_arg(ap, long);
894 u_val = (format_flag == 'd') ?
895 va_arg(ap,int) : va_arg(ap,unsigned int);
896 #else /* CONFIG_PRINTF > PRINTF_NOMODIFIERS */
897 u_val = va_arg(ap,int);
898 #endif /* CONFIG_PRINTF > PRINTF_NOMODIFIERS */
899 if (format_flag == 'd')
901 if (((int)u_val) < 0)
904 put_one_char('-', secret_pointer);
908 while (div_val > 1 && div_val > u_val)
914 outChar = (u_val / div_val) + '0';
917 if (format_flag == 'x')
918 outChar += 'a'-'9'-1;
920 outChar += 'A'-'9'-1;
922 put_one_char(outChar, secret_pointer);
929 } /* end switch(format_flag...) */
931 #endif /* CONFIG_PRINTF > PRINTF_REDUCED */
934 #endif /* CONFIG_PRINTF */