1 /* Java MessageFormat format strings.
2 Copyright (C) 2001-2004, 2006-2007, 2009, 2019 Free Software Foundation, Inc.
3 Written by Bruno Haible <haible@clisp.cons.org>, 2001.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21 #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 MessageFormat format strings are described in
38 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
message_format_parse(const char * format,char * fdi,struct spec * spec,char ** invalid_reason)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
date_format_parse(const char * format)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
number_format_parse(const char * format)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
choice_format_parse(const char * format,struct spec * spec,char ** invalid_reason)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
numbered_arg_compare(const void * p1,const void * p2)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 *
format_parse(const char * format,bool translated,char * fdi,char ** invalid_reason)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
format_free(void * descr)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
format_get_number_of_directives(void * descr)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
format_check(void * msgid_descr,void * msgstr_descr,bool equality,formatstring_error_logger_t error_logger,const char * pretty_msgid,const char * pretty_msgstr)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
format_print(void * descr)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
main()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../../gettext-runtime/intl -DHAVE_CONFIG_H -DTEST format-java.c ../gnulib-lib/libgettextlib.la"
891 * End:
892 */
893
894 #endif /* TEST */
895