1/* Perl brace format strings. 2 Copyright (C) 2004 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 2, or (at your option) 8 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, write to the Free Software Foundation, 17 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ 18 19#ifdef HAVE_CONFIG_H 20# include <config.h> 21#endif 22 23#include <stdbool.h> 24#include <stdlib.h> 25#include <string.h> 26 27#include "format.h" 28#include "xalloc.h" 29#include "gettext.h" 30 31#define _(str) gettext (str) 32 33/* Perl brace format strings are supported by Guido Flohr's libintl-perl 34 package, more precisely by the __expand and __x functions therein. 35 A format string directive here consists of 36 - an opening brace '{', 37 - an identifier [_A-Za-z][_0-9A-Za-z]*, 38 - a closing brace '}'. 39 */ 40 41struct named_arg 42{ 43 char *name; 44}; 45 46struct spec 47{ 48 unsigned int directives; 49 unsigned int named_arg_count; 50 unsigned int allocated; 51 struct named_arg *named; 52}; 53 54 55static int 56named_arg_compare (const void *p1, const void *p2) 57{ 58 return strcmp (((const struct named_arg *) p1)->name, 59 ((const struct named_arg *) p2)->name); 60} 61 62static void * 63format_parse (const char *format, bool translated, char **invalid_reason) 64{ 65 struct spec spec; 66 struct spec *result; 67 68 spec.directives = 0; 69 spec.named_arg_count = 0; 70 spec.allocated = 0; 71 spec.named = NULL; 72 73 for (; *format != '\0';) 74 if (*format++ == '{') 75 { 76 const char *f = format; 77 char c; 78 79 c = *f; 80 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') 81 { 82 do 83 c = *++f; 84 while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' 85 || (c >= '0' && c <= '9')); 86 if (c == '}') 87 { 88 /* A directive. */ 89 char *name; 90 const char *name_start = format; 91 const char *name_end = f; 92 size_t n = name_end - name_start; 93 94 name = (char *) xmalloc (n + 1); 95 memcpy (name, name_start, n); 96 name[n] = '\0'; 97 98 spec.directives++; 99 100 if (spec.allocated == spec.named_arg_count) 101 { 102 spec.allocated = 2 * spec.allocated + 1; 103 spec.named = (struct named_arg *) xrealloc (spec.named, spec.allocated * sizeof (struct named_arg)); 104 } 105 spec.named[spec.named_arg_count].name = name; 106 spec.named_arg_count++; 107 108 format = ++f; 109 } 110 } 111 } 112 113 /* Sort the named argument array, and eliminate duplicates. */ 114 if (spec.named_arg_count > 1) 115 { 116 unsigned int i, j; 117 118 qsort (spec.named, spec.named_arg_count, sizeof (struct named_arg), 119 named_arg_compare); 120 121 /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */ 122 for (i = j = 0; i < spec.named_arg_count; i++) 123 if (j > 0 && strcmp (spec.named[i].name, spec.named[j-1].name) == 0) 124 free (spec.named[i].name); 125 else 126 { 127 if (j < i) 128 spec.named[j].name = spec.named[i].name; 129 j++; 130 } 131 spec.named_arg_count = j; 132 } 133 134 result = (struct spec *) xmalloc (sizeof (struct spec)); 135 *result = spec; 136 return result; 137} 138 139static void 140format_free (void *descr) 141{ 142 struct spec *spec = (struct spec *) descr; 143 144 if (spec->named != NULL) 145 { 146 unsigned int i; 147 for (i = 0; i < spec->named_arg_count; i++) 148 free (spec->named[i].name); 149 free (spec->named); 150 } 151 free (spec); 152} 153 154static int 155format_get_number_of_directives (void *descr) 156{ 157 struct spec *spec = (struct spec *) descr; 158 159 return spec->directives; 160} 161 162static bool 163format_check (void *msgid_descr, void *msgstr_descr, bool equality, 164 formatstring_error_logger_t error_logger, 165 const char *pretty_msgstr) 166{ 167 struct spec *spec1 = (struct spec *) msgid_descr; 168 struct spec *spec2 = (struct spec *) msgstr_descr; 169 bool err = false; 170 171 if (spec1->named_arg_count + spec2->named_arg_count > 0) 172 { 173 unsigned int i, j; 174 unsigned int n1 = spec1->named_arg_count; 175 unsigned int n2 = spec2->named_arg_count; 176 177 /* Check the argument names in spec1 are contained in those of spec2. 178 Additional arguments in spec2 are allowed; they expand to themselves 179 (including the surrounding braces) at runtime. 180 Both arrays are sorted. We search for the differences. */ 181 for (i = 0, j = 0; i < n1 || j < n2; ) 182 { 183 int cmp = (i >= n1 ? 1 : 184 j >= n2 ? -1 : 185 strcmp (spec1->named[i].name, spec2->named[j].name)); 186 187 if (cmp > 0) 188 j++; 189 else if (cmp < 0) 190 { 191 if (equality) 192 { 193 if (error_logger) 194 error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"), 195 spec1->named[i].name, pretty_msgstr); 196 err = true; 197 break; 198 } 199 else 200 i++; 201 } 202 else 203 j++, i++; 204 } 205 } 206 207 return err; 208} 209 210 211struct formatstring_parser formatstring_perl_brace = 212{ 213 format_parse, 214 format_free, 215 format_get_number_of_directives, 216 format_check 217}; 218 219 220#ifdef TEST 221 222/* Test program: Print the argument list specification returned by 223 format_parse for strings read from standard input. */ 224 225#include <stdio.h> 226#include "getline.h" 227 228static void 229format_print (void *descr) 230{ 231 struct spec *spec = (struct spec *) descr; 232 unsigned int i; 233 234 if (spec == NULL) 235 { 236 printf ("INVALID"); 237 return; 238 } 239 240 printf ("{"); 241 for (i = 0; i < spec->named_arg_count; i++) 242 { 243 if (i > 0) 244 printf (", "); 245 printf ("'%s'", spec->named[i].name); 246 } 247 printf ("}"); 248} 249 250int 251main () 252{ 253 for (;;) 254 { 255 char *line = NULL; 256 size_t line_size = 0; 257 int line_len; 258 char *invalid_reason; 259 void *descr; 260 261 line_len = getline (&line, &line_size, stdin); 262 if (line_len < 0) 263 break; 264 if (line_len > 0 && line[line_len - 1] == '\n') 265 line[--line_len] = '\0'; 266 267 invalid_reason = NULL; 268 descr = format_parse (line, false, &invalid_reason); 269 270 format_print (descr); 271 printf ("\n"); 272 if (descr == NULL) 273 printf ("%s\n", invalid_reason); 274 275 free (invalid_reason); 276 free (line); 277 } 278 279 return 0; 280} 281 282/* 283 * For Emacs M-x compile 284 * Local Variables: 285 * compile-command: "/bin/sh ../libtool --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../lib -I../intl -DHAVE_CONFIG_H -DTEST format-perl-brace.c ../lib/libgettextlib.la" 286 * End: 287 */ 288 289#endif /* TEST */ 290