• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Lua format strings.
2    Copyright (C) 2012-2013, 2018-2020 Free Software Foundation, Inc.
3    Written by Ľubomír Remák <lubomirr@lubomirr.eu>, 2012.
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 "gettext.h"
27 #include "xalloc.h"
28 #include "format-invalid.h"
29 #include "c-ctype.h"
30 #include "xvasprintf.h"
31 
32 #define _(str) gettext (str)
33 
34 /* The Lua format strings are described in the Lua manual,
35    which can be found at:
36    https://www.lua.org/manual/5.2/manual.html
37 
38    A directive
39    - starts with '%'
40    - is optionally followed by any of the characters '0', '-', ' ', or
41      each of which acts as a flag,
42    - is optionally followed by a width specification: a nonempty digit
43      sequence,
44    - is optionally followed by '.' and a precision specification: a nonempty
45      digit sequence,
46    - is finished by a specifier
47        - 's', 'q', that needs a string argument,
48        - 'd', 'i', 'o', 'u', 'X', 'x', that need an integer argument,
49        - 'A', 'a', 'E', 'e', 'f', 'G', 'g', that need a floating-point argument,
50        - 'c', that needs a character argument.
51    Additionally there is the directive '%%', which takes no argument.
52 
53    Note: Lua does not distinguish between integer, floating-point
54    and character arguments, since it has a number data type only.
55    However, we should not allow users to use %d instead of %c.
56    The same applies to %s and %q - we should not allow intermixing them.
57  */
58 
59 enum format_arg_type
60 {
61   FAT_INTEGER,
62   FAT_CHARACTER,
63   FAT_FLOAT,
64   FAT_STRING,
65   FAT_ESCAPED_STRING
66 };
67 
68 struct spec
69 {
70   unsigned int directives;
71   unsigned int format_args_count;
72   enum format_arg_type *format_args;
73 };
74 
75 /* Locale independent test for a decimal digit.
76    Argument can be  'char' or 'unsigned char'.  (Whereas the argument of
77    <ctype.h> isdigit must be an 'unsigned char'.)  */
78 #undef isdigit
79 #define isdigit(c) ((unsigned int) ((c) - '0') < 10)
80 
81 static void format_free (void *descr);
82 
83 static void *
format_parse(const char * format,bool translated,char * fdi,char ** invalid_reason)84 format_parse (const char *format, bool translated, char *fdi,
85               char **invalid_reason)
86 {
87 
88   const char *format_start = format;
89   const char *fatstr = format;
90   struct spec *result = NULL;
91   unsigned int format_args_allocated;
92 
93   result = XMALLOC (struct spec);
94   result->directives = 0;
95   result->format_args_count = 0;
96   result->format_args = NULL;
97   format_args_allocated = 0;
98 
99   for (; *fatstr != '\0';)
100     {
101       if (*fatstr++ == '%')
102         {
103           FDI_SET (fatstr - 1, FMTDIR_START);
104           result->directives++;
105 
106           if (*fatstr != '%')
107             {
108               enum format_arg_type type;
109 
110               /* Remove width. */
111               while (isdigit (*fatstr))
112                 fatstr++;
113 
114               if (*fatstr == '.')
115                 {
116                   fatstr++;
117 
118                   /* Remove precision. */
119                   while (isdigit (*fatstr))
120                     fatstr++;
121                 }
122 
123               switch (*fatstr)
124                 {
125                 case 'c':
126                   type = FAT_CHARACTER;
127                   break;
128                 case 'd':
129                 case 'i':
130                 case 'o':
131                 case 'u':
132                 case 'X':
133                 case 'x':
134                   type = FAT_INTEGER;
135                   break;
136                 case 'a':
137                 case 'A':
138                 case 'E':
139                 case 'e':
140                 case 'f':
141                 case 'g':
142                 case 'G':
143                   type = FAT_FLOAT;
144                   break;
145                 case 's':
146                   type = FAT_STRING;
147                   break;
148                 case 'q':
149                   type = FAT_ESCAPED_STRING;
150                   break;
151                 default:
152                   if (*fatstr == '\0')
153                     {
154                       *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
155                       FDI_SET (fatstr - 1, FMTDIR_ERROR);
156                     }
157                   else
158                     {
159                       *invalid_reason =
160                         INVALID_CONVERSION_SPECIFIER (result->
161                                                       format_args_count + 1,
162                                                       *fatstr);
163                       FDI_SET (fatstr, FMTDIR_ERROR);
164                     }
165                   goto fmt_error;
166                 }
167 
168               if (result->format_args_count == format_args_allocated)
169                 {
170                   format_args_allocated = 2 * format_args_allocated + 10;
171                   result->format_args =
172                     xrealloc (result->format_args,
173                               format_args_allocated *
174                               sizeof (enum format_arg_type));
175                 }
176               result->format_args[result->format_args_count++] = type;
177             }
178           FDI_SET (fatstr, FMTDIR_END);
179           fatstr++;
180         }
181     }
182 
183   return result;
184 
185 fmt_error:
186   format_free (result);
187   return NULL;
188 }
189 
190 static void
format_free(void * descr)191 format_free (void *descr)
192 {
193   struct spec *spec = (struct spec *) descr;
194 
195   if (spec->format_args != NULL)
196     free (spec->format_args);
197   free (spec);
198 }
199 
200 static int
format_get_number_of_directives(void * descr)201 format_get_number_of_directives (void *descr)
202 {
203   struct spec *spec = (struct spec *) descr;
204 
205   return spec->directives;
206 }
207 
208 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)209 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
210               formatstring_error_logger_t error_logger,
211               const char *pretty_msgid, const char *pretty_msgstr)
212 {
213   struct spec *spec1 = (struct spec *) msgid_descr;
214   struct spec *spec2 = (struct spec *) msgstr_descr;
215 
216   if (spec1->format_args_count + spec2->format_args_count > 0)
217     {
218       unsigned int i, n1, n2;
219 
220       n1 = spec1->format_args_count;
221       n2 = spec2->format_args_count;
222 
223       for (i = 0; i < n1 || i < n2; i++)
224         {
225           if (i >= n1)
226             {
227               if (error_logger)
228                 error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"),
229                               i + 1, pretty_msgstr, pretty_msgid);
230               return true;
231             }
232           else if (i >= n2)
233             {
234               if (error_logger)
235                 error_logger (_("a format specification for argument %u doesn't exist in '%s'"),
236                               i + 1, pretty_msgstr);
237               return true;
238             }
239           else if (spec1->format_args[i] != spec2->format_args[i])
240             {
241               if (error_logger)
242                 error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"),
243                               pretty_msgid, pretty_msgstr, i + 1);
244               return true;
245             }
246         }
247     }
248 
249   return false;
250 }
251 
252 struct formatstring_parser formatstring_lua =
253 {
254   format_parse,
255   format_free,
256   format_get_number_of_directives,
257   NULL,
258   format_check
259 };
260 
261 #ifdef TEST
262 
263 /* Test program: Print the argument list specification returned by
264    format_parse for strings read from standard input.  */
265 
266 #include <stdio.h>
267 
268 static void
format_print(void * descr)269 format_print (void *descr)
270 {
271   struct spec *spec = (struct spec *) descr;
272   unsigned int i;
273 
274   if (spec == NULL)
275     {
276       printf ("INVALID");
277       return;
278     }
279 
280   printf ("(");
281   for (i = 0; i < spec->format_args_count; i++)
282     {
283       if (i > 0)
284         printf (" ");
285       switch (spec->format_args[i])
286         {
287         case FAT_INTEGER:
288           printf ("i");
289           break;
290         case FAT_FLOAT:
291           printf ("f");
292           break;
293         case FAT_CHARACTER:
294           printf ("c");
295           break;
296         case FAT_STRING:
297           printf ("s");
298           break;
299         case FAT_ESCAPED_STRING:
300           printf ("q");
301           break;
302         default:
303           abort ();
304         }
305     }
306   printf (")");
307 }
308 
309 int
main()310 main ()
311 {
312   for (;;)
313     {
314       char *line = NULL;
315       size_t line_size = 0;
316       int line_len;
317       char *invalid_reason;
318       void *descr;
319 
320       line_len = getline (&line, &line_size, stdin);
321       if (line_len < 0)
322         break;
323       if (line_len > 0 && line[line_len - 1] == '\n')
324         line[--line_len] = '\0';
325 
326       invalid_reason = NULL;
327       descr = format_parse (line, false, NULL, &invalid_reason);
328 
329       format_print (descr);
330       printf ("\n");
331       if (descr == NULL)
332         printf ("%s\n", invalid_reason);
333 
334       free (invalid_reason);
335       free (line);
336     }
337 
338   return 0;
339 }
340 
341 /*
342  * For Emacs M-x compile
343  * Local Variables:
344  * 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-lua.c ../gnulib-lib/libgettextlib.la"
345  * End:
346  */
347 
348 #endif /* TEST */
349