- Tabs to spaces.
[AROS.git] / workbench / libs / locale / formatstring.c
blobe24fab634f3053f25b98d13b9eff5d5a98a761d3
1 /*
2 Copyright © 1995-2011, The AROS Development Team. All rights reserved.
3 $Id$
5 Desc:
6 Lang: english
7 */
9 #include <stdarg.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <exec/types.h>
13 #include <utility/hooks.h>
14 #include <proto/utility.h>
15 #include <libraries/locale.h>
16 #include <aros/asmcall.h>
17 #include "locale_intern.h"
19 #include <aros/debug.h>
21 #if USE_QUADFMT
22 typedef QUAD FMTLARGESTTYPE;
23 typedef UQUAD UFMTLARGESTTYPE;
24 #else /* USE_QUADFMT */
25 typedef LONG FMTLARGESTTYPE;
26 typedef ULONG UFMTLARGESTTYPE;
27 #endif /* USE_QUADFMT */
29 static const UBYTE hexarray[] = "0123456789abcdef";
30 static const UBYTE HEXarray[] = "0123456789ABCDEF";
32 static inline APTR stream_addr(APTR *args, ULONG len)
34 APTR ret = *args;
36 /* LONG data are actually IPTR-aligned */
37 *args += (len > sizeof(UWORD)) ? sizeof(IPTR) : len;
38 return ret;
41 /* The stuff below is based on old gcc includes (where you can actually read how
42 varargs work) and ABI documentation.
43 It works in the same way as traditional va_arg() except it fetches
44 pointers to arguments, not arguments themselves (since on 32-bit machines
45 argument values may be larger than pointers). */
47 #if defined(__PPC__)
49 static inline APTR va_addr(va_list args, ULONG len)
51 APTR ret;
53 if (len == sizeof(UQUAD))
55 /* On PPC UQUAD is aligned. and occupies 2 registers (plus may waste one more for alignment) */
56 if (args->gpr < 7)
58 ULONG *regsave = (ULONG *)args->reg_save_area;
60 args->gpr += args->gpr & 1;
61 ret = &regsave[args->gpr];
62 args->gpr += 2;
64 else
66 args->gpr = 8;
67 ret = (APTR)(((IPTR)(args->overflow_arg_area + 7)) & ~7);
68 args->overflow_arg_area = ret + sizeof(UQUAD);
71 else
73 if (args->gpr < 8)
75 ULONG *regsave = (ULONG *)args->reg_save_area;
77 ret = &regsave[args->gpr++];
79 else
81 ret = args->overflow_arg_area;
82 args->overflow_arg_area += sizeof(ULONG);
85 return ret;
88 #elif defined(__x86_64__)
90 static inline APTR va_addr(va_list args, ULONG len)
92 APTR ret;
94 if (args->gp_offset < 48)
96 ret = args->reg_save_area + args->gp_offset;
97 args->gp_offset += sizeof(IPTR);
99 else
101 ret = args->overflow_arg_area;
102 args->overflow_arg_area += sizeof(IPTR);
104 return ret;
107 #elif defined(__arm__)
109 #define va_addr(args, len) stream_addr(&args.__ap, len)
110 #define is_va_list(ap) ap.__ap
111 #define null_va_list(ap) va_list ap = {NULL}
113 #else
115 /* This works for i386 and m68k */
116 static inline APTR _va_addr(va_list *args)
118 va_list ret = *args;
120 *args += sizeof(IPTR);
121 return ret;
124 #define va_addr(args, len) _va_addr(&args)
126 #endif
128 #ifndef is_va_list
130 #define is_va_list(ap) ap
131 #define null_va_list(ap) void *ap = NULL
133 #endif
135 APTR InternalFormatString(const struct Locale * locale,
136 CONST_STRPTR fmtTemplate, CONST_APTR dataStream,
137 const struct Hook * putCharFunc, va_list VaListStream)
139 enum
140 { OUTPUT = 0,
141 FOUND_FORMAT
142 } state;
144 ULONG template_pos;
145 BOOL end;
146 ULONG max_argpos;
147 ULONG arg_counter;
148 BOOL scanning;
150 #define INDICES 256
151 IPTR indices[INDICES];
153 if (!fmtTemplate)
154 return (APTR) dataStream;
156 template_pos = 0; /* Current position in the template string */
157 state = OUTPUT; /* current state of parsing */
158 end = FALSE;
159 max_argpos = 0;
160 arg_counter = 0;
161 scanning = TRUE; /* The first time I will go through
162 and determine the width of the data in the dataStream */
164 memset(indices, sizeof(APTR), sizeof(indices));
166 while (!end)
169 ** A format description starts here?
171 if (fmtTemplate[template_pos] == '%')
173 arg_counter++;
174 state = FOUND_FORMAT;
177 switch (state)
179 case OUTPUT:
181 ** Call the hook for this character
183 if (!scanning)
185 AROS_UFC3(VOID, putCharFunc->h_Entry,
186 AROS_UFCA(const struct Hook *, putCharFunc, A0),
187 AROS_UFCA(const struct Locale *, locale, A2),
188 AROS_UFCA(UBYTE, fmtTemplate[template_pos], A1));
192 ** End of template string? -> End of this function.
194 if (fmtTemplate[template_pos] == '\0')
196 if (scanning)
199 ** The scanning phase is over. Next time we do the output.
201 int i;
202 scanning = FALSE;
203 template_pos = 0;
204 arg_counter = 0;
207 ** prepare the indices array
209 if (is_va_list(VaListStream))
211 for (i = 0; i <= max_argpos; i++)
212 indices[i] =
213 (IPTR) va_addr(VaListStream, indices[i]);
215 else
217 for (i = 0; i <= max_argpos; i++)
218 indices[i] =
219 (IPTR) stream_addr((APTR *) & dataStream,
220 indices[i]);
224 else
227 ** We already went through the output phase. So this is
228 ** the end of it.
230 end = TRUE;
233 else
234 template_pos++;
236 //kprintf("OUTPUT: template_pos: %d\n",template_pos);
238 break;
240 case FOUND_FORMAT:
242 ** The '%' was found in the template string
244 template_pos++;
246 //kprintf("FOUND_FORMAT: template_pos: %d\n",template_pos);
248 ** Does the user want the '%' to be printed?
250 if (fmtTemplate[template_pos] == '%')
252 if (!scanning)
254 AROS_UFC3(VOID, putCharFunc->h_Entry,
255 AROS_UFCA(const struct Hook *, putCharFunc, A0),
256 AROS_UFCA(const struct Locale *, locale, A2),
257 AROS_UFCA(UBYTE, fmtTemplate[template_pos], A1));
259 template_pos++;
260 arg_counter--; //stegerg
262 else
265 ** Now parsing...
266 ** Template format: %[arg_pos$][flags][width][.limit][length]type
268 ** arg_pos specifies the position of the argument in the dataStream
269 ** flags only '-' is allowed
270 ** width
271 ** .limit
272 ** datasize size of the datatype
273 ** type b,d,D,u,U,x,X,s,c
275 ULONG arg_pos = 1;
276 BOOL left = FALSE; // no flag was found
277 UBYTE fill = ' ';
278 ULONG minus;
279 ULONG width = 0;
280 ULONG limit = ~0;
281 ULONG buflen = 0;
282 ULONG datasize;
283 UFMTLARGESTTYPE tmp = 0;
284 #define BUFFERSIZE 128
285 UBYTE buf[BUFFERSIZE];
286 UBYTE *buffer = buf;
289 ** arg_pos
292 //kprintf("next char: %c\n",fmtTemplate[template_pos]);
294 if (fmtTemplate[template_pos] >= '0' &&
295 fmtTemplate[template_pos] <= '9')
297 ULONG old_template_pos = template_pos;
299 for (arg_pos = 0; (fmtTemplate[template_pos] >= '0' &&
300 fmtTemplate[template_pos] <= '9');
301 template_pos++)
303 arg_pos =
304 arg_pos * 10 + fmtTemplate[template_pos] - '0';
307 if (fmtTemplate[template_pos] == '$')
308 template_pos++;
309 else
311 arg_pos = arg_counter;
312 template_pos = old_template_pos;
315 else
316 arg_pos = arg_counter;
319 ** flags
321 if (fmtTemplate[template_pos] == '-')
323 template_pos++;
324 left = TRUE;
328 ** fill character a '0'?
330 if (fmtTemplate[template_pos] == '0')
332 template_pos++;
333 fill = '0';
337 ** width
339 if (fmtTemplate[template_pos] >= '0' &&
340 fmtTemplate[template_pos] <= '9')
342 for (width = 0; (fmtTemplate[template_pos] >= '0' &&
343 fmtTemplate[template_pos] <= '9');
344 template_pos++)
346 width =
347 width * 10 + fmtTemplate[template_pos] - '0';
352 ** limit
354 if (fmtTemplate[template_pos] == '.')
356 template_pos++;
358 if (fmtTemplate[template_pos] >= '0' &&
359 fmtTemplate[template_pos] <= '9')
361 for (limit = 0; (fmtTemplate[template_pos] >= '0' &&
362 fmtTemplate[template_pos] <= '9');
363 template_pos++)
365 limit =
366 limit * 10 + fmtTemplate[template_pos] -
367 '0';
373 ** Length
375 switch (fmtTemplate[template_pos])
377 #if USE_QUADFMT
378 case 'L':
379 datasize = sizeof(UQUAD);
380 template_pos++;
381 break;
382 #endif /* USE_QUADFMT */
384 case 'l':
385 template_pos++;
386 #if USE_QUADFMT
387 if (fmtTemplate[template_pos] == 'l')
389 datasize = sizeof(UQUAD);
390 template_pos++;
392 else
393 #endif /* USE_QUADFMT */
394 datasize = sizeof(ULONG);
395 break;
397 default:
398 /* For C-style varargs default size is ULONG, single 'l' is effectively ignored */
399 datasize =
400 is_va_list(VaListStream) ? sizeof(ULONG) :
401 sizeof(UWORD);
402 break;
406 ** Print it according to the given type info.
408 switch (fmtTemplate[template_pos])
410 case 'b': /* BSTR, see autodocs */
412 ** Important parameters:
413 ** arg_pos, left, buflen, limit
415 if (!scanning)
417 BSTR s = (BSTR) * (UBYTE **) indices[arg_pos - 1];
419 if (s != (BSTR) BNULL)
421 buffer = AROS_BSTR_ADDR(s);
422 buflen = AROS_BSTR_strlen(s);
424 else
426 buffer = "";
427 buflen = 0;
430 #if !USE_GLOBALLIMIT
431 if (buflen > limit)
432 buflen = limit;
433 #endif /* !USE_GLOBALLIMIT */
435 else
436 indices[arg_pos - 1] = sizeof(BPTR);
437 break;
439 case 'd': /* signed decimal */
440 case 'u': /* unsigned decimal */
442 minus = fmtTemplate[template_pos] == 'd';
444 if (!scanning)
446 switch (datasize)
448 #if USE_QUADFMT
449 case 8:
450 tmp = *(UQUAD *) indices[arg_pos - 1];
451 //buffer = &buf[16+1];
452 minus *= (FMTLARGESTTYPE) tmp < 0;
453 if (minus)
454 tmp = -tmp;
455 break;
456 #endif /* USE_QUADFMT */
458 case 4:
459 tmp = *(ULONG *) indices[arg_pos - 1];
460 //buffer = &buf[8+1];
461 minus *= (LONG) tmp < 0;
462 if (minus)
463 tmp = (ULONG) - tmp;
464 break;
466 default: /* 2 */
467 tmp = *(UWORD *) indices[arg_pos - 1];
468 //buffer = &buf[4+1];
469 minus *= (WORD) tmp < 0;
470 if (minus)
471 tmp = (UWORD) - tmp;
472 break;
475 buffer = &buf[BUFFERSIZE];
478 *--buffer = (tmp % 10) + '0';
479 tmp /= 10;
480 buflen++;
482 while (tmp);
484 if (minus)
486 *--buffer = '-';
487 buflen++;
491 else
492 indices[arg_pos - 1] = datasize;
493 break;
495 case 'D': /* signed decimal with locale's formatting conventions */
496 case 'U': /* unsigned decimal with locale's formatting conventions */
497 if (!scanning)
499 UBYTE groupsize;
500 ULONG group_index = 0;
502 minus = fmtTemplate[template_pos] == 'D';
504 switch (datasize)
506 #if USE_QUADFMT
507 case 8:
508 tmp = *(UQUAD *) indices[arg_pos - 1];
509 minus *= (FMTLARGESTTYPE) tmp < 0;
510 if (minus)
511 tmp = -tmp;
512 break;
513 #endif /* USE_QUADFMT */
515 case 4:
516 tmp = *(ULONG *) indices[arg_pos - 1];
517 minus *= (LONG) tmp < 0;
518 if (minus)
519 tmp = (ULONG) - tmp;
520 break;
522 default: /* 2 */
523 tmp = *(UWORD *) indices[arg_pos - 1];
524 minus *= (WORD) tmp < 0;
525 if (minus)
526 tmp = (UWORD) - tmp;
527 break;
530 /* BUFFERSIZE should be big enough to format a string
531 ** according to locale's formatting conventions
533 buffer = &buf[BUFFERSIZE];
534 groupsize =
535 locale ? locale->
536 loc_Grouping[group_index] : 255;
540 *--buffer = (tmp % 10) + '0';
541 tmp /= 10;
542 buflen++;
544 groupsize--;
546 if (groupsize == 0 && tmp != 0)
549 ** Write the separator
552 *--buffer =
553 locale->loc_GroupSeparator[group_index];
555 groupsize =
556 locale->loc_Grouping[group_index + 1];
558 if (groupsize == 0)
561 ** Supposed to use the previous element
563 groupsize =
564 locale->loc_Grouping[group_index];
566 else
567 group_index++;
569 buflen++;
572 while (tmp);
574 if (minus)
576 *--buffer = '-';
577 buflen++;
580 else
581 indices[arg_pos - 1] = datasize;
582 break;
584 case 'p': /* lower case pointer string */
585 case 'P': /* upper case pointer string */
586 fill = '0';
587 width = sizeof(APTR) * 2;
588 /* %p is always at least natural pointer size */
589 if (datasize < sizeof(APTR))
590 datasize = sizeof(APTR);
591 case 'x': /* upper case hexadecimal string */
592 case 'X': /* lower case hexadecimal string */
594 if (!scanning)
596 const UBYTE *hexa;
598 switch (datasize)
600 #if USE_QUADFMT
601 case 8:
602 tmp = *(UQUAD *) indices[arg_pos - 1];
603 //buffer = &buf[16+1];
604 break;
605 #endif /* USE_QUADFMT */
607 case 4:
608 tmp = *(ULONG *) indices[arg_pos - 1];
609 //buffer = &buf[8+1];
610 break;
612 default: /* 2 */
613 tmp = *(UWORD *) indices[arg_pos - 1];
614 //buffer = &buf[4+1];
615 break;
618 buffer = &buf[BUFFERSIZE];
620 /* NOTE: x/X is reverse to printf, coz orig RawDoFmt %lx for uppercase. */
621 hexa = (fmtTemplate[template_pos] == 'X' ||
622 fmtTemplate[template_pos] ==
623 'p') ? hexarray : HEXarray;
626 *--buffer = hexa[tmp & 0x0f];
627 tmp >>= 4;
628 buflen++;
630 while (tmp);
632 else
633 indices[arg_pos - 1] = datasize;
634 break;
636 case 's': /* NULL terminated string */
638 if (!scanning)
640 buffer = *(UBYTE **) indices[arg_pos - 1];
643 * RawDoFmt() in original AmigaOS(tm) formats NULL pointers as empty strings,
644 * and not something like "(null)". Some software may rely on this behavior.
645 * %b is handled in similar manner.
647 if (!buffer)
648 buffer = "";
649 buflen = strlen(buffer);
651 #if !USE_GLOBALLIMIT
652 if (buflen > limit)
653 buflen = limit;
654 #endif /* !USE_GLOBALLIMIT */
656 else
657 indices[arg_pos - 1] = sizeof(UBYTE *); /* the pointer has 4 bytes */
659 break;
661 case 'c': /* Character */
662 if (!scanning)
664 switch (datasize)
666 #if USE_QUADFMT
667 case 8:
668 buf[0] =
669 (UBYTE) * (UQUAD *) indices[arg_pos - 1];
670 break;
671 #endif /* USE_QUADFMT */
673 case 4:
674 buf[0] =
675 (UBYTE) * (ULONG *) indices[arg_pos - 1];
676 break;
678 default: /* 2 */
679 buf[0] =
680 (UBYTE) * (WORD *) indices[arg_pos - 1];
681 break;
684 buflen = 1;
686 else
687 indices[arg_pos - 1] = datasize;
688 break;
690 default:
691 /* Ignore the faulty '%' */
693 if (!scanning)
695 buf[0] = fmtTemplate[template_pos];
696 width = 1;
697 buflen = 1;
700 arg_pos = --arg_counter;
701 break;
705 if (!scanning)
707 int i;
710 Now everything I need is known:
711 buffer - contains the string to be printed
712 buflen - size of the string
713 fill - the pad character
714 left - is 1 if the string should be left aligned
715 width - is the minimal width of the field
716 limit - maximum number of characters to output from a string, default ~0
719 #if USE_GLOBALLIMIT
720 if (buflen > limit)
721 buflen = limit;
722 #endif /* USE_GLOBALLIMIT */
724 /* Print padding if right aligned */
725 if (!left)
726 for (i = buflen; i < width; i++)
727 AROS_UFC3(VOID, putCharFunc->h_Entry,
728 AROS_UFCA(const struct Hook *, putCharFunc, A0),
729 AROS_UFCA(const struct Locale *, locale, A2),
730 AROS_UFCA(UBYTE, fill, A1));
732 /* Print body up to buflen */
733 for (i = 0; i < buflen; i++)
735 AROS_UFC3(VOID, putCharFunc->h_Entry,
736 AROS_UFCA(const struct Hook *, putCharFunc, A0),
737 AROS_UFCA(const struct Locale *, locale, A2),
738 AROS_UFCA(UBYTE, *buffer++, A1));
741 /* Pad right if left aligned */
742 if (left)
743 for (i = buflen; i < width; i++)
744 AROS_UFC3(VOID, putCharFunc->h_Entry,
745 AROS_UFCA(const struct Hook *, putCharFunc, A0),
746 AROS_UFCA(const struct Locale *, locale, A2),
747 AROS_UFCA(UBYTE, fill, A1));
750 template_pos++;
752 if (arg_pos > max_argpos)
753 max_argpos = arg_pos;
756 state = OUTPUT;
757 break;
761 return (APTR) indices[max_argpos];
764 /*****************************************************************************
766 NAME */
767 #include <proto/locale.h>
769 AROS_LH4(APTR, FormatString,
771 /* SYNOPSIS */
772 AROS_LHA(const struct Locale *, locale, A0),
773 AROS_LHA(CONST_STRPTR, fmtTemplate, A1),
774 AROS_LHA(CONST_APTR, dataStream, A2),
775 AROS_LHA(const struct Hook *, putCharFunc, A3),
777 /* LOCATION */
778 struct LocaleBase *, LocaleBase, 11, Locale)
780 /* FUNCTION
782 INPUTS
784 RESULT
786 NOTES
788 EXAMPLE
790 BUGS
792 SEE ALSO
794 INTERNALS
796 *****************************************************************************/
798 AROS_LIBFUNC_INIT
800 null_va_list(vaListStream);
802 return InternalFormatString(locale, fmtTemplate, dataStream,
803 putCharFunc, vaListStream);
805 AROS_LIBFUNC_EXIT