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