Coverage Report

Created: 2026-08-17 07:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gettext/gettext-tools/src/format-java-printf.c
Line
Count
Source
1
/* Java printf format strings.
2
   Copyright (C) 2001-2026 Free Software Foundation, Inc.
3
4
   This program is free software: you can redistribute it and/or modify
5
   it under the terms of the GNU General Public License as published by
6
   the Free Software Foundation; either version 3 of the License, or
7
   (at your option) any later version.
8
9
   This program is distributed in the hope that it will be useful,
10
   but WITHOUT ANY WARRANTY; without even the implied warranty of
11
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
   GNU General Public License for more details.
13
14
   You should have received a copy of the GNU General Public License
15
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
16
17
/* Written by Bruno Haible.  */
18
19
#include <config.h>
20
21
#include <stdbool.h>
22
#include <stdlib.h>
23
24
#include "format.h"
25
#include "attribute.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
0
#define _(str) gettext (str)
33
34
/* Java printf format strings are described in java/util/Formatter.html.
35
   A directive
36
   - starts with '%' or '%<' or '%m$' where m is a positive integer,
37
   - is optionally followed by any of the characters '#', '0', '-', ' ', '+',
38
     ',', '(',
39
   - is optionally followed by a width specification: a nonempty digit sequence,
40
   - is optionally followed by '.' and a precision specification: a nonempty
41
     digit sequence,
42
   - is finished by a specifier
43
       - '%', 'n', that need no argument,
44
         Restrictions:
45
         - For '%': flags other than '-' are invalid, and a precision is
46
                    invalid.
47
         - For 'n': flags, width, and precision are invalid.
48
       - 'b', 'B', 'h', 'H', 's', 'S', that need a general argument.
49
         Restrictions:
50
         Flags other than '#' and '-' are invalid.
51
       - 'c', 'C', that need a character argument,
52
         Restrictions:
53
         Flags other than '-' are invalid.
54
         A precision is invalid.
55
       - 'd', 'o', 'x', 'X', that need an integer argument,
56
         Restrictions:
57
         - For 'd': The flag '#' is invalid.
58
         - For 'o', 'x', 'X': The flag ',' is invalid.
59
         A precision is invalid.
60
       - 'e', 'E', 'f', 'g', 'G', 'a', 'A', that need a floating-point argument,
61
         Restrictions:
62
         - For 'a', 'A': The flags ',', '(' are invalid.
63
       - 't', 'T', followed by one of
64
           'H', 'I', 'k', 'l', 'M', 'S', 'L', 'N', 'p', 'z', 'Z', 's', 'Q',
65
           'B', 'b', 'h', 'A', 'a', 'C', 'Y', 'y', 'j', 'm', 'd', 'e',
66
           'R', 'T', 'r', 'D', 'F', 'c'
67
         that need a date/time argument.
68
         Restrictions:
69
         Flags other than '-' are invalid.
70
         A precision is invalid.
71
   Numbered ('%m$') and unnumbered argument specifications can be mixed in the
72
   same string.  Numbered argument specifications have no influence on the
73
   unnumbered argument counter.
74
 */
75
76
enum format_arg_type
77
{
78
  FAT_NONE              = 0,
79
  /* Basic types */
80
  FAT_GENERAL           = 1,
81
  FAT_CHARACTER         = 2,
82
  FAT_INTEGER           = 3,
83
  FAT_FLOATINGPOINT     = 4,
84
  FAT_DATETIME          = 5
85
};
86
#ifdef __cplusplus
87
typedef int format_arg_type_t;
88
#else
89
typedef enum format_arg_type format_arg_type_t;
90
#endif
91
92
enum
93
{
94
  /* Flags */
95
  FAT_ALTERNATE         = 1 << 0, /* '#' */
96
  FAT_ZERO_PADDED       = 1 << 1, /* '0' */
97
  FAT_LEFT_JUSTIFIED    = 1 << 2, /* '-' */
98
  FAT_SPACE_SIGN        = 1 << 3, /* ' ' */
99
  FAT_SIGN              = 1 << 4, /* '+' */
100
  FAT_OBEY_LOCALE       = 1 << 5, /* ',' */
101
  FAT_MONETARY          = 1 << 6, /* '(' */
102
  /* Width */
103
  FAT_WIDTH             = 1 << 7,
104
  /* Precision */
105
  FAT_PRECISION         = 1 << 8,
106
};
107
108
struct numbered_arg
109
{
110
  size_t number;
111
  format_arg_type_t type;
112
};
113
114
struct spec
115
{
116
  size_t directives;
117
  size_t numbered_arg_count;
118
  struct numbered_arg *numbered
119
    COUNTED_BY (numbered_arg_count);
120
};
121
122
123
static int
124
numbered_arg_compare (const void *p1, const void *p2)
125
0
{
126
0
  size_t n1 = ((const struct numbered_arg *) p1)->number;
127
0
  size_t n2 = ((const struct numbered_arg *) p2)->number;
128
129
0
  return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
130
0
}
131
132
#define INVALID_LAST_ARG(directive_number) \
133
0
  xasprintf (_("In the directive number %zu, the reference to the argument of the previous directive is invalid."), directive_number)
