tmi2/
tmi2/bin/
tmi2/etc/
tmi2/lib/
tmi2/lib/adm/
tmi2/lib/adm/daemons/languages/
tmi2/lib/adm/daemons/network/I3/
tmi2/lib/adm/daemons/virtual/template/
tmi2/lib/adm/obj/master/
tmi2/lib/adm/priv/
tmi2/lib/adm/shell/
tmi2/lib/adm/tmp/
tmi2/lib/cmds/
tmi2/lib/d/
tmi2/lib/d/Conf/
tmi2/lib/d/Conf/adm/
tmi2/lib/d/Conf/boards/
tmi2/lib/d/Conf/cmds/
tmi2/lib/d/Conf/data/
tmi2/lib/d/Conf/logs/
tmi2/lib/d/Conf/obj/
tmi2/lib/d/Conf/text/help/
tmi2/lib/d/Fooland/adm/
tmi2/lib/d/Fooland/data/
tmi2/lib/d/Fooland/data/attic/
tmi2/lib/d/Fooland/items/
tmi2/lib/d/TMI/
tmi2/lib/d/TMI/adm/
tmi2/lib/d/TMI/boards/
tmi2/lib/d/TMI/data/
tmi2/lib/d/TMI/rooms/
tmi2/lib/d/grid/
tmi2/lib/d/grid/adm/
tmi2/lib/d/grid/data/
tmi2/lib/d/std/
tmi2/lib/d/std/adm/
tmi2/lib/data/adm/
tmi2/lib/data/adm/daemons/
tmi2/lib/data/adm/daemons/doc_d/
tmi2/lib/data/adm/daemons/emoted/
tmi2/lib/data/adm/daemons/network/http/
tmi2/lib/data/adm/daemons/network/services/mail_q/
tmi2/lib/data/adm/daemons/network/smtp/
tmi2/lib/data/adm/daemons/news/archives/
tmi2/lib/data/attic/connection/
tmi2/lib/data/attic/user/
tmi2/lib/data/std/connection/b/
tmi2/lib/data/std/connection/l/
tmi2/lib/data/std/user/a/
tmi2/lib/data/std/user/b/
tmi2/lib/data/std/user/d/
tmi2/lib/data/std/user/f/
tmi2/lib/data/std/user/l/
tmi2/lib/data/std/user/x/
tmi2/lib/data/u/d/dm/working/doc_d/
tmi2/lib/data/u/l/leto/doc_d/
tmi2/lib/data/u/l/leto/smtp/
tmi2/lib/doc/
tmi2/lib/doc/driverdoc/applies/
tmi2/lib/doc/driverdoc/concepts/
tmi2/lib/doc/driverdoc/driver/
tmi2/lib/doc/driverdoc/efuns/arrays/
tmi2/lib/doc/driverdoc/efuns/buffers/
tmi2/lib/doc/driverdoc/efuns/compile/
tmi2/lib/doc/driverdoc/efuns/ed/
tmi2/lib/doc/driverdoc/efuns/floats/
tmi2/lib/doc/driverdoc/efuns/functions/
tmi2/lib/doc/driverdoc/efuns/general/
tmi2/lib/doc/driverdoc/efuns/numbers/
tmi2/lib/doc/driverdoc/efuns/parsing/
tmi2/lib/doc/driverdoc/lpc/constructs/
tmi2/lib/doc/driverdoc/lpc/preprocessor/
tmi2/lib/doc/driverdoc/lpc/types/
tmi2/lib/doc/driverdoc/platforms/
tmi2/lib/doc/mudlib/
tmi2/lib/ftp/
tmi2/lib/log/
tmi2/lib/obj/net/
tmi2/lib/obj/shells/
tmi2/lib/std/board/
tmi2/lib/std/body/
tmi2/lib/std/fun/
tmi2/lib/std/living/
tmi2/lib/std/object/
tmi2/lib/std/shop/
tmi2/lib/std/socket/
tmi2/lib/std/virtual/
tmi2/lib/student/
tmi2/lib/student/kalypso/
tmi2/lib/student/kalypso/armor/
tmi2/lib/student/kalypso/rooms/
tmi2/lib/student/kalypso/weapons/
tmi2/lib/u/l/leto/
tmi2/lib/u/l/leto/cmds/
tmi2/lib/www/errors/
tmi2/lib/www/gateways/
tmi2/lib/www/images/
tmi2/old/
tmi2/v21.7a10/
tmi2/v21.7a10/ChangeLog.old/
tmi2/v21.7a10/compat/simuls/
tmi2/v21.7a10/include/
tmi2/v21.7a10/testsuite/
tmi2/v21.7a10/testsuite/clone/
tmi2/v21.7a10/testsuite/command/
tmi2/v21.7a10/testsuite/data/
tmi2/v21.7a10/testsuite/etc/
tmi2/v21.7a10/testsuite/include/
tmi2/v21.7a10/testsuite/inherit/
tmi2/v21.7a10/testsuite/inherit/master/
tmi2/v21.7a10/testsuite/log/
tmi2/v21.7a10/testsuite/u/
tmi2/v21.7a10/tmp/
/*
 * sprintf.c v1.05 for LPMud 3.0.52
 *
 * An implementation of (s)printf() for LPC, with quite a few
 * extensions (note that as no floating point exists, some parameters
 * have slightly different meaning or restrictions to "standard"
 * (s)printf.)  Implemented by Lynscar (Sean A Reith).
 * 2/28/93: float support for MudOS added by jacques/blackthorn
 *
 * This version supports the following as modifiers:
 *  " "   pad positive integers with a space.
 *  "+"   pad positive integers with a plus sign.
 *  "-"   left adjusted within field size.
 *        NB: std (s)printf() defaults to right justification, which is
 *            unnatural in the context of a mainly string based language
 *            but has been retained for "compatability" ;)
 *  "|"   centered within field size.
 *  "="   column mode if strings are greater than field size.  this is only
 *        meaningful with strings, all other types ignore
 *        this.  columns are auto-magically word wrapped.
 *  "#"   table mode, print a list of '\n' separated 'words' in a
 *        table within the field size.  only meaningful with strings.
 *   n    specifies the field size, a '*' specifies to use the corresponding
 *        arg as the field size.  if n is prepended with a zero, then is padded
 *        zeros, else it is padded with spaces (or specified pad string).
 *  "."n  presision of n, simple strings truncate after this (if presision is
 *        greater than field size, then field size = presision), tables use
 *        presision to specify the number of columns (if presision not specified
 *        then tables calculate a best fit), all other types ignore this.
 *  ":"n  n specifies the fs _and_ the presision, if n is prepended by a zero
 *        then it is padded with zeros instead of spaces.
 *  "@"   the argument is an array.  the corresponding format_info (minus the
 *        "@") is applyed to each element of the array.
 *  "'X'" The char(s) between the single-quotes are used to pad to field
 *        size (defaults to space) (if both a zero (in front of field
 *        size) and a pad string are specified, the one specified second
 *        overrules).  NOTE:  to include "'" in the pad string, you must
 *        use "\\'" (as the backslash has to be escaped past the
 *        interpreter), similarly, to include "\" requires "\\\\".
 * The following are the possible type specifiers.
 *  "%"   in which case no arguments are interpreted, and a "%" is inserted, and
 *        all modifiers are ignored.
 *  "O"   the argument is an LPC datatype.
 *  "s"   the argument is a string.
 *  "d"   the integer arg is printed in decimal.
 *  "i"   as d.
 *  "f"   floating point value.
 *  "c"   the integer arg is to be printed as a character.
 *  "o"   the integer arg is printed in octal.
 *  "x"   the integer arg is printed in hex.
 *  "X"   the integer arg is printed in hex (in capitals).
 */

