master xplshn/aruu / cmd / extra / yap / getline.c
  1/* Copyright (c) 1985 Ceriel J.H. Jacobs */
  2
  3#include "getline.h"
  4#include "assert.h"
  5#include "display.h"
  6#include "in_all.h"
  7#include "main.h"
  8#include "options.h"
  9#include "output.h"
 10#include "process.h"
 11#include "prompt.h"
 12#include "term.h"
 13
 14#define BLOCKSIZE 2048 /* size of blocks */
 15#define CHUNK     50   /* # of blockheaders allocated at a time */
 16
 17/*
 18 * Blocks are kept in an array in line-number order. Each block stores the raw
 19 * text and the offsets of the lines parsed from it.
 20 */
 21
 22struct block {
 23  int b_flags;    /* Contains the following flags: */
 24#define PARTLY 02 /* block not filled completely (eof) */
 25  long  b_end;    /* line number of last line in block */
 26  char *b_info;   /* the block */
 27  int  *b_offs;   /* line offsets within the block */
 28};
 29
 30static struct block *blocklist, /* beginning of the list of blocks */
 31    *maxblocklist,              /* first free entry in the list */
 32    *topblocklist;              /* end of allocated core for the list */
 33static long lastreadline;       /* lineno of last line read */
 34static int  ENDseen;
 35
 36static void          nextblock(struct block *pblock);
 37static char         *re_alloc(char *ptr, unsigned newsize);
 38static struct block *getblock(long n, int disable_interrupt);
 39
 40static struct block *
 41new_block()
 42{
 43  struct block *pblock = maxblocklist - 1;
 44
 45  if (!maxblocklist || !(pblock->b_flags & PARTLY)) {
 46    /*
 47     * There is no last block, or it was filled completely,
 48     * so allocate a new blockheader.
 49     */
 50    int siz;
 51
 52    pblock = blocklist;
 53    if (maxblocklist == topblocklist) {
 54      /*
 55       * No blockheaders left. Allocate new ones
 56       */
 57      siz       = topblocklist - pblock;
 58      blocklist = pblock =
 59          (struct block *)re_alloc((char *)pblock, (unsigned)((siz + CHUNK) * sizeof(*pblock)));
 60      pblock += siz;
 61      topblocklist = pblock + CHUNK;
 62      maxblocklist = pblock;
 63      for (; pblock < topblocklist; pblock++) {
 64        pblock->b_end   = 0;
 65        pblock->b_info  = 0;
 66        pblock->b_flags = 0;
 67      }
 68      if (!siz) {
 69        /*
 70         * Create dummy header cell.
 71         */
 72        maxblocklist++;
 73      }
 74    }
 75    pblock = maxblocklist++;
 76  }
 77  nextblock(pblock);
 78  return pblock;
 79}
 80
 81/*
 82 * Return the block in which line 'n' of the current file can be found.
 83 * If "disable_interrupt" = 0, the call may be interrupted, in which
 84 * case it returns 0.
 85 */
 86
 87static struct block *
 88getblock(long n, int disable_interrupt)
 89{
 90  struct block *pblock;
 91
 92  if (stdf < 0) {
 93    /*
 94     * Not file descriptor, so return end of file
 95     */
 96    return 0;
 97  }
 98  pblock = maxblocklist - 1;
 99  if (n < lastreadline || (n == lastreadline && !(pblock->b_flags & PARTLY))) {
100    /*
101     * The line asked for has been read already.
102     * Perform binary search in the blocklist to find the block
103     * where it's in.
104     */
105    struct block *min, *mid;
106
107    min = blocklist + 1;
108    do {
109      mid = min + (pblock - min) / 2;
110      if (n > mid->b_end) {
111        min = mid + 1;
112      } else
113        pblock = mid;
114    } while (min < pblock);
115    /* Found, pblock is now a reference to the block wanted */
116    return pblock;
117  }
118
119  /*
120   * The line was'nt read yet, so read blocks until found
121   */
122  for (;;) {
123    if (interrupt && !disable_interrupt)
124      return 0;
125    pblock = new_block();
126    if (pblock->b_end >= n) {
127      return pblock;
128    }
129    if (pblock->b_flags & PARTLY) {
130      /*
131       * We did not find it, and the last block could not be
132       * read completely, so return 0;
133       */
134      return 0;
135    }
136  }
137  /* NOTREACHED */
138}
139
140char *
141getline(long n, int disable_interrupt)
142{
143  struct block *pblock;
144
145  if (!(pblock = getblock(n, disable_interrupt))) {
146    return (char *)0;
147  }
148  return pblock->b_info + pblock->b_offs[n - ((pblock - 1)->b_end + 1)];
149}
150
151/*
152 * Find the last line of the input, and return its number
153 */
154
155long
156to_lastline()
157{
158  for (;;) {
159    if (!getline(lastreadline + 1, 0)) {
160      /*
161       * "lastreadline" always contains the linenumber of
162       * the last line read. So, if the call to getline
163       * succeeds, "lastreadline" is affected
164       */
165      if (interrupt)
166        return -1L;
167      return lastreadline;
168    }
169  }
170  /* NOTREACHED */
171}
172
173char *
174alloc(unsigned size)
175{
176  char *pmem;
177
178  pmem = malloc(size);
179  if (!pmem && size != 0) {
180    panic("No core");
181  }
182  return pmem;
183}
184
185/*
186 * Re-allocate the memorychunk pointed to by ptr, to let it
187 * grow or shrink.
188 */
189
190static char *
191re_alloc(char *ptr, unsigned newsize)
192{
193  char *pmem;
194
195  pmem = realloc(ptr, newsize);
196  if (!pmem && newsize != 0) {
197    panic("No core");
198  }
199  return pmem;
200}
201
202static char *saved;
203static long  filldegree;
204
205/*
206 * Try to read the block indicated by pblock
207 */
208
209static void
210nextblock(struct block *pblock)
211{
212  char *c,                   /* Run through pblock->b_info */
213      *c1;                   /* indicate end of pblock->b_info */
214  int            *poff;      /* pointer in line-offset list */
215  int             cnt;       /* # of characters read */
216  unsigned        siz;       /* Size of allocated line-offset list */
217  static unsigned savedsiz;  /* saved "siz" */
218  static int     *savedpoff; /* saved "poff" */
219  static char    *savedc1;   /* saved "c1" */
220
221  if (pblock->b_flags & PARTLY) {
222    /*
223     * The block was already partly filled. Initialize locals
224     * accordingly
225     */
226    poff            = savedpoff;
227    siz             = savedsiz;
228    pblock->b_flags = 0;
229    c1              = savedc1;
230    if (c1 == pblock->b_info || *(c1 - 1)) {
231      /*
232       * We had incremented "lastreadline" temporarily,
233       * because the last line could not be completely read
234       * last time we tried. Undo this increment
235       */
236      poff--;
237      --lastreadline;
238    }
239  } else {
240    if (saved) {
241      /*
242       * There were leftovers from the previous block
243       */
244      pblock->b_info = saved;
245      c1             = savedc1;
246      saved          = 0;
247    } else { /* Allocate new block */
248      pblock->b_info = c1 = alloc(BLOCKSIZE + 1);
249    }
250    /*
251     * Allocate some space for line-offsets
252     */
253    pblock->b_offs = poff = (int *)alloc((unsigned)(100 * sizeof(int)));
254    siz                   = 99;
255    *poff++               = 0;
256  }
257  c = c1;
258  for (;;) {
259    /*
260     * Read loop
261     */
262    cnt = read(stdf, c1, BLOCKSIZE - (c1 - pblock->b_info));
263    if (cnt < 0) {
264      /*
265       * Interrupted read
266       */
267      if (errno == EINTR)
268        continue;
269      error("Could not read input file");
270      cnt = 0;
271    }
272    c1 += cnt;
273    if (c1 != pblock->b_info + BLOCKSIZE) {
274      ENDseen = 1;
275      pblock->b_flags |= PARTLY;
276    }
277    break;
278  }
279  assert(c <= c1);
280  while (c < c1) {
281    /*
282     * Now process the block
283     */
284    if (*c == '\n') {
285      /*
286       * Newlines are replaced by '\0', so that "getline"
287       * can deliver one line at a time
288       */
289      *c = 0;
290      lastreadline++;
291      /*
292       * Remember the line-offset
293       */
294      if (poff == pblock->b_offs + siz) {
295        /*
296         * No space for it, allocate some more
297         */
298        pblock->b_offs =
299            (int *)re_alloc((char *)pblock->b_offs, (unsigned)((siz + 51) * sizeof(int)));
300        poff = pblock->b_offs + siz;
301        siz += 50;
302      }
303      *poff++ = c - pblock->b_info + 1;
304    }
305    c++;
306  }
307  assert(c == c1);
308  *c = 0;
309  if (c != pblock->b_info && *(c - 1) != 0) {
310    /*
311     * The last line read does not end with a newline, so add one
312     */
313    lastreadline++;
314    *poff++ = c - pblock->b_info + 1;
315    if (!(pblock->b_flags & PARTLY) && *(poff - 2) != 0) {
316      /*
317       * Save the started line; it will be in the next block.
318       * Remove the newline we added just now.
319       */
320      saved = c1 = alloc(BLOCKSIZE + 1);
321      c          = pblock->b_info + *(--poff - 1);
322      while (*c)
323        *c1++ = *c++;
324      c       = pblock->b_info + *(poff - 1);
325      savedc1 = c1;
326      --lastreadline;
327    }
328  }
329  pblock->b_end = lastreadline;
330  if (pblock->b_flags & PARTLY) {
331    /*
332     * Take care, that we can call "nextblock" again, to fill in
333     * the rest of this block
334     */
335    savedsiz  = siz;
336    savedpoff = poff;
337    savedc1   = c;
338    if (c == pblock->b_info) {
339      lastreadline++;
340      pblock->b_end = 0;
341    }
342  } else {
343    cnt        = pblock - blocklist;
344    filldegree = ((c - pblock->b_info) + (cnt - 1) * filldegree) / cnt;
345  }
346  assert(pblock->b_end - (pblock - 1)->b_end <= poff - pblock->b_offs);
347}
348
349/*
350 * Called after processing a file.
351 * Free all core.
352 */
353
354void
355do_clean()
356{
357  struct block *pblock;
358  char         *p;
359
360  for (pblock = blocklist; pblock < maxblocklist; pblock++) {
361    if ((p = pblock->b_info) != 0) {
362      free(p);
363      free((char *)pblock->b_offs);
364    }
365  }
366  if ((p = (char *)blocklist) != 0) {
367    free(p);
368  }
369  blocklist    = 0;
370  maxblocklist = 0;
371  topblocklist = 0;
372  lastreadline = 0;
373  filldegree   = 0;
374  ENDseen      = 0;
375  if ((p = saved) != 0)
376    free(p);
377  saved = 0;
378}
379
380/*
381 * Get a character. If possible, do some workahead.
382 */
383
384int
385getch()
386{
387  int         flags, bytes_ready, bytes_read;
388  struct stat buf;
389  char        c;
390
391  flush();
392  if (startcomm) {
393    /*
394     * Command line option command
395     */
396    if (*startcomm)
397      return *startcomm++;
398    return '\n';
399  }
400  if (stdf >= 0) {
401    /*
402     * Make reads from the terminal non-blocking, so that
403     * we can see if the user typed something
404     */
405    flags = fcntl(0, F_GETFL, 0);
406    if (flags != -1 && fcntl(0, F_SETFL, flags | O_NONBLOCK) != -1) {
407      bytes_read = 0;
408      while (!ENDseen &&
409			       ((bytes_read = read(0, &c, 1)) == 0
410#ifdef EWOULDBLOCK
411			        || (bytes_read < 0 && errno == EWOULDBLOCK)
412#endif
413#ifdef EAGAIN
414			        || (bytes_read < 0 && errno == EAGAIN)
415#endif
416			            ) &&
417			       (nopipe ||
418			        (fstat(stdf, &buf) >= 0 && buf.st_size > 0))) {
419        /*
420         * Do some read ahead, after making sure there
421         * is input and the user did not type a command
422         */
423        new_block();
424      }
425      (void)fcntl(0, F_SETFL, flags);
426      if (bytes_read < 0) {
427        /*
428         * Could this have happened?
429         * I'm not sure, because the read is
430         * nonblocking. Can it be interrupted then?
431         */
432        return -1;
433      }
434      if (bytes_read > 0)
435        return c & 0x7f;
436    }
437  }
438  if (ioctl(0, FIONREAD, (char *)&bytes_ready) >= 0 && stdf >= 0) {
439    while (!ENDseen && bytes_ready == 0
440           && (nopipe || (fstat(stdf, &buf) >= 0 && buf.st_size > 0))) {
441      if (interrupt)
442        return -1;
443      new_block();
444      if (ioctl(0, FIONREAD, (char *)&bytes_ready) < 0) {
445        break;
446      }
447    }
448  }
449  if (read(0, &c, 1) <= 0)
450    return -1;
451  return c & 0x7f;
452}
453
454/*
455 * Get the position of line "ln" in the file.
456 */
457
458long
459getpos(long ln)
460{
461  struct block *pblock;
462  long          i;
463
464  pblock = getblock(ln, 1);
465  assert(pblock != 0);
466  i = filldegree * (pblock - blocklist);
467  return i - (filldegree - pblock->b_offs[ln - (pblock - 1)->b_end]);
468}