Add -lm dependency for gettextlib to fix LTO build
[platform/upstream/gettext.git] / gettext-tools / src / format-elisp.c
1 /* Emacs Lisp format strings.
2    Copyright (C) 2001-2004, 2006-2007, 2009, 2015 Free Software
3    Foundation, Inc.
4    Written by Bruno Haible <haible@clisp.cons.org>, 2002.
5
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
18
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
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 /* Emacs Lisp format strings are implemented in emacs-21.1/src/editfns.c,
36    xemacs-21.1.14/src/editfns.c and xemacs-21.1.14/src/doprnt.c.
37    A directive
38    - starts with '%' or '%m$' where m is a positive integer,
39    - is optionally followed by any of the characters '#', '0', '-', ' ', '+',
40      each of which acts as a flag,
41    - is optionally followed by a width specification: '*' (reads an argument)
42      or a nonempty digit sequence,
43    - is optionally followed by '.' and a precision specification: '*' (reads
44      an argument) or a nonempty digit sequence,
45    - is finished by a specifier
46        - '%', that needs no argument,
47        - 'c', that need a character argument,
48        - 'd', 'i', 'x', 'X', 'o', that need an integer argument,
49        - 'e', 'E', 'f', 'g', 'G', that need a floating-point argument,
50        - 's', that need an argument and prints it using princ,
51        - 'S', that need an argument and prints it using prin1.
52    Numbered ('%m$') and unnumbered argument specifications can be used in the
53    same string. The effect of '%m$' is to set the current argument number to
54    m. The current argument number is incremented after processing a directive.
55  */
56
57 enum format_arg_type
58 {
59   FAT_NONE,
60   FAT_CHARACTER,
61   FAT_INTEGER,
62   FAT_FLOAT,
63   FAT_OBJECT_PRETTY,
64   FAT_OBJECT
65 };
66
67 struct numbered_arg
68 {
69   unsigned int number;
70   enum format_arg_type type;
71 };
72
73 struct spec
74 {
75   unsigned int directives;
76   unsigned int numbered_arg_count;
77   unsigned int allocated;
78   struct numbered_arg *numbered;
79 };
80
81 /* Locale independent test for a decimal digit.
82    Argument can be  'char' or 'unsigned char'.  (Whereas the argument of
83    <ctype.h> isdigit must be an 'unsigned char'.)  */
84 #undef isdigit
85 #define isdigit(c) ((unsigned int) ((c) - '0') < 10)
86
87
88 static int
89 numbered_arg_compare (const void *p1, const void *p2)
90 {
91   unsigned int n1 = ((const struct numbered_arg *) p1)->number;
92   unsigned int n2 = ((const struct numbered_arg *) p2)->number;
93
94   return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
95 }
96
97 static void *
98 format_parse (const char *format, bool translated, char *fdi,
99               char **invalid_reason)
100 {
101   const char *const format_start = format;
102   struct spec spec;
103   struct spec *result;
104   unsigned int number;
105
106   spec.directives = 0;
107   spec.numbered_arg_count = 0;
108   spec.allocated = 0;
109   spec.numbered = NULL;
110   number = 1;
111
112   for (; *format != '\0';)
113     if (*format++ == '%')
114       {
115         /* A directive.  */
116         enum format_arg_type type;
117
118         FDI_SET (format - 1, FMTDIR_START);
119         spec.directives++;
120
121         if (isdigit (*format))
122           {
123             const char *f = format;
124             unsigned int m = 0;
125
126             do
127               {
128                 m = 10 * m + (*f - '0');
129                 f++;
130               }
131             while (isdigit (*f));
132
133             if (*f == '$' && m > 0)
134               {
135                 number = m;
136                 format = ++f;
137               }
138           }
139
140         /* Parse flags.  */
141         while (*format == ' ' || *format == '+' || *format == '-'
142                || *format == '#' || *format == '0')
143           format++;
144
145         /* Parse width.  */
146         if (*format == '*')
147           {
148             format++;
149
150             if (spec.allocated == spec.numbered_arg_count)
151               {
152                 spec.allocated = 2 * spec.allocated + 1;
153                 spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg));
154               }
155             spec.numbered[spec.numbered_arg_count].number = number;
156             spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
157             spec.numbered_arg_count++;
158
159             number++;
160           }
161         else if (isdigit (*format))
162           {
163             do format++; while (isdigit (*format));
164           }
165
166         /* Parse precision.  */
167         if (*format == '.')
168           {
169             format++;
170
171             if (*format == '*')
172               {
173                 format++;
174
175                 if (spec.allocated == spec.numbered_arg_count)
176                   {
177                     spec.allocated = 2 * spec.allocated + 1;
178                     spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg));
179                   }
180                 spec.numbered[spec.numbered_arg_count].number = number;
181                 spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
182                 spec.numbered_arg_count++;
183
184                 number++;
185               }
186             else if (isdigit (*format))
187               {
188                 do format++; while (isdigit (*format));
189               }
190           }
191
192         switch (*format)
193           {
194           case '%':
195             type = FAT_NONE;
196             break;
197           case 'c':
198             type = FAT_CHARACTER;
199             break;
200           case 'd': case 'i': case 'x': case 'X': case 'o':
201             type = FAT_INTEGER;
202             break;
203           case 'e': case 'E': case 'f': case 'g': case 'G':
204             type = FAT_FLOAT;
205             break;
206           case 's':
207             type = FAT_OBJECT_PRETTY;
208             break;
209           case 'S':
210             type = FAT_OBJECT;
211             break;
212           default:
213             if (*format == '\0')
214               {
215                 *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
216                 FDI_SET (format - 1, FMTDIR_ERROR);
217               }
218             else
219               {
220                 *invalid_reason =
221                   INVALID_CONVERSION_SPECIFIER (spec.directives, *format);
222                 FDI_SET (format, FMTDIR_ERROR);
223               }
224             goto bad_format;
225           }
226
227         if (type != FAT_NONE)
228           {
229             if (spec.allocated == spec.numbered_arg_count)
230               {
231                 spec.allocated = 2 * spec.allocated + 1;
232                 spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg));
233               }
234             spec.numbered[spec.numbered_arg_count].number = number;
235             spec.numbered[spec.numbered_arg_count].type = type;
236             spec.numbered_arg_count++;
237
238             number++;
239           }
240
241         FDI_SET (format, FMTDIR_END);
242
243         format++;
244       }
245
246   /* Sort the numbered argument array, and eliminate duplicates.  */
247   if (spec.numbered_arg_count > 1)
248     {
249       unsigned int i, j;
250       bool err;
251
252       qsort (spec.numbered, spec.numbered_arg_count,
253              sizeof (struct numbered_arg), numbered_arg_compare);
254
255       /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
256       err = false;
257       for (i = j = 0; i < spec.numbered_arg_count; i++)
258         if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
259           {
260             enum format_arg_type type1 = spec.numbered[i].type;
261             enum format_arg_type type2 = spec.numbered[j-1].type;
262             enum format_arg_type type_both;
263
264             if (type1 == type2)
265               type_both = type1;
266             else
267               {
268                 /* Incompatible types.  */
269                 type_both = FAT_NONE;
270                 if (!err)
271                   *invalid_reason =
272                     INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number);
273                 err = true;
274               }
275
276             spec.numbered[j-1].type = type_both;
277           }
278         else
279           {
280             if (j < i)
281               {
282                 spec.numbered[j].number = spec.numbered[i].number;
283                 spec.numbered[j].type = spec.numbered[i].type;
284               }
285             j++;
286           }
287       spec.numbered_arg_count = j;
288       if (err)
289         /* *invalid_reason has already been set above.  */
290         goto bad_format;
291     }
292
293   result = XMALLOC (struct spec);
294   *result = spec;
295   return result;
296
297  bad_format:
298   if (spec.numbered != NULL)
299     free (spec.numbered);
300   return NULL;
301 }
302
303 static void
304 format_free (void *descr)
305 {
306   struct spec *spec = (struct spec *) descr;
307
308   if (spec->numbered != NULL)
309     free (spec->numbered);
310   free (spec);
311 }
312
313 static int
314 format_get_number_of_directives (void *descr)
315 {
316   struct spec *spec = (struct spec *) descr;
317
318   return spec->directives;
319 }
320
321 static bool
322 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
323               formatstring_error_logger_t error_logger,
324               const char *pretty_msgid, const char *pretty_msgstr)
325 {
326   struct spec *spec1 = (struct spec *) msgid_descr;
327   struct spec *spec2 = (struct spec *) msgstr_descr;
328   bool err = false;
329
330   if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
331     {
332       unsigned int i, j;
333       unsigned int n1 = spec1->numbered_arg_count;
334       unsigned int n2 = spec2->numbered_arg_count;
335
336       /* Check the argument names are the same.
337          Both arrays are sorted.  We search for the first difference.  */
338       for (i = 0, j = 0; i < n1 || j < n2; )
339         {
340           int cmp = (i >= n1 ? 1 :
341                      j >= n2 ? -1 :
342                      spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
343                      spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
344                      0);
345
346           if (cmp > 0)
347             {
348               if (error_logger)
349                 error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"),
350                               spec2->numbered[j].number, pretty_msgstr,
351                               pretty_msgid);
352               err = true;
353               break;
354             }
355           else if (cmp < 0)
356             {
357               if (equality)
358                 {
359                   if (error_logger)
360                     error_logger (_("a format specification for argument %u doesn't exist in '%s'"),
361                                   spec1->numbered[i].number, pretty_msgstr);
362                   err = true;
363                   break;
364                 }
365               else
366                 i++;
367             }
368           else
369             j++, i++;
370         }
371       /* Check the argument types are the same.  */
372       if (!err)
373         for (i = 0, j = 0; j < n2; )
374           {
375             if (spec1->numbered[i].number == spec2->numbered[j].number)
376               {
377                 if (spec1->numbered[i].type != spec2->numbered[j].type)
378                   {
379                     if (error_logger)
380                       error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"),
381                                     pretty_msgid, pretty_msgstr,
382                                     spec2->numbered[j].number);
383                     err = true;
384                     break;
385                   }
386                 j++, i++;
387               }
388             else
389               i++;
390           }
391     }
392
393   return err;
394 }
395
396
397 struct formatstring_parser formatstring_elisp =
398 {
399   format_parse,
400   format_free,
401   format_get_number_of_directives,
402   NULL,
403   format_check
404 };
405
406
407 #ifdef TEST
408
409 /* Test program: Print the argument list specification returned by
410    format_parse for strings read from standard input.  */
411
412 #include <stdio.h>
413
414 static void
415 format_print (void *descr)
416 {
417   struct spec *spec = (struct spec *) descr;
418   unsigned int last;
419   unsigned int i;
420
421   if (spec == NULL)
422     {
423       printf ("INVALID");
424       return;
425     }
426
427   printf ("(");
428   last = 1;
429   for (i = 0; i < spec->numbered_arg_count; i++)
430     {
431       unsigned int number = spec->numbered[i].number;
432
433       if (i > 0)
434         printf (" ");
435       if (number < last)
436         abort ();
437       for (; last < number; last++)
438         printf ("_ ");
439       switch (spec->numbered[i].type)
440         {
441         case FAT_CHARACTER:
442           printf ("c");
443           break;
444         case FAT_INTEGER:
445           printf ("i");
446           break;
447         case FAT_FLOAT:
448           printf ("f");
449           break;
450         case FAT_OBJECT_PRETTY:
451           printf ("s");
452           break;
453         case FAT_OBJECT:
454           printf ("*");
455           break;
456         default:
457           abort ();
458         }
459       last = number + 1;
460     }
461   printf (")");
462 }
463
464 int
465 main ()
466 {
467   for (;;)
468     {
469       char *line = NULL;
470       size_t line_size = 0;
471       int line_len;
472       char *invalid_reason;
473       void *descr;
474
475       line_len = getline (&line, &line_size, stdin);
476       if (line_len < 0)
477         break;
478       if (line_len > 0 && line[line_len - 1] == '\n')
479         line[--line_len] = '\0';
480
481       invalid_reason = NULL;
482       descr = format_parse (line, false, NULL, &invalid_reason);
483
484       format_print (descr);
485       printf ("\n");
486       if (descr == NULL)
487         printf ("%s\n", invalid_reason);
488
489       free (invalid_reason);
490       free (line);
491     }
492
493   return 0;
494 }
495
496 /*
497  * For Emacs M-x compile
498  * Local Variables:
499  * 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-elisp.c ../gnulib-lib/libgettextlib.la"
500  * End:
501  */
502
503 #endif /* TEST */