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