134
135
#define INVALID_WIDTH_FOR(directive_number,conv_char) \
136
0
  xasprintf (_("In the directive number %zu, a width is invalid for the conversion '%c'."), directive_number, conv_char)
137
138
#define INVALID_PRECISION_FOR(directive_number,conv_char) \
139
0
  xasprintf (_("In the directive number %zu, a precision is invalid for the conversion '%c'."), directive_number, conv_char)
140
141
#define INVALID_DATETIME_CONVERSION_SUFFIX(directive_number,conv_char,suffix_char) \
142
0
  (c_isprint (conv_char) \
143
0
   ? xasprintf (_("In the directive number %zu, for the conversion '%c', the character '%c' is not a valid conversion suffix."), directive_number, conv_char, suffix_char) \
144
0
   : xasprintf (_("The character that terminates the directive number %zu, for the conversion '%c', is not a valid conversion suffix."), directive_number, conv_char))
145
146
static void *
147
format_parse (const char *format, bool translated, char *fdi,
148
              char **invalid_reason)
149
0
{
150
0
  const char *const format_start = format;
151
152
0
  struct spec spec;
153
0
  spec.directives = 0;
154
0
  spec.numbered_arg_count = 0;
155
0
  spec.numbered = NULL;
156
0
  size_t numbered_allocated = 0;
157
0
  size_t unnumbered_arg_count = 0;
158
0
  size_t last_arg_number = 0;
159
160
0
  for (; *format != '\0';)
161
0
    if (*format++ == '%')
162
0
      {
163
        /* A directive.  */
164
0
        FDI_SET (format - 1, FMTDIR_START);
165
0
        spec.directives++;
166
167
0
        size_t number = 0;
168
0
        if (*format == '<')
169
0
          {
170
0
            if (last_arg_number == 0)
171
0
              {
172
0
                *invalid_reason = INVALID_LAST_ARG (spec.directives);
173
0
                FDI_SET (format, FMTDIR_ERROR);
174
0
                goto bad_format;
175
0
              }
176
0
            number = last_arg_number;
177
0
            format++;
178
0
          }
179
0
        else if (c_isdigit (*format))
180
0
          {
181
0
            const char *f = format;
182
0
            size_t m = 0;
183
184
0
            do
185
0
              {
186
0
                m = 10 * m + (*f - '0');
187
0
                f++;
188
0
              }
189
0
            while (c_isdigit (*f));
190
191
0
            if (*f == '$')
192
0
              {
193
0
                if (m == 0)
194
0
                  {
195
0
                    *invalid_reason = INVALID_ARGNO_0 (spec.directives);
196
0
                    FDI_SET (f, FMTDIR_ERROR);
197
0
                    goto bad_format;
198
0
                  }
199
0
                number = m;
200
0
                format = ++f;
201
0
              }
202
0
          }
203
204
        /* Parse flags.  */
205
0
        unsigned int flags = 0;
206
0
        for (;;)
207
0
          {
208
0
            if (*format == '#')
209
0
              {
210
0
                flags |= FAT_ALTERNATE;
211
0
                format++;
212
0
              }
213
0
            else if (*format == '0')
214
0
              {
215
0
                flags |= FAT_ZERO_PADDED;
216
0
                format++;
217
0
              }
218
0
            else if (*format == '-')
219
0
              {
220
0
                flags |= FAT_LEFT_JUSTIFIED;
221
0
                format++;
222
0
              }
223
0
            else if (*format == ' ')
224
0
              {
225
0
                flags |= FAT_SPACE_SIGN;
226
0
                format++;
227
0
              }
228
0
            else if (*format == '+')
229
0
              {
230
0
                flags |= FAT_SIGN;
231
0
                format++;
232
0
              }
233
0
            else if (*format == ',')
234
0
              {
235
0
                flags |= FAT_OBEY_LOCALE;
236
0
                format++;
237
0
              }
238
0
            else if (*format == '(')
239
0
              {
240
0
                flags |= FAT_MONETARY;
241
0
                format++;
242
0
              }
243
0
            else
244
0
              break;
245
0
          }
246
247
        /* Parse width.  */
248
0
        if (c_isdigit (*format))
249
0
          {
250
0
            do format++; while (c_isdigit (*format));
251
0
            flags |= FAT_WIDTH;
252
0
          }
253
254
        /* Parse precision.  */
255
0
        if (*format == '.')
256
0
          {
257
0
            format++;
258
259
0
            if (!c_isdigit (*format))
260
0
              {
261
0
                if (*format == '\0')
262
0
                  {
263
0
                    *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
264
0
                    FDI_SET (format - 1, FMTDIR_ERROR);
265
0
                  }
266
0
                else
267
0
                  {
268
0
                    *invalid_reason = INVALID_PRECISION_MISSING (spec.directives);
269
0
                    FDI_SET (format, FMTDIR_ERROR);
270
0
                  }
271
0
                goto bad_format;
272
0
              }
273
274
0
            do format++; while (c_isdigit (*format));
275
0
            flags |= FAT_PRECISION;
276
0
          }
277
278
        /* Parse conversion.  */
279
0
        format_arg_type_t type;
280
0
        unsigned int invalid_flags;
281
0
        switch (*format)
282
0
          {
283
0
          case '%':
284
0
            type = FAT_NONE;
285
0
            invalid_flags = (FAT_ALTERNATE | FAT_ZERO_PADDED | FAT_SPACE_SIGN
286
0
                             | FAT_SIGN | FAT_OBEY_LOCALE | FAT_MONETARY)
287
0
                            | FAT_PRECISION;
288
0
            break;
289
0
          case 'n':
290
0
            type = FAT_NONE;
291
0
            invalid_flags = (FAT_ALTERNATE | FAT_ZERO_PADDED | FAT_LEFT_JUSTIFIED
292
0
                             | FAT_SPACE_SIGN | FAT_SIGN | FAT_OBEY_LOCALE
293
0
                             | FAT_MONETARY)
294
0
                            | FAT_WIDTH | FAT_PRECISION;
295
0
            break;
296
0
          case 'b': case 'B':
297
0
          case 'h': case 'H':
298
0
          case 's': case 'S':
299
0
            type = FAT_GENERAL;
300
0
            invalid_flags = (FAT_ZERO_PADDED | FAT_SPACE_SIGN | FAT_SIGN
301
0
                             | FAT_OBEY_LOCALE | FAT_MONETARY);
302
0
            break;
303
0
          case 'c': case 'C':
304
0
            type = FAT_CHARACTER;
305
0
            invalid_flags = (FAT_ALTERNATE | FAT_ZERO_PADDED | FAT_SPACE_SIGN
306
0
                             | FAT_SIGN | FAT_OBEY_LOCALE | FAT_MONETARY)
307
0
                            | FAT_PRECISION;
308
0
            break;
309
0
          case 'd':
310
0
            type = FAT_INTEGER;
311
0
            invalid_flags = FAT_ALTERNATE | FAT_PRECISION;
312
0
            break;
313
0
          case 'o': case 'x': case 'X':
314
0
            type = FAT_INTEGER;
315
0
            invalid_flags = FAT_OBEY_LOCALE | FAT_PRECISION;
316
0
            break;
317
0
          case 'e': case 'E':
318
0
          case 'f':
319
0
          case 'g': case 'G':
320
0
            type = FAT_FLOATINGPOINT;
321
0
            invalid_flags = 0;
322
0
            break;
323
0
          case 'a': case 'A':
324
0
            type = FAT_FLOATINGPOINT;
325
0
            invalid_flags = FAT_OBEY_LOCALE | FAT_MONETARY;
326
0
            break;
327
0
          case 't': case 'T':
328
0
            type = FAT_DATETIME;
329
0
            invalid_flags = (FAT_ALTERNATE | FAT_ZERO_PADDED | FAT_SPACE_SIGN
330
0
                             | FAT_SIGN | FAT_OBEY_LOCALE | FAT_MONETARY)
331
0
                            | FAT_PRECISION;
332
0
            break;
333
0
          default:
334
0
            if (*format == '\0')
335
0
              {
336
0
                *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
337
0
                FDI_SET (format - 1, FMTDIR_ERROR);
338
0
              }
339
0
            else
340
0
              {
341
0
                *invalid_reason =
342
0
                  INVALID_CONVERSION_SPECIFIER (spec.directives, *format);
343
0
                FDI_SET (format, FMTDIR_ERROR);
344
0
              }
345
0
            goto bad_format;
346
0
          }
347
348
        /* Report invalid flags, width, precision.  */
349
0
        invalid_flags &= flags;
350
0
        if (invalid_flags & FAT_ALTERNATE)
351
0
          {
352
0
            *invalid_reason = INVALID_FLAG_FOR (spec.directives, '#', *format);
353
0
            FDI_SET (format, FMTDIR_ERROR);
354
0
            goto bad_format;
355
0
          }
356
0
        if (invalid_flags & FAT_ZERO_PADDED)
357
0
          {
358
0
            *invalid_reason = INVALID_FLAG_FOR (spec.directives, '0', *format);
359
0
            FDI_SET (format, FMTDIR_ERROR);
360
0
            goto bad_format;
361
0
          }
362
0
        if (invalid_flags & FAT_LEFT_JUSTIFIED)
363
0
          {
364
0
            *invalid_reason = INVALID_FLAG_FOR (spec.directives, '-', *format);
365
0
            FDI_SET (format, FMTDIR_ERROR);
366
0
            goto bad_format;
367
0
          }
368
0
        if (invalid_flags & FAT_SPACE_SIGN)
369
0
          {
370
0
            *invalid_reason = INVALID_FLAG_FOR (spec.directives, ' ', *format);
371
0
            FDI_SET (format, FMTDIR_ERROR);
372
0
            goto bad_format;
373
0
          }
374
0
        if (invalid_flags & FAT_SIGN)
375
0
          {
376
0
            *invalid_reason = INVALID_FLAG_FOR (spec.directives, '+', *format);
377
0
            FDI_SET (format, FMTDIR_ERROR);
378
0
            goto bad_format;
379
0
          }
380
0
        if (invalid_flags & FAT_OBEY_LOCALE)
381
0
          {
382
0
            *invalid_reason = INVALID_FLAG_FOR (spec.directives, ',', *format);
383
0
            FDI_SET (format, FMTDIR_ERROR);
384
0
            goto bad_format;
385
0
          }
386
0
        if (invalid_flags & FAT_MONETARY)
387
0
          {
388
0
            *invalid_reason = INVALID_FLAG_FOR (spec.directives, '(', *format);
389
0
            FDI_SET (format, FMTDIR_ERROR);
390
0
            goto bad_format;
391
0
          }
392
0
        if (invalid_flags & FAT_WIDTH)
393
0
          {
394
0
            *invalid_reason = INVALID_WIDTH_FOR (spec.directives, *format);
395
0
            FDI_SET (format, FMTDIR_ERROR);
396
0
            goto bad_format;
397
0
          }
398
0
        if (invalid_flags & FAT_PRECISION)
399
0
          {
400
0
            *invalid_reason = INVALID_PRECISION_FOR (spec.directives, *format);
401
0
            FDI_SET (format, FMTDIR_ERROR);
402
0
            goto bad_format;
403
0
          }
404
405
0
        if (type == FAT_DATETIME)
406
0
          {
407
0
            format++;
408
409
            /* Parse conversion suffix.  */
410
0
            switch (*format)
411
0
              {
412
0
              case 'H': case 'I': case 'k': case 'l': case 'M': case 'S':
413
0
              case 'L': case 'N': case 'p': case 'z': case 'Z': case 's':
414
0
              case 'Q':
415
0
              case 'B': case 'b': case 'h': case 'A': case 'a': case 'C':
416
0
              case 'Y': case 'y': case 'j': case 'm': case 'd': case 'e':
417
0
              case 'R': case 'T': case 'r': case 'D': case 'F': case 'c':
418
0
                break;
419
0
              default:
420
0
                if (*format == '\0')
421
0
                  {
422
0
                    *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
423
0
                    FDI_SET (format - 1, FMTDIR_ERROR);
424
0
                  }
425
0
                else
426
0
                  {
427
0
                    *invalid_reason =
428
0
                      INVALID_DATETIME_CONVERSION_SUFFIX (spec.directives,
429
0
                                                          format[-1], *format);
430
0
                    FDI_SET (format, FMTDIR_ERROR);
431
0
                  }
432
0
                goto bad_format;
433
0
              }
434
0
          }
435
436
0
        if (type != FAT_NONE)
437
0
          {
438
0
            if (number == 0)
439
0
              number = ++unnumbered_arg_count;
440
441
0
            if (numbered_allocated == spec.numbered_arg_count)
442
0
              {
443
0
                numbered_allocated = 2 * numbered_allocated + 1;
444
0
                spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
445
0
              }
446
0
            size_t numbered_index = spec.numbered_arg_count++;
447
0
            spec.numbered[numbered_index].number = number;
448
0
            spec.numbered[numbered_index].type = type;
449
450
0
            last_arg_number = number;
451
0
          }
452
453
0
        FDI_SET (format, FMTDIR_END);
454
455
0
        format++;
456
0
      }
457
458
  /* Sort the numbered argument array, and eliminate duplicates.  */
459
0
  if (spec.numbered_arg_count > 1)
460
0
    {
461
0
      qsort (spec.numbered, spec.numbered_arg_count,
462
0
             sizeof (struct numbered_arg), numbered_arg_compare);
463
464
      /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
465
0
      bool err = false;
466
0
      size_t i, j;
467
0
      for (i = j = 0; i < spec.numbered_arg_count; i++)
468
0
        if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
469
0
          {
470
0
            enum format_arg_type type1 = spec.numbered[i].type;
471
0
            enum format_arg_type type2 = spec.numbered[j-1].type;
472
473
0
            enum format_arg_type type_both;
474
0
            if (type1 == type2)
475
0
              type_both = type1;
476
0
            else
477
0
              {
478
                /* Incompatible types.  */
479
0
                type_both = FAT_NONE;
480
0
                if (!err)
481
0
                  *invalid_reason =
482
0
                    INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number);
483
0
                err = true;
484
0
              }
485
486
0
            spec.numbered[j-1].type = type_both;
487
0
          }
488
0
        else
489
0
          {
490
0
            if (j < i)
491
0
              {
492
0
                spec.numbered[j].number = spec.numbered[i].number;
493
0
                spec.numbered[j].type = spec.numbered[i].type;
494
0
              }
495
0
            j++;
496
0
          }
497
0
      spec.numbered_arg_count = j;
498
0
      if (err)
499
        /* *invalid_reason has already been set above.  */
500
0
        goto bad_format;
501
0
    }