#include "std.h"
#include "lpc_incl.h"
#include "efuns_incl.h"
#include "simul_efun.h"
#include "ignore.h"
#include "lex.h"
#include "stralloc.h"

/*
 * If this #define is defined then error messages are returned,
 * otherwise error() is called (ie: A "wrongness in the fabric...")
 */
#define RETURN_ERROR_MESSAGES

#if defined(F_SPRINTF) || defined(F_PRINTF)

typedef unsigned int format_info;

/*
 * Format of format_info:
 *   00000000 0000xxxx : argument type:
 *				0000 : type not found yet;
 *				0001 : error type not found;
 *				0010 : percent sign, null argument;
 *				0011 : LPC datatype;
 *				0100 : string;
 *				1000 : integer;
 *				1001 : char;
 *				1010 : octal;
 *				1011 : hex;
 *				1100 : HEX;
 *				1101 : float;
 *   00000000 00xx0000 : justification:
 *				00 : right;
 *				01 : centre;
 *				10 : left;
 *   00000000 xx000000 : positive pad char:
 *				00 : none;
 *				01 : ' ';
 *				10 : '+';
 *   0000000x 00000000 : array mode?
 *   000000x0 00000000 : column mode?
 *   00000x00 00000000 : table mode?
 */

#define INFO_T 0xF
#define INFO_T_ERROR 0x1
#define INFO_T_NULL 0x2
#define INFO_T_LPC 0x3
#define INFO_T_STRING 0x4
#define INFO_T_INT 0x8
#define INFO_T_CHAR 0x9
#define INFO_T_OCT 0xA
#define INFO_T_HEX 0xB
#define INFO_T_C_HEX 0xC
#define INFO_T_FLOAT 0xD

#define INFO_J 0x30
#define INFO_J_CENTRE 0x10
#define INFO_J_LEFT 0x20

#define INFO_PP 0xC0
#define INFO_PP_SPACE 0x40
#define INFO_PP_PLUS 0x80

#define INFO_ARRAY 0x100
#define INFO_COLS 0x200
#define INFO_TABLE 0x400

#define ERROR(x) LONGJMP(error_jmp, x)
#define SPRINTF_ERROR(x)  { if (clean) { free_svalue(clean, "sprintf error");\
				  FREE(clean); clean = 0;  } ERROR(x); }

#define ERR_BUFF_OVERFLOW	0x1	/* buffer overflowed */
#define ERR_TO_FEW_ARGS		0x2	/* more arguments spec'ed than passed */
#define ERR_INVALID_STAR	0x3	/* invalid arg to * */
#define ERR_PRES_EXPECTED	0x4	/* expected presision not found */
#define ERR_INVALID_FORMAT_STR	0x5	/* error in format string */
#define ERR_INCORRECT_ARG_S	0x6	/* invalid arg to %s */
#define ERR_CST_REQUIRES_FS	0x7	/* field size not given for c/t */
#define ERR_BAD_INT_TYPE	0x8	/* bad integer type... */
#define ERR_UNDEFINED_TYPE	0x9	/* undefined type found */
#define ERR_QUOTE_EXPECTED	0xA	/* expected ' not found */
#define ERR_UNEXPECTED_EOS	0xB	/* fs terminated unexpectedly */
#define ERR_NULL_PS		0xC	/* pad string is null */
#define ERR_ARRAY_EXPECTED      0xD	/* Yep!  You guessed it. */
#define ERR_RECOVERY_ONLY       0xE	/* err msg already done...just
					 * recover */

#define ADD_CHAR(x) {\
  outbuf_addchar(&obuff, x);\
  if (startignore) \
    if (x == '^') \
      inignore = !inignore; \
    else \
      curpos += 2*!inignore; \
  else \
    if (x == '%') \
      startignore = 1; \
    else \
      curpos += !inignore; \
  if (obuff.real_size == USHRT_MAX) ERROR(ERR_BUFF_OVERFLOW); \
}

#define M_ADD_CHAR(x) {\
  outbuf_addchar(&obuff, x);\
  if (startignore) \
    if (x == '^') \
      inignore = !inignore; \
    else \
      curpos += 2*!inignore; \
  else \
    if (x == '%') \
      startignore = 1; \
    else \
      curpos += !inignore; \
  if (obuff.real_size == USHRT_MAX) { SPRINTF_ERROR(ERR_BUFF_OVERFLOW); } \
}

