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