diff options
Diffstat (limited to 'Src/input.c')
-rw-r--r-- | Src/input.c | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/Src/input.c b/Src/input.c new file mode 100644 index 000000000..576341a7c --- /dev/null +++ b/Src/input.c @@ -0,0 +1,530 @@ +/* + * input.c - read and store lines of input + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + + +/* + * This file deals with input buffering, supplying characters to the + * history expansion code a character at a time. Input is stored on a + * stack, which allows insertion of strings into the input, possibly with + * flags marking the end of alias expansion, with minimal copying of + * strings. The same stack is used to record the fact that the input + * is a history or alias expansion and to store the alias while it is in use. + * + * Input is taken either from zle, if appropriate, or read directly from + * the input file, or may be supplied by some other part of the shell (such + * as `eval' or $(...) substitution). In the last case, it should be + * supplied by pushing a new level onto the stack, via inpush(input_string, + * flag, alias); if the current input really needs to be altered, use + * inputsetline(input_string, flag). `Flag' can include or's of INP_FREE + * (if the input string is to be freed when used), INP_CONT (if the input + * is to continue onto what's already in the input queue), INP_ALIAS + * (push supplied alias onto stack) or INP_HIST (ditto, but used to + * mark history expansion). `alias' is ignored unless INP_ALIAS or + * INP_HIST is supplied. INP_ALIAS is always set if INP_HIST is. + * + * Note that the input string is itself used as the input buffer: it is not + * copied, nor is it every written back to, so using a constant string + * should work. Consequently, when passing areas of memory from the heap + * it is necessary that that heap last as long as the operation of reading + * the string. After the string is read, the stack should be popped with + * inpop(), which effectively flushes any unread input as well as restoring + * the previous input state. + * + * The internal flag INP_ALCONT shows that the stack element was pushed + * by an alias expansion; it should not be needed elsewhere. + * + * The global variable inalmore is set to indicate aliases should + * continue to be expanded because the last alias expansion ended + * in a space. It is only reset after a complete word was read + * without expanding a new alias, in exalias(). + * + * PWS 1996/12/10 + */ + +#include "zsh.mdh" +#include "input.pro" + +/* the shell input fd */ + +/**/ +int SHIN; + +/* buffered shell input for non-interactive shells */ + +/**/ +FILE *bshin; + +/* != 0 means we are reading input from a string */ + +/**/ +int strin; + +/* total # of characters waiting to be read. */ + +/**/ +int inbufct; + +/* the flags controlling the input routines in input.c: see INP_* in zsh.h */ + +/**/ +int inbufflags; + +static char *inbuf; /* Current input buffer */ +static char *inbufptr; /* Pointer into input buffer */ +static char *inbufpush; /* Character at which to re-push alias */ +static int inbufleft; /* Characters left in current input + stack element */ + + + /* Input must be stacked since the input queue is used by + * various different parts of the shell. + */ + +struct instacks { + char *buf, *bufptr; + Alias alias; + int bufleft, bufct, flags; +}; +static struct instacks *instack, *instacktop; +/* + * Input stack size. We need to push the stack for aliases, history + * expansion, and reading from internal strings: only if these operations + * are nested do we need more than one extra level. Thus we shouldn't need + * too much space as a rule. Initially, INSTACK_INITIAL is allocated; if + * more is required, an extra INSTACK_EXPAND is added each time. + */ +#define INSTACK_INITIAL 4 +#define INSTACK_EXPAND 4 + +static int instacksz = INSTACK_INITIAL; + +/* Read a line from bshin. Convert tokens and * + * null characters to Meta c^32 character pairs. */ + +/**/ +char * +shingetline(void) +{ + char *line = NULL; + int ll = 0; + int c; + char buf[BUFSIZ]; + char *p; + + p = buf; + for (;;) { + do { + errno = 0; + c = fgetc(bshin); + } while (c < 0 && errno == EINTR); + if (c < 0 || c == '\n') { + if (c == '\n') + *p++ = '\n'; + if (p > buf) { + *p++ = '\0'; + line = zrealloc(line, ll + (p - buf)); + memcpy(line + ll, buf, p - buf); + } + return line; + } + if (imeta(c)) { + *p++ = Meta; + *p++ = c ^ 32; + } else + *p++ = c; + if (p >= buf + BUFSIZ - 1) { + line = zrealloc(line, ll + (p - buf) + 1); + memcpy(line + ll, buf, p - buf); + ll += p - buf; + line[ll] = '\0'; + p = buf; + } + } +} + +/* Get the next character from the input. + * Will call inputline() to get a new line where necessary. + */ + +/**/ +int +ingetc(void) +{ + char lastc; + + if (lexstop) + return ' '; + for (;;) { + if (inbufleft) { + inbufleft--; + inbufct--; + if (itok(lastc = STOUC(*inbufptr++))) + continue; + return lastc; + } + + /* If the next element down the input stack is a continuation of + * this, use it. + */ + if (inbufflags & INP_CONT) { + inpoptop(); + continue; + } + /* + * Otherwise, see if we have reached the end of input + * (due to an error, or to reading from a single string). + */ + if (strin || errflag) { + lexstop = 1; + return ' '; + } + /* As a last resort, get some more input */ + if (inputline()) + return ' '; + } +} + +/* Read a line from the current command stream and store it as input */ + +/**/ +static int +inputline(void) +{ + char *ingetcline, *ingetcpmptl = NULL, *ingetcpmptr = NULL; + + /* If reading code interactively, work out the prompts. */ + if (interact && isset(SHINSTDIN)) + if (!isfirstln) + ingetcpmptl = prompt2; + else { + ingetcpmptl = prompt; + if (rprompt) + ingetcpmptr = rprompt; + } + if (!(interact && isset(SHINSTDIN) && SHTTY != -1 && isset(USEZLE))) { + /* + * If not using zle, read the line straight from the input file. + * Possibly we don't get the whole line at once: in that case, + * we get another chunk with the next call to inputline(). + */ + + if (interact && isset(SHINSTDIN)) { + /* + * We may still be interactive (e.g. running under emacs), + * so output a prompt if necessary. We don't know enough + * about the input device to be able to handle an rprompt, + * though. + */ + char *pptbuf; + int pptlen; + pptbuf = unmetafy(promptexpand(ingetcpmptl, 0, NULL, NULL), &pptlen); + write(2, (WRITE_ARG_2_T)pptbuf, pptlen); + free(pptbuf); + } + ingetcline = shingetline(); + } else + ingetcline = (char *)zleread(ingetcpmptl, ingetcpmptr, 1); + if (!ingetcline) { + return lexstop = 1; + } + if (errflag) { + free(ingetcline); + return lexstop = errflag = 1; + } + /* Look for a space, to see if this shouldn't be put into history */ + if (isfirstln) + spaceflag = *ingetcline == ' '; + if (isset(VERBOSE)) { + /* Output the whole line read so far. */ + zputs(ingetcline, stderr); + fflush(stderr); + } + if (*ingetcline && ingetcline[strlen(ingetcline) - 1] == '\n') { + /* We've now read a complete line. */ + lineno++; + if (interact && isset(SUNKEYBOARDHACK) && isset(SHINSTDIN) && + SHTTY != -1 && *ingetcline && ingetcline[1] && + ingetcline[strlen(ingetcline) - 2] == '`') { + /* Junk an unmatched "`" at the end of the line. */ + int ct; + char *ptr; + + for (ct = 0, ptr = ingetcline; *ptr; ptr++) + if (*ptr == '`') + ct++; + if (ct & 1) { + ptr[-2] = '\n'; + ptr[-1] = '\0'; + } + } + } + isfirstch = 1; + /* Put this into the input channel. */ + inputsetline(ingetcline, INP_FREE); + + return 0; +} + +/* + * Put a string in the input queue: + * inbuf is only freeable if the flags include INP_FREE. + */ + +/**/ +static void +inputsetline(char *str, int flags) +{ + if ((inbufflags & INP_FREE) && inbuf) { + free(inbuf); + } + inbuf = inbufptr = str; + inbufleft = strlen(inbuf); + + /* + * inbufct must reflect the total number of characters left, + * as it used by other parts of the shell, so we need to take account + * of whether the input stack continues, and whether there + * is an extra space to add on at the end. + */ + if (flags & INP_CONT) + inbufct += inbufleft; + else + inbufct = inbufleft; + inbufflags = flags; +} + +/* + * Backup one character of the input. + * The last character can always be backed up, provided we didn't just + * expand an alias or a history reference. + * In fact, the character is ignored and the previous character is used. + * (If that's wrong, the bug is in the calling code. Use the #ifdef DEBUG + * code to check.) + */ + +/**/ +void +inungetc(int c) +{ + if (!lexstop) { + if (inbufptr != inbuf) { +#ifdef DEBUG + /* Just for debugging: enable only if foul play suspected. */ + if (inbufptr[-1] != (char) c) + fprintf(stderr, "Warning: backing up wrong character.\n"); +#endif + /* Just decrement the pointer: if it's not the same + * character being pushed back, we're in trouble anyway. + */ + inbufptr--; + inbufct++; + inbufleft++; + } +#ifdef DEBUG + else if (!(inbufflags & INP_CONT)) { + /* Just for debugging */ + fprintf(stderr, "Attempt to inungetc() at start of input.\n"); + } +#endif + else { + /* + * The character is being backed up from a previous input stack + * layer. However, there was an expansion in the middle, so we + * can't back up where we want to. Instead, we just push it + * onto the input stack as an extra character. + */ + char *cback = (char *)zcalloc(2); + cback[0] = (char) c; + inpush(cback, INP_FREE|INP_CONT, NULL); + } + /* If we are back at the start of a segment, + * we may need to restore an alias popped from the stack. + * Note this may be a dummy (history expansion) entry. + */ + if (inbufptr == inbufpush && inbufflags & INP_ALCONT) { + /* + * Go back up the stack over all entries which were alias + * expansions and were pushed with nothing remaining to read. + */ + do { + if (instacktop->alias) + instacktop->alias->inuse = 1; + instacktop++; + } while ((instacktop->flags & INP_ALCONT) && !instacktop->bufleft); + inbufflags = INP_CONT|INP_ALIAS; + inbufleft = 0; + inbuf = inbufptr = ""; + } + } +} + +/* stuff a whole file into the input queue and print it */ + +/**/ +int +stuff(char *fn) +{ + FILE *in; + char *buf; + int len; + + if (!(in = fopen(unmeta(fn), "r"))) { + zerr("can't open %s", fn, 0); + return 1; + } + fseek(in, 0, 2); + len = ftell(in); + fseek(in, 0, 0); + buf = (char *)zalloc(len + 1); + if (!(fread(buf, len, 1, in))) { + zerr("read error on %s", fn, 0); + fclose(in); + zfree(buf, len + 1); + return 1; + } + fclose(in); + buf[len] = '\0'; + fwrite(buf, len, 1, stderr); + fflush(stderr); + inputsetline(metafy(buf, len, META_REALLOC), INP_FREE); + return 0; +} + +/* flush input queue */ + +/**/ +void +inerrflush(void) +{ + while (!lexstop && inbufct) + ingetc(); +} + +/* Set some new input onto a new element of the input stack */ + +/**/ +void +inpush(char *str, int flags, Alias inalias) +{ + if (!instack) { + /* Initial stack allocation */ + instack = (struct instacks *)zalloc(instacksz*sizeof(struct instacks)); + instacktop = instack; + } + + instacktop->buf = inbuf; + instacktop->bufptr = inbufptr; + instacktop->bufleft = inbufleft; + instacktop->bufct = inbufct; + inbufflags &= ~INP_ALCONT; + if (flags & (INP_ALIAS|INP_HIST)) { + /* + * Text is expansion for history or alias, so continue + * back to old level when done. Also mark stack top + * as alias continuation so as to back up if necessary, + * and mark alias as in use. + */ + flags |= INP_CONT|INP_ALIAS; + instacktop->flags = inbufflags | INP_ALCONT; + if ((instacktop->alias = inalias)) + inalias->inuse = 1; + } else { + /* If we are continuing an alias expansion, record the alias + * expansion in new set of flags (do we need this?) + */ + if (((instacktop->flags = inbufflags) & INP_ALIAS) && + (flags & INP_CONT)) + flags |= INP_ALIAS; + } + + instacktop++; + if (instacktop == instack + instacksz) { + /* Expand the stack */ + instack = (struct instacks *) + realloc(instack, + (instacksz + INSTACK_EXPAND)*sizeof(struct instacks)); + instacktop = instack + instacksz; + instacksz += INSTACK_EXPAND; + } + /* + * We maintain the entry above the highest one with real + * text as a flag to inungetc() that it can stop re-pushing the stack. + */ + instacktop->flags = 0; + + inbufpush = inbuf = NULL; + + inputsetline(str, flags); +} + +/* Remove the top element of the stack */ + +/**/ +static void +inpoptop(void) +{ + if (inbuf && (inbufflags & INP_FREE)) + free(inbuf); + + instacktop--; + + inbuf = instacktop->buf; + inbufptr = inbufpush = instacktop->bufptr; + inbufleft = instacktop->bufleft; + inbufct = instacktop->bufct; + inbufflags = instacktop->flags; + + if (!(inbufflags & INP_ALCONT)) + return; + + if (instacktop->alias) { + char *t = instacktop->alias->text; + /* a real alias: mark it as unused. */ + instacktop->alias->inuse = 0; + if (*t && t[strlen(t) - 1] == ' ') { + inalmore = 1; + histbackword(); + } + } +} + +/* Remove the top element of the stack and all its continuations. */ + +/**/ +void +inpop(void) +{ + int remcont; + + do { + remcont = inbufflags & INP_CONT; + + inpoptop(); + } while (remcont); +} |