/************************************************************************
 *									*
 *			Copyright (c) 1982, Fred Fish			*
 *			    All Rights Reserved				*
 *									*
 *	This software and/or documentation is released for public	*
 *	distribution for personal, non-commercial use only.		*
 *	Limited rights to use, modify, and redistribute are hereby	*
 *	granted for non-commercial purposes, provided that all		*
 *	copyright notices remain intact and all changes are clearly	*
 *	documented.  The author makes no warranty of any kind with	*
 *	respect to this product and explicitly disclaims any implied	*
 *	warranties of merchantability or fitness for any particular	*
 *	purpose.							*
 *									*
 ************************************************************************
 */
/*
 * Modified:
 *	1 May 86 ...!ihnp4!ut-sally!ut-ngp!mic
 *		Now forces a '\0' at end of tgoto string.  Tgoto wasn't,
 *		and this screwed up VT100-style (i.e. variable) cursor
 *		addressing.
 *
 *	Sep 88, Apr 89 Tom Hageman
 *		Some optimizations
 *	Jul-Aug 1992, TRH
 *		Add Arcane + GNU extension formats
 *	(See Log at end)
 */

#ifdef RCS_ID
static const char rcsid[] =
	"$Id: tgoto.c,v 1.6 1993/05/24 22:44:48 tom Exp tom $";
#endif

/*
 *  LIBRARY FUNCTION
 *
 *	tgoto   expand cursor addressing string from cm capability
 *
 *  KEY WORDS
 *
 *	termcap
 *
 *  SYNOPSIS
 *
 *	char *tgoto (cm,destcol,destline)
 *	const char *cm;
 *	int destcol;
 *	int destline;
 *
 *  DESCRIPTION
 *
 *	Returns cursor addressing string, decoded from the cm
 *	capability string, to move cursor to column destcol on
 *	line destline.
 *
 *	The following sequences uses one input argument, either
 *	line or column, and place the appropriate substitution
 *	in the output string:
 *
 *		%d	substitute decimal value (in ASCII)
 *		%2	like %d but forces field width to 2
 *		%3	like %d but forces field width to 3
 *		%.	like %c
 *		%+x	like %c but adds ASCII value of x
 *
 *	The following sequences cause processing modifications
 *	but do not "use up" one of the arguments.  If they
 *	act on an argument they act on the next one to
 *	be converted.
 *
 *		%>xy	if next value to be converted is
 *			greater than value of ASCII char x
 *			then add value of ASCII char y.
 *		%r	reverse substitution of line
 *			and column (line is substituted
 *			first by default).
 *		%i	causes input values destcol and
 *			destline to be incremented.
 *		%%	gives single % character in output.
 *
#if (TCP & EXTENSION) && (TCP & ARCANE)
 *	Gnu Termcap extensions:
 *
 *		%a[=+-/*][cp]x	arithmetic.
 *		%b	backup (reuse) previous argument.
 *		%f	forward (skip) next argument.
 *		%m	Xor next 2 arguments with 0177.
 *
 *		%Cx	argument mod 96, then %+
 *		%s	output argument as string.
#endif
 *
 *  BUGS
 *
#if !(TCP & ARCANE)
 *	Does not implement some of the more arcane sequences for
 *	radically weird terminals (specifically %n, %B, & %D).
 *	If you have one of these you deserve whatever happens.
#endif
 *	Does not avoid output of \n or ^D.
 */

#include <stdio.h>
#include "_termcap.h"

#if _STDARG_
#   include <stdarg.h>
#else
#   include <varargs.h>
#endif

#if (TCP & TPARAM)
#   define MAXARGS 9
#else
#   define MAXARGS 2
#endif

#if (TCP & BCUP)
extern char *BC;
extern char *UP;
#else
#   define BC	NULL
#   define UP	NULL
#endif

#if (TCP & TPARAM)
static char *_tparam __(( const char *_(cm), char *_(buf), int _(size),
#   if (TCP & BCUP)
			  const char *_(BC), const char *_(UP),
#   endif
			  int _(args)[] ));
#endif

static const char OOPS[] = "OOPS";


/*
 *  PSEUDO CODE
 *
 *	Begin tgoto
 *	    If no string to process then
 *		Return pointer to error string.
 *	    Else
 *		Initialize pointer to input string.
 *		Initialize pointer to result string.
 *		First arg is line number by default.
 *		Second arg is col number by default.
 *		No arguments processed yet.
 *		While there is another character to process
 *		    If character is a not a % character then
 *			Simply copy to output.
 *		    Else
 *			If all args processed
 *				return unprocessed cm string as error message
 *			Process the control sequence:
 *			Switch on next character after %
 *			Case 'd':
 *			    setup for %d type conversion (variable width).
 *			    Break;
 *			Case '2':
 *			    setup for %d type conversion (width 2).
 *			    Break;
 *			Case '3':
 *			    setup for %d type conversion (width 3).
 *			    Break;
 *			Case '.'
 *			    Process %c type conversion.
 *			    Continue;
 *			Case '+':
 *			    Process %c type conversion with offset.
 *			    Continue;
 *			Case '>':
 *			    Process argument modification.
 *			    Continue;
 *			Case 'r':
 *			    Process argument reversal.
 *			    Continue;
 *			Case 'i':
 *			    Increment argument values.
 *			    Continue;
 *			Case '%':
 *			    Copy to output, incrementing pointers.
 *			    Continue;
#if (TCP)
 *			etc...
#endif
 *			Default:
 *			    return pointer to error string.
 *			End switch
 *			Process %d type conversion
 *		    End if
 *		End while
 *		Return pointer to static output string.
 *	    End if
 *	End tgoto
 *
 */

