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