/*
 * 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 <stdio.h>
#include <string.h>
#include <setjmp.h>
#include <sys/types.h>

#include "config.h"
#include "lint.h"
#include "opcodes.h"
#include "stdio.h"
#include "interpret.h"
#include "mapping.h"
#include "object.h"
#include "sent.h"
#include "ignore.h"
#include "exec.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)

extern char *xalloc(), *string_copy();
extern INLINE void free_svalue PROT((struct svalue *));

extern struct object *current_object;

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 BUFF_SIZE LARGEST_PRINTABLE_STRING

#define ERROR(x) LONGJMP(error_jmp, 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 ADD_CHAR(x) {\
  buff[bpos] = x;\
  if (startignore) \
    if (buff[bpos++] == '^') \
      inignore = !inignore; \
    else \
      curpos += 2*!inignore; \
  else \
    if (buff[bpos++] == '%') \
      startignore = 1; \
    else \
      curpos += !inignore; \
  if (bpos>BUFF_SIZE) ERROR(ERR_BUFF_OVERFLOW); \
}

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

#define SAVE_CHAR(pointer) {\
  savechars *new;\
  new = (savechars *)DXALLOC(sizeof(savechars), 107, "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[BUFF_SIZE];	/* buffer for returned string */
unsigned int bpos;		/* position in buff */
unsigned int curpos;		/* cursor position */
unsigned int inignore;          /* are we not counting these characters */
unsigned int startignore;       /* we found the first char... now for next */
jmp_buf error_jmp;              /* LONGJMP() buffer for error catching */

/*
 * Probably should make this a #define...
 */
void stradd(dst, size, add)
  char **dst, *add;
  int *size;
{
  int i;

  if ((i = (strlen(*dst) + strlen(add))) >= *size) {
    *size += i + 1;
    *dst = (char *)DREALLOC(*dst, *size, 108, "stradd");
  }
  strcat(*dst, add);
} /* end of stradd() */

void numadd(dst, size, num)
  char **dst;
  int *size, num;
{
  int i,
  num_l, /* length of num as a string */
  nve;   /* true if num negative */

  if (num < 0) { num *= -1; nve=1; } else nve=0;
  for (i=num/10, num_l=nve+1; i; i /= 10, num_l++) ;
  i = strlen(*dst); /* i = length of constructed string so far */
  if ((i + num_l) >= *size) {
    *size += i + num_l + 2;
    *dst = (char *)DREALLOC(*dst, *size, 109, "stradd");
  }
  (*dst)[i+num_l] = '\0';
  if (nve) (*dst)[i] = '-'; else i--;
  for (num_l-=nve; num_l; num_l--, num /= 10) (*dst)[i+num_l] = (num%10) + '0';
} /* end of numadd() */

void floatadd(dst, size, flt)
     char **dst;
     int *size;
     double flt;
{
  int i;
  int flt_l; /* length of float as a string */
  char buf[80];

  sprintf(buf,"%g",flt);
  flt_l = strlen(buf) + 1;

  i = strlen(*dst); /* i = length of constructed string so far */
  if ((i + flt_l) >= *size) {
    *size += i + flt_l + 2;
    *dst = (char *)DREALLOC(*dst, *size, 109, "stradd");
  }
  sprintf(*dst, "%s", buf);
} /* end of floatadd() */

/*
 * This is a function purely because stradd() is, to keep same param
 * passing...
 */
void add_indent(dst, size, indent)
  char **dst;
  int *size, indent;
{
  int i;

  i = strlen(*dst);
  if ((i + indent) >= *size) {
    *size += i + indent + 1;
    *dst = (char *)DREALLOC(*dst, *size, 110, "add_indent");
  }
  for (;indent;indent--) (*dst)[i++] = ' ';
  (*dst)[i] = '\0';
}

