1 /* Tcl format strings.
2 Copyright (C) 2001-2004, 2006-2007, 2009, 2019-2020, 2023 Free Software Foundation, Inc.
3 Written by Bruno Haible <haible@clisp.cons.org>, 2002.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21
22 #include <stdbool.h>
23 #include <stdlib.h>
24
25 #include "format.h"
26 #include "c-ctype.h"
27 #include "xalloc.h"
28 #include "xvasprintf.h"
29 #include "format-invalid.h"
30 #include "gettext.h"
31
32 #define _(str) gettext (str)
33
34 /* Tcl format strings are described in the tcl8.3.3/doc/format.n manual
35 page and implemented in the function Tcl_FormatObjCmd in
36 tcl8.3.3/generic/tclCmdAH.c.
37 A directive
38 - starts with '%' or '%m$' where m is a positive integer,
39 - is optionally followed by any of the characters '#', '0', '-', ' ', '+',
40 each of which acts as a flag,
41 - is optionally followed by a width specification: '*' (reads an argument)
42 or a nonempty digit sequence,
43 - is optionally followed by '.' and a precision specification: '*' (reads
44 an argument) or a nonempty digit sequence,
45 - is optionally followed by a size specifier, 'h' or 'l'. 'l' is ignored.
46 - is finished by a specifier
47 - '%', that needs no argument,
48 - 'c', that needs a character argument,
49 - 's', that needs a string argument,
50 - 'i', 'd', that need a signed integer argument,
51 - 'o', 'u', 'x', 'X', that need an unsigned integer argument,
52 - 'e', 'E', 'f', 'g', 'G', that need a floating-point argument.
53 Numbered ('%m$') and unnumbered argument specifications cannot be used
54 in the same string.
55 */
56
57 enum format_arg_type
58 {
59 FAT_NONE,
60 FAT_CHARACTER,
61 FAT_STRING,
62 FAT_INTEGER,
63 FAT_UNSIGNED_INTEGER,
64 FAT_SHORT_INTEGER,
65 FAT_SHORT_UNSIGNED_INTEGER,
66 FAT_FLOAT
67 };
68
69 struct numbered_arg
70 {
71 unsigned int number;
72 enum format_arg_type type;
73 };
74
75 struct spec
76 {
77 unsigned int directives;
78 unsigned int numbered_arg_count;
79 struct numbered_arg *numbered;
80 };
81
82 /* Locale independent test for a decimal digit.
83 Argument can be 'char' or 'unsigned char'. (Whereas the argument of
84 <ctype.h> isdigit must be an 'unsigned char'.) */
85 #undef isdigit
86 #define isdigit(c) ((unsigned int) ((c) - '0') < 10)
87
88
89 static int
90 numbered_arg_compare (const void *p1, const void *p2)
91 {
92 unsigned int n1 = ((const struct numbered_arg *) p1)->number;
93 unsigned int n2 = ((const struct numbered_arg *) p2)->number;
94
95 return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
96 }
97
98 static void *
99 format_parse (const char *format, bool translated, char *fdi,
100 char **invalid_reason)
101 {
102 const char *const format_start = format;
103 struct spec spec;
104 unsigned int numbered_allocated;
105 struct spec *result;
106 bool seen_numbered_arg;
107 bool seen_unnumbered_arg;
108 unsigned int number;
109
110 spec.directives = 0;
111 spec.numbered_arg_count = 0;
112 spec.numbered = NULL;
113 numbered_allocated = 0;
114 seen_numbered_arg = false;
115 seen_unnumbered_arg = false;
116 number = 1;
117
118 for (; *format != '\0';)
119 /* Invariant: !seen_numbered_arg || !seen_unnumbered_arg. */
120 if (*format++ == '%')
121 {
122 /* A directive. */
123 FDI_SET (format - 1, FMTDIR_START);
124 spec.directives++;
125
126 if (*format != '%')
127 {
128 bool is_numbered_arg;
129 bool short_flag;
130 enum format_arg_type type;
131
132 is_numbered_arg = false;
133 if (isdigit (*format))
134 {
135 const char *f = format;
136 unsigned int m = 0;
137
138 do
139 {
140 m = 10 * m + (*f - '0');
141 f++;
142 }
143 while (isdigit (*f));
144
145 if (*f == '$')
146 {
147 if (m == 0)
148 {
149 *invalid_reason = INVALID_ARGNO_0 (spec.directives);
150 FDI_SET (f, FMTDIR_ERROR);
151 goto bad_format;
152 }
153 number = m;
154 format = ++f;
155
156 /* Numbered and unnumbered specifications are exclusive. */
157 if (seen_unnumbered_arg)
158 {
159 *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
160 FDI_SET (format - 1, FMTDIR_ERROR);
161 goto bad_format;
162 }
163 is_numbered_arg = true;
164 seen_numbered_arg = true;
165 }
166 }
167
168 /* Numbered and unnumbered specifications are exclusive. */
169 if (!is_numbered_arg)
170 {
171 if (seen_numbered_arg)
172 {
173 *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
174 FDI_SET (format - 1, FMTDIR_ERROR);
175 goto bad_format;
176 }
177 seen_unnumbered_arg = true;
178 }
179
180 /* Parse flags. */
181 while (*format == ' ' || *format == '+' || *format == '-'
182 || *format == '#' || *format == '0')
183 format++;
184
185 /* Parse width. */
186 if (*format == '*')
187 {
188 format++;
189
190 if (numbered_allocated == spec.numbered_arg_count)
191 {
192 numbered_allocated = 2 * numbered_allocated + 1;
193 spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
194 }
195 spec.numbered[spec.numbered_arg_count].number = number;
196 spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
197 spec.numbered_arg_count++;
198
199 number++;
200 }
201 else if (isdigit (*format))
202 {
203 do format++; while (isdigit (*format));
204 }
205
206 /* Parse precision. */
207 if (*format == '.')
208 {
209 format++;
210
211 if (*format == '*')
212 {
213 format++;
214
215 if (numbered_allocated == spec.numbered_arg_count)
216 {
217 numbered_allocated = 2 * numbered_allocated + 1;
218 spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
219 }
220 spec.numbered[spec.numbered_arg_count].number = number;
221 spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
222 spec.numbered_arg_count++;
223
224 number++;
225 }
226 else if (isdigit (*format))
227 {
228 do format++; while (isdigit (*format));
229 }
230 }
231
232 /* Parse optional size specification. */
233 short_flag = false;
234 if (*format == 'h')
235 short_flag = true, format++;
236 else if (*format == 'l')
237 format++;
238
239 switch (*format)
240 {
241 case 'c':
242 type = FAT_CHARACTER;
243 break;
244 case 's':
245 type = FAT_STRING;
246 break;
247 case 'i': case 'd':
248 type = (short_flag ? FAT_SHORT_INTEGER : FAT_INTEGER);
249 break;
250 case 'u': case 'o': case 'x': case 'X':
251 type = (short_flag ? FAT_SHORT_UNSIGNED_INTEGER : FAT_UNSIGNED_INTEGER);
252 break;
253 case 'e': case 'E': case 'f': case 'g': case 'G':
254 type = FAT_FLOAT;
255 break;
256 default:
257 if (*format == '\0')
258 {
259 *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
260 FDI_SET (format - 1, FMTDIR_ERROR);
261 }
262 else
263 {
264 *invalid_reason =
265 INVALID_CONVERSION_SPECIFIER (spec.directives, *format);
266 FDI_SET (format, FMTDIR_ERROR);
267 }
268 goto bad_format;
269 }
270
271 if (numbered_allocated == spec.numbered_arg_count)
272 {
273 numbered_allocated = 2 * numbered_allocated + 1;
274 spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
275 }
276 spec.numbered[spec.numbered_arg_count].number = number;
277 spec.numbered[spec.numbered_arg_count].type = type;
278 spec.numbered_arg_count++;
279
280 number++;
281 }
282
283 FDI_SET (format, FMTDIR_END);
284
285 format++;
286 }
287
288 /* Sort the numbered argument array, and eliminate duplicates. */
289 if (spec.numbered_arg_count > 1)
290 {
291 unsigned int i, j;
292 bool err;
293
294 qsort (spec.numbered, spec.numbered_arg_count,
295 sizeof (struct numbered_arg), numbered_arg_compare);
296
297 /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */
298 err = false;
299 for (i = j = 0; i < spec.numbered_arg_count; i++)
300 if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
301 {
302 enum format_arg_type type1 = spec.numbered[i].type;
303 enum format_arg_type type2 = spec.numbered[j-1].type;
304 enum format_arg_type type_both;
305
306 if (type1 == type2)
307 type_both = type1;
308 else
309 {
310 /* Incompatible types. */
311 type_both = FAT_NONE;
312 if (!err)
313 *invalid_reason =
314 INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number);
315 err = true;
316 }
317
318 spec.numbered[j-1].type = type_both;
319 }
320 else
321 {
322 if (j < i)
323 {
324 spec.numbered[j].number = spec.numbered[i].number;
325 spec.numbered[j].type = spec.numbered[i].type;
326 }
327 j++;
328 }
329 spec.numbered_arg_count = j;
330 if (err)
331 /* *invalid_reason has already been set above. */
332 goto bad_format;
333 }
334
335 result = XMALLOC (struct spec);
336 *result = spec;
337 return result;
338
339 bad_format:
340 if (spec.numbered != NULL)
341 free (spec.numbered);
342 return NULL;
343 }
344
345 static void
346 format_free (void *descr)
347 {
348 struct spec *spec = (struct spec *) descr;
349
350 if (spec->numbered != NULL)
351 free (spec->numbered);
352 free (spec);
353 }
354
355 static int
356 format_get_number_of_directives (void *descr)
357 {
358 struct spec *spec = (struct spec *) descr;
359
360 return spec->directives;
361 }
362
363 static bool
364 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
365 formatstring_error_logger_t error_logger,
366 const char *pretty_msgid, const char *pretty_msgstr)
367 {
368 struct spec *spec1 = (struct spec *) msgid_descr;
369 struct spec *spec2 = (struct spec *) msgstr_descr;
370 bool err = false;
371
372 if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
373 {
374 unsigned int i, j;
375 unsigned int n1 = spec1->numbered_arg_count;
376 unsigned int n2 = spec2->numbered_arg_count;
377
378 /* Check that the argument numbers are the same.
379 Both arrays are sorted. We search for the first difference. */
380 for (i = 0, j = 0; i < n1 || j < n2; )
381 {
382 int cmp = (i >= n1 ? 1 :
383 j >= n2 ? -1 :
384 spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
385 spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
386 0);
387
388 if (cmp > 0)
389 {
390 if (error_logger)
391 error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"),
392 spec2->numbered[j].number, pretty_msgstr,
393 pretty_msgid);
394 err = true;
395 break;
396 }
397 else if (cmp < 0)
398 {
399 if (equality)
400 {
401 if (error_logger)
402 error_logger (_("a format specification for argument %u doesn't exist in '%s'"),
403 spec1->numbered[i].number, pretty_msgstr);
404 err = true;
405 break;
406 }
407 else
408 i++;
409 }
410 else
411 j++, i++;
412 }
413 /* Check the argument types are the same. */
414 if (!err)
415 for (i = 0, j = 0; j < n2; )
416 {
417 if (spec1->numbered[i].number == spec2->numbered[j].number)
418 {
419 if (spec1->numbered[i].type != spec2->numbered[j].type)
420 {
421 if (error_logger)
422 error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"),
423 pretty_msgid, pretty_msgstr,
424 spec2->numbered[j].number);
425 err = true;
426 break;
427 }
428 j++, i++;
429 }
430 else
431 i++;
432 }
433 }
434
435 return err;
436 }
437
438
439 struct formatstring_parser formatstring_tcl =
440 {
441 format_parse,
442 format_free,
443 format_get_number_of_directives,
444 NULL,
445 format_check
446 };
447
448
449 #ifdef TEST
450
451 /* Test program: Print the argument list specification returned by
452 format_parse for strings read from standard input. */
453
454 #include <stdio.h>
455
456 static void
457 format_print (void *descr)
458 {
459 struct spec *spec = (struct spec *) descr;
460 unsigned int last;
461 unsigned int i;
462
463 if (spec == NULL)
464 {
465 printf ("INVALID");
466 return;
467 }
468
469 printf ("(");
470 last = 1;
471 for (i = 0; i < spec->numbered_arg_count; i++)
472 {
473 unsigned int number = spec->numbered[i].number;
474
475 if (i > 0)
476 printf (" ");
477 if (number < last)
478 abort ();
479 for (; last < number; last++)
480 printf ("_ ");
481 switch (spec->numbered[i].type)
482 {
483 case FAT_CHARACTER:
484 printf ("c");
485 break;
486 case FAT_STRING:
487 printf ("s");
488 break;
489 case FAT_INTEGER:
490 printf ("i");
491 break;
492 case FAT_UNSIGNED_INTEGER:
493 printf ("[unsigned]i");
494 break;
495 case FAT_SHORT_INTEGER:
496 printf ("hi");
497 break;
498 case FAT_SHORT_UNSIGNED_INTEGER:
499 printf ("[unsigned]hi");
500 break;
501 case FAT_FLOAT:
502 printf ("f");
503 break;
504 default:
505 abort ();
506 }
507 last = number + 1;
508 }
509 printf (")");
510 }
511
512 int
513 main ()
514 {
515 for (;;)
516 {
517 char *line = NULL;
518 size_t line_size = 0;
519 int line_len;
520 char *invalid_reason;
521 void *descr;
522
523 line_len = getline (&line, &line_size, stdin);
524 if (line_len < 0)
525 break;
526 if (line_len > 0 && line[line_len - 1] == '\n')
527 line[--line_len] = '\0';
528
529 invalid_reason = NULL;
530 descr = format_parse (line, false, NULL, &invalid_reason);
531
532 format_print (descr);
533 printf ("\n");
534 if (descr == NULL)
535 printf ("%s\n", invalid_reason);
536
537 free (invalid_reason);
538 free (line);
539 }
540
541 return 0;
542 }
543
544 /*
545 * For Emacs M-x compile
546 * Local Variables:
547 * compile-command: "/bin/sh ../libtool --tag=CC --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../gnulib-lib -I../../gettext-runtime/intl -DHAVE_CONFIG_H -DTEST format-tcl.c ../gnulib-lib/libgettextlib.la"
548 * End:
549 */
550
551 #endif /* TEST */