1/* #ifdef-format output routines for GNU DIFF.
2
3   Copyright (C) 1989, 1991, 1992, 1993, 1994, 2001, 2002 Free
4   Software Foundation, Inc.
5
6   This file is part of GNU DIFF.
7
8   GNU DIFF is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY.  No author or distributor
10   accepts responsibility to anyone for the consequences of using it
11   or for whether it serves any particular purpose or works at all,
12   unless he says so in writing.  Refer to the GNU DIFF General Public
13   License for full details.
14
15   Everyone is granted permission to copy, modify and redistribute
16   GNU DIFF, but only under the conditions described in the
17   GNU DIFF General Public License.   A copy of this license is
18   supposed to have been given to you along with GNU DIFF so you
19   can know your rights and responsibilities.  It should be in a
20   file named COPYING.  Among other things, the copyright notice
21   and this notice must be preserved on all copies.  */
22
23#include "diff.h"
24
25#include <xalloc.h>
26
27struct group
28{
29  struct file_data const *file;
30  lin from, upto; /* start and limit lines for this group of lines */
31};
32
33static char const *format_group (FILE *, char const *, char,
34				 struct group const *);
35static char const *do_printf_spec (FILE *, char const *,
36				   struct file_data const *, lin,
37				   struct group const *);
38static char const *scan_char_literal (char const *, char *);
39static lin groups_letter_value (struct group const *, char);
40static void format_ifdef (char const *, lin, lin, lin, lin);
41static void print_ifdef_hunk (struct change *);
42static void print_ifdef_lines (FILE *, char const *, struct group const *);
43
44static lin next_line;
45
46/* Print the edit-script SCRIPT as a merged #ifdef file.  */
47
48void
49print_ifdef_script (struct change *script)
50{
51  next_line = - files[0].prefix_lines;
52  print_script (script, find_change, print_ifdef_hunk);
53  if (next_line < files[0].valid_lines)
54    {
55      begin_output ();
56      format_ifdef (group_format[UNCHANGED], next_line, files[0].valid_lines,
57		    next_line - files[0].valid_lines + files[1].valid_lines,
58		    files[1].valid_lines);
59    }
60}
61
62/* Print a hunk of an ifdef diff.
63   This is a contiguous portion of a complete edit script,
64   describing changes in consecutive lines.  */
65
66static void
67print_ifdef_hunk (struct change *hunk)
68{
69  lin first0, last0, first1, last1;
70
71  /* Determine range of line numbers involved in each file.  */
72  enum changes changes = analyze_hunk (hunk, &first0, &last0, &first1, &last1);
73  if (!changes)
74    return;
75
76  begin_output ();
77
78  /* Print lines up to this change.  */
79  if (next_line < first0)
80    format_ifdef (group_format[UNCHANGED], next_line, first0,
81		  next_line - first0 + first1, first1);
82
83  /* Print this change.  */
84  next_line = last0 + 1;
85  format_ifdef (group_format[changes], first0, next_line, first1, last1 + 1);
86}
87
88/* Print a set of lines according to FORMAT.
89   Lines BEG0 up to END0 are from the first file;
90   lines BEG1 up to END1 are from the second file.  */
91
92static void
93format_ifdef (char const *format, lin beg0, lin end0, lin beg1, lin end1)
94{
95  struct group groups[2];
96
97  groups[0].file = &files[0];
98  groups[0].from = beg0;
99  groups[0].upto = end0;
100  groups[1].file = &files[1];
101  groups[1].from = beg1;
102  groups[1].upto = end1;
103  format_group (outfile, format, 0, groups);
104}
105
106/* Print to file OUT a set of lines according to FORMAT.
107   The format ends at the first free instance of ENDCHAR.
108   Yield the address of the terminating character.
109   GROUPS specifies which lines to print.
110   If OUT is zero, do not actually print anything; just scan the format.  */
111
112static char const *
113format_group (register FILE *out, char const *format, char endchar,
114	      struct group const *groups)
115{
116  register char c;
117  register char const *f = format;
118
119  while ((c = *f) != endchar && c != 0)
120    {
121      char const *f1 = ++f;
122      if (c == '%')
123	switch ((c = *f++))
124	  {
125	  case '%':
126	    break;
127
128	  case '(':
129	    /* Print if-then-else format e.g. `%(n=1?thenpart:elsepart)'.  */
130	    {
131	      int i;
132	      uintmax_t value[2];
133	      FILE *thenout, *elseout;
134
135	      for (i = 0; i < 2; i++)
136		{
137		  if (ISDIGIT (*f))
138		    {
139		      char *fend;
140		      errno = 0;
141		      value[i] = strtoumax (f, &fend, 10);
142		      if (errno)
143			goto bad_format;
144		      f = fend;
145		    }
146		  else
147		    {
148		      value[i] = groups_letter_value (groups, *f);
149		      if (value[i] == -1)
150			goto bad_format;
151		      f++;
152		    }
153		  if (*f++ != "=?"[i])
154		    goto bad_format;
155		}
156	      if (value[0] == value[1])
157		thenout = out, elseout = 0;
158	      else
159		thenout = 0, elseout = out;
160	      f = format_group (thenout, f, ':', groups);
161	      if (*f)
162		{
163		  f = format_group (elseout, f + 1, ')', groups);
164		  if (*f)
165		    f++;
166		}
167	    }
168	    continue;
169
170	  case '<':
171	    /* Print lines deleted from first file.  */
172	    print_ifdef_lines (out, line_format[OLD], &groups[0]);
173	    continue;
174
175	  case '=':
176	    /* Print common lines.  */
177	    print_ifdef_lines (out, line_format[UNCHANGED], &groups[0]);
178	    continue;
179
180	  case '>':
181	    /* Print lines inserted from second file.  */
182	    print_ifdef_lines (out, line_format[NEW], &groups[1]);
183	    continue;
184
185	  default:
186	    f = do_printf_spec (out, f - 2, 0, 0, groups);
187	    if (f)
188	      continue;
189	    /* Fall through. */
190	  bad_format:
191	    c = '%';
192	    f = f1;
193	    break;
194	  }
195
196      if (out)
197	putc (c, out);
198    }
199
200  return f;
201}
202
203/* For the line group pair G, return the number corresponding to LETTER.
204   Return -1 if LETTER is not a group format letter.  */
205static lin
206groups_letter_value (struct group const *g, char letter)
207{
208  switch (letter)
209    {
210    case 'E': letter = 'e'; g++; break;
211    case 'F': letter = 'f'; g++; break;
212    case 'L': letter = 'l'; g++; break;
213    case 'M': letter = 'm'; g++; break;
214    case 'N': letter = 'n'; g++; break;
215    }
216
217  switch (letter)
218    {
219      case 'e': return translate_line_number (g->file, g->from) - 1;
220      case 'f': return translate_line_number (g->file, g->from);
221      case 'l': return translate_line_number (g->file, g->upto) - 1;
222      case 'm': return translate_line_number (g->file, g->upto);
223      case 'n': return g->upto - g->from;
224      default: return -1;
225    }
226}
227
228/* Print to file OUT, using FORMAT to print the line group GROUP.
229   But do nothing if OUT is zero.  */
230static void
231print_ifdef_lines (register FILE *out, char const *format,
232		   struct group const *group)
233{
234  struct file_data const *file = group->file;
235  char const * const *linbuf = file->linbuf;
236  lin from = group->from, upto = group->upto;
237
238  if (!out)
239    return;
240
241  /* If possible, use a single fwrite; it's faster.  */
242  if (!expand_tabs && format[0] == '%')
243    {
244      if (format[1] == 'l' && format[2] == '\n' && !format[3] && from < upto)
245	{
246	  fwrite (linbuf[from], sizeof (char),
247		  linbuf[upto] + (linbuf[upto][-1] != '\n') -  linbuf[from],
248		  out);
249	  return;
250	}
251      if (format[1] == 'L' && !format[2])
252	{
253	  fwrite (linbuf[from], sizeof (char),
254		  linbuf[upto] -  linbuf[from], out);
255	  return;
256	}
257    }
258
259  for (;  from < upto;  from++)
260    {
261      register char c;
262      register char const *f = format;
263
264      while ((c = *f++) != 0)
265	{
266	  char const *f1 = f;
267	  if (c == '%')
268	    switch ((c = *f++))
269	      {
270	      case '%':
271		break;
272
273	      case 'l':
274		output_1_line (linbuf[from],
275			       (linbuf[from + 1]
276				- (linbuf[from + 1][-1] == '\n')),
277			       0, 0);
278		continue;
279
280	      case 'L':
281		output_1_line (linbuf[from], linbuf[from + 1], 0, 0);
282		continue;
283
284	      default:
285		f = do_printf_spec (out, f - 2, file, from, 0);
286		if (f)
287		  continue;
288		c = '%';
289		f = f1;
290		break;
291	      }
292
293	  putc (c, out);
294	}
295    }
296}
297
298static char const *
299do_printf_spec (FILE *out, char const *spec,
300		struct file_data const *file, lin n,
301		struct group const *groups)
302{
303  char const *f = spec;
304  char c;
305  char c1;
306
307  /* Scan printf-style SPEC of the form %[-'0]*[0-9]*(.[0-9]*)?[cdoxX].  */
308  /* assert (*f == '%'); */
309  f++;
310  while ((c = *f++) == '-' || c == '\'' || c == '0')
311    continue;
312  while (ISDIGIT (c))
313    c = *f++;
314  if (c == '.')
315    while (ISDIGIT (c = *f++))
316      continue;
317  c1 = *f++;
318
319  switch (c)
320    {
321    case 'c':
322      if (c1 != '\'')
323	return 0;
324      else
325	{
326	  char value;
327	  f = scan_char_literal (f, &value);
328	  if (!f)
329	    return 0;
330	  if (out)
331	    putc (value, out);
332	}
333      break;
334
335    case 'd': case 'o': case 'x': case 'X':
336      {
337	lin value;
338
339	if (file)
340	  {
341	    if (c1 != 'n')
342	      return 0;
343	    value = translate_line_number (file, n);
344	  }
345	else
346	  {
347	    value = groups_letter_value (groups, c1);
348	    if (value < 0)
349	      return 0;
350	  }
351
352	if (out)
353	  {
354	    /* For example, if the spec is "%3xn", use the printf
355	       format spec "%3lx".  Here the spec prefix is "%3".  */
356	    long long_value = value;
357	    size_t spec_prefix_len = f - spec - 2;
358#if HAVE_C_VARARRAYS
359	    char format[spec_prefix_len + 3];
360#else
361	    char *format = xmalloc (spec_prefix_len + 3);
362#endif
363	    char *p = format + spec_prefix_len;
364	    memcpy (format, spec, spec_prefix_len);
365	    *p++ = 'l';
366	    *p++ = c;
367	    *p = '\0';
368	    fprintf (out, format, long_value);
369#if ! HAVE_C_VARARRAYS
370	    free (format);
371#endif
372	  }
373      }
374      break;
375
376    default:
377      return 0;
378    }
379
380  return f;
381}
382
383/* Scan the character literal represented in the string LIT; LIT points just
384   after the initial apostrophe.  Put the literal's value into *VALPTR.
385   Yield the address of the first character after the closing apostrophe,
386   or zero if the literal is ill-formed.  */
387static char const *
388scan_char_literal (char const *lit, char *valptr)
389{
390  register char const *p = lit;
391  char value;
392  ptrdiff_t digits;
393  char c = *p++;
394
395  switch (c)
396    {
397      case 0:
398      case '\'':
399	return 0;
400
401      case '\\':
402	value = 0;
403	while ((c = *p++) != '\'')
404	  {
405	    unsigned int digit = c - '0';
406	    if (8 <= digit)
407	      return 0;
408	    value = 8 * value + digit;
409	  }
410	digits = p - lit - 2;
411	if (! (1 <= digits && digits <= 3))
412	  return 0;
413	break;
414
415      default:
416	value = c;
417	if (*p++ != '\'')
418	  return 0;
419	break;
420    }
421
422  *valptr = value;
423  return p;
424}
425