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