(root)/
bison-3.8.2/
src/
location.c
       1  /* Locations for Bison
       2  
       3     Copyright (C) 2002, 2005-2015, 2018-2021 Free Software Foundation,
       4     Inc.
       5  
       6     This file is part of Bison, the GNU Compiler Compiler.
       7  
       8     This program is free software: you can redistribute it and/or modify
       9     it under the terms of the GNU General Public License as published by
      10     the Free Software Foundation, either version 3 of the License, or
      11     (at your option) any later version.
      12  
      13     This program is distributed in the hope that it will be useful,
      14     but WITHOUT ANY WARRANTY; without even the implied warranty of
      15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      16     GNU General Public License for more details.
      17  
      18     You should have received a copy of the GNU General Public License
      19     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      20  
      21  #include <config.h>
      22  #include "system.h"
      23  
      24  #include <mbfile.h>
      25  #include <mbswidth.h>
      26  #include <quotearg.h>
      27  #include <stdio.h>    /* fileno */
      28  #include <sys/ioctl.h>
      29  #include <sys/stat.h> /* fstat */
      30  #include <termios.h>
      31  
      32  #ifdef WINSIZE_IN_PTEM
      33  # include <sys/stream.h>
      34  # include <sys/ptem.h>
      35  #endif
      36  
      37  #include "complain.h"
      38  #include "getargs.h"
      39  #include "location.h"
      40  
      41  location const empty_loc = EMPTY_LOCATION_INIT;
      42  
      43  /* The terminal width.  Not less than 40.  */
      44  static int
      45  columns (void)
      46  {
      47    const char *cp = getenv ("COLUMNS");
      48    int res = 80;
      49    if (cp && *cp)
      50      {
      51        long l = strtol (cp, NULL, 10);
      52        res = 0 <= l && l <= INT_MAX ? l : INT_MAX;
      53      }
      54    else
      55      {
      56  #ifdef TIOCGWINSZ
      57        struct winsize ws;
      58        if (ioctl (STDERR_FILENO, TIOCGWINSZ, &ws) != -1
      59            && 0 < ws.ws_col && ws.ws_col == (size_t) ws.ws_col)
      60          res = ws.ws_col;
      61  #endif
      62      }
      63    return max_int (res, 40);
      64  }
      65  
      66  /* Available screen width.  */
      67  static int screen_width = 80;
      68  
      69  /* The ellipsis symbol to use for this locale, and the number of
      70     screen-columns it uses.  */
      71  static const char *ellipsis = "...";
      72  static int ellipsize = 3;
      73  
      74  /* If BUF is null, add BUFSIZE (which in this case must be less than
      75     INT_MAX) to COLUMN; otherwise, add mbsnwidth (BUF, BUFSIZE, 0) to
      76     COLUMN.  If an overflow occurs, return INT_MAX.  */
      77  
      78  static inline int
      79  add_column_width (int column, char const *buf, size_t bufsize)
      80  {
      81    int width
      82      = buf ? mbsnwidth (buf, bufsize, 0)
      83      : INT_MAX <= bufsize ? INT_MAX
      84      : bufsize;
      85    return column <= INT_MAX - width ? column + width : INT_MAX;
      86  }
      87  
      88  static void
      89  boundary_compute (boundary *cur, char const *token, size_t size)
      90  {
      91    int line = cur->line;
      92    int column = cur->column;
      93    int byte = cur->byte;
      94    char const *p0 = token;
      95    char const *p = token;
      96    char const *lim = token + size;
      97  
      98    for (p = token; p < lim; ++p)
      99      switch (*p)
     100        {
     101        case '\n':
     102          line += line < INT_MAX;
     103          column = 1;
     104          byte = 1;
     105          p0 = p + 1;
     106          break;
     107  
     108        case '\t':
     109          column = add_column_width (column, p0, p - p0);
     110          column = add_column_width (column, NULL, 8 - ((column - 1) & 7));
     111          p0 = p + 1;
     112          byte += byte < INT_MAX;
     113          break;
     114  
     115        default:
     116          byte += byte < INT_MAX;
     117          break;
     118        }
     119    column = add_column_width (column, p0, p - p0);
     120  
     121    cur->line = line;
     122    cur->column = column;
     123    cur->byte = byte;
     124  }
     125  
     126  
     127  /* Set *LOC and adjust scanner cursor to account for token TOKEN of
     128     size SIZE.  */
     129  
     130  void
     131  location_compute (location *loc, boundary *cur, char const *token, size_t size)
     132  {
     133    loc->start = *cur;
     134    boundary_compute (cur, token, size);
     135    loc->end = *cur;
     136  
     137    if (loc->end.line == INT_MAX && loc->start.line != INT_MAX)
     138      complain (loc, Wother, _("line number overflow"));
     139    if (loc->end.column == INT_MAX && loc->start.column != INT_MAX)
     140      complain (loc, Wother, _("column number overflow"));
     141    /* TRANSLATORS: we are counting bytes, and there are too many.  */
     142    if (loc->end.byte == INT_MAX && loc->start.byte != INT_MAX)
     143      complain (loc, Wother, _("byte number overflow"));
     144  }
     145  
     146  static int
     147  boundary_print (boundary const *b, FILE *out)
     148  {
     149    return fprintf (out, "%s:%d.%d@%d",
     150                    quotearg_n_style (3, escape_quoting_style, b->file),
     151                    b->line, b->column, b->byte);
     152  }
     153  
     154  int
     155  location_print (location loc, FILE *out)
     156  {
     157    int res = 0;
     158    if (location_empty (loc))
     159      res += fprintf (out, "(empty location)");
     160    else if (trace_flag & trace_locations)
     161      {
     162        res += boundary_print (&loc.start, out);
     163        res += fprintf (out, "-");
     164        res += boundary_print (&loc.end, out);
     165      }
     166    else
     167      {
     168        aver (loc.start.file);
     169        aver (loc.end.file);
     170        int end_col = 0 != loc.end.column ? loc.end.column - 1 : 0;
     171        res += fprintf (out, "%s",
     172                        quotearg_n_style (3, escape_quoting_style, loc.start.file));
     173        if (0 < loc.start.line)
     174          {
     175            res += fprintf (out, ":%d", loc.start.line);
     176            if (0 < loc.start.column)
     177              res += fprintf (out, ".%d", loc.start.column);
     178          }
     179        if (loc.start.file != loc.end.file)
     180          {
     181            res += fprintf (out, "-%s",
     182                            quotearg_n_style (3, escape_quoting_style,
     183                                              loc.end.file));
     184            if (0 < loc.end.line)
     185              {
     186                res += fprintf (out, ":%d", loc.end.line);
     187                if (0 <= end_col)
     188                  res += fprintf (out, ".%d", end_col);
     189              }
     190          }
     191        else if (0 < loc.end.line)
     192          {
     193            if (loc.start.line < loc.end.line)
     194              {
     195                res += fprintf (out, "-%d", loc.end.line);
     196                if (0 <= end_col)
     197                  res += fprintf (out, ".%d", end_col);
     198              }
     199            else if (0 <= end_col && loc.start.column < end_col)
     200              res += fprintf (out, "-%d", end_col);
     201          }
     202      }
     203  
     204    return res;
     205  }
     206  
     207  
     208  /* Persistent data used by location_caret to avoid reopening and rereading the
     209     same file all over for each error.  */
     210  static struct
     211  {
     212    /* Raw input file.  */
     213    FILE *file;
     214    /* Input file as a stream of multibyte characters.  */
     215    mb_file_t mbfile;
     216    /* The position within the last file we quoted.  If POS.FILE is non
     217       NULL, but FILE is NULL, it means this file is special and should
     218       not be quoted. */
     219    boundary pos;
     220    /* Offset in FILE of the current line (i.e., where line POS.LINE
     221       starts).  */
     222    size_t offset;
     223    /* Length of the current line.  */
     224    int line_len;
     225    /* Given the initial column to display, the offset (number of
     226       characters to skip at the beginning of the line). */
     227    int skip;
     228  
     229    /* Available width to quote the source file.  Eight chars are
     230       consumed by the left-margin (with line number).  */
     231    int width;
     232  } caret_info;
     233  
     234  void caret_init (void)
     235  {
     236    screen_width = columns ();
     237    /* TRANSLATORS: This is used when a line is too long, and is
     238       displayed truncated.  Use an ellipsis appropriate for your
     239       language, remembering that "…" (U+2026 HORIZONTAL ELLIPSIS)
     240       sometimes misdisplays and that "..." (three ASCII periods) is a
     241       safer choice in some locales.  */
     242    ellipsis = _("...");
     243    ellipsize = mbswidth (ellipsis, 0);
     244  }
     245  
     246  void
     247  caret_free (void)
     248  {
     249    if (caret_info.file)
     250      {
     251        fclose (caret_info.file);
     252        caret_info.file = NULL;
     253      }
     254  }
     255  
     256  /* Open FILE for quoting, if needed, and if possible.  Return whether
     257     the file can quoted. */
     258  static bool
     259  caret_set_file (const char *file)
     260  {
     261    /* If a different file than before, close and let the rest open
     262       the new one. */
     263    if (caret_info.pos.file && caret_info.pos.file != file)
     264      {
     265        caret_free ();
     266        caret_info.pos.file = NULL;
     267      }
     268    if (!caret_info.pos.file)
     269      {
     270        caret_info.pos.file = file;
     271        if ((caret_info.file = fopen (caret_info.pos.file, "r")))
     272          {
     273            /* If the file is not regular (imagine #line 1 "/dev/stdin"
     274               in the input file for instance), don't try to quote the
     275               file.  Keep caret_info.file set so that we don't try to
     276               open it again, but leave caret_info.file NULL so that we
     277               don't try to quote it. */
     278            struct stat buf;
     279            if (fstat (fileno (caret_info.file), &buf) == 0
     280                && buf.st_mode & S_IFREG)
     281              {
     282                caret_info.pos.line = 1;
     283                mbf_init (caret_info.mbfile, caret_info.file);
     284              }
     285            else
     286              caret_free ();
     287          }
     288      }
     289    return !!caret_info.file;
     290  }
     291  
     292  /* Getc, but smash \r\n as \n.  */
     293  static void
     294  caret_getc_internal (mbchar_t *res)
     295  {
     296    mbf_getc (*res, caret_info.mbfile);
     297    if (mb_iseq (*res, '\r'))
     298      {
     299        mbchar_t c;
     300        mbf_getc (c, caret_info.mbfile);
     301        if (mb_iseq (c, '\n'))
     302          mb_copy (res, &c);
     303        else
     304          mbf_ungetc (c, caret_info.mbfile);
     305      }
     306  }
     307  
     308  #define caret_getc(Var) caret_getc_internal(&Var)
     309  
     310  /* Move CARET_INFO (which has a valid FILE) to the line number LINE.
     311     Compute and cache that line's length in CARET_INFO.LINE_LEN.
     312     Return whether successful.  */
     313  static bool
     314  caret_set_line (int line)
     315  {
     316    /* If the line we want to quote is seekable (the same line as the previous
     317       location), just seek it. If it was a previous line, we lost track of it,
     318       so return to the start of file.  */
     319    if (line < caret_info.pos.line)
     320      {
     321        caret_info.pos.line = 1;
     322        caret_info.offset = 0;
     323      }
     324    if (fseek (caret_info.file, caret_info.offset, SEEK_SET))
     325      return false;
     326  
     327    /* If this is the same line as the previous one, we are done. */
     328    if (line < caret_info.pos.line)
     329      return true;
     330  
     331    /* Advance to the line's position, keeping track of the offset.  */
     332    while (caret_info.pos.line < line)
     333      {
     334        mbchar_t c;
     335        caret_getc (c);
     336        if (mb_iseof (c))
     337          /* Something is wrong, that line number does not exist.  */
     338          return false;
     339        caret_info.pos.line += mb_iseq (c, '\n');
     340      }
     341    caret_info.offset = ftell (caret_info.file);
     342    caret_info.pos.column = 1;
     343    /* Reset mbf's internal state.
     344       FIXME: should be done in mbfile.  */
     345    caret_info.mbfile.eof_seen = 0;
     346  
     347    /* Find the number of columns of this line.  */
     348    while (true)
     349      {
     350        mbchar_t c;
     351        caret_getc (c);
     352        if (mb_iseof (c) || mb_iseq (c, '\n'))
     353          break;
     354        boundary_compute (&caret_info.pos, mb_ptr (c), mb_len (c));
     355      }
     356    caret_info.line_len = caret_info.pos.column;
     357    /* Go back to the beginning of line.  */
     358    if (fseek (caret_info.file, caret_info.offset, SEEK_SET))
     359      return false;
     360    /* Reset mbf's internal state.
     361       FIXME: should be done in mbfile.  */
     362    caret_info.mbfile.eof_seen = 0;
     363    caret_info.pos.column = 1;
     364    return true;
     365  }
     366  
     367  /* Compute CARET_INFO.WIDTH and CARET_INFO.SKIP based on the fact that
     368     the first column to display in the current line is COL.  */
     369  static bool
     370  caret_set_column (int col)
     371  {
     372    /* Available width.  Eight chars are consumed by the left-margin
     373       (with line number).  */
     374    caret_info.width = screen_width - 8;
     375    caret_info.skip = 0;
     376    if (caret_info.width < caret_info.line_len)
     377      {
     378        /* We cannot quote the whole line.  Make sure we can see the
     379           beginning of the location.  */
     380        caret_info.skip = caret_info.width < col ? col - 10 : 0;
     381      }
     382    /* If we skip the initial part, we insert "..." before.  */
     383    if (caret_info.skip)
     384      caret_info.width -= ellipsize;
     385    /* If the end of line does not fit, we also need to truncate the
     386       end, and leave "..." there.  */
     387    if (caret_info.width < caret_info.line_len - caret_info.skip)
     388      caret_info.width -= ellipsize;
     389    return true;
     390  }
     391  
     392  void
     393  location_caret (location loc, const char *style, FILE *out)
     394  {
     395    if (!(feature_flag & feature_caret))
     396      return;
     397    if (!loc.start.line)
     398      return;
     399    if (!caret_set_file (loc.start.file))
     400      return;
     401    if (!caret_set_line (loc.start.line))
     402      return;
     403    if (!caret_set_column (loc.start.column))
     404      return;
     405  
     406    const int width = caret_info.width;
     407    const int skip = caret_info.skip;
     408  
     409    /* Read the actual line.  Don't update the offset, so that we keep a pointer
     410       to the start of the line.  */
     411    {
     412      mbchar_t c;
     413      caret_getc (c);
     414      if (!mb_iseof (c))
     415        {
     416          /* The last column to highlight.  Only the first line of
     417             multiline locations are quoted, in which case the ending
     418             column is the end of line.
     419  
     420             We used to work with byte offsets, and that was much
     421             easier.  However, we went back to using (visual) columns to
     422             support truncating of long lines.  */
     423          const int col_end
     424            = loc.start.line == loc.end.line
     425            ? loc.end.column
     426            : caret_info.line_len;
     427          /* Quote the file (at most the first line in the case of
     428             multiline locations).  */
     429          {
     430            fprintf (out, "%5d | %s", loc.start.line, skip ? ellipsis : "");
     431            /* Whether we opened the style.  If the line is not as
     432               expected (maybe the file was changed since the scanner
     433               ran), we might reach the end before we actually saw the
     434               opening column.  */
     435            enum { before, inside, after } state = before;
     436            while (!mb_iseof (c) && !mb_iseq (c, '\n'))
     437              {
     438                // We might have already opened (and even closed!) the
     439                // style and yet have the equality of the columns if we
     440                // just saw zero-width characters.
     441                if (state == before
     442                    && caret_info.pos.column == loc.start.column)
     443                  {
     444                    begin_use_class (style, out);
     445                    state = inside;
     446                  }
     447                if (skip < caret_info.pos.column)
     448                  mb_putc (c, out);
     449                boundary_compute (&caret_info.pos, mb_ptr (c), mb_len (c));
     450                caret_getc (c);
     451                if (state == inside
     452                    && (caret_info.pos.column == col_end
     453                        || width < caret_info.pos.column - skip))
     454                  {
     455                    end_use_class (style, out);
     456                    state = after;
     457                  }
     458                if (width < caret_info.pos.column - skip)
     459                  {
     460                    fputs (ellipsis, out);
     461                    break;
     462                  }
     463              }
     464            if (state == inside)
     465              {
     466                // The line is shorter than expected.
     467                end_use_class (style, out);
     468                state = after;
     469              }
     470            putc ('\n', out);
     471          }
     472  
     473          /* Print the carets with the same indentation as above.  */
     474          {
     475            fprintf (out, "      | %*s",
     476                     loc.start.column - 1 - skip + (skip ? ellipsize : 0), "");
     477            begin_use_class (style, out);
     478            putc ('^', out);
     479            /* Underlining a multiline location ends with the first
     480               line.  */
     481            for (int i = loc.start.column - 1 - skip + 1,
     482                   i_end = min_int (col_end - 1 - skip, width);
     483                 i < i_end; ++i)
     484              putc ('~', out);
     485            end_use_class (style, out);
     486            putc ('\n', out);
     487          }
     488        }
     489    }
     490  }
     491  
     492  void
     493  location_caret_suggestion (location loc, const char *s, FILE *out)
     494  {
     495    if (!(feature_flag & feature_caret))
     496      return;
     497    const char *style = "fixit-insert";
     498    fprintf (out, "      | %*s",
     499             loc.start.column - 1 - caret_info.skip
     500             + (caret_info.skip ? ellipsize : 0),
     501             "");
     502    begin_use_class (style, out);
     503    fputs (s, out);
     504    end_use_class (style, out);
     505    putc ('\n', out);
     506  }
     507  
     508  bool
     509  location_empty (location loc)
     510  {
     511    return !loc.start.file && !loc.start.line && !loc.start.column
     512      && !loc.end.file && !loc.end.line && !loc.end.column;
     513  }
     514  
     515  static inline int
     516  str_to_int (const char *s)
     517  {
     518    long l = strtol (s, NULL, 10);
     519    return l < 0 ? -1 : l <= INT_MAX ? l : INT_MAX;
     520  }
     521  
     522  void
     523  boundary_set_from_string (boundary *bound, char *str)
     524  {
     525    /* Search backwards: the file name may contain '.'  or ':'.  */
     526    char *at = strrchr (str, '@');
     527    if (at)
     528      {
     529        *at = '\0';
     530        bound->byte = str_to_int (at + 1);
     531      }
     532    {
     533      char *dot = strrchr (str, '.');
     534      aver (dot);
     535      *dot = '\0';
     536      bound->column = str_to_int (dot + 1);
     537      if (!at)
     538        bound->byte = bound->column;
     539    }
     540    {
     541      char *colon = strrchr (str, ':');
     542      aver (colon);
     543      *colon = '\0';
     544      bound->line = str_to_int (colon + 1);
     545    }
     546    bound->file = uniqstr_new (str);
     547  }