diff options
Diffstat (limited to 'lisp/format.c')
-rw-r--r-- | lisp/format.c | 2121 |
1 files changed, 2121 insertions, 0 deletions
diff --git a/lisp/format.c b/lisp/format.c new file mode 100644 index 0000000..aa593d6 --- /dev/null +++ b/lisp/format.c @@ -0,0 +1,2121 @@ +/* + * 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.28 2002/11/30 23:13:11 paulo Exp $ */ + +#include "io.h" +#include "write.h" +#include "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*, 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*, 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 FmtDefs AsciiDefs = { + 4, + { + {0, 0}, /* mincol */ + {0, 1}, /* colinc */ + {0, 0}, /* minpad */ + {1, ' '}, /* padchar */ + }, +}; + +static FmtDefs IntegerDefs = { + 4, + { + {0, 0}, /* mincol */ + {1, ' '}, /* padchar */ + {1, ','}, /* commachar */ + {0, 3}, /* commainterval */ + }, +}; + +static FmtDefs RadixDefs = { + 5, + { + {0, 10}, /* radix */ + {0, 0}, /* mincol */ + {1, ' '}, /* padchar */ + {1, ','}, /* commachar */ + {0, 3}, /* commainterval */ + }, +}; + +static FmtDefs NoneDefs = { + 0, +}; + +static FmtDefs FixedFloatDefs = { + 5, + { + {0, 0}, /* w */ + {0, 16}, /* d */ + {0, 0}, /* k */ + {1, '\0'}, /* overflowchar */ + {1, ' '}, /* padchar */ + }, +}; + +static 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 FmtDefs DollarFloatDefs = { + 4, + { + {0, 2}, /* d */ + {0, 1}, /* n */ + {0, 0}, /* w */ + {1, ' '}, /* padchar */ + }, +}; + +static FmtDefs OneDefs = { + 1, + { + {0, 1}, + }, +}; + +static FmtDefs TabulateDefs = { + 2, + { + {0, 0}, /* colnum */ + {0, 1}, /* colinc */ + }, +}; + +extern LispObj *Oprint_escape; + +/* + * Implementation + */ +static void +merge_arguments(FmtArgs *arguments, FmtDefs *defaults, int *code) +{ + int count; + 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 char *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 char *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 char *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, 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, 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; + 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, check, atsign, collon, mincol, padchar, commachar, commainterval; + + i = check = (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, *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; + 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; + char *string; + + string = LispGetSstring(SSTREAMP(stream), &length); + stream = LSTRING(string, length); + } + + GC_LEAVE(); + + return (stream); +} |