(root)/
gettext-0.22.4/
gettext-tools/
src/
format-perl.c
       1  /* Perl format strings.
       2     Copyright (C) 2004, 2006-2007, 2009, 2019-2020, 2023 Free Software Foundation, Inc.
       3     Written by Bruno Haible <bruno@clisp.org>, 2003.
       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  /* Perl format strings are implemented in function Perl_sv_vcatpvfn in
      35     perl-5.8.0/sv.c.
      36     A directive
      37     - starts with '%' or '%m$' where m is a positive integer starting with a
      38       nonzero digit,
      39     - is optionally followed by any of the characters '#', '0', '-', ' ', '+',
      40       each of which acts as a flag,
      41     - is optionally followed by a vector specification: 'v' or '*v' (reads an
      42       argument) or '*m$v' where m is a positive integer starting with a nonzero
      43       digit,
      44     - is optionally followed by a width specification: '*' (reads an argument)
      45       or '*m$' where m is a positive integer starting with a nonzero digit or
      46       a nonempty digit sequence starting with a nonzero digit,
      47     - is optionally followed by '.' and a precision specification: '*' (reads
      48       an argument) or '*m$' where m is a positive integer starting with a
      49       nonzero digit or a digit sequence,
      50     - is optionally followed by a size specifier, one of 'h' 'l' 'll' 'L' 'q'
      51       'V' 'I32' 'I64' 'I',
      52     - is finished by a specifier
      53         - '%', that needs no argument,
      54         - 'c', that needs a small integer argument,
      55         - 's', that needs a string argument,
      56         - '_', that needs a scalar vector argument,
      57         - 'p', that needs a pointer argument,
      58         - 'i', 'd', 'D', that need an integer argument,
      59         - 'u', 'U', 'b', 'o', 'O', 'x', 'X', that need an unsigned integer
      60           argument,
      61         - 'e', 'E', 'f', 'F', 'g', 'G', that need a floating-point argument,
      62         - 'n', that needs a pointer to integer.
      63     So there can be numbered argument specifications:
      64     - '%m$' for the format string,
      65     - '*m$v' for the vector,
      66     - '*m$' for the width,
      67     - '.*m$' for the precision.
      68     Numbered and unnumbered argument specifications can be used in the same
      69     string. The effect of '%m$' is to take argument number m, without affecting
      70     the current argument number. The current argument number is incremented
      71     after processing a directive with an unnumbered argument specification.
      72   */
      73  
      74  enum format_arg_type
      75  {
      76    FAT_NONE              = 0,
      77    /* Basic types */
      78    FAT_INTEGER           = 1,
      79    FAT_DOUBLE            = 2,
      80    FAT_CHAR              = 3,
      81    FAT_STRING            = 4,
      82    FAT_SCALAR_VECTOR     = 5,
      83    FAT_POINTER           = 6,
      84    FAT_COUNT_POINTER     = 7,
      85    /* Flags */
      86    FAT_UNSIGNED          = 1 << 3,
      87    FAT_SIZE_SHORT        = 1 << 4,
      88    FAT_SIZE_V            = 2 << 4,
      89    FAT_SIZE_PTR          = 3 << 4,
      90    FAT_SIZE_LONG         = 4 << 4,
      91    FAT_SIZE_LONGLONG     = 5 << 4,
      92    /* Bitmasks */
      93    FAT_SIZE_MASK         = (FAT_SIZE_SHORT | FAT_SIZE_V | FAT_SIZE_PTR
      94                             | FAT_SIZE_LONG | FAT_SIZE_LONGLONG)
      95  };
      96  #ifdef __cplusplus
      97  typedef int format_arg_type_t;
      98  #else
      99  typedef enum format_arg_type format_arg_type_t;
     100  #endif
     101  
     102  struct numbered_arg
     103  {
     104    unsigned int number;
     105    format_arg_type_t type;
     106  };
     107  
     108  struct spec
     109  {
     110    unsigned int directives;
     111    unsigned int numbered_arg_count;
     112    struct numbered_arg *numbered;
     113  };
     114  
     115  /* Locale independent test for a decimal digit.
     116     Argument can be  'char' or 'unsigned char'.  (Whereas the argument of
     117     <ctype.h> isdigit must be an 'unsigned char'.)  */
     118  #undef isdigit
     119  #define isdigit(c) ((unsigned int) ((c) - '0') < 10)
     120  
     121  /* Locale independent test for a nonzero decimal digit.  */
     122  #define isnonzerodigit(c) ((unsigned int) ((c) - '1') < 9)
     123  
     124  
     125  static int
     126  numbered_arg_compare (const void *p1, const void *p2)
     127  {
     128    unsigned int n1 = ((const struct numbered_arg *) p1)->number;
     129    unsigned int n2 = ((const struct numbered_arg *) p2)->number;
     130  
     131    return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
     132  }
     133  
     134  static void *
     135  format_parse (const char *format, bool translated, char *fdi,
     136                char **invalid_reason)
     137  {
     138    const char *const format_start = format;
     139    unsigned int directives;
     140    unsigned int numbered_arg_count;
     141    struct numbered_arg *numbered;
     142    unsigned int numbered_allocated;
     143    unsigned int unnumbered_arg_count;
     144    struct spec *result;
     145  
     146    directives = 0;
     147    numbered_arg_count = 0;
     148    numbered = NULL;
     149    numbered_allocated = 0;
     150    unnumbered_arg_count = 0;
     151  
     152    for (; *format != '\0';)
     153      if (*format++ == '%')
     154        {
     155          /* A directive.  */
     156          unsigned int number = 0;
     157          bool vectorize = false;
     158          format_arg_type_t type;
     159          format_arg_type_t size;
     160  
     161          FDI_SET (format - 1, FMTDIR_START);
     162          directives++;
     163  
     164          if (isnonzerodigit (*format))
     165            {
     166              const char *f = format;
     167              unsigned int m = 0;
     168  
     169              do
     170                {
     171                  m = 10 * m + (*f - '0');
     172                  f++;
     173                }
     174              while (isdigit (*f));
     175  
     176              if (*f == '$')
     177                {
     178                  number = m;
     179                  format = ++f;
     180                }
     181            }
     182  
     183          /* Parse flags.  */
     184          while (*format == ' ' || *format == '+' || *format == '-'
     185                 || *format == '#' || *format == '0')
     186            format++;
     187  
     188          /* Parse vector.  */
     189          if (*format == 'v')
     190            {
     191              format++;
     192              vectorize = true;
     193            }
     194          else if (*format == '*')
     195            {
     196              const char *f = format;
     197  
     198              f++;
     199              if (*f == 'v')
     200                {
     201                  format = ++f;
     202                  vectorize = true;
     203  
     204                  /* Unnumbered argument.  */
     205                  if (numbered_allocated == numbered_arg_count)
     206                    {
     207                      numbered_allocated = 2 * numbered_allocated + 1;
     208                      numbered = (struct numbered_arg *) xrealloc (numbered, numbered_allocated * sizeof (struct numbered_arg));
     209                    }
     210                  numbered[numbered_arg_count].number = ++unnumbered_arg_count;
     211                  numbered[numbered_arg_count].type = FAT_SCALAR_VECTOR; /* or FAT_STRING? */
     212                  numbered_arg_count++;
     213                }
     214              else if (isnonzerodigit (*f))
     215                {
     216                  unsigned int m = 0;
     217  
     218                  do
     219                    {
     220                      m = 10 * m + (*f - '0');
     221                      f++;
     222                    }
     223                  while (isdigit (*f));
     224  
     225                  if (*f == '$')
     226                    {
     227                      f++;
     228                      if (*f == 'v')
     229                        {
     230                          unsigned int vector_number = m;
     231  
     232                          format = ++f;
     233                          vectorize = true;
     234  
     235                          /* Numbered argument.  */
     236                          /* Note: As of perl-5.8.0, this is not correctly
     237                             implemented in perl's sv.c.  */
     238                          if (numbered_allocated == numbered_arg_count)
     239                            {
     240                              numbered_allocated = 2 * numbered_allocated + 1;
     241                              numbered = (struct numbered_arg *) xrealloc (numbered, numbered_allocated * sizeof (struct numbered_arg));
     242                            }
     243                          numbered[numbered_arg_count].number = vector_number;
     244                          numbered[numbered_arg_count].type = FAT_SCALAR_VECTOR; /* or FAT_STRING? */
     245                          numbered_arg_count++;
     246                        }
     247                    }
     248                }
     249            }
     250  
     251          if (vectorize)
     252            {
     253              /* Numbered or unnumbered argument.  */
     254              if (numbered_allocated == numbered_arg_count)
     255                {
     256                  numbered_allocated = 2 * numbered_allocated + 1;
     257                  numbered = (struct numbered_arg *) xrealloc (numbered, numbered_allocated * sizeof (struct numbered_arg));
     258                }
     259              numbered[numbered_arg_count].number = (number ? number : ++unnumbered_arg_count);
     260              numbered[numbered_arg_count].type = FAT_SCALAR_VECTOR;
     261              numbered_arg_count++;
     262            }
     263  
     264          /* Parse width.  */
     265          if (*format == '*')
     266            {
     267              unsigned int width_number = 0;
     268  
     269              format++;
     270  
     271              if (isnonzerodigit (*format))
     272                {
     273                  const char *f = format;
     274                  unsigned int m = 0;
     275  
     276                  do
     277                    {
     278                      m = 10 * m + (*f - '0');
     279                      f++;
     280                    }
     281                  while (isdigit (*f));
     282  
     283                  if (*f == '$')
     284                    {
     285                      width_number = m;
     286                      format = ++f;
     287                    }
     288                }
     289  
     290              /* Numbered or unnumbered argument.  */
     291              /* Note: As of perl-5.8.0, this is not correctly
     292                 implemented in perl's sv.c.  */
     293              if (numbered_allocated == numbered_arg_count)
     294                {
     295                  numbered_allocated = 2 * numbered_allocated + 1;
     296                  numbered = (struct numbered_arg *) xrealloc (numbered, numbered_allocated * sizeof (struct numbered_arg));
     297                }
     298              numbered[numbered_arg_count].number = (width_number ? width_number : ++unnumbered_arg_count);
     299              numbered[numbered_arg_count].type = FAT_INTEGER;
     300              numbered_arg_count++;
     301            }
     302          else if (isnonzerodigit (*format))
     303            {
     304              do format++; while (isdigit (*format));
     305            }
     306  
     307          /* Parse precision.  */
     308          if (*format == '.')
     309            {
     310              format++;
     311  
     312              if (*format == '*')
     313                {
     314                  unsigned int precision_number = 0;
     315  
     316                  format++;
     317  
     318                  if (isnonzerodigit (*format))
     319                    {
     320                      const char *f = format;
     321                      unsigned int m = 0;
     322  
     323                      do
     324                        {
     325                          m = 10 * m + (*f - '0');
     326                          f++;
     327                        }
     328                      while (isdigit (*f));
     329  
     330                      if (*f == '$')
     331                        {
     332                          precision_number = m;
     333                          format = ++f;
     334                        }
     335                    }
     336  
     337                  /* Numbered or unnumbered argument.  */
     338                  if (numbered_allocated == numbered_arg_count)
     339                    {
     340                      numbered_allocated = 2 * numbered_allocated + 1;
     341                      numbered = (struct numbered_arg *) xrealloc (numbered, numbered_allocated * sizeof (struct numbered_arg));
     342                    }
     343                  numbered[numbered_arg_count].number = (precision_number ? precision_number : ++unnumbered_arg_count);
     344                  numbered[numbered_arg_count].type = FAT_INTEGER;
     345                  numbered_arg_count++;
     346                }
     347              else
     348                {
     349                  while (isdigit (*format)) format++;
     350                }
     351            }
     352  
     353          /* Parse size.  */
     354          size = 0;
     355          if (*format == 'h')
     356            {
     357              size = FAT_SIZE_SHORT;
     358              format++;
     359            }
     360          else if (*format == 'l')
     361            {
     362              if (format[1] == 'l')
     363                {
     364                  size = FAT_SIZE_LONGLONG;
     365                  format += 2;
     366                }
     367              else
     368                {
     369                  size = FAT_SIZE_LONG;
     370                  format++;
     371                }
     372            }
     373          else if (*format == 'L' || *format == 'q')
     374            {
     375              size = FAT_SIZE_LONGLONG;
     376              format++;
     377            }
     378          else if (*format == 'V')
     379            {
     380              size = FAT_SIZE_V;
     381              format++;
     382            }
     383          else if (*format == 'I')
     384            {
     385              if (format[1] == '6' && format[2] == '4')
     386                {
     387                  size = FAT_SIZE_LONGLONG;
     388                  format += 3;
     389                }
     390              else if (format[1] == '3' && format[2] == '2')
     391                {
     392                  size = 0; /* FAT_SIZE_INT */
     393                  format += 3;
     394                }
     395              else
     396                {
     397                  size = FAT_SIZE_PTR;
     398                  format++;
     399                }
     400            }
     401  
     402          switch (*format)
     403            {
     404            case '%':
     405              type = FAT_NONE;
     406              break;
     407            case 'c':
     408              type = FAT_CHAR;
     409              break;
     410            case 's':
     411              type = FAT_STRING;
     412              break;
     413            case '_':
     414              type = FAT_SCALAR_VECTOR;
     415              break;
     416            case 'D':
     417              type = FAT_INTEGER | FAT_SIZE_V;
     418              break;
     419            case 'i': case 'd':
     420              type = FAT_INTEGER | size;
     421              break;
     422            case 'U': case 'O':
     423              type = FAT_INTEGER | FAT_UNSIGNED | FAT_SIZE_V;
     424              break;
     425            case 'u': case 'b': case 'o': case 'x': case 'X':
     426              type = FAT_INTEGER | FAT_UNSIGNED | size;
     427              break;
     428            case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
     429              if (size == FAT_SIZE_SHORT || size == FAT_SIZE_LONG)
     430                {
     431                  *invalid_reason =
     432                    xasprintf (_("In the directive number %u, the size specifier is incompatible with the conversion specifier '%c'."), directives, *format);
     433                  FDI_SET (format, FMTDIR_ERROR);
     434                  goto bad_format;
     435                }
     436              type = FAT_DOUBLE | size;
     437              break;
     438            case 'p':
     439              type = FAT_POINTER;
     440              break;
     441            case 'n':
     442              type = FAT_COUNT_POINTER | size;
     443              break;
     444            default:
     445              if (*format == '\0')
     446                {
     447                  *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
     448                  FDI_SET (format - 1, FMTDIR_ERROR);
     449                }
     450              else
     451                {
     452                  *invalid_reason =
     453                    INVALID_CONVERSION_SPECIFIER (directives, *format);
     454                  FDI_SET (format, FMTDIR_ERROR);
     455                }
     456              goto bad_format;
     457            }
     458  
     459          if (type != FAT_NONE && !vectorize)
     460            {
     461              /* Numbered or unnumbered argument.  */
     462              if (numbered_allocated == numbered_arg_count)
     463                {
     464                  numbered_allocated = 2 * numbered_allocated + 1;
     465                  numbered = (struct numbered_arg *) xrealloc (numbered, numbered_allocated * sizeof (struct numbered_arg));
     466                }
     467              numbered[numbered_arg_count].number = (number ? number : ++unnumbered_arg_count);
     468              numbered[numbered_arg_count].type = type;
     469              numbered_arg_count++;
     470            }
     471  
     472          FDI_SET (format, FMTDIR_END);
     473  
     474          format++;
     475        }
     476  
     477    /* Sort the numbered argument array, and eliminate duplicates.  */
     478    if (numbered_arg_count > 1)
     479      {
     480        unsigned int i, j;
     481        bool err;
     482  
     483        qsort (numbered, numbered_arg_count,
     484               sizeof (struct numbered_arg), numbered_arg_compare);
     485  
     486        /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
     487        err = false;
     488        for (i = j = 0; i < numbered_arg_count; i++)
     489          if (j > 0 && numbered[i].number == numbered[j-1].number)
     490            {
     491              format_arg_type_t type1 = numbered[i].type;
     492              format_arg_type_t type2 = numbered[j-1].type;
     493              format_arg_type_t type_both;
     494  
     495              if (type1 == type2)
     496                type_both = type1;
     497              else
     498                {
     499                  /* Incompatible types.  */
     500                  type_both = FAT_NONE;
     501                  if (!err)
     502                    *invalid_reason =
     503                      INVALID_INCOMPATIBLE_ARG_TYPES (numbered[i].number);
     504                  err = true;
     505                }
     506  
     507              numbered[j-1].type = type_both;
     508            }
     509          else
     510            {
     511              if (j < i)
     512                {
     513                  numbered[j].number = numbered[i].number;
     514                  numbered[j].type = numbered[i].type;
     515                }
     516              j++;
     517            }
     518        numbered_arg_count = j;
     519        if (err)
     520          /* *invalid_reason has already been set above.  */
     521          goto bad_format;
     522      }
     523  
     524    result = XMALLOC (struct spec);
     525    result->directives = directives;
     526    result->numbered_arg_count = numbered_arg_count;
     527    result->numbered = numbered;
     528    return result;
     529  
     530   bad_format:
     531    if (numbered != NULL)
     532      free (numbered);
     533    return NULL;
     534  }
     535  
     536  static void
     537  format_free (void *descr)
     538  {
     539    struct spec *spec = (struct spec *) descr;
     540  
     541    if (spec->numbered != NULL)
     542      free (spec->numbered);
     543    free (spec);
     544  }
     545  
     546  static int
     547  format_get_number_of_directives (void *descr)
     548  {
     549    struct spec *spec = (struct spec *) descr;
     550  
     551    return spec->directives;
     552  }
     553  
     554  static bool
     555  format_check (void *msgid_descr, void *msgstr_descr, bool equality,
     556                formatstring_error_logger_t error_logger,
     557                const char *pretty_msgid, const char *pretty_msgstr)
     558  {
     559    struct spec *spec1 = (struct spec *) msgid_descr;
     560    struct spec *spec2 = (struct spec *) msgstr_descr;
     561    bool err = false;
     562  
     563    if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
     564      {
     565        unsigned int i, j;
     566        unsigned int n1 = spec1->numbered_arg_count;
     567        unsigned int n2 = spec2->numbered_arg_count;
     568  
     569        /* Check that the argument numbers are the same.
     570           Both arrays are sorted.  We search for the first difference.  */
     571        for (i = 0, j = 0; i < n1 || j < n2; )
     572          {
     573            int cmp = (i >= n1 ? 1 :
     574                       j >= n2 ? -1 :
     575                       spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
     576                       spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
     577                       0);
     578  
     579            if (cmp > 0)
     580              {
     581                if (error_logger)
     582                  error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"),
     583                                spec2->numbered[j].number, pretty_msgstr,
     584                                pretty_msgid);
     585                err = true;
     586                break;
     587              }
     588            else if (cmp < 0)
     589              {
     590                if (equality)
     591                  {
     592                    if (error_logger)
     593                      error_logger (_("a format specification for argument %u doesn't exist in '%s'"),
     594                                    spec1->numbered[i].number, pretty_msgstr);
     595                    err = true;
     596                    break;
     597                  }
     598                else
     599                  i++;
     600              }
     601            else
     602              j++, i++;
     603          }
     604        /* Check the argument types are the same.  */
     605        if (!err)
     606          for (i = 0, j = 0; j < n2; )
     607            {
     608              if (spec1->numbered[i].number == spec2->numbered[j].number)
     609                {
     610                  if (spec1->numbered[i].type != spec2->numbered[j].type)
     611                    {
     612                      if (error_logger)
     613                        error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"),
     614                                      pretty_msgid, pretty_msgstr,
     615                                      spec2->numbered[j].number);
     616                      err = true;
     617                      break;
     618                    }
     619                  j++, i++;
     620                }
     621              else
     622                i++;
     623            }
     624      }
     625  
     626    return err;
     627  }
     628  
     629  
     630  struct formatstring_parser formatstring_perl =
     631  {
     632    format_parse,
     633    format_free,
     634    format_get_number_of_directives,
     635    NULL,
     636    format_check
     637  };
     638  
     639  
     640  #ifdef TEST
     641  
     642  /* Test program: Print the argument list specification returned by
     643     format_parse for strings read from standard input.  */
     644  
     645  #include <stdio.h>
     646  
     647  static void
     648  format_print (void *descr)
     649  {
     650    struct spec *spec = (struct spec *) descr;
     651    unsigned int last;
     652    unsigned int i;
     653  
     654    if (spec == NULL)
     655      {
     656        printf ("INVALID");
     657        return;
     658      }
     659  
     660    printf ("(");
     661    last = 1;
     662    for (i = 0; i < spec->numbered_arg_count; i++)
     663      {
     664        unsigned int number = spec->numbered[i].number;
     665  
     666        if (i > 0)
     667          printf (" ");
     668        if (number < last)
     669          abort ();
     670        for (; last < number; last++)
     671          printf ("_ ");
     672        if (spec->numbered[i].type & FAT_UNSIGNED)
     673          printf ("[unsigned]");
     674        switch (spec->numbered[i].type & FAT_SIZE_MASK)
     675          {
     676          case 0:
     677            break;
     678          case FAT_SIZE_SHORT:
     679            printf ("[short]");
     680            break;
     681          case FAT_SIZE_V:
     682            printf ("[IV]");
     683            break;
     684          case FAT_SIZE_PTR:
     685            printf ("[PTR]");
     686            break;
     687          case FAT_SIZE_LONG:
     688            printf ("[long]");
     689            break;
     690          case FAT_SIZE_LONGLONG:
     691            printf ("[long long]");
     692            break;
     693          default:
     694            abort ();
     695          }
     696        switch (spec->numbered[i].type & ~(FAT_UNSIGNED | FAT_SIZE_MASK))
     697          {
     698          case FAT_INTEGER:
     699            printf ("i");
     700            break;
     701          case FAT_DOUBLE:
     702            printf ("f");
     703            break;
     704          case FAT_CHAR:
     705            printf ("c");
     706            break;
     707          case FAT_STRING:
     708            printf ("s");
     709            break;
     710          case FAT_SCALAR_VECTOR:
     711            printf ("sv");
     712            break;
     713          case FAT_POINTER:
     714            printf ("p");
     715            break;
     716          case FAT_COUNT_POINTER:
     717            printf ("n");
     718            break;
     719          default:
     720            abort ();
     721          }
     722        last = number + 1;
     723      }
     724    printf (")");
     725  }
     726  
     727  int
     728  main ()
     729  {
     730    for (;;)
     731      {
     732        char *line = NULL;
     733        size_t line_size = 0;
     734        int line_len;
     735        char *invalid_reason;
     736        void *descr;
     737  
     738        line_len = getline (&line, &line_size, stdin);
     739        if (line_len < 0)
     740          break;
     741        if (line_len > 0 && line[line_len - 1] == '\n')
     742          line[--line_len] = '\0';
     743  
     744        invalid_reason = NULL;
     745        descr = format_parse (line, false, NULL, &invalid_reason);
     746  
     747        format_print (descr);
     748        printf ("\n");
     749        if (descr == NULL)
     750          printf ("%s\n", invalid_reason);
     751  
     752        free (invalid_reason);
     753        free (line);
     754      }
     755  
     756    return 0;
     757  }
     758  
     759  /*
     760   * For Emacs M-x compile
     761   * Local Variables:
     762   * 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-perl.c ../gnulib-lib/libgettextlib.la"
     763   * End:
     764   */
     765  
     766  #endif /* TEST */