char *
tgoto(cm, destcol, destline)
const char	*cm;
int		destcol;
int		destline;
{
	static char	output[TGOTOBUFSIZE];
	int		args[2];
	register int	*parg = &args[2];
#if (TCP & DYNALLOC)
	static char	*outbuf;

	if (outbuf != output && outbuf != OOPS)
		free(outbuf);
#   if !(TCP & TPARAM)
	outbuf = output;
#	define output	outbuf
#   endif
#endif /* (TCP & DYNALLOC) */

	*--parg = destcol;	/* args[1] */
	*--parg = destline;	/* args[0] */

#if (TCP & TPARAM)
	return (
#   if (TCP & DYNALLOC)
		outbuf =
#   endif
		_tparam(cm, output, TGOTOBUFSIZE,
#   if (TCP & BCUP)
			BC, UP,
#   endif
			parg));
}

char *
#if (_ANSI_DEFUN_ && _STDARG_)
tparam(const char *cap, char *buf, int size, ...)
#else
tparam(cap, buf, size, va_alist)
const char	*cap;
char		*buf;
int		size;
va_dcl
#endif
{
	va_list		ap;
	register int	*parg;
#if (STACK_DIRECTION > 0 || STACK_DIRECTION == 0)
	/* Copy parameters to an array if the parameter stack grows in the
	   `wrong' direction (or if parameters are not passed on the stack). */
	int		args[MAXARGS];
	register int	*dp = args;
	register int	n;
#endif

	va_begin(ap, size);

#if (STACK_DIRECTION < 0 || STACK_DIRECTION == 0)
	/* If the parameter stack grows in the `right' direction, cheat;
	   parameters can be accessed directly as an array. */
	parg = (int *) ap;
#endif
#if !(STACK_DIRECTION < 0)
#   if (STACK_DIRECTION > 0)
	n = MAXARGS;
#   else /* (STACK_DIRECTION == 0) */
#     ifdef STACK_DIRECTION
	n = MAXARGS;
#     else
	/* Stack direction is unknown at compile-time.  Make an educated
	   guess by getting a single parameter off the stack, and then
	   comparing old and new values of the `va_list' pointer (assuming
	   it is, indeed, a pointer; if this gives you problems, e.g., if
	   `va_list' is declared as some struct, you should define
	   STACK_DIRECTION explicitly as 0) */
	n = MAXARGS - 1;
	*dp++ = va_arg(ap, int);
	if ((*(char **) &ap - (char *) parg) != sizeof(int))
#     endif
	{
		do  *dp++ = va_arg(ap, int);  while (--n);
		parg = args;
	}
#   endif
#endif /* !(STACK_DIRECTION < 0) */

	va_end(ap);

	return _tparam(cap, buf, size,
#   if (TCP & BCUP)
		       NULL, NULL,
#   endif
		       parg);
}

/* {{TODO: size check; BC/UP; dynamic allocation.}} */
static char *
_tparam(cm, output, size,
#   if (TCP & BCUP)
	BC, UP,
#   endif
	args)
