Add support for [apply]
[jimtcl.git] / jim-format.c
blobbf149a2f60fe47847a8d66974f5a23a15268ab90
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_NewStringObj(interp, "", 0);
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_fromunicode(spec, code);
306 formatted_buf = spec;
307 formatted_chars = 1;
308 break;
311 case 'e':
312 case 'E':
313 case 'f':
314 case 'g':
315 case 'G':
316 doubleType = 1;
317 /* fall through */
318 case 'd':
319 case 'u':
320 case 'o':
321 case 'x':
322 case 'X': {
323 jim_wide w;
324 double d;
325 int length;
327 /* Fill in the width and precision */
328 if (width) {
329 p += sprintf(p, "%ld", width);
331 if (gotPrecision) {
332 p += sprintf(p, ".%ld", precision);
335 /* Now the modifier, and get the actual value here */
336 if (doubleType) {
337 if (Jim_GetDouble(interp, objv[objIndex], &d) != JIM_OK) {
338 goto error;
340 length = MAX_FLOAT_WIDTH;
342 else {
343 if (Jim_GetWide(interp, objv[objIndex], &w) != JIM_OK) {
344 goto error;
346 length = JIM_INTEGER_SPACE;
347 if (useShort) {
348 *p++ = 'h';
349 if (ch == 'd') {
350 w = (short)w;
352 else {
353 w = (unsigned short)w;
356 else {
357 *p++ = 'l';
358 #ifdef HAVE_LONG_LONG
359 if (sizeof(long long) == sizeof(jim_wide)) {
360 *p++ = 'l';
362 #endif
366 *p++ = (char) ch;
367 *p = '\0';
369 /* Adjust length for width and precision */
370 if (width > length) {
371 length = width;
373 if (gotPrecision) {
374 length += precision;
377 /* Increase the size of the buffer if needed */
378 if (num_buffer_size < length + 1) {
379 num_buffer_size = length + 1;
380 num_buffer = Jim_Realloc(num_buffer, num_buffer_size);
383 if (doubleType) {
384 snprintf(num_buffer, length + 1, spec, d);
386 else {
387 formatted_bytes = snprintf(num_buffer, length + 1, spec, w);
389 formatted_chars = formatted_bytes = strlen(num_buffer);
390 formatted_buf = num_buffer;
391 break;
394 default: {
395 /* Just reuse the 'spec' buffer */
396 spec[0] = ch;
397 spec[1] = '\0';
398 Jim_SetResultFormatted(interp, "bad field specifier \"%s\"", spec);
399 goto error;
403 if (!gotMinus) {
404 while (formatted_chars < width) {
405 Jim_AppendString(interp, resultPtr, &pad, 1);
406 formatted_chars++;
410 Jim_AppendString(interp, resultPtr, formatted_buf, formatted_bytes);
412 while (formatted_chars < width) {
413 Jim_AppendString(interp, resultPtr, &pad, 1);
414 formatted_chars++;
417 objIndex += gotSequential;
419 if (numBytes) {
420 Jim_AppendString(interp, resultPtr, span, numBytes);
423 Jim_Free(num_buffer);
424 return resultPtr;
426 errorMsg:
427 Jim_SetResultString(interp, msg, -1);
428 error:
429 Jim_FreeNewObj(interp, resultPtr);
430 Jim_Free(num_buffer);
431 return NULL;