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