15dfecf96Smrg/*
25dfecf96Smrg * Copyright (c) 2002 by The XFree86 Project, Inc.
35dfecf96Smrg *
45dfecf96Smrg * Permission is hereby granted, free of charge, to any person obtaining a
55dfecf96Smrg * copy of this software and associated documentation files (the "Software"),
65dfecf96Smrg * to deal in the Software without restriction, including without limitation
75dfecf96Smrg * the rights to use, copy, modify, merge, publish, distribute, sublicense,
85dfecf96Smrg * and/or sell copies of the Software, and to permit persons to whom the
95dfecf96Smrg * Software is furnished to do so, subject to the following conditions:
105dfecf96Smrg *
115dfecf96Smrg * The above copyright notice and this permission notice shall be included in
125dfecf96Smrg * all copies or substantial portions of the Software.
135dfecf96Smrg *
145dfecf96Smrg * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
155dfecf96Smrg * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
165dfecf96Smrg * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
175dfecf96Smrg * THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
185dfecf96Smrg * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
195dfecf96Smrg * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
205dfecf96Smrg * SOFTWARE.
215dfecf96Smrg *
225dfecf96Smrg * Except as contained in this notice, the name of the XFree86 Project shall
235dfecf96Smrg * not be used in advertising or otherwise to promote the sale, use or other
245dfecf96Smrg * dealings in this Software without prior written authorization from the
255dfecf96Smrg * XFree86 Project.
265dfecf96Smrg *
275dfecf96Smrg * Author: Paulo César Pereira de Andrade
285dfecf96Smrg */
295dfecf96Smrg
305dfecf96Smrg/* $XFree86: xc/programs/xedit/lisp/format.c,v 1.29tsi Exp $ */
315dfecf96Smrg
325dfecf96Smrg#include "lisp/io.h"
335dfecf96Smrg#include "lisp/write.h"
345dfecf96Smrg#include "lisp/format.h"
355dfecf96Smrg#include <ctype.h>
365dfecf96Smrg
375dfecf96Smrg#define MAXFMT			8
385dfecf96Smrg#define NOERROR			0
395dfecf96Smrg
405dfecf96Smrg/* parse error codes */
415dfecf96Smrg#define PARSE_2MANYPARM		1	/* too many directive parameters */
425dfecf96Smrg#define PARSE_2MANYATS		2	/* more than one @ in directive */
435dfecf96Smrg#define PARSE_2MANYCOLS		3	/* more than one : in directive */
445dfecf96Smrg#define PARSE_NOARGSLEFT	4	/* no arguments left to format */
455dfecf96Smrg#define PARSE_BADFMTARG		5	/* argument is not an integer or char */
465dfecf96Smrg#define PARSE_BADDIRECTIVE	6	/* unknown format directive */
475dfecf96Smrg#define PARSE_BADINTEGER	7	/* bad integer representation */
485dfecf96Smrg
495dfecf96Smrg/* merge error codes */
505dfecf96Smrg#define MERGE_2MANY		1	/* too many parameters to directive */
515dfecf96Smrg#define MERGE_NOCHAR		2	/* parameter must be a character */
525dfecf96Smrg#define MERGE_NOINT		3	/* parameter must be an integer */
535dfecf96Smrg
545dfecf96Smrg/* generic error codes */
555dfecf96Smrg#define GENERIC_RADIX		1	/* radix not in range 2-36 */
565dfecf96Smrg#define GENERIC_NEGATIVE	2	/* parameter is negative */
575dfecf96Smrg#define GENERIC_BADSTRING	3	/* argument is not a string */
585dfecf96Smrg#define GENERIC_BADLIST		4	/* argument is not a list */
595dfecf96Smrg
605dfecf96Smrg#define IF_SPECIFIED(arg)	(arg).specified ? &((arg).value) : NULL
615dfecf96Smrg
625dfecf96Smrg#define UPANDOUT_NORMAL		1
635dfecf96Smrg#define UPANDOUT_COLLON		2
645dfecf96Smrg#define UPANDOUT_HASH		4	/* only useful inside a ~{ iteration
655dfecf96Smrg					 * forces loop finalization. */
665dfecf96Smrg
675dfecf96Smrg#define ITERATION_NORMAL	1
685dfecf96Smrg#define ITERATION_LAST		2
695dfecf96Smrg
705dfecf96Smrg/*
715dfecf96Smrg * Types
725dfecf96Smrg */
735dfecf96Smrg/* parameter to format */
745dfecf96Smrgtypedef struct {
755dfecf96Smrg    unsigned int achar : 1;	/* value was specified as a character */
765dfecf96Smrg    unsigned int specified : 1;	/* set if value was specified */
775dfecf96Smrg    unsigned int offset : 30;	/* offset in format string, for error printing */
785dfecf96Smrg    int value;
795dfecf96Smrg} FmtArg;
805dfecf96Smrg
815dfecf96Smrg/* information about format parameters */
825dfecf96Smrgtypedef struct {
835dfecf96Smrg    unsigned int atsign : 1;	/* @ specified */
845dfecf96Smrg    unsigned int collon : 1;	/* : specified */
855dfecf96Smrg    unsigned int command : 8;	/* the format command */
865dfecf96Smrg    unsigned int count : 4;	/* number of arguments processed */
875dfecf96Smrg    unsigned int offset : 10;	/* offset in format string, for error printing */
885dfecf96Smrg    char *base, *format;
895dfecf96Smrg    FmtArg arguments[MAXFMT];
905dfecf96Smrg} FmtArgs;
915dfecf96Smrg
925dfecf96Smrg/* used for combining default format parameter values */
935dfecf96Smrgtypedef struct {
945dfecf96Smrg    int achar;
955dfecf96Smrg    int value;
965dfecf96Smrg} FmtDef;
975dfecf96Smrg
985dfecf96Smrg/* number of default format parameter values and defaults */
995dfecf96Smrgtypedef struct {
1005dfecf96Smrg    int count;
1015dfecf96Smrg    FmtDef defaults[MAXFMT];
1025dfecf96Smrg} FmtDefs;
1035dfecf96Smrg
1045dfecf96Smrg/* used on recursive calls to LispFormat */
1055dfecf96Smrgtypedef struct {
1065dfecf96Smrg    FmtArgs args;
1075dfecf96Smrg    LispObj *base_arguments;	/* pointer to first format argument */
1085dfecf96Smrg    int total_arguments;	/* number of objects in base_arguments */
1095dfecf96Smrg    char **format;		/* if need to update format string pointer */
1105dfecf96Smrg    LispObj **object;		/* CAR(arguments), for plural check */
1115dfecf96Smrg    LispObj **arguments;	/* current element of base_arguments */
1125dfecf96Smrg    int *num_arguments;		/* number of arguments after arguments */
1135dfecf96Smrg    int upandout;		/* information for recursive calls */
1145dfecf96Smrg    int iteration;		/* only set if in ~:{... or ~:@{ and in the
1155dfecf96Smrg				 * last argument list, hint for upandout */
1165dfecf96Smrg} FmtInfo;
1175dfecf96Smrg
1185dfecf96Smrg/*
1195dfecf96Smrg * Prototypes
1205dfecf96Smrg */
121f765521fSmrgstatic void merge_arguments(FmtArgs*, const FmtDefs*, int*);
1225dfecf96Smrgstatic char *parse_arguments(char*, FmtArgs*, int*, LispObj**, int*);
1235dfecf96Smrgstatic void merge_error(FmtArgs*, int);
1245dfecf96Smrgstatic void parse_error(FmtArgs*, int);
1255dfecf96Smrgstatic void generic_error(FmtArgs*, int);
126f765521fSmrgstatic void format_error(FmtArgs*, const char*);
1275dfecf96Smrg
1285dfecf96Smrgstatic int format_object(LispObj*, LispObj*);
1295dfecf96Smrg
1305dfecf96Smrgstatic void format_ascii(LispObj*, LispObj*, FmtArgs*);
1315dfecf96Smrgstatic void format_in_radix(LispObj*, LispObj*, int, FmtArgs*);
1325dfecf96Smrgstatic void format_radix_special(LispObj*, LispObj*, FmtArgs*);
1335dfecf96Smrgstatic void format_roman(LispObj*, LispObj*, FmtArgs*);
1345dfecf96Smrgstatic void format_english(LispObj*, LispObj*, FmtArgs*);
1355dfecf96Smrgstatic void format_character(LispObj*, LispObj*, FmtArgs*);
1365dfecf96Smrgstatic void format_fixed_float(LispObj*, LispObj*, FmtArgs*);
1375dfecf96Smrgstatic void format_exponential_float(LispObj*, LispObj*, FmtArgs*);
1385dfecf96Smrgstatic void format_general_float(LispObj*, LispObj*, FmtArgs*);
1395dfecf96Smrgstatic void format_dollar_float(LispObj*, LispObj*, FmtArgs*);
1405dfecf96Smrgstatic void format_tabulate(LispObj*, FmtArgs*);
1415dfecf96Smrg
1425dfecf96Smrgstatic void format_goto(FmtInfo*);
1435dfecf96Smrgstatic void format_indirection(LispObj*, LispObj*, FmtInfo*);
1445dfecf96Smrg
1455dfecf96Smrgstatic void list_formats(FmtInfo*, int, char**, char***, int*, int*, int*, int*);
1465dfecf96Smrgstatic void free_formats(char**, int);
1475dfecf96Smrg
1485dfecf96Smrgstatic void format_case_conversion(LispObj*, FmtInfo*);
1495dfecf96Smrgstatic void format_conditional(LispObj*, FmtInfo*);
1505dfecf96Smrgstatic void format_iterate(LispObj*, FmtInfo*);
1515dfecf96Smrgstatic void format_justify(LispObj*, FmtInfo*);
1525dfecf96Smrg
1535dfecf96Smrgstatic void LispFormat(LispObj*, FmtInfo*);
1545dfecf96Smrg
1555dfecf96Smrg/*
1565dfecf96Smrg * Initialization
1575dfecf96Smrg */
158f765521fSmrgstatic const FmtDefs AsciiDefs = {
1595dfecf96Smrg    4,
1605dfecf96Smrg    {
1615dfecf96Smrg	{0, 0},			/* mincol */
1625dfecf96Smrg	{0, 1},			/* colinc */
1635dfecf96Smrg	{0, 0},			/* minpad */
1645dfecf96Smrg	{1, ' '},		/* padchar */
1655dfecf96Smrg    },
1665dfecf96Smrg};
1675dfecf96Smrg
168f765521fSmrgstatic const FmtDefs IntegerDefs = {
1695dfecf96Smrg    4,
1705dfecf96Smrg    {
1715dfecf96Smrg	{0, 0},			/* mincol */
1725dfecf96Smrg	{1, ' '},		/* padchar */
1735dfecf96Smrg	{1, ','},		/* commachar */
1745dfecf96Smrg	{0, 3},			/* commainterval */
1755dfecf96Smrg    },
1765dfecf96Smrg};
1775dfecf96Smrg
178f765521fSmrgstatic const FmtDefs RadixDefs = {
1795dfecf96Smrg    5,
1805dfecf96Smrg    {
1815dfecf96Smrg	{0, 10},		/* radix */
1825dfecf96Smrg	{0, 0},			/* mincol */
1835dfecf96Smrg	{1, ' '},		/* padchar */
1845dfecf96Smrg	{1, ','},		/* commachar */
1855dfecf96Smrg	{0, 3},			/* commainterval */
1865dfecf96Smrg    },
1875dfecf96Smrg};
1885dfecf96Smrg
189f765521fSmrgstatic const FmtDefs NoneDefs = {
1905dfecf96Smrg    0,
1915dfecf96Smrg};
1925dfecf96Smrg
193f765521fSmrgstatic const FmtDefs FixedFloatDefs = {
1945dfecf96Smrg    5,
1955dfecf96Smrg    {
1965dfecf96Smrg	{0, 0},			/* w */
1975dfecf96Smrg	{0, 16},		/* d */
1985dfecf96Smrg	{0, 0},			/* k */
1995dfecf96Smrg	{1, '\0'},		/* overflowchar */
2005dfecf96Smrg	{1, ' '},		/* padchar */
2015dfecf96Smrg    },
2025dfecf96Smrg};
2035dfecf96Smrg
204f765521fSmrgstatic const FmtDefs ExponentialFloatDefs = {
2055dfecf96Smrg    7,
2065dfecf96Smrg    {
2075dfecf96Smrg	{0, 0},			/* w */
2085dfecf96Smrg	{0, 16},		/* d */
2095dfecf96Smrg	{0, 0},			/* e */
2105dfecf96Smrg	{0, 1},			/* k */
2115dfecf96Smrg	{1, '\0'},		/* overflowchar */
2125dfecf96Smrg	{1, ' '},		/* padchar */
2135dfecf96Smrg	{1, 'E'},		/* exponentchar */
2145dfecf96Smrg	/* XXX if/when more than one float format,
2155dfecf96Smrg	 * should default to object type */
2165dfecf96Smrg    },
2175dfecf96Smrg};
2185dfecf96Smrg
219f765521fSmrgstatic const FmtDefs DollarFloatDefs = {
2205dfecf96Smrg    4,
2215dfecf96Smrg    {
2225dfecf96Smrg	{0, 2},			/* d */
2235dfecf96Smrg	{0, 1},			/* n */
2245dfecf96Smrg	{0, 0},			/* w */
2255dfecf96Smrg	{1, ' '},		/* padchar */
2265dfecf96Smrg    },
2275dfecf96Smrg};
2285dfecf96Smrg
229f765521fSmrgstatic const FmtDefs OneDefs = {
2305dfecf96Smrg    1,
2315dfecf96Smrg    {
2325dfecf96Smrg	{0, 1},
2335dfecf96Smrg    },
2345dfecf96Smrg};
2355dfecf96Smrg
236f765521fSmrgstatic const FmtDefs TabulateDefs = {
2375dfecf96Smrg    2,
2385dfecf96Smrg    {
2395dfecf96Smrg	{0, 0},			/* colnum */
2405dfecf96Smrg	{0, 1},			/* colinc */
2415dfecf96Smrg    },
2425dfecf96Smrg};
2435dfecf96Smrg
2445dfecf96Smrgextern LispObj *Oprint_escape;
2455dfecf96Smrg
2465dfecf96Smrg/*
2475dfecf96Smrg * Implementation
2485dfecf96Smrg */
2495dfecf96Smrgstatic void
250f765521fSmrgmerge_arguments(FmtArgs *arguments, const FmtDefs *defaults, int *code)
2515dfecf96Smrg{
2525dfecf96Smrg    int count;
253f765521fSmrg    const FmtDef *defaul;
2545dfecf96Smrg    FmtArg *argument;
2555dfecf96Smrg
2565dfecf96Smrg    defaul = &(defaults->defaults[0]);
2575dfecf96Smrg    argument = &(arguments->arguments[0]);
2585dfecf96Smrg    for (count = 0; count < defaults->count; count++, argument++, defaul++) {
2595dfecf96Smrg	if (count >= arguments->count)
2605dfecf96Smrg	    argument->specified = 0;
2615dfecf96Smrg	if (argument->specified) {
2625dfecf96Smrg	    if (argument->achar != defaul->achar) {
2635dfecf96Smrg		*code = defaul->achar ? MERGE_NOCHAR : MERGE_NOINT;
2645dfecf96Smrg		arguments->offset = argument->offset;
2655dfecf96Smrg		return;
2665dfecf96Smrg	    }
2675dfecf96Smrg	}
2685dfecf96Smrg	else {
2695dfecf96Smrg	    argument->specified = 0;
2705dfecf96Smrg	    argument->achar = defaul->achar;
2715dfecf96Smrg	    argument->value = defaul->value;
2725dfecf96Smrg	}
2735dfecf96Smrg    }
2745dfecf96Smrg
2755dfecf96Smrg    /* check if extra arguments were provided */
2765dfecf96Smrg    if (arguments->count > defaults->count)
2775dfecf96Smrg	*code = MERGE_2MANY;
2785dfecf96Smrg}
2795dfecf96Smrg
2805dfecf96Smrg/* the pointer arguments may be null, useful when just testing/parsing
2815dfecf96Smrg * the directive parameters */
2825dfecf96Smrgstatic char *
2835dfecf96Smrgparse_arguments(char *format, FmtArgs *arguments,
2845dfecf96Smrg		int *num_objects, LispObj **objects, int *code)
2855dfecf96Smrg{
2865dfecf96Smrg    int test;
2875dfecf96Smrg    char *ptr;
2885dfecf96Smrg    FmtArg *argument;
2895dfecf96Smrg    unsigned int tmpcmd = 0;
2905dfecf96Smrg
2915dfecf96Smrg    /* initialize */
2925dfecf96Smrg    test = objects == NULL || code == NULL || num_objects == NULL;
2935dfecf96Smrg    ptr = format;
2945dfecf96Smrg    argument = &(arguments->arguments[0]);
2955dfecf96Smrg    arguments->atsign = arguments->collon = arguments->command = 0;
2965dfecf96Smrg
2975dfecf96Smrg    /* parse format parameters */
2985dfecf96Smrg    for (arguments->count = 0;; arguments->count++) {
2995dfecf96Smrg	arguments->offset = ptr - format + 1;
3005dfecf96Smrg	if (arguments->count >= MAXFMT) {
3015dfecf96Smrg	    if (!test)
3025dfecf96Smrg		*code = PARSE_2MANYPARM;
3035dfecf96Smrg	    return (ptr);
3045dfecf96Smrg	}
3055dfecf96Smrg	if (*ptr == '\'') {		/* character parameter value */
3065dfecf96Smrg	    ++ptr;			/* skip ' */
3075dfecf96Smrg	    argument->achar = argument->specified = 1;
3085dfecf96Smrg	    argument->value = *ptr++;
3095dfecf96Smrg	}
3105dfecf96Smrg	else if (*ptr == ',') {		/* use default parameter value */
3115dfecf96Smrg	    argument->achar = 0;
3125dfecf96Smrg	    argument->specified = 0;
3135dfecf96Smrg	    /* don't increment ptr, will be incremented below */
3145dfecf96Smrg	}
3155dfecf96Smrg	else if (*ptr == '#') {		/* number of arguments is value */
3165dfecf96Smrg	    ++ptr;			/* skip # */
3175dfecf96Smrg	    argument->achar = 0;
3185dfecf96Smrg	    argument->specified = 1;
3195dfecf96Smrg	    if (!test)
3205dfecf96Smrg		argument->value = *num_objects;
3215dfecf96Smrg	}
3225dfecf96Smrg	else if (*ptr == 'v' ||
3235dfecf96Smrg		 *ptr == 'V') {		/* format object argument is value */
3245dfecf96Smrg	    LispObj *object;
3255dfecf96Smrg
3265dfecf96Smrg	    ++ptr;			/* skip V */
3275dfecf96Smrg	    if (!test) {
3285dfecf96Smrg		if (!CONSP(*objects)) {
3295dfecf96Smrg		    *code = PARSE_NOARGSLEFT;
3305dfecf96Smrg		    return (ptr);
3315dfecf96Smrg		}
3325dfecf96Smrg		object = CAR((*objects));
3335dfecf96Smrg		if (FIXNUMP(object)) {
3345dfecf96Smrg		    argument->achar = 0;
3355dfecf96Smrg		    argument->specified = 1;
3365dfecf96Smrg		    argument->value = FIXNUM_VALUE(object);
3375dfecf96Smrg		}
3385dfecf96Smrg		else if (SCHARP(object)) {
3395dfecf96Smrg		    argument->achar = argument->specified = 1;
3405dfecf96Smrg		    argument->value = SCHAR_VALUE(object);
3415dfecf96Smrg		}
3425dfecf96Smrg		else {
3435dfecf96Smrg		    *code = PARSE_BADFMTARG;
3445dfecf96Smrg		    return (ptr);
3455dfecf96Smrg		}
3465dfecf96Smrg		*objects = CDR(*objects);
3475dfecf96Smrg		--*num_objects;
3485dfecf96Smrg	    }
3495dfecf96Smrg	}
3505dfecf96Smrg	else if (isdigit(*ptr) ||
3515dfecf96Smrg		*ptr == '-' || *ptr == '+') {	/* integer parameter value */
3525dfecf96Smrg	    int sign;
3535dfecf96Smrg
3545dfecf96Smrg	    argument->achar = 0;
3555dfecf96Smrg	    argument->specified = 1;
3565dfecf96Smrg	    if (!isdigit(*ptr)) {
3575dfecf96Smrg		sign = *ptr++ == '-';
3585dfecf96Smrg	    }
3595dfecf96Smrg	    else
3605dfecf96Smrg		sign = 0;
3615dfecf96Smrg	    if (!test && !isdigit(*ptr)) {
3625dfecf96Smrg		*code = PARSE_BADINTEGER;
3635dfecf96Smrg		return (ptr);
3645dfecf96Smrg	    }
3655dfecf96Smrg	    argument->value = *ptr++ - '0';
3665dfecf96Smrg	    while (isdigit(*ptr)) {
3675dfecf96Smrg		argument->value = (argument->value * 10) + (*ptr++ - '0');
3685dfecf96Smrg		if (argument->value > 65536) {
3695dfecf96Smrg		    if (!test) {
3705dfecf96Smrg			*code = PARSE_BADINTEGER;
3715dfecf96Smrg			return (ptr);
3725dfecf96Smrg		    }
3735dfecf96Smrg		}
3745dfecf96Smrg	    }
3755dfecf96Smrg	    if (sign)
3765dfecf96Smrg		argument->value = -argument->value;
3775dfecf96Smrg	}
3785dfecf96Smrg	else				/* no more arguments to format */
3795dfecf96Smrg	    break;
3805dfecf96Smrg
3815dfecf96Smrg	if (*ptr == ',')
3825dfecf96Smrg	    ++ptr;
3835dfecf96Smrg
3845dfecf96Smrg	/* remember offset of format parameter, for better error printing */
3855dfecf96Smrg	argument->offset = arguments->offset;
3865dfecf96Smrg	argument++;
3875dfecf96Smrg    }
3885dfecf96Smrg
3895dfecf96Smrg    /* check for extra flags */
3905dfecf96Smrg    for (;;) {
3915dfecf96Smrg	if (*ptr == '@') {		/* check for special parameter atsign */
3925dfecf96Smrg	    if (arguments->atsign) {
3935dfecf96Smrg		if (!test) {
3945dfecf96Smrg		    *code = PARSE_2MANYATS;
3955dfecf96Smrg		    return (ptr);
3965dfecf96Smrg		}
3975dfecf96Smrg	    }
3985dfecf96Smrg	    ++ptr;
3995dfecf96Smrg	    ++arguments->offset;
4005dfecf96Smrg	    arguments->atsign = 1;
4015dfecf96Smrg	}
4025dfecf96Smrg	else if (*ptr == ':') {		/* check for special parameter collon */
4035dfecf96Smrg	    if (arguments->collon) {
4045dfecf96Smrg		if (!test) {
4055dfecf96Smrg		    *code = PARSE_2MANYCOLS;
4065dfecf96Smrg		    return (ptr);
4075dfecf96Smrg		}
4085dfecf96Smrg	    }
4095dfecf96Smrg	    ++ptr;
4105dfecf96Smrg	    ++arguments->offset;
4115dfecf96Smrg	    arguments->collon = 1;
4125dfecf96Smrg	}
4135dfecf96Smrg	else				/* next value is format command */
4145dfecf96Smrg	    break;
4155dfecf96Smrg    }
4165dfecf96Smrg
4175dfecf96Smrg    if (!test)
4185dfecf96Smrg	*code = NOERROR;
4195dfecf96Smrg    arguments->command = *ptr++;
4205dfecf96Smrg    tmpcmd = arguments->command;
4215dfecf96Smrg    if (islower(tmpcmd))
4225dfecf96Smrg	arguments->command = toupper(tmpcmd);
4235dfecf96Smrg    ++arguments->offset;
4245dfecf96Smrg
4255dfecf96Smrg    return (ptr);
4265dfecf96Smrg}
4275dfecf96Smrg
4285dfecf96Smrgstatic void
4295dfecf96Smrgparse_error(FmtArgs *args, int code)
4305dfecf96Smrg{
431f765521fSmrg    static const char * const errors[] = {
4325dfecf96Smrg	NULL,
4335dfecf96Smrg	"too many parameters to directive",
4345dfecf96Smrg	"too many @ parameters",
4355dfecf96Smrg	"too many : parameters",
4365dfecf96Smrg	"no arguments left to format",
4375dfecf96Smrg	"argument is not a fixnum integer or a character",
4385dfecf96Smrg	"unknown format directive",
4395dfecf96Smrg	"parameter is not a fixnum integer",
4405dfecf96Smrg    };
4415dfecf96Smrg
4425dfecf96Smrg    format_error(args, errors[code]);
4435dfecf96Smrg}
4445dfecf96Smrg
4455dfecf96Smrgstatic void
4465dfecf96Smrgmerge_error(FmtArgs *args, int code)
4475dfecf96Smrg{
448f765521fSmrg    static const char * const errors[] = {
4495dfecf96Smrg	NULL,
4505dfecf96Smrg	"too many parameters to directive",
4515dfecf96Smrg	"argument must be a character",
4525dfecf96Smrg	"argument must be a fixnum integer",
4535dfecf96Smrg    };
4545dfecf96Smrg
4555dfecf96Smrg    format_error(args, errors[code]);
4565dfecf96Smrg}
4575dfecf96Smrg
4585dfecf96Smrgstatic void
4595dfecf96Smrggeneric_error(FmtArgs *args, int code)
4605dfecf96Smrg{
461f765521fSmrg    static const char * const errors[] = {
4625dfecf96Smrg	NULL,
4635dfecf96Smrg	"radix must be in the range 2 to 36, inclusive",
4645dfecf96Smrg	"parameter must be positive",
4655dfecf96Smrg	"argument must be a string",
4665dfecf96Smrg	"argument must be a list",
4675dfecf96Smrg    };
4685dfecf96Smrg
4695dfecf96Smrg    format_error(args, errors[code]);
4705dfecf96Smrg}
4715dfecf96Smrg
4725dfecf96Smrgstatic void
473f765521fSmrgformat_error(FmtArgs *args, const char *str)
4745dfecf96Smrg{
4755dfecf96Smrg    char *message;
4765dfecf96Smrg    int errorlen, formatlen;
4775dfecf96Smrg
4785dfecf96Smrg    /* number of bytes of format to be printed */
4795dfecf96Smrg    formatlen = (args->format - args->base) + args->offset;
4805dfecf96Smrg
4815dfecf96Smrg    /* length of specific error message */
4825dfecf96Smrg    errorlen = strlen(str) + 1;			/* plus '\n' */
4835dfecf96Smrg
4845dfecf96Smrg    /* XXX allocate string with LispMalloc,
4855dfecf96Smrg     * so that it will be freed in LispTopLevel */
4865dfecf96Smrg    message = LispMalloc(formatlen + errorlen + 1);
4875dfecf96Smrg
4885dfecf96Smrg    sprintf(message, "%s\n", str);
4895dfecf96Smrg    memcpy(message + errorlen, args->base, formatlen);
4905dfecf96Smrg    message[errorlen + formatlen] = '\0';
4915dfecf96Smrg
4925dfecf96Smrg    LispDestroy("FORMAT: %s", message);
4935dfecf96Smrg}
4945dfecf96Smrg
4955dfecf96Smrgstatic int
4965dfecf96Smrgformat_object(LispObj *stream, LispObj *object)
4975dfecf96Smrg{
4985dfecf96Smrg    int length;
4995dfecf96Smrg
5005dfecf96Smrg    length = LispWriteObject(stream, object);
5015dfecf96Smrg
5025dfecf96Smrg    return (length);
5035dfecf96Smrg}
5045dfecf96Smrg
5055dfecf96Smrgstatic void
5065dfecf96Smrgformat_ascii(LispObj *stream, LispObj *object, FmtArgs *args)
5075dfecf96Smrg{
5085dfecf96Smrg    GC_ENTER();
5095dfecf96Smrg    LispObj *string = NIL;
5105dfecf96Smrg    int length = 0,
5115dfecf96Smrg	atsign = args->atsign,
5125dfecf96Smrg	collon = args->collon,
5135dfecf96Smrg	mincol = args->arguments[0].value,
5145dfecf96Smrg	colinc = args->arguments[1].value,
5155dfecf96Smrg	minpad = args->arguments[2].value,
5165dfecf96Smrg	padchar = args->arguments[3].value;
5175dfecf96Smrg
5185dfecf96Smrg    /* check/correct arguments */
5195dfecf96Smrg    if (mincol < 0)
5205dfecf96Smrg	mincol = 0;
5215dfecf96Smrg    if (colinc < 0)
5225dfecf96Smrg	colinc = 1;
5235dfecf96Smrg    if (minpad < 0)
5245dfecf96Smrg	minpad = 0;
5255dfecf96Smrg    /* XXX pachar can be the null character? */
5265dfecf96Smrg
5275dfecf96Smrg    if (object == NIL)
5285dfecf96Smrg	length = collon ? 2 : 3;	    /* () or NIL */
5295dfecf96Smrg
5305dfecf96Smrg    /* left padding */
5315dfecf96Smrg    if (atsign) {
5325dfecf96Smrg	/* if length not yet known */
5335dfecf96Smrg	if (object == NIL) {
5345dfecf96Smrg	    string = LSTRINGSTREAM("", STREAM_READ | STREAM_WRITE, 0);
5355dfecf96Smrg	    GC_PROTECT(string);
5365dfecf96Smrg	    length = LispWriteObject(string, object);
5375dfecf96Smrg	}
5385dfecf96Smrg
5395dfecf96Smrg	/* output minpad characters at left */
5405dfecf96Smrg	if (minpad) {
5415dfecf96Smrg	    length += minpad;
5425dfecf96Smrg	    LispWriteChars(stream, padchar, minpad);
5435dfecf96Smrg	}
5445dfecf96Smrg
5455dfecf96Smrg	if (colinc) {
5465dfecf96Smrg	    /* puts colinc spaces at a time,
5475dfecf96Smrg	     * until at least mincol chars out */
5485dfecf96Smrg	    while (length < mincol) {
5495dfecf96Smrg		LispWriteChars(stream, padchar, colinc);
5505dfecf96Smrg		length += colinc;
5515dfecf96Smrg	    }
5525dfecf96Smrg	}
5535dfecf96Smrg    }
5545dfecf96Smrg
5555dfecf96Smrg    if (object == NIL) {
5565dfecf96Smrg	if (collon)
5575dfecf96Smrg	    LispWriteStr(stream, "()", 2);
5585dfecf96Smrg	else
559f14f4646Smrg	    LispWriteStr(stream,  Snil->value, 3);
5605dfecf96Smrg    }
5615dfecf96Smrg    else {
5625dfecf96Smrg	/* if string is not NIL, atsign was specified
5635dfecf96Smrg	 * and object printed to string */
5645dfecf96Smrg	if (string == NIL)
5655dfecf96Smrg	    length = format_object(stream, object);
5665dfecf96Smrg	else {
5675dfecf96Smrg	    int size;
568f765521fSmrg	    const char *str = LispGetSstring(SSTREAMP(string), &size);
5695dfecf96Smrg
5705dfecf96Smrg	    LispWriteStr(stream, str, size);
5715dfecf96Smrg	}
5725dfecf96Smrg    }
5735dfecf96Smrg
5745dfecf96Smrg    /* right padding */
5755dfecf96Smrg    if (!atsign) {
5765dfecf96Smrg	/* output minpad characters at left */
5775dfecf96Smrg	if (minpad) {
5785dfecf96Smrg	    length += minpad;
5795dfecf96Smrg	    LispWriteChars(stream, padchar, minpad);
5805dfecf96Smrg	}
5815dfecf96Smrg	if (colinc) {
5825dfecf96Smrg	    /* puts colinc spaces at a time,
5835dfecf96Smrg	     * until at least mincol chars out */
5845dfecf96Smrg	    while (length < mincol) {
5855dfecf96Smrg		LispWriteChars(stream, padchar, colinc);
5865dfecf96Smrg		length += colinc;
5875dfecf96Smrg	    }
5885dfecf96Smrg	}
5895dfecf96Smrg    }
5905dfecf96Smrg
5915dfecf96Smrg    GC_LEAVE();
5925dfecf96Smrg}
5935dfecf96Smrg
5945dfecf96Smrg/* assumes radix is 0 or in range 2 - 36 */
5955dfecf96Smrgstatic void
5965dfecf96Smrgformat_in_radix(LispObj *stream, LispObj *object, int radix, FmtArgs *args)
5975dfecf96Smrg{
5985dfecf96Smrg    if (INTEGERP(object)) {
5995dfecf96Smrg	int i, atsign, collon, mincol, padchar, commachar, commainterval;
6005dfecf96Smrg
6015dfecf96Smrg	i = (radix == 0);
6025dfecf96Smrg	atsign = args->atsign;
6035dfecf96Smrg	collon = args->collon;
6045dfecf96Smrg	if (radix == 0) {
6055dfecf96Smrg	    radix = args->arguments[0].value;
6065dfecf96Smrg	    if (radix < 2 || radix > 36) {
6075dfecf96Smrg		args->offset = args->arguments[0].offset;
6085dfecf96Smrg		generic_error(args, GENERIC_RADIX);
6095dfecf96Smrg	    }
6105dfecf96Smrg	}
6115dfecf96Smrg	mincol = args->arguments[i++].value;
6125dfecf96Smrg	padchar = args->arguments[i++].value;
6135dfecf96Smrg	commachar = args->arguments[i++].value;
6145dfecf96Smrg	commainterval = args->arguments[i++].value;
6155dfecf96Smrg
6165dfecf96Smrg	LispFormatInteger(stream, object, radix, atsign, collon,
6175dfecf96Smrg			  mincol, padchar, commachar, commainterval);
6185dfecf96Smrg    }
6195dfecf96Smrg    else
6205dfecf96Smrg	format_object(stream, object);
6215dfecf96Smrg}
6225dfecf96Smrg
6235dfecf96Smrgstatic void
6245dfecf96Smrgformat_radix_special(LispObj *stream, LispObj *object, FmtArgs *args)
6255dfecf96Smrg{
6265dfecf96Smrg    if (FIXNUMP(object)) {
6275dfecf96Smrg	if (args->atsign)
6285dfecf96Smrg	    format_roman(stream, object, args);
6295dfecf96Smrg	else
6305dfecf96Smrg	    format_english(stream, object, args);
6315dfecf96Smrg    }
6325dfecf96Smrg    else
6335dfecf96Smrg	format_object(stream, object);
6345dfecf96Smrg}
6355dfecf96Smrg
6365dfecf96Smrgstatic void
6375dfecf96Smrgformat_roman(LispObj *stream, LispObj *object, FmtArgs *args)
6385dfecf96Smrg{
6395dfecf96Smrg    long value = 0;
6405dfecf96Smrg    int cando, new_roman = args->collon == 0;
6415dfecf96Smrg
6425dfecf96Smrg    if (FIXNUMP(object)) {
6435dfecf96Smrg	value = FIXNUM_VALUE(object);
6445dfecf96Smrg	if (new_roman)
6455dfecf96Smrg	    cando = value >= 1 && value <= 3999;
6465dfecf96Smrg	else
6475dfecf96Smrg	    cando = value >= 1 && value <= 4999;
6485dfecf96Smrg    }
6495dfecf96Smrg    else
6505dfecf96Smrg	cando = 0;
6515dfecf96Smrg
6525dfecf96Smrg    if (cando)
6535dfecf96Smrg	LispFormatRomanInteger(stream, value, new_roman);
6545dfecf96Smrg    else
6555dfecf96Smrg	format_object(stream, object);
6565dfecf96Smrg}
6575dfecf96Smrg
6585dfecf96Smrgstatic void
6595dfecf96Smrgformat_english(LispObj *stream, LispObj *object, FmtArgs *args)
6605dfecf96Smrg{
6615dfecf96Smrg    int cando;
6625dfecf96Smrg    long number = 0;
6635dfecf96Smrg
6645dfecf96Smrg    if (FIXNUMP(object)) {
6655dfecf96Smrg	number = FIXNUM_VALUE(object);
6665dfecf96Smrg	cando = number >= -999999999 && number <= 999999999;
6675dfecf96Smrg    }
6685dfecf96Smrg    else
6695dfecf96Smrg	cando = 0;
6705dfecf96Smrg
6715dfecf96Smrg    if (cando)
6725dfecf96Smrg	LispFormatEnglishInteger(stream, number, args->collon);
6735dfecf96Smrg    else
6745dfecf96Smrg	format_object(stream, object);
6755dfecf96Smrg}
6765dfecf96Smrg
6775dfecf96Smrgstatic void
6785dfecf96Smrgformat_character(LispObj *stream, LispObj *object, FmtArgs *args)
6795dfecf96Smrg{
6805dfecf96Smrg    if (SCHARP(object))
6815dfecf96Smrg	LispFormatCharacter(stream, object, args->atsign, args->collon);
6825dfecf96Smrg    else
6835dfecf96Smrg	format_object(stream, object);
6845dfecf96Smrg}
6855dfecf96Smrg
6865dfecf96Smrgstatic void
6875dfecf96Smrgformat_fixed_float(LispObj *stream, LispObj *object, FmtArgs *args)
6885dfecf96Smrg{
6895dfecf96Smrg    if (FLOATP(object))
6905dfecf96Smrg	LispFormatFixedFloat(stream, object, args->atsign,
6915dfecf96Smrg			     args->arguments[0].value,
6925dfecf96Smrg			     IF_SPECIFIED(args->arguments[1]),
6935dfecf96Smrg			     args->arguments[2].value,
6945dfecf96Smrg			     args->arguments[3].value,
6955dfecf96Smrg			     args->arguments[4].value);
6965dfecf96Smrg    else
6975dfecf96Smrg	format_object(stream, object);
6985dfecf96Smrg}
6995dfecf96Smrg
7005dfecf96Smrgstatic void
7015dfecf96Smrgformat_exponential_float(LispObj *stream, LispObj *object, FmtArgs *args)
7025dfecf96Smrg{
7035dfecf96Smrg    if (FLOATP(object))
7045dfecf96Smrg	LispFormatExponentialFloat(stream, object, args->atsign,
7055dfecf96Smrg				   args->arguments[0].value,
7065dfecf96Smrg				   IF_SPECIFIED(args->arguments[1]),
7075dfecf96Smrg				   args->arguments[2].value,
7085dfecf96Smrg				   args->arguments[3].value,
7095dfecf96Smrg				   args->arguments[4].value,
7105dfecf96Smrg				   args->arguments[5].value,
7115dfecf96Smrg				   args->arguments[6].value);
7125dfecf96Smrg    else
7135dfecf96Smrg	format_object(stream, object);
7145dfecf96Smrg}
7155dfecf96Smrg
7165dfecf96Smrgstatic void
7175dfecf96Smrgformat_general_float(LispObj *stream, LispObj *object, FmtArgs *args)
7185dfecf96Smrg{
7195dfecf96Smrg    if (FLOATP(object))
7205dfecf96Smrg	LispFormatGeneralFloat(stream, object, args->atsign,
7215dfecf96Smrg				args->arguments[0].value,
7225dfecf96Smrg				IF_SPECIFIED(args->arguments[1]),
7235dfecf96Smrg				args->arguments[2].value,
7245dfecf96Smrg				args->arguments[3].value,
7255dfecf96Smrg				args->arguments[4].value,
7265dfecf96Smrg				args->arguments[5].value,
7275dfecf96Smrg				args->arguments[6].value);
7285dfecf96Smrg    else
7295dfecf96Smrg	format_object(stream, object);
7305dfecf96Smrg}
7315dfecf96Smrg
7325dfecf96Smrgstatic void
7335dfecf96Smrgformat_dollar_float(LispObj *stream, LispObj *object, FmtArgs *args)
7345dfecf96Smrg{
7355dfecf96Smrg    if (FLOATP(object))
7365dfecf96Smrg	LispFormatDollarFloat(stream, object,
7375dfecf96Smrg			      args->atsign, args->collon,
7385dfecf96Smrg			      args->arguments[0].value,
7395dfecf96Smrg			      args->arguments[1].value,
7405dfecf96Smrg			      args->arguments[2].value,
7415dfecf96Smrg			      args->arguments[3].value);
7425dfecf96Smrg    else
7435dfecf96Smrg	format_object(stream, object);
7445dfecf96Smrg}
7455dfecf96Smrg
7465dfecf96Smrgstatic void
7475dfecf96Smrgformat_tabulate(LispObj *stream, FmtArgs *args)
7485dfecf96Smrg{
7495dfecf96Smrg    int atsign = args->atsign,
7505dfecf96Smrg	colnum = args->arguments[0].value,
7515dfecf96Smrg	colinc = args->arguments[1].value,
7525dfecf96Smrg	column;
7535dfecf96Smrg
7545dfecf96Smrg    column = LispGetColumn(stream);
7555dfecf96Smrg
7565dfecf96Smrg    if (atsign) {
7575dfecf96Smrg	/* relative tabulation */
7585dfecf96Smrg	if (colnum > 0) {
7595dfecf96Smrg	    LispWriteChars(stream, ' ', colnum);
7605dfecf96Smrg	    column += colnum;
7615dfecf96Smrg	}
7625dfecf96Smrg	/* tabulate until at a multiple of colinc */
7635dfecf96Smrg	if (colinc > 0)
7645dfecf96Smrg	    LispWriteChars(stream, ' ', colinc - (column % colinc));
7655dfecf96Smrg    }
7665dfecf96Smrg    else {
7675dfecf96Smrg	/* if colinc not specified, just move to given column */
7685dfecf96Smrg	if (colinc <= 0)
7695dfecf96Smrg	    LispWriteChars(stream, ' ', column - colnum);
7705dfecf96Smrg	else {
7715dfecf96Smrg	    /* always output at least colinc spaces */
7725dfecf96Smrg	    do {
7735dfecf96Smrg		LispWriteChars(stream, ' ', colinc);
7745dfecf96Smrg		colnum -= colinc;
7755dfecf96Smrg	    } while (colnum > column);
7765dfecf96Smrg	}
7775dfecf96Smrg    }
7785dfecf96Smrg}
7795dfecf96Smrg
7805dfecf96Smrgstatic void
7815dfecf96Smrgformat_goto(FmtInfo *info)
7825dfecf96Smrg{
7835dfecf96Smrg    int count, num_arguments;
7845dfecf96Smrg    LispObj *object, *arguments;
7855dfecf96Smrg
7865dfecf96Smrg    /* number of arguments to ignore or goto offset */
7875dfecf96Smrg    count = info->args.arguments[0].value;
7885dfecf96Smrg    if (count < 0)
7895dfecf96Smrg	generic_error(&(info->args), GENERIC_NEGATIVE);
7905dfecf96Smrg
7915dfecf96Smrg    if (info->args.atsign) {
7925dfecf96Smrg	/* absolute goto */
7935dfecf96Smrg
7945dfecf96Smrg	/* if not specified, defaults to zero */
7955dfecf96Smrg	if (!(info->args.arguments[0].specified))
7965dfecf96Smrg	    count = 0;
7975dfecf96Smrg
7985dfecf96Smrg	/* if offset too large */
7995dfecf96Smrg	if (count > info->total_arguments)
8005dfecf96Smrg	    parse_error(&(info->args), PARSE_NOARGSLEFT);
8015dfecf96Smrg	else if (count != info->total_arguments - *(info->num_arguments)) {
8025dfecf96Smrg	    /* calculate new parameters */
8035dfecf96Smrg	    object = NIL;
8045dfecf96Smrg	    arguments = info->base_arguments;
8055dfecf96Smrg	    num_arguments = info->total_arguments - count;
8065dfecf96Smrg
8075dfecf96Smrg	    for (; count > 0; count--, arguments = CDR(arguments))
8085dfecf96Smrg		object = CAR(arguments);
8095dfecf96Smrg
8105dfecf96Smrg	    /* update format information */
8115dfecf96Smrg	    *(info->object) = object;
8125dfecf96Smrg	    *(info->arguments) = arguments;
8135dfecf96Smrg	    *(info->num_arguments) = num_arguments;
8145dfecf96Smrg	}
8155dfecf96Smrg    }
8165dfecf96Smrg    else if (count) {
8175dfecf96Smrg	/* relative goto, ignore or go back count arguments */
8185dfecf96Smrg
8195dfecf96Smrg	/* prepare to update parameters */
8205dfecf96Smrg	arguments = *(info->arguments);
8215dfecf96Smrg	num_arguments = *(info->num_arguments);
8225dfecf96Smrg
8235dfecf96Smrg	/* go back count arguments? */
8245dfecf96Smrg	if (info->args.collon)
8255dfecf96Smrg	    count = -count;
8265dfecf96Smrg
8275dfecf96Smrg	num_arguments -= count;
8285dfecf96Smrg
8295dfecf96Smrg	if (count > 0) {
8305dfecf96Smrg	    if (count > *(info->num_arguments))
8315dfecf96Smrg		parse_error(&(info->args), PARSE_NOARGSLEFT);
8325dfecf96Smrg
8335dfecf96Smrg	    object = *(info->object);
8345dfecf96Smrg	    for (; count > 0; count--, arguments = CDR(arguments))
8355dfecf96Smrg		object = CAR(arguments);
8365dfecf96Smrg	}
8375dfecf96Smrg	else {		/* count < 0 */
8385dfecf96Smrg	    if (info->total_arguments + count - *(info->num_arguments) < 0)
8395dfecf96Smrg		parse_error(&(info->args), PARSE_NOARGSLEFT);
8405dfecf96Smrg
8415dfecf96Smrg	    object = NIL;
8425dfecf96Smrg	    arguments = info->base_arguments;
8435dfecf96Smrg	    for (count = 0; count < info->total_arguments - num_arguments;
8445dfecf96Smrg		count++, arguments = CDR(arguments))
8455dfecf96Smrg		object = CAR(arguments);
8465dfecf96Smrg	}
8475dfecf96Smrg
8485dfecf96Smrg	/* update format parameters */
8495dfecf96Smrg	*(info->object) = object;
8505dfecf96Smrg	*(info->arguments) = arguments;
8515dfecf96Smrg	*(info->num_arguments) = num_arguments;
8525dfecf96Smrg    }
8535dfecf96Smrg}
8545dfecf96Smrg
8555dfecf96Smrgstatic void
8565dfecf96Smrgformat_indirection(LispObj *stream, LispObj *format, FmtInfo *info)
8575dfecf96Smrg{
8585dfecf96Smrg    char *string;
8595dfecf96Smrg    LispObj *object;
8605dfecf96Smrg    FmtInfo indirect_info;
8615dfecf96Smrg
8625dfecf96Smrg    if (!STRINGP(format))
8635dfecf96Smrg	generic_error(&(info->args), GENERIC_BADSTRING);
8645dfecf96Smrg    string = THESTR(format);
8655dfecf96Smrg
8665dfecf96Smrg    /* most information is the same */
8675dfecf96Smrg    memcpy(&indirect_info, info, sizeof(FmtInfo));
8685dfecf96Smrg
8695dfecf96Smrg    /* set new format string */
8705dfecf96Smrg    indirect_info.args.base = indirect_info.args.format = string;
8715dfecf96Smrg    indirect_info.format = &string;
8725dfecf96Smrg
8735dfecf96Smrg    if (info->args.atsign) {
8745dfecf96Smrg	/* use current arguments */
8755dfecf96Smrg
8765dfecf96Smrg	/* do the indirect format */
8775dfecf96Smrg	LispFormat(stream, &indirect_info);
8785dfecf96Smrg    }
8795dfecf96Smrg    else {
8805dfecf96Smrg	/* next argument is the recursive call arguments */
8815dfecf96Smrg
8825dfecf96Smrg	int num_arguments;
8835dfecf96Smrg
8845dfecf96Smrg	/* it is valid to not have a list following string, as string may
8855dfecf96Smrg	 * not have format directives */
8865dfecf96Smrg	if (CONSP(*(indirect_info.arguments)))
8875dfecf96Smrg	    object = CAR(*(indirect_info.arguments));
8885dfecf96Smrg	else
8895dfecf96Smrg	    object = NIL;
8905dfecf96Smrg
8915dfecf96Smrg	if (!LISTP(object) || !CONSP(*(info->arguments)))
8925dfecf96Smrg	    generic_error(&(info->args), GENERIC_BADLIST);
8935dfecf96Smrg
8945dfecf96Smrg	/* update information now */
8955dfecf96Smrg	*(info->object) = object;
8965dfecf96Smrg	*(info->arguments) = CDR(*(info->arguments));
8975dfecf96Smrg	*(info->num_arguments) -= 1;
8985dfecf96Smrg
8995dfecf96Smrg	/* set arguments for recursive call */
9005dfecf96Smrg	indirect_info.base_arguments = object;
9015dfecf96Smrg	indirect_info.arguments = &object;
9025dfecf96Smrg	for (num_arguments = 0; CONSP(object); object = CDR(object))
9035dfecf96Smrg	    ++num_arguments;
9045dfecf96Smrg
9055dfecf96Smrg	/* note that indirect_info.arguments is a pointer to "object",
9065dfecf96Smrg	 * keep it pointing to the correct object */
9075dfecf96Smrg	object = indirect_info.base_arguments;
9085dfecf96Smrg	indirect_info.total_arguments = num_arguments;
9095dfecf96Smrg	indirect_info.num_arguments = &num_arguments;
9105dfecf96Smrg
9115dfecf96Smrg	/* do the indirect format */
9125dfecf96Smrg	LispFormat(stream, &indirect_info);
9135dfecf96Smrg    }
9145dfecf96Smrg}
9155dfecf96Smrg
9165dfecf96Smrg/* update pointers to a list of format strings:
9175dfecf96Smrg *	for '(' and '{' only one list is required
9185dfecf96Smrg *	for '[' and '<' more than one may be returned
9195dfecf96Smrg *	has_default is only meaningful for '[' and '<'
9205dfecf96Smrg *	comma_width and line_width are only meaningful to '<', and
9215dfecf96Smrg *	    only valid if has_default set
9225dfecf96Smrg * if the string is finished prematurely, LispDestroy is called
9235dfecf96Smrg * format_ptr is updated to the correct pointer in the "main" format string
9245dfecf96Smrg */
9255dfecf96Smrgstatic void
9265dfecf96Smrglist_formats(FmtInfo *info, int command, char **format_ptr,
9275dfecf96Smrg	     char ***format_list, int *format_count, int *has_default,
9285dfecf96Smrg	     int *comma_width, int *line_width)
9295dfecf96Smrg{
9305dfecf96Smrg    /* instead of processing the directives recursively, just separate the
9315dfecf96Smrg     * input formats in separate strings, then see if one of then need to
9325dfecf96Smrg     * be used */
9335dfecf96Smrg    FmtArgs args;
9345dfecf96Smrg    int counters[] = {  0,   0,   0,   0};
9355dfecf96Smrg		    /* '[', '(', '{', '<' */
9365dfecf96Smrg    char *format, *next_format, *start, **formats;
9375dfecf96Smrg    int num_formats, format_index, separator, add_format;
9385dfecf96Smrg
9395dfecf96Smrg    /* initialize */
9405dfecf96Smrg    formats = NULL;
9415dfecf96Smrg    num_formats = format_index = 0;
9425dfecf96Smrg    if (has_default != NULL)
9435dfecf96Smrg	*has_default = 0;
9445dfecf96Smrg    if (comma_width != NULL)
9455dfecf96Smrg	*comma_width = 0;
9465dfecf96Smrg    if (line_width != NULL)
9475dfecf96Smrg	*line_width = 0;
9485dfecf96Smrg    format = start = next_format = *format_ptr;
9495dfecf96Smrg    switch (command) {
9505dfecf96Smrg	case '[': counters[0] = 1; format_index = 0; break;
9515dfecf96Smrg	case '(': counters[1] = 1; format_index = 1; break;
9525dfecf96Smrg	case '{': counters[2] = 1; format_index = 2; break;
9535dfecf96Smrg	case '<': counters[3] = 1; format_index = 3; break;
9545dfecf96Smrg    }
9555dfecf96Smrg
9565dfecf96Smrg#define	LIST_FORMATS_ADD	1
9575dfecf96Smrg#define	LIST_FORMATS_DONE	2
9585dfecf96Smrg
9595dfecf96Smrg    /* fill list of format options to conditional */
9605dfecf96Smrg    while (*format) {
9615dfecf96Smrg	if (*format == '~') {
9625dfecf96Smrg	    separator = add_format = 0;
9635dfecf96Smrg	    args.format = format + 1;
9645dfecf96Smrg	    next_format = parse_arguments(format + 1, &args, NULL, NULL, NULL);
9655dfecf96Smrg	    switch (args.command) {
9665dfecf96Smrg 		case '[': ++counters[0];    break;
9675dfecf96Smrg		case ']': --counters[0];    break;
9685dfecf96Smrg		case '(': ++counters[1];    break;
9695dfecf96Smrg		case ')': --counters[1];    break;
9705dfecf96Smrg		case '{': ++counters[2];    break;
9715dfecf96Smrg		case '}': --counters[2];    break;
9725dfecf96Smrg		case '<': ++counters[3];    break;
9735dfecf96Smrg		case '>': --counters[3];    break;
9745dfecf96Smrg		case ';': separator = 1;    break;
9755dfecf96Smrg	    }
9765dfecf96Smrg
9775dfecf96Smrg	    /* check if a new format string must be added */
9785dfecf96Smrg	    if (separator && counters[format_index] == 1 &&
9795dfecf96Smrg		(command == '[' || command == '<'))
9805dfecf96Smrg		add_format = LIST_FORMATS_ADD;
9815dfecf96Smrg	    else if (counters[format_index] == 0)
9825dfecf96Smrg		add_format = LIST_FORMATS_DONE;
9835dfecf96Smrg
9845dfecf96Smrg	    if (add_format) {
9855dfecf96Smrg		int length = format - start;
9865dfecf96Smrg
9875dfecf96Smrg		formats = LispRealloc(formats,
9885dfecf96Smrg				      (num_formats + 1) * sizeof(char*));
9895dfecf96Smrg
9905dfecf96Smrg		formats[num_formats] = LispMalloc(length + 1);
9915dfecf96Smrg		strncpy(formats[num_formats], start, length);
9925dfecf96Smrg		formats[num_formats][length] = '\0';
9935dfecf96Smrg		++num_formats;
9945dfecf96Smrg		/* loop finished? */
9955dfecf96Smrg		if (add_format == LIST_FORMATS_DONE)
9965dfecf96Smrg		    break;
9975dfecf96Smrg		else if (command == '[' && has_default != NULL)
9985dfecf96Smrg		    /* will be set only for the last parameter, what is
9995dfecf96Smrg		     * expected, just don't warn about it in the incorrect
10005dfecf96Smrg		     * place */
10015dfecf96Smrg		    *has_default = args.collon != 0;
10025dfecf96Smrg		else if (command == '<' && num_formats == 1) {
10035dfecf96Smrg		    /* if the first parameter to '<', there may be overrides
10045dfecf96Smrg		     * to comma-width and line-width */
10055dfecf96Smrg		    if (args.collon && has_default != NULL) {
10065dfecf96Smrg			*has_default = 1;
10075dfecf96Smrg			if (comma_width != NULL &&
10085dfecf96Smrg			    args.arguments[0].specified &&
10095dfecf96Smrg			    !args.arguments[0].achar)
10105dfecf96Smrg			    *comma_width = args.arguments[0].value;
10115dfecf96Smrg			if (line_width != NULL &&
10125dfecf96Smrg			    args.arguments[1].specified &&
10135dfecf96Smrg			    !args.arguments[1].achar)
10145dfecf96Smrg			    *line_width = args.arguments[1].value;
10155dfecf96Smrg		    }
10165dfecf96Smrg		}
10175dfecf96Smrg		start = next_format;
10185dfecf96Smrg	    }
10195dfecf96Smrg	    format = next_format;
10205dfecf96Smrg	}
10215dfecf96Smrg	else
10225dfecf96Smrg	    ++format;
10235dfecf96Smrg    }
10245dfecf96Smrg
10255dfecf96Smrg    /* check if format string did not finish prematurely */
10265dfecf96Smrg    if (counters[format_index] != 0) {
10275dfecf96Smrg	char error_message[64];
10285dfecf96Smrg
10295dfecf96Smrg	sprintf(error_message, "expecting ~%c", command);
10305dfecf96Smrg	format_error(&(info->args), error_message);
10315dfecf96Smrg    }
10325dfecf96Smrg
10335dfecf96Smrg    /* update pointers */
10345dfecf96Smrg    *format_list = formats;
10355dfecf96Smrg    *format_count = num_formats;
10365dfecf96Smrg    *format_ptr = next_format;
10375dfecf96Smrg}
10385dfecf96Smrg
10395dfecf96Smrgstatic void
10405dfecf96Smrgfree_formats(char **formats, int num_formats)
10415dfecf96Smrg{
10425dfecf96Smrg    if (num_formats) {
10435dfecf96Smrg	while (--num_formats >= 0)
10445dfecf96Smrg	    LispFree(formats[num_formats]);
10455dfecf96Smrg	LispFree(formats);
10465dfecf96Smrg    }
10475dfecf96Smrg}
10485dfecf96Smrg
10495dfecf96Smrgstatic void
10505dfecf96Smrgformat_case_conversion(LispObj *stream, FmtInfo *info)
10515dfecf96Smrg{
10525dfecf96Smrg    GC_ENTER();
10535dfecf96Smrg    LispObj *string;
10545dfecf96Smrg    FmtInfo case_info;
10555dfecf96Smrg    char *str, *ptr;
10565dfecf96Smrg    char *format, *next_format, **formats;
10575dfecf96Smrg    int atsign, collon, num_formats, length;
10585dfecf96Smrg
10595dfecf96Smrg    atsign = info->args.atsign;
10605dfecf96Smrg    collon = info->args.collon;
10615dfecf96Smrg
10625dfecf96Smrg    /* output to a string, before case conversion */
10635dfecf96Smrg    string = LSTRINGSTREAM("", STREAM_READ | STREAM_WRITE, 0);
10645dfecf96Smrg    GC_PROTECT(string);
10655dfecf96Smrg
10665dfecf96Smrg    /* most information is the same */
10675dfecf96Smrg    memcpy(&case_info, info, sizeof(FmtInfo));
10685dfecf96Smrg
10695dfecf96Smrg    /* list formats */
10705dfecf96Smrg    next_format = *(info->format);
10715dfecf96Smrg    list_formats(info, '(', &next_format, &formats, &num_formats,
10725dfecf96Smrg		 NULL, NULL, NULL);
10735dfecf96Smrg
10745dfecf96Smrg    /* set new format string */
10755dfecf96Smrg    format = formats[0];
10765dfecf96Smrg    case_info.args.base = case_info.args.format = format;
10775dfecf96Smrg    case_info.format = &format;
10785dfecf96Smrg
10795dfecf96Smrg    /* format text to string */
10805dfecf96Smrg    LispFormat(string, &case_info);
10815dfecf96Smrg
10825dfecf96Smrg    str = ptr = LispGetSstring(SSTREAMP(string), &length);
10835dfecf96Smrg
10845dfecf96Smrg    /* do case conversion */
10855dfecf96Smrg    if (!atsign && !collon) {
10865dfecf96Smrg	/* convert all upercase to lowercase */
10875dfecf96Smrg	for (; *ptr; ptr++) {
10885dfecf96Smrg	    if (isupper(*ptr))
10895dfecf96Smrg		*ptr = tolower(*ptr);
10905dfecf96Smrg	}
10915dfecf96Smrg    }
10925dfecf96Smrg    else if (atsign && collon) {
10935dfecf96Smrg	/* convert all lowercase to upercase */
10945dfecf96Smrg	for (; *ptr; ptr++) {
10955dfecf96Smrg	    if (islower(*ptr))
10965dfecf96Smrg		*ptr = toupper(*ptr);
10975dfecf96Smrg	}
10985dfecf96Smrg    }
10995dfecf96Smrg    else {
11005dfecf96Smrg	int upper = 1;
11015dfecf96Smrg
11025dfecf96Smrg	/* skip non-alphanumeric characters */
11035dfecf96Smrg	for (; *ptr; ptr++)
11045dfecf96Smrg	    if (isalnum(*ptr))
11055dfecf96Smrg		break;
11065dfecf96Smrg
11075dfecf96Smrg	/* capitalize words */
11085dfecf96Smrg	for (; *ptr; ptr++) {
11095dfecf96Smrg	    if (isalnum(*ptr)) {
11105dfecf96Smrg		if (upper) {
11115dfecf96Smrg		    if (islower(*ptr))
11125dfecf96Smrg			*ptr = toupper(*ptr);
11135dfecf96Smrg		    upper = 0;
11145dfecf96Smrg		}
11155dfecf96Smrg		else if (isupper(*ptr))
11165dfecf96Smrg		    *ptr = tolower(*ptr);
11175dfecf96Smrg	    }
11185dfecf96Smrg	    else
11195dfecf96Smrg		upper = collon;
11205dfecf96Smrg		/* if collon, capitalize all words, else just first word */
11215dfecf96Smrg	}
11225dfecf96Smrg    }
11235dfecf96Smrg
11245dfecf96Smrg    /* output case converted string */
11255dfecf96Smrg    LispWriteStr(stream, str, length);
11265dfecf96Smrg
11275dfecf96Smrg    /* temporary string stream is not necessary anymore */
11285dfecf96Smrg    GC_LEAVE();
11295dfecf96Smrg
11305dfecf96Smrg    /* free temporary memory */
11315dfecf96Smrg    free_formats(formats, num_formats);
11325dfecf96Smrg
11335dfecf96Smrg    /* this information always updated */
11345dfecf96Smrg    *(info->format) = next_format;
11355dfecf96Smrg}
11365dfecf96Smrg
11375dfecf96Smrgstatic void
11385dfecf96Smrgformat_conditional(LispObj *stream, FmtInfo *info)
11395dfecf96Smrg{
11405dfecf96Smrg    LispObj *object, *arguments;
11415dfecf96Smrg    char *format, *next_format, **formats;
11425dfecf96Smrg    int choice, num_formats, has_default, num_arguments;
11435dfecf96Smrg
11445dfecf96Smrg    /* save information that may change */
11455dfecf96Smrg    object = *(info->object);
11465dfecf96Smrg    arguments = *(info->arguments);
11475dfecf96Smrg    num_arguments = *(info->num_arguments);
11485dfecf96Smrg
11495dfecf96Smrg    /* initialize */
11505dfecf96Smrg    choice = -1;
11515dfecf96Smrg    next_format = *(info->format);
11525dfecf96Smrg
11535dfecf96Smrg    /* list formats */
11545dfecf96Smrg    list_formats(info, '[',
11555dfecf96Smrg		 &next_format, &formats, &num_formats, &has_default, NULL, NULL);
11565dfecf96Smrg
11575dfecf96Smrg    /* ~:[false;true] */
11585dfecf96Smrg    if (info->args.collon) {
11595dfecf96Smrg	/* one argument always consumed */
11605dfecf96Smrg	if (!CONSP(arguments))
11615dfecf96Smrg	    parse_error(&(info->args), PARSE_NOARGSLEFT);
11625dfecf96Smrg	object = CAR(arguments);
11635dfecf96Smrg	arguments = CDR(arguments);
11645dfecf96Smrg	--num_arguments;
11655dfecf96Smrg	choice = object == NIL ? 0 : 1;
11665dfecf96Smrg    }
11675dfecf96Smrg    /* ~@[true] */
11685dfecf96Smrg    else if (info->args.atsign) {
11695dfecf96Smrg	/* argument consumed only if nil, but one must be available */
11705dfecf96Smrg	if (!CONSP(arguments))
11715dfecf96Smrg	    parse_error(&(info->args), PARSE_NOARGSLEFT);
11725dfecf96Smrg	if (CAR(arguments) != NIL)
11735dfecf96Smrg	    choice = 0;
11745dfecf96Smrg	else {
11755dfecf96Smrg	    object = CAR(arguments);
11765dfecf96Smrg	    arguments = CDR(arguments);
11775dfecf96Smrg	    --num_arguments;
11785dfecf96Smrg	}
11795dfecf96Smrg    }
11805dfecf96Smrg    /* ~n[...~] */
11815dfecf96Smrg    else if (info->args.arguments[0].specified)
11825dfecf96Smrg	/* no arguments consumed */
11835dfecf96Smrg	choice = info->args.arguments[0].value;
11845dfecf96Smrg    /* ~[...~] */
11855dfecf96Smrg    else {
11865dfecf96Smrg	/* one argument consumed, it is the index in the available formats */
11875dfecf96Smrg	if (!CONSP(arguments))
11885dfecf96Smrg	    parse_error(&(info->args), PARSE_NOARGSLEFT);
11895dfecf96Smrg	object = CAR(arguments);
11905dfecf96Smrg	arguments = CDR(arguments);
11915dfecf96Smrg	--num_arguments;
11925dfecf96Smrg	/* no error if it isn't a number? */
11935dfecf96Smrg	if (FIXNUMP(object))
11945dfecf96Smrg	    choice = FIXNUM_VALUE(object);
11955dfecf96Smrg    }
11965dfecf96Smrg
11975dfecf96Smrg    /* update anything that may have changed */
11985dfecf96Smrg    *(info->object) = object;
11995dfecf96Smrg    *(info->arguments) = arguments;
12005dfecf96Smrg    *(info->num_arguments) = num_arguments;
12015dfecf96Smrg
12025dfecf96Smrg    /* if choice is out of range check if there is a default choice */
12035dfecf96Smrg    if (has_default && (choice < 0 || choice >= num_formats))
12045dfecf96Smrg	choice = num_formats - 1;
12055dfecf96Smrg
12065dfecf96Smrg    /* if one of the formats must be parsed */
12075dfecf96Smrg    if (choice >= 0 && choice < num_formats) {
12085dfecf96Smrg	FmtInfo conditional_info;
12095dfecf96Smrg
12105dfecf96Smrg	/* most information is the same */
12115dfecf96Smrg	memcpy(&conditional_info, info, sizeof(FmtInfo));
12125dfecf96Smrg
12135dfecf96Smrg	/* set new format string */
12145dfecf96Smrg	format = formats[choice];
12155dfecf96Smrg	conditional_info.args.base = conditional_info.args.format = format;
12165dfecf96Smrg	conditional_info.format = &format;
12175dfecf96Smrg
12185dfecf96Smrg	/* do the conditional format */
12195dfecf96Smrg	LispFormat(stream, &conditional_info);
12205dfecf96Smrg    }
12215dfecf96Smrg
12225dfecf96Smrg    /* free temporary memory */
12235dfecf96Smrg    free_formats(formats, num_formats);
12245dfecf96Smrg
12255dfecf96Smrg    /* this information always updated */
12265dfecf96Smrg    *(info->format) = next_format;
12275dfecf96Smrg}
12285dfecf96Smrg
12295dfecf96Smrgstatic void
12305dfecf96Smrgformat_iterate(LispObj *stream, FmtInfo *info)
12315dfecf96Smrg{
12325dfecf96Smrg    FmtInfo iterate_info;
12335dfecf96Smrg    LispObj *object, *arguments, *iarguments, *iobject;
12345dfecf96Smrg    char *format, *next_format, *loop_format, **formats;
12355dfecf96Smrg    int num_arguments, iterate, iterate_max, has_max, has_min, inum_arguments,
12365dfecf96Smrg	num_formats;
12375dfecf96Smrg
12385dfecf96Smrg    /* save information that may change */
12395dfecf96Smrg    object = *(info->object);
12405dfecf96Smrg    arguments = *(info->arguments);
12415dfecf96Smrg    num_arguments = *(info->num_arguments);
12425dfecf96Smrg
12435dfecf96Smrg    /* initialize */
12445dfecf96Smrg    iterate = has_min = 0;
12455dfecf96Smrg    next_format = *(info->format);
12465dfecf96Smrg
12475dfecf96Smrg    /* if has_max set, iterate at most iterate_max times */
12485dfecf96Smrg    has_max = info->args.arguments[0].specified;
12495dfecf96Smrg    iterate_max = info->args.arguments[0].value;
12505dfecf96Smrg
12515dfecf96Smrg    /* list formats */
12525dfecf96Smrg    list_formats(info, '{', &next_format, &formats, &num_formats,
12535dfecf96Smrg		 NULL, NULL, NULL);
12545dfecf96Smrg    loop_format = formats[0];
12555dfecf96Smrg
12565dfecf96Smrg    /* most information is the same */
12575dfecf96Smrg    memcpy(&iterate_info, info, sizeof(FmtInfo));
12585dfecf96Smrg
12595dfecf96Smrg    /* ~{...~} */
12605dfecf96Smrg    if (!info->args.atsign && !info->args.collon) {
12615dfecf96Smrg	/* next argument is the argument list for the iteration */
12625dfecf96Smrg
12635dfecf96Smrg	/* fetch argument list, must exist */
12645dfecf96Smrg	if (!CONSP(arguments))
12655dfecf96Smrg	    parse_error(&(info->args), PARSE_NOARGSLEFT);
12665dfecf96Smrg	iarguments = object = CAR(arguments);
12675dfecf96Smrg	object = CAR(arguments);
12685dfecf96Smrg	arguments = CDR(arguments);
12695dfecf96Smrg	--num_arguments;
12705dfecf96Smrg
12715dfecf96Smrg	inum_arguments = 0;
12725dfecf96Smrg	if (CONSP(object)) {
12735dfecf96Smrg	    /* count arguments to format */
12745dfecf96Smrg	    for (iobject = object; CONSP(iobject); iobject = CDR(iobject))
12755dfecf96Smrg		++inum_arguments;
12765dfecf96Smrg	}
12775dfecf96Smrg	else if (object != NIL)
12785dfecf96Smrg	    generic_error(&(info->args), GENERIC_BADLIST);
12795dfecf96Smrg
12805dfecf96Smrg	iobject = NIL;
12815dfecf96Smrg
12825dfecf96Smrg	/* set new arguments to recursive calls */
12835dfecf96Smrg	iarguments = object;
12845dfecf96Smrg	iterate_info.base_arguments = iarguments;
12855dfecf96Smrg	iterate_info.total_arguments = inum_arguments;
12865dfecf96Smrg	iterate_info.object = &iobject;
12875dfecf96Smrg	iterate_info.arguments = &iarguments;
12885dfecf96Smrg	iterate_info.num_arguments = &inum_arguments;
12895dfecf96Smrg
12905dfecf96Smrg	/* iterate */
12915dfecf96Smrg	for (;; iterate++) {
12925dfecf96Smrg	    /* if maximum iterations done or all arguments consumed */
12935dfecf96Smrg	    if (has_max && iterate > iterate_max)
12945dfecf96Smrg		break;
12955dfecf96Smrg	    else if (inum_arguments == 0 && (!has_min || iterate > 0))
12965dfecf96Smrg		break;
12975dfecf96Smrg
12985dfecf96Smrg	    format = loop_format;
12995dfecf96Smrg
13005dfecf96Smrg	    /* set new format string */
13015dfecf96Smrg	    iterate_info.args.base = iterate_info.args.format = format;
13025dfecf96Smrg	    iterate_info.format = &format;
13035dfecf96Smrg
13045dfecf96Smrg	    /* information for possible ~^, in this case ~:^ is a noop */
13055dfecf96Smrg	    iterate_info.iteration = ITERATION_NORMAL;
13065dfecf96Smrg
13075dfecf96Smrg	    /* do the format */
13085dfecf96Smrg	    LispFormat(stream, &iterate_info);
13095dfecf96Smrg
13105dfecf96Smrg	    /* check for forced loop break */
13115dfecf96Smrg	    if (iterate_info.upandout & UPANDOUT_HASH)
13125dfecf96Smrg		break;
13135dfecf96Smrg	}
13145dfecf96Smrg    }
13155dfecf96Smrg    /* ~:@{...~} */
13165dfecf96Smrg    else if (info->args.atsign && info->args.collon) {
13175dfecf96Smrg	/* every following argument is the argument list for the iteration */
13185dfecf96Smrg
13195dfecf96Smrg	/* iterate */
13205dfecf96Smrg	for (;; iterate++) {
13215dfecf96Smrg	    /* if maximum iterations done or all arguments consumed */
13225dfecf96Smrg	    if (has_max && iterate > iterate_max)
13235dfecf96Smrg		break;
13245dfecf96Smrg	    else if (num_arguments == 0 && (!has_min || iterate > 0))
13255dfecf96Smrg		break;
13265dfecf96Smrg
13275dfecf96Smrg	    /* fetch argument list, must exist */
13285dfecf96Smrg	    if (!CONSP(arguments))
13295dfecf96Smrg		parse_error(&(info->args), PARSE_NOARGSLEFT);
13305dfecf96Smrg	    iarguments = object = CAR(arguments);
13315dfecf96Smrg	    object = CAR(arguments);
13325dfecf96Smrg	    arguments = CDR(arguments);
13335dfecf96Smrg	    --num_arguments;
13345dfecf96Smrg
13355dfecf96Smrg	    inum_arguments = 0;
13365dfecf96Smrg	    if (CONSP(object)) {
13375dfecf96Smrg		/* count arguments to format */
13385dfecf96Smrg		for (iobject = object; CONSP(iobject); iobject = CDR(iobject))
13395dfecf96Smrg		    ++inum_arguments;
13405dfecf96Smrg	    }
13415dfecf96Smrg	    else if (object != NIL)
13425dfecf96Smrg		generic_error(&(info->args), GENERIC_BADLIST);
13435dfecf96Smrg
13445dfecf96Smrg	    iobject = NIL;
13455dfecf96Smrg
13465dfecf96Smrg	    /* set new arguments to recursive calls */
13475dfecf96Smrg	    iarguments = object;
13485dfecf96Smrg	    iterate_info.base_arguments = iarguments;
13495dfecf96Smrg	    iterate_info.total_arguments = inum_arguments;
13505dfecf96Smrg	    iterate_info.object = &iobject;
13515dfecf96Smrg	    iterate_info.arguments = &iarguments;
13525dfecf96Smrg	    iterate_info.num_arguments = &inum_arguments;
13535dfecf96Smrg
13545dfecf96Smrg	    format = loop_format;
13555dfecf96Smrg
13565dfecf96Smrg	    /* set new format string */
13575dfecf96Smrg	    iterate_info.args.base = iterate_info.args.format = format;
13585dfecf96Smrg	    iterate_info.format = &format;
13595dfecf96Smrg
13605dfecf96Smrg	    /* information for possible ~^ */
13615dfecf96Smrg	    iterate_info.iteration =
13625dfecf96Smrg		num_arguments > 0 ? ITERATION_NORMAL : ITERATION_LAST;
13635dfecf96Smrg
13645dfecf96Smrg	    /* do the format */
13655dfecf96Smrg	    LispFormat(stream, &iterate_info);
13665dfecf96Smrg
13675dfecf96Smrg	    /* check for forced loop break */
13685dfecf96Smrg	    if (iterate_info.upandout & UPANDOUT_HASH)
13695dfecf96Smrg		break;
13705dfecf96Smrg	}
13715dfecf96Smrg    }
13725dfecf96Smrg    /* ~:{...~} */
13735dfecf96Smrg    else if (info->args.collon) {
13745dfecf96Smrg	/* next argument is a list of lists */
13755dfecf96Smrg
13765dfecf96Smrg	LispObj *sarguments, *sobject;
13775dfecf96Smrg	int snum_arguments;
13785dfecf96Smrg
13795dfecf96Smrg	/* fetch argument list, must exist */
13805dfecf96Smrg	if (!CONSP(arguments))
13815dfecf96Smrg	    parse_error(&(info->args), PARSE_NOARGSLEFT);
13825dfecf96Smrg	sarguments = object = CAR(arguments);
13835dfecf96Smrg	object = CAR(arguments);
13845dfecf96Smrg	arguments = CDR(arguments);
13855dfecf96Smrg	--num_arguments;
13865dfecf96Smrg
13875dfecf96Smrg	snum_arguments = 0;
13885dfecf96Smrg	if (CONSP(object)) {
13895dfecf96Smrg	    /* count arguments to format */
13905dfecf96Smrg	    for (sobject = object; CONSP(sobject); sobject = CDR(sobject))
13915dfecf96Smrg		++snum_arguments;
13925dfecf96Smrg	}
13935dfecf96Smrg	else
13945dfecf96Smrg	    generic_error(&(info->args), GENERIC_BADLIST);
13955dfecf96Smrg
13965dfecf96Smrg	/* iterate */
13975dfecf96Smrg	for (;; iterate++) {
13985dfecf96Smrg	    /* if maximum iterations done or all arguments consumed */
13995dfecf96Smrg	    if (has_max && iterate > iterate_max)
14005dfecf96Smrg		break;
14015dfecf96Smrg	    else if (snum_arguments == 0 && (!has_min || iterate > 0))
14025dfecf96Smrg		break;
14035dfecf96Smrg
14045dfecf96Smrg	    /* fetch argument list, must exist */
14055dfecf96Smrg	    if (!CONSP(sarguments))
14065dfecf96Smrg		parse_error(&(info->args), PARSE_NOARGSLEFT);
14075dfecf96Smrg	    iarguments = sobject = CAR(sarguments);
14085dfecf96Smrg	    sobject = CAR(sarguments);
14095dfecf96Smrg	    sarguments = CDR(sarguments);
14105dfecf96Smrg	    --snum_arguments;
14115dfecf96Smrg
14125dfecf96Smrg	    inum_arguments = 0;
14135dfecf96Smrg	    if (CONSP(object)) {
14145dfecf96Smrg		/* count arguments to format */
14155dfecf96Smrg		for (iobject = sobject; CONSP(iobject); iobject = CDR(iobject))
14165dfecf96Smrg		    ++inum_arguments;
14175dfecf96Smrg	    }
14185dfecf96Smrg	    else if (sobject != NIL)
14195dfecf96Smrg		generic_error(&(info->args), GENERIC_BADLIST);
14205dfecf96Smrg
14215dfecf96Smrg	    iobject = NIL;
14225dfecf96Smrg
14235dfecf96Smrg	    /* set new arguments to recursive calls */
14245dfecf96Smrg	    iarguments = sobject;
14255dfecf96Smrg	    iterate_info.base_arguments = iarguments;
14265dfecf96Smrg	    iterate_info.total_arguments = inum_arguments;
14275dfecf96Smrg	    iterate_info.object = &iobject;
14285dfecf96Smrg	    iterate_info.arguments = &iarguments;
14295dfecf96Smrg	    iterate_info.num_arguments = &inum_arguments;
14305dfecf96Smrg
14315dfecf96Smrg	    format = loop_format;
14325dfecf96Smrg
14335dfecf96Smrg	    /* set new format string */
14345dfecf96Smrg	    iterate_info.args.base = iterate_info.args.format = format;
14355dfecf96Smrg	    iterate_info.format = &format;
14365dfecf96Smrg
14375dfecf96Smrg	    /* information for possible ~^ */
14385dfecf96Smrg	    iterate_info.iteration =
14395dfecf96Smrg		snum_arguments > 0 ? ITERATION_NORMAL : ITERATION_LAST;
14405dfecf96Smrg
14415dfecf96Smrg	    /* do the format */
14425dfecf96Smrg	    LispFormat(stream, &iterate_info);
14435dfecf96Smrg
14445dfecf96Smrg	    /* check for forced loop break */
14455dfecf96Smrg	    if (iterate_info.upandout & UPANDOUT_HASH)
14465dfecf96Smrg		break;
14475dfecf96Smrg	}
14485dfecf96Smrg    }
14495dfecf96Smrg    /* ~@{...~} */
14505dfecf96Smrg    else if (info->args.atsign) {
14515dfecf96Smrg	/* current argument list is used */
14525dfecf96Smrg
14535dfecf96Smrg	/* set new arguments to recursive calls */
14545dfecf96Smrg	iterate_info.base_arguments = info->base_arguments;
14555dfecf96Smrg	iterate_info.total_arguments = info->total_arguments;
14565dfecf96Smrg	iterate_info.object = &object;
14575dfecf96Smrg	iterate_info.arguments = &arguments;
14585dfecf96Smrg	iterate_info.num_arguments = &num_arguments;
14595dfecf96Smrg
14605dfecf96Smrg	for (;; iterate++) {
14615dfecf96Smrg	    /* if maximum iterations done or all arguments consumed */
14625dfecf96Smrg	    if (has_max && iterate > iterate_max)
14635dfecf96Smrg		break;
14645dfecf96Smrg	    else if (num_arguments == 0 && (!has_min || iterate > 0))
14655dfecf96Smrg		break;
14665dfecf96Smrg
14675dfecf96Smrg	    format = loop_format;
14685dfecf96Smrg
14695dfecf96Smrg	    /* set new format string */
14705dfecf96Smrg	    iterate_info.args.base = iterate_info.args.format = format;
14715dfecf96Smrg	    iterate_info.format = &format;
14725dfecf96Smrg
14735dfecf96Smrg	    /* information for possible ~^, in this case ~:^ is a noop */
14745dfecf96Smrg	    iterate_info.iteration = ITERATION_NORMAL;
14755dfecf96Smrg
14765dfecf96Smrg	    /* do the format */
14775dfecf96Smrg	    LispFormat(stream, &iterate_info);
14785dfecf96Smrg
14795dfecf96Smrg	    /* check for forced loop break */
14805dfecf96Smrg	    if (iterate_info.upandout & UPANDOUT_HASH)
14815dfecf96Smrg		break;
14825dfecf96Smrg	}
14835dfecf96Smrg    }
14845dfecf96Smrg
14855dfecf96Smrg    /* free temporary memory */
14865dfecf96Smrg    free_formats(formats, num_formats);
14875dfecf96Smrg
14885dfecf96Smrg    /* update anything that may have changed */
14895dfecf96Smrg    *(info->object) = object;
14905dfecf96Smrg    *(info->arguments) = arguments;
14915dfecf96Smrg    *(info->num_arguments) = num_arguments;
14925dfecf96Smrg
14935dfecf96Smrg    /* this information always updated */
14945dfecf96Smrg    *(info->format) = next_format;
14955dfecf96Smrg}
14965dfecf96Smrg
14975dfecf96Smrgstatic void
14985dfecf96Smrgformat_justify(LispObj *stream, FmtInfo *info)
14995dfecf96Smrg{
15005dfecf96Smrg    GC_ENTER();
15015dfecf96Smrg    FmtInfo justify_info;
1502f765521fSmrg    char **formats, *format, *next_format;
1503f765521fSmrg    const char *str;
15045dfecf96Smrg    LispObj *string, *strings = NIL, *cons;
15055dfecf96Smrg    int atsign = info->args.atsign,
15065dfecf96Smrg	collon = info->args.collon,
15075dfecf96Smrg	mincol = info->args.arguments[0].value,
15085dfecf96Smrg	colinc = info->args.arguments[1].value,
15095dfecf96Smrg	minpad = info->args.arguments[2].value,
15105dfecf96Smrg	padchar = info->args.arguments[3].value;
15115dfecf96Smrg    int i, k, total_length, length, padding, num_formats, has_default,
15125dfecf96Smrg	comma_width, line_width, size, extra;
15135dfecf96Smrg
15145dfecf96Smrg    next_format = *(info->format);
15155dfecf96Smrg
15165dfecf96Smrg    /* list formats */
15175dfecf96Smrg    list_formats(info, '<', &next_format, &formats, &num_formats,
15185dfecf96Smrg		 &has_default, &comma_width, &line_width);
15195dfecf96Smrg
15205dfecf96Smrg    /* initialize list of strings streams */
15215dfecf96Smrg    if (num_formats) {
15225dfecf96Smrg	string = LSTRINGSTREAM("", STREAM_READ | STREAM_WRITE, 0);
15235dfecf96Smrg	strings = cons = CONS(string, NIL);
15245dfecf96Smrg	GC_PROTECT(strings);
15255dfecf96Smrg	for (i = 1; i < num_formats; i++) {
15265dfecf96Smrg	    string = LSTRINGSTREAM("", STREAM_READ | STREAM_WRITE, 0);
15275dfecf96Smrg	    RPLACD(cons, CONS(string, NIL));
15285dfecf96Smrg	    cons = CDR(cons);
15295dfecf96Smrg	}
15305dfecf96Smrg    }
15315dfecf96Smrg
15325dfecf96Smrg    /* most information is the same */
15335dfecf96Smrg    memcpy(&justify_info, info, sizeof(FmtInfo));
15345dfecf96Smrg
15355dfecf96Smrg    /* loop formating strings */
15365dfecf96Smrg    for (i = 0, cons = strings; i < num_formats; i++, cons = CDR(cons)) {
15375dfecf96Smrg	/* set new format string */
15385dfecf96Smrg	format = formats[i];
15395dfecf96Smrg	justify_info.args.base = justify_info.args.format = format;
15405dfecf96Smrg	justify_info.format = &format;
15415dfecf96Smrg
15425dfecf96Smrg	/* format string, maybe consuming arguments */
15435dfecf96Smrg	LispFormat(CAR(cons), &justify_info);
15445dfecf96Smrg
15455dfecf96Smrg	/* if format was aborted, it is discarded */
15465dfecf96Smrg	if (justify_info.upandout)
15475dfecf96Smrg	    RPLACA(cons, NIL);
15485dfecf96Smrg	/* check if the entire "main" iteration must be aborted */
15495dfecf96Smrg	if (justify_info.upandout & UPANDOUT_COLLON) {
15505dfecf96Smrg	    for (cons = CDR(cons); i < num_formats; i++, cons = CDR(cons))
15515dfecf96Smrg		RPLACA(cons, NIL);
15525dfecf96Smrg	    break;
15535dfecf96Smrg	}
15545dfecf96Smrg    }
15555dfecf96Smrg
15565dfecf96Smrg    /* free temporary format strings */
15575dfecf96Smrg    free_formats(formats, num_formats);
15585dfecf96Smrg
15595dfecf96Smrg    /* remove aborted formats */
15605dfecf96Smrg	/* first remove leading discarded formats */
15615dfecf96Smrg    if (CAR(strings) == NIL) {
15625dfecf96Smrg	while (CAR(strings) == NIL) {
15635dfecf96Smrg	    strings = CDR(strings);
15645dfecf96Smrg	    --num_formats;
15655dfecf96Smrg	}
15665dfecf96Smrg	/* keep strings gc protected, discarding first entries */
15675dfecf96Smrg	lisp__data.protect.objects[gc__protect] = strings;
15685dfecf96Smrg    }
15695dfecf96Smrg	/* now remove intermediary discarded formats */
15705dfecf96Smrg    cons = strings;
15715dfecf96Smrg    while (CONSP(cons)) {
15725dfecf96Smrg	if (CONSP(CDR(cons)) && CAR(CDR(cons)) == NIL) {
15735dfecf96Smrg	    RPLACD(cons, CDR(CDR(cons)));
15745dfecf96Smrg	    --num_formats;
15755dfecf96Smrg	}
15765dfecf96Smrg	else
15775dfecf96Smrg	    cons = CDR(cons);
15785dfecf96Smrg    }
15795dfecf96Smrg
15805dfecf96Smrg    /* calculate total length required for output */
15815dfecf96Smrg    if (has_default)
15825dfecf96Smrg	cons = CDR(strings);	/* if has_defaults, strings is surely a list */
15835dfecf96Smrg    else
15845dfecf96Smrg	cons = strings;
15855dfecf96Smrg    for (total_length = 0; CONSP(cons); cons = CDR(cons))
15865dfecf96Smrg	total_length += SSTREAMP(CAR(cons))->length;
15875dfecf96Smrg
15885dfecf96Smrg    /* initialize pointer to string streams */
15895dfecf96Smrg    if (has_default)
15905dfecf96Smrg	cons = CDR(strings);
15915dfecf96Smrg    else
15925dfecf96Smrg	cons = strings;
15935dfecf96Smrg
15945dfecf96Smrg    /* check if padding will need to be printed */
15955dfecf96Smrg    extra = 0;
15965dfecf96Smrg    padding = mincol - total_length;
15975dfecf96Smrg    if (padding < 0)
15985dfecf96Smrg	k = padding = 0;
15995dfecf96Smrg    else {
16005dfecf96Smrg	int num_fields = num_formats - (has_default != 0);
16015dfecf96Smrg
16025dfecf96Smrg	if (num_fields > 1) {
16035dfecf96Smrg	    /* check if padding is distributed in num_fields or
16045dfecf96Smrg	     * num_fields - 1 steps */
16055dfecf96Smrg	    if (!collon)
16065dfecf96Smrg		--num_fields;
16075dfecf96Smrg	}
16085dfecf96Smrg
16095dfecf96Smrg	if (num_fields)
16105dfecf96Smrg	    k = padding / num_fields;
16115dfecf96Smrg	else
16125dfecf96Smrg	    k = padding;
16135dfecf96Smrg
16145dfecf96Smrg	if (k <= 0)
16155dfecf96Smrg	    k = colinc;
16165dfecf96Smrg	else if (colinc)
16175dfecf96Smrg	    k = k + (k % colinc);
16185dfecf96Smrg	extra = mincol - (num_fields * k + total_length);
16195dfecf96Smrg	if (extra < 0)
16205dfecf96Smrg	    extra = 0;
16215dfecf96Smrg    }
16225dfecf96Smrg    if (padding && k < minpad) {
16235dfecf96Smrg	k = minpad;
16245dfecf96Smrg	if (colinc)
16255dfecf96Smrg	    k = k + (k % colinc);
16265dfecf96Smrg    }
16275dfecf96Smrg
16285dfecf96Smrg    /* first check for the special case of only one string being justified */
16295dfecf96Smrg    if (num_formats - has_default == 1) {
16305dfecf96Smrg	if (has_default && line_width > 0 && comma_width >= 0 &&
16315dfecf96Smrg	    total_length + comma_width > line_width) {
16325dfecf96Smrg	    str = LispGetSstring(SSTREAMP(CAR(strings)), &size);
16335dfecf96Smrg	    LispWriteStr(stream, str, size);
16345dfecf96Smrg	}
16355dfecf96Smrg	string = has_default ? CAR(CDR(strings)) : CAR(strings);
16365dfecf96Smrg	/* check if need left padding */
16375dfecf96Smrg	if (k && !atsign) {
16385dfecf96Smrg	    LispWriteChars(stream, padchar, k);
16395dfecf96Smrg	    k = 0;
16405dfecf96Smrg	}
16415dfecf96Smrg	/* check for centralizing text */
16425dfecf96Smrg	else if (k && atsign && collon) {
16435dfecf96Smrg	    LispWriteChars(stream, padchar, k / 2 + ((k / 2) & 1));
16445dfecf96Smrg	    k -= k / 2;
16455dfecf96Smrg	}
16465dfecf96Smrg	str = LispGetSstring(SSTREAMP(string), &size);
16475dfecf96Smrg	LispWriteStr(stream, str, size);
16485dfecf96Smrg	/* if any padding remaining */
16495dfecf96Smrg	if (k)
16505dfecf96Smrg	    LispWriteChars(stream, padchar, k);
16515dfecf96Smrg    }
16525dfecf96Smrg    else {
16535dfecf96Smrg	LispObj *result;
16545dfecf96Smrg	int last, spaces_before, padout;
16555dfecf96Smrg
16565dfecf96Smrg	/* if has default, need to check output length */
16575dfecf96Smrg	if (has_default && line_width > 0 && comma_width >= 0) {
16585dfecf96Smrg	    result = LSTRINGSTREAM("", STREAM_READ | STREAM_WRITE, 0);
16595dfecf96Smrg	    GC_PROTECT(result);
16605dfecf96Smrg	}
16615dfecf96Smrg	/* else write directly to stream */
16625dfecf96Smrg	else
16635dfecf96Smrg	    result = stream;
16645dfecf96Smrg
16655dfecf96Smrg	/* loop printing justified text */
16665dfecf96Smrg	    /* padout controls padding for cases where padding is
16675dfecf96Smrg	     * is separated in n-1 chunks, where n is the number of
16685dfecf96Smrg	     * formatted strings.
16695dfecf96Smrg	     */
16705dfecf96Smrg	for (i = padout = 0; CONSP(cons); i++, cons = CDR(cons), --extra) {
16715dfecf96Smrg	    string = CAR(cons);
16725dfecf96Smrg	    last = !CONSP(CDR(cons));
16735dfecf96Smrg
16745dfecf96Smrg	    spaces_before = (i != 0 || collon) && (!last || !atsign);
16755dfecf96Smrg
16765dfecf96Smrg	    if (!spaces_before) {
16775dfecf96Smrg		/* check for special case */
16785dfecf96Smrg		if (last && atsign && collon && padding > 0) {
16795dfecf96Smrg		    int spaces;
16805dfecf96Smrg
16815dfecf96Smrg		    spaces = minpad > colinc ? minpad : colinc;
16825dfecf96Smrg		    LispWriteChars(result, padchar, spaces + (extra > 0));
16835dfecf96Smrg		    k -= spaces;
16845dfecf96Smrg		}
16855dfecf96Smrg		str = LispGetSstring(SSTREAMP(string), &size);
16865dfecf96Smrg		LispWriteStr(result, str, size);
16875dfecf96Smrg		padout = 0;
16885dfecf96Smrg	    }
16895dfecf96Smrg	    if (!padout)
16905dfecf96Smrg		LispWriteChars(result, padchar, k + (extra > 0));
16915dfecf96Smrg	    padout = k;
16925dfecf96Smrg	    /* if not first string, or if left padding specified */
16935dfecf96Smrg	    if (spaces_before) {
16945dfecf96Smrg		str = LispGetSstring(SSTREAMP(string), &size);
16955dfecf96Smrg		LispWriteStr(result, str, size);
16965dfecf96Smrg		padout = 0;
16975dfecf96Smrg	    }
16985dfecf96Smrg	    padding -= k;
16995dfecf96Smrg	}
17005dfecf96Smrg
17015dfecf96Smrg	if (has_default && line_width > 0 && comma_width >= 0) {
17025dfecf96Smrg	    length = SSTREAMP(result)->length + LispGetColumn(stream);
17035dfecf96Smrg
17045dfecf96Smrg	    /* if current line is too large */
17055dfecf96Smrg	    if (has_default && length + comma_width > line_width) {
17065dfecf96Smrg		str = LispGetSstring(SSTREAMP(CAR(strings)), &size);
17075dfecf96Smrg		LispWriteStr(stream, str, size);
17085dfecf96Smrg	    }
17095dfecf96Smrg
17105dfecf96Smrg	    /* write result to stream */
17115dfecf96Smrg	    str = LispGetSstring(SSTREAMP(result), &size);
17125dfecf96Smrg	    LispWriteStr(stream, str, size);
17135dfecf96Smrg	}
17145dfecf96Smrg    }
17155dfecf96Smrg
17165dfecf96Smrg    /* unprotect string streams from GC */
17175dfecf96Smrg    GC_LEAVE();
17185dfecf96Smrg
17195dfecf96Smrg    /* this information always updated */
17205dfecf96Smrg    *(info->format) = next_format;
17215dfecf96Smrg}
17225dfecf96Smrg
17235dfecf96Smrgstatic void
17245dfecf96SmrgLispFormat(LispObj *stream, FmtInfo *info)
17255dfecf96Smrg{
17265dfecf96Smrg    FmtArgs *args;
1727f765521fSmrg    const FmtDefs *defs = NULL;
17285dfecf96Smrg    LispObj *object, *arguments;
17295dfecf96Smrg    char stk[256], *format, *next_format;
17305dfecf96Smrg    int length, num_arguments, code, need_update, need_argument, hash, head;
17315dfecf96Smrg
17325dfecf96Smrg    /* arguments that will be updated on function exit */
17335dfecf96Smrg    format = *(info->format);
17345dfecf96Smrg    object = *(info->object);
17355dfecf96Smrg    arguments = *(info->arguments);
17365dfecf96Smrg    num_arguments = *(info->num_arguments);
17375dfecf96Smrg
17385dfecf96Smrg    /* initialize */
17395dfecf96Smrg    length = 0;
17405dfecf96Smrg    args = &(info->args);
17415dfecf96Smrg    info->upandout = 0;
17425dfecf96Smrg
17435dfecf96Smrg    while (*format) {
17445dfecf96Smrg	if (*format == '~') {
17455dfecf96Smrg	    /* flush non formatted characters */
17465dfecf96Smrg	    if (length) {
17475dfecf96Smrg		LispWriteStr(stream, stk, length);
17485dfecf96Smrg		length = 0;
17495dfecf96Smrg	    }
17505dfecf96Smrg
17515dfecf96Smrg	    need_argument = need_update = hash = 0;
17525dfecf96Smrg
17535dfecf96Smrg	    /* parse parameters */
17545dfecf96Smrg	    args->format = format + 1;
17555dfecf96Smrg	    next_format = parse_arguments(format + 1, args, &num_arguments,
17565dfecf96Smrg					  &arguments, &code);
17575dfecf96Smrg	    if (code != NOERROR)
17585dfecf96Smrg		parse_error(args, code);
17595dfecf96Smrg
17605dfecf96Smrg	    /* check parameters */
17615dfecf96Smrg	    switch (args->command) {
17625dfecf96Smrg		case 'A': case 'S':
17635dfecf96Smrg		    defs = &AsciiDefs;
17645dfecf96Smrg		    break;
17655dfecf96Smrg		case 'B': case 'O': case 'D': case 'X':
17665dfecf96Smrg		    defs = &IntegerDefs;
17675dfecf96Smrg		    break;
17685dfecf96Smrg		case 'R':
17695dfecf96Smrg		    defs = &RadixDefs;
17705dfecf96Smrg		    break;
17715dfecf96Smrg		case 'P': case 'C':
17725dfecf96Smrg		    defs = &NoneDefs;
17735dfecf96Smrg		    break;
17745dfecf96Smrg		case 'F':
17755dfecf96Smrg		    defs = &FixedFloatDefs;
17765dfecf96Smrg		    break;
17775dfecf96Smrg		case 'E': case 'G':
17785dfecf96Smrg		    defs = &ExponentialFloatDefs;
17795dfecf96Smrg		    break;
17805dfecf96Smrg		case '$':
17815dfecf96Smrg		    defs = &DollarFloatDefs;
17825dfecf96Smrg		    break;
17835dfecf96Smrg		case '%': case '&': case '|': case '~': case '\n':
17845dfecf96Smrg		    defs = &OneDefs;
17855dfecf96Smrg		    break;
17865dfecf96Smrg		case 'T':
17875dfecf96Smrg		    defs = &TabulateDefs;
17885dfecf96Smrg		    break;
17895dfecf96Smrg		case '*':
17905dfecf96Smrg		    defs = &OneDefs;
17915dfecf96Smrg		    break;
17925dfecf96Smrg		case '?': case '(':
17935dfecf96Smrg		    defs = &NoneDefs;
17945dfecf96Smrg		    break;
17955dfecf96Smrg		case ')':
17965dfecf96Smrg		    /* this is never seen, processed in format_case_conversion */
17975dfecf96Smrg		    format_error(args, "no match for directive ~)");
17985dfecf96Smrg		case '[':
17995dfecf96Smrg		    defs = &OneDefs;
18005dfecf96Smrg		    break;
18015dfecf96Smrg		case ']':
18025dfecf96Smrg		    /* this is never seen, processed in format_conditional */
18035dfecf96Smrg		    format_error(args, "no match for directive ~]");
18045dfecf96Smrg		case '{':
18055dfecf96Smrg		    defs = &OneDefs;
18065dfecf96Smrg		    break;
18075dfecf96Smrg		case '}':
18085dfecf96Smrg		    /* this is never seen, processed in format_iterate */
18095dfecf96Smrg		    format_error(args, "no match for directive ~}");
18105dfecf96Smrg		case '<':
18115dfecf96Smrg		    defs = &AsciiDefs;
18125dfecf96Smrg		    break;
18135dfecf96Smrg		case '>':
18145dfecf96Smrg		    /* this is never seen, processed in format_justify */
18155dfecf96Smrg		    format_error(args, "no match for directive ~>");
18165dfecf96Smrg		case ';':
18175dfecf96Smrg		    /* this is never seen here */
18185dfecf96Smrg		    format_error(args, "misplaced directive ~;");
18195dfecf96Smrg		case '#':
18205dfecf96Smrg		    /* special handling for ~#^ */
18215dfecf96Smrg		    if (*next_format == '^') {
18225dfecf96Smrg			++next_format;
18235dfecf96Smrg			hash = 1;
18245dfecf96Smrg			defs = &NoneDefs;
18255dfecf96Smrg			args->command = '^';
18265dfecf96Smrg			break;
18275dfecf96Smrg		    }
18285dfecf96Smrg		    parse_error(args, PARSE_BADDIRECTIVE);
18295dfecf96Smrg		case '^':
18305dfecf96Smrg		    defs = &NoneDefs;
18315dfecf96Smrg		    break;
18325dfecf96Smrg		default:
18335dfecf96Smrg		    parse_error(args, PARSE_BADDIRECTIVE);
18345dfecf96Smrg		    break;
18355dfecf96Smrg	    }
18365dfecf96Smrg	    merge_arguments(args, defs, &code);
18375dfecf96Smrg	    if (code != NOERROR)
18385dfecf96Smrg		merge_error(args, code);
18395dfecf96Smrg
18405dfecf96Smrg	    /* check if an argument is required by directive */
18415dfecf96Smrg	    switch (args->command) {
18425dfecf96Smrg		case 'A': case 'S':
18435dfecf96Smrg		case 'B': case 'O': case 'D': case 'X': case 'R':
18445dfecf96Smrg		    need_argument = 1;
18455dfecf96Smrg		    break;
18465dfecf96Smrg		case 'P':
18475dfecf96Smrg		    /* if collon specified, plural is the last print argument */
18485dfecf96Smrg		    need_argument = !args->collon;
18495dfecf96Smrg		    break;
18505dfecf96Smrg		case 'C':
18515dfecf96Smrg		    need_argument = 1;
18525dfecf96Smrg		    break;
18535dfecf96Smrg		case 'F': case 'E': case 'G': case '$':
18545dfecf96Smrg		    need_argument = 1;
18555dfecf96Smrg		    break;
18565dfecf96Smrg		case '%': case '&': case '|': case '~': case '\n':
18575dfecf96Smrg		    break;
18585dfecf96Smrg		case 'T':
18595dfecf96Smrg		    break;
18605dfecf96Smrg		case '*':			/* check arguments below */
18615dfecf96Smrg		    need_update = 1;
18625dfecf96Smrg		    break;
18635dfecf96Smrg		case '?':
18645dfecf96Smrg		    need_argument = need_update = 1;
18655dfecf96Smrg		    break;
18665dfecf96Smrg		case '(': case '[': case '{': case '<':
18675dfecf96Smrg		    need_update = 1;
18685dfecf96Smrg		    break;
18695dfecf96Smrg		case '^':
18705dfecf96Smrg		    break;
18715dfecf96Smrg	    }
18725dfecf96Smrg	    if (need_argument) {
18735dfecf96Smrg		if (!CONSP(arguments))
18745dfecf96Smrg		    parse_error(args, PARSE_NOARGSLEFT);
18755dfecf96Smrg		object = CAR(arguments);
18765dfecf96Smrg		arguments = CDR(arguments);
18775dfecf96Smrg		--num_arguments;
18785dfecf96Smrg	    }
18795dfecf96Smrg
18805dfecf96Smrg	    /* will do recursive calls that change info */
18815dfecf96Smrg	    if (need_update) {
18825dfecf96Smrg		*(info->format) = next_format;
18835dfecf96Smrg		*(info->object) = object;
18845dfecf96Smrg		*(info->arguments) = arguments;
18855dfecf96Smrg		*(info->num_arguments) = num_arguments;
18865dfecf96Smrg	    }
18875dfecf96Smrg
18885dfecf96Smrg	    /* everything seens fine, print the format directive */
18895dfecf96Smrg	    switch (args->command) {
18905dfecf96Smrg		case 'A':
18915dfecf96Smrg		    head = lisp__data.env.length;
18925dfecf96Smrg		    LispAddVar(Oprint_escape, NIL);
18935dfecf96Smrg		    ++lisp__data.env.head;
18945dfecf96Smrg		    format_ascii(stream, object, args);
18955dfecf96Smrg		    lisp__data.env.head = lisp__data.env.length = head;
18965dfecf96Smrg		    break;
18975dfecf96Smrg		case 'S':
18985dfecf96Smrg		    head = lisp__data.env.length;
18995dfecf96Smrg		    LispAddVar(Oprint_escape, T);
19005dfecf96Smrg		    ++lisp__data.env.head;
19015dfecf96Smrg		    format_ascii(stream, object, args);
19025dfecf96Smrg		    lisp__data.env.head = lisp__data.env.length = head;
19035dfecf96Smrg		    break;
19045dfecf96Smrg		case 'B':
19055dfecf96Smrg		    format_in_radix(stream, object, 2, args);
19065dfecf96Smrg		    break;
19075dfecf96Smrg		case 'O':
19085dfecf96Smrg		    format_in_radix(stream, object, 8, args);
19095dfecf96Smrg		    break;
19105dfecf96Smrg		case 'D':
19115dfecf96Smrg		    format_in_radix(stream, object, 10, args);
19125dfecf96Smrg		    break;
19135dfecf96Smrg		case 'X':
19145dfecf96Smrg		    format_in_radix(stream, object, 16, args);
19155dfecf96Smrg		    break;
19165dfecf96Smrg		case 'R':
19175dfecf96Smrg		    /* if a single argument specified */
19185dfecf96Smrg		    if (args->count)
19195dfecf96Smrg			format_in_radix(stream, object, 0, args);
19205dfecf96Smrg		    else
19215dfecf96Smrg			format_radix_special(stream, object, args);
19225dfecf96Smrg		    break;
19235dfecf96Smrg		case 'P':
19245dfecf96Smrg		    if (args->atsign) {
19255dfecf96Smrg			if (FIXNUMP(object) && FIXNUM_VALUE(object) == 1)
19265dfecf96Smrg			    LispWriteChar(stream, 'y');
19275dfecf96Smrg			else
19285dfecf96Smrg			    LispWriteStr(stream, "ies", 3);
19295dfecf96Smrg		    }
19305dfecf96Smrg		    else if (!FIXNUMP(object) || FIXNUM_VALUE(object) != 1)
19315dfecf96Smrg			LispWriteChar(stream, 's');
19325dfecf96Smrg		    break;
19335dfecf96Smrg		case 'C':
19345dfecf96Smrg		    format_character(stream, object, args);
19355dfecf96Smrg		    break;
19365dfecf96Smrg		case 'F':
19375dfecf96Smrg		    format_fixed_float(stream, object, args);
19385dfecf96Smrg		    break;
19395dfecf96Smrg		case 'E':
19405dfecf96Smrg		    format_exponential_float(stream, object, args);
19415dfecf96Smrg		    break;
19425dfecf96Smrg		case 'G':
19435dfecf96Smrg		    format_general_float(stream, object, args);
19445dfecf96Smrg		    break;
19455dfecf96Smrg		case '$':
19465dfecf96Smrg		    format_dollar_float(stream, object, args);
19475dfecf96Smrg		    break;
19485dfecf96Smrg		case '&':
19495dfecf96Smrg		    if (LispGetColumn(stream) == 0)
19505dfecf96Smrg			--args->arguments[0].value;
19515dfecf96Smrg		case '%':
19525dfecf96Smrg		    LispWriteChars(stream, '\n', args->arguments[0].value);
19535dfecf96Smrg		    break;
19545dfecf96Smrg		case '|':
19555dfecf96Smrg		    LispWriteChars(stream, '\f', args->arguments[0].value);
19565dfecf96Smrg		    break;
19575dfecf96Smrg		case '~':
19585dfecf96Smrg		    LispWriteChars(stream, '~', args->arguments[0].value);
19595dfecf96Smrg		    break;
19605dfecf96Smrg		case '\n':
19615dfecf96Smrg		    if (!args->collon) {
19625dfecf96Smrg			if (args->atsign)
19635dfecf96Smrg			    LispWriteChar(stream, '\n');
19645dfecf96Smrg			/* ignore newline and following spaces */
19655dfecf96Smrg			while (*next_format && isspace(*next_format))
19665dfecf96Smrg			    ++next_format;
19675dfecf96Smrg		    }
19685dfecf96Smrg		    break;
19695dfecf96Smrg		case 'T':
19705dfecf96Smrg		    format_tabulate(stream, args);
19715dfecf96Smrg		    break;
19725dfecf96Smrg		case '*':
19735dfecf96Smrg		    format_goto(info);
19745dfecf96Smrg		    break;
19755dfecf96Smrg		case '?':
19765dfecf96Smrg		    format_indirection(stream, object, info);
19775dfecf96Smrg		    need_update = 1;
19785dfecf96Smrg		    break;
19795dfecf96Smrg		case '(':
19805dfecf96Smrg		    format_case_conversion(stream, info);
19815dfecf96Smrg		    /* next_format if far from what is set now */
19825dfecf96Smrg		    next_format = *(info->format);
19835dfecf96Smrg		    break;
19845dfecf96Smrg		case '[':
19855dfecf96Smrg		    format_conditional(stream, info);
19865dfecf96Smrg		    /* next_format if far from what is set now */
19875dfecf96Smrg		    next_format = *(info->format);
19885dfecf96Smrg		    break;
19895dfecf96Smrg		case '{':
19905dfecf96Smrg		    format_iterate(stream, info);
19915dfecf96Smrg		    /* next_format if far from what is set now */
19925dfecf96Smrg		    next_format = *(info->format);
19935dfecf96Smrg		    break;
19945dfecf96Smrg		case '<':
19955dfecf96Smrg		    format_justify(stream, info);
19965dfecf96Smrg		    /* next_format if far from what is set now */
19975dfecf96Smrg		    next_format = *(info->format);
19985dfecf96Smrg		    break;
19995dfecf96Smrg		case '^':
20005dfecf96Smrg		    if (args->collon) {
20015dfecf96Smrg			if (hash && num_arguments == 0) {
20025dfecf96Smrg			    info->upandout = UPANDOUT_HASH;
20035dfecf96Smrg			    goto format_up_and_out;
20045dfecf96Smrg			}
20055dfecf96Smrg			if (info->iteration &&
20065dfecf96Smrg			    info->iteration == ITERATION_NORMAL)
20075dfecf96Smrg			/* not exactly an error, but in this case,
20085dfecf96Smrg			 * command is ignored */
20095dfecf96Smrg			    break;
20105dfecf96Smrg			info->upandout = UPANDOUT_COLLON;
20115dfecf96Smrg			goto format_up_and_out;
20125dfecf96Smrg		    }
20135dfecf96Smrg		    else if (num_arguments == 0) {
20145dfecf96Smrg			info->upandout = UPANDOUT_NORMAL;
20155dfecf96Smrg			goto format_up_and_out;
20165dfecf96Smrg		    }
20175dfecf96Smrg		    break;
20185dfecf96Smrg	    }
20195dfecf96Smrg
20205dfecf96Smrg	    if (need_update) {
20215dfecf96Smrg		object = *(info->object);
20225dfecf96Smrg		arguments = *(info->arguments);
20235dfecf96Smrg		num_arguments = *(info->num_arguments);
20245dfecf96Smrg	    }
20255dfecf96Smrg
20265dfecf96Smrg	    format = next_format;
20275dfecf96Smrg	}
20285dfecf96Smrg	else {
20295dfecf96Smrg	    if (length >= sizeof(stk)) {
20305dfecf96Smrg		LispWriteStr(stream, stk, length);
20315dfecf96Smrg		length = 0;
20325dfecf96Smrg	    }
20335dfecf96Smrg	    stk[length++] = *format++;
20345dfecf96Smrg	}
20355dfecf96Smrg    }
20365dfecf96Smrg
20375dfecf96Smrg    /* flush any peding output */
20385dfecf96Smrg    if (length)
20395dfecf96Smrg	LispWriteStr(stream, stk, length);
20405dfecf96Smrg
20415dfecf96Smrgformat_up_and_out:
20425dfecf96Smrg    /* update for recursive call */
20435dfecf96Smrg    *(info->format) = format;
20445dfecf96Smrg    *(info->object) = object;
20455dfecf96Smrg    *(info->arguments) = arguments;
20465dfecf96Smrg    *(info->num_arguments) = num_arguments;
20475dfecf96Smrg}
20485dfecf96Smrg
20495dfecf96SmrgLispObj *
20505dfecf96SmrgLisp_Format(LispBuiltin *builtin)
20515dfecf96Smrg/*
20525dfecf96Smrg format destination control-string &rest arguments
20535dfecf96Smrg */
20545dfecf96Smrg{
20555dfecf96Smrg    GC_ENTER();
20565dfecf96Smrg    FmtInfo info;
20575dfecf96Smrg    LispObj *object;
20585dfecf96Smrg    char *control_string;
20595dfecf96Smrg    int num_arguments;
20605dfecf96Smrg
20615dfecf96Smrg    LispObj *stream, *format, *arguments;
20625dfecf96Smrg
20635dfecf96Smrg    arguments = ARGUMENT(2);
20645dfecf96Smrg    format = ARGUMENT(1);
20655dfecf96Smrg    stream = ARGUMENT(0);
20665dfecf96Smrg
20675dfecf96Smrg    /* check format and stream */
20685dfecf96Smrg    CHECK_STRING(format);
20695dfecf96Smrg    if (stream == NIL) {	/* return a string */
20705dfecf96Smrg	stream = LSTRINGSTREAM("", STREAM_READ | STREAM_WRITE, 0);
20715dfecf96Smrg	GC_PROTECT(stream);
20725dfecf96Smrg    }
20735dfecf96Smrg    else if (stream == T ||	/* print directly to *standard-output* */
20745dfecf96Smrg	     stream == STANDARD_OUTPUT)
20755dfecf96Smrg	stream = NIL;
20765dfecf96Smrg    else {
20775dfecf96Smrg	CHECK_STREAM(stream);
20785dfecf96Smrg	if (!stream->data.stream.writable)
20795dfecf96Smrg	    LispDestroy("%s: stream %s is not writable",
20805dfecf96Smrg			STRFUN(builtin), STROBJ(stream));
20815dfecf96Smrg    }
20825dfecf96Smrg
20835dfecf96Smrg    /* count number of arguments */
20845dfecf96Smrg    for (object = arguments, num_arguments = 0; CONSP(object);
20855dfecf96Smrg	 object = CDR(object), num_arguments++)
20865dfecf96Smrg	;
20875dfecf96Smrg
20885dfecf96Smrg    /* initialize plural/argument info */
20895dfecf96Smrg    object = NIL;
20905dfecf96Smrg
20915dfecf96Smrg    /* the format string */
20925dfecf96Smrg    control_string = THESTR(format);
20935dfecf96Smrg
20945dfecf96Smrg    /* arguments to recursive calls */
20955dfecf96Smrg    info.args.base = control_string;
20965dfecf96Smrg    info.base_arguments = arguments;
20975dfecf96Smrg    info.total_arguments = num_arguments;
20985dfecf96Smrg    info.format = &control_string;
20995dfecf96Smrg    info.object = &object;
21005dfecf96Smrg    info.arguments = &arguments;
21015dfecf96Smrg    info.num_arguments = &num_arguments;
21025dfecf96Smrg    info.iteration = 0;
21035dfecf96Smrg
21045dfecf96Smrg    /* format arguments */
21055dfecf96Smrg    LispFormat(stream, &info);
21065dfecf96Smrg
21075dfecf96Smrg    /* if printing to stdout */
21085dfecf96Smrg    if (stream == NIL)
21095dfecf96Smrg	LispFflush(Stdout);
21105dfecf96Smrg    /* else if printing to string-stream, return a string */
21115dfecf96Smrg    else if (stream->data.stream.type == LispStreamString) {
21125dfecf96Smrg	int length;
2113f765521fSmrg	const char *string;
21145dfecf96Smrg
21155dfecf96Smrg	string = LispGetSstring(SSTREAMP(stream), &length);
21165dfecf96Smrg	stream = LSTRING(string, length);
21175dfecf96Smrg    }
21185dfecf96Smrg
21195dfecf96Smrg    GC_LEAVE();
21205dfecf96Smrg
21215dfecf96Smrg    return (stream);
21225dfecf96Smrg}
2123