/*
 * 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(obj, str, size, indent, trailing, indent2)
  struct svalue *obj;
  char **str;
  int size, indent, trailing, indent2;
{
  int i;

  /* prevent an infinite recursion on self-referential structures */
  if (indent > 200) {
     error("structure too deep to print.\n");
     return;
  }
  if (!indent2)
    add_indent(str, &size, indent);
  switch (obj->type) {
    case T_INVALID:
      stradd(str,&size,"T_INVALID");
      break;
    case T_LVALUE:
      stradd(str, &size, "lvalue: ");
      svalue_to_string(obj->u.lvalue, str, size, indent+2, trailing, 0);
      break;
    case T_NUMBER:
      numadd(str, &size, obj->u.number);
      break;
    case T_REAL:
      floatadd(str, &size, obj->u.real);
      break;
    case T_STRING:
      stradd(str, &size, "\"");
      stradd(str, &size, obj->u.string);
      stradd(str, &size, "\"");
      break;
    case T_POINTER:
      if (!(obj->u.vec->size)) {
        stradd(str, &size, "({ })");
      } else {
        stradd(str, &size, "({ /* sizeof() == ");
        numadd(str, &size, obj->u.vec->size);
        stradd(str, &size, " */\n");
        for (i=0; i<(obj->u.vec->size)-1; i++)
          svalue_to_string(&(obj->u.vec->item[i]), str, size, indent+2, 1, 0);
        svalue_to_string(&(obj->u.vec->item[i]), str, size, indent+2, 0, 0);
        stradd(str, &size, "\n");
        add_indent(str, &size, indent);
        stradd(str, &size, "})");
      }
      break;
    case T_FUNCTION:
      stradd(str, &size, "(: ");
      svalue_to_string(&(obj->u.fp->obj), str, size, indent+2, trailing, 0);
      stradd(str, &size, ", ");
      svalue_to_string(&(obj->u.fp->fun), str, size, indent+2, trailing, 0);
      stradd(str, &size, " :)");
      break;
    case T_MAPPING:
      if (!(obj->u.map->count)) {
        stradd(str, &size, "([ ])");
      } else {
        stradd(str, &size, "([ /* sizeof() == ");
        numadd(str, &size, obj->u.map->count);
        stradd(str, &size, " */\n");
        for (i=0;i<(int)(obj->u.map->table_size);i++) {
          struct node *elm;

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

      stradd(str, &size, obj->u.ob->name);
      push_object(obj->u.ob);
      temp = apply_master_ob("object_name", 1);
      if (temp && (temp->type == T_STRING)) {
        stradd(str, &size, " (\"");
        stradd(str, &size, temp->u.string);
        stradd(str, &size, "\")");
      }
      /*
       * These flags aren't that useful...
       *
      if (obj->u.ob->flags & O_HEART_BEAT) stradd(str,&size," (hb)");
      if (obj->u.ob->flags & O_IS_WIZARD) stradd(str,&size," (wiz)");
      if (obj->u.ob->flags & O_ENABLE_COMMANDS) stradd(str,&size," (enabled)");
      if (obj->u.ob->flags & O_CLONE) stradd(str,&size," (clone)");
      if (obj->u.ob->flags & O_DESTRUCTED) stradd(str,&size," (destructed)");
      if (obj->u.ob->flags & O_SWAPPED) stradd(str,&size," (swapped)");
      if (obj->u.ob->flags & O_ONCE_INTERACTIVE) stradd(str,&size," (x-activ)");
      if (obj->u.ob->flags & O_APPROVED) stradd(str,&size," (ok)");
      if (obj->u.ob->flags & O_RESET_STATE) stradd(str,&size," (reset)");
      if (obj->u.ob->flags & O_WILL_CLEAN_UP) stradd(str,&size," (clean up)");
       */
      break;
    }
    default:
      stradd(str, &size, "!ERROR: GARBAGE SVALUE!");
  } /* end of switch (obj->type) */
  if (trailing) stradd(str, &size, ",\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
 */
int ignorestrlen(str)
  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.
 */
void add_justified(str, pad, fs, finfo, trailing)
  char *str, *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++) ADD_CHAR(str[i]);
      fs -= len;
      len = strlen(pad);
      if (trailing) for (i=0; fs>0; i++, fs--) {
        if (pad[i%len] == '\\') i++;
        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++; }
        ADD_CHAR(pad[i%l]);
      }
      for (i=0; i<len; i++) ADD_CHAR(str[i]);
      j = (fs - len2)/2;
      if (trailing) for (i=0; i<j; i++) {
        if (pad[i%l] == '\\') { i++; j++; }
        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++; }
        ADD_CHAR(pad[i%l]);
      }
      for (i=0; i<len; i++) 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.
 */
int add_column(column, trailing)
  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? */
