revert between 56095 -> 55830 in arch
[AROS.git] / rom / exec / rawdofmt.c
blob8d21d2530f7f2673cf247efbd43376d38bc0cc9d
1 /*
2 Copyright © 1995-2014, The AROS Development Team. All rights reserved.
3 $Id$
5 Desc: Format a string and emit it.
6 Lang: english
7 */
9 #include <dos/dos.h>
10 #include <aros/libcall.h>
11 #include <aros/asmcall.h>
12 #include <exec/rawfmt.h>
13 #include <proto/exec.h>
14 #include <string.h>
16 #include <stdarg.h>
18 #include "exec_intern.h"
19 #include "exec_util.h"
21 #ifdef __arm__
23 #define is_va_list(ap) ap.__ap
24 #define null_va_list(ap) va_list ap = {NULL}
25 #define VA_NULL {NULL}
27 #else
29 #define is_va_list(ap) ap
30 #define null_va_list(ap) void *ap = NULL
32 #endif
34 /* Fetch the data from a va_list.
36 Variables are allocated in the va_list using the default argument
37 promotion rule of the C standard, which states that:
39 "types char and short int are promoted to int, and float is promoted
40 to double" (http://www.eskimo.com/~scs/C-faq/q15.2.html)
42 That rule directly translates into relations on types sizes, rather
43 than the types themselves, since sizeof(char) and sizeof(short) is always
44 less than or equal to sizeof(int), and sizeof(float) is always less than
45 or equal to sizeof(double). In addition, we also handle the case of
46 sizeof(long), whilst the sizeof(double) case is not handled for two reasons:
48 1) RawDoFmt() doesn't handle floating point values.
49 2) Given (1), sizeof(type) > sizeof(long) would hold true if
50 and only if type were a 64 bit pointer and long's and pointers
51 had different sizes (quite unusual). */
53 #define fetch_va_arg(type) \
54 ({ \
55 type res; \
57 if (sizeof(type) <= sizeof(int)) \
58 res = (type)(IPTR)va_arg(VaListStream, int); \
59 else \
60 if (sizeof(type) == sizeof(long)) \
61 res = (type)(IPTR)va_arg(VaListStream, long); \
62 else \
63 res = (type)(IPTR)va_arg(VaListStream, void *); \
65 res; \
68 /* Fetch an argument from memory.
70 We can be sure data is always aligned to a WORD boundary,
71 which is what we need.
73 However, on some architectures some kind of data needs to
74 have a certain alignment greater than 2 bytes, and this
75 code will miserably fail if DataStream is not properly
76 aligned; in such cases data should be fetched in pieces,
77 taking into account the endianess of the machine.
79 Currently it is assumed that ULONG values are stored
80 in an array of IPTRs. This results in good portability for
81 both 32- and 64-bit systems. */
83 #define fetch_mem_arg(type) \
84 ({ \
85 IPTR res; \
87 if (sizeof(type) <= sizeof(UWORD)) \
88 { \
89 res = *(UWORD *)DataStream ; \
90 DataStream = (UWORD *)DataStream + 1; \
91 } \
92 else if (sizeof(type) <= sizeof(ULONG)) \
93 { \
94 res = *(ULONG *)DataStream; \
95 DataStream = (ULONG *)DataStream + 1; \
96 } \
97 else if (sizeof(type) <= sizeof(IPTR)) \
98 { \
99 res = *(IPTR *)DataStream; \
100 DataStream = (IPTR *)DataStream + 1; \
102 (type)res; \
105 /* Fetch the data either from memory or from the va_list, depending
106 on the value of VaListStream. */
107 #define fetch_arg(type) \
108 (is_va_list(VaListStream) ? fetch_va_arg(type) : fetch_mem_arg(type))
111 * Fetch a number from the stream.
113 * size - one of 'h', 'l', 'i'
114 * sign - <0 or >= 0.
116 * EXPERIMENTAL: 'i' is used to represent full IPTR value on 64-bit systems
118 #define fetch_number(size, sign) \
119 (sign >= 0 \
120 ? (size == 'i' ? fetch_arg(IPTR) : (size == 'l' ? fetch_arg(ULONG) : fetch_arg(UWORD))) \
121 : (size == 'i' ? fetch_arg(SIPTR) : (size == 'l' ? fetch_arg(LONG) : fetch_arg(WORD))))
123 /* Call the PutCharProc function with the given parameters. */
124 #define PutCh(ch) \
125 do \
127 switch ((IPTR)PutChProc) \
129 case (IPTR)RAWFMTFUNC_STRING: \
130 *(PutChData++) = ch; \
131 break; \
132 case (IPTR)RAWFMTFUNC_SERIAL: \
133 RawPutChar(ch); \
134 break; \
135 case (IPTR)RAWFMTFUNC_COUNT: \
136 (*((ULONG *)PutChData))++; \
137 break; \
138 default: \
139 if (is_va_list(VaListStream)) \
141 APTR (*proc)(APTR, UBYTE) = (APTR)PutChProc;\
142 PutChData = proc((APTR)PutChData, ch); \
144 else \
146 AROS_UFC2NR(void, PutChProc, \
147 AROS_UFCA(UBYTE, (ch), D0), \
148 AROS_UFCA(APTR , PutChData, A3)); \
151 } while (0)
154 * DataStream == NULL can't be used to select between new or old style PutChProc() because
155 * RawDoFmt(<string without parameters>, NULL, PutChProc, PutChData); is valid and used by
156 * m68k programs.
157 * In order to get around we use specially formed va_list with NULL value.
160 APTR InternalRawDoFmt(CONST_STRPTR FormatString, APTR DataStream, VOID_FUNC PutChProc,
161 APTR inPutChData, va_list VaListStream)
163 #if defined(mc68000)
164 /* Frequently, AmigaOS users of RawDoFmt() rely upon the AmigaOS
165 * behaviour that A3 *in this routine* is the pointer to PutChData,
166 * *and* that it can be modified in PutChProc.
168 register volatile UBYTE *PutChData asm("%a3");
169 #else
170 UBYTE *PutChData = inPutChData;
171 #endif
173 /* As long as there is something to format left */
174 while (*FormatString)
176 /* Check for '%' sign */
177 if (*FormatString == '%')
180 left - left align flag
181 fill - pad character
182 minus - 1: number is negative
183 minwidth - minimum width
184 maxwidth - maximum width
185 size - one of 'h', 'l', 'i'.
186 width - width of printable string
187 buf - pointer to printable string
189 int left = 0;
190 int fill = ' ';
191 int minus = 0;
192 int size = 'h';
193 ULONG minwidth = 0;
194 ULONG maxwidth = ~0;
195 ULONG width = 0;
196 UBYTE *buf;
198 /* Number of decimal places required to convert a unsigned long to
199 ascii. The formula is: ceil(number_of_bits*log10(2)).
200 Since I can't do this here I use .302 instead of log10(2) and
201 +1 instead of ceil() which most often leads to exactly the
202 same result (and never becomes smaller).
204 Note that when the buffer is large enough for decimal it's
205 large enough for hexadecimal as well. */
207 #define CBUFSIZE (sizeof(IPTR)*8*302/1000+1)
208 /* The buffer for converting long to ascii. */
209 UBYTE cbuf[CBUFSIZE];
210 ULONG i;
212 /* Skip over '%' character */
213 FormatString++;
215 /* '-' modifier? (left align) */
216 if (*FormatString == '-')
217 left = *FormatString++;
219 /* '0' modifer? (pad with zeros) */
220 if (*FormatString == '0')
221 fill = *FormatString++;
223 /* Get minimal width */
224 while (*FormatString >= '0' && *FormatString <= '9')
226 minwidth = minwidth * 10 + (*FormatString++ - '0');
229 /* Dot following width modifier? */
230 if(*FormatString == '.')
232 FormatString++;
233 /* Get maximum width */
235 if(*FormatString >= '0' && *FormatString <= '9')
237 maxwidth = 0;
239 maxwidth = maxwidth *10 + (*FormatString++ - '0');
240 while (*FormatString >= '0' && *FormatString <= '9');
244 /* size modifiers */
245 switch (*FormatString)
247 case 'l':
248 case 'i':
249 size = *FormatString++;
250 break;
253 /* Switch over possible format characters. Sets minus, width and buf. */
254 switch(*FormatString)
256 /* BCPL string */
257 case 'b':
259 BSTR s = fetch_arg(BSTR);
261 if (s)
263 buf = AROS_BSTR_ADDR(s);
264 width = AROS_BSTR_strlen(s);
266 else
268 buf = "";
269 width = 0;
272 break;
275 /* C string */
276 case 's':
277 buf = fetch_arg(UBYTE *);
279 if (!buf)
280 buf = "";
281 width = strlen(buf);
283 break;
285 IPTR number = 0; int base;
286 static const char digits[] = "0123456789ABCDEF";
288 case 'p':
289 case 'P':
290 fill = '0';
291 minwidth = sizeof(APTR)*2;
292 size = 'i';
293 case 'x':
294 case 'X':
295 base = 16;
296 number = fetch_number(size, 1);
298 goto do_number;
300 case 'd':
301 case 'D':
302 base = 10;
303 number = fetch_number(size, -1);
304 minus = (SIPTR)number < 0;
306 if (minus) number = -number;
308 goto do_number;
310 case 'u':
311 case 'U':
312 base = 10;
313 number = fetch_number(size, 1);
315 do_number:
317 buf = &cbuf[CBUFSIZE];
320 *--buf = digits[number % base];
321 number /= base;
322 width++;
323 } while (number);
325 break;
329 /* single character */
330 case 'c':
331 /* Some space for the result */
332 buf = cbuf;
333 width = 1;
335 *buf = fetch_number(size, 1);
337 break;
339 /* '%' before '\0'? */
340 case '\0':
342 This is nonsense - but do something useful:
343 Instead of reading over the '\0' reuse the '\0'.
345 FormatString--;
346 /* Get compiler happy */
347 buf = NULL;
348 break;
350 /* Convert '%unknown' to 'unknown'. This includes '%%' to '%'. */
351 default:
352 buf = (UBYTE *)FormatString;
353 width = 1;
354 break;
357 if (width > maxwidth) width = maxwidth;
359 /* Skip the format character */
360 FormatString++;
363 Now everything I need is known:
364 buf - contains the string to be printed
365 width - the size of the string
366 minus - is 1 if there is a '-' to print
367 fill - is the pad character
368 left - is 1 if the string should be left aligned
369 minwidth - is the minimal width of the field
370 (maxwidth is already part of width)
372 So just print it.
375 /* Print '-' (if there is one and the pad character is no space) */
376 if (minus && fill != ' ')
377 PutCh('-');
379 /* Pad left if not left aligned */
380 if (!left)
381 for (i = width + minus; i < minwidth; i++)
382 PutCh(fill);
384 /* Print '-' (if there is one and the pad character is a space) */
385 if(minus && fill == ' ')
386 PutCh('-');
388 /* Print body upto width */
389 for(i=0; i<width; i++) {
390 PutCh(*buf);
391 buf++;
394 /* Pad right if left aligned */
395 if(left)
396 for(i = width + minus; i<minwidth; i++)
397 PutCh(fill);
399 else
401 /* No '%' sign? Put the formatstring out */
402 PutCh(*FormatString);
403 FormatString++;
406 /* All done. Put the terminator out. */
407 PutCh('\0');
409 /* Return the rest of the DataStream or buffer. */
410 return is_va_list(VaListStream) ? (APTR)PutChData : DataStream;
413 /*****************************************************************************
415 NAME */
417 AROS_LH4I(RAWARG,RawDoFmt,
419 /* SYNOPSIS */
420 AROS_LHA(CONST_STRPTR, FormatString, A0),
421 AROS_LHA(RAWARG, DataStream, A1),
422 AROS_LHA(VOID_FUNC, PutChProc, A2),
423 AROS_LHA(APTR, PutChData, A3),
425 /* LOCATION */
426 struct ExecBase *, SysBase, 87, Exec)
428 /* FUNCTION
429 printf-style formatting function with callback hook.
431 INPUTS
432 FormatString - Pointer to the format string with any of the following
433 DataStream formatting options allowed:
435 %[leftalign][minwidth.][maxwidth][size][type]
437 leftalign - '-' means align left. Default: align right.
438 minwidth - minimum width of field. Defaults to 0.
439 maxwidth - maximum width of field (for strings only).
440 Defaults to no limit.
442 size - 'l' means LONG. 'll' or 'L' means QUAD
443 (AROS extension). Defaults to WORD, if
444 nothing is specified.
446 type - 'b' BSTR. It will use the internal representation
447 of the BSTR defined by the ABI.
448 'c' single character.
449 'd' signed decimal number.
450 's' C string. NULL terminated.
451 'u' unsigned decimal number.
452 'x' unsigned hexadecimal number.
453 'P' pointer. Size depends on the architecture.
454 'p' The same as 'P', for AmigaOS v4 compatibility.
456 DataStream - Pointer to a zone of memory containing the data. Data has to be
457 WORD aligned.
459 PutChProc - Callback function. Called for each character, including
460 the NULL terminator. The fuction is called as follows:
462 AROS_UFC2(void, PutChProc,
463 AROS_UFCA(UBYTE, char, D0),
464 AROS_UFCA(APTR , PutChData, A3));
466 Additionally, PutChProc can be set to one of the following
467 magic values:
469 RAWFMTFUNC_STRING - Write output to string buffer pointed
470 to by PutChData which is incremented
471 every character.
472 RAWFMTFUNC_SERIAL - Write output to debug output. PutChData
473 is ignored and not touched.
474 RAWFMTFUNC_COUNT - Count number of characters in the result.
475 PutChData is a pointer to ULONG which
476 is incremented every character. Initial
477 value of the counter is kept as it is.
479 If you want to be compatible with AmigaOS you
480 should check that exec.library has at least version 45.
482 PutChData - Data propagated to each call of the callback hook.
484 RESULT
485 Pointer to the rest of the DataStream.
487 NOTES
488 The field size defaults to WORD which may be different from the
489 default integer size of the compiler. If you don't take care about
490 this the result will be messy.
492 There are different solutions for GCC:
493 - Define Datastream between #pragma pack(2) / #pragma pack().
494 - Use __attribute__((packed)) for Datastream.
495 - Only use type of LONG/ULONG for integer variables. Additionally only use
496 %ld/%lu in FormatString.
498 EXAMPLE
499 Build a sprintf style function:
501 void my_sprintf(UBYTE *buffer, UBYTE *format, ...);
503 static void callback(UBYTE chr __reg(d0), UBYTE **data __reg(a3))
505 *(*data)++=chr;
508 void my_sprintf(UBYTE *buffer, UBYTE *format, ...)
510 AROS_SLOWSTACKFORMAT_PRE(format)
511 RawDoFmt(format, AROS_SLOWSTACKFORMAT_ARG(format), &callback, &buffer);
512 AROS_SLOWSTACKFORMAT_POST(format)
515 The above example uses AROS_SLOWSTACKFORMAT_* macros in the function
516 in order to make sure that arguments are all passed on
517 the stack on all architectures. The alternative is to use
518 VNewRawDoFmt() function which takes va_list instead of array
519 DataStream.
521 BUGS
522 PutChData cannot be modified from the callback hook on non-m68k
523 systems.
525 SEE ALSO
527 INTERNALS
528 In AROS this function supports also 'i' type specifier
529 standing for full IPTR argument. This makes difference on
530 64-bit machines. At the moment this addition is not stable
531 and subject to change. Consider using %P or %p to output
532 full 64-bit pointers.
534 When locale.library starts up this function is replaced
535 with advanced version, supporting extensions supported
536 by FormatString() function.
538 ******************************************************************************/
540 AROS_LIBFUNC_INIT
542 null_va_list(vaListStream);
544 return InternalRawDoFmt(FormatString, (APTR)DataStream, PutChProc, PutChData, vaListStream);
546 AROS_LIBFUNC_EXIT
547 } /* RawDoFmt */