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