2 * Implements the internals of the format command for jim
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following
14 * disclaimer in the documentation and/or other materials
15 * provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
19 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21 * JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
28 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 * The views and conclusions contained in the software and documentation
31 * are those of the authors and should not be interpreted as representing
32 * official policies, either expressed or implied, of the Jim Tcl Project.
34 * Based on code originally from Tcl 8.5:
36 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
37 * Copyright (c) 1999 by Scriptics Corporation.
39 * See the file "tcl.license.terms" for information on usage and redistribution of
40 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
48 #define JIM_INTEGER_SPACE 24
49 #define MAX_FLOAT_WIDTH 320
52 * Apply the printf-like format in fmtObjPtr with the given arguments.
54 * Returns a new object with zero reference count if OK, or NULL on error.
56 Jim_Obj
*Jim_FormatString(Jim_Interp
*interp
, Jim_Obj
*fmtObjPtr
, int objc
, Jim_Obj
*const *objv
)
58 const char *span
, *format
, *formatEnd
, *msg
;
59 int numBytes
= 0, objIndex
= 0, gotXpg
= 0, gotSequential
= 0;
60 static const char * const mixedXPG
=
61 "cannot mix \"%\" and \"%n$\" conversion specifiers";
62 static const char * const badIndex
[2] = {
63 "not enough arguments for all format specifiers",
64 "\"%n$\" argument index out of range"
69 /* A single buffer is used to store numeric fields (with sprintf())
70 * This buffer is allocated/reallocated as necessary
72 char *num_buffer
= NULL
;
73 int num_buffer_size
= 0;
75 span
= format
= Jim_GetString(fmtObjPtr
, &formatLen
);
76 formatEnd
= format
+ formatLen
;
77 resultPtr
= Jim_NewEmptyStringObj(interp
);
79 while (format
!= formatEnd
) {
81 int gotMinus
, sawFlag
;
82 int gotPrecision
, useShort
;
83 long width
, precision
;
89 char spec
[2*JIM_INTEGER_SPACE
+ 12];
94 const char *formatted_buf
;
96 step
= utf8_tounicode(format
, &ch
);
103 Jim_AppendString(interp
, resultPtr
, span
, numBytes
);
108 * Saw a % : process the format specifier.
110 * Step 0. Handle special case of escaped format marker (i.e., %%).
113 step
= utf8_tounicode(format
, &ch
);
122 * Step 1. XPG3 position specifier
127 int position
= strtoul(format
, &end
, 10);
130 objIndex
= position
- 1;
132 step
= utf8_tounicode(format
, &ch
);
148 if ((objIndex
< 0) || (objIndex
>= objc
)) {
149 msg
= badIndex
[gotXpg
];
154 * Step 2. Set of flags. Also build up the sprintf spec.
179 step
= utf8_tounicode(format
, &ch
);
180 /* Only allow one of each flag, so if we have more than 5 flags, stop */
181 } while (sawFlag
&& (p
- spec
<= 5));
184 * Step 3. Minimum field width.
189 width
= strtoul(format
, &end
, 10);
191 step
= utf8_tounicode(format
, &ch
);
192 } else if (ch
== '*') {
193 if (objIndex
>= objc
- 1) {
194 msg
= badIndex
[gotXpg
];
197 if (Jim_GetLong(interp
, objv
[objIndex
], &width
) != JIM_OK
) {
209 step
= utf8_tounicode(format
, &ch
);
216 gotPrecision
= precision
= 0;
220 step
= utf8_tounicode(format
, &ch
);
223 precision
= strtoul(format
, &end
, 10);
225 step
= utf8_tounicode(format
, &ch
);
226 } else if (ch
== '*') {
227 if (objIndex
>= objc
- 1) {
228 msg
= badIndex
[gotXpg
];
231 if (Jim_GetLong(interp
, objv
[objIndex
], &precision
) != JIM_OK
) {
236 * TODO: Check this truncation logic.
244 step
= utf8_tounicode(format
, &ch
);
248 * Step 5. Length modifier.
255 step
= utf8_tounicode(format
, &ch
);
256 } else if (ch
== 'l') {
257 /* Just for compatibility. All non-short integers are wide. */
259 step
= utf8_tounicode(format
, &ch
);
262 step
= utf8_tounicode(format
, &ch
);
270 * Step 6. The actual conversion character.
279 /* Each valid conversion will set:
280 * formatted_buf - the result to be added
281 * formatted_chars - the length of formatted_buf in characters
282 * formatted_bytes - the length of formatted_buf in bytes
286 msg
= "format string ended in middle of field specifier";
289 formatted_buf
= Jim_GetString(objv
[objIndex
], &formatted_bytes
);
290 formatted_chars
= Jim_Utf8Length(interp
, objv
[objIndex
]);
291 if (gotPrecision
&& (precision
< formatted_chars
)) {
292 /* Need to build a (null terminated) truncated string */
293 formatted_chars
= precision
;
294 formatted_bytes
= utf8_index(formatted_buf
, precision
);
301 if (Jim_GetWide(interp
, objv
[objIndex
], &code
) != JIM_OK
) {
304 /* Just store the value in the 'spec' buffer */
305 formatted_bytes
= utf8_getchars(spec
, code
);
306 formatted_buf
= spec
;
316 if (Jim_GetWide(interp
, objv
[objIndex
], (jim_wide
*)&w
) != JIM_OK
) {
319 length
= sizeof(w
) * 8;
321 /* XXX: width and precision not yet implemented for binary
322 * also flags in 'spec', e.g. #, 0, -
325 /* Increase the size of the buffer if needed */
326 if (num_buffer_size
< length
+ 1) {
327 num_buffer_size
= length
+ 1;
328 num_buffer
= Jim_Realloc(num_buffer
, num_buffer_size
);
332 for (i
= length
; i
> 0; ) {
334 if (w
& ((unsigned jim_wide
)1 << i
)) {
335 num_buffer
[j
++] = '1';
337 else if (j
|| i
== 0) {
338 num_buffer
[j
++] = '0';
342 formatted_chars
= formatted_bytes
= j
;
343 formatted_buf
= num_buffer
;
363 /* Fill in the width and precision */
365 p
+= sprintf(p
, "%ld", width
);
368 p
+= sprintf(p
, ".%ld", precision
);
371 /* Now the modifier, and get the actual value here */
373 if (Jim_GetDouble(interp
, objv
[objIndex
], &d
) != JIM_OK
) {
376 length
= MAX_FLOAT_WIDTH
;
379 if (Jim_GetWide(interp
, objv
[objIndex
], &w
) != JIM_OK
) {
382 length
= JIM_INTEGER_SPACE
;
388 w
= (unsigned short)w
;
392 #ifdef HAVE_LONG_LONG
393 if (sizeof(long long) == sizeof(jim_wide
)) {
402 /* Put some reasonable limits on the field size */
403 if (width
> 10000 || length
> 10000 || precision
> 10000) {
404 Jim_SetResultString(interp
, "format too long", -1);
409 /* Adjust length for width and precision */
410 if (width
> length
) {
417 /* Increase the size of the buffer if needed */
418 if (num_buffer_size
< length
+ 1) {
419 num_buffer_size
= length
+ 1;
420 num_buffer
= Jim_Realloc(num_buffer
, num_buffer_size
);
424 snprintf(num_buffer
, length
+ 1, spec
, d
);
427 formatted_bytes
= snprintf(num_buffer
, length
+ 1, spec
, w
);
429 formatted_chars
= formatted_bytes
= strlen(num_buffer
);
430 formatted_buf
= num_buffer
;
435 /* Just reuse the 'spec' buffer */
438 Jim_SetResultFormatted(interp
, "bad field specifier \"%s\"", spec
);
444 while (formatted_chars
< width
) {
445 Jim_AppendString(interp
, resultPtr
, &pad
, 1);
450 Jim_AppendString(interp
, resultPtr
, formatted_buf
, formatted_bytes
);
452 while (formatted_chars
< width
) {
453 Jim_AppendString(interp
, resultPtr
, &pad
, 1);
457 objIndex
+= gotSequential
;
460 Jim_AppendString(interp
, resultPtr
, span
, numBytes
);
463 Jim_Free(num_buffer
);
467 Jim_SetResultString(interp
, msg
, -1);
469 Jim_FreeNewObj(interp
, resultPtr
);
470 Jim_Free(num_buffer
);