#define T_ADD_CHAR(x) {\
  outbuf_addchar(&obuff, x);\
  if (startignore) \
    if (x == '^') \
      inignore = !inignore; \
    else \
      curpos += 2*!inignore; \
  else \
    if (x == '%') \
      startignore = 1; \
    else \
      curpos += !inignore; \
  if (obuff.real_size == USHRT_MAX) obuff.real_size--; \
}

#define GET_NEXT_ARG {\
  if (++arg >= argc) ERROR(ERR_TO_FEW_ARGS); \
  carg = (argv+arg);\
}

#define SAVE_CHAR(pointer) {\
  savechars *new;\
  new = ALLOCATE(savechars, TAG_TEMPORARY, "SAVE_CHAR"); \
  new->what = *(pointer);\
  new->where = pointer;\
  new->next = saves;\
  saves = new;\
}

/*
 * list of characters to restore before exiting.
 */
typedef struct SaveChars {
    char what;
    char *where;
    struct SaveChars *next;
}         savechars;

typedef struct ColumnSlashTable {
    union CSTData {
	char *col;		/* column data */
	char **tab;		/* table data */
    }       d;			/* d == data */
    unsigned short int nocols;	/* number of columns in table *sigh* */
    char *pad;
    unsigned int start;		/* starting cursor position */
    unsigned int size;		/* column/table width */
    int pres;			/* presision */
    format_info info;		/* formatting data */
    struct ColumnSlashTable *next;
}                cst;		/* Columns Slash Tables */

static char buff[LARGEST_PRINTABLE_STRING];/* buffer for returned errors */
static outbuffer_t obuff;
static unsigned int curpos;	/* cursor position */
static unsigned int inignore;	/* are we not counting these characters */
static unsigned int startignore;/* we found the first char... now for next */
static jmp_buf error_jmp;	/* LONGJMP() buffer for error catching */

static void numadd PROT((outbuffer_t *, int num));
static void add_indent PROT((outbuffer_t *, int indent));
static int ignorestrlen PROT((char *str));
static void add_justified PROT((char *str, char *pad, int fs, format_info finfo, short int trailing));
static int add_column PROT((cst ** column, short int trailing));
static int add_table PROT((cst ** table, short int trailing));

static void numadd P2(outbuffer_t *, outbuf, int, num)
{
    int i, num_l,		/* length of num as a string */
        nve;			/* true if num negative */
    int space;
    int chop;
    char *p;

    if (num < 0) {
	/* Beek: yes, it's possible for num < 0, and num * -1 < 0. */
	/* Beek: This shouldn't be a hardcoded const (assumes int is 4 bytes)*/
	num = (num * -1) & 0x7fffffff;
	nve = 1;
    } else
	nve = 0;
    for (i = num / 10, num_l = nve + 1; i; i /= 10, num_l++);
    if ((space = outbuf_extend(outbuf, num_l))) {
	chop = num_l - space;
	while (chop--) 
	    num /= 10; /* lose that last digits that got chopped */
	p = outbuf->buffer + outbuf->real_size;
	outbuf->real_size += space;
	p[space] = 0;
	if (nve) {
	    *p++ = '-';
	    space--;
	}
	while (space--) {
	    p[space] = (num % 10) + '0';
	    num /= 10;
	}
    }
}				/* end of numadd() */

static void add_indent P2(outbuffer_t *, outbuf, int, indent)
{
    int l;
    
    if ((l = outbuf_extend(outbuf, indent))) {
	memset(outbuf->buffer + outbuf->real_size, ' ', l);
	*(outbuf->buffer + outbuf->real_size + l) = 0;
	outbuf->real_size += l;
    }
}

/*
 * Converts any LPC datatype into an arbitrary string format
 * and returns a pointer to this string.
 * Scary number of parameters for a recursive function.
 */
