mirror of
git://git.psyced.org/git/psyclpc
synced 2024-08-15 03:20:16 +00:00
2488 lines
80 KiB
C
2488 lines
80 KiB
C
/*---------------------------------------------------------------------------
|
|
* sprintf for LPMud
|
|
*
|
|
* Implemented and put into the public domain by Lynscar (Sean A Reith).
|
|
*---------------------------------------------------------------------------
|
|
* An implementation of (s)printf() for LPC, with quite a few
|
|
* extensions (some parameters have slightly different meaning or
|
|
* restrictions to "standard" (s)printf.)
|
|
*
|
|
* 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.
|
|
* "Q" the argument is an LPC datatype, strings are printed in LPC notation.
|
|
* "s" the argument is a string.
|
|
* "d" the integer arg is printed in decimal.
|
|
* "i" as d.
|
|
* "b" the integer arg is printed in binary (negative numbers in 2's
|
|
* compliment)
|
|
* "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).
|
|
* "e","E","f","F","g","G"
|
|
* floating point formatting like in C.
|
|
* "^" prints "%^" for compatibility with terminal_colour() strings.
|
|
* No modifiers are allowed.
|
|
*
|
|
* This version supports the following as modifiers:
|
|
* " " pad positive integers with a space.
|
|
* "+" pad positive integers with a plus sign.
|
|
* "-" left aligned within field size.
|
|
* NB: std (s)printf() defaults to right alignment, which is
|
|
* unnatural in the context of a mainly string based language
|
|
* but has been retained for "compatability" ;)
|
|
* "|" centered within field size.
|
|
* "$" justified to field size. Makes sense only for strings and columns
|
|
* of strings.
|
|
* "=" column mode if strings are greater than field size. this is only
|
|
* meaningful with strings, all other types are ignored. The strings
|
|
* are broken into the size of 'precision', and the last line is
|
|
* padded to have a length of 'fs'.
|
|
* "#" with strings: table mode - print a list of '\n' separated 'words' in a
|
|
* compact table within the field size.
|
|
* with %O/%Q: compact mode - omit most whitespace and shorten
|
|
* identifiers
|
|
* n specifies the field size, a '*' specifies to use the corresponding
|
|
* arg as the field size. If n is prepended with a zero, then the field
|
|
* is printed with leading zeros.
|
|
* "."n precision of n, simple strings truncate after this (if precision is
|
|
* greater than field size, then field size = precision), tables use
|
|
* precision to specify the number of columns (if precision not specified
|
|
* then tables calculate a best fit), all other types ignore this.
|
|
* ":"n n specifies the fs _and_ the precision, 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).
|
|
* To include "'" in the pad string, you must use "\\'" (as the
|
|
* backslash has to be escaped past the interpreter), similarly, to
|
|
* include "\" requires "\\\\".
|
|
*---------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "driver.h"
|
|
#include "typedefs.h"
|
|
|
|
#include "my-alloca.h"
|
|
#include <stdio.h>
|
|
#include <setjmp.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "sprintf.h"
|
|
|
|
#include "actions.h"
|
|
#include "array.h"
|
|
#include "closure.h"
|
|
#include "comm.h"
|
|
#include "interpret.h"
|
|
#include "main.h"
|
|
#include "mapping.h"
|
|
#include "mstrings.h"
|
|
#include "object.h"
|
|
#include "ptrtable.h"
|
|
#include "random.h"
|
|
#include "sent.h"
|
|
#include "simulate.h"
|
|
#include "simul_efun.h"
|
|
#include "stdstrings.h"
|
|
#ifdef USE_STRUCTS
|
|
#include "structs.h"
|
|
#endif /* USE_STRUCTS */
|
|
#include "svalue.h"
|
|
#include "swap.h"
|
|
#include "xalloc.h"
|
|
|
|
/* If this #define is defined then error messages are returned,
|
|
* otherwise errorf() is called (ie: A "wrongness in the fabric...")
|
|
*/
|
|
#undef RETURN_ERROR_MESSAGES
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/* Format of format_info:
|
|
*
|
|
* 00000000 0000xxxx : argument type INFO_T:
|
|
* 0000 : type not found yet;
|
|
* 0001 : error type not found;
|
|
* 0010 : percent sign, null argument;
|
|
* 0011 : LPC datatype;
|
|
* 0100 : LPC datatype, quoted;
|
|
* 0101 : string;
|
|
* 0110 : integer
|
|
* 0111 : float
|
|
* 00000000 00xx0000 : alignment INFO_A:
|
|
* 00 : right;
|
|
* 01 : centre;
|
|
* 10 : left;
|
|
* 11 : justified;
|
|
* 00000000 xx000000 : positive pad char INFO_PP:
|
|
* 00 : none;
|
|
* 01 : ' ';
|
|
* 10 : '+';
|
|
* 0000000x 00000000 : array mode (INFO_ARRAY)?
|
|
* 000000x0 00000000 : column mode (INFO_COLS)?
|
|
* 00000x00 00000000 : table mode (INFO_TABLE)?
|
|
*
|
|
* 0000x000 00000000 : field is to be left-padded with zero
|
|
* 000x0000 00000000 : pad-spaces before a newline are kept
|
|
*/
|
|
|
|
typedef unsigned int format_info;
|
|
|
|
enum format_info_t {
|
|
INFO_T = 0xF,
|
|
INFO_T_ERROR = 0x1,
|
|
INFO_T_NULL = 0x2,
|
|
INFO_T_LPC = 0x3,
|
|
INFO_T_QLPC = 0x4,
|
|
INFO_T_STRING = 0x5,
|
|
INFO_T_INT = 0x6,
|
|
INFO_T_FLOAT = 0x7,
|
|
|
|
INFO_A = 0x30, /* Right alignment */
|
|
INFO_A_CENTRE = 0x10,
|
|
INFO_A_LEFT = 0x20,
|
|
INFO_A_JUSTIFY = 0x30,
|
|
|
|
INFO_PP = 0xC0,
|
|
INFO_PP_SPACE = 0x40,
|
|
INFO_PP_PLUS = 0x80,
|
|
|
|
INFO_ARRAY = 0x100,
|
|
INFO_COLS = 0x200,
|
|
INFO_TABLE = 0x400,
|
|
|
|
INFO_PS_ZERO = 0x800,
|
|
INFO_PS_KEEP = 0x1000,
|
|
};
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
#define BUFF_SIZE 0x20000 /* 128 KByte */
|
|
/* Max size of returned string.
|
|
*/
|
|
|
|
/* The error handling */
|
|
|
|
enum format_err {
|
|
ERR_ID_NUMBER = 0xFFFF, /* Mask for the error number */
|
|
ERR_ARGUMENT = 0xFFFF0000, /* Mask for the arg number */
|
|
|
|
ERR_BUFF_OVERFLOW = 0x1, /* buffer overflowed */
|
|
ERR_TO_FEW_ARGS = 0x2, /* more arguments spec'ed than passed */
|
|
ERR_INVALID_STAR = 0x3, /* invalid arg to * */
|
|
ERR_PREC_EXPECTED = 0x4, /* expected precision not found */
|
|
ERR_INVALID_FORMAT_STR = 0x5, /* error in format string */
|
|
ERR_INCORRECT_ARG = 0x6, /* invalid arg to %[idcxXs] */
|
|
ERR_CST_REQUIRES_FS = 0x7, /* field size not given for c/t */
|
|
ERR_UNDEFINED_TYPE = 0x8, /* undefined type found */
|
|
ERR_QUOTE_EXPECTED = 0x9, /* expected ' not found */
|
|
ERR_UNEXPECTED_EOS = 0xA, /* fs terminated unexpectedly */
|
|
ERR_NULL_PS = 0xB, /* pad string is null */
|
|
ERR_ARRAY_EXPECTED = 0xC, /* array expected */
|
|
ERR_NOMEM = 0xD, /* Out of memory */
|
|
ERR_SIZE_OVERFLOW = 0xE, /* Fieldsize/precision numeric overflow */
|
|
};
|
|
|
|
#define ERROR(x) (longjmp(st->error_jmp, (x)))
|
|
|
|
#define ERROR1(e,a) ERROR((e) | (a)<<16)
|
|
#define EXTRACT_ERR_ARGUMENT(i) ((i)>>16)
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/* Types */
|
|
|
|
typedef struct SaveChars savechars;
|
|
typedef struct ColumnSlashTable cst;
|
|
typedef struct sprintf_buffer sprintf_buffer_t;
|
|
typedef struct stsf_locals stsf_locals_t;
|
|
typedef struct fmt_state fmt_state_t;
|
|
|
|
|
|
/* --- struct SaveChars: list of characters to restore before exiting.
|
|
*/
|
|
struct SaveChars
|
|
{
|
|
char what; /* Saved character */
|
|
char *where; /* Original position */
|
|
savechars *next;
|
|
};
|
|
|
|
|
|
/* --- struct ColumnSlashTable: data for one column or table
|
|
*
|
|
* All tables and columns in one line are kept in a linked
|
|
* list and added line-wise to the result string.
|
|
*/
|
|
|
|
struct ColumnSlashTable
|
|
{
|
|
union CSTData {
|
|
char *col; /* column data, possibly multiple lines */
|
|
char **tab; /* table data */
|
|
} d; /* d == data */
|
|
unsigned short nocols; /* number of columns in table *sigh* */
|
|
char *pad; /* the pad string */
|
|
size_t start; /* starting cursor position */
|
|
size_t size; /* column/table width */
|
|
int pres; /* precision */
|
|
format_info info; /* formatting data */
|
|
cst *next; /* next column structure */
|
|
};
|
|
|
|
/* --- struct sprintf_buffer: dynamic string buffer
|
|
*
|
|
* The structure implements a dynamic string buffer. The structure
|
|
* sits at the end of the allocated memory and knows where the
|
|
* char* pointing to the begin of the allocated memory is. This
|
|
* way all the user sees is a char[] which automagically happens
|
|
* to be of the right size.
|
|
*/
|
|
|
|
struct sprintf_buffer
|
|
{
|
|
/* char text[.size - sizeof(sprintf_buffer_t)]; */
|
|
#define BUF_TEXT(b) ((char *)(b))
|
|
int offset; /* Offset from .size to the first free byte
|
|
* (ie. a negative number).
|
|
*/
|
|
int size; /* Total size of the buffer */
|
|
char **start; /* Pointer to the string pointer */
|
|
};
|
|
|
|
|
|
/* --- struct stsf_locals: auxiliary structure
|
|
*
|
|
* The structure is used when printing a mapping to pass
|
|
* the essential data.
|
|
*/
|
|
|
|
struct stsf_locals
|
|
{
|
|
sprintf_buffer_t *spb; /* Target buffer */
|
|
int indent; /* Indentation */
|
|
int num_values; /* Mapping width */
|
|
Bool quote; /* TRUE: Quote strings */
|
|
Bool compact; /* TRUE: Compact output */
|
|
fmt_state_t *st; /* sprintf state */
|
|
};
|
|
|
|
/* --- struct fmt_state: status of the sprintf operation
|
|
*
|
|
* This structure contains several data items which the functions
|
|
* here use and modify while creating the result string.
|
|
* The structure is allocated by the string_print_formatted()
|
|
* in order to achieve re-entrancy.
|
|
*
|
|
* The canonic declaration for this structure as parameter
|
|
* is 'fmt_state_t *st', and is expected as such by some
|
|
* macros.
|
|
*/
|
|
|
|
struct fmt_state
|
|
{
|
|
savechars * saves; /* Characters to restore */
|
|
cst * csts; /* list of columns/tables to be done */
|
|
svalue_t clean; /* holds a temporary string */
|
|
char * tmp; /* holds a temporary string buffer */
|
|
char buff[BUFF_SIZE]; /* Buffer for returned string. */
|
|
size_t bpos; /* Position in buff. */
|
|
ssize_t sppos;
|
|
/* -1, or the buffer position of the first character of
|
|
* a trailing space padding. This is used to remove space
|
|
* padding at the end of lines.
|
|
*/
|
|
unsigned int line_start; /* Position where to start a line. */
|
|
jmp_buf error_jmp;
|
|
/* Error-exit context. In case of errors, the functions longjmp()
|
|
* directly back to the top function which then handles the error.
|
|
*/
|
|
struct pointer_table * ptable;
|
|
/* When printing svalue, this keeps track of arrays and mappings
|
|
* in order to catch recursions.
|
|
*/
|
|
int32 pointer_id;
|
|
/* Next ID to give to an array or mapping when printing svalues.
|
|
*/
|
|
};
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static Bool static_fmt_used = MY_FALSE;
|
|
static fmt_state_t static_fmt;
|
|
/* A reusable instance of fmt_state_t, to avoid repeated re-allocations
|
|
* (and subsequent large block fragmentation).
|
|
* Since sprintf() can be used recursively (through master::printf_obj_name)
|
|
* static_fmt_used acts as mutex. A recursive sprintf() call will then
|
|
* allocate a temporary fmt_state_t structure.
|
|
*/
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/* Forward declarations */
|
|
|
|
static sprintf_buffer_t *svalue_to_string(fmt_state_t *
|
|
, svalue_t *, sprintf_buffer_t *
|
|
, int, Bool, Bool, Bool, Bool);
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/* static helper functions */
|
|
static inline void ADD_CHAR(fmt_state_t *st, char x) {
|
|
if (st->bpos >= BUFF_SIZE) ERROR(ERR_BUFF_OVERFLOW);
|
|
if (x == '\n' && st->sppos != -1) st->bpos = st->sppos;
|
|
st->sppos = -1;
|
|
st->buff[st->bpos++] = x;
|
|
}
|
|
|
|
/* Add character <x> to the buffer.
|
|
*/
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static inline void ADD_STRN(fmt_state_t *st, const char *s, size_t n) {
|
|
if (st->bpos + n > BUFF_SIZE) ERROR(ERR_BUFF_OVERFLOW);
|
|
if (n >= 1 && s[0] == '\n' && st->sppos != -1) st->bpos = st->sppos;
|
|
st->sppos = -1;
|
|
memcpy(st->buff+st->bpos, s, n);
|
|
st->bpos += n;
|
|
}
|
|
|
|
/* Add the <n> characters from <s> to the buffer.
|
|
*/
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static inline void ADD_CHARN(fmt_state_t *st, char c, size_t n) {
|
|
/* n must not be negative! */
|
|
if (st->bpos + n > BUFF_SIZE) ERROR(ERR_BUFF_OVERFLOW);
|
|
if (n >= 1 && c == '\n' && st->sppos != -1) st->bpos = st->sppos;
|
|
st->sppos = -1;
|
|
memset(st->buff+st->bpos, c, n);
|
|
st->bpos += n;
|
|
}
|
|
|
|
/* Add character <c> <n>-times to the buffer.
|
|
*/
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static inline void ADD_PADDING(fmt_state_t *st, const char *pad, size_t N) {
|
|
int n = N;
|
|
|
|
if (!pad[1]) {
|
|
ADD_CHARN(st, *pad, n);
|
|
} else {
|
|
int i, l;
|
|
|
|
l = strlen(pad);
|
|
for (i=0; --n >= 0; ) {
|
|
if (pad[i] == '\\')
|
|
i++;
|
|
ADD_CHAR(st, pad[i]);
|
|
if (++i == l)
|
|
i = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Add the padding string <pad> to the buffer, repeatedly if necessary,
|
|
* yielding a total length of <N>.
|
|
*/
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static sprintf_buffer_t *
|
|
realloc_sprintf_buffer (fmt_state_t *st, sprintf_buffer_t *b)
|
|
|
|
/* Increase the size of buffer <b> and return the new buffer.
|
|
* At the time of call, a positive .offset determines how much more
|
|
* data is needed. If it's negative to begin with, the size is just
|
|
* doubled.
|
|
*/
|
|
|
|
{
|
|
int offset = b->offset;
|
|
int size = b->size;
|
|
char **start = b->start;
|
|
char *newstart;
|
|
|
|
/* Get more memory */
|
|
do {
|
|
if (size > BUFF_SIZE)
|
|
ERROR(ERR_BUFF_OVERFLOW);
|
|
offset -= size;
|
|
size *= 2;
|
|
newstart = rexalloc(*start, size);
|
|
if (!newstart)
|
|
ERROR(ERR_NOMEM);
|
|
*start = newstart;
|
|
} while (offset >= 0);
|
|
|
|
b = (sprintf_buffer_t*)(*start+size - sizeof(sprintf_buffer_t));
|
|
b->offset = offset;
|
|
b->size = size;
|
|
b->start = start;
|
|
return b;
|
|
} /* realloc_sprintf_buffer() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
straddn (fmt_state_t *st, sprintf_buffer_t **buffer, char *add, int len)
|
|
|
|
/* Add string <add> of <len> characters to the <buffer>.
|
|
*/
|
|
|
|
{
|
|
sprintf_buffer_t *b = *buffer;
|
|
int o;
|
|
|
|
o = b->offset;
|
|
if ( (b->offset = o + len) >= 0)
|
|
{
|
|
*buffer = b = realloc_sprintf_buffer(st, b);
|
|
o = b->offset - len;
|
|
}
|
|
memcpy(BUF_TEXT(b) + o, add, len);
|
|
} /* straddn() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
stradd (fmt_state_t *st, sprintf_buffer_t **buffer, char *add)
|
|
|
|
/* Add string <add> to the <buffer>.
|
|
* The function add_indent() intentionally matches our signature.
|
|
*/
|
|
|
|
{
|
|
straddn(st, buffer, add, strlen(add));
|
|
} /* stradd() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
numadd (fmt_state_t *st, sprintf_buffer_t **buffer, p_int num)
|
|
|
|
/* Add the <num>ber to the <buffer>.
|
|
*/
|
|
|
|
{
|
|
sprintf_buffer_t *b = *buffer;
|
|
int i, j;
|
|
Bool nve;
|
|
|
|
if (num < 0)
|
|
{
|
|
/* Negative number: remember that and make
|
|
* the number positive.
|
|
*/
|
|
if ( (num *= -1) < 0)
|
|
{
|
|
/* num == MININT: add <num>+1 to the buffer, the
|
|
* increment the last digit by one.
|
|
* Since all possible MIN values are powers of two,
|
|
* the last digit can never be 9.
|
|
*/
|
|
numadd(st, buffer, num+1);
|
|
b = *buffer;
|
|
BUF_TEXT(b)[b->offset - 1] += 1;
|
|
return;
|
|
}
|
|
nve = MY_TRUE;
|
|
}
|
|
else
|
|
nve = MY_FALSE;
|
|
|
|
/* Determine the number of digits required */
|
|
if (num <= 1)
|
|
j = 1;
|
|
else
|
|
j = (int) ceil(log10((double)num+1));
|
|
if (nve)
|
|
j++;
|
|
|
|
/* Get the memory */
|
|
i = b->offset;
|
|
if ((b->offset = i + j) >= 0) {
|
|
*buffer = b = realloc_sprintf_buffer(st, b);
|
|
i = b->offset - j;
|
|
}
|
|
|
|
BUF_TEXT(b)[i+j] = '\0'; /* Add terminator in advance */
|
|
|
|
/* '-' required? */
|
|
if (nve)
|
|
{
|
|
BUF_TEXT(b)[i] = '-';
|
|
j--;
|
|
}
|
|
else
|
|
i--;
|
|
|
|
/* Now store the number */
|
|
for (; j; j--, num /= 10)
|
|
BUF_TEXT(b)[i+j] = (num%10) + '0';
|
|
} /* num_add() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
add_indent (fmt_state_t *st, sprintf_buffer_t **buffer, int indent)
|
|
|
|
/* Add <indent> characters indentation to <buffer>.
|
|
* The function intentionally matches the signature of stradd().
|
|
*/
|
|
|
|
{
|
|
int i;
|
|
sprintf_buffer_t *b = *buffer;
|
|
char *p;
|
|
|
|
i = b->offset;
|
|
if ( (b->offset = i + indent) >= 0)
|
|
{
|
|
*buffer = b = realloc_sprintf_buffer(st, b);
|
|
i = b->offset - indent;
|
|
}
|
|
|
|
p = BUF_TEXT(b) + i;
|
|
for (;indent;indent--)
|
|
*p++ = ' ';
|
|
*p = '\0';
|
|
} /* add_indent() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
svalue_to_string_filter(svalue_t *key, svalue_t *data, void *extra)
|
|
|
|
/* Filter to add a mapping entry to the given sprintf_buffer.
|
|
* <extra> is a stsf_locals*.
|
|
*/
|
|
|
|
{
|
|
int i;
|
|
stsf_locals_t *locals = (stsf_locals_t *)extra;
|
|
char *delimiter = ":";
|
|
|
|
i = locals->num_values;
|
|
locals->spb =
|
|
svalue_to_string(locals->st, key, locals->spb, locals->indent, !i, locals->quote, locals->compact, MY_FALSE);
|
|
while (--i >= 0)
|
|
{
|
|
stradd(locals->st, &locals->spb, delimiter);
|
|
locals->spb = svalue_to_string(locals->st, data++, locals->spb, 1, !i, locals->quote, locals->compact, MY_FALSE);
|
|
delimiter = ";";
|
|
}
|
|
} /* svalue_to_string_filter() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static sprintf_buffer_t *
|
|
svalue_to_string ( fmt_state_t *st
|
|
, svalue_t *obj, sprintf_buffer_t *str
|
|
, int indent, Bool trailing, Bool quoteStrings
|
|
, Bool compact, Bool prefixed)
|
|
|
|
/* Print the value <obj> into the buffer <str> with indentation <indent>.
|
|
* If <trailing> is true, add ",\n" after the printed value.
|
|
* If <qoute> is true, special characters in strings are quoted LPC-style.
|
|
* If <compact> is true, a short output format is used.
|
|
* If <prefixed> is true, the caller has printed something ahead of this
|
|
* value, meaning that for the first line no indentation is required.
|
|
*
|
|
* Result is the (updated) string buffer.
|
|
* The function calls itself for recursive values.
|
|
*/
|
|
|
|
{
|
|
mp_int i;
|
|
|
|
if (!compact && !prefixed)
|
|
add_indent(st, &str, indent);
|
|
|
|
switch (obj->type)
|
|
{
|
|
case T_INVALID:
|
|
stradd(st, &str, "T_INVALID");
|
|
break;
|
|
|
|
case T_LVALUE:
|
|
stradd(st, &str, compact ? "l:" : "lvalue: ");
|
|
str = svalue_to_string(st, obj->u.lvalue, str, indent+2, trailing, quoteStrings, compact, MY_FALSE);
|
|
break;
|
|
|
|
case T_NUMBER:
|
|
numadd(st, &str, obj->u.number);
|
|
break;
|
|
|
|
case T_FLOAT:
|
|
{
|
|
char s[200]; /* TODO: Might be too small */
|
|
double d;
|
|
|
|
d = READ_DOUBLE(obj);
|
|
#ifdef HAVE_TRUNC
|
|
if (trunc(d) == d)
|
|
#else
|
|
if ((d >= 0.0 ? floor(d) : ceil(d)) == d)
|
|
#endif
|
|
sprintf(s, "%g.0", d );
|
|
else
|
|
sprintf(s, "%g", d );
|
|
stradd(st, &str, s);
|
|
break;
|
|
}
|
|
|
|
case T_STRING:
|
|
stradd(st, &str, "\"");
|
|
|
|
if (!quoteStrings)
|
|
{
|
|
straddn(st, &str, get_txt(obj->u.str), mstrsize(obj->u.str));
|
|
}
|
|
else
|
|
{
|
|
size_t len;
|
|
|
|
/* Compute the size of the result string */
|
|
for (len = 0, i = mstrsize(obj->u.str); i > 0; --i)
|
|
{
|
|
unsigned char c = (unsigned char) get_txt(obj->u.str)[i];
|
|
|
|
switch(c)
|
|
{
|
|
case '"':
|
|
case '\n':
|
|
case '\r':
|
|
case '\t':
|
|
case '\a':
|
|
case 0x1b:
|
|
case 0x08:
|
|
case 0x00:
|
|
case '\\':
|
|
len += 2; break;
|
|
default:
|
|
if (c >= 0x20 && c < 0x7F)
|
|
{
|
|
len++;
|
|
}
|
|
else
|
|
{
|
|
len += 4;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( len == mstrsize(obj->u.str) )
|
|
{
|
|
/* No special characters found */
|
|
stradd(st, &str, get_txt(obj->u.str));
|
|
}
|
|
else
|
|
{
|
|
char * tmpstr, *dest;
|
|
unsigned char *src;
|
|
|
|
/* Allocate the temporary string */
|
|
tmpstr = alloca(len+1);
|
|
|
|
src = (unsigned char *)get_txt(obj->u.str);
|
|
dest = tmpstr;
|
|
for (i = mstrsize(obj->u.str); i > 0; --i)
|
|
{
|
|
unsigned char c = *src++;
|
|
|
|
switch(c)
|
|
{
|
|
case '"': strcpy(dest, "\\\""); dest += 2; break;
|
|
case '\n': strcpy(dest, "\\n"); dest += 2; break;
|
|
case '\r': strcpy(dest, "\\r"); dest += 2; break;
|
|
case '\t': strcpy(dest, "\\t"); dest += 2; break;
|
|
case '\a': strcpy(dest, "\\a"); dest += 2; break;
|
|
case 0x1b: strcpy(dest, "\\e"); dest += 2; break;
|
|
case 0x08: strcpy(dest, "\\b"); dest += 2; break;
|
|
case 0x00: strcpy(dest, "\\0"); dest += 2; break;
|
|
case '\\': strcpy(dest, "\\\\"); dest += 2; break;
|
|
default:
|
|
if (c >= 0x20 && c < 0x7F)
|
|
{
|
|
*dest++ = (char)c;
|
|
}
|
|
else
|
|
{
|
|
static char hex[] = "0123456789abcdef";
|
|
*dest++ = '\\';
|
|
*dest++ = 'x';
|
|
*dest++ = hex[c >> 4];
|
|
*dest++ = hex[c & 0xf];
|
|
}
|
|
break;
|
|
}
|
|
} /* for() */
|
|
*dest = '\0';
|
|
|
|
stradd(st, &str, tmpstr);
|
|
}
|
|
|
|
}
|
|
|
|
stradd(st, &str, "\"");
|
|
break;
|
|
|
|
case T_QUOTED_ARRAY:
|
|
{
|
|
i = obj->x.quotes;
|
|
do {
|
|
stradd(st, &str, "\'");
|
|
} while (--i);
|
|
}
|
|
/* FALLTHROUGH */
|
|
|
|
case T_POINTER:
|
|
{
|
|
size_t size;
|
|
|
|
size = VEC_SIZE(obj->u.vec);
|
|
if (!size)
|
|
{
|
|
stradd(st, &str, compact ? "({})" : "({ })");
|
|
}
|
|
else
|
|
{
|
|
struct pointer_record *prec;
|
|
|
|
prec = find_add_pointer(st->ptable, obj->u.vec, MY_TRUE);
|
|
if (!prec->id_number)
|
|
{
|
|
/* New array */
|
|
prec->id_number = st->pointer_id++;
|
|
|
|
if (compact)
|
|
{
|
|
stradd(st, &str, "({#");
|
|
numadd(st, &str, prec->id_number);
|
|
stradd(st, &str, " ");
|
|
}
|
|
else
|
|
{
|
|
stradd(st, &str, "({ /* #");
|
|
numadd(st, &str, prec->id_number);
|
|
stradd(st, &str, ", size: ");
|
|
numadd(st, &str, size);
|
|
stradd(st, &str, " */\n");
|
|
}
|
|
for (i = 0; (size_t)i < size-1; i++)
|
|
{
|
|
str = svalue_to_string(st, &(obj->u.vec->item[i]), str, indent+2, MY_TRUE, quoteStrings, compact, MY_FALSE);
|
|
}
|
|
str = svalue_to_string(st, &(obj->u.vec->item[i]), str, indent+2, MY_FALSE, quoteStrings, compact, MY_FALSE);
|
|
if (!compact)
|
|
{
|
|
stradd(st, &str, "\n");
|
|
add_indent(st, &str, indent);
|
|
}
|
|
stradd(st, &str, "})");
|
|
}
|
|
else
|
|
{
|
|
/* Recursion! */
|
|
stradd(st, &str, compact ? "({#" : "({ #");
|
|
numadd(st, &str, prec->id_number);
|
|
stradd(st, &str, compact ? "})" : " })");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
#ifdef USE_STRUCTS
|
|
case T_STRUCT:
|
|
{
|
|
struct_t *strct = obj->u.strct;
|
|
size_t size;
|
|
struct pointer_record *prec;
|
|
|
|
size = struct_size(strct);
|
|
|
|
prec = find_add_pointer(st->ptable, strct, MY_TRUE);
|
|
if (!prec->id_number)
|
|
{
|
|
/* New array */
|
|
prec->id_number = st->pointer_id++;
|
|
|
|
if (compact)
|
|
{
|
|
stradd(st, &str, "(<'");
|
|
stradd(st, &str, get_txt(struct_unique_name(strct)));
|
|
stradd(st, &str, "'#");
|
|
numadd(st, &str, prec->id_number);
|
|
stradd(st, &str, " ");
|
|
}
|
|
else
|
|
{
|
|
stradd(st, &str, "(<'");
|
|
stradd(st, &str, get_txt(struct_unique_name(strct)));
|
|
stradd(st, &str, "' /* #");
|
|
numadd(st, &str, prec->id_number);
|
|
stradd(st, &str, ", size: ");
|
|
numadd(st, &str, size);
|
|
stradd(st, &str, " */");
|
|
}
|
|
if (size)
|
|
{
|
|
if (!compact)
|
|
stradd(st, &str, "\n");
|
|
for (i = 0; (size_t)i < size-1; i++)
|
|
{
|
|
if (!compact)
|
|
{
|
|
add_indent(st, &str, indent+2);
|
|
stradd(st, &str, "/* ");
|
|
stradd(st, &str, get_txt(strct->type->member[i].name));
|
|
stradd(st, &str, ": */ ");
|
|
}
|
|
str = svalue_to_string(st, &(strct->member[i]), str, indent+2, MY_TRUE, quoteStrings, compact, !compact);
|
|
}
|
|
if (!compact)
|
|
{
|
|
add_indent(st, &str, indent+2);
|
|
stradd(st, &str, "/* ");
|
|
stradd(st, &str, get_txt(strct->type->member[i].name));
|
|
stradd(st, &str, ": */ ");
|
|
}
|
|
str = svalue_to_string(st, &(strct->member[i]), str, indent+2, MY_FALSE, quoteStrings, compact, !compact);
|
|
if (!compact)
|
|
{
|
|
stradd(st, &str, "\n");
|
|
add_indent(st, &str, indent);
|
|
}
|
|
}
|
|
stradd(st, &str, ">)");
|
|
}
|
|
else
|
|
{
|
|
/* Recursion! */
|
|
stradd(st, &str, compact ? "(<#" : "(< #");
|
|
numadd(st, &str, prec->id_number);
|
|
stradd(st, &str, compact ? ">)" : " >)");
|
|
}
|
|
break;
|
|
}
|
|
#endif /* USE_STRUCTS */
|
|
|
|
case T_MAPPING:
|
|
{
|
|
struct stsf_locals locals;
|
|
struct pointer_record *prec;
|
|
|
|
prec = find_add_pointer(st->ptable, obj->u.map, MY_TRUE);
|
|
if (!prec->id_number)
|
|
{
|
|
/* New mapping */
|
|
prec->id_number = st->pointer_id++;
|
|
|
|
stradd(st, &str, compact ? "([#" : "([ /* #");
|
|
numadd(st, &str, prec->id_number);
|
|
stradd(st, &str, compact ? " " : " */\n");
|
|
|
|
locals.spb = str;
|
|
locals.indent = indent + 2;
|
|
locals.num_values = obj->u.map->num_values;
|
|
locals.st = st;
|
|
locals.quote = quoteStrings;
|
|
locals.compact = compact;
|
|
walk_mapping(obj->u.map, svalue_to_string_filter, &locals);
|
|
str = locals.spb;
|
|
if (!compact)
|
|
{
|
|
/* Remove the ',' from the trailing ',\n', if any */
|
|
if (BUF_TEXT(str)[str->offset - 1] == '\n'
|
|
&& BUF_TEXT(str)[str->offset - 2] == ','
|
|
)
|
|
{
|
|
BUF_TEXT(str)[str->offset - 2] = '\n';
|
|
str->offset--;
|
|
}
|
|
add_indent(st, &str, indent);
|
|
}
|
|
else
|
|
{
|
|
/* Remove the trailing, if any */
|
|
if (BUF_TEXT(str)[str->offset - 1] == ',')
|
|
str->offset--;
|
|
}
|
|
stradd(st, &str, "])");
|
|
}
|
|
else
|
|
{
|
|
/* Recursion! */
|
|
stradd(st, &str, compact ? "([#" : "([ #");
|
|
numadd(st, &str, prec->id_number);
|
|
stradd(st, &str, compact ? "])" : " ])");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case T_OBJECT:
|
|
{
|
|
svalue_t *temp;
|
|
|
|
if (obj->u.ob->flags & O_DESTRUCTED)
|
|
{
|
|
/* *obj might be a mapping key, thus we mustn't change it. */
|
|
stradd(st, &str,"0");
|
|
break;
|
|
}
|
|
if (!compat_mode)
|
|
stradd(st, &str, "/");
|
|
stradd(st, &str, get_txt(obj->u.ob->name));
|
|
if (!compact)
|
|
{
|
|
push_ref_object(inter_sp, obj->u.ob, "sprintf");
|
|
temp = apply_master(STR_PRINTF_OBJ_NAME, 1);
|
|
if (temp && (temp->type == T_STRING)) {
|
|
stradd(st, &str, " (\"");
|
|
stradd(st, &str, get_txt(temp->u.str));
|
|
stradd(st, &str, "\")");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case T_SYMBOL:
|
|
i = obj->x.quotes;
|
|
do {
|
|
stradd(st, &str, "\'");
|
|
} while (--i);
|
|
stradd(st, &str, get_txt(obj->u.str));
|
|
break;
|
|
|
|
case T_CLOSURE:
|
|
{
|
|
string_t * rc;
|
|
|
|
rc = closure_to_string(obj, compact);
|
|
stradd(st, &str, get_txt(rc));
|
|
free_mstring(rc);
|
|
|
|
break;
|
|
} /* case T_CLOSURE */
|
|
|
|
case T_CHAR_LVALUE:
|
|
{
|
|
char buf[2];
|
|
|
|
buf[0] = *obj->u.charp;
|
|
buf[1] = '\0';
|
|
stradd(st, &str, "'");
|
|
stradd(st, &str, buf);
|
|
stradd(st, &str, "'");
|
|
if (!compact)
|
|
{
|
|
stradd(st, &str, " (");
|
|
numadd(st, &str, buf[0] & 0xff);
|
|
stradd(st, &str, ")");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case T_PROTECTED_CHAR_LVALUE:
|
|
{
|
|
stradd(st, &str, compact ? "p char:" : "prot char: ");
|
|
str = svalue_to_string(st, obj->u.lvalue, str, indent+2, trailing, quoteStrings, compact, MY_FALSE);
|
|
break;
|
|
}
|
|
|
|
case T_STRING_RANGE_LVALUE:
|
|
case T_PROTECTED_STRING_RANGE_LVALUE:
|
|
{
|
|
if (obj->type == T_PROTECTED_STRING_RANGE_LVALUE)
|
|
stradd(st, &str, compact ? "p:" : "prot: ");
|
|
stradd(st, &str, "\"");
|
|
stradd(st, &str, get_txt(obj->u.str));
|
|
stradd(st, &str, "\"");
|
|
break;
|
|
}
|
|
|
|
case T_POINTER_RANGE_LVALUE:
|
|
case T_PROTECTED_POINTER_RANGE_LVALUE:
|
|
{
|
|
size_t size;
|
|
|
|
if (obj->type == T_PROTECTED_POINTER_RANGE_LVALUE)
|
|
stradd(st, &str, compact ? "p:" : "prot: ");
|
|
|
|
size = VEC_SIZE(obj->u.vec);
|
|
if (!size)
|
|
{
|
|
stradd(st, &str, compact ? "({})" : "({ })");
|
|
}
|
|
else
|
|
{
|
|
struct pointer_record *prec;
|
|
|
|
prec = find_add_pointer(st->ptable, obj->u.vec, MY_TRUE);
|
|
if (!prec->id_number)
|
|
{
|
|
/* New array */
|
|
prec->id_number = st->pointer_id++;
|
|
|
|
if (compact)
|
|
{
|
|
stradd(st, &str, "({#");
|
|
numadd(st, &str, prec->id_number);
|
|
stradd(st, &str, " ");
|
|
}
|
|
else
|
|
{
|
|
stradd(st, &str, "({ /* #");
|
|
numadd(st, &str, prec->id_number);
|
|
stradd(st, &str, ", size: ");
|
|
numadd(st, &str, size);
|
|
stradd(st, &str, " */\n");
|
|
}
|
|
for (i = 0; (size_t)i < size-1; i++)
|
|
str = svalue_to_string(st, &(obj->u.vec->item[i]), str, indent+2, MY_TRUE, quoteStrings, compact, MY_FALSE);
|
|
str = svalue_to_string(st, &(obj->u.vec->item[i]), str, indent+2, MY_FALSE, quoteStrings, compact, MY_FALSE);
|
|
if (!compact)
|
|
{
|
|
stradd(st, &str, "\n");
|
|
add_indent(st, &str, indent);
|
|
}
|
|
stradd(st, &str, "})");
|
|
}
|
|
else
|
|
{
|
|
/* Recursion! */
|
|
stradd(st, &str, compact ? "({#" : "({ #");
|
|
numadd(st, &str, prec->id_number);
|
|
stradd(st, &str, compact ? "})" : " })");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case T_PROTECTED_LVALUE:
|
|
stradd(st, &str, compact ? "p l:" : "prot lvalue: ");
|
|
str = svalue_to_string(st, obj->u.lvalue, str, indent+2, trailing, quoteStrings, compact, MY_FALSE);
|
|
break;
|
|
|
|
default:
|
|
stradd(st, &str, "!ERROR: GARBAGE SVALUE (");
|
|
numadd(st, &str, obj->type);
|
|
stradd(st, &str, ")!");
|
|
} /* end of switch (obj->type) */
|
|
|
|
if (trailing)
|
|
stradd(st, &str, compact ? "," : ",\n");
|
|
|
|
return str;
|
|
} /* svalue_to_string() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
add_justified ( fmt_state_t *st
|
|
, char *str, size_t len, int fs
|
|
)
|
|
|
|
/* Justify string <str> (length <len>) within the fieldsize <fs>.
|
|
* After that, add it to the global buff[].
|
|
*/
|
|
|
|
{
|
|
size_t sppos;
|
|
int num_words; /* Number of words in the input */
|
|
int num_chars; /* Number of non-space characters in the input */
|
|
int num_spaces; /* Number of spaces required */
|
|
int min_spaces; /* Min number of pad spaces */
|
|
size_t pos;
|
|
|
|
/* Check how much data we have */
|
|
|
|
num_words = 0;
|
|
num_chars = 0;
|
|
|
|
/* Find the first non-space character.
|
|
* If it's all spaces, return.
|
|
*/
|
|
for (pos = 0; pos < len && *str == ' '; pos++, str++) NOOP;
|
|
if (pos >= len)
|
|
return;
|
|
|
|
len -= (size_t)pos;
|
|
pos = 0;
|
|
num_words = 1;
|
|
|
|
while (pos < len)
|
|
{
|
|
/* Find the end of the word */
|
|
for ( ; pos < len && str[pos] != ' '; pos++, num_chars++) NOOP;
|
|
if (pos >= len)
|
|
break;
|
|
|
|
/* Find the start of the next word */
|
|
for ( ; pos < len && str[pos] == ' '; pos++) NOOP;
|
|
if (pos >= len)
|
|
break;
|
|
|
|
/* We got a new word - count it */
|
|
num_words++;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (fs < num_words - 1 + num_chars)
|
|
fatal("add_justified(): fieldsize %d < data length %d\n", fs, num_words - 1 + num_chars);
|
|
#endif
|
|
|
|
/* Compute the number of spaces we need to insert.
|
|
* It is guaranteed here that we have enough space to insert
|
|
* at least one space between each word.
|
|
*/
|
|
num_spaces = fs - num_chars;
|
|
if (num_words == 1)
|
|
min_spaces = num_spaces;
|
|
else
|
|
min_spaces = num_spaces / (num_words-1);
|
|
/* min_spaces * (num_words-1) <= num_spaces < min_spaces * num_words */
|
|
|
|
/* Loop again over the data, now adding spaces as we go. */
|
|
|
|
for (pos = 0; pos < len; )
|
|
{
|
|
int mark;
|
|
int padlength;
|
|
|
|
/* Find the end of the current word */
|
|
for (mark = pos ; pos < len && str[pos] != ' '; pos++) NOOP;
|
|
|
|
/* Add the word */
|
|
ADD_STRN(st, str+mark, pos - mark);
|
|
num_words--;
|
|
|
|
if (pos >= len || num_words < 1)
|
|
break;
|
|
|
|
/* There is a word following - add spaces */
|
|
if (num_words == 1) /* Last word: add all remaining padding */
|
|
padlength = (int)num_spaces;
|
|
else if (num_spaces < min_spaces * num_words) /* Space underrun */
|
|
padlength = 1;
|
|
else if (num_spaces == min_spaces * num_words)
|
|
/* Exactly the min. padlength per word avail. */
|
|
padlength = min_spaces;
|
|
else if (num_spaces >= min_spaces * num_words + 2)
|
|
/* Force an extra space */
|
|
padlength = min_spaces+1;
|
|
else /* Randomly add one space */
|
|
padlength = min_spaces + (int)random_number(2);
|
|
sppos = st->bpos;
|
|
ADD_PADDING(st, " ", padlength);
|
|
st->sppos = sppos;
|
|
num_spaces -= padlength;
|
|
|
|
/* Find the start of the next word */
|
|
for ( ; pos < len && str[pos] == ' '; pos++) NOOP;
|
|
}
|
|
} /* add_justified() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static void
|
|
add_aligned ( fmt_state_t *st
|
|
, char *str, size_t len, char *pad, int fs
|
|
, format_info finfo)
|
|
|
|
/* Align string <str> (length <len>) within the fieldsize <fs> according
|
|
* to the <finfo>. After that, add it to the global buff[].
|
|
*/
|
|
|
|
{
|
|
size_t sppos;
|
|
Bool is_space_pad;
|
|
|
|
if ((size_t)fs < len)
|
|
fs = len;
|
|
|
|
sppos = 0;
|
|
is_space_pad = MY_FALSE;
|
|
if (pad[0] == ' ' && pad[1] == '\0' && !(finfo & INFO_PS_KEEP))
|
|
is_space_pad = MY_TRUE;
|
|
|
|
switch(finfo & INFO_A)
|
|
{
|
|
case INFO_A_JUSTIFY:
|
|
case INFO_A_LEFT:
|
|
/* Also called for the last line of a justified block */
|
|
ADD_STRN(st, str, len);
|
|
if (is_space_pad)
|
|
sppos = st->bpos;
|
|
ADD_PADDING(st, pad, fs - len);
|
|
if (is_space_pad)
|
|
st->sppos = sppos;
|
|
break;
|
|
|
|
case INFO_A_CENTRE:
|
|
if (finfo & INFO_PS_ZERO)
|
|
{
|
|
ADD_PADDING(st, "0", (fs - len + 1) >> 1);
|
|
}
|
|
else
|
|
{
|
|
ADD_PADDING(st, pad, (fs - len + 1) >> 1);
|
|
}
|
|
ADD_STRN(st, str, len);
|
|
if (is_space_pad)
|
|
sppos = st->bpos;
|
|
ADD_PADDING(st, pad, (fs - len) >> 1);
|
|
if (is_space_pad)
|
|
st->sppos = sppos;
|
|
break;
|
|
|
|
default:
|
|
{ /* std (s)printf defaults to right alignment.
|
|
*/
|
|
if (finfo & INFO_PS_ZERO)
|
|
{
|
|
ADD_PADDING(st, "0", fs - len);
|
|
}
|
|
else
|
|
{
|
|
ADD_PADDING(st, pad, fs - len);
|
|
}
|
|
ADD_STRN(st, str, len);
|
|
}
|
|
}
|
|
} /* add_aligned() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static int
|
|
add_column (fmt_state_t *st, cst **column)
|
|
|
|
/* Add the the next line from <column> to the buffer buff[].
|
|
* Result 0: column not finished (more lines/data pending)
|
|
* 1: column completed and removed from the list
|
|
* 2: column completed with terminating \n, column removed from
|
|
* the list.
|
|
*/
|
|
|
|
{
|
|
#define COL (*column)
|
|
|
|
unsigned int done;
|
|
mp_int length;
|
|
unsigned int save;
|
|
char *COL_D = COL->d.col;
|
|
char *p;
|
|
|
|
/* Set done to the actual number of characters to copy.
|
|
*/
|
|
length = COL->pres;
|
|
if ((COL->info & INFO_A) == INFO_A_JUSTIFY && length > (mp_int)COL->size)
|
|
length = COL->size;
|
|
for (p = COL_D; length && *p && *p !='\n'; p++, length--) NOOP;
|
|
done = p - COL_D;
|
|
if (*p && *p !='\n')
|
|
{
|
|
/* Column data longer than the permitted size: find a
|
|
* a space to do wordwrapping.
|
|
*/
|
|
save = done;
|
|
for (; ; done--,p--)
|
|
{
|
|
/* handle larger than column size words... */
|
|
if (!done)
|
|
{
|
|
/* Sorry, it's one big word.
|
|
* Print the word over the fieldsize.
|
|
*/
|
|
done = save - 1;
|
|
p += save;
|
|
break;
|
|
}
|
|
if (*p == ' ')
|
|
{
|
|
/* If went more than one character back, check if
|
|
* the next word is longer than permitted. If that is
|
|
* the case we might as well start breaking it up right
|
|
* here.
|
|
*/
|
|
if (save-2 > done)
|
|
{
|
|
char *p2;
|
|
|
|
length = COL->pres;
|
|
if ((COL->info & INFO_A) == INFO_A_JUSTIFY
|
|
&& length > (mp_int)COL->size)
|
|
length = COL->size;
|
|
for ( p2 = p+1, length--
|
|
; length && *p2 && *p2 !='\n' && *p2 != ' '
|
|
; p2++, length--) NOOP;
|
|
if (*p2 && *p2 != '\n' && *p2 != ' ')
|
|
{
|
|
/* Yup, the next word is far too long. */
|
|
p += save - done;
|
|
done = save - 1;
|
|
}
|
|
/* else: the next word is not too long */
|
|
}
|
|
/* else: breaking too long word here would look silly anyway
|
|
*/
|
|
break;
|
|
} /* if (p == ' ') */
|
|
} /* for (done) */
|
|
} /* if (breaking needed) */
|
|
|
|
/* On justified formatting, don't format the last line that way, nor
|
|
* justified lines ending in NL.
|
|
*/
|
|
if ((COL->info & INFO_A) == INFO_A_JUSTIFY
|
|
&& *COL_D && *(COL_D+1)
|
|
&& *p != '\n' && *p != '\0'
|
|
)
|
|
{
|
|
add_justified(st, COL_D, p - COL_D, COL->size);
|
|
}
|
|
else
|
|
{
|
|
add_aligned(st, COL_D, p - COL_D, COL->pad, COL->size, COL->info);
|
|
}
|
|
|
|
COL_D += done; /* inc'ed below ... */
|
|
|
|
/* if this or the next character is a '\0' 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;
|
|
xfree(COL);
|
|
COL = temp;
|
|
return ret;
|
|
}
|
|
|
|
/* Column not finished */
|
|
COL->d.col = COL_D;
|
|
return 0;
|
|
|
|
#undef COL
|
|
|
|
} /* add_column() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static Bool
|
|
add_table (fmt_state_t *st, cst **table)
|
|
|
|
/* Add the next line of <table> to the buffer.
|
|
* Return TRUE if the table was completed and removed from the list.
|
|
*/
|
|
|
|
{
|
|
unsigned int done, i;
|
|
|
|
#define TAB (*table)
|
|
#define TAB_D (TAB->d.tab[i])
|
|
|
|
/* Loop over all columns of the table */
|
|
for (i = 0; i < TAB->nocols && TAB_D; i++)
|
|
{
|
|
/* Get the length to add */
|
|
for (done = 0; (TAB_D[done]) && (TAB_D[done] != '\n'); done++) NOOP;
|
|
|
|
add_aligned(st, TAB_D, done, TAB->pad, TAB->size, TAB->info);
|
|
|
|
TAB_D += done; /* inc'ed next line ... */
|
|
if (!(*TAB_D) || !(*(++TAB_D)))
|
|
TAB_D = NULL;
|
|
}
|
|
|
|
/* Fill up the end of the table if required */
|
|
if (i < TAB->nocols)
|
|
{
|
|
done = TAB->size;
|
|
for (; i < TAB->nocols; i++)
|
|
{
|
|
/* TAB->size is not negative. */
|
|
ADD_CHARN(st, ' ', done);
|
|
}
|
|
}
|
|
|
|
if (!TAB->d.tab[0])
|
|
{
|
|
/* Table finished */
|
|
|
|
cst *temp;
|
|
|
|
temp = TAB->next;
|
|
xfree(TAB->d.tab);
|
|
xfree(TAB);
|
|
TAB = temp;
|
|
return MY_TRUE;
|
|
}
|
|
|
|
return MY_FALSE;
|
|
|
|
#undef TAB
|
|
#undef TAB_D
|
|
|
|
} /* add_table() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static string_t *
|
|
string_print_formatted (char *format_str, int argc, svalue_t *argv)
|
|
|
|
/* The (s)printf() function: format <format_str> with the given arguments
|
|
* and return a pointer to the result (a string with one reference).
|
|
*
|
|
* If an error occurs and RETURN_ERROR_MESSAGES is defined, an error
|
|
* will return the error string as result; if R_E_M is undefined, an
|
|
* true errorf() is raised.
|
|
*/
|
|
|
|
{
|
|
#ifndef RETURN_ERROR_MESSAGES
|
|
static char buff[BUFF_SIZE]; /* For error messages */
|
|
#endif
|
|
string_t *result; /* The result string */
|
|
fmt_state_t *st; /* The formatting state */
|
|
volatile fmt_state_t *vst; /* A copy of st, stored in a volatile var
|
|
* to survive a longjmp()
|
|
*/
|
|
svalue_t *carg; /* current arg */
|
|
int arg; /* current arg number */
|
|
format_info finfo; /* parse formatting info */
|
|
char format_char; /* format type */
|
|
unsigned int nelemno; /* next offset into array */
|
|
unsigned int fpos; /* position in format_str */
|
|
p_uint fs; /* field size */
|
|
int pres; /* precision */
|
|
unsigned int err_num; /* error code */
|
|
char *pad; /* fs pad string */
|
|
int column_stat; /* Most recent column add status */
|
|
|
|
# define GET_NEXT_ARG {\
|
|
if (++arg >= argc) ERROR(ERR_TO_FEW_ARGS); \
|
|
carg = (argv+arg);\
|
|
}
|
|
|
|
# define SAVE_CHAR(pointer) {\
|
|
savechars *new;\
|
|
new = xalloc(sizeof(savechars));\
|
|
if (!new) \
|
|
ERROR(ERR_NOMEM); \
|
|
new->what = *(pointer);\
|
|
new->where = pointer;\
|
|
new->next = st->saves;\
|
|
st->saves = new;\
|
|
}
|
|
|
|
result = NULL; /* To get rid of a warning */
|
|
|
|
if (!static_fmt_used)
|
|
{
|
|
st = &static_fmt;
|
|
static_fmt_used = MY_TRUE;
|
|
}
|
|
else
|
|
xallocate(st, sizeof *st, "sprintf() context");
|
|
|
|
st->saves = NULL;
|
|
st->clean.u.str = NULL;
|
|
st->tmp = NULL;
|
|
st->csts = NULL;
|
|
st->ptable = NULL;
|
|
|
|
vst = st;
|
|
|
|
if (0 != (err_num = setjmp(((fmt_state_t*)st)->error_jmp)))
|
|
{
|
|
/* error handling */
|
|
char *err;
|
|
cst *tcst;
|
|
|
|
st = (fmt_state_t*)vst;
|
|
|
|
if (st->ptable)
|
|
free_pointer_table(st->ptable);
|
|
|
|
/* Restore the saved characters */
|
|
while (st->saves)
|
|
{
|
|
savechars *tmp;
|
|
*(st->saves->where) = st->saves->what;
|
|
tmp = st->saves;
|
|
st->saves = st->saves->next;
|
|
xfree(tmp);
|
|
}
|
|
|
|
/* Get rid of a temp string */
|
|
if (st->clean.u.str)
|
|
free_mstring(st->clean.u.str);
|
|
if (st->tmp)
|
|
xfree(st->tmp);
|
|
|
|
/* Free column and table data */
|
|
while ( NULL != (tcst = st->csts) )
|
|
{
|
|
st->csts = tcst->next;
|
|
if ((tcst->info & (INFO_COLS|INFO_TABLE)) == INFO_TABLE
|
|
&& tcst->d.tab)
|
|
xfree(tcst->d.tab);
|
|
xfree(tcst);
|
|
}
|
|
|
|
/* Select the error string */
|
|
switch(err_num & ERR_ID_NUMBER)
|
|
{
|
|
default:
|
|
#ifdef DEBUG
|
|
fatal("undefined (s)printf() error 0x%X\n", err_num);
|
|
#endif
|
|
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_PREC_EXPECTED:
|
|
err = "Expected precision not found.";
|
|
break;
|
|
|
|
case ERR_INVALID_FORMAT_STR:
|
|
err = "Error in format string.";
|
|
break;
|
|
|
|
case ERR_INCORRECT_ARG:
|
|
err = "incorrect argument type to %%%c.";
|
|
break;
|
|
|
|
case ERR_CST_REQUIRES_FS:
|
|
err = "Column/table mode requires a field size.";
|
|
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_NOMEM:
|
|
err = "Out of memory.";
|
|
break;
|
|
|
|
case ERR_SIZE_OVERFLOW:
|
|
err = "Fieldsize or precision too large.";
|
|
break;
|
|
}
|
|
|
|
/* Create the error message in buff[] */
|
|
st->buff[0]='\0';
|
|
if ((err_num & ERR_ID_NUMBER) != ERR_NOMEM)
|
|
{
|
|
int line;
|
|
string_t *file;
|
|
|
|
file = NULL;
|
|
line = get_line_number_if_any(&file);
|
|
sprintf(st->buff, "%s:%d: ", get_txt(file), line);
|
|
|
|
if (file)
|
|
free_mstring(file);
|
|
}
|
|
|
|
#ifdef RETURN_ERROR_MESSAGES
|
|
strcat(st->buff, "(s)printf() error: ");
|
|
#else
|
|
strcat(st->buff, "(s)printf(): ");
|
|
#endif
|
|
sprintf(st->buff + strlen(st->buff)
|
|
, err, EXTRACT_ERR_ARGUMENT(err_num));
|
|
strcat(st->buff, "\n");
|
|
#ifndef RETURN_ERROR_MESSAGES
|
|
strcpy(buff, st->buff);
|
|
if (st == &static_fmt)
|
|
static_fmt_used = MY_FALSE;
|
|
else
|
|
xfree(st);
|
|
errorf("%s", buff); /* buff may contain a '%' */
|
|
/* NOTREACHED */
|
|
#else
|
|
result = new_mstring(st->buff);
|
|
if (!result)
|
|
result = ref_mstring(STR_OUT_OF_MEMORY);
|
|
xfree(st);
|
|
#endif /* RETURN_ERROR_MESSAGES */
|
|
return result;
|
|
}
|
|
|
|
st = (fmt_state_t*)vst;
|
|
|
|
format_char = 0;
|
|
nelemno = 0;
|
|
column_stat = 0;
|
|
|
|
arg = -1;
|
|
st->bpos = 0;
|
|
st->sppos = -1;
|
|
st->line_start = 0;
|
|
|
|
/* Walk through the format string */
|
|
for (fpos = 0; MY_TRUE; fpos++)
|
|
{
|
|
if ((format_str[fpos] == '\n') || (!format_str[fpos]))
|
|
{
|
|
/* Line- or Format end */
|
|
|
|
if (!st->csts)
|
|
{
|
|
/* No columns/tables to resolve, but add a second
|
|
* newline if there is one pending from an added
|
|
* column
|
|
*/
|
|
|
|
if (column_stat == 2)
|
|
ADD_CHAR(st, '\n');
|
|
column_stat = 0;
|
|
if (!format_str[fpos])
|
|
break;
|
|
ADD_CHAR(st, '\n');
|
|
st->line_start = st->bpos;
|
|
continue;
|
|
}
|
|
|
|
column_stat = 0; /* If there was a newline pending, it
|
|
* will be implicitely added now.
|
|
*/
|
|
ADD_CHAR(st, '\n');
|
|
st->line_start = st->bpos;
|
|
|
|
/* Handle pending columns and tables */
|
|
while (st->csts)
|
|
{
|
|
cst **temp;
|
|
|
|
/* Add one line from each column/table */
|
|
temp = &st->csts;
|
|
while (*temp)
|
|
{
|
|
p_int i;
|
|
if ((*temp)->info & INFO_COLS)
|
|
{
|
|
if (*((*temp)->d.col-1) != '\n')
|
|
while (*((*temp)->d.col) == ' ')
|
|
(*temp)->d.col++;
|
|
i = (*temp)->start - (st->bpos - st->line_start);
|
|
ADD_CHARN(st, ' ', i);
|
|
column_stat = add_column(st, temp);
|
|
if (!column_stat)
|
|
temp = &((*temp)->next);
|
|
}
|
|
else
|
|
{
|
|
i = (*temp)->start - (st->bpos - st->line_start);
|
|
if (i > 0)
|
|
ADD_CHARN(st, ' ', i);
|
|
if (!add_table(st, temp))
|
|
temp = &((*temp)->next);
|
|
}
|
|
} /* while (*temp) */
|
|
|
|
if (st->csts || format_str[fpos] == '\n')
|
|
ADD_CHAR(st, '\n');
|
|
st->line_start = st->bpos;
|
|
} /* while (csts) */
|
|
|
|
if (column_stat == 2 && format_str[fpos] != '\n')
|
|
ADD_CHAR(st, '\n');
|
|
|
|
if (!format_str[fpos])
|
|
break;
|
|
continue;
|
|
} /* if newline or formatend */
|
|
|
|
if (format_str[fpos] == '%')
|
|
{
|
|
/* Another format entry */
|
|
|
|
if (format_str[fpos+1] == '%')
|
|
{
|
|
ADD_CHAR(st, '%');
|
|
fpos++;
|
|
continue;
|
|
}
|
|
|
|
if (format_str[fpos+1] == '^')
|
|
{
|
|
ADD_CHAR(st, '%');
|
|
fpos++;
|
|
ADD_CHAR(st, '^');
|
|
continue;
|
|
}
|
|
|
|
GET_NEXT_ARG;
|
|
fs = 0;
|
|
pres = 0;
|
|
pad = " ";
|
|
finfo = 0;
|
|
|
|
/* Parse the formatting entry */
|
|
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] == '*'))
|
|
{
|
|
/* Precision resp. fieldwidth */
|
|
if (pres == -1) /* then looking for pres */
|
|
{
|
|
if (format_str[fpos] == '*')
|
|
{
|
|
/* Get the value from the args */
|
|
if (carg->type != T_NUMBER)
|
|
ERROR(ERR_INVALID_STAR);
|
|
pres = carg->u.number;
|
|
GET_NEXT_ARG;
|
|
continue;
|
|
}
|
|
|
|
/* Parse the number */
|
|
pres = format_str[fpos] - '0';
|
|
for ( fpos++
|
|
; format_str[fpos]>='0' && format_str[fpos]<='9'
|
|
; fpos++)
|
|
{
|
|
int new_pres = pres*10 + format_str[fpos] - '0';
|
|
|
|
if (new_pres < pres) /* Overflow */
|
|
ERROR(ERR_SIZE_OVERFLOW);
|
|
|
|
pres = new_pres;
|
|
}
|
|
}
|
|
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] == '*')
|
|
)
|
|
finfo |= INFO_PS_ZERO;
|
|
else
|
|
{
|
|
if (format_str[fpos] == '*')
|
|
{
|
|
if (carg->type != T_NUMBER)
|
|
ERROR(ERR_INVALID_STAR);
|
|
if ((p_int)(fs = carg->u.number) < 0)
|
|
{
|
|
#ifdef NO64BIT
|
|
if (fs == (unsigned int)PINT_MIN)
|
|
fs = PINT_MAX;
|
|
#else
|
|
if (fs == (unsigned int)INT_MIN)
|
|
fs = INT_MAX;
|
|
#endif
|
|
else
|
|
fs = -fs;
|
|
finfo |= INFO_A_LEFT;
|
|
}
|
|
|
|
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++)
|
|
{
|
|
int new_fs = fs*10 + format_str[fpos] - '0';
|
|
|
|
if (new_fs < fs) /* Overflow */
|
|
ERROR(ERR_SIZE_OVERFLOW);
|
|
|
|
fs = new_fs;
|
|
}
|
|
|
|
if (pres == -2) /* colon */
|
|
pres = fs;
|
|
}
|
|
|
|
fpos--; /* bout to get incremented */
|
|
continue;
|
|
} /* if (precision/alignment field) */
|
|
|
|
/* fpos now points to format type */
|
|
|
|
switch (format_str[fpos])
|
|
{
|
|
case ' ': finfo |= INFO_PP_SPACE; break;
|
|
case '+': finfo |= INFO_PP_PLUS; break;
|
|
case '-': finfo |= INFO_A_LEFT; break;
|
|
case '|': finfo |= INFO_A_CENTRE; break;
|
|
case '$': finfo |= INFO_A_JUSTIFY; 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 'Q': finfo |= INFO_T_QLPC; break;
|
|
case 's': finfo |= INFO_T_STRING; break;
|
|
case 'i': finfo |= INFO_T_INT; format_char = 'd'; break;
|
|
case 'd':
|
|
case 'c':
|
|
case 'o':
|
|
case 'b':
|
|
case 'B':
|
|
case 'x':
|
|
case 'X':
|
|
format_char = format_str[fpos];
|
|
finfo |= INFO_T_INT;
|
|
break;
|
|
case 'f':
|
|
case 'F':
|
|
case 'g':
|
|
case 'G':
|
|
case 'e':
|
|
case 'E':
|
|
format_char = format_str[fpos];
|
|
finfo |= INFO_T_FLOAT;
|
|
break;
|
|
case '\'':
|
|
pad = &(format_str[++fpos]);
|
|
finfo |= INFO_PS_KEEP;
|
|
while (1)
|
|
{
|
|
if (!format_str[fpos])
|
|
ERROR(ERR_UNEXPECTED_EOS);
|
|
|
|
if (format_str[fpos] == '\\')
|
|
{
|
|
if (!format_str[fpos+1])
|
|
ERROR(ERR_UNEXPECTED_EOS);
|
|
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;
|
|
break;
|
|
}
|
|
} /* for(format parsing) */
|
|
|
|
if (pres < 0)
|
|
ERROR(ERR_PREC_EXPECTED);
|
|
|
|
/* Now handle the different arg types...
|
|
*/
|
|
if (finfo & INFO_ARRAY)
|
|
{
|
|
if (carg->type != T_POINTER)
|
|
ERROR(ERR_ARRAY_EXPECTED);
|
|
if (carg->u.vec == &null_vector)
|
|
{
|
|
fpos--; /* 'bout to get incremented */
|
|
continue;
|
|
}
|
|
carg = (argv+arg)->u.vec->item;
|
|
nelemno = 1; /* next element number */
|
|
}
|
|
|
|
while(1)
|
|
{
|
|
switch(finfo & INFO_T)
|
|
{
|
|
case INFO_T_ERROR:
|
|
ERROR(ERR_INVALID_FORMAT_STR);
|
|
|
|
case INFO_T_NULL:
|
|
{
|
|
/* never reached... */
|
|
fprintf(stderr, "%s: (s)printf: INFO_T_NULL.... found.\n"
|
|
, get_txt(current_object->name));
|
|
ADD_CHAR(st, '%');
|
|
break;
|
|
}
|
|
|
|
case INFO_T_LPC:
|
|
case INFO_T_QLPC:
|
|
{
|
|
sprintf_buffer_t *b;
|
|
# define CLEANSIZ 0x200
|
|
|
|
if (st->clean.u.str)
|
|
free_mstring(st->clean.u.str);
|
|
st->clean.u.str = NULL;
|
|
if (st->tmp)
|
|
xfree(st->tmp);
|
|
|
|
st->tmp = xalloc(CLEANSIZ);
|
|
if (!st->tmp)
|
|
ERROR(ERR_NOMEM);
|
|
st->tmp[0] = '\0';
|
|
|
|
st->ptable = new_pointer_table();
|
|
if (!st->ptable)
|
|
ERROR(ERR_NOMEM);
|
|
st->pointer_id = 1;
|
|
|
|
b = (sprintf_buffer_t *)
|
|
( st->tmp+CLEANSIZ-sizeof(sprintf_buffer_t) );
|
|
b->offset = -CLEANSIZ+(p_int)sizeof(sprintf_buffer_t);
|
|
b->size = CLEANSIZ;
|
|
b->start = &st->tmp;
|
|
b = svalue_to_string(st, carg, b, 0, MY_FALSE
|
|
, (finfo & INFO_T) == INFO_T_QLPC
|
|
, (finfo & INFO_TABLE)
|
|
, MY_FALSE
|
|
);
|
|
finfo &= ~INFO_TABLE; /* since we fall through */
|
|
|
|
/* Store the created result in .clean and pass it
|
|
* to case INFO_T_STRING is 'the' carg.
|
|
*/
|
|
put_string(&st->clean, new_n_mstring(st->tmp, b->size + b->offset - (p_int)sizeof(sprintf_buffer_t) ));
|
|
carg = &st->clean;
|
|
|
|
free_pointer_table(st->ptable);
|
|
st->ptable = NULL;
|
|
/* FALLTHROUGH */
|
|
}
|
|
|
|
case INFO_T_STRING:
|
|
{
|
|
mp_int slen;
|
|
|
|
if (carg->type != T_STRING)
|
|
ERROR1(ERR_INCORRECT_ARG, 's');
|
|
slen = mstrsize(carg->u.str);
|
|
|
|
if (finfo & (INFO_COLS | INFO_TABLE) )
|
|
{
|
|
/* Add a new column/table info
|
|
* ... this is complicated ...
|
|
*/
|
|
cst **temp;
|
|
|
|
if (!fs)
|
|
ERROR(ERR_CST_REQUIRES_FS);
|
|
|
|
if ((finfo & (INFO_COLS | INFO_TABLE))
|
|
== (INFO_COLS | INFO_TABLE)
|
|
)
|
|
ERROR(ERR_INVALID_FORMAT_STR);
|
|
|
|
/* Find the end of the list of columns/tables */
|
|
temp = &st->csts;
|
|
while (*temp)
|
|
temp = &((*temp)->next);
|
|
|
|
if (finfo & INFO_COLS)
|
|
{
|
|
/* Create a new columns structure */
|
|
*temp = xalloc(sizeof(cst));
|
|
if (!*temp)
|
|
ERROR(ERR_NOMEM);
|
|
(*temp)->next = NULL;
|
|
(*temp)->d.col = get_txt(carg->u.str);
|
|
(*temp)->pad = pad;
|
|
(*temp)->size = fs;
|
|
(*temp)->pres = (pres) ? (int)pres : (int)fs;
|
|
(*temp)->info = finfo;
|
|
(*temp)->start = st->bpos - st->line_start;
|
|
|
|
/* Format the first line from the column */
|
|
column_stat = add_column(st, temp);
|
|
}
|
|
else
|
|
{
|
|
/* (finfo & INFO_TABLE) */
|
|
unsigned int n, len, max;
|
|
int tpres;
|
|
char c, *s, *start;
|
|
p_uint i;
|
|
|
|
# define TABLE get_txt(carg->u.str)
|
|
|
|
/* Create the new table structure */
|
|
(*temp) = (cst *)xalloc(sizeof(cst));
|
|
if (!*temp)
|
|
ERROR(ERR_NOMEM);
|
|
(*temp)->pad = pad;
|
|
(*temp)->info = finfo;
|
|
(*temp)->start = st->bpos - st->line_start;
|
|
(*temp)->next = NULL;
|
|
|
|
/* Determine the size of the table */
|
|
max = len = 0;
|
|
n = 1;
|
|
s = TABLE;
|
|
if ( '\0' != (c = *(start = s)) ) for (;;)
|
|
{
|
|
if (c != '\n')
|
|
{
|
|
if ( '\0' != (c = *++s) )
|
|
continue;
|
|
else
|
|
break;
|
|
}
|
|
len = s - start;
|
|
if (len > max)
|
|
max = len;
|
|
n++;
|
|
if ( '\0' != (c = *(start = ++s)) )
|
|
{
|
|
continue;
|
|
}
|
|
n--;
|
|
break;
|
|
} /* if() for() */
|
|
|
|
/* Now: n = number of lines
|
|
* max = max length of the lines
|
|
*/
|
|
|
|
tpres = pres;
|
|
if (tpres)
|
|
{
|
|
(*temp)->size = fs/tpres;
|
|
}
|
|
else
|
|
{
|
|
len = s - start;
|
|
if (len > max)
|
|
max = len; /* the null terminated word */
|
|
tpres = fs/(max+2);
|
|
/* at least two separating spaces */
|
|
if (!tpres)
|
|
tpres = 1;
|
|
(*temp)->size = fs/tpres;
|
|
}
|
|
|
|
len = n/tpres; /* length of average column */
|
|
|
|
if (n < (unsigned int)tpres)
|
|
tpres = n;
|
|
if (len*tpres < n)
|
|
len++;
|
|
/* Since the table will be filled by column,
|
|
* the result will be a rectangle with as little
|
|
* as possible empty space. This means we have
|
|
* to adjust the no of columns (tpres) to
|
|
* what the fill algorithm will actually
|
|
* produce.
|
|
*/
|
|
if (len > 1 && n%tpres)
|
|
tpres -= (tpres - n%tpres)/len;
|
|
|
|
(*temp)->d.tab = xalloc(tpres*sizeof(char *));
|
|
if (!(*temp)->d.tab)
|
|
ERROR(ERR_NOMEM);
|
|
(*temp)->nocols = tpres; /* heavy sigh */
|
|
(*temp)->d.tab[0] = TABLE;
|
|
|
|
if (tpres == 1)
|
|
goto add_table_now;
|
|
|
|
/* Subformat the table, replacing some characters
|
|
* in the given string.
|
|
* The original chars are saved for later restoring.
|
|
*/
|
|
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 >= (unsigned int)tpres)
|
|
goto add_table_now;
|
|
n = 0;
|
|
}
|
|
}
|
|
} /* for (fs) */
|
|
|
|
add_table_now:
|
|
/* Now add the table (at least the first line) */
|
|
add_table(st, temp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Bool justifyString;
|
|
|
|
/* not column or table */
|
|
if (pres && pres < slen)
|
|
{
|
|
slen = pres;
|
|
}
|
|
|
|
/* Determine whether to print this string
|
|
* justified, or not.
|
|
*/
|
|
justifyString = MY_FALSE;
|
|
if (fs && (finfo & INFO_A) == INFO_A_JUSTIFY)
|
|
{
|
|
/* Flush the string only if it doesn't
|
|
* end in a NL.
|
|
* Also, strip off trailing spaces.
|
|
*/
|
|
for ( ; slen > 0
|
|
&& get_txt(carg->u.str)[slen-1] == ' '
|
|
; slen--
|
|
) NOOP;
|
|
|
|
if ( slen != 0
|
|
&& (unsigned int)slen <= fs
|
|
&& get_txt(carg->u.str)[slen-1] != '\n'
|
|
)
|
|
justifyString = MY_TRUE;
|
|
}
|
|
|
|
/* not column or table */
|
|
|
|
if (justifyString)
|
|
{
|
|
add_justified(st, get_txt(carg->u.str), slen, fs);
|
|
}
|
|
else if (fs && fs > (unsigned int)slen)
|
|
{
|
|
add_aligned(st, get_txt(carg->u.str)
|
|
, slen, pad, fs, finfo);
|
|
}
|
|
else
|
|
{
|
|
ADD_STRN(st, get_txt(carg->u.str), slen);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case INFO_T_INT:
|
|
case INFO_T_FLOAT:
|
|
/* We 'cheat' by using the systems sprintf() to format
|
|
* the number in most of the formats.
|
|
*/
|
|
if ((finfo & INFO_T ) == INFO_T_INT
|
|
&& (format_char == 'b' || format_char == 'B')
|
|
)
|
|
{
|
|
/* Dummy for finding most significant bit */
|
|
int signi = 0;
|
|
int isize = sizeof(carg->u.number) * CHAR_BIT;
|
|
char temp[sizeof(carg->u.number) * CHAR_BIT + 1];
|
|
int tmpl;
|
|
|
|
memset(temp, '\0', sizeof(temp));
|
|
|
|
if (carg->type != T_NUMBER)
|
|
{
|
|
ERROR1(ERR_INCORRECT_ARG, format_char);
|
|
}
|
|
/* Calculate binary representation (2's compliment) */
|
|
while ( --isize > -1 )
|
|
{
|
|
if ( ( carg->u.number & ( 1 << isize ) ) != 0 )
|
|
{
|
|
signi = 1;
|
|
strcat( temp, "1" );
|
|
}
|
|
else if ( signi )
|
|
strcat( temp, "0" );
|
|
}
|
|
tmpl = strlen(temp);
|
|
if ((size_t)tmpl >= sizeof(temp))
|
|
fatal("Local buffer overflow in sprintf() for int.\n");
|
|
if (pres && tmpl > pres)
|
|
tmpl = pres; /* well.... */
|
|
if ((unsigned int)tmpl < fs)
|
|
add_aligned(st, temp, tmpl, pad, fs, finfo);
|
|
else
|
|
ADD_STRN(st, temp, tmpl);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
/* Synthesized format for sprintf() */
|
|
char cheat[6+sizeof(PRI_PINT_PREFIX)-1];
|
|
char temp[1024];
|
|
/* The buffer must be big enough to hold the biggest float
|
|
* in non-exponential representation. 1 KByte is hopefully
|
|
* far on the safe side.
|
|
* TODO: Allocate it dynamically?
|
|
*/
|
|
double value; /* The value to print */
|
|
int numdig; /* (Estimated) number of digits before the '.' */
|
|
Bool zeroCharHack = MY_FALSE;
|
|
char *p = cheat; /* pointer to the format buffer */
|
|
int tmpl;
|
|
|
|
*(p++) = '%';
|
|
switch (finfo & INFO_PP)
|
|
{
|
|
case INFO_PP_SPACE: *(p++) = ' '; break;
|
|
case INFO_PP_PLUS: *(p++) = '+'; break;
|
|
}
|
|
if ((finfo & INFO_T) == INFO_T_FLOAT)
|
|
{
|
|
if (carg->type != T_FLOAT) /* sigh... */
|
|
{
|
|
ERROR1(ERR_INCORRECT_ARG, format_char);
|
|
}
|
|
*(p++) = '.';
|
|
*(p++) = '*';
|
|
*(p++) = format_char;
|
|
*p = '\0';
|
|
|
|
value = READ_DOUBLE(carg);
|
|
|
|
if ('e' == format_char
|
|
|| 'E' == format_char
|
|
|| fabs(value) < 1.0)
|
|
numdig = 1;
|
|
else
|
|
numdig = (int) ceil(log10(fabs(value)));
|
|
if (value < 0.0)
|
|
numdig++;
|
|
|
|
if ((size_t)pres > (sizeof(temp) - 12 - numdig))
|
|
{
|
|
pres = sizeof(temp) - 12 - numdig;
|
|
}
|
|
sprintf(temp, cheat, pres, READ_DOUBLE(carg));
|
|
tmpl = strlen(temp);
|
|
if ((size_t)tmpl >= sizeof(temp))
|
|
fatal("Local buffer overflow in sprintf() for float.\n");
|
|
}
|
|
else
|
|
{
|
|
if (carg->type != T_NUMBER) /* sigh... */
|
|
{
|
|
ERROR1(ERR_INCORRECT_ARG, format_char);
|
|
}
|
|
|
|
/* System sprintf() can't handle ("%c", 0), but LDMud
|
|
* strings can. So in that case we format with
|
|
* character 0x01 and convert to 0 afterwards.
|
|
*/
|
|
if (format_char == 'c') {
|
|
if (carg->u.number == 0)
|
|
{
|
|
carg->u.number = 1;
|
|
zeroCharHack = MY_TRUE;
|
|
}
|
|
}
|
|
/* insert the correct length modifier for a p_int
|
|
* type. (If the prefix is an empty string, this will
|
|
* be probably optimized by the compiler anyway. */
|
|
else {
|
|
p = memcpy(p, PRI_PINT_PREFIX,
|
|
strlen(PRI_PINT_PREFIX));
|
|
p += strlen(PRI_PINT_PREFIX);
|
|
}
|
|
*(p++) = format_char;
|
|
*p = '\0';
|
|
sprintf(temp, cheat, carg->u.number);
|
|
tmpl = strlen(temp);
|
|
if ((size_t)tmpl >= sizeof(temp))
|
|
fatal("Local buffer overflow in sprintf() for int.\n");
|
|
if (pres && tmpl > pres)
|
|
tmpl = pres; /* well.... */
|
|
|
|
if (zeroCharHack)
|
|
{
|
|
int pos;
|
|
for (pos = 0; pos < tmpl; ++pos)
|
|
{
|
|
if (temp[pos] == 0x01)
|
|
temp[pos] = 0x00;
|
|
}
|
|
}
|
|
}
|
|
if ((unsigned int)tmpl < fs)
|
|
{
|
|
if ((finfo & INFO_PS_ZERO) != 0
|
|
&& ( temp[0] == ' '
|
|
|| temp[0] == '+'
|
|
|| temp[0] == '-'
|
|
)
|
|
&& (finfo & INFO_A) != INFO_A_LEFT
|
|
)
|
|
{
|
|
/* Non-left alignment and we're printing
|
|
* with leading zeroes: preserve the sign
|
|
* character in the right place.
|
|
*/
|
|
ADD_STRN(st, temp, 1);
|
|
add_aligned(st, temp+1, tmpl-1, pad, fs-1, finfo);
|
|
}
|
|
else
|
|
add_aligned(st, temp, tmpl, pad, fs, finfo);
|
|
}
|
|
else
|
|
ADD_STRN(st, temp, tmpl);
|
|
break;
|
|
}
|
|
default: /* type not found */
|
|
ERROR(ERR_UNDEFINED_TYPE);
|
|
}
|
|
|
|
if (!(finfo & INFO_ARRAY))
|
|
break;
|
|
|
|
if (nelemno >= (size_t)VEC_SIZE((argv+arg)->u.vec))
|
|
break;
|
|
|
|
carg = (argv+arg)->u.vec->item+nelemno++;
|
|
} /* end of while (1) */
|
|
fpos--; /* bout to get incremented */
|
|
continue;
|
|
} /* if format entry */
|
|
|
|
/* Nothing to format: just copy the character */
|
|
ADD_CHAR(st, format_str[fpos]);
|
|
} /* for (fpos=0; 1; fpos++) */
|
|
|
|
ADD_CHAR(st, '\0'); /* Terminate the formatted string */
|
|
|
|
/* Restore characters */
|
|
while (st->saves)
|
|
{
|
|
savechars *tmp;
|
|
*(st->saves->where) = st->saves->what;
|
|
tmp = st->saves;
|
|
st->saves = st->saves->next;
|
|
xfree(tmp);
|
|
}
|
|
|
|
/* Free the temp string */
|
|
if (st->clean.u.str)
|
|
free_mstring(st->clean.u.str);
|
|
if (st->tmp)
|
|
xfree(st->tmp);
|
|
|
|
/* Copy over the result */
|
|
if (st->bpos > 1)
|
|
result = new_n_mstring(st->buff, st->bpos-1);
|
|
else
|
|
result = ref_mstring(STR_EMPTY);
|
|
if (!result)
|
|
result = ref_mstring(STR_OUT_OF_MEMORY);
|
|
|
|
if (st == &static_fmt)
|
|
static_fmt_used = MY_FALSE;
|
|
else
|
|
xfree(st);
|
|
|
|
/* Done */
|
|
return result;
|
|
|
|
#undef GET_NEXT_ARG
|
|
#undef SAVE_CHAR
|
|
|
|
} /* string_print_formatted() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
v_printf (svalue_t *sp, int num_arg)
|
|
|
|
/* EFUN printf()
|
|
*
|
|
* void printf(string format, ...)
|
|
*
|
|
* A cross between sprintf() and write(). Returns void and prints
|
|
* the result string to the user.
|
|
*/
|
|
|
|
{
|
|
string_t *str;
|
|
|
|
str = string_print_formatted(get_txt((sp-num_arg+1)->u.str)
|
|
, num_arg-1, sp-num_arg+2);
|
|
if (command_giver)
|
|
tell_object(command_giver, str);
|
|
else
|
|
add_message(FMT_STRING, str);
|
|
free_mstring(str);
|
|
sp = pop_n_elems(num_arg, sp);
|
|
|
|
return sp;
|
|
} /* v_printf() */
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
svalue_t *
|
|
v_sprintf (svalue_t *sp, int num_arg)
|
|
|
|
/* EFUN sprintf()
|
|
*
|
|
* string sprintf(string fmt, ...)
|
|
*
|
|
* Generate a string according to the <fmt> and the following
|
|
* arguments and put it onto the stack.
|
|
*
|
|
* <fmt> follows the C-style sprintf-format string in style,
|
|
* and partly in meaning, too.
|
|
*/
|
|
|
|
{
|
|
string_t *s;
|
|
|
|
s = string_print_formatted(get_txt((sp-num_arg+1)->u.str),
|
|
num_arg-1, sp-num_arg+2);
|
|
sp = pop_n_elems(num_arg, sp);
|
|
if (!s)
|
|
push_number(sp, 0);
|
|
else
|
|
push_string(sp, s);
|
|
|
|
return sp;
|
|
} /* v_sprintf() */
|
|
|
|
/***************************************************************************/
|
|
|