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