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