• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-WNDR4500v2-V1.0.0.60_1.0.38/ap/gpl/timemachine/gettext-0.17/gettext-tools/src/
1/* Java format strings.
2   Copyright (C) 2001-2004, 2006-2007 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
106enum 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
114struct numbered_arg
115{
116  unsigned int number;
117  enum format_arg_type type;
118};
119
120struct 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.  */
130static bool date_format_parse (const char *format);
131static bool number_format_parse (const char *format);
132static 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.  */
150static bool
151message_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.  */
360static bool
361date_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.  */
372static bool
373number_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.  */
515static bool
516choice_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
616static int
617numbered_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
625static void *
626format_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
699static void
700format_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
709static int
710format_get_number_of_directives (void *descr)
711{
712  struct spec *spec = (struct spec *) descr;
713
714  return spec->directives;
715}
716
717static bool
718format_check (void *msgid_descr, void *msgstr_descr, bool equality,
719	      formatstring_error_logger_t error_logger,
720	      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 'msgid'"),
746			      spec2->numbered[j].number, pretty_msgstr);
747	      err = true;
748	      break;
749	    }
750	  else if (cmp < 0)
751	    {
752	      if (equality)
753		{
754		  if (error_logger)
755		    error_logger (_("a format specification for argument {%u} doesn't exist in '%s'"),
756				  spec1->numbered[i].number, pretty_msgstr);
757		  err = true;
758		  break;
759		}
760	      else
761		i++;
762	    }
763	  else
764	    j++, i++;
765	}
766      /* Check the argument types are the same.  */
767      if (!err)
768	for (i = 0, j = 0; j < n2; )
769	  {
770	    if (spec1->numbered[i].number == spec2->numbered[j].number)
771	      {
772		if (spec1->numbered[i].type != spec2->numbered[j].type)
773		  {
774		    if (error_logger)
775		      error_logger (_("format specifications in 'msgid' and '%s' for argument {%u} are not the same"),
776				    pretty_msgstr, spec2->numbered[j].number);
777		    err = true;
778		    break;
779		  }
780		j++, i++;
781	      }
782	    else
783	      i++;
784	  }
785    }
786
787  return err;
788}
789
790
791struct formatstring_parser formatstring_java =
792{
793  format_parse,
794  format_free,
795  format_get_number_of_directives,
796  NULL,
797  format_check
798};
799
800
801#ifdef TEST
802
803/* Test program: Print the argument list specification returned by
804   format_parse for strings read from standard input.  */
805
806#include <stdio.h>
807
808static void
809format_print (void *descr)
810{
811  struct spec *spec = (struct spec *) descr;
812  unsigned int last;
813  unsigned int i;
814
815  if (spec == NULL)
816    {
817      printf ("INVALID");
818      return;
819    }
820
821  printf ("(");
822  last = 0;
823  for (i = 0; i < spec->numbered_arg_count; i++)
824    {
825      unsigned int number = spec->numbered[i].number;
826
827      if (i > 0)
828	printf (" ");
829      if (number < last)
830	abort ();
831      for (; last < number; last++)
832	printf ("_ ");
833      switch (spec->numbered[i].type)
834	{
835	case FAT_OBJECT:
836	  printf ("*");
837	  break;
838	case FAT_NUMBER:
839	  printf ("Number");
840	  break;
841	case FAT_DATE:
842	  printf ("Date");
843	  break;
844	default:
845	  abort ();
846	}
847      last = number + 1;
848    }
849  printf (")");
850}
851
852int
853main ()
854{
855  for (;;)
856    {
857      char *line = NULL;
858      size_t line_size = 0;
859      int line_len;
860      char *invalid_reason;
861      void *descr;
862
863      line_len = getline (&line, &line_size, stdin);
864      if (line_len < 0)
865	break;
866      if (line_len > 0 && line[line_len - 1] == '\n')
867	line[--line_len] = '\0';
868
869      invalid_reason = NULL;
870      descr = format_parse (line, false, NULL, &invalid_reason);
871
872      format_print (descr);
873      printf ("\n");
874      if (descr == NULL)
875	printf ("%s\n", invalid_reason);
876
877      free (invalid_reason);
878      free (line);
879    }
880
881  return 0;
882}
883
884/*
885 * For Emacs M-x compile
886 * Local Variables:
887 * 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"
888 * End:
889 */
890
891#endif /* TEST */
892