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