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