void svalue_to_string P5(svalue_t *, obj, outbuffer_t *, outbuf, int, indent, int, trailing, int, indent2)
{
    int i;

    /* prevent an infinite recursion on self-referential structures */
    if (indent > 200) {
	FREE_MSTR(outbuf->buffer);
	error("structure too deep to print.\n");
	return;
    }
    if (!indent2)
	add_indent(outbuf, indent);
    switch (obj->type) {
    case T_INVALID:
	outbuf_add(outbuf, "T_INVALID");
	break;
    case T_LVALUE:
	outbuf_add(outbuf, "lvalue: ");
	svalue_to_string(obj->u.lvalue, outbuf, indent + 2, trailing, 0);
	break;
    case T_NUMBER:
	numadd(outbuf, obj->u.number);
	break;
    case T_REAL:
	outbuf_addv(outbuf, "%g", obj->u.real);
	break;
    case T_STRING:
	outbuf_add(outbuf, "\"");
	outbuf_add(outbuf, obj->u.string);
	outbuf_add(outbuf, "\"");
	break;
    case T_CLASS:
	{
	    int n = obj->u.arr->size;
	    outbuf_add(outbuf, "CLASS( ");
	    numadd(outbuf, n);
	    outbuf_add(outbuf, n == 1 ? " element\n" : " elements\n");
	    for (i = 0; i < (obj->u.arr->size) - 1; i++)
		svalue_to_string(&(obj->u.arr->item[i]), outbuf,
				 indent + 2, 1, 0);
	    svalue_to_string(&(obj->u.arr->item[i]), outbuf, 
			     indent + 2, 0, 0);
	    outbuf_add(outbuf, "\n");
	    add_indent(outbuf, indent);
	    outbuf_add(outbuf, " )");
	    break;
	}
    case T_ARRAY:
	if (!(obj->u.arr->size)) {
	    outbuf_add(outbuf, "({ })");
	} else {
	    outbuf_add(outbuf, "({ /* sizeof() == ");
	    numadd(outbuf, obj->u.arr->size);
	    outbuf_add(outbuf, " */\n");
	    for (i = 0; i < (obj->u.arr->size) - 1; i++)
		svalue_to_string(&(obj->u.arr->item[i]), outbuf, indent + 2, 1, 0);
	    svalue_to_string(&(obj->u.arr->item[i]), outbuf, indent + 2, 0, 0);
	    outbuf_add(outbuf, "\n");
	    add_indent(outbuf, indent);
	    outbuf_add(outbuf, "})");
	}
	break;
    case T_BUFFER:
	outbuf_add(outbuf, "<buffer>");
	break;
    case T_FUNCTION:
	{
	    svalue_t tmp;
	    tmp.type = T_ARRAY;

	    outbuf_add(outbuf, "(: ");
	    switch (obj->u.fp->hdr.type) {
	    case FP_LOCAL | FP_NOT_BINDABLE:
		outbuf_add(outbuf,
		       obj->u.fp->hdr.owner->prog->functions[obj->u.fp->f.local.index].name);
		break;
	    case FP_SIMUL:
		outbuf_add(outbuf, simuls[obj->u.fp->f.simul.index]->name);
		break;
	    case FP_FUNCTIONAL:
	    case FP_FUNCTIONAL | FP_NOT_BINDABLE:
		{
		    char buf[10];
		    int n = obj->u.fp->f.functional.num_arg;
		    
		    outbuf_add(outbuf, "<code>(");
		    for (i=1; i < n; i++) {
			sprintf(buf, "$%i, ", i);
			outbuf_add(outbuf, buf);
		    }
		    if (n) {
			sprintf(buf, "$%i", n);
			outbuf_add(outbuf, buf);
		    }
		    outbuf_add(outbuf, ")");
		    break;
		}
	    case FP_EFUN:
		{
		    int i;
		    i = obj->u.fp->f.efun.index;
		    outbuf_add(outbuf, instrs[i].name);
		    break;
		}
	    }
	    if (obj->u.fp->hdr.args) {
		for (i=0; i<obj->u.fp->hdr.args->size; i++) {
		    outbuf_add(outbuf, ", ");
		    svalue_to_string(&(obj->u.fp->hdr.args->item[i]), outbuf, indent, 0, 0);
		}
	    } 
	}
	outbuf_add(outbuf, " :)");
	break;
    case T_MAPPING:
	if (!(obj->u.map->count)) {
	    outbuf_add(outbuf, "([ ])");
	} else {
	    outbuf_add(outbuf, "([ /* sizeof() == ");
	    numadd(outbuf, obj->u.map->count);
	    outbuf_add(outbuf, " */\n");
	    for (i = 0; i <= (int) (obj->u.map->table_size); i++) {
		mapping_node_t *elm;

		for (elm = obj->u.map->table[i]; elm; elm = elm->next) {
		    svalue_to_string(&(elm->values[0]), outbuf, indent + 2, 0, 0);
		    outbuf_add(outbuf, " : ");
		    svalue_to_string(&(elm->values[1]), outbuf, indent + 4, 1, 1);
		}
	    }
	    add_indent(outbuf, indent);
	    outbuf_add(outbuf, "])");
	}
	break;
    case T_OBJECT:
	{
	    svalue_t *temp;

	    if (obj->u.ob->flags & O_DESTRUCTED) {
		numadd(outbuf, 0);
		break;
	    }

	    outbuf_add(outbuf, obj->u.ob->name);
	    push_object(obj->u.ob);
	    temp = safe_apply_master_ob(APPLY_OBJECT_NAME, 1);
	    if (temp && temp != (svalue_t *) -1 && (temp->type == T_STRING)) {
		outbuf_add(outbuf, " (\"");
		outbuf_add(outbuf, temp->u.string);
		outbuf_add(outbuf, "\")");
	    }
	    break;
	}
    default:
	outbuf_add(outbuf, "!ERROR: GARBAGE SVALUE!");
    }				/* end of switch (obj->type) */
    if (trailing)
	outbuf_add(outbuf, ",\n");
}				/* end of svalue_to_string() */

/* The ignore strlen is so that the pading will work with our wonderful
 * ansi colour stuff.  bing onwards.  This was hacked in by Pinkfish
 * so you may hold me responsible if you wish
 *
 * Beek: Can I press charges?
 */
static int ignorestrlen P1(char *, str)
{
    int len = 0, inignore = 0, first = 0;

    while (*str++) {
	if (first) {
	    if (*str == IGNORE_C2)
		inignore = !inignore;
	    len += !inignore;
	    first = 0;
	}
	if (*str == IGNORE_C1)
	    first = 1;
	else
	    len += !inignore;
    }
    return len;
}

/*
 * Adds the string "str" to the buff after justifying it within "fs".
 * "trailing" is a flag which is set if trailing justification is to be done.
 * "str" is unmodified.  trailing is, of course, ignored in the case
 * of right justification.
 */
/* In the following actually use T_ADD_CHAR (truncating if length is too
 * long :) )   It's better to be consistent to free clean maybe, but that
 * requires passing it to add_justified. It's even better to rewrite it
 * - volunteers? :)
 *
 * Sym
 */
static void add_justified P5(char *, str, char *, pad, int, fs, format_info, finfo, short int, trailing)
{
    int i, len, len2;

    len = strlen(str);
    len2 = ignorestrlen(str);
    switch (finfo & INFO_J) {
    case INFO_J_LEFT:
	for (i = 0; i < len; i++)
	    T_ADD_CHAR(str[i]);
	fs -= len;
	len = strlen(pad);
	if (trailing)
	    for (i = 0; fs > 0; i++, fs--) {
		if (pad[i % len] == '\\')
		    i++;
		T_ADD_CHAR(pad[i % len]);
	    }
	break;
    case INFO_J_CENTRE:{
	    int j, l;

	    l = strlen(pad);
	    if (!l) {
/* Irk! */
		l = 1;
		pad = " ";
	    }
	    j = (fs - len2) / 2 + (fs - len2) % 2;
	    for (i = 0; i < j; i++) {
		if (pad[i % l] == '\\') {
		    i++;
		    j++;
		}
		T_ADD_CHAR(pad[i % l]);
	    }
	    for (i = 0; i < len; i++)
		T_ADD_CHAR(str[i]);
	    j = (fs - len2) / 2;
	    if (trailing)
		for (i = 0; i < j; i++) {
		    if (pad[i % l] == '\\') {
			i++;
			j++;
		    }
		    T_ADD_CHAR(pad[i % l]);
		}
	    break;
	}
    default:{			/* std (s)printf defaults to right
				 * justification */
	    int l;

	    fs -= len2;
	    l = strlen(pad);
	    for (i = 0; i < fs; i++) {
		if (pad[i % l] == '\\') {
		    i++;
		    fs++;
		}
		T_ADD_CHAR(pad[i % l]);
	    }
	    for (i = 0; i < len; i++)
		T_ADD_CHAR(str[i]);
	}
    }
}				/* end of add_justified() */

