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