502
503
0
  struct spec *result = XMALLOC (struct spec);
504
0
  *result = spec;
505
0
  return result;
506
507
0
 bad_format:
508
0
  if (spec.numbered != NULL)
509
0
    free (spec.numbered);
510
0
  return NULL;
511
0
}
512
513
static void
514
format_free (void *descr)
515
0
{
516
0
  struct spec *spec = (struct spec *) descr;
517
518
0
  if (spec->numbered != NULL)
519
0
    free (spec->numbered);
520
0
  free (spec);
521
0
}
522
523
static int
524
format_get_number_of_directives (void *descr)
525
0
{
526
0
  struct spec *spec = (struct spec *) descr;
527
528
0
  return spec->directives;
529
0
}
530
531
static bool
532
format_check (void *msgid_descr, void *msgstr_descr, bool equality,
533
              formatstring_error_logger_t error_logger, void *error_logger_data,
534
              const char *pretty_msgid, const char *pretty_msgstr)
535
0
{
536
0
  struct spec *spec1 = (struct spec *) msgid_descr;
537
0
  struct spec *spec2 = (struct spec *) msgstr_descr;
538
0
  bool err = false;
539
540
0
  if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
541
0
    {
542
0
      size_t n1 = spec1->numbered_arg_count;
543
0
      size_t n2 = spec2->numbered_arg_count;
544
545
      /* Check that the argument numbers are the same.
546
         Both arrays are sorted.  We search for the first difference.  */
547
0
      {
548
0
        size_t i, j;
549
0
        for (i = 0, j = 0; i < n1 || j < n2; )
550
0
          {
551
0
            int cmp = (i >= n1 ? 1 :
552
0
                       j >= n2 ? -1 :
553
0
                       spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
554
0
                       spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
555
0
                       0);
556
557
0
            if (cmp > 0)
558
0
              {
559
0
                if (error_logger)
560
0
                  error_logger (error_logger_data,
561
0
                                _("a format specification for argument %zu, as in '%s', doesn't exist in '%s'"),
562
0
                                spec2->numbered[j].number, pretty_msgstr,
563
0
                                pretty_msgid);
564
0
                err = true;
565
0
                break;
566
0
              }
567
0
            else if (cmp < 0)
568
0
              {
569
0
                if (equality)
570
0
                  {
571
0
                    if (error_logger)
572
0
                      error_logger (error_logger_data,
573
0
                                    _("a format specification for argument %zu doesn't exist in '%s'"),
574
0
                                    spec1->numbered[i].number, pretty_msgstr);
575
0
                    err = true;
576
0
                    break;
577
0
                  }
578
0
                else
579
0
                  i++;
580
0
              }
581
0
            else
582
0
              j++, i++;
583
0
          }
584
0
      }
585
      /* Check the argument types are the same.  */
586
0
      if (!err)
587
0
        {
588
0
          size_t i, j;
589
0
          for (i = 0, j = 0; j < n2; )
590
0
            {
591
0
              if (spec1->numbered[i].number == spec2->numbered[j].number)
592
0
                {
593
0
                  if (spec1->numbered[i].type != spec2->numbered[j].type)
594
0
                    {
595
0
                      if (error_logger)
596
0
                        error_logger (error_logger_data,
597
0
                                      _("format specifications in '%s' and '%s' for argument %zu are not the same"),
598
0
                                      pretty_msgid, pretty_msgstr,
599
0
                                      spec2->numbered[j].number);
600
0
                      err = true;
601
0
                      break;
602
0
                    }
603
0
                  j++, i++;
604
0
                }
605
0
              else
606
0
                i++;
607
0
            }
608
0
        }
609
0
    }
610
611
0
  return err;
612
0
}
613
614
615
struct formatstring_parser formatstring_java_printf =
616
{
617
  format_parse,
618
  format_free,
619
  format_get_number_of_directives,
620
  NULL,
621
  format_check
622
};
623
624
625
#ifdef TEST
626
627
/* Test program: Print the argument list specification returned by
628
   format_parse for strings read from standard input.  */
