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