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 * The reduced version of formatter is suitable when program size is critical
35 * rather than formatting power. This routine uses less than 20 bytes of
36 * stack space which makes it practical even in systems with less than 256
39 * The only formatting specifiers supported by the reduced formatter are:
41 * %% %c %s %d %o %x %X and %hd %ho %hx %hX %ld %lo %lx %lX
44 * It means that real variables are not supported as well as field
45 * width and precision arguments.
50 * Revision 1.5 2004/07/29 22:57:09 bernie
51 * Switch to new-style config handling.
53 * Revision 1.4 2004/07/21 00:20:20 bernie
54 * Allow completely disabling printf()-like formatter.
56 * Revision 1.3 2004/07/18 22:00:15 bernie
57 * Reorganize configuration parameters to match DevLib's convention.
59 * Revision 1.2 2004/06/03 11:27:09 bernie
60 * Add dual-license information.
62 * Revision 1.1 2004/05/23 15:43:16 bernie
63 * Import mware modules.
68 #include <compiler.h> /* progmem macros */
69 #include <config.h> /* CONFIG_ macros */
73 #if CONFIG_PRINTF > PRINTF_NOFLOAT
75 #endif /* CONFIG_PRINTF > PRINTF_NOFLOAT */
77 #ifndef FRMWRI_BUFSIZE
78 /*bernie: save some memory, who cares about floats with lots of decimals? */
79 /*#define FRMWRI_BUFSIZE 134*/
80 #define FRMWRI_BUFSIZE 32
87 #if CONFIG_PRINTF > PRINTF_NOMODIFIERS
88 #define IS_SHORT (h_modifier || (sizeof(int) == 2 && !l_modifier))
90 #define IS_SHORT (sizeof(int) == 2)
91 #endif /* CONFIG_PRINTF > PRINTF_NOMODIFIERS */
94 #if CONFIG_PRINTF > PRINTF_NOFLOAT
96 static char *float_conversion(MEM_ATTRIBUTE long double value,
97 MEM_ATTRIBUTE short nr_of_digits,
98 MEM_ATTRIBUTE char *buf,
99 MEM_ATTRIBUTE char format_flag,
100 MEM_ATTRIBUTE char g_flag,
101 MEM_ATTRIBUTE char alternate_flag)
103 MEM_ATTRIBUTE char *cp;
104 MEM_ATTRIBUTE char *buf_pointer;
105 MEM_ATTRIBUTE short n, i, dec_point_pos, integral_10_log;
112 while (value >= 1e11) /* To speed up things a bit */
115 integral_10_log += 10;
123 else if (value) /* Not just 0.0 */
125 while (value <= 1e-10) /* To speed up things a bit */
128 integral_10_log -= 10;
138 if (integral_10_log < nr_of_digits && integral_10_log >= -4)
141 nr_of_digits -= integral_10_log;
145 /* %#G - No removal of trailing zeros */
148 /* %G - Removal of trailing zeros */
159 /* Less than one... */
160 if (integral_10_log < 0)
162 *buf_pointer++ = '0';
163 if ((n = nr_of_digits) || alternate_flag)
164 *buf_pointer++ = '.';
166 while (--i > integral_10_log && nr_of_digits)
168 *buf_pointer++ = '0';
171 if (integral_10_log < (-n - 1))
172 /* Nothing more to do */
178 dec_point_pos = - integral_10_log;
183 while (i <= nr_of_digits )
185 value -= (long double)(n = (short)value); /* n=Digit value=Remainder */
186 value *= 10; /* Prepare for next shot */
187 *buf_pointer++ = n + '0';
188 if ( ! i++ && (nr_of_digits || alternate_flag))
189 *buf_pointer++ = '.';
192 /* Rounding possible */
196 cp = buf_pointer - 1;
201 if ( (*cp += n) == ('9' + 1) )
209 } while (cp-- > buf);
218 if (*(cp - 1) == '.')
243 /* %G - Remove trailing zeros */
246 while (*(buf_pointer - 1) == '0')
248 if (*(buf_pointer - 1) == '.')
255 *buf_pointer++ = format_flag;
256 if (integral_10_log < 0)
258 *buf_pointer++ = '-';
259 integral_10_log = -integral_10_log;
262 *buf_pointer++ = '+';
268 *buf_pointer++ = (integral_10_log % 10) + '0';
269 integral_10_log /= 10;
270 } while ( integral_10_log || n < 2 );
271 for ( i = n ; n > 0 ; n-- )
272 *(buf_pointer - 11 - i + n) = *(buf_pointer - n);
275 return (buf_pointer);
278 #endif /* CONFIG_PRINTF > PRINTF_NOFLOAT */
281 * This routine forms the core and entry of the formatter.
283 * The conversion performed conforms to the ANSI specification for "printf".
286 PGM_FUNC(_formatted_write)(const char * PGM_ATTR format,
287 void put_one_char(char, void *),
288 void *secret_pointer,
291 #if CONFIG_PRINTF > PRINTF_REDUCED
292 MEM_ATTRIBUTE static char bad_conversion[] = "???";
293 MEM_ATTRIBUTE static char null_pointer[] = "<NULL>";
295 MEM_ATTRIBUTE char format_flag;
296 MEM_ATTRIBUTE int precision;
298 MEM_ATTRIBUTE int field_width, nr_of_chars;
299 MEM_ATTRIBUTE char plus_space_flag, left_adjust, l_L_modifier;
300 MEM_ATTRIBUTE char h_modifier, alternate_flag;
301 MEM_ATTRIBUTE char nonzero_value;
302 MEM_ATTRIBUTE unsigned long ulong, div_factor;
304 #if CONFIG_PRINTF > PRINTF_NOFLOAT
305 MEM_ATTRIBUTE long double fvalue;
308 MEM_ATTRIBUTE char *buf_pointer;
309 MEM_ATTRIBUTE char *ptr;
310 MEM_ATTRIBUTE const char *hex;
311 MEM_ATTRIBUTE char zeropad;
312 MEM_ATTRIBUTE char buf[FRMWRI_BUFSIZE];
315 for (;;) /* Until full format string read */
317 while ((format_flag = PGM_READ_CHAR(format++)) != '%') /* Until '%' or '\0' */
320 return (nr_of_chars);
321 put_one_char(format_flag, secret_pointer);
324 if (PGM_READ_CHAR(format) == '%') /* %% prints as % */
327 put_one_char('%', secret_pointer);
332 plus_space_flag = left_adjust = alternate_flag = zeropad = 0;
333 ptr = buf_pointer = &buf[0];
334 hex = "0123456789ABCDEF";
336 /* check for leading '-', '+', ' ','#' or '0' flags */
339 switch (PGM_READ_CHAR(format))
345 plus_space_flag = PGM_READ_CHAR(format);
362 /* Optional field width (may be '*') */
363 if (PGM_READ_CHAR(format) == '*')
365 field_width = va_arg(ap, int);
368 field_width = -field_width;
376 while (PGM_READ_CHAR(format) >= '0' && PGM_READ_CHAR(format) <= '9')
377 field_width = field_width * 10 + (PGM_READ_CHAR(format++) - '0');
383 /* Optional precision (or '*') */
384 if (PGM_READ_CHAR(format) == '.')
386 if (PGM_READ_CHAR(++format) == '*')
388 precision = va_arg(ap, int);
394 while (PGM_READ_CHAR(format) >= '0' && PGM_READ_CHAR(format) <= '9')
395 precision = precision * 10 + (PGM_READ_CHAR(format++) - '0');
401 /* At this point, "left_adjust" is nonzero if there was
402 * a sign, "zeropad" is 1 if there was a leading zero
403 * and 0 otherwise, "field_width" and "precision"
404 * contain numbers corresponding to the digit strings
405 * before and after the decimal point, respectively,
406 * and "plus_space_flag" is either 0 (no flag) or
407 * contains a plus or space character. If there was no
408 * decimal point, "precision" will be -1.
411 l_L_modifier = h_modifier = 0;
413 /* Optional 'l','L' r 'h' modifier? */
414 switch (PGM_READ_CHAR(format))
428 * At exit from the following switch, we will emit
429 * the characters starting at "buf_pointer" and
432 switch (format_flag = PGM_READ_CHAR(format++))
436 if (sizeof(short) != sizeof(int))
438 if (sizeof(int) != sizeof(long))
441 *va_arg(ap, short *) = nr_of_chars;
442 else if (l_L_modifier)
443 *va_arg(ap, long *) = nr_of_chars;
445 *va_arg(ap, int *) = nr_of_chars;
450 *va_arg(ap, short *) = nr_of_chars;
452 *va_arg(ap, int *) = nr_of_chars;
458 *va_arg(ap, long *) = nr_of_chars;
460 *va_arg(ap, int *) = nr_of_chars;
465 buf[0] = va_arg(ap, int);
470 if ( !(buf_pointer = va_arg(ap, char *)) )
471 buf_pointer = null_pointer;
474 for (n=0; *buf_pointer++ && n < precision; n++)
481 if (alternate_flag && !precision)
484 hex = "0123456789abcdef";
488 if (format_flag == 'p')
489 #if defined(__AVR__) || defined(__I196__) /* 16bit pointers */
490 ulong = (unsigned long)(unsigned short)va_arg(ap, char *);
491 #else /* 32bit pointers */
492 ulong = (unsigned long)va_arg(ap, char *);
493 #endif /* 32bit pointers */
494 else if (sizeof(short) == sizeof(int))
495 ulong = l_L_modifier ?
496 va_arg(ap, unsigned long) : (unsigned long)va_arg(ap, unsigned int);
499 (unsigned long)(unsigned short) va_arg(ap, int)
500 : (unsigned long)va_arg(ap, int);
501 div_factor = (format_flag == 'o') ?
502 8 : (format_flag == 'u') ? 10 : 16;
504 goto INTEGRAL_CONVERSION;
508 if (sizeof(short) == sizeof(long))
510 if ( (long)(ulong = va_arg(ap, unsigned long)) < 0)
512 plus_space_flag = '-';
513 ulong = (unsigned long)(-((signed long)ulong));
516 else if (sizeof(short) == sizeof(int))
518 if ( (long)(ulong = l_L_modifier ?
519 va_arg(ap,unsigned long) : (unsigned long)va_arg(ap,int)) < 0)
521 plus_space_flag = '-';
522 ulong = (unsigned long)(-((signed long)ulong));
527 if ( (signed long)(ulong = (unsigned long) (h_modifier ?
528 (short) va_arg(ap, int) : va_arg(ap,int))) < 0)
530 plus_space_flag = '-';
531 ulong = (unsigned long)(-((signed long)ulong));
536 /* Now convert to digits */
538 ptr = buf_pointer = &buf[FRMWRI_BUFSIZE - 1];
539 nonzero_value = (ulong != 0);
541 /* No char if zero and zero precision */
542 if (precision != 0 || nonzero_value)
544 *--buf_pointer = hex[ulong % div_factor];
545 while (ulong /= div_factor);
547 /* "precision" takes precedence */
550 precision = field_width - (plus_space_flag != 0);
551 while (precision > (int)(ptr - buf_pointer))
552 *--buf_pointer = '0';
554 if (alternate_flag && nonzero_value)
556 if (format_flag == 'x' || format_flag == 'X')
558 *--buf_pointer = format_flag;
559 *--buf_pointer = '0';
561 else if ((format_flag == 'o') && (*buf_pointer != '0'))
563 *--buf_pointer = '0';
568 #if CONFIG_PRINTF > PRINTF_NOFLOAT
577 goto FLOATING_CONVERSION;
588 if (sizeof(double) != sizeof(long double))
590 if ( (fvalue = l_L_modifier ?
591 va_arg(ap,long double) : va_arg(ap,double)) < 0)
593 plus_space_flag = '-';
597 else if ( (fvalue = va_arg(ap,long double)) < 0)
599 plus_space_flag = '-';
602 ptr = float_conversion (fvalue,
604 buf_pointer += field_width,
610 precision = field_width - (plus_space_flag != 0);
611 while (precision > ptr - buf_pointer)
612 *--buf_pointer = '0';
616 #else /* CONFIG_PRINTF <= PRINTF_NOFLOAT */
622 ptr = buf_pointer = bad_conversion;
626 #endif /* CONFIG_PRINTF <= PRINTF_NOFLOAT */
628 case '\0': /* Really bad place to find NUL in */
632 /* Undefined conversion! */
633 ptr = buf_pointer = bad_conversion;
640 * This part emittes the formatted string to "put_one_char".
643 /* If field_width == 0 then nothing should be written. */
644 precision = ptr - buf_pointer;
646 if ( precision > field_width)
652 n = field_width - precision - (plus_space_flag != 0);
655 /* emit any leading pad characters */
659 put_one_char(' ', secret_pointer);
663 /* emit flag characters (if any) */
666 put_one_char(plus_space_flag, secret_pointer);
670 /* emit the string itself */
671 while (--precision >= 0)
673 put_one_char(*buf_pointer++, secret_pointer);
677 /* emit trailing space characters */
681 put_one_char(' ', secret_pointer);
686 #else /* PRINTF_REDUCED starts here */
688 #if CONFIG_PRINTF > PRINTF_NOMODIFIERS
689 char l_modifier, h_modifier;
690 unsigned long u_val, div_val;
692 unsigned int u_val, div_val;
693 #endif /* CONFIG_PRINTF > PRINTF_NOMODIFIERS */
696 unsigned int nr_of_chars, base;
701 for (;;) /* Until full format string read */
703 while ((format_flag = PGM_READ_CHAR(format++)) != '%') /* Until '%' or '\0' */
706 return (nr_of_chars);
707 put_one_char(format_flag, secret_pointer);
711 #if CONFIG_PRINTF > PRINTF_NOMODIFIERS
712 /*=================================*/
713 /* Optional 'l' or 'h' modifiers ? */
714 /*=================================*/
715 l_modifier = h_modifier = 0;
716 switch (PGM_READ_CHAR(format))
728 #endif /* CONFIG_PRINTF > PRINTF_NOMODIFIERS */
730 switch (format_flag = PGM_READ_CHAR(format++))
733 format_flag = va_arg(ap, int);
735 put_one_char(format_flag, secret_pointer);
740 ptr = va_arg(ap, char *);
741 while ((format_flag = *ptr++))
743 put_one_char(format_flag, secret_pointer);
753 div_val = 0x40000000;
754 goto CONVERSION_LOOP;
761 div_val = 1000000000;
762 goto CONVERSION_LOOP;
770 div_val = 0x10000000;
773 #if CONFIG_PRINTF > PRINTF_NOMODIFIERS
775 u_val = (format_flag == 'd') ?
776 (short)va_arg(ap, int) : (unsigned short)va_arg(ap, int);
778 u_val = va_arg(ap, long);
780 u_val = (format_flag == 'd') ?
781 va_arg(ap,int) : va_arg(ap,unsigned int);
782 #else /* CONFIG_PRINTF > PRINTF_NOMODIFIERS */
783 u_val = va_arg(ap,int);
784 #endif /* CONFIG_PRINTF > PRINTF_NOMODIFIERS */
785 if (format_flag == 'd')
787 if (((int)u_val) < 0)
790 put_one_char('-', secret_pointer);
794 while (div_val > 1 && div_val > u_val)
800 outChar = (u_val / div_val) + '0';
803 if (format_flag == 'x')
804 outChar += 'a'-'9'-1;
806 outChar += 'A'-'9'-1;
808 put_one_char(outChar, secret_pointer);
815 } /* end switch(format_flag...) */
817 #endif /* CONFIG_PRINTF > PRINTF_REDUCED */
820 #endif /* CONFIG_PRINTF */