Fix a couple of minor build issues
[jimtcl.git] / jim-format.c
blob7876d3ec3c0a9e3df9d760ce4f77c883dc582ab5
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_UTF_MAX 3
49 #define JIM_INTEGER_SPACE 24
50 #define MAX_FLOAT_WIDTH 320
52 /**
53 * Apply the printf-like format in fmtObjPtr with the given arguments.
55 * Returns a new object with zero reference count if OK, or NULL on error.
57 Jim_Obj *Jim_FormatString(Jim_Interp *interp, Jim_Obj *fmtObjPtr, int objc, Jim_Obj *const *objv)
59 const char *span, *format, *formatEnd, *msg;
60 int numBytes = 0, objIndex = 0, gotXpg = 0, gotSequential = 0;
61 static const char * const mixedXPG =
62 "cannot mix \"%\" and \"%n$\" conversion specifiers";
63 static const char * const badIndex[2] = {
64 "not enough arguments for all format specifiers",
65 "\"%n$\" argument index out of range"
67 int formatLen;
68 Jim_Obj *resultPtr;
70 /* A single buffer is used to store numeric fields (with sprintf())
71 * This buffer is allocated/reallocated as necessary
73 char *num_buffer = NULL;
74 int num_buffer_size = 0;
76 span = format = Jim_GetString(fmtObjPtr, &formatLen);
77 formatEnd = format + formatLen;
78 resultPtr = Jim_NewEmptyStringObj(interp);
80 while (format != formatEnd) {
81 char *end;
82 int gotMinus, sawFlag;
83 int gotPrecision, useShort;
84 long width, precision;
85 int newXpg;
86 int ch;
87 int step;
88 int doubleType;
89 char pad = ' ';
90 char spec[2*JIM_INTEGER_SPACE + 12];
91 char *p;
93 int formatted_chars;
94 int formatted_bytes;
95 const char *formatted_buf;
97 step = utf8_tounicode(format, &ch);
98 format += step;
99 if (ch != '%') {
100 numBytes += step;
101 continue;
103 if (numBytes) {
104 Jim_AppendString(interp, resultPtr, span, numBytes);
105 numBytes = 0;
109 * Saw a % : process the format specifier.
111 * Step 0. Handle special case of escaped format marker (i.e., %%).
114 step = utf8_tounicode(format, &ch);
115 if (ch == '%') {
116 span = format;
117 numBytes = step;
118 format += step;
119 continue;
123 * Step 1. XPG3 position specifier
126 newXpg = 0;
127 if (isdigit(ch)) {
128 int position = strtoul(format, &end, 10);
129 if (*end == '$') {
130 newXpg = 1;
131 objIndex = position - 1;
132 format = end + 1;
133 step = utf8_tounicode(format, &ch);
136 if (newXpg) {
137 if (gotSequential) {
138 msg = mixedXPG;
139 goto errorMsg;
141 gotXpg = 1;
142 } else {
143 if (gotXpg) {
144 msg = mixedXPG;
145 goto errorMsg;
147 gotSequential = 1;
149 if ((objIndex < 0) || (objIndex >= objc)) {
150 msg = badIndex[gotXpg];
151 goto errorMsg;
155 * Step 2. Set of flags. Also build up the sprintf spec.
157 p = spec;
158 *p++ = '%';
160 gotMinus = 0;
161 sawFlag = 1;
162 do {
163 switch (ch) {
164 case '-':
165 gotMinus = 1;
166 break;
167 case '0':
168 pad = ch;
169 break;
170 case ' ':
171 case '+':
172 case '#':
173 break;
174 default:
175 sawFlag = 0;
176 continue;
178 *p++ = ch;
179 format += step;
180 step = utf8_tounicode(format, &ch);
181 } while (sawFlag);
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 *p++ = 'h';
385 if (ch == 'd') {
386 w = (short)w;
388 else {
389 w = (unsigned short)w;
392 else {
393 *p++ = 'l';
394 #ifdef HAVE_LONG_LONG
395 if (sizeof(long long) == sizeof(jim_wide)) {
396 *p++ = 'l';
398 #endif
402 *p++ = (char) ch;
403 *p = '\0';
405 /* Adjust length for width and precision */
406 if (width > length) {
407 length = width;
409 if (gotPrecision) {
410 length += precision;
413 /* Increase the size of the buffer if needed */
414 if (num_buffer_size < length + 1) {
415 num_buffer_size = length + 1;
416 num_buffer = Jim_Realloc(num_buffer, num_buffer_size);
419 if (doubleType) {
420 snprintf(num_buffer, length + 1, spec, d);
422 else {
423 formatted_bytes = snprintf(num_buffer, length + 1, spec, w);
425 formatted_chars = formatted_bytes = strlen(num_buffer);
426 formatted_buf = num_buffer;
427 break;
430 default: {
431 /* Just reuse the 'spec' buffer */
432 spec[0] = ch;
433 spec[1] = '\0';
434 Jim_SetResultFormatted(interp, "bad field specifier \"%s\"", spec);
435 goto error;
439 if (!gotMinus) {
440 while (formatted_chars < width) {
441 Jim_AppendString(interp, resultPtr, &pad, 1);
442 formatted_chars++;
446 Jim_AppendString(interp, resultPtr, formatted_buf, formatted_bytes);
448 while (formatted_chars < width) {
449 Jim_AppendString(interp, resultPtr, &pad, 1);
450 formatted_chars++;
453 objIndex += gotSequential;
455 if (numBytes) {
456 Jim_AppendString(interp, resultPtr, span, numBytes);
459 Jim_Free(num_buffer);
460 return resultPtr;
462 errorMsg:
463 Jim_SetResultString(interp, msg, -1);
464 error:
465 Jim_FreeNewObj(interp, resultPtr);
466 Jim_Free(num_buffer);
467 return NULL;