xenocara/app/xedit/lisp/format.c
2015-05-10 10:07:47 +00:00

2123 lines
55 KiB
C

/*
* Copyright (c) 2002 by The XFree86 Project, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Except as contained in this notice, the name of the XFree86 Project shall
* not be used in advertising or otherwise to promote the sale, use or other
* dealings in this Software without prior written authorization from the
* XFree86 Project.
*
* Author: Paulo César Pereira de Andrade
*/
/* $XFree86: xc/programs/xedit/lisp/format.c,v 1.29tsi Exp $ */
#include "lisp/io.h"
#include "lisp/write.h"
#include "lisp/format.h"
#include <ctype.h>
#define MAXFMT 8
#define NOERROR 0
/* parse error codes */
#define PARSE_2MANYPARM 1 /* too many directive parameters */
#define PARSE_2MANYATS 2 /* more than one @ in directive */
#define PARSE_2MANYCOLS 3 /* more than one : in directive */
#define PARSE_NOARGSLEFT 4 /* no arguments left to format */
#define PARSE_BADFMTARG 5 /* argument is not an integer or char */
#define PARSE_BADDIRECTIVE 6 /* unknown format directive */
#define PARSE_BADINTEGER 7 /* bad integer representation */
/* merge error codes */
#define MERGE_2MANY 1 /* too many parameters to directive */
#define MERGE_NOCHAR 2 /* parameter must be a character */
#define MERGE_NOINT 3 /* parameter must be an integer */
/* generic error codes */
#define GENERIC_RADIX 1 /* radix not in range 2-36 */
#define GENERIC_NEGATIVE 2 /* parameter is negative */
#define GENERIC_BADSTRING 3 /* argument is not a string */
#define GENERIC_BADLIST 4 /* argument is not a list */
#define IF_SPECIFIED(arg) (arg).specified ? &((arg).value) : NULL
#define UPANDOUT_NORMAL 1
#define UPANDOUT_COLLON 2
#define UPANDOUT_HASH 4 /* only useful inside a ~{ iteration
* forces loop finalization. */
#define ITERATION_NORMAL 1
#define ITERATION_LAST 2
/*
* Types
*/
/* parameter to format */
typedef struct {
unsigned int achar : 1; /* value was specified as a character */
unsigned int specified : 1; /* set if value was specified */
unsigned int offset : 30; /* offset in format string, for error printing */
int value;
} FmtArg;
/* information about format parameters */
typedef struct {
unsigned int atsign : 1; /* @ specified */
unsigned int collon : 1; /* : specified */
unsigned int command : 8; /* the format command */
unsigned int count : 4; /* number of arguments processed */
unsigned int offset : 10; /* offset in format string, for error printing */
char *base, *format;
FmtArg arguments[MAXFMT];
} FmtArgs;
/* used for combining default format parameter values */
typedef struct {
int achar;
int value;
} FmtDef;
/* number of default format parameter values and defaults */
typedef struct {
int count;
FmtDef defaults[MAXFMT];
} FmtDefs;
/* used on recursive calls to LispFormat */
typedef struct {
FmtArgs args;
LispObj *base_arguments; /* pointer to first format argument */
int total_arguments; /* number of objects in base_arguments */
char **format; /* if need to update format string pointer */
LispObj **object; /* CAR(arguments), for plural check */
LispObj **arguments; /* current element of base_arguments */
int *num_arguments; /* number of arguments after arguments */
int upandout; /* information for recursive calls */
int iteration; /* only set if in ~:{... or ~:@{ and in the
* last argument list, hint for upandout */
} FmtInfo;
/*
* Prototypes
*/
static void merge_arguments(FmtArgs*, const FmtDefs*, int*);
static char *parse_arguments(char*, FmtArgs*, int*, LispObj**, int*);
static void merge_error(FmtArgs*, int);
static void parse_error(FmtArgs*, int);
static void generic_error(FmtArgs*, int);
static void format_error(FmtArgs*, const char*);
static int format_object(LispObj*, LispObj*);
static void format_ascii(LispObj*, LispObj*, FmtArgs*);
static void format_in_radix(LispObj*, LispObj*, int, FmtArgs*);
static void format_radix_special(LispObj*, LispObj*, FmtArgs*);
static void format_roman(LispObj*, LispObj*, FmtArgs*);
static void format_english(LispObj*, LispObj*, FmtArgs*);
static void format_character(LispObj*, LispObj*, FmtArgs*);
static void format_fixed_float(LispObj*, LispObj*, FmtArgs*);
static void format_exponential_float(LispObj*, LispObj*, FmtArgs*);
static void format_general_float(LispObj*, LispObj*, FmtArgs*);
static void format_dollar_float(LispObj*, LispObj*, FmtArgs*);
static void format_tabulate(LispObj*, FmtArgs*);
static void format_goto(FmtInfo*);
static void format_indirection(LispObj*, LispObj*, FmtInfo*);
static void list_formats(FmtInfo*, int, char**, char***, int*, int*, int*, int*);
static void free_formats(char**, int);
static void format_case_conversion(LispObj*, FmtInfo*);
static void format_conditional(LispObj*, FmtInfo*);
static void format_iterate(LispObj*, FmtInfo*);
static void format_justify(LispObj*, FmtInfo*);
static void LispFormat(LispObj*, FmtInfo*);
/*
* Initialization
*/
static const FmtDefs AsciiDefs = {
4,
{
{0, 0}, /* mincol */
{0, 1}, /* colinc */
{0, 0}, /* minpad */
{1, ' '}, /* padchar */
},
};
static const FmtDefs IntegerDefs = {
4,
{
{0, 0}, /* mincol */
{1, ' '}, /* padchar */
{1, ','}, /* commachar */
{0, 3}, /* commainterval */
},
};
static const FmtDefs RadixDefs = {
5,
{
{0, 10}, /* radix */
{0, 0}, /* mincol */
{1, ' '}, /* padchar */
{1, ','}, /* commachar */
{0, 3}, /* commainterval */
},
};
static const FmtDefs NoneDefs = {
0,
};
static const FmtDefs FixedFloatDefs = {
5,
{
{0, 0}, /* w */
{0, 16}, /* d */
{0, 0}, /* k */
{1, '\0'}, /* overflowchar */
{1, ' '}, /* padchar */
},
};
static const FmtDefs ExponentialFloatDefs = {
7,
{
{0, 0}, /* w */
{0, 16}, /* d */
{0, 0}, /* e */
{0, 1}, /* k */
{1, '\0'}, /* overflowchar */
{1, ' '}, /* padchar */
{1, 'E'}, /* exponentchar */
/* XXX if/when more than one float format,
* should default to object type */
},
};
static const FmtDefs DollarFloatDefs = {
4,
{
{0, 2}, /* d */
{0, 1}, /* n */
{0, 0}, /* w */
{1, ' '}, /* padchar */
},
};
static const FmtDefs OneDefs = {
1,
{
{0, 1},
},
};
static const FmtDefs TabulateDefs = {
2,
{
{0, 0}, /* colnum */
{0, 1}, /* colinc */
},
};
extern LispObj *Oprint_escape;
/*
* Implementation
*/
static void
merge_arguments(FmtArgs *arguments, const FmtDefs *defaults, int *code)
{
int count;
const FmtDef *defaul;
FmtArg *argument;
defaul = &(defaults->defaults[0]);
argument = &(arguments->arguments[0]);
for (count = 0; count < defaults->count; count++, argument++, defaul++) {
if (count >= arguments->count)
argument->specified = 0;
if (argument->specified) {
if (argument->achar != defaul->achar) {
*code = defaul->achar ? MERGE_NOCHAR : MERGE_NOINT;
arguments->offset = argument->offset;
return;
}
}
else {
argument->specified = 0;
argument->achar = defaul->achar;
argument->value = defaul->value;
}
}
/* check if extra arguments were provided */
if (arguments->count > defaults->count)
*code = MERGE_2MANY;
}
/* the pointer arguments may be null, useful when just testing/parsing
* the directive parameters */
static char *
parse_arguments(char *format, FmtArgs *arguments,
int *num_objects, LispObj **objects, int *code)
{
int test;
char *ptr;
FmtArg *argument;
unsigned int tmpcmd = 0;
/* initialize */
test = objects == NULL || code == NULL || num_objects == NULL;
ptr = format;
argument = &(arguments->arguments[0]);
arguments->atsign = arguments->collon = arguments->command = 0;
/* parse format parameters */
for (arguments->count = 0;; arguments->count++) {
arguments->offset = ptr - format + 1;
if (arguments->count >= MAXFMT) {
if (!test)
*code = PARSE_2MANYPARM;
return (ptr);
}
if (*ptr == '\'') { /* character parameter value */
++ptr; /* skip ' */
argument->achar = argument->specified = 1;
argument->value = *ptr++;
}
else if (*ptr == ',') { /* use default parameter value */
argument->achar = 0;
argument->specified = 0;
/* don't increment ptr, will be incremented below */
}
else if (*ptr == '#') { /* number of arguments is value */
++ptr; /* skip # */
argument->achar = 0;
argument->specified = 1;
if (!test)
argument->value = *num_objects;
}
else if (*ptr == 'v' ||
*ptr == 'V') { /* format object argument is value */
LispObj *object;
++ptr; /* skip V */
if (!test) {
if (!CONSP(*objects)) {
*code = PARSE_NOARGSLEFT;
return (ptr);
}
object = CAR((*objects));
if (FIXNUMP(object)) {
argument->achar = 0;
argument->specified = 1;
argument->value = FIXNUM_VALUE(object);
}
else if (SCHARP(object)) {
argument->achar = argument->specified = 1;
argument->value = SCHAR_VALUE(object);
}
else {
*code = PARSE_BADFMTARG;
return (ptr);
}
*objects = CDR(*objects);
--*num_objects;
}
}
else if (isdigit(*ptr) ||
*ptr == '-' || *ptr == '+') { /* integer parameter value */
int sign;
argument->achar = 0;
argument->specified = 1;
if (!isdigit(*ptr)) {
sign = *ptr++ == '-';
}
else
sign = 0;
if (!test && !isdigit(*ptr)) {
*code = PARSE_BADINTEGER;
return (ptr);
}
argument->value = *ptr++ - '0';
while (isdigit(*ptr)) {
argument->value = (argument->value * 10) + (*ptr++ - '0');
if (argument->value > 65536) {
if (!test) {
*code = PARSE_BADINTEGER;
return (ptr);
}
}
}
if (sign)
argument->value = -argument->value;
}
else /* no more arguments to format */
break;
if (*ptr == ',')
++ptr;
/* remember offset of format parameter, for better error printing */
argument->offset = arguments->offset;
argument++;
}
/* check for extra flags */
for (;;) {
if (*ptr == '@') { /* check for special parameter atsign */
if (arguments->atsign) {
if (!test) {
*code = PARSE_2MANYATS;
return (ptr);
}
}
++ptr;
++arguments->offset;
arguments->atsign = 1;
}
else if (*ptr == ':') { /* check for special parameter collon */
if (arguments->collon) {
if (!test) {
*code = PARSE_2MANYCOLS;
return (ptr);
}
}
++ptr;
++arguments->offset;
arguments->collon = 1;
}
else /* next value is format command */
break;
}
if (!test)
*code = NOERROR;
arguments->command = *ptr++;
tmpcmd = arguments->command;
if (islower(tmpcmd))
arguments->command = toupper(tmpcmd);
++arguments->offset;
return (ptr);
}
static void
parse_error(FmtArgs *args, int code)
{
static const char * const errors[] = {
NULL,
"too many parameters to directive",
"too many @ parameters",
"too many : parameters",
"no arguments left to format",
"argument is not a fixnum integer or a character",
"unknown format directive",
"parameter is not a fixnum integer",
};
format_error(args, errors[code]);
}
static void
merge_error(FmtArgs *args, int code)
{
static const char * const errors[] = {
NULL,
"too many parameters to directive",
"argument must be a character",
"argument must be a fixnum integer",
};
format_error(args, errors[code]);
}
static void
generic_error(FmtArgs *args, int code)
{
static const char * const errors[] = {
NULL,
"radix must be in the range 2 to 36, inclusive",
"parameter must be positive",
"argument must be a string",
"argument must be a list",
};
format_error(args, errors[code]);
}
static void
format_error(FmtArgs *args, const char *str)
{
char *message;
int errorlen, formatlen;
/* number of bytes of format to be printed */
formatlen = (args->format - args->base) + args->offset;
/* length of specific error message */
errorlen = strlen(str) + 1; /* plus '\n' */
/* XXX allocate string with LispMalloc,
* so that it will be freed in LispTopLevel */
message = LispMalloc(formatlen + errorlen + 1);
sprintf(message, "%s\n", str);
memcpy(message + errorlen, args->base, formatlen);
message[errorlen + formatlen] = '\0';
LispDestroy("FORMAT: %s", message);
}
static int
format_object(LispObj *stream, LispObj *object)
{
int length;
length = LispWriteObject(stream, object);
return (length);
}
static void
format_ascii(LispObj *stream, LispObj *object, FmtArgs *args)
{
GC_ENTER();
LispObj *string = NIL;
int length = 0,
atsign = args->atsign,
collon = args->collon,
mincol = args->arguments[0].value,
colinc = args->arguments[1].value,
minpad = args->arguments[2].value,
padchar = args->arguments[3].value;
/* check/correct arguments */
if (mincol < 0)
mincol = 0;
if (colinc < 0)
colinc = 1;
if (minpad < 0)
minpad = 0;
/* XXX pachar can be the null character? */
if (object == NIL)
length = collon ? 2 : 3; /* () or NIL */
/* left padding */
if (atsign) {
/* if length not yet known */
if (object == NIL) {
string = LSTRINGSTREAM("", STREAM_READ | STREAM_WRITE, 0);
GC_PROTECT(string);
length = LispWriteObject(string, object);
}
/* output minpad characters at left */
if (minpad) {
length += minpad;
LispWriteChars(stream, padchar, minpad);
}
if (colinc) {
/* puts colinc spaces at a time,
* until at least mincol chars out */
while (length < mincol) {
LispWriteChars(stream, padchar, colinc);
length += colinc;
}
}
}
if (object == NIL) {
if (collon)
LispWriteStr(stream, "()", 2);
else
LispWriteStr(stream, Snil->value, 3);
}
else {
/* if string is not NIL, atsign was specified
* and object printed to string */
if (string == NIL)
length = format_object(stream, object);
else {
int size;
const char *str = LispGetSstring(SSTREAMP(string), &size);
LispWriteStr(stream, str, size);
}
}
/* right padding */
if (!atsign) {
/* output minpad characters at left */
if (minpad) {
length += minpad;
LispWriteChars(stream, padchar, minpad);
}
if (colinc) {
/* puts colinc spaces at a time,
* until at least mincol chars out */
while (length < mincol) {
LispWriteChars(stream, padchar, colinc);
length += colinc;
}
}
}
GC_LEAVE();
}
/* assumes radix is 0 or in range 2 - 36 */
static void
format_in_radix(LispObj *stream, LispObj *object, int radix, FmtArgs *args)
{
if (INTEGERP(object)) {
int i, atsign, collon, mincol, padchar, commachar, commainterval;
i = (radix == 0);
atsign = args->atsign;
collon = args->collon;
if (radix == 0) {
radix = args->arguments[0].value;
if (radix < 2 || radix > 36) {
args->offset = args->arguments[0].offset;
generic_error(args, GENERIC_RADIX);
}
}
mincol = args->arguments[i++].value;
padchar = args->arguments[i++].value;
commachar = args->arguments[i++].value;
commainterval = args->arguments[i++].value;
LispFormatInteger(stream, object, radix, atsign, collon,
mincol, padchar, commachar, commainterval);
}
else
format_object(stream, object);
}
static void
format_radix_special(LispObj *stream, LispObj *object, FmtArgs *args)
{
if (FIXNUMP(object)) {
if (args->atsign)
format_roman(stream, object, args);
else
format_english(stream, object, args);
}
else
format_object(stream, object);
}
static void
format_roman(LispObj *stream, LispObj *object, FmtArgs *args)
{
long value = 0;
int cando, new_roman = args->collon == 0;
if (FIXNUMP(object)) {
value = FIXNUM_VALUE(object);
if (new_roman)
cando = value >= 1 && value <= 3999;
else
cando = value >= 1 && value <= 4999;
}
else
cando = 0;
if (cando)
LispFormatRomanInteger(stream, value, new_roman);
else
format_object(stream, object);
}
static void
format_english(LispObj *stream, LispObj *object, FmtArgs *args)
{
int cando;
long number = 0;
if (FIXNUMP(object)) {
number = FIXNUM_VALUE(object);
cando = number >= -999999999 && number <= 999999999;
}
else
cando = 0;
if (cando)
LispFormatEnglishInteger(stream, number, args->collon);
else
format_object(stream, object);
}
static void
format_character(LispObj *stream, LispObj *object, FmtArgs *args)
{
if (SCHARP(object))
LispFormatCharacter(stream, object, args->atsign, args->collon);
else
format_object(stream, object);
}
static void
format_fixed_float(LispObj *stream, LispObj *object, FmtArgs *args)
{
if (FLOATP(object))
LispFormatFixedFloat(stream, object, args->atsign,
args->arguments[0].value,
IF_SPECIFIED(args->arguments[1]),
args->arguments[2].value,
args->arguments[3].value,
args->arguments[4].value);
else
format_object(stream, object);
}
static void
format_exponential_float(LispObj *stream, LispObj *object, FmtArgs *args)
{
if (FLOATP(object))
LispFormatExponentialFloat(stream, object, args->atsign,
args->arguments[0].value,
IF_SPECIFIED(args->arguments[1]),
args->arguments[2].value,
args->arguments[3].value,
args->arguments[4].value,
args->arguments[5].value,
args->arguments[6].value);
else
format_object(stream, object);
}
static void
format_general_float(LispObj *stream, LispObj *object, FmtArgs *args)
{
if (FLOATP(object))
LispFormatGeneralFloat(stream, object, args->atsign,
args->arguments[0].value,
IF_SPECIFIED(args->arguments[1]),
args->arguments[2].value,
args->arguments[3].value,
args->arguments[4].value,
args->arguments[5].value,
args->arguments[6].value);
else
format_object(stream, object);
}
static void
format_dollar_float(LispObj *stream, LispObj *object, FmtArgs *args)
{
if (FLOATP(object))
LispFormatDollarFloat(stream, object,
args->atsign, args->collon,
args->arguments[0].value,
args->arguments[1].value,
args->arguments[2].value,
args->arguments[3].value);
else
format_object(stream, object);
}
static void
format_tabulate(LispObj *stream, FmtArgs *args)
{
int atsign = args->atsign,
colnum = args->arguments[0].value,
colinc = args->arguments[1].value,
column;
column = LispGetColumn(stream);
if (atsign) {
/* relative tabulation */
if (colnum > 0) {
LispWriteChars(stream, ' ', colnum);
column += colnum;
}
/* tabulate until at a multiple of colinc */
if (colinc > 0)
LispWriteChars(stream, ' ', colinc - (column % colinc));
}
else {
/* if colinc not specified, just move to given column */
if (colinc <= 0)
LispWriteChars(stream, ' ', column - colnum);
else {
/* always output at least colinc spaces */
do {
LispWriteChars(stream, ' ', colinc);
colnum -= colinc;
} while (colnum > column);
}
}
}
static void
format_goto(FmtInfo *info)
{
int count, num_arguments;
LispObj *object, *arguments;
/* number of arguments to ignore or goto offset */
count = info->args.arguments[0].value;
if (count < 0)
generic_error(&(info->args), GENERIC_NEGATIVE);
if (info->args.atsign) {
/* absolute goto */
/* if not specified, defaults to zero */
if (!(info->args.arguments[0].specified))
count = 0;
/* if offset too large */
if (count > info->total_arguments)
parse_error(&(info->args), PARSE_NOARGSLEFT);
else if (count != info->total_arguments - *(info->num_arguments)) {
/* calculate new parameters */
object = NIL;
arguments = info->base_arguments;
num_arguments = info->total_arguments - count;
for (; count > 0; count--, arguments = CDR(arguments))
object = CAR(arguments);
/* update format information */
*(info->object) = object;
*(info->arguments) = arguments;
*(info->num_arguments) = num_arguments;
}
}
else if (count) {
/* relative goto, ignore or go back count arguments */
/* prepare to update parameters */
arguments = *(info->arguments);
num_arguments = *(info->num_arguments);
/* go back count arguments? */
if (info->args.collon)
count = -count;
num_arguments -= count;
if (count > 0) {
if (count > *(info->num_arguments))
parse_error(&(info->args), PARSE_NOARGSLEFT);
object = *(info->object);
for (; count > 0; count--, arguments = CDR(arguments))
object = CAR(arguments);
}
else { /* count < 0 */
if (info->total_arguments + count - *(info->num_arguments) < 0)
parse_error(&(info->args), PARSE_NOARGSLEFT);
object = NIL;
arguments = info->base_arguments;
for (count = 0; count < info->total_arguments - num_arguments;
count++, arguments = CDR(arguments))
object = CAR(arguments);
}
/* update format parameters */
*(info->object) = object;
*(info->arguments) = arguments;
*(info->num_arguments) = num_arguments;
}
}
static void
format_indirection(LispObj *stream, LispObj *format, FmtInfo *info)
{
char *string;
LispObj *object;
FmtInfo indirect_info;
if (!STRINGP(format))
generic_error(&(info->args), GENERIC_BADSTRING);
string = THESTR(format);
/* most information is the same */
memcpy(&indirect_info, info, sizeof(FmtInfo));
/* set new format string */
indirect_info.args.base = indirect_info.args.format = string;
indirect_info.format = &string;
if (info->args.atsign) {
/* use current arguments */
/* do the indirect format */
LispFormat(stream, &indirect_info);
}
else {
/* next argument is the recursive call arguments */
int num_arguments;
/* it is valid to not have a list following string, as string may
* not have format directives */
if (CONSP(*(indirect_info.arguments)))
object = CAR(*(indirect_info.arguments));
else
object = NIL;
if (!LISTP(object) || !CONSP(*(info->arguments)))
generic_error(&(info->args), GENERIC_BADLIST);
/* update information now */
*(info->object) = object;
*(info->arguments) = CDR(*(info->arguments));
*(info->num_arguments) -= 1;
/* set arguments for recursive call */
indirect_info.base_arguments = object;
indirect_info.arguments = &object;
for (num_arguments = 0; CONSP(object); object = CDR(object))
++num_arguments;
/* note that indirect_info.arguments is a pointer to "object",
* keep it pointing to the correct object */
object = indirect_info.base_arguments;
indirect_info.total_arguments = num_arguments;
indirect_info.num_arguments = &num_arguments;
/* do the indirect format */
LispFormat(stream, &indirect_info);
}
}
/* update pointers to a list of format strings:
* for '(' and '{' only one list is required
* for '[' and '<' more than one may be returned
* has_default is only meaningful for '[' and '<'
* comma_width and line_width are only meaningful to '<', and
* only valid if has_default set
* if the string is finished prematurely, LispDestroy is called
* format_ptr is updated to the correct pointer in the "main" format string
*/
static void
list_formats(FmtInfo *info, int command, char **format_ptr,
char ***format_list, int *format_count, int *has_default,
int *comma_width, int *line_width)
{
/* instead of processing the directives recursively, just separate the
* input formats in separate strings, then see if one of then need to
* be used */
FmtArgs args;
int counters[] = { 0, 0, 0, 0};
/* '[', '(', '{', '<' */
char *format, *next_format, *start, **formats;
int num_formats, format_index, separator, add_format;
/* initialize */
formats = NULL;
num_formats = format_index = 0;
if (has_default != NULL)
*has_default = 0;
if (comma_width != NULL)
*comma_width = 0;
if (line_width != NULL)
*line_width = 0;
format = start = next_format = *format_ptr;
switch (command) {
case '[': counters[0] = 1; format_index = 0; break;
case '(': counters[1] = 1; format_index = 1; break;
case '{': counters[2] = 1; format_index = 2; break;
case '<': counters[3] = 1; format_index = 3; break;
}
#define LIST_FORMATS_ADD 1
#define LIST_FORMATS_DONE 2
/* fill list of format options to conditional */
while (*format) {
if (*format == '~') {
separator = add_format = 0;
args.format = format + 1;
next_format = parse_arguments(format + 1, &args, NULL, NULL, NULL);
switch (args.command) {
case '[': ++counters[0]; break;
case ']': --counters[0]; break;
case '(': ++counters[1]; break;
case ')': --counters[1]; break;
case '{': ++counters[2]; break;
case '}': --counters[2]; break;
case '<': ++counters[3]; break;
case '>': --counters[3]; break;
case ';': separator = 1; break;
}
/* check if a new format string must be added */
if (separator && counters[format_index] == 1 &&
(command == '[' || command == '<'))
add_format = LIST_FORMATS_ADD;
else if (counters[format_index] == 0)
add_format = LIST_FORMATS_DONE;
if (add_format) {
int length = format - start;
formats = LispRealloc(formats,
(num_formats + 1) * sizeof(char*));
formats[num_formats] = LispMalloc(length + 1);
strncpy(formats[num_formats], start, length);
formats[num_formats][length] = '\0';
++num_formats;
/* loop finished? */
if (add_format == LIST_FORMATS_DONE)
break;
else if (command == '[' && has_default != NULL)
/* will be set only for the last parameter, what is
* expected, just don't warn about it in the incorrect
* place */
*has_default = args.collon != 0;
else if (command == '<' && num_formats == 1) {
/* if the first parameter to '<', there may be overrides
* to comma-width and line-width */
if (args.collon && has_default != NULL) {
*has_default = 1;
if (comma_width != NULL &&
args.arguments[0].specified &&
!args.arguments[0].achar)
*comma_width = args.arguments[0].value;
if (line_width != NULL &&
args.arguments[1].specified &&
!args.arguments[1].achar)
*line_width = args.arguments[1].value;
}
}
start = next_format;
}
format = next_format;
}
else
++format;
}
/* check if format string did not finish prematurely */
if (counters[format_index] != 0) {
char error_message[64];
sprintf(error_message, "expecting ~%c", command);
format_error(&(info->args), error_message);
}
/* update pointers */
*format_list = formats;
*format_count = num_formats;
*format_ptr = next_format;
}
static void
free_formats(char **formats, int num_formats)
{
if (num_formats) {
while (--num_formats >= 0)
LispFree(formats[num_formats]);
LispFree(formats);
}
}
static void
format_case_conversion(LispObj *stream, FmtInfo *info)
{
GC_ENTER();
LispObj *string;
FmtInfo case_info;
char *str, *ptr;
char *format, *next_format, **formats;
int atsign, collon, num_formats, length;
atsign = info->args.atsign;
collon = info->args.collon;
/* output to a string, before case conversion */
string = LSTRINGSTREAM("", STREAM_READ | STREAM_WRITE, 0);
GC_PROTECT(string);
/* most information is the same */
memcpy(&case_info, info, sizeof(FmtInfo));
/* list formats */
next_format = *(info->format);
list_formats(info, '(', &next_format, &formats, &num_formats,
NULL, NULL, NULL);
/* set new format string */
format = formats[0];
case_info.args.base = case_info.args.format = format;
case_info.format = &format;
/* format text to string */
LispFormat(string, &case_info);
str = ptr = LispGetSstring(SSTREAMP(string), &length);
/* do case conversion */
if (!atsign && !collon) {
/* convert all upercase to lowercase */
for (; *ptr; ptr++) {
if (isupper(*ptr))
*ptr = tolower(*ptr);
}
}
else if (atsign && collon) {
/* convert all lowercase to upercase */
for (; *ptr; ptr++) {
if (islower(*ptr))
*ptr = toupper(*ptr);
}
}
else {
int upper = 1;
/* skip non-alphanumeric characters */
for (; *ptr; ptr++)
if (isalnum(*ptr))
break;
/* capitalize words */
for (; *ptr; ptr++) {
if (isalnum(*ptr)) {
if (upper) {
if (islower(*ptr))
*ptr = toupper(*ptr);
upper = 0;
}
else if (isupper(*ptr))
*ptr = tolower(*ptr);
}
else
upper = collon;
/* if collon, capitalize all words, else just first word */
}
}
/* output case converted string */
LispWriteStr(stream, str, length);
/* temporary string stream is not necessary anymore */
GC_LEAVE();
/* free temporary memory */
free_formats(formats, num_formats);
/* this information always updated */
*(info->format) = next_format;
}
static void
format_conditional(LispObj *stream, FmtInfo *info)
{
LispObj *object, *arguments;
char *format, *next_format, **formats;
int choice, num_formats, has_default, num_arguments;
/* save information that may change */
object = *(info->object);
arguments = *(info->arguments);
num_arguments = *(info->num_arguments);
/* initialize */
choice = -1;
next_format = *(info->format);
/* list formats */
list_formats(info, '[',
&next_format, &formats, &num_formats, &has_default, NULL, NULL);
/* ~:[false;true] */
if (info->args.collon) {
/* one argument always consumed */
if (!CONSP(arguments))
parse_error(&(info->args), PARSE_NOARGSLEFT);
object = CAR(arguments);
arguments = CDR(arguments);
--num_arguments;
choice = object == NIL ? 0 : 1;
}
/* ~@[true] */
else if (info->args.atsign) {
/* argument consumed only if nil, but one must be available */
if (!CONSP(arguments))
parse_error(&(info->args), PARSE_NOARGSLEFT);
if (CAR(arguments) != NIL)
choice = 0;
else {
object = CAR(arguments);
arguments = CDR(arguments);
--num_arguments;
}
}
/* ~n[...~] */
else if (info->args.arguments[0].specified)
/* no arguments consumed */
choice = info->args.arguments[0].value;
/* ~[...~] */
else {
/* one argument consumed, it is the index in the available formats */
if (!CONSP(arguments))
parse_error(&(info->args), PARSE_NOARGSLEFT);
object = CAR(arguments);
arguments = CDR(arguments);
--num_arguments;
/* no error if it isn't a number? */
if (FIXNUMP(object))
choice = FIXNUM_VALUE(object);
}
/* update anything that may have changed */
*(info->object) = object;
*(info->arguments) = arguments;
*(info->num_arguments) = num_arguments;
/* if choice is out of range check if there is a default choice */
if (has_default && (choice < 0 || choice >= num_formats))
choice = num_formats - 1;
/* if one of the formats must be parsed */
if (choice >= 0 && choice < num_formats) {
FmtInfo conditional_info;
/* most information is the same */
memcpy(&conditional_info, info, sizeof(FmtInfo));
/* set new format string */
format = formats[choice];
conditional_info.args.base = conditional_info.args.format = format;
conditional_info.format = &format;
/* do the conditional format */
LispFormat(stream, &conditional_info);
}
/* free temporary memory */
free_formats(formats, num_formats);
/* this information always updated */
*(info->format) = next_format;
}
static void
format_iterate(LispObj *stream, FmtInfo *info)
{
FmtInfo iterate_info;
LispObj *object, *arguments, *iarguments, *iobject;
char *format, *next_format, *loop_format, **formats;
int num_arguments, iterate, iterate_max, has_max, has_min, inum_arguments,
num_formats;
/* save information that may change */
object = *(info->object);
arguments = *(info->arguments);
num_arguments = *(info->num_arguments);
/* initialize */
iterate = has_min = 0;
next_format = *(info->format);
/* if has_max set, iterate at most iterate_max times */
has_max = info->args.arguments[0].specified;
iterate_max = info->args.arguments[0].value;
/* list formats */
list_formats(info, '{', &next_format, &formats, &num_formats,
NULL, NULL, NULL);
loop_format = formats[0];
/* most information is the same */
memcpy(&iterate_info, info, sizeof(FmtInfo));
/* ~{...~} */
if (!info->args.atsign && !info->args.collon) {
/* next argument is the argument list for the iteration */
/* fetch argument list, must exist */
if (!CONSP(arguments))
parse_error(&(info->args), PARSE_NOARGSLEFT);
iarguments = object = CAR(arguments);
object = CAR(arguments);
arguments = CDR(arguments);
--num_arguments;
inum_arguments = 0;
if (CONSP(object)) {
/* count arguments to format */
for (iobject = object; CONSP(iobject); iobject = CDR(iobject))
++inum_arguments;
}
else if (object != NIL)
generic_error(&(info->args), GENERIC_BADLIST);
iobject = NIL;
/* set new arguments to recursive calls */
iarguments = object;
iterate_info.base_arguments = iarguments;
iterate_info.total_arguments = inum_arguments;
iterate_info.object = &iobject;
iterate_info.arguments = &iarguments;
iterate_info.num_arguments = &inum_arguments;
/* iterate */
for (;; iterate++) {
/* if maximum iterations done or all arguments consumed */
if (has_max && iterate > iterate_max)
break;
else if (inum_arguments == 0 && (!has_min || iterate > 0))
break;
format = loop_format;
/* set new format string */
iterate_info.args.base = iterate_info.args.format = format;
iterate_info.format = &format;
/* information for possible ~^, in this case ~:^ is a noop */
iterate_info.iteration = ITERATION_NORMAL;
/* do the format */
LispFormat(stream, &iterate_info);
/* check for forced loop break */
if (iterate_info.upandout & UPANDOUT_HASH)
break;
}
}
/* ~:@{...~} */
else if (info->args.atsign && info->args.collon) {
/* every following argument is the argument list for the iteration */
/* iterate */
for (;; iterate++) {
/* if maximum iterations done or all arguments consumed */
if (has_max && iterate > iterate_max)
break;
else if (num_arguments == 0 && (!has_min || iterate > 0))
break;
/* fetch argument list, must exist */
if (!CONSP(arguments))
parse_error(&(info->args), PARSE_NOARGSLEFT);
iarguments = object = CAR(arguments);
object = CAR(arguments);
arguments = CDR(arguments);
--num_arguments;
inum_arguments = 0;
if (CONSP(object)) {
/* count arguments to format */
for (iobject = object; CONSP(iobject); iobject = CDR(iobject))
++inum_arguments;
}
else if (object != NIL)
generic_error(&(info->args), GENERIC_BADLIST);
iobject = NIL;
/* set new arguments to recursive calls */
iarguments = object;
iterate_info.base_arguments = iarguments;
iterate_info.total_arguments = inum_arguments;
iterate_info.object = &iobject;
iterate_info.arguments = &iarguments;
iterate_info.num_arguments = &inum_arguments;
format = loop_format;
/* set new format string */
iterate_info.args.base = iterate_info.args.format = format;
iterate_info.format = &format;
/* information for possible ~^ */
iterate_info.iteration =
num_arguments > 0 ? ITERATION_NORMAL : ITERATION_LAST;
/* do the format */
LispFormat(stream, &iterate_info);
/* check for forced loop break */
if (iterate_info.upandout & UPANDOUT_HASH)
break;
}
}
/* ~:{...~} */
else if (info->args.collon) {
/* next argument is a list of lists */
LispObj *sarguments, *sobject;
int snum_arguments;
/* fetch argument list, must exist */
if (!CONSP(arguments))
parse_error(&(info->args), PARSE_NOARGSLEFT);
sarguments = object = CAR(arguments);
object = CAR(arguments);
arguments = CDR(arguments);
--num_arguments;
snum_arguments = 0;
if (CONSP(object)) {
/* count arguments to format */
for (sobject = object; CONSP(sobject); sobject = CDR(sobject))
++snum_arguments;
}
else
generic_error(&(info->args), GENERIC_BADLIST);
/* iterate */
for (;; iterate++) {
/* if maximum iterations done or all arguments consumed */
if (has_max && iterate > iterate_max)
break;
else if (snum_arguments == 0 && (!has_min || iterate > 0))
break;
/* fetch argument list, must exist */
if (!CONSP(sarguments))
parse_error(&(info->args), PARSE_NOARGSLEFT);
iarguments = sobject = CAR(sarguments);
sobject = CAR(sarguments);
sarguments = CDR(sarguments);
--snum_arguments;
inum_arguments = 0;
if (CONSP(object)) {
/* count arguments to format */
for (iobject = sobject; CONSP(iobject); iobject = CDR(iobject))
++inum_arguments;
}
else if (sobject != NIL)
generic_error(&(info->args), GENERIC_BADLIST);
iobject = NIL;
/* set new arguments to recursive calls */
iarguments = sobject;
iterate_info.base_arguments = iarguments;
iterate_info.total_arguments = inum_arguments;
iterate_info.object = &iobject;
iterate_info.arguments = &iarguments;
iterate_info.num_arguments = &inum_arguments;
format = loop_format;
/* set new format string */
iterate_info.args.base = iterate_info.args.format = format;
iterate_info.format = &format;
/* information for possible ~^ */
iterate_info.iteration =
snum_arguments > 0 ? ITERATION_NORMAL : ITERATION_LAST;
/* do the format */
LispFormat(stream, &iterate_info);
/* check for forced loop break */
if (iterate_info.upandout & UPANDOUT_HASH)
break;
}
}
/* ~@{...~} */
else if (info->args.atsign) {
/* current argument list is used */
/* set new arguments to recursive calls */
iterate_info.base_arguments = info->base_arguments;
iterate_info.total_arguments = info->total_arguments;
iterate_info.object = &object;
iterate_info.arguments = &arguments;
iterate_info.num_arguments = &num_arguments;
for (;; iterate++) {
/* if maximum iterations done or all arguments consumed */
if (has_max && iterate > iterate_max)
break;
else if (num_arguments == 0 && (!has_min || iterate > 0))
break;
format = loop_format;
/* set new format string */
iterate_info.args.base = iterate_info.args.format = format;
iterate_info.format = &format;
/* information for possible ~^, in this case ~:^ is a noop */
iterate_info.iteration = ITERATION_NORMAL;
/* do the format */
LispFormat(stream, &iterate_info);
/* check for forced loop break */
if (iterate_info.upandout & UPANDOUT_HASH)
break;
}
}
/* free temporary memory */
free_formats(formats, num_formats);
/* update anything that may have changed */
*(info->object) = object;
*(info->arguments) = arguments;
*(info->num_arguments) = num_arguments;
/* this information always updated */
*(info->format) = next_format;
}
static void
format_justify(LispObj *stream, FmtInfo *info)
{
GC_ENTER();
FmtInfo justify_info;
char **formats, *format, *next_format;
const char *str;
LispObj *string, *strings = NIL, *cons;
int atsign = info->args.atsign,
collon = info->args.collon,
mincol = info->args.arguments[0].value,
colinc = info->args.arguments[1].value,
minpad = info->args.arguments[2].value,
padchar = info->args.arguments[3].value;
int i, k, total_length, length, padding, num_formats, has_default,
comma_width, line_width, size, extra;
next_format = *(info->format);
/* list formats */
list_formats(info, '<', &next_format, &formats, &num_formats,
&has_default, &comma_width, &line_width);
/* initialize list of strings streams */
if (num_formats) {
string = LSTRINGSTREAM("", STREAM_READ | STREAM_WRITE, 0);
strings = cons = CONS(string, NIL);
GC_PROTECT(strings);
for (i = 1; i < num_formats; i++) {
string = LSTRINGSTREAM("", STREAM_READ | STREAM_WRITE, 0);
RPLACD(cons, CONS(string, NIL));
cons = CDR(cons);
}
}
/* most information is the same */
memcpy(&justify_info, info, sizeof(FmtInfo));
/* loop formating strings */
for (i = 0, cons = strings; i < num_formats; i++, cons = CDR(cons)) {
/* set new format string */
format = formats[i];
justify_info.args.base = justify_info.args.format = format;
justify_info.format = &format;
/* format string, maybe consuming arguments */
LispFormat(CAR(cons), &justify_info);
/* if format was aborted, it is discarded */
if (justify_info.upandout)
RPLACA(cons, NIL);
/* check if the entire "main" iteration must be aborted */
if (justify_info.upandout & UPANDOUT_COLLON) {
for (cons = CDR(cons); i < num_formats; i++, cons = CDR(cons))
RPLACA(cons, NIL);
break;
}
}
/* free temporary format strings */
free_formats(formats, num_formats);
/* remove aborted formats */
/* first remove leading discarded formats */
if (CAR(strings) == NIL) {
while (CAR(strings) == NIL) {
strings = CDR(strings);
--num_formats;
}
/* keep strings gc protected, discarding first entries */
lisp__data.protect.objects[gc__protect] = strings;
}
/* now remove intermediary discarded formats */
cons = strings;
while (CONSP(cons)) {
if (CONSP(CDR(cons)) && CAR(CDR(cons)) == NIL) {
RPLACD(cons, CDR(CDR(cons)));
--num_formats;
}
else
cons = CDR(cons);
}
/* calculate total length required for output */
if (has_default)
cons = CDR(strings); /* if has_defaults, strings is surely a list */
else
cons = strings;
for (total_length = 0; CONSP(cons); cons = CDR(cons))
total_length += SSTREAMP(CAR(cons))->length;
/* initialize pointer to string streams */
if (has_default)
cons = CDR(strings);
else
cons = strings;
/* check if padding will need to be printed */
extra = 0;
padding = mincol - total_length;
if (padding < 0)
k = padding = 0;
else {
int num_fields = num_formats - (has_default != 0);
if (num_fields > 1) {
/* check if padding is distributed in num_fields or
* num_fields - 1 steps */
if (!collon)
--num_fields;
}
if (num_fields)
k = padding / num_fields;
else
k = padding;
if (k <= 0)
k = colinc;
else if (colinc)
k = k + (k % colinc);
extra = mincol - (num_fields * k + total_length);
if (extra < 0)
extra = 0;
}
if (padding && k < minpad) {
k = minpad;
if (colinc)
k = k + (k % colinc);
}
/* first check for the special case of only one string being justified */
if (num_formats - has_default == 1) {
if (has_default && line_width > 0 && comma_width >= 0 &&
total_length + comma_width > line_width) {
str = LispGetSstring(SSTREAMP(CAR(strings)), &size);
LispWriteStr(stream, str, size);
}
string = has_default ? CAR(CDR(strings)) : CAR(strings);
/* check if need left padding */
if (k && !atsign) {
LispWriteChars(stream, padchar, k);
k = 0;
}
/* check for centralizing text */
else if (k && atsign && collon) {
LispWriteChars(stream, padchar, k / 2 + ((k / 2) & 1));
k -= k / 2;
}
str = LispGetSstring(SSTREAMP(string), &size);
LispWriteStr(stream, str, size);
/* if any padding remaining */
if (k)
LispWriteChars(stream, padchar, k);
}
else {
LispObj *result;
int last, spaces_before, padout;
/* if has default, need to check output length */
if (has_default && line_width > 0 && comma_width >= 0) {
result = LSTRINGSTREAM("", STREAM_READ | STREAM_WRITE, 0);
GC_PROTECT(result);
}
/* else write directly to stream */
else
result = stream;
/* loop printing justified text */
/* padout controls padding for cases where padding is
* is separated in n-1 chunks, where n is the number of
* formatted strings.
*/
for (i = padout = 0; CONSP(cons); i++, cons = CDR(cons), --extra) {
string = CAR(cons);
last = !CONSP(CDR(cons));
spaces_before = (i != 0 || collon) && (!last || !atsign);
if (!spaces_before) {
/* check for special case */
if (last && atsign && collon && padding > 0) {
int spaces;
spaces = minpad > colinc ? minpad : colinc;
LispWriteChars(result, padchar, spaces + (extra > 0));
k -= spaces;
}
str = LispGetSstring(SSTREAMP(string), &size);
LispWriteStr(result, str, size);
padout = 0;
}
if (!padout)
LispWriteChars(result, padchar, k + (extra > 0));
padout = k;
/* if not first string, or if left padding specified */
if (spaces_before) {
str = LispGetSstring(SSTREAMP(string), &size);
LispWriteStr(result, str, size);
padout = 0;
}
padding -= k;
}
if (has_default && line_width > 0 && comma_width >= 0) {
length = SSTREAMP(result)->length + LispGetColumn(stream);
/* if current line is too large */
if (has_default && length + comma_width > line_width) {
str = LispGetSstring(SSTREAMP(CAR(strings)), &size);
LispWriteStr(stream, str, size);
}
/* write result to stream */
str = LispGetSstring(SSTREAMP(result), &size);
LispWriteStr(stream, str, size);
}
}
/* unprotect string streams from GC */
GC_LEAVE();
/* this information always updated */
*(info->format) = next_format;
}
static void
LispFormat(LispObj *stream, FmtInfo *info)
{
FmtArgs *args;
const FmtDefs *defs = NULL;
LispObj *object, *arguments;
char stk[256], *format, *next_format;
int length, num_arguments, code, need_update, need_argument, hash, head;
/* arguments that will be updated on function exit */
format = *(info->format);
object = *(info->object);
arguments = *(info->arguments);
num_arguments = *(info->num_arguments);
/* initialize */
length = 0;
args = &(info->args);
info->upandout = 0;
while (*format) {
if (*format == '~') {
/* flush non formatted characters */
if (length) {
LispWriteStr(stream, stk, length);
length = 0;
}
need_argument = need_update = hash = 0;
/* parse parameters */
args->format = format + 1;
next_format = parse_arguments(format + 1, args, &num_arguments,
&arguments, &code);
if (code != NOERROR)
parse_error(args, code);
/* check parameters */
switch (args->command) {
case 'A': case 'S':
defs = &AsciiDefs;
break;
case 'B': case 'O': case 'D': case 'X':
defs = &IntegerDefs;
break;
case 'R':
defs = &RadixDefs;
break;
case 'P': case 'C':
defs = &NoneDefs;
break;
case 'F':
defs = &FixedFloatDefs;
break;
case 'E': case 'G':
defs = &ExponentialFloatDefs;
break;
case '$':
defs = &DollarFloatDefs;
break;
case '%': case '&': case '|': case '~': case '\n':
defs = &OneDefs;
break;
case 'T':
defs = &TabulateDefs;
break;
case '*':
defs = &OneDefs;
break;
case '?': case '(':
defs = &NoneDefs;
break;
case ')':
/* this is never seen, processed in format_case_conversion */
format_error(args, "no match for directive ~)");
case '[':
defs = &OneDefs;
break;
case ']':
/* this is never seen, processed in format_conditional */
format_error(args, "no match for directive ~]");
case '{':
defs = &OneDefs;
break;
case '}':
/* this is never seen, processed in format_iterate */
format_error(args, "no match for directive ~}");
case '<':
defs = &AsciiDefs;
break;
case '>':
/* this is never seen, processed in format_justify */
format_error(args, "no match for directive ~>");
case ';':
/* this is never seen here */
format_error(args, "misplaced directive ~;");
case '#':
/* special handling for ~#^ */
if (*next_format == '^') {
++next_format;
hash = 1;
defs = &NoneDefs;
args->command = '^';
break;
}
parse_error(args, PARSE_BADDIRECTIVE);
case '^':
defs = &NoneDefs;
break;
default:
parse_error(args, PARSE_BADDIRECTIVE);
break;
}
merge_arguments(args, defs, &code);
if (code != NOERROR)
merge_error(args, code);
/* check if an argument is required by directive */
switch (args->command) {
case 'A': case 'S':
case 'B': case 'O': case 'D': case 'X': case 'R':
need_argument = 1;
break;
case 'P':
/* if collon specified, plural is the last print argument */
need_argument = !args->collon;
break;
case 'C':
need_argument = 1;
break;
case 'F': case 'E': case 'G': case '$':
need_argument = 1;
break;
case '%': case '&': case '|': case '~': case '\n':
break;
case 'T':
break;
case '*': /* check arguments below */
need_update = 1;
break;
case '?':
need_argument = need_update = 1;
break;
case '(': case '[': case '{': case '<':
need_update = 1;
break;
case '^':
break;
}
if (need_argument) {
if (!CONSP(arguments))
parse_error(args, PARSE_NOARGSLEFT);
object = CAR(arguments);
arguments = CDR(arguments);
--num_arguments;
}
/* will do recursive calls that change info */
if (need_update) {
*(info->format) = next_format;
*(info->object) = object;
*(info->arguments) = arguments;
*(info->num_arguments) = num_arguments;
}
/* everything seens fine, print the format directive */
switch (args->command) {
case 'A':
head = lisp__data.env.length;
LispAddVar(Oprint_escape, NIL);
++lisp__data.env.head;
format_ascii(stream, object, args);
lisp__data.env.head = lisp__data.env.length = head;
break;
case 'S':
head = lisp__data.env.length;
LispAddVar(Oprint_escape, T);
++lisp__data.env.head;
format_ascii(stream, object, args);
lisp__data.env.head = lisp__data.env.length = head;
break;
case 'B':
format_in_radix(stream, object, 2, args);
break;
case 'O':
format_in_radix(stream, object, 8, args);
break;
case 'D':
format_in_radix(stream, object, 10, args);
break;
case 'X':
format_in_radix(stream, object, 16, args);
break;
case 'R':
/* if a single argument specified */
if (args->count)
format_in_radix(stream, object, 0, args);
else
format_radix_special(stream, object, args);
break;
case 'P':
if (args->atsign) {
if (FIXNUMP(object) && FIXNUM_VALUE(object) == 1)
LispWriteChar(stream, 'y');
else
LispWriteStr(stream, "ies", 3);
}
else if (!FIXNUMP(object) || FIXNUM_VALUE(object) != 1)
LispWriteChar(stream, 's');
break;
case 'C':
format_character(stream, object, args);
break;
case 'F':
format_fixed_float(stream, object, args);
break;
case 'E':
format_exponential_float(stream, object, args);
break;
case 'G':
format_general_float(stream, object, args);
break;
case '$':
format_dollar_float(stream, object, args);
break;
case '&':
if (LispGetColumn(stream) == 0)
--args->arguments[0].value;
case '%':
LispWriteChars(stream, '\n', args->arguments[0].value);
break;
case '|':
LispWriteChars(stream, '\f', args->arguments[0].value);
break;
case '~':
LispWriteChars(stream, '~', args->arguments[0].value);
break;
case '\n':
if (!args->collon) {
if (args->atsign)
LispWriteChar(stream, '\n');
/* ignore newline and following spaces */
while (*next_format && isspace(*next_format))
++next_format;
}
break;
case 'T':
format_tabulate(stream, args);
break;
case '*':
format_goto(info);
break;
case '?':
format_indirection(stream, object, info);
need_update = 1;
break;
case '(':
format_case_conversion(stream, info);
/* next_format if far from what is set now */
next_format = *(info->format);
break;
case '[':
format_conditional(stream, info);
/* next_format if far from what is set now */
next_format = *(info->format);
break;
case '{':
format_iterate(stream, info);
/* next_format if far from what is set now */
next_format = *(info->format);
break;
case '<':
format_justify(stream, info);
/* next_format if far from what is set now */
next_format = *(info->format);
break;
case '^':
if (args->collon) {
if (hash && num_arguments == 0) {
info->upandout = UPANDOUT_HASH;
goto format_up_and_out;
}
if (info->iteration &&
info->iteration == ITERATION_NORMAL)
/* not exactly an error, but in this case,
* command is ignored */
break;
info->upandout = UPANDOUT_COLLON;
goto format_up_and_out;
}
else if (num_arguments == 0) {
info->upandout = UPANDOUT_NORMAL;
goto format_up_and_out;
}
break;
}
if (need_update) {
object = *(info->object);
arguments = *(info->arguments);
num_arguments = *(info->num_arguments);
}
format = next_format;
}
else {
if (length >= sizeof(stk)) {
LispWriteStr(stream, stk, length);
length = 0;
}
stk[length++] = *format++;
}
}
/* flush any peding output */
if (length)
LispWriteStr(stream, stk, length);
format_up_and_out:
/* update for recursive call */
*(info->format) = format;
*(info->object) = object;
*(info->arguments) = arguments;
*(info->num_arguments) = num_arguments;
}
LispObj *
Lisp_Format(LispBuiltin *builtin)
/*
format destination control-string &rest arguments
*/
{
GC_ENTER();
FmtInfo info;
LispObj *object;
char *control_string;
int num_arguments;
LispObj *stream, *format, *arguments;
arguments = ARGUMENT(2);
format = ARGUMENT(1);
stream = ARGUMENT(0);
/* check format and stream */
CHECK_STRING(format);
if (stream == NIL) { /* return a string */
stream = LSTRINGSTREAM("", STREAM_READ | STREAM_WRITE, 0);
GC_PROTECT(stream);
}
else if (stream == T || /* print directly to *standard-output* */
stream == STANDARD_OUTPUT)
stream = NIL;
else {
CHECK_STREAM(stream);
if (!stream->data.stream.writable)
LispDestroy("%s: stream %s is not writable",
STRFUN(builtin), STROBJ(stream));
}
/* count number of arguments */
for (object = arguments, num_arguments = 0; CONSP(object);
object = CDR(object), num_arguments++)
;
/* initialize plural/argument info */
object = NIL;
/* the format string */
control_string = THESTR(format);
/* arguments to recursive calls */
info.args.base = control_string;
info.base_arguments = arguments;
info.total_arguments = num_arguments;
info.format = &control_string;
info.object = &object;
info.arguments = &arguments;
info.num_arguments = &num_arguments;
info.iteration = 0;
/* format arguments */
LispFormat(stream, &info);
/* if printing to stdout */
if (stream == NIL)
LispFflush(Stdout);
/* else if printing to string-stream, return a string */
else if (stream->data.stream.type == LispStreamString) {
int length;
const char *string;
string = LispGetSstring(SSTREAMP(stream), &length);
stream = LSTRING(string, length);
}
GC_LEAVE();
return (stream);
}