/*
 * 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 flags INP_ALCONT and INP_HISTCONT show that the stack
 * element was pushed by an alias or history expansion; they 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
 */

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif

#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. */

/**/
mod_export 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. */

/**/
mod_export char *
shingetline(void)
{
    char *line = NULL;
    int ll = 0;
    int c;
    char buf[BUFSIZ];
    char *p;
    int q = queue_signal_level();

    p = buf;
    winch_unblock();
    dont_queue_signals();
    for (;;) {
	/* Can't fgets() here because we need to accept '\0' bytes */
	do {
	    errno = 0;
	    c = fgetc(bshin);
	} while (c < 0 && errno == EINTR);
	if (c < 0 || c == '\n') {
	    winch_block();
	    restore_queue_signals(q);
	    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) {
	    winch_block();
	    queue_signals();
	    line = zrealloc(line, ll + (p - buf) + 1);
	    memcpy(line + ll, buf, p - buf);
	    ll += p - buf;
	    line[ll] = '\0';
	    p = buf;
	    winch_unblock();
	    dont_queue_signals();
	}
    }
}

/* Get the next character from the input.
 * Will call inputline() to get a new line where necessary.
 */

/**/
int
ingetc(void)
{
    int lastc = ' ';

    if (lexstop)
	return ' ';
    for (;;) {
	if (inbufleft) {
	    inbufleft--;
	    inbufct--;
	    if (itok(lastc = STOUC(*inbufptr++)))
		continue;
	    if (((inbufflags & INP_LINENO) || !strin) && lastc == '\n')
		lineno++;
	    break;
	}

	/*
	 * See if we have reached the end of input
	 * (due to an error, or to reading from a single string).
	 * Check the remaining characters left, since if there aren't
	 * any we don't want to pop the stack---it'll mark any aliases
	 * as not in use before we've finished processing.
	 */
	if (!inbufct && (strin || errflag)) {
	    lexstop = 1;
	    break;
	}
	/* If the next element down the input stack is a continuation of
	 * this, use it.
	 */
	if (inbufflags & INP_CONT) {
	    inpoptop();
	    continue;
	}
	/* As a last resort, get some more input */
	if (inputline())
	    break;
    }
    if (!lexstop)
	zshlex_raw_add(lastc);
    return lastc;
}

/* Read a line from the current command stream and store it as input */

