zlib: Don't use PASTE for INTMAX error messages
[jimtcl.git] / jim-format.c
blobdc6f8aeda4f41658c0e3cbdc265815f1ce9ec2fd
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 } while (sawFlag);
183 * Step 3. Minimum field width.
186 width = 0;
187 if (isdigit(ch)) {
188 width = strtoul(format, &end, 10);
189 format = end;
190 step = utf8_tounicode(format, &ch);
191 } else if (ch == '*') {
192 if (objIndex >= objc - 1) {
193 msg = badIndex[gotXpg];
194 goto errorMsg;
196 if (Jim_GetLong(interp, objv[objIndex], &width) != JIM_OK) {
197 goto error;
199 if (width < 0) {
200 width = -width;
201 if (!gotMinus) {
202 *p++ = '-';
203 gotMinus = 1;
206 objIndex++;
207 format += step;
208 step = utf8_tounicode(format, &ch);
212 * Step 4. Precision.
215 gotPrecision = precision = 0;
216 if (ch == '.') {
217 gotPrecision = 1;
218 format += step;
219 step = utf8_tounicode(format, &ch);
221 if (isdigit(ch)) {
222 precision = strtoul(format, &end, 10);
223 format = end;
224 step = utf8_tounicode(format, &ch);
225 } else if (ch == '*') {
226 if (objIndex >= objc - 1) {
227 msg = badIndex[gotXpg];
228 goto errorMsg;
230 if (Jim_GetLong(interp, objv[objIndex], &precision) != JIM_OK) {
231 goto error;
235 * TODO: Check this truncation logic.
238 if (precision < 0) {
239 precision = 0;
241 objIndex++;
242 format += step;
243 step = utf8_tounicode(format, &ch);
247 * Step 5. Length modifier.
250 useShort = 0;
251 if (ch == 'h') {
252 useShort = 1;
253 format += step;
254 step = utf8_tounicode(format, &ch);
255 } else if (ch == 'l') {
256 /* Just for compatibility. All non-short integers are wide. */
257 format += step;
258 step = utf8_tounicode(format, &ch);
259 if (ch == 'l') {
260 format += step;
261 step = utf8_tounicode(format, &ch);
265 format += step;
266 span = format;
269 * Step 6. The actual conversion character.
272 if (ch == 'i') {
273 ch = 'd';
276 doubleType = 0;
278 /* Each valid conversion will set:
279 * formatted_buf - the result to be added
280 * formatted_chars - the length of formatted_buf in characters
281 * formatted_bytes - the length of formatted_buf in bytes
283 switch (ch) {
284 case '\0':
285 msg = "format string ended in middle of field specifier";
286 goto errorMsg;
287 case 's': {
288 formatted_buf = Jim_GetString(objv[objIndex], &formatted_bytes);
289 formatted_chars = Jim_Utf8Length(interp, objv[objIndex]);
290 if (gotPrecision && (precision < formatted_chars)) {
291 /* Need to build a (null terminated) truncated string */
292 formatted_chars = precision;
293 formatted_bytes = utf8_index(formatted_buf, precision);
295 break;
297 case 'c': {
298 jim_wide code;
300 if (Jim_GetWide(interp, objv[objIndex], &code) != JIM_OK) {
301 goto error;
303 /* Just store the value in the 'spec' buffer */
304 formatted_bytes = utf8_getchars(spec, code);
305 formatted_buf = spec;
306 formatted_chars = 1;
307 break;
309 case 'b': {
310 unsigned jim_wide w;
311 int length;
312 int i;
313 int j;
315 if (Jim_GetWide(interp, objv[objIndex], (jim_wide *)&w) != JIM_OK) {
316 goto error;
318 length = sizeof(w) * 8;
320 /* XXX: width and precision not yet implemented for binary
321 * also flags in 'spec', e.g. #, 0, -
324 /* Increase the size of the buffer if needed */
325 if (num_buffer_size < length + 1) {
326 num_buffer_size = length + 1;
327 num_buffer = Jim_Realloc(num_buffer, num_buffer_size);
330 j = 0;
331 for (i = length; i > 0; ) {
332 i--;
333 if (w & ((unsigned jim_wide)1 << i)) {
334 num_buffer[j++] = '1';
336 else if (j || i == 0) {
337 num_buffer[j++] = '0';
340 num_buffer[j] = 0;
341 formatted_chars = formatted_bytes = j;
342 formatted_buf = num_buffer;
343 break;
346 case 'e':
347 case 'E':
348 case 'f':
349 case 'g':
350 case 'G':
351 doubleType = 1;
352 /* fall through */
353 case 'd':
354 case 'u':
355 case 'o':
356 case 'x':
357 case 'X': {
358 jim_wide w;
359 double d;
360 int length;
362 /* Fill in the width and precision */
363 if (width) {
364 p += sprintf(p, "%ld", width);
366 if (gotPrecision) {
367 p += sprintf(p, ".%ld", precision);
370 /* Now the modifier, and get the actual value here */
371 if (doubleType) {
372 if (Jim_GetDouble(interp, objv[objIndex], &d) != JIM_OK) {
373 goto error;
375 length = MAX_FLOAT_WIDTH;
377 else {
378 if (Jim_GetWide(interp, objv[objIndex], &w) != JIM_OK) {
379 goto error;
381 length = JIM_INTEGER_SPACE;
382 if (useShort) {
383 if (ch == 'd') {
384 w = (short)w;
386 else {
387 w = (unsigned short)w;
390 *p++ = 'l';
391 #ifdef HAVE_LONG_LONG
392 if (sizeof(long long) == sizeof(jim_wide)) {
393 *p++ = 'l';
395 #endif
398 *p++ = (char) ch;
399 *p = '\0';
401 /* Adjust length for width and precision */
402 if (width > length) {
403 length = width;
405 if (gotPrecision) {
406 length += precision;
409 /* Increase the size of the buffer if needed */
410 if (num_buffer_size < length + 1) {
411 num_buffer_size = length + 1;
412 num_buffer = Jim_Realloc(num_buffer, num_buffer_size);
415 if (doubleType) {
416 snprintf(num_buffer, length + 1, spec, d);
418 else {
419 formatted_bytes = snprintf(num_buffer, length + 1, spec, w);
421 formatted_chars = formatted_bytes = strlen(num_buffer);
422 formatted_buf = num_buffer;
423 break;
426 default: {
427 /* Just reuse the 'spec' buffer */
428 spec[0] = ch;
429 spec[1] = '\0';
430 Jim_SetResultFormatted(interp, "bad field specifier \"%s\"", spec);
431 goto error;
435 if (!gotMinus) {
436 while (formatted_chars < width) {
437 Jim_AppendString(interp, resultPtr, &pad, 1);
438 formatted_chars++;
442 Jim_AppendString(interp, resultPtr, formatted_buf, formatted_bytes);
444 while (formatted_chars < width) {
445 Jim_AppendString(interp, resultPtr, &pad, 1);
446 formatted_chars++;
449 objIndex += gotSequential;
451 if (numBytes) {
452 Jim_AppendString(interp, resultPtr, span, numBytes);
455 Jim_Free(num_buffer);
456 return resultPtr;
458 errorMsg:
459 Jim_SetResultString(interp, msg, -1);
460 error:
461 Jim_FreeNewObj(interp, resultPtr);
462 Jim_Free(num_buffer);
463 return NULL;