xref: /netbsd-src/external/gpl2/gettext/dist/gettext-tools/src/format-tcl.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
1 /* Tcl format strings.
2    Copyright (C) 2001-2004, 2006 Free Software Foundation, Inc.
3    Written by Bruno Haible <haible@clisp.cons.org>, 2002.
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 2, or (at your option)
8    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, write to the Free Software Foundation,
17    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
18 
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22 
23 #include <stdbool.h>
24 #include <stdlib.h>
25 
26 #include "format.h"
27 #include "c-ctype.h"
28 #include "xalloc.h"
29 #include "xvasprintf.h"
30 #include "format-invalid.h"
31 #include "gettext.h"
32 
33 #define _(str) gettext (str)
34 
35 /* Tcl format strings are described in the tcl8.3.3/doc/format.n manual
36    page and implemented in the function Tcl_FormatObjCmd in
37    tcl8.3.3/generic/tclCmdAH.c.
38    A directive
39    - starts with '%' or '%m$' where m is a positive integer,
40    - is optionally followed by any of the characters '#', '0', '-', ' ', '+',
41      each of which acts as a flag,
42    - is optionally followed by a width specification: '*' (reads an argument)
43      or a nonempty digit sequence,
44    - is optionally followed by '.' and a precision specification: '*' (reads
45      an argument) or a nonempty digit sequence,
46    - is optionally followed by a size specifier, 'h' or 'l'. 'l' is ignored.
47    - is finished by a specifier
48        - '%', that needs no argument,
49        - 'c', that needs a character argument,
50        - 's', that needs a string argument,
51        - 'i', 'd', that need a signed integer argument,
52        - 'o', 'u', 'x', 'X', that need an unsigned integer argument,
53        - 'e', 'E', 'f', 'g', 'G', that need a floating-point argument.
54    Numbered ('%m$') and unnumbered argument specifications cannot be used
55    in the same string.
56  */
57 
58 enum format_arg_type
59 {
60   FAT_NONE,
61   FAT_CHARACTER,
62   FAT_STRING,
63   FAT_INTEGER,
64   FAT_UNSIGNED_INTEGER,
65   FAT_SHORT_INTEGER,
66   FAT_SHORT_UNSIGNED_INTEGER,
67   FAT_FLOAT
68 };
69 
70 struct numbered_arg
71 {
72   unsigned int number;
73   enum format_arg_type type;
74 };
75 
76 struct spec
77 {
78   unsigned int directives;
79   unsigned int numbered_arg_count;
80   unsigned int allocated;
81   struct numbered_arg *numbered;
82 };
83 
84 /* Locale independent test for a decimal digit.
85    Argument can be  'char' or 'unsigned char'.  (Whereas the argument of
86    <ctype.h> isdigit must be an 'unsigned char'.)  */
87 #undef isdigit
88 #define isdigit(c) ((unsigned int) ((c) - '0') < 10)
89 
90 
91 static int
numbered_arg_compare(const void * p1,const void * p2)92 numbered_arg_compare (const void *p1, const void *p2)
93 {
94   unsigned int n1 = ((const struct numbered_arg *) p1)->number;
95   unsigned int n2 = ((const struct numbered_arg *) p2)->number;
96 
97   return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
98 }
99 
100 static void *
format_parse(const char * format,bool translated,char ** invalid_reason)101 format_parse (const char *format, bool translated, char **invalid_reason)
102 {
103   struct spec spec;
104   struct spec *result;
105   bool seen_numbered_arg;
106   bool seen_unnumbered_arg;
107   unsigned int number;
108 
109   spec.directives = 0;
110   spec.numbered_arg_count = 0;
111   spec.allocated = 0;
112   spec.numbered = NULL;
113   seen_numbered_arg = false;
114   seen_unnumbered_arg = false;
115   number = 1;
116 
117   for (; *format != '\0';)
118     if (*format++ == '%')
119       {
120 	/* A directive.  */
121 	spec.directives++;
122 
123 	if (*format != '%')
124 	  {
125 	    bool is_numbered_arg;
126 	    bool short_flag;
127 	    enum format_arg_type type;
128 
129 	    is_numbered_arg = false;
130 	    if (isdigit (*format))
131 	      {
132 		const char *f = format;
133 		unsigned int m = 0;
134 
135 		do
136 		  {
137 		    m = 10 * m + (*f - '0');
138 		    f++;
139 		  }
140 		while (isdigit (*f));
141 
142 		if (*f == '$')
143 		  {
144 		    if (m == 0)
145 		      {
146 			*invalid_reason = INVALID_ARGNO_0 (spec.directives);
147 			goto bad_format;
148 		      }
149 		    number = m;
150 		    format = ++f;
151 
152 		    /* Numbered and unnumbered specifications are exclusive.  */
153 		    if (seen_unnumbered_arg)
154 		      {
155 			*invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
156 			goto bad_format;
157 		      }
158 		    is_numbered_arg = true;
159 		    seen_numbered_arg = true;
160 		  }
161 	      }
162 
163 	    /* Numbered and unnumbered specifications are exclusive.  */
164 	    if (!is_numbered_arg)
165 	      {
166 		if (seen_numbered_arg)
167 		  {
168 		    *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
169 		    goto bad_format;
170 		  }
171 		seen_unnumbered_arg = true;
172 	      }
173 
174 	    /* Parse flags.  */
175 	    while (*format == ' ' || *format == '+' || *format == '-'
176 		   || *format == '#' || *format == '0')
177 	      format++;
178 
179 	    /* Parse width.  */
180 	    if (*format == '*')
181 	      {
182 		format++;
183 
184 		if (spec.allocated == spec.numbered_arg_count)
185 		  {
186 		    spec.allocated = 2 * spec.allocated + 1;
187 		    spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg));
188 		  }
189 		spec.numbered[spec.numbered_arg_count].number = number;
190 		spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
191 		spec.numbered_arg_count++;
192 
193 		number++;
194 	      }
195 	    else if (isdigit (*format))
196 	      {
197 		do format++; while (isdigit (*format));
198 	      }
199 
200 	    /* Parse precision.  */
201 	    if (*format == '.')
202 	      {
203 		format++;
204 
205 		if (*format == '*')
206 		  {
207 		    format++;
208 
209 		    if (spec.allocated == spec.numbered_arg_count)
210 		      {
211 			spec.allocated = 2 * spec.allocated + 1;
212 			spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg));
213 		      }
214 		    spec.numbered[spec.numbered_arg_count].number = number;
215 		    spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
216 		    spec.numbered_arg_count++;
217 
218 		    number++;
219 		  }
220 		else if (isdigit (*format))
221 		  {
222 		    do format++; while (isdigit (*format));
223 		  }
224 	      }
225 
226 	    /* Parse optional size specification.  */
227 	    short_flag = false;
228 	    if (*format == 'h')
229 	      short_flag = true, format++;
230 	    else if (*format == 'l')
231 	      format++;
232 
233 	    switch (*format)
234 	      {
235 	      case 'c':
236 		type = FAT_CHARACTER;
237 		break;
238 	      case 's':
239 		type = FAT_STRING;
240 		break;
241 	      case 'i': case 'd':
242 		type = (short_flag ? FAT_SHORT_INTEGER : FAT_INTEGER);
243 		break;
244 	      case 'u': case 'o': case 'x': case 'X':
245 		type = (short_flag ? FAT_SHORT_UNSIGNED_INTEGER : FAT_UNSIGNED_INTEGER);
246 		break;
247 	      case 'e': case 'E': case 'f': case 'g': case 'G':
248 		type = FAT_FLOAT;
249 		break;
250 	      default:
251 		*invalid_reason =
252 		  (*format == '\0'
253 		   ? INVALID_UNTERMINATED_DIRECTIVE ()
254 		   : INVALID_CONVERSION_SPECIFIER (spec.directives, *format));
255 		goto bad_format;
256 	      }
257 
258 	    if (spec.allocated == spec.numbered_arg_count)
259 	      {
260 		spec.allocated = 2 * spec.allocated + 1;
261 		spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg));
262 	      }
263 	    spec.numbered[spec.numbered_arg_count].number = number;
264 	    spec.numbered[spec.numbered_arg_count].type = type;
265 	    spec.numbered_arg_count++;
266 
267 	    number++;
268 	  }
269 
270 	format++;
271       }
272 
273   /* Sort the numbered argument array, and eliminate duplicates.  */
274   if (spec.numbered_arg_count > 1)
275     {
276       unsigned int i, j;
277       bool err;
278 
279       qsort (spec.numbered, spec.numbered_arg_count,
280 	     sizeof (struct numbered_arg), numbered_arg_compare);
281 
282       /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
283       err = false;
284       for (i = j = 0; i < spec.numbered_arg_count; i++)
285 	if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
286 	  {
287 	    enum format_arg_type type1 = spec.numbered[i].type;
288 	    enum format_arg_type type2 = spec.numbered[j-1].type;
289 	    enum format_arg_type type_both;
290 
291 	    if (type1 == type2)
292 	      type_both = type1;
293 	    else
294 	      {
295 		/* Incompatible types.  */
296 		type_both = FAT_NONE;
297 		if (!err)
298 		  *invalid_reason =
299 		    INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number);
300 		err = true;
301 	      }
302 
303 	    spec.numbered[j-1].type = type_both;
304 	  }
305 	else
306 	  {
307 	    if (j < i)
308 	      {
309 		spec.numbered[j].number = spec.numbered[i].number;
310 		spec.numbered[j].type = spec.numbered[i].type;
311 	      }
312 	    j++;
313 	  }
314       spec.numbered_arg_count = j;
315       if (err)
316 	/* *invalid_reason has already been set above.  */
317 	goto bad_format;
318     }
319 
320   result = (struct spec *) xmalloc (sizeof (struct spec));
321   *result = spec;
322   return result;
323 
324  bad_format:
325   if (spec.numbered != NULL)
326     free (spec.numbered);
327   return NULL;
328 }
329 
330 static void
format_free(void * descr)331 format_free (void *descr)
332 {
333   struct spec *spec = (struct spec *) descr;
334 
335   if (spec->numbered != NULL)
336     free (spec->numbered);
337   free (spec);
338 }
339 
340 static int
format_get_number_of_directives(void * descr)341 format_get_number_of_directives (void *descr)
342 {
343   struct spec *spec = (struct spec *) descr;
344 
345   return spec->directives;
346 }
347 
348 static bool
format_check(void * msgid_descr,void * msgstr_descr,bool equality,formatstring_error_logger_t error_logger,const char * pretty_msgstr)349 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
350 	      formatstring_error_logger_t error_logger,
351 	      const char *pretty_msgstr)
352 {
353   struct spec *spec1 = (struct spec *) msgid_descr;
354   struct spec *spec2 = (struct spec *) msgstr_descr;
355   bool err = false;
356 
357   if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
358     {
359       unsigned int i, j;
360       unsigned int n1 = spec1->numbered_arg_count;
361       unsigned int n2 = spec2->numbered_arg_count;
362 
363       /* Check the argument names are the same.
364 	 Both arrays are sorted.  We search for the first difference.  */
365       for (i = 0, j = 0; i < n1 || j < n2; )
366 	{
367 	  int cmp = (i >= n1 ? 1 :
368 		     j >= n2 ? -1 :
369 		     spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
370 		     spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
371 		     0);
372 
373 	  if (cmp > 0)
374 	    {
375 	      if (error_logger)
376 		error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in 'msgid'"),
377 			      spec2->numbered[j].number, pretty_msgstr);
378 	      err = true;
379 	      break;
380 	    }
381 	  else if (cmp < 0)
382 	    {
383 	      if (equality)
384 		{
385 		  if (error_logger)
386 		    error_logger (_("a format specification for argument %u doesn't exist in '%s'"),
387 				  spec1->numbered[i].number, pretty_msgstr);
388 		  err = true;
389 		  break;
390 		}
391 	      else
392 		i++;
393 	    }
394 	  else
395 	    j++, i++;
396 	}
397       /* Check the argument types are the same.  */
398       if (!err)
399 	for (i = 0, j = 0; j < n2; )
400 	  {
401 	    if (spec1->numbered[i].number == spec2->numbered[j].number)
402 	      {
403 		if (spec1->numbered[i].type != spec2->numbered[j].type)
404 		  {
405 		    if (error_logger)
406 		      error_logger (_("format specifications in 'msgid' and '%s' for argument %u are not the same"),
407 				    pretty_msgstr, spec2->numbered[j].number);
408 		    err = true;
409 		    break;
410 		  }
411 		j++, i++;
412 	      }
413 	    else
414 	      i++;
415 	  }
416     }
417 
418   return err;
419 }
420 
421 
422 struct formatstring_parser formatstring_tcl =
423 {
424   format_parse,
425   format_free,
426   format_get_number_of_directives,
427   NULL,
428   format_check
429 };
430 
431 
432 #ifdef TEST
433 
434 /* Test program: Print the argument list specification returned by
435    format_parse for strings read from standard input.  */
436 
437 #include <stdio.h>
438 #include "getline.h"
439 
440 static void
format_print(void * descr)441 format_print (void *descr)
442 {
443   struct spec *spec = (struct spec *) descr;
444   unsigned int last;
445   unsigned int i;
446 
447   if (spec == NULL)
448     {
449       printf ("INVALID");
450       return;
451     }
452 
453   printf ("(");
454   last = 1;
455   for (i = 0; i < spec->numbered_arg_count; i++)
456     {
457       unsigned int number = spec->numbered[i].number;
458 
459       if (i > 0)
460 	printf (" ");
461       if (number < last)
462 	abort ();
463       for (; last < number; last++)
464 	printf ("_ ");
465       switch (spec->numbered[i].type)
466 	{
467 	case FAT_CHARACTER:
468 	  printf ("c");
469 	  break;
470 	case FAT_STRING:
471 	  printf ("s");
472 	  break;
473 	case FAT_INTEGER:
474 	  printf ("i");
475 	  break;
476 	case FAT_UNSIGNED_INTEGER:
477 	  printf ("[unsigned]i");
478 	  break;
479 	case FAT_SHORT_INTEGER:
480 	  printf ("hi");
481 	  break;
482 	case FAT_SHORT_UNSIGNED_INTEGER:
483 	  printf ("[unsigned]hi");
484 	  break;
485 	case FAT_FLOAT:
486 	  printf ("f");
487 	  break;
488 	default:
489 	  abort ();
490 	}
491       last = number + 1;
492     }
493   printf (")");
494 }
495 
496 int
main()497 main ()
498 {
499   for (;;)
500     {
501       char *line = NULL;
502       size_t line_size = 0;
503       int line_len;
504       char *invalid_reason;
505       void *descr;
506 
507       line_len = getline (&line, &line_size, stdin);
508       if (line_len < 0)
509 	break;
510       if (line_len > 0 && line[line_len - 1] == '\n')
511 	line[--line_len] = '\0';
512 
513       invalid_reason = NULL;
514       descr = format_parse (line, false, &invalid_reason);
515 
516       format_print (descr);
517       printf ("\n");
518       if (descr == NULL)
519 	printf ("%s\n", invalid_reason);
520 
521       free (invalid_reason);
522       free (line);
523     }
524 
525   return 0;
526 }
527 
528 /*
529  * For Emacs M-x compile
530  * Local Variables:
531  * compile-command: "/bin/sh ../libtool --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../lib -I../intl -DHAVE_CONFIG_H -DTEST format-tcl.c ../lib/libgettextlib.la"
532  * End:
533  */
534 
535 #endif /* TEST */
536