xref: /plan9/sys/src/ape/cmd/diff/ifdef.c (revision 0b459c2cb92b7c9d88818e9a2f72e678e5bc4553)
1 /* #ifdef-format output routines for GNU DIFF.
2    Copyright (C) 1989, 1991, 1992, 1993, 1994 Free Software Foundation, Inc.
3 
4 This file is part of GNU DIFF.
5 
6 GNU DIFF is distributed in the hope that it will be useful,
7 but WITHOUT ANY WARRANTY.  No author or distributor
8 accepts responsibility to anyone for the consequences of using it
9 or for whether it serves any particular purpose or works at all,
10 unless he says so in writing.  Refer to the GNU DIFF General Public
11 License for full details.
12 
13 Everyone is granted permission to copy, modify and redistribute
14 GNU DIFF, but only under the conditions described in the
15 GNU DIFF General Public License.   A copy of this license is
16 supposed to have been given to you along with GNU DIFF so you
17 can know your rights and responsibilities.  It should be in a
18 file named COPYING.  Among other things, the copyright notice
19 and this notice must be preserved on all copies.  */
20 
21 
22 #include "diff.h"
23 
24 struct group
25 {
26   struct file_data const *file;
27   int from, upto; /* start and limit lines for this group of lines */
28 };
29 
30 static char *format_group PARAMS((FILE *, char *, int, struct group const *));
31 static char *scan_char_literal PARAMS((char *, int *));
32 static char *scan_printf_spec PARAMS((char *));
33 static int groups_letter_value PARAMS((struct group const *, int));
34 static void format_ifdef PARAMS((char *, int, int, int, int));
35 static void print_ifdef_hunk PARAMS((struct change *));
36 static void print_ifdef_lines PARAMS((FILE *, char *, struct group const *));
37 
38 static int next_line;
39 
40 /* Print the edit-script SCRIPT as a merged #ifdef file.  */
41 
42 void
print_ifdef_script(script)43 print_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 
61 static void
print_ifdef_hunk(hunk)62 print_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 
93 static void
format_ifdef(format,beg0,end0,beg1,end1)94 format_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 (outfile, format, '\0', groups);
107 }
108 
109 /* Print to file OUT 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 
115 static char *
format_group(out,format,endchar,groups)116 format_group (out, format, endchar, groups)
117      register FILE *out;
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 		FILE *thenout, *elseout;
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 		  thenout = out, elseout = 0;
163 		else
164 		  thenout = 0, elseout = out;
165 		f = format_group (thenout, f, ':', groups);
166 		if (*f)
167 		  {
168 		    f = format_group (elseout, 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 (out, line_format[OLD], &groups[0]);
178 	      continue;
179 
180 	    case '=':
181 	      /* Print common lines.  */
182 	      print_ifdef_lines (out, line_format[UNCHANGED], &groups[0]);
183 	      continue;
184 
185 	    case '>':
186 	      /* Print lines inserted from second file.  */
187 	      print_ifdef_lines (out, 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 (out)
215 		  {
216 		    /* Temporarily replace e.g. "%3dnx" with "%3d\0x".  */
217 		    *speclim = 0;
218 		    fprintf (out, 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 (out)
232 	putc (c, out);
233     }
234   return f;
235 }
236 
237 /* For the line group pair G, return the number corresponding to LETTER.
238    Return -1 if LETTER is not a group format letter.  */
239 static int
groups_letter_value(g,letter)240 groups_letter_value (g, letter)
241      struct group const *g;
242      int letter;
243 {
244   if (ISUPPER (letter))
245     {
246       g++;
247       letter = tolower (letter);
248     }
249   switch (letter)
250     {
251       case 'e': return translate_line_number (g->file, g->from) - 1;
252       case 'f': return translate_line_number (g->file, g->from);
253       case 'l': return translate_line_number (g->file, g->upto) - 1;
254       case 'm': return translate_line_number (g->file, g->upto);
255       case 'n': return g->upto - g->from;
256       default: return -1;
257     }
258 }
259 
260 /* Print to file OUT, using FORMAT to print the line group GROUP.
261    But do nothing if OUT is zero.  */
262 static void
print_ifdef_lines(out,format,group)263 print_ifdef_lines (out, format, group)
264      register FILE *out;
265      char *format;
266      struct group const *group;
267 {
268   struct file_data const *file = group->file;
269   char const * const *linbuf = file->linbuf;
270   int from = group->from, upto = group->upto;
271 
272   if (!out)
273     return;
274 
275   /* If possible, use a single fwrite; it's faster.  */
276   if (!tab_expand_flag && format[0] == '%')
277     {
278       if (format[1] == 'l' && format[2] == '\n' && !format[3])
279 	{
280 	  fwrite (linbuf[from], sizeof (char),
281 		  linbuf[upto] + (linbuf[upto][-1] != '\n') -  linbuf[from],
282 		  out);
283 	  return;
284 	}
285       if (format[1] == 'L' && !format[2])
286 	{
287 	  fwrite (linbuf[from], sizeof (char),
288 		  linbuf[upto] -  linbuf[from], out);
289 	  return;
290 	}
291     }
292 
293   for (;  from < upto;  from++)
294     {
295       register char c;
296       register char *f = format;
297 
298       while ((c = *f++) != 0)
299 	{
300 	  if (c == '%')
301 	    {
302 	      char *spec = f;
303 	      switch ((c = *f++))
304 		{
305 		case '%':
306 		  break;
307 
308 		case 'l':
309 		  output_1_line (linbuf[from],
310 				 linbuf[from + 1]
311 				   - (linbuf[from + 1][-1] == '\n'), 0, 0);
312 		  continue;
313 
314 		case 'L':
315 		  output_1_line (linbuf[from], linbuf[from + 1], 0, 0);
316 		  continue;
317 
318 		default:
319 		  {
320 		    int value;
321 		    char *speclim;
322 
323 		    f = scan_printf_spec (spec);
324 		    if (!f)
325 		      goto bad_format;
326 		    speclim = f;
327 		    c = *f++;
328 		    switch (c)
329 		      {
330 			case '\'':
331 			  f = scan_char_literal (f, &value);
332 			  if (!f)
333 			    goto bad_format;
334 			  break;
335 
336 			case 'n':
337 			  value = translate_line_number (file, from);
338 			  break;
339 
340 			default:
341 			  goto bad_format;
342 		      }
343 		    /* Temporarily replace e.g. "%3dnx" with "%3d\0x".  */
344 		    *speclim = 0;
345 		    fprintf (out, spec - 1, value);
346 		    /* Undo the temporary replacement.  */
347 		    *speclim = c;
348 		  }
349 		  continue;
350 
351 		bad_format:
352 		  c = '%';
353 		  f = spec;
354 		  break;
355 		}
356 	    }
357 	  putc (c, out);
358 	}
359     }
360 }
361 
362 /* Scan the character literal represented in the string LIT; LIT points just
363    after the initial apostrophe.  Put the literal's value into *INTPTR.
364    Yield the address of the first character after the closing apostrophe,
365    or zero if the literal is ill-formed.  */
366 static char *
scan_char_literal(lit,intptr)367 scan_char_literal (lit, intptr)
368      char *lit;
369      int *intptr;
370 {
371   register char *p = lit;
372   int value, digits;
373   char c = *p++;
374 
375   switch (c)
376     {
377       case 0:
378       case '\'':
379 	return 0;
380 
381       case '\\':
382 	value = 0;
383 	while ((c = *p++) != '\'')
384 	  {
385 	    unsigned digit = c - '0';
386 	    if (8 <= digit)
387 	      return 0;
388 	    value = 8 * value + digit;
389 	  }
390 	digits = p - lit - 2;
391 	if (! (1 <= digits && digits <= 3))
392 	  return 0;
393 	break;
394 
395       default:
396 	value = c;
397 	if (*p++ != '\'')
398 	  return 0;
399 	break;
400     }
401   *intptr = value;
402   return p;
403 }
404 
405 /* Scan optional printf-style SPEC of the form `-*[0-9]*(.[0-9]*)?[cdoxX]'.
406    Return the address of the character following SPEC, or zero if failure.  */
407 static char *
scan_printf_spec(spec)408 scan_printf_spec (spec)
409      register char *spec;
410 {
411   register unsigned char c;
412 
413   while ((c = *spec++) == '-')
414     continue;
415   while (ISDIGIT (c))
416     c = *spec++;
417   if (c == '.')
418     while (ISDIGIT (c = *spec++))
419       continue;
420   switch (c)
421     {
422       case 'c': case 'd': case 'o': case 'x': case 'X':
423 	return spec;
424 
425       default:
426 	return 0;
427     }
428 }
429