some fixes to accented characters
[tangerine.git] / rom / exec / rawdofmt.c
blob508782d8020a1b3fbd2c8c04721e041c327acf05
1 /*
2 Copyright © 1995-2008, The AROS Development Team. All rights reserved.
3 $Id$
5 Desc: Format a string and emit it.
6 Lang: english
7 */
8 #include <dos/dos.h>
9 #include <aros/libcall.h>
10 #include <aros/asmcall.h>
11 #include <exec/rawfmt.h>
12 #include <proto/exec.h>
13 #include <string.h>
15 #include <stdarg.h>
17 #include "exec_util.h"
19 /* Fetch the data from a va_list.
21 Variables are allocated in the va_list using the default argument
22 promotion rule of the C standard, which states that:
24 "types char and short int are promoted to int, and float is promoted
25 to double" (http://www.eskimo.com/~scs/C-faq/q15.2.html)
27 That rule directly translates into relations on types sizes, rather
28 than the types themselves, since sizeof(char) and sizeof(short) is always
29 less than or equal to sizeof(int), and sizeof(float) is always less than
30 or equal to sizeof(double). In addition, we also handle the case of
31 sizeof(long), whilst the sizeof(double) case is not handled for two reasons:
33 1) RawDoFmt() doesn't handle floating point values.
34 2) Given (1), sizeof(type) > sizeof(long) would hold true if
35 and only if type were a 64 bit pointer and long's and pointers
36 had different sizes (quite unusual). */
38 #define fetch_va_arg(type) \
39 ({ \
40 type res; \
42 if (sizeof(type) <= sizeof(int)) \
43 res = (type)va_arg(VaListStream, int); \
44 else \
45 if (sizeof(type) == sizeof(long)) \
46 res = (type)va_arg(VaListStream, long); \
47 else \
48 res = (type)(IPTR)va_arg(VaListStream, void *); \
50 res; \
53 /* Fetch an argument from memory.
55 We can be sure data is always aligned to a WORD boundary,
56 which is what we need.
58 However, on some architectures some kind of data needs to
59 have a certain alignment greater than 2 bytes, and this
60 code will miserably fail if DataStream is not properly
61 aligned; in such cases data should be fetched in pieces,
62 taking into account the endianess of the machine.
64 Another possibility would be to assume that DataStream
65 is a pointer to an array of objects on systems other
66 than 68k, and arrays are always properly aligned. */
69 #define fetch_mem_arg(type) \
70 ({ \
71 type __retval = *(type *)DataStream; \
72 DataStream = (type *)DataStream + 1; \
73 __retval; \
76 /* Fetch the data either from memory or from the va_list, depending
77 on the value of VaListStream. */
78 #define fetch_arg(type) \
79 (DataStream ? fetch_mem_arg(type) : fetch_va_arg(type))
81 /* Fetch a number from the stream.
83 size - one of 'w', 'l', 'i'
84 sign - <0 or >= 0. */
85 #define fetch_number(size, sign) \
86 (sign >= 0 \
87 ? (size == 'w' ? fetch_arg(UWORD) : fetch_arg(IPTR)) \
88 : (size == 'w' ? fetch_arg(WORD) : fetch_arg(SIPTR)))
91 /* Call the PutCharProc funtion with the given parameters. */
92 #define PutCh(ch) \
93 do \
94 { \
95 switch ((IPTR)PutChProc) \
96 { \
97 case (IPTR)RAWFMTFUNC_STRING: \
98 *UPutChData++ = ch; \
99 break; \
100 case (IPTR)RAWFMTFUNC_SERIAL: \
101 RawPutChar(ch); \
102 break; \
103 case (IPTR)RAWFMTFUNC_COUNT: \
104 (*((ULONG *)PutChData))++; \
105 break; \
106 default: \
107 AROS_UFC2(void, PutChProc, \
108 AROS_UFCA(UBYTE, (ch), D0), \
109 AROS_UFCA(APTR , PutChData, A3)); \
111 } while (0)
113 APTR InternalRawDoFmt(CONST_STRPTR FormatString, APTR DataStream, VOID_FUNC PutChProc,
114 APTR PutChData, va_list VaListStream)
116 #ifdef __mc68000
117 register APTR __PutChData asm("a3") = PutChData;
118 # define PutChData __PutChData
119 #endif
121 UBYTE *UPutChData = PutChData;
123 /* As long as there is something to format left */
124 while (*FormatString)
126 /* Check for '%' sign */
127 if (*FormatString == '%')
130 left - left align flag
131 fill - pad character
132 minus - 1: number is negative
133 minwidth - minimum width
134 maxwidth - maximum width
135 size - one of 'w', 'l', 'i'.
136 width - width of printable string
137 buf - pointer to printable string
139 int left = 0;
140 int fill = ' ';
141 int minus = 0;
142 int size = 'w';
143 ULONG minwidth = 0;
144 ULONG maxwidth = ~0;
145 ULONG width = 0;
146 UBYTE *buf;
148 /* Number of decimal places required to convert a unsigned long to
149 ascii. The formula is: ceil(number_of_bits*log10(2)).
150 Since I can't do this here I use .302 instead of log10(2) and
151 +1 instead of ceil() which most often leads to exactly the
152 same result (and never becomes smaller).
154 Note that when the buffer is large enough for decimal it's
155 large enough for hexdecimal as well. */
157 #define CBUFSIZE (sizeof(ULONG)*8*302/1000+1)
158 /* The buffer for converting long to ascii. */
159 UBYTE cbuf[CBUFSIZE];
160 ULONG i;
162 /* Skip over '%' character */
163 FormatString++;
165 /* '-' modifier? (left align) */
166 if (*FormatString == '-')
167 left = *FormatString++;
169 /* '0' modifer? (pad with zeros) */
170 if (*FormatString == '0')
171 fill = *FormatString++;
173 /* Get minimal width */
174 while (*FormatString >= '0' && *FormatString <= '9')
176 minwidth = minwidth * 10 + (*FormatString++ - '0');
179 /* Dot following width modifier? */
180 if(*FormatString == '.')
182 FormatString++;
183 /* Get maximum width */
185 if(*FormatString >= '0' && *FormatString <= '9')
187 maxwidth = 0;
189 maxwidth = maxwidth *10 + (*FormatString++ - '0');
190 while (*FormatString >= '0' && *FormatString <= '9');
194 /* size modifiers */
195 switch (*FormatString)
197 case 'l':
198 case 'i':
199 size = *FormatString++;
200 break;
203 /* Switch over possible format characters. Sets minus, width and buf. */
204 switch(*FormatString)
206 /* BCPL string */
207 case 'b':
209 BSTR s = fetch_arg(BSTR);
211 buf = AROS_BSTR_ADDR(s);
212 width = AROS_BSTR_strlen(s);
214 break;
217 /* C string */
218 case 's':
219 buf = fetch_arg(UBYTE *);
221 if (!buf)
222 buf = "(null)";
224 width = strlen(buf);
226 break;
228 IPTR number = 0; int base;
229 static const char digits[] = "0123456789ABCDEF";
231 case 'x':
232 base = 16;
233 number = fetch_number(size, 1);
235 goto do_number;
237 case 'd':
238 base = 10;
239 number = fetch_number(size, -1);
240 minus = (SIPTR)number < 0;
242 if (minus) number = -number;
244 goto do_number;
246 case 'u':
247 base = 10;
248 number = fetch_number(size, 1);
250 do_number:
252 buf = &cbuf[CBUFSIZE];
255 *--buf = digits[number % base];
256 number /= base;
257 width++;
258 } while (number);
260 break;
264 /* single character */
265 case 'c':
266 /* Some space for the result */
267 buf = cbuf;
268 width = 1;
270 *buf = fetch_number(size, 1);
272 break;
274 /* '%' before '\0'? */
275 case '\0':
277 This is nonsense - but do something useful:
278 Instead of reading over the '\0' reuse the '\0'.
280 FormatString--;
281 /* Get compiler happy */
282 buf = NULL;
283 break;
285 /* Convert '%unknown' to 'unknown'. This includes '%%' to '%'. */
286 default:
287 buf = (UBYTE *)FormatString;
288 width = 1;
289 break;
292 if (width > maxwidth) width = maxwidth;
294 /* Skip the format character */
295 FormatString++;
298 Now everything I need is known:
299 buf - contains the string to be printed
300 width - the size of the string
301 minus - is 1 if there is a '-' to print
302 fill - is the pad character
303 left - is 1 if the string should be left aligned
304 minwidth - is the minimal width of the field
305 (maxwidth is already part of width)
307 So just print it.
310 /* Print '-' (if there is one and the pad character is no space) */
311 if (minus && fill != ' ')
312 PutCh('-');
314 /* Pad left if not left aligned */
315 if (!left)
316 for (i = width + minus; i < minwidth; i++)
317 PutCh(fill);
319 /* Print '-' (if there is one and the pad character is a space) */
320 if(minus && fill == ' ')
321 PutCh('-');
323 /* Print body upto width */
324 for(i=0; i<width; i++)
325 PutCh(*buf++);
327 /* Pad right if left aligned */
328 if(left)
329 for(i = width + minus; i<minwidth; i++)
330 PutCh(fill);
332 else
334 /* No '%' sign? Put the formatstring out */
335 PutCh(*FormatString++);
338 /* All done. Put the terminator out. */
339 PutCh('\0');
341 /* Return the rest of the DataStream or buffer. */
342 return VaListStream ? UPutChData : DataStream;
346 /*****************************************************************************
348 NAME */
350 AROS_LH4I(APTR,RawDoFmt,
352 /* SYNOPSIS */
353 AROS_LHA(CONST_STRPTR, FormatString, A0),
354 AROS_LHA(APTR, DataStream, A1),
355 AROS_LHA(VOID_FUNC, PutChProc, A2),
356 AROS_LHA(APTR, PutChData, A3),
358 /* LOCATION */
359 struct ExecBase *, SysBase, 87, Exec)
361 /* FUNCTION
362 printf-style formatting function with callback hook.
364 INPUTS
365 FormatString - Pointer to the format string with any of the following
366 DataStream formatting options allowed:
368 %[leftalign][minwidth.][maxwidth][size][type]
370 leftalign - '-' means align left. Default: align right.
371 minwidth - minimum width of field. Defaults to 0.
372 maxwidth - maximum width of field (for strings only).
373 Defaults to no limit.
375 size - 'l' means IPTR (LONG on 32-bit architectures and QUAD on 64-bit).
376 Defaults to WORD, if nothing is specified.
378 type - 'b' BSTR. It will use the internal representation
379 of the BSTR defined by the ABI.
380 'c' single character.
381 'd' signed decimal number.
382 's' C string. NULL terminated.
383 'u' unsigned decimal number.
384 'x' unsigned hexdecimal number.
386 DataStream - Pointer to a zone of memory containing the data. Data has to be
387 WORD aligned.
389 PutChProc - Callback function. In caseCalled for each character, including
390 the NULL terminator. The fuction is called as follow:
392 AROS_UFC2(void, PutChProc,
393 AROS_UFCA(UBYTE, char, D0),
394 AROS_UFCA(APTR , PutChData, A3));
396 The argument may be NULL. This makes RawDoFmt() use an internal
397 function. If you want to be compatible with AmigaOS you
398 should check that exec.library has at least version 45.
400 PutChData - Data propagated to each call of the callback hook.
402 RESULT
403 Pointer to the rest of the DataStream.
405 NOTES
406 The field size defaults to WORD which may be different from the
407 default integer size of the compiler. If you don't take care about
408 this the result will be messy.
410 There are different solutions for GCC:
411 - Define Datastream between #pragma pack(2) / #pragma pack().
412 - Use __attribute__((packed)) for Datastream.
413 - Only use type of LONG/ULONG for integer variables. Additionally only use
414 %ld/%lu in FormatString.
416 EXAMPLE
417 Build a sprintf style function:
419 __stackparm void my_sprintf(UBYTE *buffer, UBYTE *format, ...);
421 static void callback(UBYTE chr __reg(d0), UBYTE **data __reg(a3))
423 *(*data)++=chr;
426 void my_sprintf(UBYTE *buffer, UBYTE *format, ...)
428 RawDoFmt(format, &format+1, &callback, &buffer);
431 The above example uses __stackparm attribute in the function
432 prototype in order to make sure that arguments are all passed on
433 the stack on all architectures. The alternative is to use
434 VNewRawDoFmt() function which takes va_list instead of array
435 DataStream.
437 BUGS
438 PutChData cannot be modified from the callback hook on non-m68k
439 systems.
441 SEE ALSO
443 INTERNALS
445 ******************************************************************************/
447 AROS_LIBFUNC_INIT
449 return InternalRawDoFmt(FormatString, DataStream, PutChProc, PutChData, NULL);
451 AROS_LIBFUNC_EXIT
452 } /* RawDoFmt */