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 chann-
14 * els 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.
20 * -DFLOAT_SUPPORT Full ANSI formatter
21 * -DNO_FLOATS Full ANSI except floats
22 * -DREDUCED_SUPPORT Reduced 'int' type of converter
24 * The reduced version of formatter is suitable when program size is critical
25 * rather than formatting power. This routine uses less than 20 bytes of
26 * stack space which makes it practical even in systems with less than 256
29 * The only formatting specifiers supported by the reduced formatter are:
31 * %% %c %s %d %o %x %X and %hd %ho %hx %hX %ld %lo %lx %lX
34 * It means that real variables are not supported as well as field
35 * width and precision arguments.
37 * The last eight (h and l modifiers) can easily be removed by
38 * removing the line "#define MODIFIERS_IN_REDUCED".
43 * Revision 1.2 2004/06/03 11:27:09 bernie
44 * Add dual-license information.
46 * Revision 1.1 2004/05/23 15:43:16 bernie
47 * Import mware modules.
54 #undef REDUCED_SUPPORT
58 #ifndef REDUCED_SUPPORT
59 #error -DFLOAT_SUPPORT, -DNO_FLOATS or -DREDUCED_SUPPORT missing
64 /* Make it easy for customers to disable h and l
65 modifiers in REDUCED_SUPPORT by removing the next #define */
66 #define MODIFIERS_IN_REDUCED
69 #include "compiler.h" /* progmem macros */
72 #ifndef FRMWRI_BUFSIZE
73 /*bernie: save some memory, who cares about floats with lots of decimals? */
74 /*#define FRMWRI_BUFSIZE 134*/
75 #define FRMWRI_BUFSIZE 32
82 #ifdef MODIFIERS_IN_REDUCED
83 #define IS_SHORT (h_modifier || (sizeof(int) == 2 && !l_modifier))
85 #define IS_SHORT (sizeof(int) == 2)
91 static char *float_conversion(MEM_ATTRIBUTE long double value,
92 MEM_ATTRIBUTE short nr_of_digits,
93 MEM_ATTRIBUTE char *buf,
94 MEM_ATTRIBUTE char format_flag,
95 MEM_ATTRIBUTE char g_flag,
96 MEM_ATTRIBUTE char alternate_flag)
98 MEM_ATTRIBUTE char *cp;
99 MEM_ATTRIBUTE char *buf_pointer;
100 MEM_ATTRIBUTE short n, i, dec_point_pos, integral_10_log;
107 while (value >= 1e11) /* To speed up things a bit */
110 integral_10_log += 10;
118 else if (value) /* Not just 0.0 */
120 while (value <= 1e-10) /* To speed up things a bit */
123 integral_10_log -= 10;
133 if (integral_10_log < nr_of_digits && integral_10_log >= -4)
136 nr_of_digits -= integral_10_log;
141 /* %#G - No removal of trailing zeros */
146 /* %G - Removal of trailing zeros */
158 /* Less than one... */
159 if (integral_10_log < 0)
161 *buf_pointer++ = '0';
162 if ((n = nr_of_digits) || alternate_flag)
164 *buf_pointer++ = '.';
167 while (--i > integral_10_log && nr_of_digits)
169 *buf_pointer++ = '0';
172 if (integral_10_log < (-n - 1))
174 /* Nothing more to do */
181 dec_point_pos = - integral_10_log;
186 while (i <= nr_of_digits )
188 value -= (long double)(n = (short)value); /* n=Digit value=Remainder */
189 value *= 10; /* Prepare for next shot */
190 *buf_pointer++ = n + '0';
191 if ( ! i++ && (nr_of_digits || alternate_flag))
193 *buf_pointer++ = '.';
197 /* Rounding possible */
201 cp = buf_pointer - 1;
206 if ( (*cp += n) == ('9' + 1) )
216 } while (cp-- > buf);
225 if (*(cp - 1) == '.')
252 /* %G - Remove trailing zeros */
255 while (*(buf_pointer - 1) == '0')
259 if (*(buf_pointer - 1) == '.')
268 *buf_pointer++ = format_flag;
269 if (integral_10_log < 0)
271 *buf_pointer++ = '-';
272 integral_10_log = -integral_10_log;
276 *buf_pointer++ = '+';
283 *buf_pointer++ = (integral_10_log % 10) + '0';
284 integral_10_log /= 10;
285 } while ( integral_10_log || n < 2 );
286 for ( i = n ; n > 0 ; n-- )
287 *(buf_pointer - 11 - i + n) = *(buf_pointer - n);
290 return (buf_pointer);
293 #endif /* FLOAT_SUPPORT */
296 * This routine forms the core and entry of the formatter.
298 * The conversion performed conforms to the ANSI specification for "printf".
301 PGM_FUNC(_formatted_write)(const char * PGM_ATTR format,
302 void put_one_char(char, void *),
303 void *secret_pointer,
306 MEM_ATTRIBUTE static char bad_conversion[] = "???";
307 MEM_ATTRIBUTE static char null_pointer[] = "<NULL>";
309 #ifndef REDUCED_SUPPORT
310 MEM_ATTRIBUTE char format_flag;
311 MEM_ATTRIBUTE int precision;
313 MEM_ATTRIBUTE int field_width, nr_of_chars;
314 MEM_ATTRIBUTE char plus_space_flag, left_adjust, l_L_modifier;
315 MEM_ATTRIBUTE char h_modifier, alternate_flag;
316 MEM_ATTRIBUTE char nonzero_value;
317 MEM_ATTRIBUTE unsigned long ulong, div_factor;
320 MEM_ATTRIBUTE long double fvalue;
323 MEM_ATTRIBUTE char *buf_pointer;
324 MEM_ATTRIBUTE char *ptr;
325 MEM_ATTRIBUTE const char *hex;
326 MEM_ATTRIBUTE char zeropad;
327 MEM_ATTRIBUTE char buf[FRMWRI_BUFSIZE];
330 for (;;) /* Until full format string read */
332 while ((format_flag = PGM_READ_CHAR(format++)) != '%') /* Until '%' or '\0' */
335 return (nr_of_chars);
336 put_one_char (format_flag, secret_pointer);
339 if (PGM_READ_CHAR(format) == '%') /* %% prints as % */
342 put_one_char('%', secret_pointer);
347 plus_space_flag = left_adjust = alternate_flag = zeropad = 0;
348 ptr = buf_pointer = &buf[0];
349 hex = "0123456789ABCDEF";
351 /* check for leading '-', '+', ' ','#' or '0' flags */
354 switch (PGM_READ_CHAR(format))
360 plus_space_flag = PGM_READ_CHAR(format);
377 /* Optional field width (may be '*') */
378 if (PGM_READ_CHAR(format) == '*')
380 field_width = va_arg(ap, int);
383 field_width = -field_width;
391 while (PGM_READ_CHAR(format) >= '0' && PGM_READ_CHAR(format) <= '9')
392 field_width = field_width * 10 + (PGM_READ_CHAR(format++) - '0');
398 /* Optional precision (or '*') */
399 if (PGM_READ_CHAR(format) == '.')
401 if (PGM_READ_CHAR(++format) == '*')
403 precision = va_arg(ap, int);
409 while (PGM_READ_CHAR(format) >= '0' && PGM_READ_CHAR(format) <= '9')
410 precision = precision * 10 + (PGM_READ_CHAR(format++) - '0');
416 /* At this point, "left_adjust" is nonzero if there was
417 * a sign, "zeropad" is 1 if there was a leading zero
418 * and 0 otherwise, "field_width" and "precision"
419 * contain numbers corresponding to the digit strings
420 * before and after the decimal point, respectively,
421 * and "plus_space_flag" is either 0 (no flag) or
422 * contains a plus or space character. If there was no
423 * decimal point, "precision" will be -1.
426 l_L_modifier = h_modifier = 0;
428 /* Optional 'l','L' r 'h' modifier? */
429 switch (PGM_READ_CHAR(format))
443 * At exit from the following switch, we will emit
444 * the characters starting at "buf_pointer" and
447 switch (format_flag = PGM_READ_CHAR(format++))
451 if (sizeof(short) != sizeof(int))
453 if (sizeof(int) != sizeof(long))
456 *va_arg(ap, short *) = nr_of_chars;
457 else if (l_L_modifier)
458 *va_arg(ap, long *) = nr_of_chars;
460 *va_arg(ap, int *) = nr_of_chars;
465 *va_arg(ap, short *) = nr_of_chars;
467 *va_arg(ap, int *) = nr_of_chars;
473 *va_arg(ap, long *) = nr_of_chars;
475 *va_arg(ap, int *) = nr_of_chars;
480 buf[0] = va_arg(ap, int);
485 if ( !(buf_pointer = va_arg(ap, char *)) )
486 buf_pointer = null_pointer;
489 for (n=0; *buf_pointer++ && n < precision; n++)
496 if (alternate_flag && !precision)
499 hex = "0123456789abcdef";
503 if (format_flag == 'p')
504 #if defined(__AVR__) || defined(__I196__) /* 16bit pointers */
505 ulong = (unsigned long)(unsigned short)va_arg(ap, char *);
506 #else /* 32bit pointers */
507 ulong = (unsigned long)va_arg(ap, char *);
508 #endif /* 32bit pointers */
509 else if (sizeof(short) == sizeof(int))
510 ulong = l_L_modifier ?
511 va_arg(ap, unsigned long) : (unsigned long)va_arg(ap, unsigned int);
514 (unsigned long)(unsigned short) va_arg(ap, int)
515 : (unsigned long)va_arg(ap, int);
516 div_factor = (format_flag == 'o') ?
517 8 : (format_flag == 'u') ? 10 : 16;
519 goto INTEGRAL_CONVERSION;
523 if (sizeof(short) == sizeof(long))
525 if ( (long)(ulong = va_arg(ap, unsigned long)) < 0)
527 plus_space_flag = '-';
528 ulong = (unsigned long)(-((signed long)ulong));
531 else if (sizeof(short) == sizeof(int))
533 if ( (long)(ulong = l_L_modifier ?
534 va_arg(ap,unsigned long) : (unsigned long)va_arg(ap,int)) < 0)
536 plus_space_flag = '-';
537 ulong = (unsigned long)(-((signed long)ulong));
542 if ( (signed long)(ulong = (unsigned long) (h_modifier ?
543 (short) va_arg(ap, int) : va_arg(ap,int))) < 0)
545 plus_space_flag = '-';
546 ulong = (unsigned long)(-((signed long)ulong));
551 /* Now convert to digits */
553 ptr = buf_pointer = &buf[FRMWRI_BUFSIZE - 1];
554 nonzero_value = (ulong != 0);
556 /* No char if zero and zero precision */
557 if (precision != 0 || nonzero_value)
559 *--buf_pointer = hex[ulong % div_factor];
560 while (ulong /= div_factor);
562 /* "precision" takes precedence */
565 precision = field_width - (plus_space_flag != 0);
566 while (precision > (int)(ptr - buf_pointer))
567 *--buf_pointer = '0';
569 if (alternate_flag && nonzero_value)
571 if (format_flag == 'x' || format_flag == 'X')
573 *--buf_pointer = format_flag;
574 *--buf_pointer = '0';
576 else if ((format_flag == 'o') && (*buf_pointer != '0'))
578 *--buf_pointer = '0';
592 goto FLOATING_CONVERSION;
603 if (sizeof(double) != sizeof(long double))
605 if ( (fvalue = l_L_modifier ?
606 va_arg(ap,long double) : va_arg(ap,double)) < 0)
608 plus_space_flag = '-';
612 else if ( (fvalue = va_arg(ap,long double)) < 0)
614 plus_space_flag = '-';
617 ptr = float_conversion (fvalue,
619 buf_pointer += field_width,
625 precision = field_width - (plus_space_flag != 0);
626 while (precision > ptr - buf_pointer)
627 *--buf_pointer = '0';
631 #else /* !FLOAT_SUPPORT */
637 ptr = buf_pointer = bad_conversion;
641 #endif /* !FLOAT_SUPPORT */
643 case '\0': /* Really bad place to find NUL in */
647 /* Undefined conversion! */
648 ptr = buf_pointer = bad_conversion;
655 * This part emittes the formatted string to "put_one_char".
658 /* If field_width == 0 then nothing should be written. */
659 precision = ptr - buf_pointer;
661 if ( precision > field_width)
667 n = field_width - precision - (plus_space_flag != 0);
670 /* emit any leading pad characters */
674 put_one_char(' ', secret_pointer);
678 /* emit flag characters (if any) */
681 put_one_char(plus_space_flag, secret_pointer);
685 /* emit the string itself */
686 while (--precision >= 0)
688 put_one_char(*buf_pointer++, secret_pointer);
692 /* emit trailing space characters */
696 put_one_char(' ', secret_pointer);
701 #else /* REDUCED_SUPPORT STARTS HERE */
703 #ifdef MODIFIERS_IN_REDUCED
704 char l_modifier, h_modifier;
705 unsigned long u_val, div_val;
707 unsigned int u_val, div_val;
710 unsigned int nr_of_chars, base;
715 for (;;) /* Until full format string read */
717 while ((format_flag = PGM_READ_CHAR(format++)) != '%') /* Until '%' or '\0' */
720 return (nr_of_chars);
721 put_one_char (format_flag, secret_pointer);
725 #ifdef MODIFIERS_IN_REDUCED
726 /*=================================*/
727 /* Optional 'l' or 'h' modifiers ? */
728 /*=================================*/
729 l_modifier = h_modifier = 0;
730 switch (PGM_READ_CHAR(format))
744 switch (format_flag = PGM_READ_CHAR(format++))
747 format_flag = va_arg(ap, int);
749 put_one_char (format_flag, secret_pointer);
754 ptr = va_arg(ap, char *);
755 while (format_flag = *ptr++)
757 put_one_char (format_flag, secret_pointer);
767 div_val = 0x40000000;
768 goto CONVERSION_LOOP;
775 div_val = 1000000000;
776 goto CONVERSION_LOOP;
784 div_val = 0x10000000;
787 #ifdef MODIFIERS_IN_REDUCED
789 u_val = (format_flag == 'd') ?
790 (short)va_arg(ap, int) : (unsigned short)va_arg(ap, int);
792 u_val = va_arg(ap, long);
794 u_val = (format_flag == 'd') ?
795 va_arg(ap,int) : va_arg(ap,unsigned int);
797 u_val = va_arg(ap,int);
799 if (format_flag == 'd')
801 if (((int)u_val) < 0)
804 put_one_char ('-', secret_pointer);
808 while (div_val > 1 && div_val > u_val)
814 outChar = (u_val / div_val) + '0';
816 if (format_flag == 'x')
817 outChar += 'a'-'9'-1;
819 outChar += 'A'-'9'-1;
820 put_one_char (outChar, secret_pointer);
827 } /* end switch(format_flag...) */