Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-tools / src / format-java.c
1 /* Java format strings.
2    Copyright (C) 2001-2004, 2006-2007, 2009, 2015 Free Software
3    Foundation, Inc.
4    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
5
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
18
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22 #include <alloca.h>
23
24 #include <stdbool.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "format.h"
29 #include "c-ctype.h"
30 #include "xalloc.h"
31 #include "xmalloca.h"
32 #include "xvasprintf.h"
33 #include "format-invalid.h"
34 #include "gettext.h"
35
36 #define _(str) gettext (str)
37
38 /* Java format strings are described in java/text/MessageFormat.html.
39    See also the ICU documentation class_MessageFormat.html.
40
41    messageFormatPattern := string ( "{" messageFormatElement "}" string )*
42
43    messageFormatElement := argument { "," elementFormat }
44
45    elementFormat := "time" { "," datetimeStyle }
46                   | "date" { "," datetimeStyle }
47                   | "number" { "," numberStyle }
48                   | "choice" { "," choiceStyle }
49
50    datetimeStyle := "short"
51                     | "medium"
52                     | "long"
53                     | "full"
54                     | dateFormatPattern
55
56    numberStyle := "currency"
57                  | "percent"
58                  | "integer"
59                  | numberFormatPattern
60
61    choiceStyle := choiceFormatPattern
62
63    dateFormatPattern see SimpleDateFormat.applyPattern
64
65    numberFormatPattern see DecimalFormat.applyPattern
66
67    choiceFormatPattern see ChoiceFormat constructor
68
69    In strings, literal curly braces can be used if quoted between single
70    quotes.  A real single quote is represented by ''.
71
72    If a pattern is used, then unquoted braces in the pattern, if any, must
73    match: that is, "ab {0} de" and "ab '}' de" are ok, but "ab {0'}' de" and
74    "ab } de" are not.
75
76    The argument is a number from 0 to 9, which corresponds to the arguments
77    presented in an array to be formatted.
78
79    It is ok to have unused arguments in the array.
80
81    Adding a dateFormatPattern / numberFormatPattern / choiceFormatPattern
82    to an elementFormat is equivalent to creating a SimpleDateFormat /
83    DecimalFormat / ChoiceFormat and use of setFormat. For example,
84
85      MessageFormat form =
86        new MessageFormat("The disk \"{1}\" contains {0,choice,0#no files|1#one file|2#{0,number} files}.");
87
88    is equivalent to
89
90      MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
91      form.setFormat(1, // Number of {} occurrence in the string!
92                     new ChoiceFormat(new double[] { 0, 1, 2 },
93                                      new String[] { "no files", "one file",
94                                                     "{0,number} files" }));
95
96    Note: The behaviour of quotes inside a choiceFormatPattern is not clear.
97    Example 1:
98      "abc{1,choice,0#{1,number,00';'000}}def"
99        JDK 1.1.x: exception
100        JDK 1.3.x: behaves like "abc{1,choice,0#{1,number,00;000}}def"
101    Example 2:
102      "abc{1,choice,0#{1,number,00';'}}def"
103        JDK 1.1.x: interprets the semicolon as number suffix
104        JDK 1.3.x: behaves like "abc{1,choice,0#{1,number,00;}}def"
105  */
106
107 enum format_arg_type
108 {
109   FAT_NONE,
110   FAT_OBJECT,   /* java.lang.Object */
111   FAT_NUMBER,   /* java.lang.Number */
112   FAT_DATE      /* java.util.Date */
113 };
114
115 struct numbered_arg
116 {
117   unsigned int number;
118   enum format_arg_type type;
119 };
120
121 struct spec
122 {
123   unsigned int directives;
124   unsigned int numbered_arg_count;
125   unsigned int allocated;
126   struct numbered_arg *numbered;
127 };
128
129
130 /* Forward declaration of local functions.  */
131 static bool date_format_parse (const char *format);
132 static bool number_format_parse (const char *format);
133 static bool choice_format_parse (const char *format, struct spec *spec,
134                                  char **invalid_reason);
135
136
137 /* Quote handling:
138    - When we see a single-quote, ignore it, but toggle the quoting flag.
139    - When we see a double single-quote, ignore the first of the two.
140    Assumes local variables format, quoting.  */
141 #define HANDLE_QUOTE \
142   if (*format == '\'' && *++format != '\'') \
143     quoting = !quoting;
144
145 /* Note that message_format_parse and choice_format_parse are mutually
146    recursive.  This is because MessageFormat can use some ChoiceFormats,
147    and a ChoiceFormat is made up from several MessageFormats.  */
148
149 /* Return true if a format is a valid messageFormatPattern.
150    Extracts argument type information into spec.  */
151 static bool
152 message_format_parse (const char *format, char *fdi, struct spec *spec,
153                       char **invalid_reason)
154 {
155   const char *const format_start = format;
156   bool quoting = false;
157
158   for (;;)
159     {
160       HANDLE_QUOTE;
161       if (!quoting && *format == '{')
162         {
163           unsigned int depth;
164           const char *element_start;
165           const char *element_end;
166           size_t n;
167           char *element_alloced;
168           char *element;
169           unsigned int number;
170           enum format_arg_type type;
171
172           FDI_SET (format, FMTDIR_START);
173           spec->directives++;
174
175           element_start = ++format;
176           depth = 0;
177           for (; *format != '\0'; format++)
178             {
179               if (*format == '{')
180                 depth++;
181               else if (*format == '}')
182                 {
183                   if (depth == 0)
184                     break;
185                   else
186                     depth--;
187                 }
188             }
189           if (*format == '\0')
190             {
191               *invalid_reason =
192                 xstrdup (_("The string ends in the middle of a directive: found '{' without matching '}'."));
193               FDI_SET (format - 1, FMTDIR_ERROR);
194               return false;
195             }
196           element_end = format++;
197
198           n = element_end - element_start;
199           element = element_alloced = (char *) xmalloca (n + 1);
200           memcpy (element, element_start, n);
201           element[n] = '\0';
202
203           if (!c_isdigit (*element))
204             {
205               *invalid_reason =
206                 xasprintf (_("In the directive number %u, '{' is not followed by an argument number."), spec->directives);
207               FDI_SET (format - 1, FMTDIR_ERROR);
208               freea (element_alloced);
209               return false;
210             }
211           number = 0;
212           do
213             {
214               number = 10 * number + (*element - '0');
215               element++;
216             }
217           while (c_isdigit (*element));
218
219           type = FAT_OBJECT;
220           if (*element == '\0')
221             ;
222           else if (strncmp (element, ",time", 5) == 0
223                    || strncmp (element, ",date", 5) == 0)
224             {
225               type = FAT_DATE;
226               element += 5;
227               if (*element == '\0')
228                 ;
229               else if (*element == ',')
230                 {
231                   element++;
232                   if (strcmp (element, "short") == 0
233                       || strcmp (element, "medium") == 0
234                       || strcmp (element, "long") == 0
235                       || strcmp (element, "full") == 0
236                       || date_format_parse (element))
237                     ;
238                   else
239                     {
240                       *invalid_reason =
241                         xasprintf (_("In the directive number %u, the substring \"%s\" is not a valid date/time style."), spec->directives, element);
242                       FDI_SET (format - 1, FMTDIR_ERROR);
243                       freea (element_alloced);
244                       return false;
245                     }
246                 }
247               else
248                 {
249                   *element = '\0';
250                   element -= 4;
251                   *invalid_reason =
252                     xasprintf (_("In the directive number %u, \"%s\" is not followed by a comma."), spec->directives, element);
253                   FDI_SET (format - 1, FMTDIR_ERROR);
254                   freea (element_alloced);
255                   return false;
256                 }
257             }
258           else if (strncmp (element, ",number", 7) == 0)
259             {
260               type = FAT_NUMBER;
261               element += 7;
262               if (*element == '\0')
263                 ;
264               else if (*element == ',')
265                 {
266                   element++;
267                   if (strcmp (element, "currency") == 0
268                       || strcmp (element, "percent") == 0
269                       || strcmp (element, "integer") == 0
270                       || number_format_parse (element))
271                     ;
272                   else
273                     {
274                       *invalid_reason =
275                         xasprintf (_("In the directive number %u, the substring \"%s\" is not a valid number style."), spec->directives, element);
276                       FDI_SET (format - 1, FMTDIR_ERROR);
277                       freea (element_alloced);
278                       return false;
279                     }
280                 }
281               else
282                 {
283                   *element = '\0';
284                   element -= 6;
285                   *invalid_reason =
286                     xasprintf (_("In the directive number %u, \"%s\" is not followed by a comma."), spec->directives, element);
287                   FDI_SET (format - 1, FMTDIR_ERROR);
288                   freea (element_alloced);
289                   return false;
290                 }
291             }
292           else if (strncmp (element, ",choice", 7) == 0)
293             {
294               type = FAT_NUMBER; /* because ChoiceFormat extends NumberFormat */
295               element += 7;
296               if (*element == '\0')
297                 ;
298               else if (*element == ',')
299                 {
300                   element++;
301                   if (choice_format_parse (element, spec, invalid_reason))
302                     ;
303                   else
304                     {
305                       FDI_SET (format - 1, FMTDIR_ERROR);
306                       freea (element_alloced);
307                       return false;
308                     }
309                 }
310               else
311                 {
312                   *element = '\0';
313                   element -= 6;
314                   *invalid_reason =
315                     xasprintf (_("In the directive number %u, \"%s\" is not followed by a comma."), spec->directives, element);
316                   FDI_SET (format - 1, FMTDIR_ERROR);
317                   freea (element_alloced);
318                   return false;
319                 }
320             }
321           else
322             {
323               *invalid_reason =
324                 xasprintf (_("In the directive number %u, the argument number is not followed by a comma and one of \"%s\", \"%s\", \"%s\", \"%s\"."), spec->directives, "time", "date", "number", "choice");
325               FDI_SET (format - 1, FMTDIR_ERROR);
326               freea (element_alloced);
327               return false;
328             }
329           freea (element_alloced);
330
331           if (spec->allocated == spec->numbered_arg_count)
332             {
333               spec->allocated = 2 * spec->allocated + 1;
334               spec->numbered = (struct numbered_arg *) xrealloc (spec->numbered, spec->allocated * sizeof (struct numbered_arg));
335             }
336           spec->numbered[spec->numbered_arg_count].number = number;
337           spec->numbered[spec->numbered_arg_count].type = type;
338           spec->numbered_arg_count++;
339
340           FDI_SET (format - 1, FMTDIR_END);
341         }
342       /* The doc says "ab}de" is invalid.  Even though JDK accepts it.  */
343       else if (!quoting && *format == '}')
344         {
345           FDI_SET (format, FMTDIR_START);
346           *invalid_reason =
347             xstrdup (_("The string starts in the middle of a directive: found '}' without matching '{'."));
348           FDI_SET (format, FMTDIR_ERROR);
349           return false;
350         }
351       else if (*format != '\0')
352         format++;
353       else
354         break;
355     }
356
357   return true;
358 }
359
360 /* Return true if a format is a valid dateFormatPattern.  */
361 static bool
362 date_format_parse (const char *format)
363 {
364   /* Any string is valid.  Single-quote starts a quoted section, to be
365      terminated at the next single-quote or string end.  Double single-quote
366      gives a single single-quote.  Non-quoted ASCII letters are first grouped
367      into blocks of equal letters.  Then each block (e.g. 'yyyy') is
368      interpreted according to some rules.  */
369   return true;
370 }
371
372 /* Return true if a format is a valid numberFormatPattern.  */
373 static bool
374 number_format_parse (const char *format)
375 {
376   /* Pattern Syntax:
377        pattern     := pos_pattern{';' neg_pattern}
378        pos_pattern := {prefix}number{suffix}
379        neg_pattern := {prefix}number{suffix}
380        number      := integer{'.' fraction}{exponent}
381        prefix      := '\u0000'..'\uFFFD' - special_characters
382        suffix      := '\u0000'..'\uFFFD' - special_characters
383        integer     := min_int | '#' | '#' integer | '#' ',' integer
384        min_int     := '0' | '0' min_int | '0' ',' min_int
385        fraction    := '0'* '#'*
386        exponent    := 'E' '0' '0'*
387      Notation:
388        X*       0 or more instances of X
389        { X }    0 or 1 instances of X
390        X | Y    either X or Y
391        X..Y     any character from X up to Y, inclusive
392        S - T    characters in S, except those in T
393      Single-quote starts a quoted section, to be terminated at the next
394      single-quote or string end.  Double single-quote gives a single
395      single-quote.
396    */
397   bool quoting = false;
398   bool seen_semicolon = false;
399
400   HANDLE_QUOTE;
401   for (;;)
402     {
403       /* Parse prefix.  */
404       while (*format != '\0'
405              && !(!quoting && (*format == '0' || *format == '#')))
406         {
407           if (format[0] == '\\')
408             {
409               if (format[1] == 'u'
410                   && c_isxdigit (format[2])
411                   && c_isxdigit (format[3])
412                   && c_isxdigit (format[4])
413                   && c_isxdigit (format[5]))
414                 format += 6;
415               else
416                 format += 2;
417             }
418           else
419             format += 1;
420           HANDLE_QUOTE;
421         }
422
423       /* Parse integer.  */
424       if (!(!quoting && (*format == '0' || *format == '#')))
425         return false;
426       while (!quoting && *format == '#')
427         {
428           format++;
429           HANDLE_QUOTE;
430           if (!quoting && *format == ',')
431             {
432               format++;
433               HANDLE_QUOTE;
434             }
435         }
436       while (!quoting && *format == '0')
437         {
438           format++;
439           HANDLE_QUOTE;
440           if (!quoting && *format == ',')
441             {
442               format++;
443               HANDLE_QUOTE;
444             }
445         }
446
447       /* Parse fraction.  */
448       if (!quoting && *format == '.')
449         {
450           format++;
451           HANDLE_QUOTE;
452           while (!quoting && *format == '0')
453             {
454               format++;
455               HANDLE_QUOTE;
456             }
457           while (!quoting && *format == '#')
458             {
459               format++;
460               HANDLE_QUOTE;
461             }
462         }
463
464       /* Parse exponent.  */
465       if (!quoting && *format == 'E')
466         {
467           const char *format_save = format;
468           format++;
469           HANDLE_QUOTE;
470           if (!quoting && *format == '0')
471             {
472               do
473                 {
474                   format++;
475                   HANDLE_QUOTE;
476                 }
477               while (!quoting && *format == '0');
478             }
479           else
480             {
481               /* Back up.  */
482               format = format_save;
483               quoting = false;
484             }
485         }
486
487       /* Parse suffix.  */
488       while (*format != '\0'
489              && (seen_semicolon || !(!quoting && *format == ';')))
490         {
491           if (format[0] == '\\')
492             {
493               if (format[1] == 'u'
494                   && c_isxdigit (format[2])
495                   && c_isxdigit (format[3])
496                   && c_isxdigit (format[4])
497                   && c_isxdigit (format[5]))
498                 format += 6;
499               else
500                 format += 2;
501             }
502           else
503             format += 1;
504           HANDLE_QUOTE;
505         }
506
507       if (seen_semicolon || !(!quoting && *format == ';'))
508         break;
509     }
510
511   return (*format == '\0');
512 }
513
514 /* Return true if a format is a valid choiceFormatPattern.
515    Extracts argument type information into spec.  */
516 static bool
517 choice_format_parse (const char *format, struct spec *spec,
518                      char **invalid_reason)
519 {
520   /* Pattern syntax:
521        pattern   := | choice | choice '|' pattern
522        choice    := number separator messageformat
523        separator := '<' | '#' | '\u2264'
524      Single-quote starts a quoted section, to be terminated at the next
525      single-quote or string end.  Double single-quote gives a single
526      single-quote.
527    */
528   bool quoting = false;
529
530   HANDLE_QUOTE;
531   if (*format == '\0')
532     return true;
533   for (;;)
534     {
535       /* Don't bother looking too precisely into the syntax of the number.
536          It can contain various Unicode characters.  */
537       bool number_nonempty;
538       char *msgformat;
539       char *mp;
540       bool msgformat_valid;
541
542       /* Parse number.  */
543       number_nonempty = false;
544       while (*format != '\0'
545              && !(!quoting && (*format == '<' || *format == '#'
546                                || strncmp (format, "\\u2264", 6) == 0
547                                || *format == '|')))
548         {
549           if (format[0] == '\\')
550             {
551               if (format[1] == 'u'
552                   && c_isxdigit (format[2])
553                   && c_isxdigit (format[3])
554                   && c_isxdigit (format[4])
555                   && c_isxdigit (format[5]))
556                 format += 6;
557               else
558                 format += 2;
559             }
560           else
561             format += 1;
562           number_nonempty = true;
563           HANDLE_QUOTE;
564         }
565
566       /* Short clause at end of pattern is valid and is ignored!  */
567       if (*format == '\0')
568         break;
569
570       if (!number_nonempty)
571         {
572           *invalid_reason =
573             xasprintf (_("In the directive number %u, a choice contains no number."), spec->directives);
574           return false;
575         }
576
577       if (*format == '<' || *format == '#')
578         format += 1;
579       else if (strncmp (format, "\\u2264", 6) == 0)
580         format += 6;
581       else
582         {
583           *invalid_reason =
584             xasprintf (_("In the directive number %u, a choice contains a number that is not followed by '<', '#' or '%s'."), spec->directives, "\\u2264");
585           return false;
586         }
587       HANDLE_QUOTE;
588
589       msgformat = (char *) xmalloca (strlen (format) + 1);
590       mp = msgformat;
591
592       while (*format != '\0' && !(!quoting && *format == '|'))
593         {
594           *mp++ = *format++;
595           HANDLE_QUOTE;
596         }
597       *mp = '\0';
598
599       msgformat_valid =
600         message_format_parse (msgformat, NULL, spec, invalid_reason);
601
602       freea (msgformat);
603
604       if (!msgformat_valid)
605         return false;
606
607       if (*format == '\0')
608         break;
609
610       format++;
611       HANDLE_QUOTE;
612     }
613
614   return true;
615 }
616
617 static int
618 numbered_arg_compare (const void *p1, const void *p2)
619 {
620   unsigned int n1 = ((const struct numbered_arg *) p1)->number;
621   unsigned int n2 = ((const struct numbered_arg *) p2)->number;
622
623   return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
624 }
625
626 static void *
627 format_parse (const char *format, bool translated, char *fdi,
628               char **invalid_reason)
629 {
630   struct spec spec;
631   struct spec *result;
632
633   spec.directives = 0;
634   spec.numbered_arg_count = 0;
635   spec.allocated = 0;
636   spec.numbered = NULL;
637
638   if (!message_format_parse (format, fdi, &spec, invalid_reason))
639     goto bad_format;
640
641   /* Sort the numbered argument array, and eliminate duplicates.  */
642   if (spec.numbered_arg_count > 1)
643     {
644       unsigned int i, j;
645       bool err;
646
647       qsort (spec.numbered, spec.numbered_arg_count,
648              sizeof (struct numbered_arg), numbered_arg_compare);
649
650       /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
651       err = false;
652       for (i = j = 0; i < spec.numbered_arg_count; i++)
653         if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
654           {
655             enum format_arg_type type1 = spec.numbered[i].type;
656             enum format_arg_type type2 = spec.numbered[j-1].type;
657             enum format_arg_type type_both;
658
659             if (type1 == type2 || type2 == FAT_OBJECT)
660               type_both = type1;
661             else if (type1 == FAT_OBJECT)
662               type_both = type2;
663             else
664               {
665                 /* Incompatible types.  */
666                 type_both = FAT_NONE;
667                 if (!err)
668                   *invalid_reason =
669                     INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number);
670                 err = true;
671               }
672
673             spec.numbered[j-1].type = type_both;
674           }
675         else
676           {
677             if (j < i)
678               {
679                 spec.numbered[j].number = spec.numbered[i].number;
680                 spec.numbered[j].type = spec.numbered[i].type;
681               }
682             j++;
683           }
684       spec.numbered_arg_count = j;
685       if (err)
686         /* *invalid_reason has already been set above.  */
687         goto bad_format;
688     }
689
690   result = XMALLOC (struct spec);
691   *result = spec;
692   return result;
693
694  bad_format:
695   if (spec.numbered != NULL)
696     free (spec.numbered);
697   return NULL;
698 }
699
700 static void
701 format_free (void *descr)
702 {
703   struct spec *spec = (struct spec *) descr;
704
705   if (spec->numbered != NULL)
706     free (spec->numbered);
707   free (spec);
708 }
709
710 static int
711 format_get_number_of_directives (void *descr)
712 {
713   struct spec *spec = (struct spec *) descr;
714
715   return spec->directives;
716 }
717
718 static bool
719 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
720               formatstring_error_logger_t error_logger,
721               const char *pretty_msgid, const char *pretty_msgstr)
722 {
723   struct spec *spec1 = (struct spec *) msgid_descr;
724   struct spec *spec2 = (struct spec *) msgstr_descr;
725   bool err = false;
726
727   if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
728     {
729       unsigned int i, j;
730       unsigned int n1 = spec1->numbered_arg_count;
731       unsigned int n2 = spec2->numbered_arg_count;
732
733       /* Check the argument names are the same.
734          Both arrays are sorted.  We search for the first difference.  */
735       for (i = 0, j = 0; i < n1 || j < n2; )
736         {
737           int cmp = (i >= n1 ? 1 :
738                      j >= n2 ? -1 :
739                      spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
740                      spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
741                      0);
742
743           if (cmp > 0)
744             {
745               if (error_logger)
746                 error_logger (_("a format specification for argument {%u}, as in '%s', doesn't exist in '%s'"),
747                               spec2->numbered[j].number, pretty_msgstr,
748                               pretty_msgid);
749               err = true;
750               break;
751             }
752           else if (cmp < 0)
753             {
754               if (equality)
755                 {
756                   if (error_logger)
757                     error_logger (_("a format specification for argument {%u} doesn't exist in '%s'"),
758                                   spec1->numbered[i].number, pretty_msgstr);
759                   err = true;
760                   break;
761                 }
762               else
763                 i++;
764             }
765           else
766             j++, i++;
767         }
768       /* Check the argument types are the same.  */
769       if (!err)
770         for (i = 0, j = 0; j < n2; )
771           {
772             if (spec1->numbered[i].number == spec2->numbered[j].number)
773               {
774                 if (spec1->numbered[i].type != spec2->numbered[j].type)
775                   {
776                     if (error_logger)
777                       error_logger (_("format specifications in '%s' and '%s' for argument {%u} are not the same"),
778                                     pretty_msgid, pretty_msgstr,
779                                     spec2->numbered[j].number);
780                     err = true;
781                     break;
782                   }
783                 j++, i++;
784               }
785             else
786               i++;
787           }
788     }
789
790   return err;
791 }
792
793
794 struct formatstring_parser formatstring_java =
795 {
796   format_parse,
797   format_free,
798   format_get_number_of_directives,
799   NULL,
800   format_check
801 };
802
803
804 #ifdef TEST
805
806 /* Test program: Print the argument list specification returned by
807    format_parse for strings read from standard input.  */
808
809 #include <stdio.h>
810
811 static void
812 format_print (void *descr)
813 {
814   struct spec *spec = (struct spec *) descr;
815   unsigned int last;
816   unsigned int i;
817
818   if (spec == NULL)
819     {
820       printf ("INVALID");
821       return;
822     }
823
824   printf ("(");
825   last = 0;
826   for (i = 0; i < spec->numbered_arg_count; i++)
827     {
828       unsigned int number = spec->numbered[i].number;
829
830       if (i > 0)
831         printf (" ");
832       if (number < last)
833         abort ();
834       for (; last < number; last++)
835         printf ("_ ");
836       switch (spec->numbered[i].type)
837         {
838         case FAT_OBJECT:
839           printf ("*");
840           break;
841         case FAT_NUMBER:
842           printf ("Number");
843           break;
844         case FAT_DATE:
845           printf ("Date");
846           break;
847         default:
848           abort ();
849         }
850       last = number + 1;
851     }
852   printf (")");
853 }
854
855 int
856 main ()
857 {
858   for (;;)
859     {
860       char *line = NULL;
861       size_t line_size = 0;
862       int line_len;
863       char *invalid_reason;
864       void *descr;
865
866       line_len = getline (&line, &line_size, stdin);
867       if (line_len < 0)
868         break;
869       if (line_len > 0 && line[line_len - 1] == '\n')
870         line[--line_len] = '\0';
871
872       invalid_reason = NULL;
873       descr = format_parse (line, false, NULL, &invalid_reason);
874
875       format_print (descr);
876       printf ("\n");
877       if (descr == NULL)
878         printf ("%s\n", invalid_reason);
879
880       free (invalid_reason);
881       free (line);
882     }
883
884   return 0;
885 }
886
887 /*
888  * For Emacs M-x compile
889  * Local Variables:
890  * compile-command: "/bin/sh ../libtool --tag=CC --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../gnulib-lib -I../intl -DHAVE_CONFIG_H -DTEST format-java.c ../gnulib-lib/libgettextlib.la"
891  * End:
892  */
893
894 #endif /* TEST */