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.3 2004/07/18 22:00:15 bernie
46 * Reorganize configuration parameters to match DevLib's convention.
48 * Revision 1.2 2004/06/03 11:27:09 bernie
49 * Add dual-license information.
51 * Revision 1.1 2004/05/23 15:43:16 bernie
52 * Import mware modules.
57 #include <compiler.h> /* progmem macros */
58 #include <config.h> /* CONFIG_ macros */
60 #ifndef CONFIG_PRINTF_NOFLOAT
62 #endif /* CONFIG_PRINTF_NOFLOAT */
64 #ifndef FRMWRI_BUFSIZE
65 /*bernie: save some memory, who cares about floats with lots of decimals? */
66 /*#define FRMWRI_BUFSIZE 134*/
67 #define FRMWRI_BUFSIZE 32
74 #ifndef CONFIG_PRINTF_NOMODIFIERS
75 #define IS_SHORT (h_modifier || (sizeof(int) == 2 && !l_modifier))
77 #define IS_SHORT (sizeof(int) == 2)
81 #ifndef CONFIG_PRINTF_NOFLOAT
83 static char *float_conversion(MEM_ATTRIBUTE long double value,
84 MEM_ATTRIBUTE short nr_of_digits,
85 MEM_ATTRIBUTE char *buf,
86 MEM_ATTRIBUTE char format_flag,
87 MEM_ATTRIBUTE char g_flag,
88 MEM_ATTRIBUTE char alternate_flag)
90 MEM_ATTRIBUTE char *cp;
91 MEM_ATTRIBUTE char *buf_pointer;
92 MEM_ATTRIBUTE short n, i, dec_point_pos, integral_10_log;
99 while (value >= 1e11) /* To speed up things a bit */
102 integral_10_log += 10;
110 else if (value) /* Not just 0.0 */
112 while (value <= 1e-10) /* To speed up things a bit */
115 integral_10_log -= 10;
125 if (integral_10_log < nr_of_digits && integral_10_log >= -4)
128 nr_of_digits -= integral_10_log;
132 /* %#G - No removal of trailing zeros */
135 /* %G - Removal of trailing zeros */
146 /* Less than one... */
147 if (integral_10_log < 0)
149 *buf_pointer++ = '0';
150 if ((n = nr_of_digits) || alternate_flag)
151 *buf_pointer++ = '.';
153 while (--i > integral_10_log && nr_of_digits)
155 *buf_pointer++ = '0';
158 if (integral_10_log < (-n - 1))
159 /* Nothing more to do */
165 dec_point_pos = - integral_10_log;
170 while (i <= nr_of_digits )
172 value -= (long double)(n = (short)value); /* n=Digit value=Remainder */
173 value *= 10; /* Prepare for next shot */
174 *buf_pointer++ = n + '0';
175 if ( ! i++ && (nr_of_digits || alternate_flag))
176 *buf_pointer++ = '.';
179 /* Rounding possible */
183 cp = buf_pointer - 1;
188 if ( (*cp += n) == ('9' + 1) )
196 } while (cp-- > buf);
205 if (*(cp - 1) == '.')
230 /* %G - Remove trailing zeros */
233 while (*(buf_pointer - 1) == '0')
235 if (*(buf_pointer - 1) == '.')
242 *buf_pointer++ = format_flag;
243 if (integral_10_log < 0)
245 *buf_pointer++ = '-';
246 integral_10_log = -integral_10_log;
249 *buf_pointer++ = '+';
255 *buf_pointer++ = (integral_10_log % 10) + '0';
256 integral_10_log /= 10;
257 } while ( integral_10_log || n < 2 );
258 for ( i = n ; n > 0 ; n-- )
259 *(buf_pointer - 11 - i + n) = *(buf_pointer - n);
262 return (buf_pointer);
265 #endif /* !CONFIG_PRINTF_NOFLOAT */
268 * This routine forms the core and entry of the formatter.
270 * The conversion performed conforms to the ANSI specification for "printf".
273 PGM_FUNC(_formatted_write)(const char * PGM_ATTR format,
274 void put_one_char(char, void *),
275 void *secret_pointer,
278 MEM_ATTRIBUTE static char bad_conversion[] = "???";
279 MEM_ATTRIBUTE static char null_pointer[] = "<NULL>";
281 #ifndef CONFIG_PRINTF_REDUCED
282 MEM_ATTRIBUTE char format_flag;
283 MEM_ATTRIBUTE int precision;
285 MEM_ATTRIBUTE int field_width, nr_of_chars;
286 MEM_ATTRIBUTE char plus_space_flag, left_adjust, l_L_modifier;
287 MEM_ATTRIBUTE char h_modifier, alternate_flag;
288 MEM_ATTRIBUTE char nonzero_value;
289 MEM_ATTRIBUTE unsigned long ulong, div_factor;
291 #ifndef CONFIG_PRINTF_NOFLOAT
292 MEM_ATTRIBUTE long double fvalue;
295 MEM_ATTRIBUTE char *buf_pointer;
296 MEM_ATTRIBUTE char *ptr;
297 MEM_ATTRIBUTE const char *hex;
298 MEM_ATTRIBUTE char zeropad;
299 MEM_ATTRIBUTE char buf[FRMWRI_BUFSIZE];
302 for (;;) /* Until full format string read */
304 while ((format_flag = PGM_READ_CHAR(format++)) != '%') /* Until '%' or '\0' */
307 return (nr_of_chars);
308 put_one_char (format_flag, secret_pointer);
311 if (PGM_READ_CHAR(format) == '%') /* %% prints as % */
314 put_one_char('%', secret_pointer);
319 plus_space_flag = left_adjust = alternate_flag = zeropad = 0;
320 ptr = buf_pointer = &buf[0];
321 hex = "0123456789ABCDEF";
323 /* check for leading '-', '+', ' ','#' or '0' flags */
326 switch (PGM_READ_CHAR(format))
332 plus_space_flag = PGM_READ_CHAR(format);
349 /* Optional field width (may be '*') */
350 if (PGM_READ_CHAR(format) == '*')
352 field_width = va_arg(ap, int);
355 field_width = -field_width;
363 while (PGM_READ_CHAR(format) >= '0' && PGM_READ_CHAR(format) <= '9')
364 field_width = field_width * 10 + (PGM_READ_CHAR(format++) - '0');
370 /* Optional precision (or '*') */
371 if (PGM_READ_CHAR(format) == '.')
373 if (PGM_READ_CHAR(++format) == '*')
375 precision = va_arg(ap, int);
381 while (PGM_READ_CHAR(format) >= '0' && PGM_READ_CHAR(format) <= '9')
382 precision = precision * 10 + (PGM_READ_CHAR(format++) - '0');
388 /* At this point, "left_adjust" is nonzero if there was
389 * a sign, "zeropad" is 1 if there was a leading zero
390 * and 0 otherwise, "field_width" and "precision"
391 * contain numbers corresponding to the digit strings
392 * before and after the decimal point, respectively,
393 * and "plus_space_flag" is either 0 (no flag) or
394 * contains a plus or space character. If there was no
395 * decimal point, "precision" will be -1.
398 l_L_modifier = h_modifier = 0;
400 /* Optional 'l','L' r 'h' modifier? */
401 switch (PGM_READ_CHAR(format))
415 * At exit from the following switch, we will emit
416 * the characters starting at "buf_pointer" and
419 switch (format_flag = PGM_READ_CHAR(format++))
423 if (sizeof(short) != sizeof(int))
425 if (sizeof(int) != sizeof(long))
428 *va_arg(ap, short *) = nr_of_chars;
429 else if (l_L_modifier)
430 *va_arg(ap, long *) = nr_of_chars;
432 *va_arg(ap, int *) = nr_of_chars;
437 *va_arg(ap, short *) = nr_of_chars;
439 *va_arg(ap, int *) = nr_of_chars;
445 *va_arg(ap, long *) = nr_of_chars;
447 *va_arg(ap, int *) = nr_of_chars;
452 buf[0] = va_arg(ap, int);
457 if ( !(buf_pointer = va_arg(ap, char *)) )
458 buf_pointer = null_pointer;
461 for (n=0; *buf_pointer++ && n < precision; n++)
468 if (alternate_flag && !precision)
471 hex = "0123456789abcdef";
475 if (format_flag == 'p')
476 #if defined(__AVR__) || defined(__I196__) /* 16bit pointers */
477 ulong = (unsigned long)(unsigned short)va_arg(ap, char *);
478 #else /* 32bit pointers */
479 ulong = (unsigned long)va_arg(ap, char *);
480 #endif /* 32bit pointers */
481 else if (sizeof(short) == sizeof(int))
482 ulong = l_L_modifier ?
483 va_arg(ap, unsigned long) : (unsigned long)va_arg(ap, unsigned int);
486 (unsigned long)(unsigned short) va_arg(ap, int)
487 : (unsigned long)va_arg(ap, int);
488 div_factor = (format_flag == 'o') ?
489 8 : (format_flag == 'u') ? 10 : 16;
491 goto INTEGRAL_CONVERSION;
495 if (sizeof(short) == sizeof(long))
497 if ( (long)(ulong = va_arg(ap, unsigned long)) < 0)
499 plus_space_flag = '-';
500 ulong = (unsigned long)(-((signed long)ulong));
503 else if (sizeof(short) == sizeof(int))
505 if ( (long)(ulong = l_L_modifier ?
506 va_arg(ap,unsigned long) : (unsigned long)va_arg(ap,int)) < 0)
508 plus_space_flag = '-';
509 ulong = (unsigned long)(-((signed long)ulong));
514 if ( (signed long)(ulong = (unsigned long) (h_modifier ?
515 (short) va_arg(ap, int) : va_arg(ap,int))) < 0)
517 plus_space_flag = '-';
518 ulong = (unsigned long)(-((signed long)ulong));
523 /* Now convert to digits */
525 ptr = buf_pointer = &buf[FRMWRI_BUFSIZE - 1];
526 nonzero_value = (ulong != 0);
528 /* No char if zero and zero precision */
529 if (precision != 0 || nonzero_value)
531 *--buf_pointer = hex[ulong % div_factor];
532 while (ulong /= div_factor);
534 /* "precision" takes precedence */
537 precision = field_width - (plus_space_flag != 0);
538 while (precision > (int)(ptr - buf_pointer))
539 *--buf_pointer = '0';
541 if (alternate_flag && nonzero_value)
543 if (format_flag == 'x' || format_flag == 'X')
545 *--buf_pointer = format_flag;
546 *--buf_pointer = '0';
548 else if ((format_flag == 'o') && (*buf_pointer != '0'))
550 *--buf_pointer = '0';
555 #ifndef CONFIG_PRINTF_NOFLOAT
564 goto FLOATING_CONVERSION;
575 if (sizeof(double) != sizeof(long double))
577 if ( (fvalue = l_L_modifier ?
578 va_arg(ap,long double) : va_arg(ap,double)) < 0)
580 plus_space_flag = '-';
584 else if ( (fvalue = va_arg(ap,long double)) < 0)
586 plus_space_flag = '-';
589 ptr = float_conversion (fvalue,
591 buf_pointer += field_width,
597 precision = field_width - (plus_space_flag != 0);
598 while (precision > ptr - buf_pointer)
599 *--buf_pointer = '0';
603 #else /* CONFIG_PRINTF_NOFLOAT */
609 ptr = buf_pointer = bad_conversion;
613 #endif /* CONFIG_PRINTF_NOFLOAT */
615 case '\0': /* Really bad place to find NUL in */
619 /* Undefined conversion! */
620 ptr = buf_pointer = bad_conversion;
627 * This part emittes the formatted string to "put_one_char".
630 /* If field_width == 0 then nothing should be written. */
631 precision = ptr - buf_pointer;
633 if ( precision > field_width)
639 n = field_width - precision - (plus_space_flag != 0);
642 /* emit any leading pad characters */
646 put_one_char(' ', secret_pointer);
650 /* emit flag characters (if any) */
653 put_one_char(plus_space_flag, secret_pointer);
657 /* emit the string itself */
658 while (--precision >= 0)
660 put_one_char(*buf_pointer++, secret_pointer);
664 /* emit trailing space characters */
668 put_one_char(' ', secret_pointer);
673 #else /* CONFIG_PRINTF_REDUCED starts here */
675 #ifndef CONFIG_PRINTF_NOMODIFIERS
676 char l_modifier, h_modifier;
677 unsigned long u_val, div_val;
679 unsigned int u_val, div_val;
680 #endif /* CONFIG_PRINTF_NOMODIFIERS */
682 char l_modifier, h_modifier;
684 unsigned int nr_of_chars, base;
689 for (;;) /* Until full format string read */
691 while ((format_flag = PGM_READ_CHAR(format++)) != '%') /* Until '%' or '\0' */
694 return (nr_of_chars);
695 put_one_char (format_flag, secret_pointer);
699 #ifndef CONFIG_PRINTF_NOMODIFIERS
700 /*=================================*/
701 /* Optional 'l' or 'h' modifiers ? */
702 /*=================================*/
703 l_modifier = h_modifier = 0;
704 switch (PGM_READ_CHAR(format))
716 #endif /* !CONFIG_PRINTF_NOMODIFIERS */
718 switch (format_flag = PGM_READ_CHAR(format++))
721 format_flag = va_arg(ap, int);
723 put_one_char (format_flag, secret_pointer);
728 ptr = va_arg(ap, char *);
729 while (format_flag = *ptr++)
731 put_one_char (format_flag, secret_pointer);
741 div_val = 0x40000000;
742 goto CONVERSION_LOOP;
749 div_val = 1000000000;
750 goto CONVERSION_LOOP;
758 div_val = 0x10000000;
761 #ifndef CONFIG_PRINTF_NOMODIFIERS
763 u_val = (format_flag == 'd') ?
764 (short)va_arg(ap, int) : (unsigned short)va_arg(ap, int);
766 u_val = va_arg(ap, long);
768 u_val = (format_flag == 'd') ?
769 va_arg(ap,int) : va_arg(ap,unsigned int);
770 #else /* CONFIG_PRINTF_NOMODIFIERS */
771 u_val = va_arg(ap,int);
772 #endif /* CONFIG_PRINTF_NOMODIFIERS */
773 if (format_flag == 'd')
775 if (((int)u_val) < 0)
778 put_one_char ('-', secret_pointer);
782 while (div_val > 1 && div_val > u_val)
788 outChar = (u_val / div_val) + '0';
790 if (format_flag == 'x')
791 outChar += 'a'-'9'-1;
793 outChar += 'A'-'9'-1;
794 put_one_char (outChar, secret_pointer);
801 } /* end switch(format_flag...) */
803 #endif /* CONFIG_PRINTF_REDUCED */