/*
 * Adds "column" to the buffer.
 * Returns 0 is column not finished.
 * Returns 1 if column completed.
 * Returns 2 if column completed has a \n at the end.
 */
static int add_column P2(cst **, column, short int, trailing)
{
    register unsigned int done, off = 0, inadd_off = 0, first_ig = 0;
    unsigned int save;
    static char tmp_buf[2049];	/* hmm, is this always enough? */
    int ret;

#define COL (*column)
#define COL_D (COL->d.col)

    for (done = 0; ((done - off) < COL->pres) && COL_D[done] && (COL_D[done] != '\n'); done++) {
	if (first_ig) {
	    if (COL_D[done] == IGNORE_C2) {
		inadd_off = !inadd_off;
		off += 2;
	    } else
		done--;
	    first_ig = 0;
	} else if (COL_D[done] == IGNORE_C1)
	    first_ig = 1;
	else
	    off += inadd_off;
    }
    if (COL_D[done] && (COL_D[done] != '\n')) {
	save = done;
	for (; done && (COL_D[done] != ' '); done--);
	/*
	 * handle larger than column size words...
	 */
	if (!done)
	    done = save;
    }
    strncpy(tmp_buf, COL_D, done);
    tmp_buf[done] = '\0';
/*
  this commented block and the one below would sometimes try to write to
  a constant string (ie, the one returned by f_range()), causing a crash.
  changed it to strncpy()...sigh  -bobf/Blackthorn
  save = COL_D[done];
  COL_D[done] = '\0';
*/
    add_justified(tmp_buf, COL->pad, COL->size, COL->info,
		  (trailing || (COL->next)));
/*
  COL_D[done] = save;
*/
    COL_D += done;
    ret = 1;
    if (*COL_D == '\n') {
	COL_D++;
	ret = 2;
    }
    /*
     * if the next character is a NULL then take this column out of
     * the list.
     */
    if (!(*COL_D)) {
	cst *temp;

	temp = COL->next;
	FREE((char *) COL);
	COL = temp;
	return ret;
    }
    return 0;
}				/* end of add_column() */

/*
 * Adds "table" to the buffer.
 * Returns 0 if table not completed.
 * Returns 1 if table completed.
 */
static int add_table P2(cst **, table, short int, trailing)
{
    char save;
    register unsigned int done, i;

#define TAB (*table)
#define TAB_D (TAB->d.tab[i])

    for (i = 0; i < TAB->nocols && TAB_D; i++) {
	for (done = 0; (TAB_D[done]) && (TAB_D[done] != '\n'); done++);
	save = TAB_D[done];
	TAB_D[done] = '\0';
	add_justified(TAB_D, TAB->pad, TAB->size, TAB->info,
		      (trailing || (i < TAB->nocols - 1) || (TAB->next)));
	TAB_D[done] = save;
	TAB_D += done;		/* inc'ed next line ... */
	if (!(*TAB_D) || !(*(++TAB_D)))
	    TAB_D = 0;
    }
    if (trailing && i < TAB->nocols)
	for (; i < TAB->nocols; i++)
	    for (done = 0; done < TAB->size; done++)
		ADD_CHAR(' ');
    if (!TAB->d.tab[0]) {
	cst *temp;

	temp = TAB->next;
	if (TAB->d.tab) FREE((char *)(TAB->d.tab));
	FREE((char *) TAB);
	TAB = temp;
	return 1;
    }
    return 0;
}				/* end of add_table() */

/*
 * THE (s)printf() function.
 * It returns a pointer to it's internal buffer (or a string in the text
 * segment) thus, the string must be copied if it has to survive after
 * this function is called again, or if it's going to be modified (esp.
 * if it risks being free()ed).
 */
