(root)/
diffutils-3.10/
src/
ifdef.c
       1  /* #ifdef-format output routines for GNU DIFF.
       2  
       3     Copyright (C) 1989, 1991-1994, 2001-2002, 2004, 2006, 2009-2013, 2015-2023
       4     Free 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 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 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 <xmalloca.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                intmax_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] = strtoimax (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] < 0)
     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" and pI is "l", use the printf
     361                 format spec "%3lx".  Here the spec prefix is "%3".  */
     362              size_t spec_prefix_len = f - spec - 2;
     363              size_t pI_len = sizeof pI - 1;
     364              char *format = xmalloca (spec_prefix_len + pI_len + 2);
     365              char *p = mempcpy (format, spec, spec_prefix_len);
     366              p = stpcpy (p, pI);
     367              *p++ = c;
     368              *p = '\0';
     369              fprintf (out, format, value);
     370              freea (format);
     371            }
     372        }
     373        break;
     374  
     375      default:
     376        return 0;
     377      }
     378  
     379    return f;
     380  }
     381  
     382  /* Scan the character literal represented in the string LIT; LIT points just
     383     after the initial apostrophe.  Put the literal's value into *VALPTR.
     384     Yield the address of the first character after the closing apostrophe,
     385     or a null pointer if the literal is ill-formed.  */
     386  static char const *
     387  scan_char_literal (char const *lit, char *valptr)
     388  {
     389    register char const *p = lit;
     390    char value;
     391    ptrdiff_t digits;
     392    char c = *p++;
     393  
     394    switch (c)
     395      {
     396        case 0:
     397        case '\'':
     398          return nullptr;
     399  
     400        case '\\':
     401          value = 0;
     402          while ((c = *p++) != '\'')
     403            {
     404              unsigned int digit = c - '0';
     405              if (8 <= digit)
     406                return nullptr;
     407              value = 8 * value + digit;
     408            }
     409          digits = p - lit - 2;
     410          if (! (1 <= digits && digits <= 3))
     411            return nullptr;
     412          break;
     413  
     414        default:
     415          value = c;
     416          if (*p++ != '\'')
     417            return nullptr;
     418          break;
     419      }
     420  
     421    *valptr = value;
     422    return p;
     423  }