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