#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; /* inc'ed below ... */
  /*
   * if this or the next character is a NULL then take this column out
   * of the list.
   */
  if (!(*COL_D) || !(*(++COL_D))) {
    cst *temp;
    int ret;

    if (*(COL_D-1) == '\n') ret = 2; else ret = 1;
    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.
 */
int add_table(table, trailing)
  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;
    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(format_str, argc, argv)
  char *format_str;
  int argc;
  struct svalue *argv;
{
  format_info finfo;
  savechars *saves;	/* chars to restore */
  cst *csts;		/* list of columns/tables to be done */
  struct svalue *carg;	/* current arg */
  VOLATILE unsigned int nelemno = 0;	/* next offset into array */
  unsigned int fpos;	/* position in format_str */
  VOLATILE unsigned int arg = 0;	/* current arg number */
  unsigned int fs;	/* field size */
  int pres;		/* presision */
  unsigned int i;
  char *pad;		/* fs pad string */

#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;
      default:
#ifdef RETURN_ERROR_MESSAGES
        debug_message("%s", 
          "ERROR: (s)printf(): !feature - undefined error 0x%X !\n", i);
        if (current_object) {
	     debug_message("program: %s, object: %s line %d\n",
		    current_prog ? current_prog->name : "",
		    current_object->name,
		    get_line_number_if_any());
        }
        sprintf(buff,
          "ERROR: (s)printf(): !feature - undefined error 0x%X !\n", i);
        fprintf(stderr, "%s:%d: %s", current_object->name,
                                     get_line_number_if_any(), buff);
        return buff;
#else
        error("ERROR: (s)printf(): !feature - undefined error 0x%X !\n", i);
#endif /* RETURN_ERROR_MESSAGES */
    }
#ifdef RETURN_ERROR_MESSAGES
    sprintf(buff, "ERROR: (s)printf(): %s in arg %u\n", err, arg);
    fprintf(stderr, "%s:%d: %s", current_object->name,
                                 get_line_number_if_any(), buff);
    debug_message("%s", "ERROR: (s)printf(): %s in arg %u\n", err, arg);
    if (current_object) {
        debug_message("program: %s, object: %s line %d\n",
		    current_prog ? current_prog->name : "",
		    current_object->name,
		    get_line_number_if_any());
    }
    return buff;
#else
    error("ERROR: (s)printf(): %s in arg %d\n", err, arg);
#endif /* RETURN_ERROR_MESSAGES */
  }
  arg = -1;
  bpos = 0;
  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_POINTER)
           ERROR(ERR_ARRAY_EXPECTED);
        if (carg->u.vec->size == 0) {
          fpos--; /* 'bout to get incremented */
          continue;
        }
        carg = (argv+arg)->u.vec->item;
        nelemno = 1; /* next element number */
      }
      while (1) {
        struct svalue *clean = 0;

        if ((finfo & INFO_T) == INFO_T_LPC) {
          clean = (struct svalue *)
			DXALLOC(sizeof(struct svalue), 111, "string_print: 1");
          clean->type = T_STRING;
          clean->subtype = STRING_MALLOC;
          clean->u.string = (char *)DXALLOC(500, 112, "string_print: 2");
          clean->u.string[0] = '\0';
          svalue_to_string(carg, &(clean->u.string), 500, 0, 0, 0);
          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);
          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 = (struct svalue *)
           DXALLOC(sizeof(struct svalue), 121, "string_print: z1");
         clean->type = T_STRING;
         clean->subtype = STRING_MALLOC;
         clean->u.string = (char *)DXALLOC(sizeof(NULL_MSG),
                         122, "string_print: z2");
         strcpy(clean->u.string, NULL_MSG);
         carg = clean;
     }

          if (carg->type != T_STRING)
            ERROR(ERR_INCORRECT_ARG_S);
          slen = strlen(carg->u.string);
          if ((finfo & INFO_COLS) || (finfo & INFO_TABLE)) {
            cst **temp;

            if (!fs)
              ERROR(ERR_CST_REQUIRES_FS);

            temp = &csts;
            while (*temp) temp = &((*temp)->next);
            if (finfo & INFO_COLS) {
              *temp = (cst *)DXALLOC(sizeof(cst), 113, "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.vec->size)))) == 2)
                  && !format_str[fpos]) {
                ADD_CHAR('\n');
              }
            } else { /* (finfo & INFO_TABLE) */
#undef max
              unsigned int n, len, max;

#define TABLE carg->u.string
              (*temp) = (cst *)DXALLOC(sizeof(cst), 114, "string_print: 4");
              (*temp)->pad = pad;
              (*temp)->info = finfo;
              (*temp)->start = curpos;
              (*temp)->next = 0;
              max = len = 0;
              n = 1;
              for (i=0; TABLE[i]; i++) {
                if (TABLE[i] == '\n') {
                  if (len > max) max = len;
                  len = 0;
                  if (TABLE[i+1]) n++;
                  continue;
                }
                len++;
              }
              if (pres) {
                (*temp)->size = fs/pres;
              } else {
                if (len > max) max = len; /* the null terminated word */
                pres = fs/(max+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 = (char **)
				DXALLOC(pres*sizeof(char *), 115, "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.vec->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.vec->size)))
          || carg->u.string[slen-1] != '\n');
            } else {
              for (i=0; i<slen; i++) 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.\n",
              cheat[i-1]);
            fprintf(stderr, "%s:%d: %s", current_object->name,
                                         get_line_number_if_any(), buff);
            return buff;
#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[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.vec->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);
        if (!(finfo & INFO_ARRAY)) break;
        if (nelemno >= (argv+arg)->u.vec->size) break;
        carg = (argv+arg)->u.vec->item+nelemno++;
      } /* end of while (1) */
      fpos--; /* bout to get incremented */
      continue;
    }
    ADD_CHAR(format_str[fpos]);
  } /* end of for (fpos=0; 1; fpos++) */
  ADD_CHAR('\0');
  while (saves) {
    savechars *tmp;
    *(saves->where) = saves->what;
    tmp = saves;
    saves = saves->next;
    FREE((char *)tmp);
  }
  return buff;
} /* end of string_print_formatted() */

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