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}