1/* librep format strings. 2 Copyright (C) 2001-2004, 2006-2007 Free Software Foundation, Inc. 3 Written by Bruno Haible <haible@clisp.cons.org>, 2001. 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 "format-invalid.h" 30#include "gettext.h" 31 32#define _(str) gettext (str) 33 34/* librep format strings are implemented in librep-0.14/src/streams.c. 35 A directive 36 - starts with '%' or '%m$' where m is a positive integer, 37 - is optionally followed by any of the characters '-', '^', '0', '+', ' ', 38 each of which acts as a flag, 39 - is optionally followed by a width specification: a nonempty digit 40 sequence, 41 - is optionally followed by '.' and a precision specification: a nonempty 42 digit sequence, 43 - is finished by a specifier 44 - '%', that needs no argument, 45 - 'c', that need a character argument, 46 - 'd', 'x', 'X', 'o', that need an integer argument, 47 - 's', that need an argument and prints it using princ, 48 - 'S', that need an argument and prints it using prin1. 49 Numbered ('%m$') and unnumbered argument specifications can be used in the 50 same string. The effect of '%m$' is to set the current argument number to 51 m. The current argument number is incremented after processing a directive. 52 */ 53 54enum format_arg_type 55{ 56 FAT_NONE, 57 FAT_CHARACTER, 58 FAT_INTEGER, 59 FAT_OBJECT_PRETTY, 60 FAT_OBJECT 61}; 62 63struct numbered_arg 64{ 65 unsigned int number; 66 enum format_arg_type type; 67}; 68 69struct spec 70{ 71 unsigned int directives; 72 unsigned int numbered_arg_count; 73 unsigned int allocated; 74 struct numbered_arg *numbered; 75}; 76 77/* Locale independent test for a decimal digit. 78 Argument can be 'char' or 'unsigned char'. (Whereas the argument of 79 <ctype.h> isdigit must be an 'unsigned char'.) */ 80#undef isdigit 81#define isdigit(c) ((unsigned int) ((c) - '0') < 10) 82 83 84static int 85numbered_arg_compare (const void *p1, const void *p2) 86{ 87 unsigned int n1 = ((const struct numbered_arg *) p1)->number; 88 unsigned int n2 = ((const struct numbered_arg *) p2)->number; 89 90 return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); 91} 92 93static void * 94format_parse (const char *format, bool translated, char *fdi, 95 char **invalid_reason) 96{ 97 const char *const format_start = format; 98 struct spec spec; 99 struct spec *result; 100 unsigned int number; 101 102 spec.directives = 0; 103 spec.numbered_arg_count = 0; 104 spec.allocated = 0; 105 spec.numbered = NULL; 106 number = 1; 107 108 for (; *format != '\0';) 109 if (*format++ == '%') 110 { 111 /* A directive. */ 112 enum format_arg_type type; 113 114 FDI_SET (format - 1, FMTDIR_START); 115 spec.directives++; 116 117 if (isdigit (*format)) 118 { 119 const char *f = format; 120 unsigned int m = 0; 121 122 do 123 { 124 m = 10 * m + (*f - '0'); 125 f++; 126 } 127 while (isdigit (*f)); 128 129 if (*f == '$' && m > 0) 130 { 131 number = m; 132 format = ++f; 133 } 134 } 135 136 /* Parse flags. */ 137 while (*format == '-' || *format == '^' || *format == '0' 138 || *format == '+' || *format == ' ') 139 format++; 140 141 /* Parse width. */ 142 if (isdigit (*format)) 143 { 144 do format++; while (isdigit (*format)); 145 } 146 147 /* Parse precision. */ 148 if (*format == '.') 149 { 150 format++; 151 152 if (isdigit (*format)) 153 { 154 do format++; while (isdigit (*format)); 155 } 156 } 157 158 switch (*format) 159 { 160 case '%': 161 type = FAT_NONE; 162 break; 163 case 'c': 164 type = FAT_CHARACTER; 165 break; 166 case 'd': case 'x': case 'X': case 'o': 167 type = FAT_INTEGER; 168 break; 169 case 's': 170 type = FAT_OBJECT_PRETTY; 171 break; 172 case 'S': 173 type = FAT_OBJECT; 174 break; 175 default: 176 if (*format == '\0') 177 { 178 *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE (); 179 FDI_SET (format - 1, FMTDIR_ERROR); 180 } 181 else 182 { 183 *invalid_reason = 184 INVALID_CONVERSION_SPECIFIER (spec.directives, *format); 185 FDI_SET (format, FMTDIR_ERROR); 186 } 187 goto bad_format; 188 } 189 190 if (type != FAT_NONE) 191 { 192 if (spec.allocated == spec.numbered_arg_count) 193 { 194 spec.allocated = 2 * spec.allocated + 1; 195 spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg)); 196 } 197 spec.numbered[spec.numbered_arg_count].number = number; 198 spec.numbered[spec.numbered_arg_count].type = type; 199 spec.numbered_arg_count++; 200 201 number++; 202 } 203 204 FDI_SET (format, FMTDIR_END); 205 206 format++; 207 } 208 209 /* Sort the numbered argument array, and eliminate duplicates. */ 210 if (spec.numbered_arg_count > 1) 211 { 212 unsigned int i, j; 213 bool err; 214 215 qsort (spec.numbered, spec.numbered_arg_count, 216 sizeof (struct numbered_arg), numbered_arg_compare); 217 218 /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ 219 err = false; 220 for (i = j = 0; i < spec.numbered_arg_count; i++) 221 if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number) 222 { 223 enum format_arg_type type1 = spec.numbered[i].type; 224 enum format_arg_type type2 = spec.numbered[j-1].type; 225 enum format_arg_type type_both; 226 227 if (type1 == type2) 228 type_both = type1; 229 else 230 { 231 /* Incompatible types. */ 232 type_both = FAT_NONE; 233 if (!err) 234 *invalid_reason = 235 INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number); 236 err = true; 237 } 238 239 spec.numbered[j-1].type = type_both; 240 } 241 else 242 { 243 if (j < i) 244 { 245 spec.numbered[j].number = spec.numbered[i].number; 246 spec.numbered[j].type = spec.numbered[i].type; 247 } 248 j++; 249 } 250 spec.numbered_arg_count = j; 251 if (err) 252 /* *invalid_reason has already been set above. */ 253 goto bad_format; 254 } 255 256 result = XMALLOC (struct spec); 257 *result = spec; 258 return result; 259 260 bad_format: 261 if (spec.numbered != NULL) 262 free (spec.numbered); 263 return NULL; 264} 265 266static void 267format_free (void *descr) 268{ 269 struct spec *spec = (struct spec *) descr; 270 271 if (spec->numbered != NULL) 272 free (spec->numbered); 273 free (spec); 274} 275 276static int 277format_get_number_of_directives (void *descr) 278{ 279 struct spec *spec = (struct spec *) descr; 280 281 return spec->directives; 282} 283 284static bool 285format_check (void *msgid_descr, void *msgstr_descr, bool equality, 286 formatstring_error_logger_t error_logger, 287 const char *pretty_msgstr) 288{ 289 struct spec *spec1 = (struct spec *) msgid_descr; 290 struct spec *spec2 = (struct spec *) msgstr_descr; 291 bool err = false; 292 293 if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0) 294 { 295 unsigned int i, j; 296 unsigned int n1 = spec1->numbered_arg_count; 297 unsigned int n2 = spec2->numbered_arg_count; 298 299 /* Check the argument names are the same. 300 Both arrays are sorted. We search for the first difference. */ 301 for (i = 0, j = 0; i < n1 || j < n2; ) 302 { 303 int cmp = (i >= n1 ? 1 : 304 j >= n2 ? -1 : 305 spec1->numbered[i].number > spec2->numbered[j].number ? 1 : 306 spec1->numbered[i].number < spec2->numbered[j].number ? -1 : 307 0); 308 309 if (cmp > 0) 310 { 311 if (error_logger) 312 error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in 'msgid'"), 313 spec2->numbered[j].number, pretty_msgstr); 314 err = true; 315 break; 316 } 317 else if (cmp < 0) 318 { 319 if (equality) 320 { 321 if (error_logger) 322 error_logger (_("a format specification for argument %u doesn't exist in '%s'"), 323 spec1->numbered[i].number, pretty_msgstr); 324 err = true; 325 break; 326 } 327 else 328 i++; 329 } 330 else 331 j++, i++; 332 } 333 /* Check the argument types are the same. */ 334 if (!err) 335 for (i = 0, j = 0; j < n2; ) 336 { 337 if (spec1->numbered[i].number == spec2->numbered[j].number) 338 { 339 if (spec1->numbered[i].type != spec2->numbered[j].type) 340 { 341 if (error_logger) 342 error_logger (_("format specifications in 'msgid' and '%s' for argument %u are not the same"), 343 pretty_msgstr, spec2->numbered[j].number); 344 err = true; 345 break; 346 } 347 j++, i++; 348 } 349 else 350 i++; 351 } 352 } 353 354 return err; 355} 356 357 358struct formatstring_parser formatstring_librep = 359{ 360 format_parse, 361 format_free, 362 format_get_number_of_directives, 363 NULL, 364 format_check 365}; 366 367 368#ifdef TEST 369 370/* Test program: Print the argument list specification returned by 371 format_parse for strings read from standard input. */ 372 373#include <stdio.h> 374 375static void 376format_print (void *descr) 377{ 378 struct spec *spec = (struct spec *) descr; 379 unsigned int last; 380 unsigned int i; 381 382 if (spec == NULL) 383 { 384 printf ("INVALID"); 385 return; 386 } 387 388 printf ("("); 389 last = 1; 390 for (i = 0; i < spec->numbered_arg_count; i++) 391 { 392 unsigned int number = spec->numbered[i].number; 393 394 if (i > 0) 395 printf (" "); 396 if (number < last) 397 abort (); 398 for (; last < number; last++) 399 printf ("_ "); 400 switch (spec->numbered[i].type) 401 { 402 case FAT_CHARACTER: 403 printf ("c"); 404 break; 405 case FAT_INTEGER: 406 printf ("i"); 407 break; 408 case FAT_OBJECT_PRETTY: 409 printf ("s"); 410 break; 411 case FAT_OBJECT: 412 printf ("*"); 413 break; 414 default: 415 abort (); 416 } 417 last = number + 1; 418 } 419 printf (")"); 420} 421 422int 423main () 424{ 425 for (;;) 426 { 427 char *line = NULL; 428 size_t line_size = 0; 429 int line_len; 430 char *invalid_reason; 431 void *descr; 432 433 line_len = getline (&line, &line_size, stdin); 434 if (line_len < 0) 435 break; 436 if (line_len > 0 && line[line_len - 1] == '\n') 437 line[--line_len] = '\0'; 438 439 invalid_reason = NULL; 440 descr = format_parse (line, false, NULL, &invalid_reason); 441 442 format_print (descr); 443 printf ("\n"); 444 if (descr == NULL) 445 printf ("%s\n", invalid_reason); 446 447 free (invalid_reason); 448 free (line); 449 } 450 451 return 0; 452} 453 454/* 455 * For Emacs M-x compile 456 * Local Variables: 457 * 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-librep.c ../gnulib-lib/libgettextlib.la" 458 * End: 459 */ 460 461#endif /* TEST */ 462