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