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