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