1 /* Object Pascal format strings.
2 Copyright (C) 2001-2004, 2006-2007, 2009-2010, 2018-2020, 2023 Free Software Foundation, Inc.
3 Written by Bruno Haible <haible@clisp.cons.org>, 2001.
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 /* Object Pascal format strings are usable with the "format" function in the
35 "sysutils" unit. They are described in
36 <https://www.freepascal.org/docs-html/rtl/sysutils/format.html>
37 and are implemented in fpc-2.4.0/rtl/objpas/sysutils/sysformt.inc.
38 Another implementation exists in Borland Delphi. The GNU Pascal's
39 "sysutils" doesn't (yet?) have the "format" function.
40
41 A directive
42 - starts with '%',
43 - either
44 - is finished with '%', or
45 - - is optionally followed by an index specification: '*' (reads an
46 argument, must be of type integer) or a nonempty digit sequence
47 or nothing (equivalent to 0), followed by ':',
48 - is optionally followed by '-', which acts as a flag,
49 - is optionally followed by a width specification: '*' (reads an
50 argument, must be of type integer) or a nonempty digit sequence,
51 - is optionally followed by '.' and a precision specification: '*'
52 (reads an argument, must be of type integer) or a nonempty digit
53 sequence,
54 - is finished by a case-insensitive specifier. If no index was
55 specified, it reads an argument; otherwise is uses the index-th
56 argument, 0-based.
57 - 'd', 'u', 'x', needs an 'integer' or 'int64' or 'qword' argument,
58 - 'e', 'f', 'g', 'n', 'm', need an 'extended' or 'currency' floating-
59 point argument,
60 - 's', needs a 'string', 'char', 'pchar', 'widestring', 'widechar',
61 'pwidechar' or 'ansistring' argument,
62 - 'p', needs a 'pointer' argument.
63 Numbered and unnumbered argument specifications can be used in the same
64 string. Numbered argument specifications have no influence on the
65 "current argument index", that is incremented each time an argument is read.
66 */
67
68 enum format_arg_type
69 {
70 FAT_INTEGER, /* integer, int64, qword */
71 FAT_FLOAT, /* extended, currency */
72 FAT_STRING, /* string, char, pchar, widestring, widechar, pwidechar,
73 ansistring */
74 FAT_POINTER
75 };
76
77 struct numbered_arg
78 {
79 unsigned int number;
80 enum format_arg_type type;
81 };
82
83 struct spec
84 {
85 unsigned int directives;
86 unsigned int numbered_arg_count;
87 struct numbered_arg *numbered;
88 };
89
90 /* Locale independent test for a decimal digit.
91 Argument can be 'char' or 'unsigned char'. (Whereas the argument of
92 <ctype.h> isdigit must be an 'unsigned char'.) */
93 #undef isdigit
94 #define isdigit(c) ((unsigned int) ((c) - '0') < 10)
95
96
97 static int
98 numbered_arg_compare (const void *p1, const void *p2)
99 {
100 unsigned int n1 = ((const struct numbered_arg *) p1)->number;
101 unsigned int n2 = ((const struct numbered_arg *) p2)->number;
102
103 return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
104 }
105
106 static void *
107 format_parse (const char *format, bool translated, char *fdi,
108 char **invalid_reason)
109 {
110 const char *const format_start = format;
111 unsigned int directives;
112 unsigned int numbered_arg_count;
113 struct numbered_arg *numbered;
114 unsigned int numbered_allocated;
115 unsigned int unnumbered_arg_count;
116 struct spec *result;
117
118 enum arg_index
119 {
120 index_numbered, /* index given by a fixed integer */
121 index_unnumbered, /* index given by unnumbered_arg_count++ */
122 index_unknown /* index is only known at run time */
123 };
124
125 directives = 0;
126 numbered_arg_count = 0;
127 numbered = NULL;
128 numbered_allocated = 0;
129 unnumbered_arg_count = 0;
130
131 for (; *format != '\0';)
132 if (*format++ == '%')
133 {
134 /* A directive. */
135 FDI_SET (format - 1, FMTDIR_START);
136 directives++;
137
138 if (*format != '%')
139 {
140 /* A complex directive. */
141 enum arg_index main_arg = index_unnumbered;
142 unsigned int main_number = 0;
143 enum format_arg_type type;
144
145 if (isdigit (*format) || *format == ':')
146 {
147 const char *f = format;
148 unsigned int m = 0;
149
150 while (isdigit (*f))
151 {
152 m = 10 * m + (*f - '0');
153 f++;
154 }
155
156 if (*f == ':')
157 {
158 main_number = m;
159 main_arg = index_numbered;
160 format = ++f;
161 }
162 }
163 else if (*format == '*')
164 {
165 if (format[1] == ':')
166 {
167 main_arg = index_unknown;
168 format += 2;
169 }
170 }
171
172 /* Parse flags. */
173 if (*format == '-')
174 format++;
175
176 /* Parse width. */
177 if (isdigit (*format))
178 {
179 do
180 format++;
181 while (isdigit (*format));
182 }
183 else if (*format == '*')
184 {
185 /* Unnumbered argument of type FAT_INTEGER. */
186 if (numbered_allocated == numbered_arg_count)
187 {
188 numbered_allocated = 2 * numbered_allocated + 1;
189 numbered = (struct numbered_arg *) xrealloc (numbered, numbered_allocated * sizeof (struct numbered_arg));
190 }
191 numbered[numbered_arg_count].number = unnumbered_arg_count;
192 numbered[numbered_arg_count].type = FAT_INTEGER;
193 numbered_arg_count++;
194 unnumbered_arg_count++;
195
196 format++;
197 }
198
199 /* Parse precision. */
200 if (*format == '.')
201 {
202 format++;
203
204 if (isdigit (*format))
205 {
206 do
207 format++;
208 while (isdigit (*format));
209 }
210 else if (*format == '*')
211 {
212 /* Unnumbered argument of type FAT_INTEGER. */
213 if (numbered_allocated == unnumbered_arg_count)
214 {
215 numbered_allocated = 2 * numbered_allocated + 1;
216 numbered = (struct numbered_arg *) xrealloc (numbered, numbered_allocated * sizeof (struct numbered_arg));
217 }
218 numbered[numbered_arg_count].number = unnumbered_arg_count;
219 numbered[numbered_arg_count].type = FAT_INTEGER;
220 numbered_arg_count++;
221 unnumbered_arg_count++;
222
223 format++;
224 }
225 else
226 --format; /* will jump to bad_format */
227 }
228
229 switch (c_tolower (*format))
230 {
231 case 'd': case 'u': case 'x':
232 type = FAT_INTEGER;
233 break;
234 case 'e': case 'f': case 'g': case 'n': case 'm':
235 type = FAT_FLOAT;
236 break;
237 case 's':
238 type = FAT_STRING;
239 break;
240 case 'p':
241 type = FAT_POINTER;
242 break;
243 default:
244 if (*format == '\0')
245 {
246 *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
247 FDI_SET (format - 1, FMTDIR_ERROR);
248 }
249 else
250 {
251 *invalid_reason =
252 INVALID_CONVERSION_SPECIFIER (directives, *format);
253 FDI_SET (format, FMTDIR_ERROR);
254 }
255 goto bad_format;
256 }
257
258 if (numbered_allocated == numbered_arg_count)
259 {
260 numbered_allocated = 2 * numbered_allocated + 1;
261 numbered = (struct numbered_arg *) xrealloc (numbered, numbered_allocated * sizeof (struct numbered_arg));
262 }
263 switch (main_arg)
264 {
265 case index_unnumbered:
266 numbered[numbered_arg_count].number = unnumbered_arg_count;
267 numbered[numbered_arg_count].type = type;
268 unnumbered_arg_count++;
269 break;
270 case index_numbered:
271 numbered[numbered_arg_count].number = main_number;
272 numbered[numbered_arg_count].type = type;
273 break;
274 case index_unknown:
275 numbered[numbered_arg_count].number = unnumbered_arg_count;
276 numbered[numbered_arg_count].type = FAT_INTEGER;
277 unnumbered_arg_count++;
278 break;
279 default:
280 abort ();
281 }
282 numbered_arg_count++;
283 }
284
285 FDI_SET (format, FMTDIR_END);
286
287 format++;
288 }
289
290 /* Sort the numbered argument array, and eliminate duplicates. */
291 if (numbered_arg_count > 1)
292 {
293 unsigned int i, j;
294 bool err;
295
296 qsort (numbered, numbered_arg_count,
297 sizeof (struct numbered_arg), numbered_arg_compare);
298
299 /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */
300 err = false;
301 for (i = j = 0; i < numbered_arg_count; i++)
302 if (j > 0 && numbered[i].number == numbered[j-1].number)
303 {
304 enum format_arg_type type1 = numbered[i].type;
305 enum format_arg_type type2 = numbered[j-1].type;
306 enum format_arg_type type_both;
307
308 if (type1 == type2)
309 type_both = type1;
310 else
311 {
312 /* Incompatible types. */
313 type_both = type1;
314 if (!err)
315 *invalid_reason =
316 INVALID_INCOMPATIBLE_ARG_TYPES (numbered[i].number);
317 err = true;
318 }
319
320 numbered[j-1].type = type_both;
321 }
322 else
323 {
324 if (j < i)
325 {
326 numbered[j].number = numbered[i].number;
327 numbered[j].type = numbered[i].type;
328 }
329 j++;
330 }
331 numbered_arg_count = j;
332 if (err)
333 /* *invalid_reason has already been set above. */
334 goto bad_format;
335 }
336
337 result = XMALLOC (struct spec);
338 result->directives = directives;
339 result->numbered_arg_count = numbered_arg_count;
340 result->numbered = numbered;
341 return result;
342
343 bad_format:
344 if (numbered != NULL)
345 free (numbered);
346 return NULL;
347 }
348
349 static void
350 format_free (void *descr)
351 {
352 struct spec *spec = (struct spec *) descr;
353
354 if (spec->numbered != NULL)
355 free (spec->numbered);
356 free (spec);
357 }
358
359 static int
360 format_get_number_of_directives (void *descr)
361 {
362 struct spec *spec = (struct spec *) descr;
363
364 return spec->directives;
365 }
366
367 static bool
368 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
369 formatstring_error_logger_t error_logger,
370 const char *pretty_msgid, const char *pretty_msgstr)
371 {
372 struct spec *spec1 = (struct spec *) msgid_descr;
373 struct spec *spec2 = (struct spec *) msgstr_descr;
374 bool err = false;
375
376 if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
377 {
378 unsigned int i, j;
379 unsigned int n1 = spec1->numbered_arg_count;
380 unsigned int n2 = spec2->numbered_arg_count;
381
382 /* Check that the argument numbers are the same.
383 Both arrays are sorted. We search for the first difference. */
384 for (i = 0, j = 0; i < n1 || j < n2; )
385 {
386 int cmp = (i >= n1 ? 1 :
387 j >= n2 ? -1 :
388 spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
389 spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
390 0);
391
392 if (cmp > 0)
393 {
394 if (error_logger)
395 error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"),
396 spec2->numbered[j].number, pretty_msgstr,
397 pretty_msgid);
398 err = true;
399 break;
400 }
401 else if (cmp < 0)
402 {
403 if (equality)
404 {
405 if (error_logger)
406 error_logger (_("a format specification for argument %u doesn't exist in '%s'"),
407 spec1->numbered[i].number, pretty_msgstr);
408 err = true;
409 break;
410 }
411 else
412 i++;
413 }
414 else
415 j++, i++;
416 }
417 /* Check the argument types are the same. */
418 if (!err)
419 for (i = 0, j = 0; j < n2; )
420 {
421 if (spec1->numbered[i].number == spec2->numbered[j].number)
422 {
423 if (spec1->numbered[i].type != spec2->numbered[j].type)
424 {
425 if (error_logger)
426 error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"),
427 pretty_msgid, pretty_msgstr,
428 spec2->numbered[j].number);
429 err = true;
430 break;
431 }
432 j++, i++;
433 }
434 else
435 i++;
436 }
437 }
438
439 return err;
440 }
441
442
443 struct formatstring_parser formatstring_pascal =
444 {
445 format_parse,
446 format_free,
447 format_get_number_of_directives,
448 NULL,
449 format_check
450 };
451
452
453 #ifdef TEST
454
455 /* Test program: Print the argument list specification returned by
456 format_parse for strings read from standard input. */
457
458 #include <stdio.h>
459
460 static void
461 format_print (void *descr)
462 {
463 struct spec *spec = (struct spec *) descr;
464 unsigned int last;
465 unsigned int i;
466
467 if (spec == NULL)
468 {
469 printf ("INVALID");
470 return;
471 }
472
473 printf ("(");
474 last = 0;
475 for (i = 0; i < spec->numbered_arg_count; i++)
476 {
477 unsigned int number = spec->numbered[i].number;
478
479 if (i > 0)
480 printf (" ");
481 if (number < last)
482 abort ();
483 for (; last < number; last++)
484 printf ("_ ");
485 switch (spec->numbered[i].type)
486 {
487 case FAT_INTEGER:
488 printf ("i");
489 break;
490 case FAT_FLOAT:
491 printf ("f");
492 break;
493 case FAT_STRING:
494 printf ("s");
495 break;
496 case FAT_POINTER:
497 printf ("p");
498 break;
499 default:
500 abort ();
501 }
502 last = number + 1;
503 }
504 printf (")");
505 }
506
507 int
508 main ()
509 {
510 for (;;)
511 {
512 char *line = NULL;
513 size_t line_size = 0;
514 int line_len;
515 char *invalid_reason;
516 void *descr;
517
518 line_len = getline (&line, &line_size, stdin);
519 if (line_len < 0)
520 break;
521 if (line_len > 0 && line[line_len - 1] == '\n')
522 line[--line_len] = '\0';
523
524 invalid_reason = NULL;
525 descr = format_parse (line, false, NULL, &invalid_reason);
526
527 format_print (descr);
528 printf ("\n");
529 if (descr == NULL)
530 printf ("%s\n", invalid_reason);
531
532 free (invalid_reason);
533 free (line);
534 }
535
536 return 0;
537 }
538
539 /*
540 * For Emacs M-x compile
541 * Local Variables:
542 * 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-pascal.c ../gnulib-lib/libgettextlib.la"
543 * End:
544 */
545
546 #endif /* TEST */