(root)/
gettext-0.22.4/
gettext-tools/
src/
format-tcl.c
       1  /* Tcl format strings.
       2     Copyright (C) 2001-2004, 2006-2007, 2009, 2019-2020, 2023 Free Software Foundation, Inc.
       3     Written by Bruno Haible <haible@clisp.cons.org>, 2002.
       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 "c-ctype.h"
      27  #include "xalloc.h"
      28  #include "xvasprintf.h"
      29  #include "format-invalid.h"
      30  #include "gettext.h"
      31  
      32  #define _(str) gettext (str)
      33  
      34  /* Tcl format strings are described in the tcl8.3.3/doc/format.n manual
      35     page and implemented in the function Tcl_FormatObjCmd in
      36     tcl8.3.3/generic/tclCmdAH.c.
      37     A directive
      38     - starts with '%' or '%m$' where m is a positive integer,
      39     - is optionally followed by any of the characters '#', '0', '-', ' ', '+',
      40       each of which acts as a flag,
      41     - is optionally followed by a width specification: '*' (reads an argument)
      42       or a nonempty digit sequence,
      43     - is optionally followed by '.' and a precision specification: '*' (reads
      44       an argument) or a nonempty digit sequence,
      45     - is optionally followed by a size specifier, 'h' or 'l'. 'l' is ignored.
      46     - is finished by a specifier
      47         - '%', that needs no argument,
      48         - 'c', that needs a character argument,
      49         - 's', that needs a string argument,
      50         - 'i', 'd', that need a signed integer argument,
      51         - 'o', 'u', 'x', 'X', that need an unsigned integer argument,
      52         - 'e', 'E', 'f', 'g', 'G', that need a floating-point argument.
      53     Numbered ('%m$') and unnumbered argument specifications cannot be used
      54     in the same string.
      55   */
      56  
      57  enum format_arg_type
      58  {
      59    FAT_NONE,
      60    FAT_CHARACTER,
      61    FAT_STRING,
      62    FAT_INTEGER,
      63    FAT_UNSIGNED_INTEGER,
      64    FAT_SHORT_INTEGER,
      65    FAT_SHORT_UNSIGNED_INTEGER,
      66    FAT_FLOAT
      67  };
      68  
      69  struct numbered_arg
      70  {
      71    unsigned int number;
      72    enum format_arg_type type;
      73  };
      74  
      75  struct spec
      76  {
      77    unsigned int directives;
      78    unsigned int numbered_arg_count;
      79    struct numbered_arg *numbered;
      80  };
      81  
      82  /* Locale independent test for a decimal digit.
      83     Argument can be  'char' or 'unsigned char'.  (Whereas the argument of
      84     <ctype.h> isdigit must be an 'unsigned char'.)  */
      85  #undef isdigit
      86  #define isdigit(c) ((unsigned int) ((c) - '0') < 10)
      87  
      88  
      89  static int
      90  numbered_arg_compare (const void *p1, const void *p2)
      91  {
      92    unsigned int n1 = ((const struct numbered_arg *) p1)->number;
      93    unsigned int n2 = ((const struct numbered_arg *) p2)->number;
      94  
      95    return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
      96  }
      97  
      98  static void *
      99  format_parse (const char *format, bool translated, char *fdi,
     100                char **invalid_reason)
     101  {
     102    const char *const format_start = format;
     103    struct spec spec;
     104    unsigned int numbered_allocated;
     105    struct spec *result;
     106    bool seen_numbered_arg;
     107    bool seen_unnumbered_arg;
     108    unsigned int number;
     109  
     110    spec.directives = 0;
     111    spec.numbered_arg_count = 0;
     112    spec.numbered = NULL;
     113    numbered_allocated = 0;
     114    seen_numbered_arg = false;
     115    seen_unnumbered_arg = false;
     116    number = 1;
     117  
     118    for (; *format != '\0';)
     119      /* Invariant: !seen_numbered_arg || !seen_unnumbered_arg.  */
     120      if (*format++ == '%')
     121        {
     122          /* A directive.  */
     123          FDI_SET (format - 1, FMTDIR_START);
     124          spec.directives++;
     125  
     126          if (*format != '%')
     127            {
     128              bool is_numbered_arg;
     129              bool short_flag;
     130              enum format_arg_type type;
     131  
     132              is_numbered_arg = false;
     133              if (isdigit (*format))
     134                {
     135                  const char *f = format;
     136                  unsigned int m = 0;
     137  
     138                  do
     139                    {
     140                      m = 10 * m + (*f - '0');
     141                      f++;
     142                    }
     143                  while (isdigit (*f));
     144  
     145                  if (*f == '$')
     146                    {
     147                      if (m == 0)
     148                        {
     149                          *invalid_reason = INVALID_ARGNO_0 (spec.directives);
     150                          FDI_SET (f, FMTDIR_ERROR);
     151                          goto bad_format;
     152                        }
     153                      number = m;
     154                      format = ++f;
     155  
     156                      /* Numbered and unnumbered specifications are exclusive.  */
     157                      if (seen_unnumbered_arg)
     158                        {
     159                          *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
     160                          FDI_SET (format - 1, FMTDIR_ERROR);
     161                          goto bad_format;
     162                        }
     163                      is_numbered_arg = true;
     164                      seen_numbered_arg = true;
     165                    }
     166                }
     167  
     168              /* Numbered and unnumbered specifications are exclusive.  */
     169              if (!is_numbered_arg)
     170                {
     171                  if (seen_numbered_arg)
     172                    {
     173                      *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
     174                      FDI_SET (format - 1, FMTDIR_ERROR);
     175                      goto bad_format;
     176                    }
     177                  seen_unnumbered_arg = true;
     178                }
     179  
     180              /* Parse flags.  */
     181              while (*format == ' ' || *format == '+' || *format == '-'
     182                     || *format == '#' || *format == '0')
     183                format++;
     184  
     185              /* Parse width.  */
     186              if (*format == '*')
     187                {
     188                  format++;
     189  
     190                  if (numbered_allocated == spec.numbered_arg_count)
     191                    {
     192                      numbered_allocated = 2 * numbered_allocated + 1;
     193                      spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
     194                    }
     195                  spec.numbered[spec.numbered_arg_count].number = number;
     196                  spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
     197                  spec.numbered_arg_count++;
     198  
     199                  number++;
     200                }
     201              else if (isdigit (*format))
     202                {
     203                  do format++; while (isdigit (*format));
     204                }
     205  
     206              /* Parse precision.  */
     207              if (*format == '.')
     208                {
     209                  format++;
     210  
     211                  if (*format == '*')
     212                    {
     213                      format++;
     214  
     215                      if (numbered_allocated == spec.numbered_arg_count)
     216                        {
     217                          numbered_allocated = 2 * numbered_allocated + 1;
     218                          spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
     219                        }
     220                      spec.numbered[spec.numbered_arg_count].number = number;
     221                      spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
     222                      spec.numbered_arg_count++;
     223  
     224                      number++;
     225                    }
     226                  else if (isdigit (*format))
     227                    {
     228                      do format++; while (isdigit (*format));
     229                    }
     230                }
     231  
     232              /* Parse optional size specification.  */
     233              short_flag = false;
     234              if (*format == 'h')
     235                short_flag = true, format++;
     236              else if (*format == 'l')
     237                format++;
     238  
     239              switch (*format)
     240                {
     241                case 'c':
     242                  type = FAT_CHARACTER;
     243                  break;
     244                case 's':
     245                  type = FAT_STRING;
     246                  break;
     247                case 'i': case 'd':
     248                  type = (short_flag ? FAT_SHORT_INTEGER : FAT_INTEGER);
     249                  break;
     250                case 'u': case 'o': case 'x': case 'X':
     251                  type = (short_flag ? FAT_SHORT_UNSIGNED_INTEGER : FAT_UNSIGNED_INTEGER);
     252                  break;
     253                case 'e': case 'E': case 'f': case 'g': case 'G':
     254                  type = FAT_FLOAT;
     255                  break;
     256                default:
     257                  if (*format == '\0')
     258                    {
     259                      *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
     260                      FDI_SET (format - 1, FMTDIR_ERROR);
     261                    }
     262                  else
     263                    {
     264                      *invalid_reason =
     265                        INVALID_CONVERSION_SPECIFIER (spec.directives, *format);
     266                      FDI_SET (format, FMTDIR_ERROR);
     267                    }
     268                  goto bad_format;
     269                }
     270  
     271              if (numbered_allocated == spec.numbered_arg_count)
     272                {
     273                  numbered_allocated = 2 * numbered_allocated + 1;
     274                  spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
     275                }
     276              spec.numbered[spec.numbered_arg_count].number = number;
     277              spec.numbered[spec.numbered_arg_count].type = type;
     278              spec.numbered_arg_count++;
     279  
     280              number++;
     281            }
     282  
     283          FDI_SET (format, FMTDIR_END);
     284  
     285          format++;
     286        }
     287  
     288    /* Sort the numbered argument array, and eliminate duplicates.  */
     289    if (spec.numbered_arg_count > 1)
     290      {
     291        unsigned int i, j;
     292        bool err;
     293  
     294        qsort (spec.numbered, spec.numbered_arg_count,
     295               sizeof (struct numbered_arg), numbered_arg_compare);
     296  
     297        /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
     298        err = false;
     299        for (i = j = 0; i < spec.numbered_arg_count; i++)
     300          if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
     301            {
     302              enum format_arg_type type1 = spec.numbered[i].type;
     303              enum format_arg_type type2 = spec.numbered[j-1].type;
     304              enum format_arg_type type_both;
     305  
     306              if (type1 == type2)
     307                type_both = type1;
     308              else
     309                {
     310                  /* Incompatible types.  */
     311                  type_both = FAT_NONE;
     312                  if (!err)
     313                    *invalid_reason =
     314                      INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number);
     315                  err = true;
     316                }
     317  
     318              spec.numbered[j-1].type = type_both;
     319            }
     320          else
     321            {
     322              if (j < i)
     323                {
     324                  spec.numbered[j].number = spec.numbered[i].number;
     325                  spec.numbered[j].type = spec.numbered[i].type;
     326                }
     327              j++;
     328            }
     329        spec.numbered_arg_count = j;
     330        if (err)
     331          /* *invalid_reason has already been set above.  */
     332          goto bad_format;
     333      }
     334  
     335    result = XMALLOC (struct spec);
     336    *result = spec;
     337    return result;
     338  
     339   bad_format:
     340    if (spec.numbered != NULL)
     341      free (spec.numbered);
     342    return NULL;
     343  }
     344  
     345  static void
     346  format_free (void *descr)
     347  {
     348    struct spec *spec = (struct spec *) descr;
     349  
     350    if (spec->numbered != NULL)
     351      free (spec->numbered);
     352    free (spec);
     353  }
     354  
     355  static int
     356  format_get_number_of_directives (void *descr)
     357  {
     358    struct spec *spec = (struct spec *) descr;
     359  
     360    return spec->directives;
     361  }
     362  
     363  static bool
     364  format_check (void *msgid_descr, void *msgstr_descr, bool equality,
     365                formatstring_error_logger_t error_logger,
     366                const char *pretty_msgid, const char *pretty_msgstr)
     367  {
     368    struct spec *spec1 = (struct spec *) msgid_descr;
     369    struct spec *spec2 = (struct spec *) msgstr_descr;
     370    bool err = false;
     371  
     372    if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
     373      {
     374        unsigned int i, j;
     375        unsigned int n1 = spec1->numbered_arg_count;
     376        unsigned int n2 = spec2->numbered_arg_count;
     377  
     378        /* Check that the argument numbers are the same.
     379           Both arrays are sorted.  We search for the first difference.  */
     380        for (i = 0, j = 0; i < n1 || j < n2; )
     381          {
     382            int cmp = (i >= n1 ? 1 :
     383                       j >= n2 ? -1 :
     384                       spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
     385                       spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
     386                       0);
     387  
     388            if (cmp > 0)
     389              {
     390                if (error_logger)
     391                  error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"),
     392                                spec2->numbered[j].number, pretty_msgstr,
     393                                pretty_msgid);
     394                err = true;
     395                break;
     396              }
     397            else if (cmp < 0)
     398              {
     399                if (equality)
     400                  {
     401                    if (error_logger)
     402                      error_logger (_("a format specification for argument %u doesn't exist in '%s'"),
     403                                    spec1->numbered[i].number, pretty_msgstr);
     404                    err = true;
     405                    break;
     406                  }
     407                else
     408                  i++;
     409              }
     410            else
     411              j++, i++;
     412          }
     413        /* Check the argument types are the same.  */
     414        if (!err)
     415          for (i = 0, j = 0; j < n2; )
     416            {
     417              if (spec1->numbered[i].number == spec2->numbered[j].number)
     418                {
     419                  if (spec1->numbered[i].type != spec2->numbered[j].type)
     420                    {
     421                      if (error_logger)
     422                        error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"),
     423                                      pretty_msgid, pretty_msgstr,
     424                                      spec2->numbered[j].number);
     425                      err = true;
     426                      break;
     427                    }
     428                  j++, i++;
     429                }
     430              else
     431                i++;
     432            }
     433      }
     434  
     435    return err;
     436  }
     437  
     438  
     439  struct formatstring_parser formatstring_tcl =
     440  {
     441    format_parse,
     442    format_free,
     443    format_get_number_of_directives,
     444    NULL,
     445    format_check
     446  };
     447  
     448  
     449  #ifdef TEST
     450  
     451  /* Test program: Print the argument list specification returned by
     452     format_parse for strings read from standard input.  */
     453  
     454  #include <stdio.h>
     455  
     456  static void
     457  format_print (void *descr)
     458  {
     459    struct spec *spec = (struct spec *) descr;
     460    unsigned int last;
     461    unsigned int i;
     462  
     463    if (spec == NULL)
     464      {
     465        printf ("INVALID");
     466        return;
     467      }
     468  
     469    printf ("(");
     470    last = 1;
     471    for (i = 0; i < spec->numbered_arg_count; i++)
     472      {
     473        unsigned int number = spec->numbered[i].number;
     474  
     475        if (i > 0)
     476          printf (" ");
     477        if (number < last)
     478          abort ();
     479        for (; last < number; last++)
     480          printf ("_ ");
     481        switch (spec->numbered[i].type)
     482          {
     483          case FAT_CHARACTER:
     484            printf ("c");
     485            break;
     486          case FAT_STRING:
     487            printf ("s");
     488            break;
     489          case FAT_INTEGER:
     490            printf ("i");
     491            break;
     492          case FAT_UNSIGNED_INTEGER:
     493            printf ("[unsigned]i");
     494            break;
     495          case FAT_SHORT_INTEGER:
     496            printf ("hi");
     497            break;
     498          case FAT_SHORT_UNSIGNED_INTEGER:
     499            printf ("[unsigned]hi");
     500            break;
     501          case FAT_FLOAT:
     502            printf ("f");
     503            break;
     504          default:
     505            abort ();
     506          }
     507        last = number + 1;
     508      }
     509    printf (")");
     510  }
     511  
     512  int
     513  main ()
     514  {
     515    for (;;)
     516      {
     517        char *line = NULL;
     518        size_t line_size = 0;
     519        int line_len;
     520        char *invalid_reason;
     521        void *descr;
     522  
     523        line_len = getline (&line, &line_size, stdin);
     524        if (line_len < 0)
     525          break;
     526        if (line_len > 0 && line[line_len - 1] == '\n')
     527          line[--line_len] = '\0';
     528  
     529        invalid_reason = NULL;
     530        descr = format_parse (line, false, NULL, &invalid_reason);
     531  
     532        format_print (descr);
     533        printf ("\n");
     534        if (descr == NULL)
     535          printf ("%s\n", invalid_reason);
     536  
     537        free (invalid_reason);
     538        free (line);
     539      }
     540  
     541    return 0;
     542  }
     543  
     544  /*
     545   * For Emacs M-x compile
     546   * Local Variables:
     547   * 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-tcl.c ../gnulib-lib/libgettextlib.la"
     548   * End:
     549   */
     550  
     551  #endif /* TEST */