xref: /freebsd/contrib/diff/src/ifdef.c (revision 2f02600abfddfc4e9f20dd384a2e729b451e16bd)
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 
27 struct group
28 {
29   struct file_data const *file;
30   lin from, upto; /* start and limit lines for this group of lines */
31 };
32 
33 static char const *format_group (FILE *, char const *, char,
34 				 struct group const *);
35 static char const *do_printf_spec (FILE *, char const *,
36 				   struct file_data const *, lin,
37 				   struct group const *);
38 static char const *scan_char_literal (char const *, char *);
39 static lin groups_letter_value (struct group const *, char);
40 static void format_ifdef (char const *, lin, lin, lin, lin);
41 static void print_ifdef_hunk (struct change *);
42 static void print_ifdef_lines (FILE *, char const *, struct group const *);
43 
44 static lin next_line0;
45 static lin next_line1;
46 
47 /* Print the edit-script SCRIPT as a merged #ifdef file.  */
48 
49 void
50 print_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 
68 static void
69 print_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 
98 static void
99 format_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 
118 static char const *
119 format_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.  */
211 static lin
212 groups_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.  */
236 static void
237 print_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 
304 static char const *
305 do_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.  */
393 static char const *
394 scan_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