629
630
#include <stdio.h>
631
632
static void
633
format_print (void *descr)
634
{
635
  struct spec *spec = (struct spec *) descr;
636
637
  if (spec == NULL)
638
    {
639
      printf ("INVALID");
640
      return;
641
    }
642
643
  printf ("(");
644
  for (size_t i = 0; i < spec->numbered_arg_count; i++)
645
    {
646
      if (i > 0)
647
        printf (" ");
648
      switch (spec->numbered[i].type)
649
        {
650
        case FAT_GENERAL:
651
          printf ("s");
652
          break;
653
        case FAT_CHARACTER:
654
          printf ("c");
655
          break;
656
        case FAT_INTEGER:
657
          printf ("d");
658
          break;
659
        case FAT_FLOATINGPOINT:
660
          printf ("f");
661
          break;
662
        case FAT_DATETIME:
663
          printf ("t");
664
          break;
665
        default:
666
          abort ();
667
        }
668
    }
669
  printf (")");
670
}
671
672
int
673
main ()
674
{
675
  for (;;)
676
    {
677
      char *line = NULL;
678
      size_t line_size = 0;
679
      int line_len = getline (&line, &line_size, stdin);
680
      if (line_len < 0)
681
        break;
682
      if (line_len > 0 && line[line_len - 1] == '\n')
683
        line[--line_len] = '\0';
684
685
      char *invalid_reason = NULL;
686
      void *descr = format_parse (line, false, NULL, &invalid_reason);
687
688
      format_print (descr);
689
      printf ("\n");
690
      if (descr == NULL)
691
        printf ("%s\n", invalid_reason);
692
693
      free (invalid_reason);
694
      free (line);
695
    }
696
697
  return 0;
698
}
699
700
/*
701
 * For Emacs M-x compile
702
 * Local Variables:
703
 * 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 -DTEST format-java-printf.c ../gnulib-lib/libgettextlib.la"
704
 * End:
705
 */
706
707
#endif /* TEST */