1 /* Resolving ambiguity of argument lists: Progressive parsing of an
2 argument list, keeping track of all possibilities.
3 Copyright (C) 2001-2019 Free Software Foundation, Inc.
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 /* Specification. */
23 #include "xg-arglist-parser.h"
24
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "error.h"
29 #include "error-progname.h"
30 #include "xalloc.h"
31 #include "xsize.h"
32
33 #include "xgettext.h"
34 #include "xg-message.h"
35
36 #include "gettext.h"
37 #define _(str) gettext (str)
38
39
40 struct arglist_parser *
arglist_parser_alloc(message_list_ty * mlp,const struct callshapes * shapes)41 arglist_parser_alloc (message_list_ty *mlp, const struct callshapes *shapes)
42 {
43 if (shapes == NULL || shapes->nshapes == 0)
44 {
45 struct arglist_parser *ap =
46 (struct arglist_parser *)
47 xmalloc (offsetof (struct arglist_parser, alternative[0]));
48
49 ap->mlp = mlp;
50 ap->keyword = NULL;
51 ap->keyword_len = 0;
52 ap->next_is_msgctxt = false;
53 ap->nalternatives = 0;
54
55 return ap;
56 }
57 else
58 {
59 struct arglist_parser *ap =
60 (struct arglist_parser *)
61 xmalloc (xsum (sizeof (struct arglist_parser),
62 xtimes (shapes->nshapes - 1,
63 sizeof (struct partial_call))));
64 size_t i;
65
66 ap->mlp = mlp;
67 ap->keyword = shapes->keyword;
68 ap->keyword_len = shapes->keyword_len;
69 ap->next_is_msgctxt = false;
70 ap->nalternatives = shapes->nshapes;
71 for (i = 0; i < shapes->nshapes; i++)
72 {
73 ap->alternative[i].argnumc = shapes->shapes[i].argnumc;
74 ap->alternative[i].argnum1 = shapes->shapes[i].argnum1;
75 ap->alternative[i].argnum2 = shapes->shapes[i].argnum2;
76 ap->alternative[i].argnum1_glib_context =
77 shapes->shapes[i].argnum1_glib_context;
78 ap->alternative[i].argnum2_glib_context =
79 shapes->shapes[i].argnum2_glib_context;
80 ap->alternative[i].argtotal = shapes->shapes[i].argtotal;
81 ap->alternative[i].xcomments = shapes->shapes[i].xcomments;
82 ap->alternative[i].msgctxt = NULL;
83 ap->alternative[i].msgctxt_pos.file_name = NULL;
84 ap->alternative[i].msgctxt_pos.line_number = (size_t)(-1);
85 ap->alternative[i].msgid = NULL;
86 ap->alternative[i].msgid_context = null_context;
87 ap->alternative[i].msgid_pos.file_name = NULL;
88 ap->alternative[i].msgid_pos.line_number = (size_t)(-1);
89 ap->alternative[i].msgid_comment = NULL;
90 ap->alternative[i].msgid_comment_is_utf8 = false;
91 ap->alternative[i].msgid_plural = NULL;
92 ap->alternative[i].msgid_plural_context = null_context;
93 ap->alternative[i].msgid_plural_pos.file_name = NULL;
94 ap->alternative[i].msgid_plural_pos.line_number = (size_t)(-1);
95 }
96
97 return ap;
98 }
99 }
100
101
102 struct arglist_parser *
arglist_parser_clone(struct arglist_parser * ap)103 arglist_parser_clone (struct arglist_parser *ap)
104 {
105 struct arglist_parser *copy =
106 (struct arglist_parser *)
107 xmalloc (xsum (sizeof (struct arglist_parser) - sizeof (struct partial_call),
108 xtimes (ap->nalternatives, sizeof (struct partial_call))));
109 size_t i;
110
111 copy->mlp = ap->mlp;
112 copy->keyword = ap->keyword;
113 copy->keyword_len = ap->keyword_len;
114 copy->next_is_msgctxt = ap->next_is_msgctxt;
115 copy->nalternatives = ap->nalternatives;
116 for (i = 0; i < ap->nalternatives; i++)
117 {
118 const struct partial_call *cp = &ap->alternative[i];
119 struct partial_call *ccp = ©->alternative[i];
120
121 ccp->argnumc = cp->argnumc;
122 ccp->argnum1 = cp->argnum1;
123 ccp->argnum2 = cp->argnum2;
124 ccp->argnum1_glib_context = cp->argnum1_glib_context;
125 ccp->argnum2_glib_context = cp->argnum2_glib_context;
126 ccp->argtotal = cp->argtotal;
127 ccp->xcomments = cp->xcomments;
128 ccp->msgctxt =
129 (cp->msgctxt != NULL ? mixed_string_clone (cp->msgctxt) : NULL);
130 ccp->msgctxt_pos = cp->msgctxt_pos;
131 ccp->msgid = (cp->msgid != NULL ? mixed_string_clone (cp->msgid) : NULL);
132 ccp->msgid_context = cp->msgid_context;
133 ccp->msgid_pos = cp->msgctxt_pos;
134 ccp->msgid_comment = add_reference (cp->msgid_comment);
135 ccp->msgid_comment_is_utf8 = cp->msgid_comment_is_utf8;
136 ccp->msgid_plural =
137 (cp->msgid_plural != NULL ? mixed_string_clone (cp->msgid_plural) : NULL);
138 ccp->msgid_plural_context = cp->msgid_plural_context;
139 ccp->msgid_plural_pos = cp->msgid_plural_pos;
140 }
141
142 return copy;
143 }
144
145
146 void
arglist_parser_remember(struct arglist_parser * ap,int argnum,mixed_string_ty * string,flag_context_ty context,char * file_name,size_t line_number,refcounted_string_list_ty * comment,bool comment_is_utf8)147 arglist_parser_remember (struct arglist_parser *ap,
148 int argnum, mixed_string_ty *string,
149 flag_context_ty context,
150 char *file_name, size_t line_number,
151 refcounted_string_list_ty *comment,
152 bool comment_is_utf8)
153 {
154 bool stored_string = false;
155 size_t nalternatives = ap->nalternatives;
156 size_t i;
157
158 if (!(argnum > 0))
159 abort ();
160 for (i = 0; i < nalternatives; i++)
161 {
162 struct partial_call *cp = &ap->alternative[i];
163
164 if (argnum == cp->argnumc)
165 {
166 cp->msgctxt = string;
167 cp->msgctxt_pos.file_name = file_name;
168 cp->msgctxt_pos.line_number = line_number;
169 stored_string = true;
170 /* Mark msgctxt as done. */
171 cp->argnumc = 0;
172 }
173 else
174 {
175 if (argnum == cp->argnum1)
176 {
177 cp->msgid = string;
178 cp->msgid_context = context;
179 cp->msgid_pos.file_name = file_name;
180 cp->msgid_pos.line_number = line_number;
181 cp->msgid_comment = add_reference (comment);
182 cp->msgid_comment_is_utf8 = comment_is_utf8;
183 stored_string = true;
184 /* Mark msgid as done. */
185 cp->argnum1 = 0;
186 }
187 if (argnum == cp->argnum2)
188 {
189 cp->msgid_plural = string;
190 cp->msgid_plural_context = context;
191 cp->msgid_plural_pos.file_name = file_name;
192 cp->msgid_plural_pos.line_number = line_number;
193 stored_string = true;
194 /* Mark msgid_plural as done. */
195 cp->argnum2 = 0;
196 }
197 }
198 }
199 /* Note: There is a memory leak here: When string was stored but is later
200 not used by arglist_parser_done, we don't free it. */
201 if (!stored_string)
202 mixed_string_free (string);
203 }
204
205
206 void
arglist_parser_remember_msgctxt(struct arglist_parser * ap,mixed_string_ty * string,flag_context_ty context,char * file_name,size_t line_number)207 arglist_parser_remember_msgctxt (struct arglist_parser *ap,
208 mixed_string_ty *string,
209 flag_context_ty context,
210 char *file_name, size_t line_number)
211 {
212 bool stored_string = false;
213 size_t nalternatives = ap->nalternatives;
214 size_t i;
215
216 for (i = 0; i < nalternatives; i++)
217 {
218 struct partial_call *cp = &ap->alternative[i];
219
220 cp->msgctxt = string;
221 cp->msgctxt_pos.file_name = file_name;
222 cp->msgctxt_pos.line_number = line_number;
223 stored_string = true;
224 /* Mark msgctxt as done. */
225 cp->argnumc = 0;
226 }
227 /* Note: There is a memory leak here: When string was stored but is later
228 not used by arglist_parser_done, we don't free it. */
229 if (!stored_string)
230 mixed_string_free (string);
231 }
232
233
234 bool
arglist_parser_decidedp(struct arglist_parser * ap,int argnum)235 arglist_parser_decidedp (struct arglist_parser *ap, int argnum)
236 {
237 size_t i;
238
239 /* Test whether all alternatives are decided.
240 Note: A decided alternative can be complete
241 cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0
242 && cp->argtotal == 0
243 or it can be failed if no literal strings were found at the specified
244 argument positions:
245 cp->argnumc <= argnum && cp->argnum1 <= argnum && cp->argnum2 <= argnum
246 or it can be failed if the number of arguments is exceeded:
247 cp->argtotal > 0 && cp->argtotal < argnum
248 */
249 for (i = 0; i < ap->nalternatives; i++)
250 {
251 struct partial_call *cp = &ap->alternative[i];
252
253 if (!((cp->argnumc <= argnum
254 && cp->argnum1 <= argnum
255 && cp->argnum2 <= argnum)
256 || (cp->argtotal > 0 && cp->argtotal < argnum)))
257 /* cp is still undecided. */
258 return false;
259 }
260 return true;
261 }
262
263
264 void
arglist_parser_done(struct arglist_parser * ap,int argnum)265 arglist_parser_done (struct arglist_parser *ap, int argnum)
266 {
267 size_t ncomplete;
268 size_t i;
269
270 /* Determine the number of complete calls. */
271 ncomplete = 0;
272 for (i = 0; i < ap->nalternatives; i++)
273 {
274 struct partial_call *cp = &ap->alternative[i];
275
276 if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0
277 && (cp->argtotal == 0 || cp->argtotal == argnum))
278 ncomplete++;
279 }
280
281 if (ncomplete > 0)
282 {
283 struct partial_call *best_cp = NULL;
284 bool ambiguous = false;
285
286 /* Find complete calls where msgctxt, msgid, msgid_plural are all
287 provided. */
288 for (i = 0; i < ap->nalternatives; i++)
289 {
290 struct partial_call *cp = &ap->alternative[i];
291
292 if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0
293 && (cp->argtotal == 0 || cp->argtotal == argnum)
294 && cp->msgctxt != NULL
295 && cp->msgid != NULL
296 && cp->msgid_plural != NULL)
297 {
298 if (best_cp != NULL)
299 {
300 ambiguous = true;
301 break;
302 }
303 best_cp = cp;
304 }
305 }
306
307 if (best_cp == NULL)
308 {
309 struct partial_call *best_cp1 = NULL;
310 struct partial_call *best_cp2 = NULL;
311
312 /* Find complete calls where msgctxt, msgid are provided. */
313 for (i = 0; i < ap->nalternatives; i++)
314 {
315 struct partial_call *cp = &ap->alternative[i];
316
317 if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0
318 && (cp->argtotal == 0 || cp->argtotal == argnum)
319 && cp->msgctxt != NULL
320 && cp->msgid != NULL)
321 {
322 if (best_cp1 != NULL)
323 {
324 ambiguous = true;
325 break;
326 }
327 best_cp1 = cp;
328 }
329 }
330
331 /* Find complete calls where msgid, msgid_plural are provided. */
332 for (i = 0; i < ap->nalternatives; i++)
333 {
334 struct partial_call *cp = &ap->alternative[i];
335
336 if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0
337 && (cp->argtotal == 0 || cp->argtotal == argnum)
338 && cp->msgid != NULL
339 && cp->msgid_plural != NULL)
340 {
341 if (best_cp2 != NULL)
342 {
343 ambiguous = true;
344 break;
345 }
346 best_cp2 = cp;
347 }
348 }
349
350 if (best_cp1 != NULL)
351 best_cp = best_cp1;
352 if (best_cp2 != NULL)
353 {
354 if (best_cp != NULL)
355 ambiguous = true;
356 else
357 best_cp = best_cp2;
358 }
359 }
360
361 if (best_cp == NULL)
362 {
363 /* Find complete calls where msgid is provided. */
364 for (i = 0; i < ap->nalternatives; i++)
365 {
366 struct partial_call *cp = &ap->alternative[i];
367
368 if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0
369 && (cp->argtotal == 0 || cp->argtotal == argnum)
370 && cp->msgid != NULL)
371 {
372 if (best_cp != NULL)
373 {
374 ambiguous = true;
375 break;
376 }
377 best_cp = cp;
378 }
379 }
380 }
381
382 if (ambiguous)
383 {
384 error_with_progname = false;
385 error_at_line (0, 0,
386 best_cp->msgid_pos.file_name,
387 best_cp->msgid_pos.line_number,
388 _("ambiguous argument specification for keyword '%.*s'"),
389 (int) ap->keyword_len, ap->keyword);
390 error_with_progname = true;
391 }
392
393 if (best_cp != NULL)
394 {
395 /* best_cp indicates the best found complete call.
396 Now call remember_a_message. */
397 flag_context_ty msgid_context;
398 flag_context_ty msgid_plural_context;
399 char *best_msgctxt;
400 char *best_msgid;
401 char *best_msgid_plural;
402 message_ty *mp;
403
404 msgid_context = best_cp->msgid_context;
405 msgid_plural_context = best_cp->msgid_plural_context;
406
407 /* Special support for the 3-argument tr operator in Qt:
408 When --qt and --keyword=tr:1,1,2c,3t are specified, add to the
409 context the information that the argument is expected to be a
410 qt-plural-format. */
411 if (recognize_qt_formatstrings ()
412 && best_cp->msgid_plural == best_cp->msgid)
413 {
414 msgid_context.is_format3 = yes_according_to_context;
415 msgid_plural_context.is_format3 = yes_according_to_context;
416 }
417
418 best_msgctxt =
419 (best_cp->msgctxt != NULL
420 ? mixed_string_contents_free1 (best_cp->msgctxt)
421 : NULL);
422 best_msgid =
423 (best_cp->msgid != NULL
424 ? mixed_string_contents_free1 (best_cp->msgid)
425 : NULL);
426 best_msgid_plural =
427 (best_cp->msgid_plural != NULL
428 ? /* Special support for the 3-argument tr operator in Qt. */
429 (best_cp->msgid_plural == best_cp->msgid
430 ? xstrdup (best_msgid)
431 : mixed_string_contents_free1 (best_cp->msgid_plural))
432 : NULL);
433
434 /* Split strings in the GNOME glib syntax "msgctxt|msgid". */
435 if (best_cp->argnum1_glib_context || best_cp->argnum2_glib_context)
436 /* split_keywordspec should not allow the context to be specified
437 in two different ways. */
438 if (best_msgctxt != NULL)
439 abort ();
440 if (best_cp->argnum1_glib_context)
441 {
442 const char *separator = strchr (best_msgid, '|');
443
444 if (separator == NULL)
445 {
446 error_with_progname = false;
447 error_at_line (0, 0,
448 best_cp->msgid_pos.file_name,
449 best_cp->msgid_pos.line_number,
450 _("warning: missing context for keyword '%.*s'"),
451 (int) ap->keyword_len, ap->keyword);
452 error_with_progname = true;
453 }
454 else
455 {
456 size_t ctxt_len = separator - best_msgid;
457 char *ctxt = XNMALLOC (ctxt_len + 1, char);
458
459 memcpy (ctxt, best_msgid, ctxt_len);
460 ctxt[ctxt_len] = '\0';
461 best_msgctxt = ctxt;
462 best_msgid = xstrdup (separator + 1);
463 }
464 }
465 if (best_msgid_plural != NULL && best_cp->argnum2_glib_context)
466 {
467 const char *separator = strchr (best_msgid_plural, '|');
468
469 if (separator == NULL)
470 {
471 error_with_progname = false;
472 error_at_line (0, 0,
473 best_cp->msgid_plural_pos.file_name,
474 best_cp->msgid_plural_pos.line_number,
475 _("warning: missing context for plural argument of keyword '%.*s'"),
476 (int) ap->keyword_len, ap->keyword);
477 error_with_progname = true;
478 }
479 else
480 {
481 size_t ctxt_len = separator - best_msgid_plural;
482 char *ctxt = XNMALLOC (ctxt_len + 1, char);
483
484 memcpy (ctxt, best_msgid_plural, ctxt_len);
485 ctxt[ctxt_len] = '\0';
486 if (best_msgctxt == NULL)
487 best_msgctxt = ctxt;
488 else
489 {
490 if (strcmp (ctxt, best_msgctxt) != 0)
491 {
492 error_with_progname = false;
493 error_at_line (0, 0,
494 best_cp->msgid_plural_pos.file_name,
495 best_cp->msgid_plural_pos.line_number,
496 _("context mismatch between singular and plural form"));
497 error_with_progname = true;
498 }
499 free (ctxt);
500 }
501 best_msgid_plural = xstrdup (separator + 1);
502 }
503 }
504
505 mp = remember_a_message (ap->mlp, best_msgctxt, best_msgid, true,
506 best_msgid_plural != NULL,
507 msgid_context,
508 &best_cp->msgid_pos,
509 NULL, best_cp->msgid_comment,
510 best_cp->msgid_comment_is_utf8);
511 if (mp != NULL && best_msgid_plural != NULL)
512 remember_a_message_plural (mp, best_msgid_plural, true,
513 msgid_plural_context,
514 &best_cp->msgid_plural_pos,
515 NULL, false);
516
517 if (best_cp->xcomments.nitems > 0)
518 {
519 /* Add best_cp->xcomments to mp->comment_dot, unless already
520 present. */
521 size_t i;
522
523 for (i = 0; i < best_cp->xcomments.nitems; i++)
524 {
525 const char *xcomment = best_cp->xcomments.item[i];
526 bool found = false;
527
528 if (mp != NULL && mp->comment_dot != NULL)
529 {
530 size_t j;
531
532 for (j = 0; j < mp->comment_dot->nitems; j++)
533 if (strcmp (xcomment, mp->comment_dot->item[j]) == 0)
534 {
535 found = true;
536 break;
537 }
538 }
539 if (!found)
540 message_comment_dot_append (mp, xcomment);
541 }
542 }
543 }
544 }
545 else
546 {
547 /* No complete call was parsed. */
548 /* Note: There is a memory leak here: When there is more than one
549 alternative, the same string can be stored in multiple alternatives,
550 and it's not easy to free all strings reliably. */
551 if (ap->nalternatives == 1)
552 {
553 if (ap->alternative[0].msgctxt != NULL)
554 free (ap->alternative[0].msgctxt);
555 if (ap->alternative[0].msgid != NULL)
556 free (ap->alternative[0].msgid);
557 if (ap->alternative[0].msgid_plural != NULL)
558 free (ap->alternative[0].msgid_plural);
559 }
560 }
561
562 for (i = 0; i < ap->nalternatives; i++)
563 drop_reference (ap->alternative[i].msgid_comment);
564 free (ap);
565 }
566