const char	*cm;
char		*output;
int		size;
#   if (TCP & BCUP)
const char	*BC, *UP;
#   endif
int		args[];
{
	register int	*parg = args;
#else  /* !(TCP & TPARAM) */
#	define size	TGOTOBUFSIZE
#endif

	/* {...this is still part of tgoto() if not (TCP & TPARAM).} */
    {
#if (TCP & BCUP)
	int			need_bc = 0;
	int			need_up = 0;
#endif
	register char		*out = output;
	register const char	*in;
#if (TCP & DYNALLOC)
	int		out_alloced = 0;
	char		*out_limit;

	if (out_limit = output)
		out_limit += size - 3;
#else
#	define out_limit	(output + size - 3)
#endif

	if ((in = cm) == NULL)
oops:		return (char *) OOPS;

	while (1) {
		if (out > out_limit) {
#if (TCP & DYNALLOC)
			register const char *saved_in = in;

			out_limit = out;
			in = output;
			if (out_alloced == 0) {
				if (!(output = malloc(out_alloced = size + 32)))
					goto oops;
				out = output;
				while (in < out_limit) { /* copy string. */
					*out++ = in++;
				}
				/* Now `out' is where it should be. */
			}
			else {
				if (!(output = realloc(output,
						       out_alloced <<= 1)))
					goto oops;
				out = output + (out_limit - in);
			}
			out_limit = output + out_alloced - 3;
			in = saved_in;
#else /* !(TCP & DYNALLOC) */
			goto oops;
#endif
		}	
		if ((*out++ = *in) == '\0')
			break;
		
		if (*in++ == '%') {
			register const char	*fmt;

			if (parg >= &args[MAXARGS])
				goto oops;

			--out;
			switch (*in++) {
			case 'd':
			d: 	fmt = "%d";
				break;
			case '2':
				fmt = "%02d";
				break;
			case '3':
				fmt = "%03d";
				break;
			case '.':
				*out++ = *parg++;
#if (TCP & BCUP)
			c:
				if (BC && UP) {
					register char c;

					while ((c = out[-1]) == '\0' ||
					       c == '\n' || c == '\4' ||
					       c == '\t') { 
						out[-1]++;
						if (parg == args) {
							need_up++;
#   if (TCP & DYNALLOC)
							outend -= strlen(UP);
#   endif
						}
						else {
							need_bc++;
#   if (TCP & DYNALLOC)
							outend -= strlen(BC);
#   endif
						}
					}
				}
#endif
				continue;
			case '+':
				*out++ = *parg++ + *in++;
#if (TCP & BCUP)
				goto c;
#else
				continue;
#endif
			case '>':
				if  (*parg > *in++)
					*parg += *in;
				in++;
				continue;
			case 'r':
			    {
				int temp = parg[0];

				parg[0] = parg[1];
				parg[1] = temp;
				continue;
			    }
			case 'i':
				parg[0]++;
				parg[1]++;
				continue;
			case '%':
				++out;		/* '%' already copied */
				continue;
#if (TCP & ARCANE)
#   if (TCP & EXTENSION)			/* Gnu termcap extensions */
			case 'a':		/* arithmetic %a[-+=][pc]<c> */
			    {
				register int	n = (unsigned) in[2];

				if (n == 0)
					goto oops;

				switch (in[1]) {
				case 'c': n &= 0177;		break;
				case 'p': n = parg[n - 0100];	break;
				default:
					goto oops;
				}
				switch (in[0]) {
				case '=': *parg  = n;	break;
				case '+': *parg += n;	break;
				case '-': *parg -= n;	break;
				case '*': *parg *= n;	break;
				case '/': *parg /= n;	break;
				default:
					goto oops;
				}
				in += 3;
				continue;
			    }
			case 'b':		/* backup/reuse */
				--parg;
				continue;
			case 'f':		/* discard */
				++parg;
				continue;

			case 'm':		/* XOR args with 0177 */
				parg[0] ^= 0177;
				parg[1] ^= 0177;
				continue;
			case 'C':		/* C100: (v % 96), then %+ */
				if (*parg >= 96) {
					*out++ = *parg / 96;
					*out++ = *parg++ % 96 + *in++;
				}
				else {
					*out++ = *parg++ + *in++;
				}
#	if (TCP & BCUP)
				goto c;
#	else
				continue;
#	endif
			case 'o':		/* like %d if n != 1 */
				if (*parg == 1)
					continue;
				goto d;
			case 'z':		/* like %d if n != 0 */
				if (*parg == 0)
					continue;
				goto d;
#   endif /* (TCP & EXTENSION) */
			case 'n':		/* XOR args with 0140 */
				parg[0] ^= 0140;
				parg[1] ^= 0140;
				continue;
			case 'B':		/* BCD 16*(v/10)+v%10 */ 
				*parg += (*parg / 10) * 6;
				continue;
			case 'D':		/* (v - 2*v%16) */
				*parg -= (*parg & 15) << 1;
				continue;
#endif /* (TCP & ARCANE) */
#if (TCP & EXTENSION)
			case 's':		/* output string */
#   if (TCP & DYNALLOC)
				if ((out_limit -= strlen((char *) *parg)) < out) {
					/* Setup for buffer expansion and
					   re-scan of %s. */
					in -= 2;
					continue;
				}
#   endif
				fmt = in;
#   ifndef __STRICT_ANSI__
				/* Most compilers allow this, even if it is
				   not strict ANSI C. */
				in = *((char **) parg)++;
#   else
				in = *(char **) parg;
				parg = (int *)((char *) parg + sizeof(char *));
#   endif
				while (*out++ = *in++) ;
				--out;
				in = fmt;
				continue;
#endif
			default:
				goto oops;
			} /* switch */

			/* only get here on "non-trivial" conversions */
#ifdef PRINTF_RETURNS_NCHARS
			out +=
#endif
			sprintf(out, fmt, *parg++);
#ifndef PRINTF_RETURNS_NCHARS
			while (*out++)
				;	/* skip output just appended	*/
			--out;
#endif
		}
	}
#if (TCP & BCUP)
	while (--need_up >= 0) {
		in = UP;
		while (*out++ = *in++) ;
		--out;
	}
	while (--need_bc >= 0) {
		in = BC;
		while (*out++ = *in++) ;
		--out;
	}
#endif
    }
	return (output);
}

/*======================================================================*
 * $Log: tgoto.c,v $
 * Revision 1.6  1993/05/24  22:44:48  tom
 * implement DYNALLOC, BCUP features; don't output %B, %D as per GNU termcap
 * 1.2 specs; (tparam): improve STACK_DIRECTION tangle; fix typo in RCS Log.
 *
 *======================================================================*/
