1/* C# format strings.
2   Copyright (C) 2003-2004, 2006 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 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
53struct spec
54{
55  unsigned int directives;
56  unsigned int numbered_arg_count;
57};
58
59static void *
60format_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
164static void
165format_free (void *descr)
166{
167  struct spec *spec = (struct spec *) descr;
168
169  free (spec);
170}
171
172static int
173format_get_number_of_directives (void *descr)
174{
175  struct spec *spec = (struct spec *) descr;
176
177  return spec->directives;
178}
179
180static bool
181format_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
204struct 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
222static void
223format_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
244int
245main ()
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