char *string_print_formatted P3(char *, format_str, int, argc, svalue_t *, argv)
{
    format_info finfo;
    savechars *saves = 0;	/* chars to restore */
    cst *csts;			/* list of columns/tables to be done */
    svalue_t *carg;	/* current arg */
    VOLATILE unsigned int nelemno = 0;	/* next offset into array */
    unsigned int fpos;		/* position in format_str */
    VOLATILE SIGNED int arg = 0;	/* current arg number */
    unsigned int fs;		/* field size */
    int pres;			/* presision */
    unsigned int i;
    char *pad;			/* fs pad string */
    char *retvalue;
    
    /* free anything that is sitting around here */
    if (obuff.buffer)
	FREE_MSTR(obuff.buffer);
    outbuf_zero(&obuff);

#ifdef cray
    if (SETJMP(error_jmp)) {	/* the cray setjmp is braindead */
	char *err;

	i = -1;
#else
    if ((i = SETJMP(error_jmp))) {	/* error handling */
	char *err;

#endif

/*
 * Must restore format_str before we exit. /Oros 930902
 */

	while (saves) {
	    savechars *tmp;

	    *(saves->where) = saves->what;
	    tmp = saves;
	    saves = saves->next;
	    FREE((char *) tmp);
	}

	switch (i) {
	case ERR_BUFF_OVERFLOW:
	    err = "BUFF_SIZE overflowed...";
	    break;
	case ERR_TO_FEW_ARGS:
	    err = "More arguments specified than passed.";
	    break;
	case ERR_INVALID_STAR:
	    err = "Incorrect argument type to *.";
	    break;
	case ERR_PRES_EXPECTED:
	    err = "Expected presision not found.";
	    break;
	case ERR_INVALID_FORMAT_STR:
	    err = "Error in format string.";
	    break;
	case ERR_INCORRECT_ARG_S:
	    err = "Incorrect argument to type %s.";
	    break;
	case ERR_CST_REQUIRES_FS:
	    err = "Column/table mode requires a field size.";
	    break;
	case ERR_BAD_INT_TYPE:
	    err = "!feature - bad integer type!";
	    break;
	case ERR_UNDEFINED_TYPE:
	    err = "!feature - undefined type!";
	    break;
	case ERR_QUOTE_EXPECTED:
	    err = "Quote expected in format string.";
	    break;
	case ERR_UNEXPECTED_EOS:
	    err = "Unexpected end of format string.";
	    break;
	case ERR_NULL_PS:
	    err = "Null pad string specified.";
	    break;
	case ERR_ARRAY_EXPECTED:
	    err = "Array expected.";
	    break;
	case ERR_RECOVERY_ONLY:
	    return string_copy(buff, "sprintf error");
	default:
#ifdef RETURN_ERROR_MESSAGES
	    sprintf(buff,
	      "ERROR: (s)printf(): !feature - undefined error 0x%X !\n", i);
	    debug_message("Program:%s File: %s: %s", current_prog->name,
		    get_line_number_if_any(), buff);
	    debug_message("%s", buff);
	    if (current_object) {
		debug_message("program: %s, object: %s, file: %s\n",
			      current_prog ? current_prog->name : "",
			      current_object->name,
			      get_line_number_if_any());
	    }
	    return string_copy(buff, "sprintf error");
#else
	    error("ERROR: (s)printf(): !feature - undefined error 0x%X !\n", i);
#endif				/* RETURN_ERROR_MESSAGES */
	}			/* end of switch */
#ifdef RETURN_ERROR_MESSAGES
	sprintf(buff, "ERROR: (s)printf(): %s (arg %u)\n", err, arg);
	debug_message("Program %s File: %s: %s", current_prog->name,
		get_line_number_if_any(), buff);
	debug_message("%s", buff);
	if (current_object) {
	    debug_message("program: %s, object: %s, file: %s\n",
			  current_prog ? current_prog->name : "",
			  current_object->name,
			  get_line_number_if_any());
	}
	return string_copy(buff, "sprintf error");
#else
	error("ERROR: (s)printf(): %s (arg: %d)\n", err, arg);
#endif				/* RETURN_ERROR_MESSAGES */
    }
    arg = -1;
    curpos = 0;
    inignore = 0;
    startignore = 0;
    csts = 0;
    saves = 0;
    for (fpos = 0; 1; fpos++) {
	if ((format_str[fpos] == '\n') || (!format_str[fpos])) {
	    int column_stat = 0;

	    if (!csts) {
		if (!format_str[fpos])
		    break;
		ADD_CHAR('\n');
		curpos = 0;
		inignore = 0;
		startignore = 0;
		continue;
	    }
	    ADD_CHAR('\n');
	    curpos = 0;
	    inignore = 0;
	    startignore = 0;
	    while (csts) {
		cst **temp;

		temp = &csts;
		while (*temp) {
		    if ((*temp)->info & INFO_COLS) {
			if (*((*temp)->d.col - 1) != '\n')
			    while (*((*temp)->d.col) == ' ')
				(*temp)->d.col++;
			for (i = curpos; i < (*temp)->start; i++)
			    ADD_CHAR(' ');
			column_stat = add_column(temp, 0);
			if (!column_stat)
			    temp = &((*temp)->next);
		    } else {
			for (i = curpos; i < (*temp)->start; i++)
			    ADD_CHAR(' ');
			if (!add_table(temp, 0))
			    temp = &((*temp)->next);
		    }
		}		/* of while (*temp) */
		if (csts || format_str[fpos] == '\n')
		    ADD_CHAR('\n');
		inignore = 0;
		startignore = 0;
		curpos = 0;
	    }			/* of while (csts) */
	    if (column_stat == 2)
		ADD_CHAR('\n');
	    if (!format_str[fpos])
		break;
	    continue;
	}
	if (format_str[fpos] == '%') {
	    if (format_str[fpos + 1] == '%') {
		ADD_CHAR('%');
		fpos++;
		continue;
	    }
	    GET_NEXT_ARG;
	    fs = 0;
	    pres = 0;
	    pad = " ";
	    finfo = 0;
	    for (fpos++; !(finfo & INFO_T); fpos++) {
		if (!format_str[fpos]) {
		    finfo |= INFO_T_ERROR;
		    break;
		}
		if (((format_str[fpos] >= '0') && (format_str[fpos] <= '9'))
		    || (format_str[fpos] == '*')) {
		    if (pres == -1) {	/* then looking for pres */
			if (format_str[fpos] == '*') {
			    if (carg->type != T_NUMBER)
				ERROR(ERR_INVALID_STAR);
			    pres = carg->u.number;
			    GET_NEXT_ARG;
			    continue;
			}
			pres = format_str[fpos] - '0';
			for (fpos++;
			     (format_str[fpos] >= '0') && (format_str[fpos] <= '9'); fpos++) {
			    pres = pres * 10 + format_str[fpos] - '0';
			}
		    } else {	/* then is fs (and maybe pres) */
			if ((format_str[fpos] == '0') && (((format_str[fpos + 1] >= '1')
							   && (format_str[fpos + 1] <= '9')) || (format_str[fpos + 1] == '*')))
			    pad = "0";
			else {
			    if (format_str[fpos] == '*') {
				if (carg->type != T_NUMBER)
				    ERROR(ERR_INVALID_STAR);
				fs = carg->u.number;
				if (pres == -2)
				    pres = fs;	/* colon */
				GET_NEXT_ARG;
				continue;
			    }
			    fs = format_str[fpos] - '0';
			}
			for (fpos++;
			     (format_str[fpos] >= '0') && (format_str[fpos] <= '9'); fpos++) {
			    fs = fs * 10 + format_str[fpos] - '0';
			}
			if (pres == -2) {	/* colon */
			    pres = fs;
			}
		    }
		    fpos--;	/* bout to get incremented */
		    continue;
		}
		switch (format_str[fpos]) {
		case ' ':
		    finfo |= INFO_PP_SPACE;
		    break;
		case '+':
		    finfo |= INFO_PP_PLUS;
		    break;
		case '-':
		    finfo |= INFO_J_LEFT;
		    break;
		case '|':
		    finfo |= INFO_J_CENTRE;
		    break;
		case '@':
		    finfo |= INFO_ARRAY;
		    break;
		case '=':
		    finfo |= INFO_COLS;
		    break;
		case '#':
		    finfo |= INFO_TABLE;
		    break;
		case '.':
		    pres = -1;
		    break;
		case ':':
		    pres = -2;
		    break;
		case '%':
		    finfo |= INFO_T_NULL;
		    break;	/* never reached */
		case 'O':
		    finfo |= INFO_T_LPC;
		    break;
		case 's':
		    finfo |= INFO_T_STRING;
		    break;
		case 'd':
		    finfo |= INFO_T_INT;
		    break;
		case 'i':
		    finfo |= INFO_T_INT;
		    break;
		case 'f':
		    finfo |= INFO_T_FLOAT;
		    break;
		case 'c':
		    finfo |= INFO_T_CHAR;
		    break;
		case 'o':
		    finfo |= INFO_T_OCT;
		    break;
		case 'x':
		    finfo |= INFO_T_HEX;
		    break;
		case 'X':
		    finfo |= INFO_T_C_HEX;
		    break;
		case '\'':
		    pad = &(format_str[++fpos]);
		    while (1) {
			if (!format_str[fpos])
			    ERROR(ERR_UNEXPECTED_EOS);
			if (format_str[fpos] == '\\') {
			    fpos += 2;
			    continue;
			}
			if (format_str[fpos] == '\'') {
			    if (format_str + fpos == pad)
				ERROR(ERR_NULL_PS);
			    SAVE_CHAR(format_str + fpos);
			    format_str[fpos] = '\0';
			    break;
			}
			fpos++;
		    }
		    break;
		default:
		    finfo |= INFO_T_ERROR;
		}
	    }			/* end of for () */
	    if (pres < 0)
		ERROR(ERR_PRES_EXPECTED);
	    /*
	     * now handle the different arg types...
	     */
	    if (finfo & INFO_ARRAY) {
		if (carg->type != T_ARRAY)
		    ERROR(ERR_ARRAY_EXPECTED);
		if (carg->u.arr->size == 0) {
		    fpos--;	/* 'bout to get incremented */
		    continue;
		}
		carg = (argv + arg)->u.arr->item;
		nelemno = 1;	/* next element number */
	    }
	    while (1) {
	        svalue_t *clean = 0;

		if ((finfo & INFO_T) == INFO_T_LPC) {
		    outbuffer_t outbuf;
		    
		    outbuf_zero(&outbuf);
		    svalue_to_string(carg, &outbuf, 0, 0, 0);
		    outbuf_fix(&outbuf);

		    /* This is gross - Beek */
		    clean = ALLOCATE(svalue_t, TAG_TEMPORARY, "string_print: 1");
		    clean->type = T_STRING;
		    clean->subtype = STRING_MALLOC;
		    clean->u.string = outbuf.buffer;
		    carg = clean;
		    finfo ^= INFO_T_LPC;
		    finfo |= INFO_T_STRING;
		}
		if ((finfo & INFO_T) == INFO_T_ERROR) {
		    ERROR(ERR_INVALID_FORMAT_STR);
		} else if ((finfo & INFO_T) == INFO_T_NULL) {
		    /* never reached... */
		    fprintf(stderr, "%s: (s)printf: INFO_T_NULL.... found.\n",
			    current_object->name);
		    M_ADD_CHAR('%');
		} else if ((finfo & INFO_T) == INFO_T_STRING) {
		    int slen;


		    /*
		     * %s null handling added 930709 by Luke Mewburn
		     * <zak@rmit.oz.au>
		     */
		    if (carg->type == T_NUMBER && carg->u.number == 0) {
			clean = ALLOCATE(svalue_t, TAG_TEMPORARY, "string_print: z1");
			clean->type = T_STRING;
			clean->subtype = STRING_MALLOC;
			clean->u.string = string_copy(NULL_MSG, "sprintf NULL");
			carg = clean;
		    }
		    if (carg->type != T_STRING) {
			SPRINTF_ERROR(ERR_INCORRECT_ARG_S);
		    }
		    slen = SVALUE_STRLEN(carg);
		    if ((finfo & INFO_COLS) || (finfo & INFO_TABLE)) {
			cst **temp;

			if (!fs) {
			    SPRINTF_ERROR(ERR_CST_REQUIRES_FS);
			}

			temp = &csts;
			while (*temp)
			    temp = &((*temp)->next);
			if (finfo & INFO_COLS) {
			    *temp = ALLOCATE(cst, TAG_TEMPORARY, "string_print: 3");
			    (*temp)->next = 0;
			    (*temp)->d.col = carg->u.string;
			    (*temp)->pad = pad;
			    (*temp)->size = fs;
			    (*temp)->pres = (pres) ? pres : fs;
			    (*temp)->info = finfo;
			    (*temp)->start = curpos;
			    if ((add_column(temp, (((format_str[fpos] != '\n')
						    && (format_str[fpos] != '\0')) || ((finfo & INFO_ARRAY)
			    && (nelemno < (argv + arg)->u.arr->size)))) == 2)
				&& !format_str[fpos]) {
				M_ADD_CHAR('\n');
			    }
			} else {/* (finfo & INFO_TABLE) */
			    unsigned int n, len, max_len;

#define TABLE carg->u.string
			    (*temp) = ALLOCATE(cst, TAG_TEMPORARY, "string_print: 4");
			    (*temp)->d.tab = 0;
			    (*temp)->pad = pad;
			    (*temp)->info = finfo;
			    (*temp)->start = curpos;
			    (*temp)->next = 0;
			    max_len = len = 0;
			    n = 1;
			    for (i = 0; TABLE[i]; i++) {
				if (TABLE[i] == '\n') {
				    if (len > max_len)
					max_len = len;
				    len = 0;
				    if (TABLE[i + 1])
					n++;
				    continue;
				}
				len++;
			    }
			    if (pres) {
				(*temp)->size = fs / pres;
			    } else {
				if (len > max_len)
				    max_len = len;	/* the null terminated word */
				pres = fs / (max_len + 2); /* at least two
							    * separating spaces */
				if (!pres)
				    pres = 1;
				(*temp)->size = fs / pres;
			    }
			    len = n / pres;	/* length of average column */
			    if (n < pres)
				pres = n;
			    if (len * pres < n)
				len++;
			    if (len > 1 && n % pres)
				pres -= (pres - n % pres) / len;
			    (*temp)->d.tab = CALLOCATE(pres, char *, TAG_TEMPORARY, "string_print: 5");
			    (*temp)->nocols = pres;	/* heavy sigh */
			    (*temp)->d.tab[0] = TABLE;
			    if (pres == 1)
				goto add_table_now;
			    i = 1;	/* the next column number */
			    n = 0;	/* the current "word" number in this
					 * column */
			    for (fs = 0; TABLE[fs]; fs++) {	/* throwing away fs... */
				if (TABLE[fs] == '\n') {
				    if (++n >= len) {
					SAVE_CHAR(((TABLE) + fs));
					TABLE[fs] = '\0';
					(*temp)->d.tab[i++] = TABLE + fs + 1;
					if (i >= pres)
					    goto add_table_now;
					n = 0;
				    }
				}
			    }
			  add_table_now:
			    add_table(temp, (((format_str[fpos] != '\n')
					      && (format_str[fpos] != '\0')) || ((finfo & INFO_ARRAY)
				&& (nelemno < (argv + arg)->u.arr->size))));
			}
		    } else {	/* not column or table */
			if (pres && pres < slen) {
			    if (carg != clean)
				SAVE_CHAR(((carg->u.string) + pres));
			    carg->u.string[pres] = '\0';
			    slen = pres;
			}
			if (fs && fs > slen) {
			    add_justified(carg->u.string, pad, fs, finfo,
					  (((format_str[fpos] != '\n') && (format_str[fpos] != '\0'))
					   || ((finfo & INFO_ARRAY) && (nelemno < (argv + arg)->u.arr->size)))
				       || carg->u.string[slen - 1] != '\n');
			} else {
			    for (i = 0; i < slen; i++)
				M_ADD_CHAR(carg->u.string[i]);
			}
		    }
		} else if (finfo & INFO_T_INT) {	/* one of the integer
							 * types */
		    char cheat[8];
		    char temp[100];

		    *cheat = '%';
		    i = 1;
		    switch (finfo & INFO_PP) {
		    case INFO_PP_SPACE:
			cheat[i++] = ' ';
			break;
		    case INFO_PP_PLUS:
			cheat[i++] = '+';
			break;
		    }
		    switch (finfo & INFO_T) {
		    case INFO_T_INT:
			cheat[i++] = 'd';
			break;
		    case INFO_T_FLOAT:
			cheat[i++] = 'f';
			break;
		    case INFO_T_CHAR:
			cheat[i++] = 'c';
			break;
		    case INFO_T_OCT:
			cheat[i++] = 'o';
			break;
		    case INFO_T_HEX:
			cheat[i++] = 'x';
			break;
		    case INFO_T_C_HEX:
			cheat[i++] = 'X';
			break;
		    default:
			ERROR(ERR_BAD_INT_TYPE);
		    }
		    if ((cheat[i - 1] == 'f' && carg->type != T_REAL) || (cheat[i - 1] != 'f' && carg->type != T_NUMBER)) {
#ifdef RETURN_ERROR_MESSAGES
			sprintf(buff,
				"ERROR: (s)printf(): Incorrect argument type to %%%c. (arg: %u)\n",
				cheat[i - 1], arg);
			fprintf(stderr, "Program %s File: %s: %s", current_prog->name,
				get_line_number_if_any(), buff);
			debug_message("%s", buff);
			if (current_object) {
			    debug_message("program: %s, object: %s, file: %s\n",
				     current_prog ? current_prog->name : "",
					  current_object->name,
					  get_line_number_if_any());
			}
			ERROR(ERR_RECOVERY_ONLY);
#else
			error("ERROR: (s)printf(): Incorrect argument type to %%%c.\n",
			      cheat[i - 1]);
#endif				/* RETURN_ERROR_MESSAGES */
		    }
		    cheat[i] = '\0';
		    /*
		     * Floatingpoint output fixed by hasse@solace.hsh.se
		     * (Kniggit@VikingMud)
		     */
		    if (carg->type == T_REAL) {
			if (pres) {
			    sprintf(cheat, "%%.%df", pres);
			    pres = 0;
			}
			sprintf(temp, cheat, carg->u.real);
		    } else
			sprintf(temp, cheat, carg->u.number);
		    {
			int tmpl = strlen(temp);

			if (pres && tmpl > pres)
			    temp[tmpl = pres] = '\0';	/* well.... */
			if (tmpl < fs)
			    add_justified(temp, pad, fs, finfo,
					  (((format_str[fpos] != '\n') && (format_str[fpos] != '\0'))
					   || ((finfo & INFO_ARRAY) && (nelemno < (argv + arg)->u.arr->size))));
			else
			    for (i = 0; i < tmpl; i++)
				ADD_CHAR(temp[i]);
		    }
		} else		/* type not found */
		    ERROR(ERR_UNDEFINED_TYPE);
		if (clean) {
		    free_svalue(clean, "string_print_formatted");
		    FREE(clean);
		    clean = 0;
		}
		if (!(finfo & INFO_ARRAY))
		    break;
		if (nelemno >= (argv + arg)->u.arr->size)
		    break;
		carg = (argv + arg)->u.arr->item + nelemno++;
	    }			/* end of while (1) */
	    fpos--;		/* bout to get incremented */
	    continue;
	}
	ADD_CHAR(format_str[fpos]);
    }				/* end of for (fpos=0; 1; fpos++) */

    while (saves) {
	savechars *tmp;

	*(saves->where) = saves->what;
	tmp = saves;
	saves = saves->next;
	FREE((char *) tmp);
    }

    outbuf_fix(&obuff);
    retvalue = obuff.buffer;
    obuff.buffer = 0;
    return retvalue;
}				/* end of string_print_formatted() */

#endif				/* defined(F_SPRINTF) || defined(F_PRINTF) */