Home | History | Annotate | Line # | Download | only in src
      1 /* C# format strings.
      2    Copyright (C) 2003-2004, 2006 Free Software Foundation, Inc.
      3    Written by Bruno Haible <bruno (at) 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 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 "gettext.h"
     31 
     32 #define _(str) gettext (str)
     33 
     34 /* C# format strings are described in the description of the .NET System.String
     35    class and implemented in
     36      pnetlib-0.5.6/runtime/System/String.cs
     37    and
     38      mcs-0.28/class/corlib/System/String.cs
     39    A format string consists of literal text (that is output verbatim), doubled
     40    braces ('{{' and '}}', that lead to a single brace when output), and
     41    directives.
     42    A directive
     43    - starts with '{',
     44    - is followed by a nonnegative integer m,
     45    - is optionally followed by ',' and an integer denoting a width,
     46    - is optionally followed by ':' and a sequence of format specifiers.
     47      (But the interpretation of the format specifiers is up to the IFormattable
     48      implementation, depending on the argument's runtime value. New classes
     49      implementing IFormattable can be defined by the user.)
     50    - is finished with '}'.
     51  */
     52 
     53 struct spec
     54 {
     55   unsigned int directives;
     56   unsigned int numbered_arg_count;
     57 };
     58 
     59 static void *
     60 format_parse (const char *format, bool translated, char **invalid_reason)
     61 {
     62   struct spec spec;
     63   struct spec *result;
     64 
     65   spec.directives = 0;
     66   spec.numbered_arg_count = 0;
     67 
     68   for (; *format != '\0';)
     69     {
     70       char c = *format++;
     71 
     72       if (c == '{')
     73 	{
     74 	  if (*format == '{')
     75 	    format++;
     76 	  else
     77 	    {
     78 	      /* A directive.  */
     79 	      unsigned int number;
     80 
     81 	      spec.directives++;
     82 
     83 	      if (!c_isdigit (*format))
     84 		{
     85 		  *invalid_reason =
     86 		    xasprintf (_("In the directive number %u, '{' is not followed by an argument number."), spec.directives);
     87 		  return NULL;
     88 		}
     89 	      number = 0;
     90 	      do
     91 		{
     92 		  number = 10 * number + (*format - '0');
     93 		  format++;
     94 		}
     95 	      while (c_isdigit (*format));
     96 
     97 	      if (*format == ',')
     98 		{
     99 		  /* Parse width.  */
    100 		  format++;
    101 		  if (*format == '-')
    102 		    format++;
    103 		  if (!c_isdigit (*format))
    104 		    {
    105 		      *invalid_reason =
    106 			xasprintf (_("In the directive number %u, ',' is not followed by a number."), spec.directives);
    107 		      return NULL;
    108 		    }
    109 		  do
    110 		    format++;
    111 		  while (c_isdigit (*format));
    112 		}
    113 
    114 	      if (*format == ':')
    115 		{
    116 		  /* Parse format specifiers.  */
    117 		  do
    118 		    format++;
    119 		  while (*format != '\0' && *format != '}');
    120 		}
    121 
    122 	      if (*format == '\0')
    123 		{
    124 		  *invalid_reason =
    125 		    xstrdup (_("The string ends in the middle of a directive: found '{' without matching '}'."));
    126 		  return NULL;
    127 		}
    128 
    129 	      if (*format != '}')
    130 		{
    131 		  *invalid_reason =
    132 		    (c_isprint (*format)
    133 		     ? xasprintf (_("The directive number %u ends with an invalid character '%c' instead of '}'."), spec.directives, *format)
    134 		     : xasprintf (_("The directive number %u ends with an invalid character instead of '}'."), spec.directives));
    135 		  return NULL;
    136 		}
    137 
    138 	      format++;
    139 
    140 	      if (spec.numbered_arg_count <= number)
    141 		spec.numbered_arg_count = number + 1;
    142 	    }
    143 	}
    144       else if (c == '}')
    145 	{
    146 	  if (*format == '}')
    147 	    format++;
    148 	  else
    149 	    {
    150 	      *invalid_reason =
    151 		(spec.directives == 0
    152 		 ? xstrdup (_("The string starts in the middle of a directive: found '}' without matching '{'."))
    153 		 : xasprintf (_("The string contains a lone '}' after directive number %u."), spec.directives));
    154 	      return NULL;
    155 	    }
    156 	}
    157     }
    158 
    159   result = (struct spec *) xmalloc (sizeof (struct spec));
    160   *result = spec;
    161   return result;
    162 }
    163 
    164 static void
    165 format_free (void *descr)
    166 {
    167   struct spec *spec = (struct spec *) descr;
    168 
    169   free (spec);
    170 }
    171 
    172 static int
    173 format_get_number_of_directives (void *descr)
    174 {
    175   struct spec *spec = (struct spec *) descr;
    176 
    177   return spec->directives;
    178 }
    179 
    180 static bool
    181 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
    182 	      formatstring_error_logger_t error_logger,
    183 	      const char *pretty_msgstr)
    184 {
    185   struct spec *spec1 = (struct spec *) msgid_descr;
    186   struct spec *spec2 = (struct spec *) msgstr_descr;
    187   bool err = false;
    188 
    189   /* Check that the argument counts are the same.  */
    190   if (equality
    191       ? spec1->numbered_arg_count != spec2->numbered_arg_count
    192       : spec1->numbered_arg_count < spec2->numbered_arg_count)
    193     {
    194       if (error_logger)
    195 	error_logger (_("number of format specifications in 'msgid' and '%s' does not match"),
    196 		      pretty_msgstr);
    197       err = true;
    198     }
    199 
    200   return err;
    201 }
    202 
    203 
    204 struct formatstring_parser formatstring_csharp =
    205 {
    206   format_parse,
    207   format_free,
    208   format_get_number_of_directives,
    209   NULL,
    210   format_check
    211 };
    212 
    213 
    214 #ifdef TEST
    215 
    216 /* Test program: Print the argument list specification returned by
    217    format_parse for strings read from standard input.  */
    218 
    219 #include <stdio.h>
    220 #include "getline.h"
    221 
    222 static void
    223 format_print (void *descr)
    224 {
    225   struct spec *spec = (struct spec *) descr;
    226   unsigned int i;
    227 
    228   if (spec == NULL)
    229     {
    230       printf ("INVALID");
    231       return;
    232     }
    233 
    234   printf ("(");
    235   for (i = 0; i < spec->numbered_arg_count; i++)
    236     {
    237       if (i > 0)
    238 	printf (" ");
    239       printf ("*");
    240     }
    241   printf (")");
    242 }
    243 
    244 int
    245 main ()
    246 {
    247   for (;;)
    248     {
    249       char *line = NULL;
    250       size_t line_size = 0;
    251       int line_len;
    252       char *invalid_reason;
    253       void *descr;
    254 
    255       line_len = getline (&line, &line_size, stdin);
    256       if (line_len < 0)
    257 	break;
    258       if (line_len > 0 && line[line_len - 1] == '\n')
    259 	line[--line_len] = '\0';
    260 
    261       invalid_reason = NULL;
    262       descr = format_parse (line, false, &invalid_reason);
    263 
    264       format_print (descr);
    265       printf ("\n");
    266       if (descr == NULL)
    267 	printf ("%s\n", invalid_reason);
    268 
    269       free (invalid_reason);
    270       free (line);
    271     }
    272 
    273   return 0;
    274 }
    275 
    276 /*
    277  * For Emacs M-x compile
    278  * Local Variables:
    279  * 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-csharp.c ../lib/libgettextlib.la"
    280  * End:
    281  */
    282 
    283 #endif /* TEST */
    284