/* heredoc.c: heredoc slurping is done here */

#include "rc.h"

struct Hq {
	Node *doc;
	char *name;
	Hq *n;
	bool quoted;
} *hq;

static bool dead = FALSE;

/*
 * read in a heredocument. A clever trick: skip over any partially matched end-of-file
 * marker storing only the number of characters matched. If the whole marker is matched,
 * return from readheredoc(). If only part of the marker is matched, copy that part into
 * the heredocument.
 *
 * BUG: if the eof string contains a newline, the state can get confused, and the
 * heredoc may continue past where it should.  on the other hand, /bin/sh seems to
 * never get out of its readheredoc() when the heredoc string contains a newline
 */

static char *readheredoc(char *eof) {
	int c;
	char *t, *buf, *bufend;
	unsigned char *s;
	SIZE_T bufsize;
	t = buf = nalloc(bufsize = 512);
	bufend = &buf[bufsize];
	dead = FALSE;
#define	RESIZE(extra) { \
		char *nbuf; \
		bufsize = bufsize * 2 + extra; \
		nbuf = nalloc(bufsize); \
		memcpy(nbuf, buf, (SIZE_T) (t - buf)); \
		t = nbuf + (t - buf); \
		buf = nbuf; \
		bufend = &buf[bufsize]; \
	}
	for (;;) {
		print_prompt2();
		for (s = (unsigned char *) eof; (c = gchar()) == *s; s++)
			;
		if (*s == '\0' && (c == '\n' || c == EOF)) {
			*t++ = '\0';
			return buf;
		}
		if (s != (unsigned char *) eof) {
			SIZE_T len = s - (unsigned char *) eof;
			if (t + len >= bufend)
				RESIZE(len);
			memcpy(t, eof, len);
			t += len;
		}
		for (;; c = gchar()) {
			if (c == EOF) {
				yyerror("heredoc incomplete");
				dead = TRUE;
				return NULL;
			}
			if (t + 1 >= bufend)
				RESIZE(0);
			*t++ = c;
			if (c == '\n')
				break;
		}
	}
}

/* parseheredoc -- turn a heredoc with variable references into a node chain */

static Node *parseheredoc(char *s) {
	int c = *s;
	Node *result = NULL;
	while (TRUE) {
		Node *node;
		switch (c) {
		default: {
			char *begin = s;
			while ((c = *s++) != '\0' && c != '$')
				;
			*--s = '\0';
			node = mk(nQword, begin, NULL);
			break;
		}
		case '$': {
			char *begin = ++s, *var;
			c = *s++;
			if (c == '$') {
				node = mk(nQword, "$", NULL);
				c = *s;
			} else {
				SIZE_T len = 0;
				do
					len++;
				while (!dnw[c = *(unsigned char *) s++]);
				if (c == '^')
					c = *s;
				else
					s--;
				var = nalloc(len + 1);
				var[len] = '\0';
				memcpy(var, begin, len);
				node = mk(nFlat, mk(nWord, var, NULL));
			}
			break;
		}
		case '\0':
			return result;
		}
		result = (result == NULL) ? node : mk(nConcat, result, node);
	}
}

/* read in heredocs when yyparse hits a newline. called from yyparse */

extern int heredoc(int end) {
	Hq *here;
	if ((here = hq) != NULL) {
		hq = NULL;
		if (end) {
			yyerror("heredoc incomplete");
			return FALSE;
		}
		do {
			Node *n = here->doc;
			char *s = readheredoc(here->name);
			if (dead)
				return FALSE;
			n->u[2].p = here->quoted ? mk(nQword, s, NULL) : parseheredoc(s);
			n->u[0].i = rHerestring;
		} while ((here = here->n) != NULL);
	}
	return TRUE;
}

/* queue pending heredocs into a queue. called from yyparse */

extern int qdoc(Node *name, Node *n) {
	Hq *new, **prev;
	if (name->type != nWord && name->type != nQword) {
		yyerror("eof-marker not a single literal word");
		flushu();
		return FALSE;
	}
	for (prev = &hq; (new = *prev) != NULL; prev = &new->n)
		;
	*prev = new = nnew(Hq);
	new->name = name->u[0].s;
	new->quoted = (name->type == nQword);
	new->doc = n;
	new->n = NULL;
	return TRUE;
}
