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