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