1/*	$NetBSD: context.c,v 1.2 2016/01/13 03:39:28 christos Exp $	*/
2
3/* Context-format output routines for GNU DIFF.
4
5   Copyright (C) 1988, 1989, 1991, 1992, 1993, 1994, 1995, 1998, 2001,
6   2002 Free Software Foundation, Inc.
7
8   This file is part of GNU DIFF.
9
10   GNU DIFF is free software; you can redistribute it and/or modify
11   it under the terms of the GNU General Public License as published by
12   the Free Software Foundation; either version 2, or (at your option)
13   any later version.
14
15   GNU DIFF is distributed in the hope that it will be useful,
16   but WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   GNU General Public License for more details.
19
20   You should have received a copy of the GNU General Public License
21   along with this program; see the file COPYING.
22   If not, write to the Free Software Foundation,
23   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
24
25#include "diff.h"
26#include <inttostr.h>
27#include <regex.h>
28
29#ifdef ST_MTIM_NSEC
30# define TIMESPEC_NS(timespec) ((timespec).ST_MTIM_NSEC)
31#else
32# define TIMESPEC_NS(timespec) 0
33#endif
34
35size_t nstrftime (char *, size_t, char const *, struct tm const *, int, int);
36
37static char const *find_function (char const * const *, lin);
38static struct change *find_hunk (struct change *);
39static void mark_ignorable (struct change *);
40static void pr_context_hunk (struct change *);
41static void pr_unidiff_hunk (struct change *);
42
43/* Last place find_function started searching from.  */
44static lin find_function_last_search;
45
46/* The value find_function returned when it started searching there.  */
47static lin find_function_last_match;
48
49/* Print a label for a context diff, with a file name and date or a label.  */
50
51static void
52print_context_label (char const *mark,
53		     struct file_data *inf,
54		     char const *label)
55{
56  if (label)
57    fprintf (outfile, "%s %s\n", mark, label);
58  else
59    {
60      char buf[MAX (INT_STRLEN_BOUND (int) + 32,
61		    INT_STRLEN_BOUND (time_t) + 11)];
62      struct tm const *tm = localtime (&inf->stat.st_mtime);
63      int nsec = TIMESPEC_NS (inf->stat.st_mtim);
64      if (! (tm && nstrftime (buf, sizeof buf, time_format, tm, 0, nsec)))
65	{
66	  long long sec = inf->stat.st_mtime;
67	  verify (info_preserved, sizeof inf->stat.st_mtime <= sizeof sec);
68	  sprintf (buf, "%lld.%.9d", sec, nsec);
69	}
70      fprintf (outfile, "%s %s\t%s\n", mark, inf->name, buf);
71    }
72}
73
74/* Print a header for a context diff, with the file names and dates.  */
75
76void
77print_context_header (struct file_data inf[], bool unidiff)
78{
79  if (unidiff)
80    {
81      print_context_label ("---", &inf[0], file_label[0]);
82      print_context_label ("+++", &inf[1], file_label[1]);
83    }
84  else
85    {
86      print_context_label ("***", &inf[0], file_label[0]);
87      print_context_label ("---", &inf[1], file_label[1]);
88    }
89}
90
91/* Print an edit script in context format.  */
92
93void
94print_context_script (struct change *script, bool unidiff)
95{
96  if (ignore_blank_lines || ignore_regexp.fastmap)
97    mark_ignorable (script);
98  else
99    {
100      struct change *e;
101      for (e = script; e; e = e->link)
102	e->ignore = 0;
103    }
104
105  find_function_last_search = - files[0].prefix_lines;
106  find_function_last_match = LIN_MAX;
107
108  if (unidiff)
109    print_script (script, find_hunk, pr_unidiff_hunk);
110  else
111    print_script (script, find_hunk, pr_context_hunk);
112}
113
114/* Print a pair of line numbers with a comma, translated for file FILE.
115   If the second number is not greater, use the first in place of it.
116
117   Args A and B are internal line numbers.
118   We print the translated (real) line numbers.  */
119
120static void
121print_context_number_range (struct file_data const *file, lin a, lin b)
122{
123  long trans_a, trans_b;
124  translate_range (file, a, b, &trans_a, &trans_b);
125
126  /* We can have B <= A in the case of a range of no lines.
127     In this case, we should print the line number before the range,
128     which is B.
129
130     POSIX 1003.1-2001 requires two line numbers separated by a comma
131     even if the line numbers are the same.  However, this does not
132     match existing practice and is surely an error in the
133     specification.  */
134
135  if (trans_b <= trans_a)
136    fprintf (outfile, "%ld", trans_b);
137  else
138    fprintf (outfile, "%ld,%ld", trans_a, trans_b);
139}
140
141/* Print FUNCTION in a context header.  */
142static void
143print_context_function (FILE *out, char const *function)
144{
145  int i;
146  putc (' ', out);
147  for (i = 0; i < 40 && function[i] != '\n'; i++)
148    continue;
149  fwrite (function, 1, i, out);
150}
151
152/* Print a portion of an edit script in context format.
153   HUNK is the beginning of the portion to be printed.
154   The end is marked by a `link' that has been nulled out.
155
156   Prints out lines from both files, and precedes each
157   line with the appropriate flag-character.  */
158
159static void
160pr_context_hunk (struct change *hunk)
161{
162  lin first0, last0, first1, last1, i;
163  char const *prefix;
164  char const *function;
165  FILE *out;
166
167  /* Determine range of line numbers involved in each file.  */
168
169  enum changes changes = analyze_hunk (hunk, &first0, &last0, &first1, &last1);
170  if (! changes)
171    return;
172
173  /* Include a context's width before and after.  */
174
175  i = - files[0].prefix_lines;
176  first0 = MAX (first0 - context, i);
177  first1 = MAX (first1 - context, i);
178  if (last0 < files[0].valid_lines - context)
179    last0 += context;
180  else
181    last0 = files[0].valid_lines - 1;
182  if (last1 < files[1].valid_lines - context)
183    last1 += context;
184  else
185    last1 = files[1].valid_lines - 1;
186
187  /* If desired, find the preceding function definition line in file 0.  */
188  function = 0;
189  if (function_regexp.fastmap)
190    function = find_function (files[0].linbuf, first0);
191
192  begin_output ();
193  out = outfile;
194
195  fprintf (out, "***************");
196
197  if (function)
198    print_context_function (out, function);
199
200  fprintf (out, "\n*** ");
201  print_context_number_range (&files[0], first0, last0);
202  fprintf (out, " ****\n");
203
204  if (changes & OLD)
205    {
206      struct change *next = hunk;
207
208      for (i = first0; i <= last0; i++)
209	{
210	  /* Skip past changes that apply (in file 0)
211	     only to lines before line I.  */
212
213	  while (next && next->line0 + next->deleted <= i)
214	    next = next->link;
215
216	  /* Compute the marking for line I.  */
217
218	  prefix = " ";
219	  if (next && next->line0 <= i)
220	    /* The change NEXT covers this line.
221	       If lines were inserted here in file 1, this is "changed".
222	       Otherwise it is "deleted".  */
223	    prefix = (next->inserted > 0 ? "!" : "-");
224
225	  print_1_line (prefix, &files[0].linbuf[i]);
226	}
227    }
228
229  fprintf (out, "--- ");
230  print_context_number_range (&files[1], first1, last1);
231  fprintf (out, " ----\n");
232
233  if (changes & NEW)
234    {
235      struct change *next = hunk;
236
237      for (i = first1; i <= last1; i++)
238	{
239	  /* Skip past changes that apply (in file 1)
240	     only to lines before line I.  */
241
242	  while (next && next->line1 + next->inserted <= i)
243	    next = next->link;
244
245	  /* Compute the marking for line I.  */
246
247	  prefix = " ";
248	  if (next && next->line1 <= i)
249	    /* The change NEXT covers this line.
250	       If lines were deleted here in file 0, this is "changed".
251	       Otherwise it is "inserted".  */
252	    prefix = (next->deleted > 0 ? "!" : "+");
253
254	  print_1_line (prefix, &files[1].linbuf[i]);
255	}
256    }
257}
258
259/* Print a pair of line numbers with a comma, translated for file FILE.
260   If the second number is smaller, use the first in place of it.
261   If the numbers are equal, print just one number.
262
263   Args A and B are internal line numbers.
264   We print the translated (real) line numbers.  */
265
266static void
267print_unidiff_number_range (struct file_data const *file, lin a, lin b)
268{
269  long trans_a, trans_b;
270  translate_range (file, a, b, &trans_a, &trans_b);
271
272  /* We can have B < A in the case of a range of no lines.
273     In this case, we should print the line number before the range,
274     which is B.  */
275  if (trans_b <= trans_a)
276    fprintf (outfile, trans_b < trans_a ? "%ld,0" : "%ld", trans_b);
277  else
278    fprintf (outfile, "%ld,%ld", trans_a, trans_b - trans_a + 1);
279}
280
281/* Print a portion of an edit script in unidiff format.
282   HUNK is the beginning of the portion to be printed.
283   The end is marked by a `link' that has been nulled out.
284
285   Prints out lines from both files, and precedes each
286   line with the appropriate flag-character.  */
287
288static void
289pr_unidiff_hunk (struct change *hunk)
290{
291  lin first0, last0, first1, last1;
292  lin i, j, k;
293  struct change *next;
294  char const *function;
295  FILE *out;
296
297  /* Determine range of line numbers involved in each file.  */
298
299  if (! analyze_hunk (hunk, &first0, &last0, &first1, &last1))
300    return;
301
302  /* Include a context's width before and after.  */
303
304  i = - files[0].prefix_lines;
305  first0 = MAX (first0 - context, i);
306  first1 = MAX (first1 - context, i);
307  if (last0 < files[0].valid_lines - context)
308    last0 += context;
309  else
310    last0 = files[0].valid_lines - 1;
311  if (last1 < files[1].valid_lines - context)
312    last1 += context;
313  else
314    last1 = files[1].valid_lines - 1;
315
316  /* If desired, find the preceding function definition line in file 0.  */
317  function = 0;
318  if (function_regexp.fastmap)
319    function = find_function (files[0].linbuf, first0);
320
321  begin_output ();
322  out = outfile;
323
324  fprintf (out, "@@ -");
325  print_unidiff_number_range (&files[0], first0, last0);
326  fprintf (out, " +");
327  print_unidiff_number_range (&files[1], first1, last1);
328  fprintf (out, " @@");
329
330  if (function)
331    print_context_function (out, function);
332
333  putc ('\n', out);
334
335  next = hunk;
336  i = first0;
337  j = first1;
338
339  while (i <= last0 || j <= last1)
340    {
341
342      /* If the line isn't a difference, output the context from file 0. */
343
344      if (!next || i < next->line0)
345	{
346	  putc (initial_tab ? '\t' : ' ', out);
347	  print_1_line (0, &files[0].linbuf[i++]);
348	  j++;
349	}
350      else
351	{
352	  /* For each difference, first output the deleted part. */
353
354	  k = next->deleted;
355	  while (k--)
356	    {
357	      putc ('-', out);
358	      if (initial_tab)
359		putc ('\t', out);
360	      print_1_line (0, &files[0].linbuf[i++]);
361	    }
362
363	  /* Then output the inserted part. */
364
365	  k = next->inserted;
366	  while (k--)
367	    {
368	      putc ('+', out);
369	      if (initial_tab)
370		putc ('\t', out);
371	      print_1_line (0, &files[1].linbuf[j++]);
372	    }
373
374	  /* We're done with this hunk, so on to the next! */
375
376	  next = next->link;
377	}
378    }
379}
380
381/* Scan a (forward-ordered) edit script for the first place that more than
382   2*CONTEXT unchanged lines appear, and return a pointer
383   to the `struct change' for the last change before those lines.  */
384
385static struct change *
386find_hunk (struct change *start)
387{
388  struct change *prev;
389  lin top0, top1;
390  lin thresh;
391
392  /* Threshold distance is 2 * CONTEXT + 1 between two non-ignorable
393     changes, but only CONTEXT if one is ignorable.  Watch out for
394     integer overflow, though.  */
395  lin non_ignorable_threshold =
396    (LIN_MAX - 1) / 2 < context ? LIN_MAX : 2 * context + 1;
397  lin ignorable_threshold = context;
398
399  do
400    {
401      /* Compute number of first line in each file beyond this changed.  */
402      top0 = start->line0 + start->deleted;
403      top1 = start->line1 + start->inserted;
404      prev = start;
405      start = start->link;
406      thresh = (prev->ignore || (start && start->ignore)
407		? ignorable_threshold
408		: non_ignorable_threshold);
409      /* It is not supposed to matter which file we check in the end-test.
410	 If it would matter, crash.  */
411      if (start && start->line0 - top0 != start->line1 - top1)
412	abort ();
413    } while (start
414	     /* Keep going if less than THRESH lines
415		elapse before the affected line.  */
416	     && start->line0 - top0 < thresh);
417
418  return prev;
419}
420
421/* Set the `ignore' flag properly in each change in SCRIPT.
422   It should be 1 if all the lines inserted or deleted in that change
423   are ignorable lines.  */
424
425static void
426mark_ignorable (struct change *script)
427{
428  while (script)
429    {
430      struct change *next = script->link;
431      lin first0, last0, first1, last1;
432
433      /* Turn this change into a hunk: detach it from the others.  */
434      script->link = 0;
435
436      /* Determine whether this change is ignorable.  */
437      script->ignore = ! analyze_hunk (script,
438				       &first0, &last0, &first1, &last1);
439
440      /* Reconnect the chain as before.  */
441      script->link = next;
442
443      /* Advance to the following change.  */
444      script = next;
445    }
446}
447
448/* Find the last function-header line in LINBUF prior to line number LINENUM.
449   This is a line containing a match for the regexp in `function_regexp'.
450   Return the address of the text, or 0 if no function-header is found.  */
451
452static char const *
453find_function (char const * const *linbuf, lin linenum)
454{
455  lin i = linenum;
456  lin last = find_function_last_search;
457  find_function_last_search = i;
458
459  while (last <= --i)
460    {
461      /* See if this line is what we want.  */
462      char const *line = linbuf[i];
463      size_t linelen = linbuf[i + 1] - line - 1;
464
465      /* FIXME: re_search's size args should be size_t, not int.  */
466      int len = MIN (linelen, INT_MAX);
467
468      if (0 <= re_search (&function_regexp, line, len, 0, len, 0))
469	{
470	  find_function_last_match = i;
471	  return line;
472	}
473    }
474  /* If we search back to where we started searching the previous time,
475     find the line we found last time.  */
476  if (find_function_last_match != LIN_MAX)
477    return linbuf[find_function_last_match];
478
479  return 0;
480}
481