/**/
static int
inputline(void)
{
    char *ingetcline, **ingetcpmptl = NULL, **ingetcpmptr = NULL;
    int context = ZLCON_LINE_START;

    /* If reading code interactively, work out the prompts. */
    if (interact && isset(SHINSTDIN)) {
	if (!isfirstln) {
	    ingetcpmptl = &prompt2;
	    if (rprompt2)
		ingetcpmptr = &rprompt2;
	    context = ZLCON_LINE_CONT;
	}
	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 ? *ingetcpmptl : NULL,
					   0, NULL, NULL, NULL), &pptlen);
	    write_loop(2, pptbuf, pptlen);
	    free(pptbuf);
	}
	ingetcline = shingetline();
    } else {
	/*
	 * Since we may have to read multiple lines before getting
	 * a complete piece of input, we tell zle not to restore the
	 * original tty settings after reading each chunk.  Instead,
	 * this is done when the history mechanism for the current input
	 * terminates, which is not until we have the whole input.
	 * This is supposed to minimise problems on systems that clobber
	 * typeahead when the terminal settings are altered.
	 *                     pws 1998/03/12
	 */
	int flags = ZLRF_HISTORY|ZLRF_NOSETTY;
	if (isset(IGNOREEOF))
	    flags |= ZLRF_IGNOREEOF;
	ingetcline = zleentry(ZLE_CMD_READ, ingetcpmptl, ingetcpmptr,
			      flags, context);
	histdone |= HISTFLAG_SETTY;
    }
    if (!ingetcline) {
	return lexstop = 1;
    }
    if (errflag) {
	free(ingetcline);
	errflag |= ERRFLAG_ERROR;
	return lexstop = 1;
    }
    if (isset(VERBOSE)) {
	/* Output the whole line read so far. */
	zputs(ingetcline, stderr);
	fflush(stderr);
    }
    if (keyboardhackchar && *ingetcline &&
	ingetcline[strlen(ingetcline) - 1] == '\n' &&
	interact && isset(SHINSTDIN) &&
	SHTTY != -1 && ingetcline[1])
    {
	char *stripptr = ingetcline + strlen(ingetcline) - 2;
	if (*stripptr == keyboardhackchar) {
	    /* Junk an unwanted character at the end of the line.
	       (key too close to return key) */
	    int ct = 1;  /* force odd */
	    char *ptr;

	    if (keyboardhackchar == '\'' || keyboardhackchar == '"' ||
		keyboardhackchar == '`') {
		/*
		 * for the chars above, also require an odd count before
		 * junking
		 */
		for (ct = 0, ptr = ingetcline; *ptr; ptr++)
		    if (*ptr == keyboardhackchar)
			ct++;
	    }
	    if (ct & 1) {
		stripptr[0] = '\n';
		stripptr[1] = '\0';
	    }
	}
    }
    isfirstch = 1;
    if ((inbufflags & INP_APPEND) && inbuf) {
	/*
	 * We need new input but need to be able to back up
	 * over the old input, so append this line.
	 * Pushing the line onto the stack doesn't have the right
	 * effect.
	 *
	 * This is quite a simple and inefficient fix, but currently
	 * we only need it when backing up over a multi-line $((...
	 * that turned out to be a command substitution rather than
	 * a math substitution, which is a very special case.
	 * So it's not worth rewriting.
	 */
	char *oinbuf = inbuf;
	int newlen = strlen(ingetcline);
	int oldlen = (int)(inbufptr - inbuf) + inbufleft;
	if (inbufflags & INP_FREE) {
	    inbuf = realloc(inbuf, oldlen + newlen + 1);
	} else {
	    inbuf = zalloc(oldlen + newlen + 1);
	    memcpy(inbuf, oinbuf, oldlen);
	}
	inbufptr += inbuf - oinbuf;
	strcpy(inbuf + oldlen, ingetcline);
	free(ingetcline);
	inbufleft += newlen;
	inbufct += newlen;
	inbufflags |= INP_FREE;
    } else {
	/* 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)
{
    queue_signals();

    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;

    unqueue_signals();
}

/*
 * 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++;
	    if (((inbufflags & INP_LINENO) || !strin) && c == '\n')
		lineno--;
	}
        else if (!(inbufflags & INP_CONT)) {
#ifdef DEBUG
	    /* Just for debugging */
	    fprintf(stderr, "Attempt to inungetc() at start of input.\n");
#endif
	    zerr("Garbled input at %c (binary file as commands?)", c);
	    return;
	}
	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 *)zshcalloc(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|INP_HISTCONT))) {
	    /*
	     * 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|INP_HISTCONT))
		     && !instacktop->bufleft);
	    if (inbufflags & INP_HISTCONT)
		inbufflags = INP_CONT|INP_ALIAS|INP_HIST;
	    else
		inbufflags = INP_CONT|INP_ALIAS;
	    inbufleft = 0;
	    inbuf = inbufptr = "";
	}
	zshlex_raw_back();
    }
}

/* stuff a whole file into the input queue and print it */

/**/
int
stuff(char *fn)
{
    FILE *in;
    char *buf;
    off_t len;

    if (!(in = fopen(unmeta(fn), "r"))) {
	zerr("can't open %s", fn);
	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);
	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 */

/**/
mod_export 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|INP_HISTCONT);
    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;
	if (flags & INP_HIST)
	    instacktop->flags = inbufflags | INP_HISTCONT;
	else
	    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 (!lexstop) {
	inbufflags &= ~(INP_ALCONT|INP_HISTCONT);
	while (inbufptr > inbuf) {
	    inbufptr--;
	    inbufct++;
	    inbufleft++;
	    /*
	     * As elsewhere in input and history mechanisms:
	     * unwinding aliases and unwinding history have different
	     * implications as aliases are after the lexer while
	     * history is before, but they're both pushed onto
	     * the input stack.
	     */
	    if ((inbufflags & (INP_ALIAS|INP_HIST|INP_RAW_KEEP)) == INP_ALIAS)
		zshlex_raw_back();
	}
    }

    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|INP_HISTCONT)))
	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. */

/**/
mod_export void
inpop(void)
{
    int remcont;

    do {
	remcont = inbufflags & INP_CONT;

	inpoptop();
    } while (remcont);
}

/*
 * Expunge any aliases from the input stack; they shouldn't appear
 * in the history and need to be flushed explicitly when we encounter
 * an error.
 */

/**/
void
inpopalias(void)
{
    while (inbufflags & INP_ALIAS)
	inpoptop();
}


/*
 * Get pointer to remaining string to read.
 */

/**/
char *
ingetptr(void)
{
    return inbufptr;
}

/*
 * Check if the current input line, including continuations, is
 * expanding an alias.  This does not detect alias expansions that
 * have been fully processed and popped from the input stack.
 * If there is an alias, the most recently expanded is returned,
 * else NULL.
 */

/**/
char *input_hasalias(void)
{
    int flags = inbufflags;
    struct instacks *instackptr = instacktop;

    for (;;)
    {
	if (!(flags & INP_CONT))
	    break;
	instackptr--;
	if (instackptr->alias)
	    return instackptr->alias->node.nam;
	flags = instackptr->flags;
    }

    return NULL;
}
