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