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