/* Resolving ambiguity of argument lists: Progressive parsing of an argument list, keeping track of all possibilities. Copyright (C) 2001-2023 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif /* Specification. */ #include "xg-arglist-parser.h" #include #include #include "error.h" #include "error-progname.h" #include "xalloc.h" #include "xsize.h" #include "xgettext.h" #include "xg-message.h" #include "gettext.h" #define _(str) gettext (str) struct arglist_parser * arglist_parser_alloc (message_list_ty *mlp, const struct callshapes *shapes) { if (shapes == NULL || shapes->nshapes == 0) { struct arglist_parser *ap = (struct arglist_parser *) xmalloc (offsetof (struct arglist_parser, alternative[0])); ap->mlp = mlp; ap->keyword = NULL; ap->keyword_len = 0; ap->next_is_msgctxt = false; ap->nalternatives = 0; return ap; } else { struct arglist_parser *ap = (struct arglist_parser *) xmalloc (xsum (sizeof (struct arglist_parser), xtimes (shapes->nshapes - 1, sizeof (struct partial_call)))); size_t i; ap->mlp = mlp; ap->keyword = shapes->keyword; ap->keyword_len = shapes->keyword_len; ap->next_is_msgctxt = false; ap->nalternatives = shapes->nshapes; for (i = 0; i < shapes->nshapes; i++) { ap->alternative[i].argnumc = shapes->shapes[i].argnumc; ap->alternative[i].argnum1 = shapes->shapes[i].argnum1; ap->alternative[i].argnum2 = shapes->shapes[i].argnum2; ap->alternative[i].argnum1_glib_context = shapes->shapes[i].argnum1_glib_context; ap->alternative[i].argnum2_glib_context = shapes->shapes[i].argnum2_glib_context; ap->alternative[i].argtotal = shapes->shapes[i].argtotal; ap->alternative[i].xcomments = shapes->shapes[i].xcomments; ap->alternative[i].msgctxt = NULL; ap->alternative[i].msgctxt_pos.file_name = NULL; ap->alternative[i].msgctxt_pos.line_number = (size_t)(-1); ap->alternative[i].msgid = NULL; ap->alternative[i].msgid_context = null_context; ap->alternative[i].msgid_pos.file_name = NULL; ap->alternative[i].msgid_pos.line_number = (size_t)(-1); ap->alternative[i].msgid_comment = NULL; ap->alternative[i].msgid_comment_is_utf8 = false; ap->alternative[i].msgid_plural = NULL; ap->alternative[i].msgid_plural_context = null_context; ap->alternative[i].msgid_plural_pos.file_name = NULL; ap->alternative[i].msgid_plural_pos.line_number = (size_t)(-1); } return ap; } } struct arglist_parser * arglist_parser_clone (struct arglist_parser *ap) { struct arglist_parser *copy = (struct arglist_parser *) xmalloc (xsum (sizeof (struct arglist_parser) - sizeof (struct partial_call), xtimes (ap->nalternatives, sizeof (struct partial_call)))); size_t i; copy->mlp = ap->mlp; copy->keyword = ap->keyword; copy->keyword_len = ap->keyword_len; copy->next_is_msgctxt = ap->next_is_msgctxt; copy->nalternatives = ap->nalternatives; for (i = 0; i < ap->nalternatives; i++) { const struct partial_call *cp = &ap->alternative[i]; struct partial_call *ccp = ©->alternative[i]; ccp->argnumc = cp->argnumc; ccp->argnum1 = cp->argnum1; ccp->argnum2 = cp->argnum2; ccp->argnum1_glib_context = cp->argnum1_glib_context; ccp->argnum2_glib_context = cp->argnum2_glib_context; ccp->argtotal = cp->argtotal; ccp->xcomments = cp->xcomments; ccp->msgctxt = (cp->msgctxt != NULL ? mixed_string_clone (cp->msgctxt) : NULL); ccp->msgctxt_pos = cp->msgctxt_pos; ccp->msgid = (cp->msgid != NULL ? mixed_string_clone (cp->msgid) : NULL); ccp->msgid_context = cp->msgid_context; ccp->msgid_pos = cp->msgid_pos; ccp->msgid_comment = add_reference (cp->msgid_comment); ccp->msgid_comment_is_utf8 = cp->msgid_comment_is_utf8; ccp->msgid_plural = (cp->msgid_plural != NULL ? mixed_string_clone (cp->msgid_plural) : NULL); ccp->msgid_plural_context = cp->msgid_plural_context; ccp->msgid_plural_pos = cp->msgid_plural_pos; } return copy; } 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) { bool stored_string = false; size_t nalternatives = ap->nalternatives; size_t i; if (!(argnum > 0)) abort (); for (i = 0; i < nalternatives; i++) { struct partial_call *cp = &ap->alternative[i]; if (argnum == cp->argnumc) { cp->msgctxt = string; cp->msgctxt_pos.file_name = file_name; cp->msgctxt_pos.line_number = line_number; stored_string = true; /* Mark msgctxt as done. */ cp->argnumc = 0; } else { if (argnum == cp->argnum1) { cp->msgid = string; cp->msgid_context = context; cp->msgid_pos.file_name = file_name; cp->msgid_pos.line_number = line_number; cp->msgid_comment = add_reference (comment); cp->msgid_comment_is_utf8 = comment_is_utf8; stored_string = true; /* Mark msgid as done. */ cp->argnum1 = 0; } if (argnum == cp->argnum2) { cp->msgid_plural = string; cp->msgid_plural_context = context; cp->msgid_plural_pos.file_name = file_name; cp->msgid_plural_pos.line_number = line_number; stored_string = true; /* Mark msgid_plural as done. */ cp->argnum2 = 0; } } } /* Note: There is a memory leak here: When string was stored but is later not used by arglist_parser_done, we don't free it. */ if (!stored_string) mixed_string_free (string); } void arglist_parser_remember_msgctxt (struct arglist_parser *ap, mixed_string_ty *string, flag_context_ty context, char *file_name, size_t line_number) { bool stored_string = false; size_t nalternatives = ap->nalternatives; size_t i; for (i = 0; i < nalternatives; i++) { struct partial_call *cp = &ap->alternative[i]; cp->msgctxt = string; cp->msgctxt_pos.file_name = file_name; cp->msgctxt_pos.line_number = line_number; stored_string = true; /* Mark msgctxt as done. */ cp->argnumc = 0; } /* Note: There is a memory leak here: When string was stored but is later not used by arglist_parser_done, we don't free it. */ if (!stored_string) mixed_string_free (string); } bool arglist_parser_decidedp (struct arglist_parser *ap, int argnum) { size_t i; /* Test whether all alternatives are decided. Note: A decided alternative can be complete cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0 && cp->argtotal == 0 or it can be failed if no literal strings were found at the specified argument positions: cp->argnumc <= argnum && cp->argnum1 <= argnum && cp->argnum2 <= argnum or it can be failed if the number of arguments is exceeded: cp->argtotal > 0 && cp->argtotal < argnum */ for (i = 0; i < ap->nalternatives; i++) { struct partial_call *cp = &ap->alternative[i]; if (!((cp->argnumc <= argnum && cp->argnum1 <= argnum && cp->argnum2 <= argnum) || (cp->argtotal > 0 && cp->argtotal < argnum))) /* cp is still undecided. */ return false; } return true; } void arglist_parser_done (struct arglist_parser *ap, int argnum) { size_t ncomplete; size_t i; /* Determine the number of complete calls. */ ncomplete = 0; for (i = 0; i < ap->nalternatives; i++) { struct partial_call *cp = &ap->alternative[i]; if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0 && (cp->argtotal == 0 || cp->argtotal == argnum)) ncomplete++; } if (ncomplete > 0) { struct partial_call *best_cp = NULL; bool ambiguous = false; /* Find complete calls where msgctxt, msgid, msgid_plural are all provided. */ for (i = 0; i < ap->nalternatives; i++) { struct partial_call *cp = &ap->alternative[i]; if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0 && (cp->argtotal == 0 || cp->argtotal == argnum) && cp->msgctxt != NULL && cp->msgid != NULL && cp->msgid_plural != NULL) { if (best_cp != NULL) { ambiguous = true; break; } best_cp = cp; } } if (best_cp == NULL) { struct partial_call *best_cp1 = NULL; struct partial_call *best_cp2 = NULL; /* Find complete calls where msgctxt, msgid are provided. */ for (i = 0; i < ap->nalternatives; i++) { struct partial_call *cp = &ap->alternative[i]; if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0 && (cp->argtotal == 0 || cp->argtotal == argnum) && cp->msgctxt != NULL && cp->msgid != NULL) { if (best_cp1 != NULL) { ambiguous = true; break; } best_cp1 = cp; } } /* Find complete calls where msgid, msgid_plural are provided. */ for (i = 0; i < ap->nalternatives; i++) { struct partial_call *cp = &ap->alternative[i]; if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0 && (cp->argtotal == 0 || cp->argtotal == argnum) && cp->msgid != NULL && cp->msgid_plural != NULL) { if (best_cp2 != NULL) { ambiguous = true; break; } best_cp2 = cp; } } if (best_cp1 != NULL) best_cp = best_cp1; if (best_cp2 != NULL) { if (best_cp != NULL) ambiguous = true; else best_cp = best_cp2; } } if (best_cp == NULL) { /* Find complete calls where msgid is provided. */ for (i = 0; i < ap->nalternatives; i++) { struct partial_call *cp = &ap->alternative[i]; if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0 && (cp->argtotal == 0 || cp->argtotal == argnum) && cp->msgid != NULL) { if (best_cp != NULL) { ambiguous = true; break; } best_cp = cp; } } } if (ambiguous) { error_with_progname = false; error_at_line (0, 0, best_cp->msgid_pos.file_name, best_cp->msgid_pos.line_number, _("ambiguous argument specification for keyword '%.*s'"), (int) ap->keyword_len, ap->keyword); error_with_progname = true; } if (best_cp != NULL) { /* best_cp indicates the best found complete call. Now call remember_a_message. */ flag_context_ty msgid_context; flag_context_ty msgid_plural_context; char *best_msgctxt; char *best_msgid; char *best_msgid_plural; message_ty *mp; msgid_context = best_cp->msgid_context; msgid_plural_context = best_cp->msgid_plural_context; /* Special support for the 3-argument tr operator in Qt: When --qt and --keyword=tr:1,1,2c,3t are specified, add to the context the information that the argument is expected to be a qt-plural-format. */ if (recognize_qt_formatstrings () && best_cp->msgid_plural == best_cp->msgid) { msgid_context.is_format3 = yes_according_to_context; msgid_plural_context.is_format3 = yes_according_to_context; } best_msgctxt = (best_cp->msgctxt != NULL ? mixed_string_contents_free1 (best_cp->msgctxt) : NULL); best_msgid = (best_cp->msgid != NULL ? mixed_string_contents_free1 (best_cp->msgid) : NULL); best_msgid_plural = (best_cp->msgid_plural != NULL ? /* Special support for the 3-argument tr operator in Qt. */ (best_cp->msgid_plural == best_cp->msgid ? xstrdup (best_msgid) : mixed_string_contents_free1 (best_cp->msgid_plural)) : NULL); /* Split strings in the GNOME glib syntax "msgctxt|msgid". */ if (best_cp->argnum1_glib_context || best_cp->argnum2_glib_context) /* split_keywordspec should not allow the context to be specified in two different ways. */ if (best_msgctxt != NULL) abort (); if (best_cp->argnum1_glib_context) { const char *separator = strchr (best_msgid, '|'); if (separator == NULL) { error_with_progname = false; error_at_line (0, 0, best_cp->msgid_pos.file_name, best_cp->msgid_pos.line_number, _("warning: missing context for keyword '%.*s'"), (int) ap->keyword_len, ap->keyword); error_with_progname = true; } else { size_t ctxt_len = separator - best_msgid; char *ctxt = XNMALLOC (ctxt_len + 1, char); memcpy (ctxt, best_msgid, ctxt_len); ctxt[ctxt_len] = '\0'; best_msgctxt = ctxt; best_msgid = xstrdup (separator + 1); } } if (best_msgid_plural != NULL && best_cp->argnum2_glib_context) { const char *separator = strchr (best_msgid_plural, '|'); if (separator == NULL) { error_with_progname = false; error_at_line (0, 0, best_cp->msgid_plural_pos.file_name, best_cp->msgid_plural_pos.line_number, _("warning: missing context for plural argument of keyword '%.*s'"), (int) ap->keyword_len, ap->keyword); error_with_progname = true; } else { size_t ctxt_len = separator - best_msgid_plural; char *ctxt = XNMALLOC (ctxt_len + 1, char); memcpy (ctxt, best_msgid_plural, ctxt_len); ctxt[ctxt_len] = '\0'; if (best_msgctxt == NULL) best_msgctxt = ctxt; else { if (strcmp (ctxt, best_msgctxt) != 0) { error_with_progname = false; error_at_line (0, 0, best_cp->msgid_plural_pos.file_name, best_cp->msgid_plural_pos.line_number, _("context mismatch between singular and plural form")); error_with_progname = true; } free (ctxt); } best_msgid_plural = xstrdup (separator + 1); } } mp = remember_a_message (ap->mlp, best_msgctxt, best_msgid, true, best_msgid_plural != NULL, msgid_context, &best_cp->msgid_pos, NULL, best_cp->msgid_comment, best_cp->msgid_comment_is_utf8); if (mp != NULL && best_msgid_plural != NULL) remember_a_message_plural (mp, best_msgid_plural, true, msgid_plural_context, &best_cp->msgid_plural_pos, NULL, false); if (best_cp->xcomments.nitems > 0) { /* Add best_cp->xcomments to mp->comment_dot, unless already present. */ size_t i; for (i = 0; i < best_cp->xcomments.nitems; i++) { const char *xcomment = best_cp->xcomments.item[i]; bool found = false; if (mp != NULL && mp->comment_dot != NULL) { size_t j; for (j = 0; j < mp->comment_dot->nitems; j++) if (strcmp (xcomment, mp->comment_dot->item[j]) == 0) { found = true; break; } } if (!found) message_comment_dot_append (mp, xcomment); } } } } else { /* No complete call was parsed. */ /* Note: There is a memory leak here: When there is more than one alternative, the same string can be stored in multiple alternatives, and it's not easy to free all strings reliably. */ if (ap->nalternatives == 1) { if (ap->alternative[0].msgctxt != NULL) free (ap->alternative[0].msgctxt); if (ap->alternative[0].msgid != NULL) free (ap->alternative[0].msgid); if (ap->alternative[0].msgid_plural != NULL) free (ap->alternative[0].msgid_plural); } } for (i = 0; i < ap->nalternatives; i++) drop_reference (ap->alternative[i].msgid_comment); free (ap); }