(root)/
gettext-0.22.4/
gettext-tools/
src/
xg-arglist-parser.c
       1  /* Resolving ambiguity of argument lists: Progressive parsing of an
       2     argument list, keeping track of all possibilities.
       3     Copyright (C) 2001-2023 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 "flexmember.h"
      31  #include "xalloc.h"
      32  #include "xsize.h"
      33  
      34  #include "xgettext.h"
      35  #include "xg-message.h"
      36  
      37  #include "gettext.h"
      38  #define _(str) gettext (str)
      39  
      40  
      41  struct arglist_parser *
      42  arglist_parser_alloc (message_list_ty *mlp, const struct callshapes *shapes)
      43  {
      44    if (shapes == NULL || shapes->nshapes == 0)
      45      {
      46        struct arglist_parser *ap =
      47          (struct arglist_parser *)
      48          xmalloc (FLEXNSIZEOF (struct arglist_parser, alternative, 0));
      49  
      50        ap->mlp = mlp;
      51        ap->keyword = NULL;
      52        ap->keyword_len = 0;
      53        ap->next_is_msgctxt = false;
      54        ap->nalternatives = 0;
      55  
      56        return ap;
      57      }
      58    else
      59      {
      60        struct arglist_parser *ap =
      61          (struct arglist_parser *)
      62          xmalloc (FLEXNSIZEOF (struct arglist_parser, alternative,
      63                                shapes->nshapes));
      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 *
     103  arglist_parser_clone (struct arglist_parser *ap)
     104  {
     105    struct arglist_parser *copy =
     106      (struct arglist_parser *)
     107      xmalloc (FLEXNSIZEOF (struct arglist_parser, alternative,
     108                            ap->nalternatives));
     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 = &copy->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->msgid_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
     147  arglist_parser_remember (struct arglist_parser *ap,
     148                           int argnum, mixed_string_ty *string,
     149                           flag_context_ty context,
     150                           const 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
     207  arglist_parser_remember_msgctxt (struct arglist_parser *ap,
     208                                   mixed_string_ty *string,
     209                                   flag_context_ty context,
     210                                   const 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
     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
     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_format4 = yes_according_to_context;
     415                msgid_plural_context.is_format4 = 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 j;
     522  
     523                for (j = 0; j < best_cp->xcomments.nitems; j++)
     524                  {
     525                    const char *xcomment = best_cp->xcomments.item[j];
     526                    bool found = false;
     527  
     528                    if (mp != NULL && mp->comment_dot != NULL)
     529                      {
     530                        size_t k;
     531  
     532                        for (k = 0; k < mp->comment_dot->nitems; k++)
     533                          if (strcmp (xcomment, mp->comment_dot->item[k]) == 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  }