tests: Add interactive mode tests
[jimtcl.git] / jim-format.c
blobe9b1d5cd0cbd6c82dc36ddd97290005d63ab6e79
1 /*
2 * Implements the internals of the format command for jim
4 * The FreeBSD license
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
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 "utf8.h"
48 #define JIM_INTEGER_SPACE 24
49 #define MAX_FLOAT_WIDTH 320
51 /**
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"
66 int formatLen;
67 Jim_Obj *resultPtr;
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) {
80 char *end;
81 int gotMinus, sawFlag;
82 int gotPrecision, useShort;
83 long width, precision;
84 int newXpg;
85 int ch;
86 int step;
87 int doubleType;
88 char pad = ' ';
89 char spec[2*JIM_INTEGER_SPACE + 12];
90 char *p;
92 int formatted_chars;
93 int formatted_bytes;
94 const char *formatted_buf;
96 step = utf8_tounicode(format, &ch);
97 format += step;
98 if (ch != '%') {
99 numBytes += step;
100 continue;
102 if (numBytes) {
103 Jim_AppendString(interp, resultPtr, span, numBytes);
104 numBytes = 0;
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);
114 if (ch == '%') {
115 span = format;
116 numBytes = step;
117 format += step;
118 continue;
122 * Step 1. XPG3 position specifier
125 newXpg = 0;
126 if (isdigit(ch)) {
127 int position = strtoul(format, &end, 10);
128 if (*end == '$') {
129 newXpg = 1;
130 objIndex = position - 1;
131 format = end + 1;
132 step = utf8_tounicode(format, &ch);
135 if (newXpg) {
136 if (gotSequential) {
137 msg = mixedXPG;
138 goto errorMsg;
140 gotXpg = 1;
141 } else {
142 if (gotXpg) {
143 msg = mixedXPG;
144 goto errorMsg;
146 gotSequential = 1;
148 if ((objIndex < 0) || (objIndex >= objc)) {
149 msg = badIndex[gotXpg];
150 goto errorMsg;
154 * Step 2. Set of flags. Also build up the sprintf spec.
156 p = spec;
157 *p++ = '%';
159 gotMinus = 0;
160 sawFlag = 1;
161 do {
162 switch (ch) {
163 case '-':
164 gotMinus = 1;
165 break;
166 case '0':
167 pad = ch;
168 break;
169 case ' ':
170 case '+':
171 case '#':
172 break;
173 default:
174 sawFlag = 0;
175 continue;
177 *p++ = ch;
178 format += step;
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.
187 width = 0;
188 if (isdigit(ch)) {
189 width = strtoul(format, &end, 10);
190 format = end;
191 step = utf8_tounicode(format, &ch);
192 } else if (ch == '*') {
193 if (objIndex >= objc - 1) {
194 msg = badIndex[gotXpg];
195 goto errorMsg;
197 if (Jim_GetLong(interp, objv[objIndex], &width) != JIM_OK) {
198 goto error;
200 if (width < 0) {
201 width = -width;
202 if (!gotMinus) {
203 *p++ = '-';
204 gotMinus = 1;
207 objIndex++;
208 format += step;
209 step = utf8_tounicode(format, &ch);
213 * Step 4. Precision.
216 gotPrecision = precision = 0;
217 if (ch == '.') {
218 gotPrecision = 1;
219 format += step;
220 step = utf8_tounicode(format, &ch);
222 if (isdigit(ch)) {
223 precision = strtoul(format, &end, 10);
224 format = end;
225 step = utf8_tounicode(format, &ch);
226 } else if (ch == '*') {
227 if (objIndex >= objc - 1) {
228 msg = badIndex[gotXpg];
229 goto errorMsg;
231 if (Jim_GetLong(interp, objv[objIndex], &precision) != JIM_OK) {
232 goto error;
236 * TODO: Check this truncation logic.
239 if (precision < 0) {
240 precision = 0;
242 objIndex++;
243 format += step;
244 step = utf8_tounicode(format, &ch);
248 * Step 5. Length modifier.
251 useShort = 0;
252 if (ch == 'h') {
253 useShort = 1;
254 format += step;
255 step = utf8_tounicode(format, &ch);
256 } else if (ch == 'l') {
257 /* Just for compatibility. All non-short integers are wide. */
258 format += step;
259 step = utf8_tounicode(format, &ch);
260 if (ch == 'l') {
261 format += step;
262 step = utf8_tounicode(format, &ch);
266 format += step;
267 span = format;
270 * Step 6. The actual conversion character.
273 if (ch == 'i') {
274 ch = 'd';
277 doubleType = 0;
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
284 switch (ch) {
285 case '\0':
286 msg = "format string ended in middle of field specifier";
287 goto errorMsg;
288 case 's': {
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);
296 break;
298 case 'c': {
299 jim_wide code;
301 if (Jim_GetWide(interp, objv[objIndex], &code) != JIM_OK) {
302 goto error;
304 /* Just store the value in the 'spec' buffer */
305 formatted_bytes = utf8_getchars(spec, code);
306 formatted_buf = spec;
307 formatted_chars = 1;
308 break;
310 case 'b': {
311 unsigned jim_wide w;
312 int length;
313 int i;
314 int j;
316 if (Jim_GetWide(interp, objv[objIndex], (jim_wide *)&w) != JIM_OK) {
317 goto error;
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);
331 j = 0;
332 for (i = length; i > 0; ) {
333 i--;
334 if (w & ((unsigned jim_wide)1 << i)) {
335 num_buffer[j++] = '1';
337 else if (j || i == 0) {
338 num_buffer[j++] = '0';
341 num_buffer[j] = 0;
342 formatted_chars = formatted_bytes = j;
343 formatted_buf = num_buffer;
344 break;
347 case 'e':
348 case 'E':
349 case 'f':
350 case 'g':
351 case 'G':
352 doubleType = 1;
353 /* fall through */
354 case 'd':
355 case 'u':
356 case 'o':
357 case 'x':
358 case 'X': {
359 jim_wide w;
360 double d;
361 int length;
363 /* Fill in the width and precision */
364 if (width) {
365 p += sprintf(p, "%ld", width);
367 if (gotPrecision) {
368 p += sprintf(p, ".%ld", precision);
371 /* Now the modifier, and get the actual value here */
372 if (doubleType) {
373 if (Jim_GetDouble(interp, objv[objIndex], &d) != JIM_OK) {
374 goto error;
376 length = MAX_FLOAT_WIDTH;
378 else {
379 if (Jim_GetWide(interp, objv[objIndex], &w) != JIM_OK) {
380 goto error;
382 length = JIM_INTEGER_SPACE;
383 if (useShort) {
384 if (ch == 'd') {
385 w = (short)w;
387 else {
388 w = (unsigned short)w;
391 *p++ = 'l';
392 #ifdef HAVE_LONG_LONG
393 if (sizeof(long long) == sizeof(jim_wide)) {
394 *p++ = 'l';
396 #endif
399 *p++ = (char) ch;
400 *p = '\0';
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);
405 goto error;
409 /* Adjust length for width and precision */
410 if (width > length) {
411 length = width;
413 if (gotPrecision) {
414 length += precision;
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);
423 if (doubleType) {
424 snprintf(num_buffer, length + 1, spec, d);
426 else {
427 formatted_bytes = snprintf(num_buffer, length + 1, spec, w);
429 formatted_chars = formatted_bytes = strlen(num_buffer);
430 formatted_buf = num_buffer;
431 break;
434 default: {
435 /* Just reuse the 'spec' buffer */
436 spec[0] = ch;
437 spec[1] = '\0';
438 Jim_SetResultFormatted(interp, "bad field specifier \"%s\"", spec);
439 goto error;
443 if (!gotMinus) {
444 while (formatted_chars < width) {
445 Jim_AppendString(interp, resultPtr, &pad, 1);
446 formatted_chars++;
450 Jim_AppendString(interp, resultPtr, formatted_buf, formatted_bytes);
452 while (formatted_chars < width) {
453 Jim_AppendString(interp, resultPtr, &pad, 1);
454 formatted_chars++;
457 objIndex += gotSequential;
459 if (numBytes) {
460 Jim_AppendString(interp, resultPtr, span, numBytes);
463 Jim_Free(num_buffer);
464 return resultPtr;
466 errorMsg:
467 Jim_SetResultString(interp, msg, -1);
468 error:
469 Jim_FreeNewObj(interp, resultPtr);
470 Jim_Free(num_buffer);
471 return NULL;