Update to the latest autosetup
[jimtcl.git] / jim-format.c
blobdb9036aa87b21cc622079089e1268c4ab9a56700
1 /*
2 * Implements the internals of the format command for jim
4 * The FreeBSD license
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
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.
42 #include <ctype.h>
43 #include <string.h>
45 #include "jim.h"
46 #include "jimautoconf.h"
47 #include "utf8.h"
49 #define JIM_UTF_MAX 3
50 #define JIM_INTEGER_SPACE 24
51 #define MAX_FLOAT_WIDTH 320
53 /**
54 * Apply the printf-like format in fmtObjPtr with the given arguments.
56 * Returns a new object with zero reference count if OK, or NULL on error.
58 Jim_Obj *Jim_FormatString(Jim_Interp *interp, Jim_Obj *fmtObjPtr, int objc, Jim_Obj *const *objv)
60 const char *span, *format, *formatEnd, *msg;
61 int numBytes = 0, objIndex = 0, gotXpg = 0, gotSequential = 0;
62 static const char *mixedXPG =
63 "cannot mix \"%\" and \"%n$\" conversion specifiers";
64 static const char *badIndex[2] = {
65 "not enough arguments for all format specifiers",
66 "\"%n$\" argument index out of range"
68 int formatLen;
69 Jim_Obj *resultPtr;
71 /* A single buffer is used to store numeric fields (with sprintf())
72 * This buffer is allocated/reallocated as necessary
74 char *num_buffer = NULL;
75 int num_buffer_size = 0;
77 span = format = Jim_GetString(fmtObjPtr, &formatLen);
78 formatEnd = format + formatLen;
79 resultPtr = Jim_NewStringObj(interp, "", 0);
81 while (format != formatEnd) {
82 char *end;
83 int gotMinus, sawFlag;
84 int gotPrecision, useShort;
85 long width, precision;
86 int newXpg;
87 int ch;
88 int step;
89 int doubleType;
90 char pad = ' ';
91 char spec[2*JIM_INTEGER_SPACE + 12];
92 char *p;
94 int formatted_chars;
95 int formatted_bytes;
96 const char *formatted_buf;
98 step = utf8_tounicode(format, &ch);
99 format += step;
100 if (ch != '%') {
101 numBytes += step;
102 continue;
104 if (numBytes) {
105 Jim_AppendString(interp, resultPtr, span, numBytes);
106 numBytes = 0;
110 * Saw a % : process the format specifier.
112 * Step 0. Handle special case of escaped format marker (i.e., %%).
115 step = utf8_tounicode(format, &ch);
116 if (ch == '%') {
117 span = format;
118 numBytes = step;
119 format += step;
120 continue;
124 * Step 1. XPG3 position specifier
127 newXpg = 0;
128 if (isdigit(ch)) {
129 int position = strtoul(format, &end, 10);
130 if (*end == '$') {
131 newXpg = 1;
132 objIndex = position - 1;
133 format = end + 1;
134 step = utf8_tounicode(format, &ch);
137 if (newXpg) {
138 if (gotSequential) {
139 msg = mixedXPG;
140 goto errorMsg;
142 gotXpg = 1;
143 } else {
144 if (gotXpg) {
145 msg = mixedXPG;
146 goto errorMsg;
148 gotSequential = 1;
150 if ((objIndex < 0) || (objIndex >= objc)) {
151 msg = badIndex[gotXpg];
152 goto errorMsg;
156 * Step 2. Set of flags. Also build up the sprintf spec.
158 p = spec;
159 *p++ = '%';
161 gotMinus = 0;
162 sawFlag = 1;
163 do {
164 switch (ch) {
165 case '-':
166 gotMinus = 1;
167 break;
168 case '0':
169 pad = ch;
170 break;
171 case ' ':
172 case '+':
173 case '#':
174 break;
175 default:
176 sawFlag = 0;
177 continue;
179 *p++ = ch;
180 format += step;
181 step = utf8_tounicode(format, &ch);
182 } while (sawFlag);
185 * Step 3. Minimum field width.
188 width = 0;
189 if (isdigit(ch)) {
190 width = strtoul(format, &end, 10);
191 format = end;
192 step = utf8_tounicode(format, &ch);
193 } else if (ch == '*') {
194 if (objIndex >= objc - 1) {
195 msg = badIndex[gotXpg];
196 goto errorMsg;
198 if (Jim_GetLong(interp, objv[objIndex], &width) != JIM_OK) {
199 goto error;
201 if (width < 0) {
202 width = -width;
203 if (!gotMinus) {
204 *p++ = '-';
205 gotMinus = 1;
208 objIndex++;
209 format += step;
210 step = utf8_tounicode(format, &ch);
214 * Step 4. Precision.
217 gotPrecision = precision = 0;
218 if (ch == '.') {
219 gotPrecision = 1;
220 format += step;
221 step = utf8_tounicode(format, &ch);
223 if (isdigit(ch)) {
224 precision = strtoul(format, &end, 10);
225 format = end;
226 step = utf8_tounicode(format, &ch);
227 } else if (ch == '*') {
228 if (objIndex >= objc - 1) {
229 msg = badIndex[gotXpg];
230 goto errorMsg;
232 if (Jim_GetLong(interp, objv[objIndex], &precision) != JIM_OK) {
233 goto error;
237 * TODO: Check this truncation logic.
240 if (precision < 0) {
241 precision = 0;
243 objIndex++;
244 format += step;
245 step = utf8_tounicode(format, &ch);
249 * Step 5. Length modifier.
252 useShort = 0;
253 if (ch == 'h') {
254 useShort = 1;
255 format += step;
256 step = utf8_tounicode(format, &ch);
257 } else if (ch == 'l') {
258 /* Just for compatibility. All non-short integers are wide. */
259 format += step;
260 step = utf8_tounicode(format, &ch);
261 if (ch == 'l') {
262 format += step;
263 step = utf8_tounicode(format, &ch);
267 format += step;
268 span = format;
271 * Step 6. The actual conversion character.
274 if (ch == 'i') {
275 ch = 'd';
278 doubleType = 0;
280 /* Each valid conversion will set:
281 * formatted_buf - the result to be added
282 * formatted_chars - the length of formatted_buf in characters
283 * formatted_bytes - the length of formatted_buf in bytes
285 switch (ch) {
286 case '\0':
287 msg = "format string ended in middle of field specifier";
288 goto errorMsg;
289 case 's': {
290 formatted_buf = Jim_GetString(objv[objIndex], &formatted_bytes);
291 formatted_chars = Jim_Utf8Length(interp, objv[objIndex]);
292 if (gotPrecision && (precision < formatted_chars)) {
293 /* Need to build a (null terminated) truncated string */
294 formatted_chars = precision;
295 formatted_bytes = utf8_index(formatted_buf, precision);
297 break;
299 case 'c': {
300 jim_wide code;
302 if (Jim_GetWide(interp, objv[objIndex], &code) != JIM_OK) {
303 goto error;
305 /* Just store the value in the 'spec' buffer */
306 formatted_bytes = utf8_fromunicode(spec, code);
307 formatted_buf = spec;
308 formatted_chars = 1;
309 break;
312 case 'e':
313 case 'E':
314 case 'f':
315 case 'g':
316 case 'G':
317 doubleType = 1;
318 /* fall through */
319 case 'd':
320 case 'u':
321 case 'o':
322 case 'x':
323 case 'X': {
324 jim_wide w;
325 double d;
326 int length;
328 /* Fill in the width and precision */
329 if (width) {
330 p += sprintf(p, "%ld", width);
332 if (gotPrecision) {
333 p += sprintf(p, ".%ld", precision);
336 /* Now the modifier, and get the actual value here */
337 if (doubleType) {
338 if (Jim_GetDouble(interp, objv[objIndex], &d) != JIM_OK) {
339 goto error;
341 length = MAX_FLOAT_WIDTH;
343 else {
344 if (Jim_GetWide(interp, objv[objIndex], &w) != JIM_OK) {
345 goto error;
347 length = JIM_INTEGER_SPACE;
348 if (useShort) {
349 *p++ = 'h';
350 if (ch == 'd') {
351 w = (short)w;
353 else {
354 w = (unsigned short)w;
357 else {
358 *p++ = 'l';
359 #ifdef HAVE_LONG_LONG
360 if (sizeof(long long) == sizeof(jim_wide)) {
361 *p++ = 'l';
363 #endif
367 *p++ = (char) ch;
368 *p = '\0';
370 /* Adjust length for width and precision */
371 if (width > length) {
372 length = width;
374 if (gotPrecision) {
375 length += precision;
378 /* Increase the size of the buffer if needed */
379 if (num_buffer_size < length + 1) {
380 num_buffer_size = length + 1;
381 num_buffer = Jim_Realloc(num_buffer, num_buffer_size);
384 if (doubleType) {
385 snprintf(num_buffer, length + 1, spec, d);
387 else {
388 formatted_bytes = snprintf(num_buffer, length + 1, spec, w);
390 formatted_chars = formatted_bytes = strlen(num_buffer);
391 formatted_buf = num_buffer;
392 break;
395 default: {
396 /* Just reuse the 'spec' buffer */
397 spec[0] = ch;
398 spec[1] = '\0';
399 Jim_SetResultFormatted(interp, "bad field specifier \"%s\"", spec);
400 goto error;
404 if (!gotMinus) {
405 while (formatted_chars < width) {
406 Jim_AppendString(interp, resultPtr, &pad, 1);
407 formatted_chars++;
411 Jim_AppendString(interp, resultPtr, formatted_buf, formatted_bytes);
413 while (formatted_chars < width) {
414 Jim_AppendString(interp, resultPtr, &pad, 1);
415 formatted_chars++;
418 objIndex += gotSequential;
420 if (numBytes) {
421 Jim_AppendString(interp, resultPtr, span, numBytes);
424 Jim_Free(num_buffer);
425 return resultPtr;
427 errorMsg:
428 Jim_SetResultString(interp, msg, -1);
429 error:
430 Jim_FreeNewObj(interp, resultPtr);
431 Jim_Free(num_buffer);
432 return NULL;