1 /* Perl brace format strings.
2 Copyright (C) 2004, 2006-2007, 2009, 2019-2020 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 <https://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 struct named_arg *named;
50 };
51
52
53 static int
named_arg_compare(const void * p1,const void * p2)54 named_arg_compare (const void *p1, const void *p2)
55 {
56 return strcmp (((const struct named_arg *) p1)->name,
57 ((const struct named_arg *) p2)->name);
58 }
59
60 static void *
format_parse(const char * format,bool translated,char * fdi,char ** invalid_reason)61 format_parse (const char *format, bool translated, char *fdi,
62 char **invalid_reason)
63 {
64 const char *const format_start = format;
65 struct spec spec;
66 unsigned int named_allocated;
67 struct spec *result;
68
69 spec.directives = 0;
70 spec.named_arg_count = 0;
71 spec.named = NULL;
72 named_allocated = 0;
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 (named_allocated == spec.named_arg_count)
104 {
105 named_allocated = 2 * named_allocated + 1;
106 spec.named = (struct named_arg *) xrealloc (spec.named, named_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
format_free(void * descr)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
format_get_number_of_directives(void * descr)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
format_check(void * msgid_descr,void * msgstr_descr,bool equality,formatstring_error_logger_t error_logger,const char * pretty_msgid,const char * pretty_msgstr)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
format_print(void * descr)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
main()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../../gettext-runtime/intl -DHAVE_CONFIG_H -DTEST format-perl-brace.c ../gnulib-lib/libgettextlib.la"
291 * End:
292 */
293
294 #endif /* TEST */
295