/*
 * 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/read.c,v 1.36tsi Exp $ */

#include <errno.h>
#include "lisp/read.h"
#include "lisp/package.h"
#include "lisp/write.h"
#include <fcntl.h>
#include <stdarg.h>

/* This should be visible only in read.c, but if an error is generated,
 * the current code in write.c will print it as #<ERROR> */
#define LABEL_BIT_COUNT		8
#define LABEL_BIT_MASK		0xff
#define MAX_LABEL_VALUE		((1L << (sizeof(long) * 8 - 9)) - 1)
#define READLABEL(label)						\
    (LispObj*)(((label) << LABEL_BIT_COUNT) | READLABEL_MASK)
#define READLABELP(object)						\
    (((unsigned long)(object) & LABEL_BIT_MASK) == READLABEL_MASK)
#define READLABEL_VALUE(object)						\
    ((long)(object) >> LABEL_BIT_COUNT)

#define READ_ENTER()							\
    LispObj *read__stream = SINPUT;					\
    int read__line = LispGetLine(read__stream)
#define READ_ERROR0(format)						\
    LispReadError(read__stream, read__line, format)
#define READ_ERROR1(format, arg1)					\
    LispReadError(read__stream, read__line, format, arg1)
#define READ_ERROR2(format, arg1, arg2)					\
    LispReadError(read__stream, read__line, format, arg1, arg2)

#define READ_ERROR_EOF()	READ_ERROR0("unexpected end of input")
#define READ_ERROR_FIXNUM()	READ_ERROR0("number is not a fixnum")
#define READ_ERROR_INVARG()	READ_ERROR0("invalid argument")

#ifdef __APPLE__
# define finite(x) isfinite(x)
#endif

/*
 * Types
 */
typedef struct _object_info {
    long label;		/* the read label of this object */
    LispObj *object;	/* the resulting object */
    long num_circles;	/* references to object before it was completely read */
} object_info;

typedef struct _read_info {
    int level;		/* level of open parentheses */

    int nodot;		/* flag set when reading a "special" list */

    int discard;	/* flag used when reading an unavailable feature */

    long circle_count;	/* if non zero, must resolve some labels */

    /* information for #<number>= and #<number># */
    object_info *objects;
    long num_objects;

    /* could use only the objects field as all circular data is known,
     * but check every object so that circular/shared references generated
     * by evaluations would not cause an infinite loop at read time */
    LispObj **circles;
    long num_circles;
} read_info;

/*
 * Protypes
 */
static LispObj *LispReadChar(LispBuiltin*, int);

static int LispGetLine(LispObj*);
#ifdef __GNUC__
#define PRINTF_FORMAT	__attribute__ ((format (printf, 3, 4)))
#else
#define PRINTF_FORMAT	/**/
#endif
static void LispReadError(LispObj*, int, const char*, ...);
#undef PRINTF_FORMAT
static void LispReadFixCircle(LispObj*, read_info*);
static LispObj *LispReadLabelCircle(LispObj*, read_info*);
static int LispReadCheckCircle(LispObj*, read_info*);
static LispObj *LispDoRead(read_info*);
static int LispSkipWhiteSpace(void);
static LispObj *LispReadList(read_info*);
static LispObj *LispReadQuote(read_info*);
static LispObj *LispReadBackquote(read_info*);
static LispObj *LispReadCommaquote(read_info*);
static LispObj *LispReadObject(int, read_info*);
static LispObj *LispParseAtom(char*, char*, int, int, LispObj*, int);
static LispObj *LispParseNumber(char*, int, LispObj*, int);
static int StringInRadix(char*, int, int);
static int AtomSeparator(int, int, int);
static LispObj *LispReadVector(read_info*);
static LispObj *LispReadMacro(read_info*);
static LispObj *LispReadFunction(read_info*);
static LispObj *LispReadRational(int, read_info*);
static LispObj *LispReadCharacter(read_info*);
static void LispSkipComment(void);
static LispObj *LispReadEval(read_info*);
static LispObj *LispReadComplex(read_info*);
static LispObj *LispReadPathname(read_info*);
static LispObj *LispReadStruct(read_info*);
static LispObj *LispReadMacroArg(read_info*);
static LispObj *LispReadArray(long, read_info*);
static LispObj *LispReadFeature(int, read_info*);
static LispObj *LispEvalFeature(LispObj*);

/*
 * Initialization
 */
static const char * const Char_Nul[] = {"Null", "Nul", NULL};
static const char * const Char_Soh[] = {"Soh", NULL};
static const char * const Char_Stx[] = {"Stx", NULL};
static const char * const Char_Etx[] = {"Etx", NULL};
static const char * const Char_Eot[] = {"Eot", NULL};
static const char * const Char_Enq[] = {"Enq", NULL};
static const char * const Char_Ack[] = {"Ack", NULL};
static const char * const Char_Bel[] = {"Bell", "Bel", NULL};
static const char * const Char_Bs[]  = {"Backspace", "Bs", NULL};
static const char * const Char_Tab[] = {"Tab", NULL};
static const char * const Char_Nl[]  = {"Newline", "Nl", "Lf", "Linefeed", NULL};
static const char * const Char_Vt[]  = {"Vt", NULL};
static const char * const Char_Np[]  = {"Page", "Np", NULL};
static const char * const Char_Cr[]  = {"Return", "Cr", NULL};
static const char * const Char_Ff[]  = {"So", "Ff", NULL};
static const char * const Char_Si[]  = {"Si", NULL};
static const char * const Char_Dle[] = {"Dle", NULL};
static const char * const Char_Dc1[] = {"Dc1", NULL};
static const char * const Char_Dc2[] = {"Dc2", NULL};
static const char * const Char_Dc3[] = {"Dc3", NULL};
static const char * const Char_Dc4[] = {"Dc4", NULL};
static const char * const Char_Nak[] = {"Nak", NULL};
static const char * const Char_Syn[] = {"Syn", NULL};
static const char * const Char_Etb[] = {"Etb", NULL};
static const char * const Char_Can[] = {"Can", NULL};
static const char * const Char_Em[]  = {"Em", NULL};
static const char * const Char_Sub[] = {"Sub", NULL};
static const char * const Char_Esc[] = {"Escape", "Esc", NULL};
static const char * const Char_Fs[]  = {"Fs", NULL};
static const char * const Char_Gs[]  = {"Gs", NULL};
static const char * const Char_Rs[]  = {"Rs", NULL};
static const char * const Char_Us[]  = {"Us", NULL};
static const char * const Char_Sp[]  = {"Space", "Sp", NULL};
static const char * const Char_Del[] = {"Rubout", "Del", "Delete", NULL};

const LispCharInfo LispChars[256] = {
    {Char_Nul},
    {Char_Soh},
    {Char_Stx},
    {Char_Etx},
    {Char_Eot},
    {Char_Enq},
    {Char_Ack},
    {Char_Bel},
    {Char_Bs},
    {Char_Tab},
    {Char_Nl},
    {Char_Vt},
    {Char_Np},
    {Char_Cr},
    {Char_Ff},
    {Char_Si},
    {Char_Dle},
    {Char_Dc1},
    {Char_Dc2},
    {Char_Dc3},
    {Char_Dc4},
    {Char_Nak},
    {Char_Syn},
    {Char_Etb},
    {Char_Can},
    {Char_Em},
    {Char_Sub},
    {Char_Esc},
    {Char_Fs},
    {Char_Gs},
    {Char_Rs},
    {Char_Us},
    {Char_Sp},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {Char_Del},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL},
    {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}

};

Atom_id Sand, Sor, Snot;


/*
 * Implementation
 */
LispObj *
Lisp_Read(LispBuiltin *builtin)
/*
 read &optional input-stream eof-error-p eof-value recursive-p
 */
{
    LispObj *result;

    LispObj *input_stream, *eof_error_p, *eof_value;

    eof_value = ARGUMENT(2);
    eof_error_p = ARGUMENT(1);
    input_stream = ARGUMENT(0);

    if (input_stream == UNSPEC)
	input_stream = NIL;
    else if (input_stream != NIL) {
	CHECK_STREAM(input_stream);
	else if (!input_stream->data.stream.readable)
	    LispDestroy("%s: stream %s is not readable",
			STRFUN(builtin), STROBJ(input_stream));
	LispPushInput(input_stream);
    }
    else if (CONSP(lisp__data.input_list)) {
	input_stream = STANDARD_INPUT;
	LispPushInput(input_stream);
    }

    if (eof_value == UNSPEC)
	eof_value = NIL;

    result = LispRead();
    if (input_stream != NIL)
	LispPopInput(input_stream);

    if (result == NULL) {
	if (eof_error_p != NIL)
	    LispDestroy("%s: EOF reading stream %s",
			STRFUN(builtin), STROBJ(input_stream));
	else
	    result = eof_value;
    }

    return (result);
}

static LispObj *
LispReadChar(LispBuiltin *builtin, int nohang)
{
    int character;

    LispObj *input_stream, *eof_error_p, *eof_value;

    eof_value = ARGUMENT(2);
    eof_error_p = ARGUMENT(1);
    input_stream = ARGUMENT(0);

    if (input_stream == UNSPEC)
	input_stream = NIL;
    else if (input_stream != NIL) {
	CHECK_STREAM(input_stream);
    }
    else
	input_stream = lisp__data.input;

    if (eof_value == UNSPEC)
	eof_value = NIL;

    character = EOF;

    if (input_stream->data.stream.readable) {
	LispFile *file = NULL;

	switch (input_stream->data.stream.type) {
	    case LispStreamStandard:
	    case LispStreamFile:
		file = FSTREAMP(input_stream);
		break;
	    case LispStreamPipe:
		file = IPSTREAMP(input_stream);
		break;
	    case LispStreamString:
		character = LispSgetc(SSTREAMP(input_stream));
		break;
	    default:
		break;
	}
	if (file != NULL) {
	    if (file->available || file->offset < file->length)
		character = LispFgetc(file);
	    else {
		if (nohang && !file->nonblock) {
		    if (fcntl(file->descriptor, F_SETFL, O_NONBLOCK) < 0)
			LispDestroy("%s: fcntl(%d): %s",
				    STRFUN(builtin), file->descriptor,
				    strerror(errno));
		    file->nonblock = 1;
		}
		else if (!nohang && file->nonblock) {
		    if (fcntl(file->descriptor, F_SETFL, 0) < 0)
			LispDestroy("%s: fcntl(%d): %s",
				    STRFUN(builtin), file->descriptor,
				    strerror(errno));
		    file->nonblock = 0;
		}
		if (nohang) {
		    unsigned char ch;

		    if (read(file->descriptor, &ch, 1) == 1)
			character = ch;
		    else if (errno == EAGAIN)
			return (NIL);	/* XXX no character available */
		    else
			character = EOF;
		}
		else
		    character = LispFgetc(file);
	    }
	}
    }
    else
	LispDestroy("%s: stream %s is unreadable",
		    STRFUN(builtin), STROBJ(input_stream));

    if (character == EOF) {
	if (eof_error_p != NIL)
	    LispDestroy("%s: EOF reading stream %s",
			STRFUN(builtin), STROBJ(input_stream));

	return (eof_value);
    }

    return (SCHAR(character));
}

LispObj *
Lisp_ReadChar(LispBuiltin *builtin)
/*
 read-char &optional input-stream eof-error-p eof-value recursive-p
 */
{
    return (LispReadChar(builtin, 0));
}

LispObj *
Lisp_ReadCharNoHang(LispBuiltin *builtin)
/*
 read-char-no-hang &optional input-stream eof-error-p eof-value recursive-p
 */
{
    return (LispReadChar(builtin, 1));
}

LispObj *
Lisp_ReadLine(LispBuiltin *builtin)
/*
 read-line &optional input-stream eof-error-p eof-value recursive-p
 */
{
    char *string;
    int ch, length;
    LispObj *result, *status = NIL;

    LispObj *input_stream, *eof_error_p, *eof_value;

    eof_value = ARGUMENT(2);
    eof_error_p = ARGUMENT(1);
    input_stream = ARGUMENT(0);

    if (input_stream == UNSPEC)
	input_stream = NIL;
    else if (input_stream == NIL)
	input_stream = STANDARD_INPUT;
    else {
	CHECK_STREAM(input_stream);
    }

    if (eof_value == UNSPEC)
	eof_value = NIL;

    result = NIL;
    string = NULL;
    length = 0;

    if (!input_stream->data.stream.readable)
	LispDestroy("%s: stream %s is unreadable",
		    STRFUN(builtin), STROBJ(input_stream));
    if (input_stream->data.stream.type == LispStreamString) {
	char *start, *end, *ptr;

	if (SSTREAMP(input_stream)->input >=
	    SSTREAMP(input_stream)->length) {
	    if (eof_error_p != NIL)
		LispDestroy("%s: EOS found reading %s",
			    STRFUN(builtin), STROBJ(input_stream));

	    status = T;
	    result = eof_value;
	    goto read_line_done;
	}

	start = SSTREAMP(input_stream)->string +
		SSTREAMP(input_stream)->input;
	end = SSTREAMP(input_stream)->string +
	      SSTREAMP(input_stream)->length;
	/* Search for a newline */
	for (ptr = start; *ptr != '\n' && ptr < end; ptr++)
	    ;
	if (ptr == end)
	    status = T;
	else if (!SSTREAMP(input_stream)->binary)
	    ++SSTREAMP(input_stream)->line;
	length = ptr - start;
	string = LispMalloc(length + 1);
	memcpy(string, start, length);
	string[length] = '\0';
	result = LSTRING2(string, length);
	/* macro LSTRING2 does not make a copy of it's arguments, and
	 * calls LispMused on it. */
	SSTREAMP(input_stream)->input += length + (status == NIL);
    }
    else /*if (input_stream->data.stream.type == LispStreamFile ||
	     input_stream->data.stream.type == LispStreamStandard ||
	     input_stream->data.stream.type == LispStreamPipe)*/ {
	LispFile *file;

	if (input_stream->data.stream.type == LispStreamPipe)
	    file = IPSTREAMP(input_stream);
	else
	    file = FSTREAMP(input_stream);

	if (file->nonblock) {
	    if (fcntl(file->descriptor, F_SETFL, 0) < 0)
		LispDestroy("%s: fcntl: %s",
			    STRFUN(builtin), strerror(errno));
	    file->nonblock = 0;
	}

	while (1) {
	    ch = LispFgetc(file);
	    if (ch == EOF) {
		if (length)
		    break;
		if (eof_error_p != NIL)
		    LispDestroy("%s: EOF found reading %s",
				STRFUN(builtin), STROBJ(input_stream));
		if (string)
		    LispFree(string);

		status = T;
		result = eof_value;
		goto read_line_done;
	    }
	    else if (ch == '\n')
		break;
	    else if ((length % 64) == 0)
		string = LispRealloc(string, length + 64);
	    string[length++] = ch;
	}
	if (string) {
	    if ((length % 64) == 0)
		string = LispRealloc(string, length + 1);
	    string[length] = '\0';
	    result = LSTRING2(string, length);
	}
	else
	    result = STRING("");
    }

read_line_done:
    RETURN(0) = status;
    RETURN_COUNT = 1;

    return (result);
}

LispObj *
LispRead(void)
{
    READ_ENTER();
    read_info info;
    LispObj *result, *code = COD;

    info.level = info.nodot = info.discard = 0;
    info.circle_count = 0;
    info.objects = NULL;
    info.num_objects = 0;

    result = LispDoRead(&info);

    /* fix circular/shared lists, note that this is done when returning to
     * the toplevel, so, if some circular/shared reference was evaluated,
     * it should have generated an expected error */
    if (info.num_objects) {
	if (info.circle_count) {
	    info.circles = NULL;
	    info.num_circles = 0;
	    LispReadFixCircle(result, &info);
	    if (info.num_circles)
		LispFree(info.circles);
	}
	LispFree(info.objects);
    }

    if (result == EOLIST)
	READ_ERROR0("object cannot start with #\\)");
    else if (result == DOT)
	READ_ERROR0("dot allowed only on lists");

    if (result != NULL && POINTERP(result)) {
	if (code == NIL)
	    COD = result;
	else
	    COD = CONS(COD, result);
    }

    return (result);
}

static int
LispGetLine(LispObj *stream)
{
    int line = -1;

    if (STREAMP(stream)) {
	switch (stream->data.stream.type) {
	    case LispStreamStandard:
	    case LispStreamFile:
		if (!FSTREAMP(stream)->binary)
		    line = FSTREAMP(stream)->line;
		break;
	    case LispStreamPipe:
		if (!IPSTREAMP(stream)->binary)
		    line = IPSTREAMP(stream)->line;
		break;
	    case LispStreamString:
		if (!SSTREAMP(stream)->binary)
		    line = SSTREAMP(stream)->line;
		break;
	    default:
		break;
	}
    }
    else if (stream == NIL && !Stdin->binary)
	line = Stdin->line;

    return (line);
}

static void
LispReadError(LispObj *stream, int line, const char *fmt, ...)
{
    char string[128];
    const char *buffer_string;
    LispObj *buffer = LSTRINGSTREAM("", STREAM_READ | STREAM_WRITE, 0);
    int length;
    va_list ap;

    va_start(ap, fmt);
    vsnprintf(string, sizeof(string), fmt, ap);
    va_end(ap);

    LispFwrite(Stderr, "*** Reading ", 12);
    LispWriteObject(buffer, stream);
    buffer_string = LispGetSstring(SSTREAMP(buffer), &length);
    LispFwrite(Stderr, buffer_string, length);
    LispFwrite(Stderr, " at line ", 9);
    if (line < 0)
	LispFwrite(Stderr, "?\n", 2);
    else {
	char str[32];

	sprintf(str, "%d\n", line);
	LispFputs(Stderr, str);
    }

    LispDestroy("READ: %s", string);
}

static void
LispReadFixCircle(LispObj *object, read_info *info)
{
    LispObj *cons;

fix_again:
    switch (OBJECT_TYPE(object)) {
	case LispCons_t:
	    for (cons = object;
		 CONSP(object);
		 cons = object, object = CDR(object)) {
		if (READLABELP(CAR(object)))
		    CAR(object) = LispReadLabelCircle(CAR(object), info);
		else if (LispReadCheckCircle(object, info))
		    return;
		else
		    LispReadFixCircle(CAR(object), info);
	    }
	    if (READLABELP(object))
		CDR(cons) = LispReadLabelCircle(object, info);
	    else
		goto fix_again;
	    break;
	case LispArray_t:
	    if (READLABELP(object->data.array.list))
		object->data.array.list =
		    LispReadLabelCircle(object->data.array.list, info);
	    else if (!LispReadCheckCircle(object, info)) {
		object = object->data.array.list;
		goto fix_again;
	    }
	    break;
	case LispStruct_t:
	    if (READLABELP(object->data.struc.fields))
		object->data.struc.fields =
		    LispReadLabelCircle(object->data.struc.fields, info);
	    else if (!LispReadCheckCircle(object, info)) {
		object = object->data.struc.fields;
		goto fix_again;
	    }
	    break;
	case LispQuote_t:
	case LispBackquote_t:
	case LispFunctionQuote_t:
	    if (READLABELP(object->data.quote))
		object->data.quote =
		    LispReadLabelCircle(object->data.quote, info);
	    else {
		object = object->data.quote;
		goto fix_again;
	    }
	    break;
	case LispComma_t:
	    if (READLABELP(object->data.comma.eval))
		object->data.comma.eval =
		    LispReadLabelCircle(object->data.comma.eval, info);
	    else {
		object = object->data.comma.eval;
		goto fix_again;
	    }
	    break;
	case LispLambda_t:
	    if (READLABELP(object->data.lambda.code))
		object->data.lambda.code =
		    LispReadLabelCircle(object->data.lambda.code, info);
	    else if (!LispReadCheckCircle(object, info)) {
		object = object->data.lambda.code;
		goto fix_again;
	    }
	    break;
	default:
	    break;
    }
}

static LispObj *
LispReadLabelCircle(LispObj *label, read_info *info)
{
    long i, value = READLABEL_VALUE(label);

    for (i = 0; i < info->num_objects; i++)
	if (info->objects[i].label == value)
	    return (info->objects[i].object);

    LispDestroy("READ: internal error");
    /*NOTREACHED*/
    return (label);
}

static int
LispReadCheckCircle(LispObj *object, read_info *info)
{
    long i;

    for (i = 0; i < info->num_circles; i++)
	if (info->circles[i] == object)
	    return (1);

    if ((info->num_circles % 16) == 0)
	info->circles = LispRealloc(info->circles, sizeof(LispObj*) *
				    (info->num_circles + 16));
    info->circles[info->num_circles++] = object;

    return (0);
}

static LispObj *
LispDoRead(read_info *info)
{
    LispObj *object;
    int ch = LispSkipWhiteSpace();

    switch (ch) {
	case '(':
	    object = LispReadList(info);
	    break;
	case ')':
	    for (ch = LispGet(); ch != EOF && ch != '\n'; ch = LispGet()) {
		if (!isspace(ch)) {
		    LispUnget(ch);
		    break;
		}
	    }
	    return (EOLIST);
	case EOF:
	    return (NULL);
	case '\'':
	    object = LispReadQuote(info);
	    break;
	case '`':
	    object = LispReadBackquote(info);
	    break;
	case ',':
	    object = LispReadCommaquote(info);
	    break;
	case '#':
	    object = LispReadMacro(info);
	    break;
	default:
	    LispUnget(ch);
	    object = LispReadObject(0, info);
	    break;
    }

    return (object);
}

static LispObj *
LispReadMacro(read_info *info)
{
    READ_ENTER();
    LispObj *result = NULL;
    int ch = LispGet();

    switch (ch) {
	case '(':
	    result = LispReadVector(info);
	    break;
	case '\'':
	   result = LispReadFunction(info);
	   break;
	case 'b':
	case 'B':
	    result = LispReadRational(2, info);
	    break;
	case 'o':
	case 'O':
	    result = LispReadRational(8, info);
	    break;
	case 'x':
	case 'X':
	    result = LispReadRational(16, info);
	    break;
	case '\\':
	    result = LispReadCharacter(info);
	    break;
	case '|':
	    LispSkipComment();
	    result = LispDoRead(info);
	    break;
	case '.':	/* eval when compiling */
	case ',':	/* eval when loading */
	    result = LispReadEval(info);
	    break;
	case 'c':
	case 'C':
	    result = LispReadComplex(info);
	    break;
	case 'p':
	case 'P':
	    result = LispReadPathname(info);
	    break;
	case 's':
	case 'S':
	    result = LispReadStruct(info);
	    break;
	case '+':
	    result = LispReadFeature(1, info);
	    break;
	case '-':
	    result = LispReadFeature(0, info);
	    break;
	case ':':
	    /* Uninterned symbol */
	    result = LispReadObject(1, info);
	    break;
	default:
	    if (isdigit(ch)) {
		LispUnget(ch);
		result = LispReadMacroArg(info);
	    }
	    else if (!info->discard)
		READ_ERROR1("undefined dispatch macro character #%c", ch);
	    break;
    }

    return (result);
}

static LispObj *
LispReadMacroArg(read_info *info)
{
    READ_ENTER();
    LispObj *result = NIL;
    long i, integer;
    int ch;

    /* skip leading zeros */
    while (ch = LispGet(), ch != EOF && isdigit(ch) && ch == '0')
	;

    if (ch == EOF)
	READ_ERROR_EOF();

    /* if ch is not a number the argument was zero */
    if (isdigit(ch)) {
	char stk[32], *str;
	int len = 1;

	stk[0] = ch;
	for (;;) {
	    ch = LispGet();
	    if (!isdigit(ch))
		break;
	    if (len + 1 >= sizeof(stk))
		READ_ERROR_FIXNUM();
	    stk[len++] = ch;
	}
	stk[len] = '\0';
	errno = 0;
	integer = strtol(stk, &str, 10);
	/* number is positive because sign is not processed here */
	if (*str || errno == ERANGE || integer > MOST_POSITIVE_FIXNUM)
	    READ_ERROR_FIXNUM();
    }
    else
	integer = 0;

    switch (ch) {
	case 'a':
	case 'A':
	    if (integer == 1) {
		/* LispReadArray and LispReadList expect
		 * the '(' being already read  */
		if ((ch = LispSkipWhiteSpace()) != '(') {
		    if (info->discard)
			return (ch == EOF ? NULL : NIL);
		    READ_ERROR0("bad array specification");
		}
		result = LispReadVector(info);
	    }
	    else
		result = LispReadArray(integer, info);
	    break;
	case 'r':
	case 'R':
	    result = LispReadRational(integer, info);
	    break;
	case '=':
	    if (integer > MAX_LABEL_VALUE)
		READ_ERROR_FIXNUM();
	    if (!info->discard) {
		long num_objects = info->num_objects;

		/* check for duplicated label */
		for (i = 0; i < info->num_objects; i++) {
		    if (info->objects[i].label == integer)
			READ_ERROR1("label #%ld# defined more than once",
				    integer);
		}
		info->objects = LispRealloc(info->objects,
					    sizeof(object_info) *
					    (num_objects + 1));
		/* if this label is referenced it is a shared/circular object */
		info->objects[num_objects].label = integer;
		info->objects[num_objects].object = NULL;
		info->objects[num_objects].num_circles = 0;
		++info->num_objects;
		result = LispDoRead(info);
		if (READLABELP(result) && READLABEL_VALUE(result) == integer)
		    READ_ERROR2("incorrect syntax #%ld= #%ld#",
				integer, integer);
		/* any reference to it now is not shared/circular */
		info->objects[num_objects].object = result;
	    }
	    else
		result = LispDoRead(info);
	    break;
	case '#':
	    if (integer > MAX_LABEL_VALUE)
		READ_ERROR_FIXNUM();
	    if (!info->discard) {
		/* search object */
		for (i = 0; i < info->num_objects; i++) {
		    if (info->objects[i].label == integer) {
			result = info->objects[i].object;
			if (result == NULL) {
			    ++info->objects[i].num_circles;
			    ++info->circle_count;
			    result = READLABEL(integer);
			}
			break;
		    }
		}
		if (i == info->num_objects)
		    READ_ERROR1("undefined label #%ld#", integer);
	    }
	    break;
	default:
	    if (!info->discard)
		READ_ERROR1("undefined dispatch macro character #%c", ch);
	    break;
    }

    return (result);
}

static int
LispSkipWhiteSpace(void)
{
    int ch;

    for (;;) {
	while (ch = LispGet(), isspace(ch) && ch != EOF)
	    ;
	if (ch == ';') {
	    while (ch = LispGet(), ch != '\n' && ch != EOF)
		;
	    if (ch == EOF)
		return (EOF);
	}
	else
	    break;
    }

    return (ch);
}

/* any data in the format '(' FORM ')' is read here */
static LispObj *
LispReadList(read_info *info)
{
    READ_ENTER();
    GC_ENTER();
    LispObj *result, *cons, *object;
    int dot = 0;

    ++info->level;
    /* check for () */
    object = LispDoRead(info);
    if (object == EOLIST) {
	--info->level;

	return (NIL);
    }

    if (object == DOT)
	READ_ERROR0("illegal start of dotted list");

    result = cons = CONS(object, NIL);

    /* make sure GC will not release data being read */
    GC_PROTECT(result);

    while ((object = LispDoRead(info)) != EOLIST) {
	if (object == NULL)
	    READ_ERROR_EOF();
	if (object == DOT) {
	    if (info->nodot == info->level)
		READ_ERROR0("dotted list not allowed");
	    /* this is a dotted list */
	    if (dot)
		READ_ERROR0("more than one . in list");
	    dot = 1;
	}
	else {
	    if (dot) {
		/* only one object after a dot */
		if (++dot > 2)
		    READ_ERROR0("more than one object after . in list");
		RPLACD(cons, object);
	    }
	    else {
		RPLACD(cons, CONS(object, NIL));
		cons = CDR(cons);
	    }
	}
    }

    /* this will happen if last list element was a dot */
    if (dot == 1)
	READ_ERROR0("illegal end of dotted list");

    --info->level;
    GC_LEAVE();

    return (result);
}

static LispObj *
LispReadQuote(read_info *info)
{
    READ_ENTER();
    LispObj *quote = LispDoRead(info), *result;

    if (INVALIDP(quote))
	READ_ERROR_INVARG();

    result = QUOTE(quote);

    return (result);
}

static LispObj *
LispReadBackquote(read_info *info)
{
    READ_ENTER();
    LispObj *backquote = LispDoRead(info), *result;

    if (INVALIDP(backquote))
	READ_ERROR_INVARG();

    result = BACKQUOTE(backquote);

    return (result);
}

static LispObj *
LispReadCommaquote(read_info *info)
{
    READ_ENTER();
    LispObj *comma, *result;
    int atlist = LispGet();

    if (atlist == EOF)
	READ_ERROR_EOF();
    else if (atlist != '@' && atlist != '.')
	LispUnget(atlist);

    comma = LispDoRead(info);
    if (comma == DOT) {
	atlist = '@';
	comma = LispDoRead(info);
    }
    if (INVALIDP(comma))
	READ_ERROR_INVARG();

    result = COMMA(comma, atlist == '@' || atlist == '.');

    return (result);
}

/*
 * Read anything that is not readily identifiable by it's first character
 * and also put the code for reading atoms, numbers and strings together.
 */
static LispObj *
LispReadObject(int unintern, read_info *info)
{
    READ_ENTER();
    LispObj *object;
    char stk[128], *string, *package, *symbol;
    int ch, length, backslash, size, quote, unreadable, collon;

    package = symbol = string = stk;
    size = sizeof(stk);
    backslash = quote = unreadable = collon = 0;
    length = 0;

    ch = LispGet();
    if (unintern && (ch == ':' || ch == '"'))
	READ_ERROR0("syntax error after #:");
    else if (ch == '"' || ch == '|')
	quote = ch;
    else if (ch == '\\') {
	unreadable = backslash = 1;
	string[length++] = ch;
    }
    else if (ch == ':') {
	collon = 1;
	string[length++] = ch;
	symbol = string + 1;
	ch = LispGet();
	if (ch == '|') {
	    quote = ch;
	    unreadable = 1;
	}
	else if (ch != EOF)
	    LispUnget(ch);
    }
    else if (ch) {
	if (islower(ch))
	    ch = toupper(ch);
	string[length++] = ch;
    }
    else
	unreadable = 1;

    /* read remaining data */
    for (; ch;) {
	ch = LispGet();

	if (ch == EOF) {
	    if (quote) {
		/* if quote, file ended with an open quoted object */
		if (string != stk)
		    LispFree(string);
		return (NULL);
	    }
	    break;
	}
	else if (ch == '\0')
	    break;

	if (ch == '\\') {
	    backslash = !backslash;
	    if (quote == '"') {
		/* only remove backslashs from strings */
		if (backslash)
		    continue;
	    }
	    else
		unreadable = 1;
	}
	else if (backslash)
	    backslash = 0;
	else if (ch == quote)
	    break;
	else if (!quote && !backslash) {
	    if (islower(ch))
		ch = toupper(ch);
	    else if (isspace(ch))
		break;
	    else if (AtomSeparator(ch, 0, 0)) {
		LispUnget(ch);
		break;
	    }
	    else if (ch == ':') {
		if (collon == 0 ||
		    (collon == (1 - unintern) && symbol == string + length)) {
		    ++collon;
		    symbol = string + length + 1;
		}
		else
		    READ_ERROR0("too many collons");
	    }
	}

	if (length + 2 >= size) {
	    if (string == stk) {
		size = 1024;
		string = LispMalloc(size);
		strcpy(string, stk);
	    }
	    else {
		size += 1024;
		string = LispRealloc(string, size);
	    }
	    symbol = string + (symbol - package);
	    package = string;
	}
	string[length++] = ch;
    }

    if (info->discard) {
	if (string != stk)
	    LispFree(string);

	return (ch == EOF ? NULL : NIL);
    }

    string[length] = '\0';

    if (unintern) {
	if (length == 0)
	    READ_ERROR0("syntax error after #:");
	object = UNINTERNED_ATOM(string);
    }

    else if (quote == '"')
	object = LSTRING(string, length);

    else if (collon) {
	/* Package specified in object name */
	symbol[-1] = '\0';
	if (collon > 1)
	    symbol[-2] = '\0';
	object = LispParseAtom(package, symbol,
			       collon == 2, unreadable,
			       read__stream, read__line);
    }

    else if (quote == '|' || (unreadable && !collon)) {
	/* Set unreadable field, this atom needs quoting to be read back */
	object = ATOM(string);
	object->data.atom->unreadable = 1;
    }

    /* Check some common symbols */
    else if (length == 1 && string[0] == 'T')
	/* The T */
	object = T;

    else if (length == 1 && string[0] == '.')
	/* The dot */
	object = DOT;

    else if (length == 3 &&
	     string[0] == 'N' && string[1] == 'I' && string[2] == 'L')
	/* The NIL */
	object = NIL;

    else if (isdigit(string[0]) || string[0] == '.' ||
	     ((string[0] == '-' || string[0] == '+') && string[1]))
	/* Looks like a number */
	object = LispParseNumber(string, 10, read__stream, read__line);

    else
	/* A normal atom */
	object = ATOM(string);

    if (string != stk)
	LispFree(string);

    return (object);
}

static LispObj *
LispParseAtom(char *package, char *symbol, int intern, int unreadable,
	      LispObj *read__stream, int read__line)
{
    LispObj *object = NULL, *thepackage = NULL;
    LispPackage *pack = NULL;

    if (!unreadable) {
	/* Until NIL and T be treated as normal symbols */
	if (symbol[0] == 'N' && symbol[1] == 'I' &&
	    symbol[2] == 'L' && symbol[3] == '\0')
	    return (NIL);
	if (symbol[0] == 'T' && symbol[1] == '\0')
	    return (T);
	unreadable = !LispCheckAtomString(symbol);
    }

    /* If package is empty, it is a keyword */
    if (package[0] == '\0') {
	thepackage = lisp__data.keyword;
	pack = lisp__data.key;
    }

    else {
	/* Else, search it in the package list */
	thepackage = LispFindPackageFromString(package);

	if (thepackage == NIL)
	    READ_ERROR1("the package %s is not available", package);

	pack = thepackage->data.package.package;
    }

    if (pack == lisp__data.pack && intern) {
	/* Redundant package specification, since requesting a
	 * intern symbol, create it if does not exist */

	object = ATOM(symbol);
	if (unreadable)
	    object->data.atom->unreadable = 1;
    }

    else if (intern || pack == lisp__data.key) {
	/* Symbol is created, or just fetched from the specified package */

	LispPackage *savepack;
	LispObj *savepackage = PACKAGE;

	/* Remember curent package */
	savepack = lisp__data.pack;

	/* Temporarily set another package */
	lisp__data.pack = pack;
	PACKAGE = thepackage;

	/* Get the object pointer */
	if (pack == lisp__data.key)
	    object = KEYWORD(LispDoGetAtom(symbol, 0)->key->value);
	else
	    object = ATOM(symbol);
	if (unreadable)
	    object->data.atom->unreadable = 1;

	/* Restore current package */
	lisp__data.pack = savepack;
	PACKAGE = savepackage;
    }

    else {
	/* Symbol must exist (and be extern) in the specified package */

	LispAtom *atom;

	atom = (LispAtom *)hash_check(pack->atoms, symbol, strlen(symbol));
	if (atom)
	    object = atom->object;

	/* No object found */
	if (object == NULL || object->data.atom->ext == 0)
	    READ_ERROR2("no extern symbol %s in package %s", symbol, package);
    }

    return (object);
}

static LispObj *
LispParseNumber(char *str, int radix, LispObj *read__stream, int read__line)
{
    int len;
    long integer;
    double dfloat;
    char *ratio, *ptr;
    LispObj *number;
    mpi *bignum;
    mpr *bigratio;

    if (radix < 2 || radix > 36)
	READ_ERROR1("radix %d is not in the range 2 to 36", radix);

    if (*str == '\0')
	return (NULL);

    ratio = strchr(str, '/');
    if (ratio) {
	/* check if looks like a correctly specified ratio */
	if (ratio[1] == '\0' || strchr(ratio + 1, '/') != NULL)
	    return (ATOM(str));

	/* ratio must point to an integer in radix base */
	*ratio++ = '\0';
    }
    else if (radix == 10) {
	int dot = 0;
	int type = 0;

	/* check if it is a floating point number */
	ptr = str;
	if (*ptr == '-' || *ptr == '+')
	    ++ptr;
	else if (*ptr == '.') {
	    dot = 1;
	    ++ptr;
	}
	while (*ptr) {
	    if (*ptr == '.') {
		if (dot)
		    return (ATOM(str));
		/* ignore it if last char is a dot */
		if (ptr[1] == '\0') {
		    *ptr = '\0';
		    break;
		}
		dot = 1;
	    }
	    else if (!isdigit(*ptr))
		break;
	    ++ptr;
	}

	switch (*ptr) {
	    case '\0':
		if (dot)		/* if dot, it is default float */
		    type = 'E';
		break;
	    case 'E': case 'S': case 'F': case 'D': case 'L':
		type = *ptr;
		*ptr = 'E';
		break;
	    default:
		return (ATOM(str));	/* syntax error */
	}

	/* if type set, it is not an integer specification */
	if (type) {
	    if (*ptr) {
		int itype = *ptr;
		char *ptype = ptr;

		++ptr;
		if (*ptr == '+' || *ptr == '-')
		    ++ptr;
		while (*ptr && isdigit(*ptr))
		    ++ptr;
		if (*ptr) {
		    *ptype = itype;

		    return (ATOM(str));
		}
	    }

	    dfloat = strtod(str, NULL);
	    if (!finite(dfloat))
		READ_ERROR0("floating point overflow");

	    return (DFLOAT(dfloat));
	}
    }

    /* check if correctly specified in the given radix */
    len = strlen(str) - 1;
    if (!ratio && radix != 10 && str[len] == '.')
	str[len] = '\0';

    if (ratio || radix != 10) {
	if (!StringInRadix(str, radix, 1)) {
	    if (ratio)
		ratio[-1] = '/';
	    return (ATOM(str));
	}
	if (ratio && !StringInRadix(ratio, radix, 0)) {
	    ratio[-1] = '/';
	    return (ATOM(str));
	}
    }

    bignum = NULL;
    bigratio = NULL;

    errno = 0;
    integer = strtol(str, NULL, radix);

    /* if does not fit in a long */
    if (errno == ERANGE) {
	bignum = LispMalloc(sizeof(mpi));
	mpi_init(bignum);
	mpi_setstr(bignum, str, radix);
    }


    if (ratio && integer != 0) {
	long denominator;

	errno = 0;
	denominator = strtol(ratio, NULL, radix);
	if (denominator == 0)
	    READ_ERROR0("divide by zero");

	if (bignum == NULL) {
	    if (integer == MINSLONG ||
		(denominator == LONG_MAX && errno == ERANGE)) {
		bigratio = LispMalloc(sizeof(mpr));
		mpr_init(bigratio);
		mpi_seti(mpr_num(bigratio), integer);
		mpi_setstr(mpr_den(bigratio), ratio, radix);
	    }
	}
	else {
	    bigratio = LispMalloc(sizeof(mpr));
	    mpr_init(bigratio);
	    mpi_set(mpr_num(bigratio), bignum);
	    mpi_clear(bignum);
	    LispFree(bignum);
	    mpi_setstr(mpr_den(bigratio), ratio, radix);
	}

	if (bigratio) {
	    mpr_canonicalize(bigratio);
	    if (mpi_fiti(mpr_num(bigratio)) &&
		mpi_fiti(mpr_den(bigratio))) {
		integer = mpi_geti(mpr_num(bigratio));
		denominator = mpi_geti(mpr_den(bigratio));
		mpr_clear(bigratio);
		LispFree(bigratio);
		if (denominator == 1)
		    number = INTEGER(integer);
		else
		    number = RATIO(integer, denominator);
	    }
	    else
		number = BIGRATIO(bigratio);
	}
	else {
	    long num = integer, den = denominator, rest;

	    if (num < 0)
		num = -num;
	    for (;;) {
		if ((rest = den % num) == 0)
		    break;
		den = num;
		num = rest;
	    }
	    if (den != 1) {
		denominator /= num;
		integer /= num;
	    }
	    if (denominator < 0) {
		integer = -integer;
		denominator = -denominator;
	    }
	    if (denominator == 1)
		number = INTEGER(integer);
	    else
		number = RATIO(integer, denominator);
	}
    }
    else if (bignum)
	number = BIGNUM(bignum);
    else
	number = INTEGER(integer);

    return (number);
}

static int
StringInRadix(char *str, int radix, int skip_sign)
{
    if (skip_sign && (*str == '-' || *str == '+'))
	++str;
    while (*str) {
	if (*str >= '0' && *str <= '9') {
	    if (*str - '0' >= radix)
		return (0);
	}
	else if (*str >= 'A' && *str <= 'Z') {
	    if (radix <= 10 || *str - 'A' + 10 >= radix)
		return (0);
	}
	else
	    return (0);
	str++;
    }

    return (1);
}

static int
AtomSeparator(int ch, int check_space, int check_backslash)
{
    if (check_space && isspace(ch))
	return (1);
    if (check_backslash && ch == '\\')
	return (1);
    return (strchr("(),\";'`#|,", ch) != NULL);
}

static LispObj *
LispReadVector(read_info *info)
{
    LispObj *objects;
    int nodot = info->nodot;

    info->nodot = info->level + 1;
    objects = LispReadList(info);
    info->nodot = nodot;

    if (info->discard)
	return (objects);

    return (VECTOR(objects));
}

static LispObj *
LispReadFunction(read_info *info)
{
    READ_ENTER();
    int nodot = info->nodot;
    LispObj *function;

    info->nodot = info->level + 1;
    function = LispDoRead(info);
    info->nodot = nodot;

    if (info->discard)
	return (function);

    if (INVALIDP(function))
	READ_ERROR_INVARG();
    else if (CONSP(function)) {
	if (CAR(function) != Olambda)
	    READ_ERROR_INVARG();

	return (FUNCTION_QUOTE(function));
    }
    else if (!SYMBOLP(function))
	READ_ERROR_INVARG();

    return (FUNCTION_QUOTE(function));
}

static LispObj *
LispReadRational(int radix, read_info *info)
{
    READ_ENTER();
    LispObj *number;
    int ch, len, size;
    char stk[128], *str;

    len = 0;
    str = stk;
    size = sizeof(stk);

    for (;;) {
	ch = LispGet();
	if (ch == EOF || isspace(ch))
	    break;
	else if (AtomSeparator(ch, 0, 1)) {
	    LispUnget(ch);
	    break;
	}
	else if (islower(ch))
	    ch = toupper(ch);
	if ((ch < '0' || ch > '9') && (ch < 'A' || ch > 'Z') &&
	    ch != '+' && ch != '-' && ch != '/') {
	    if (str != stk)
		LispFree(str);
	    if (!info->discard)
		READ_ERROR1("bad character %c for rational number", ch);
	}
	if (len + 1 >= size) {
	    if (str == stk) {
		size = 512;
		str = LispMalloc(size);
		strcpy(str + 1, stk + 1);
	    }
	    else {
		size += 512;
		str = LispRealloc(str, size);
	    }
	}
	str[len++] = ch;
    }

    if (info->discard) {
	if (str != stk)
	    LispFree(str);

	return (ch == EOF ? NULL : NIL);
    }

    str[len] = '\0';

    number = LispParseNumber(str, radix, read__stream, read__line);
    if (str != stk)
	LispFree(str);

    if (!RATIONALP(number))
	READ_ERROR0("bad rational number specification");

    return (number);
}

static LispObj *
LispReadCharacter(read_info *info)
{
    READ_ENTER();
    long c;
    int ch, len;
    char stk[64];

    ch = LispGet();
    if (ch == EOF)
	return (NULL);

    stk[0] = ch;
    len = 1;

    for (;;) {
	ch = LispGet();
	if (ch == EOF)
	    break;
	else if (ch != '-' && !isalnum(ch)) {
	    LispUnget(ch);
	    break;
	}
	if (len + 1 < sizeof(stk))
	    stk[len++] = ch;
    }
    if (len > 1) {
	const char * const *names;
	int found = 0;
	stk[len] = '\0';

	for (c = ch = 0; ch <= ' ' && !found; ch++) {
	    for (names = LispChars[ch].names; *names; names++)
		if (strcasecmp(*names, stk) == 0) {
		    c = ch;
		    found = 1;
		    break;
		}
	}
	if (!found) {
	    for (names = LispChars[0177].names; *names; names++)
		if (strcasecmp(*names, stk) == 0) {
		    c = 0177;
		    found = 1;
		    break;
		}
	}

	if (!found) {
	    if (info->discard)
		return (NIL);
	    READ_ERROR1("unkwnown character %s", stk);
	}
    }
    else
	c = stk[0];

    return (SCHAR(c));
}

static void
LispSkipComment(void)
{
    READ_ENTER();
    int ch, comm = 1;

    for (;;) {
	ch = LispGet();
	if (ch == '#') {
	    ch = LispGet();
	    if (ch == '|')
		++comm;
	    continue;
	}
	while (ch == '|') {
	    ch = LispGet();
	    if (ch == '#' && --comm == 0)
		return;
	}
	if (ch == EOF)
	    READ_ERROR_EOF();
    }
}

static LispObj *
LispReadEval(read_info *info)
{
    READ_ENTER();
    int nodot = info->nodot;
    LispObj *code;

    info->nodot = info->level + 1;
    code = LispDoRead(info);
    info->nodot = nodot;

    if (info->discard)
	return (code);

    if (INVALIDP(code))
	READ_ERROR_INVARG();

    return (EVAL(code));
}

static LispObj *
LispReadComplex(read_info *info)
{
    READ_ENTER();
    GC_ENTER();
    int nodot = info->nodot;
    LispObj *number, *arguments;

    info->nodot = info->level + 1;
    arguments = LispDoRead(info);
    info->nodot = nodot;

    /* form read */
    if (info->discard)
	return (arguments);

    if (INVALIDP(arguments) || !CONSP(arguments))
	READ_ERROR_INVARG();

    GC_PROTECT(arguments);
    number = APPLY(Ocomplex, arguments);
    GC_LEAVE();

    return (number);
}

static LispObj *
LispReadPathname(read_info *info)
{
    READ_ENTER();
    GC_ENTER();
    int nodot = info->nodot;
    LispObj *path, *arguments;

    info->nodot = info->level + 1;
    arguments = LispDoRead(info);
    info->nodot = nodot;

    /* form read */
    if (info->discard)
	return (arguments);

    if (INVALIDP(arguments))
	READ_ERROR_INVARG();

    GC_PROTECT(arguments);
    path = APPLY1(Oparse_namestring, arguments);
    GC_LEAVE();

    return (path);
}

static LispObj *
LispReadStruct(read_info *info)
{
    READ_ENTER();
    GC_ENTER();
    int len, nodot = info->nodot;
    char stk[128], *str;
    LispObj *struc, *fields;

    info->nodot = info->level + 1;
    fields = LispDoRead(info);
    info->nodot = nodot;

    /* form read */
    if (info->discard)
	return (fields);

    if (INVALIDP(fields) || !CONSP(fields) || !SYMBOLP(CAR(fields)))
	READ_ERROR_INVARG();

    GC_PROTECT(fields);

    len = ATOMID(CAR(fields))->length;
	   /* MAKE- */
    if (len + 6 > sizeof(stk))
	str = LispMalloc(len + 6);
    else
	str = stk;
    sprintf(str, "MAKE-%s", ATOMID(CAR(fields))->value);
    RPLACA(fields, ATOM(str));
    if (str != stk)
	LispFree(str);
    struc = APPLY(Omake_struct, fields);
    GC_LEAVE();

    return (struc);
}

/* XXX This is broken, needs a rewritten as soon as true vector/arrays be
 * implemented. */
static LispObj *
LispReadArray(long dimensions, read_info *info)
{
    READ_ENTER();
    GC_ENTER();
    long count;
    int nodot = info->nodot;
    LispObj *arguments, *initial, *dim, *cons, *array, *data;

    info->nodot = info->level + 1;
    data = LispDoRead(info);
    info->nodot = nodot;

    /* form read */
    if (info->discard)
	return (data);

    if (INVALIDP(data))
	READ_ERROR_INVARG();

    initial = Kinitial_contents;

    dim = cons = NIL;
    if (dimensions) {
	LispObj *array;

	for (count = 0, array = data; count < dimensions; count++) {
	    long length;
	    LispObj *item;

	    if (!CONSP(array))
		READ_ERROR0("bad array for given dimension");
	    item = array;
	    array = CAR(array);

	    for (length = 0; CONSP(item); item = CDR(item), length++)
		;

	    if (dim == NIL) {
		dim = cons = CONS(FIXNUM(length), NIL);
		GC_PROTECT(dim);
	    }
	    else {
		RPLACD(cons, CONS(FIXNUM(length), NIL));
		cons = CDR(cons);
	    }
	}
    }

    arguments = CONS(dim, CONS(initial, CONS(data, NIL)));
    GC_PROTECT(arguments);
    array = APPLY(Omake_array, arguments);
    GC_LEAVE();

    return (array);
}

static LispObj *
LispReadFeature(int with, read_info *info)
{
    READ_ENTER();
    LispObj *status;
    LispObj *feature = LispDoRead(info);

    /* form read */
    if (info->discard)
	return (feature);

    if (INVALIDP(feature))
	READ_ERROR_INVARG();

    /* paranoia check, features must be a list, possibly empty */
    if (!CONSP(FEATURES) && FEATURES != NIL)
	READ_ERROR1("%s is not a list", STROBJ(FEATURES));

    status = LispEvalFeature(feature);

    if (with) {
	if (status == T)
	    return (LispDoRead(info));

	/* need to use the field discard because the following expression
	 * may be #.FORM or #,FORM or any other form that may generate
	 * side effects */
	info->discard = 1;
	LispDoRead(info);
	info->discard = 0;

	return (LispDoRead(info));
    }

    if (status == NIL)
	return (LispDoRead(info));

    info->discard = 1;
    LispDoRead(info);
    info->discard = 0;

    return (LispDoRead(info));
}

/*
 * A very simple eval loop with AND, NOT, and OR functions for testing
 * the available features.
 */
static LispObj *
LispEvalFeature(LispObj *feature)
{
    READ_ENTER();
    Atom_id test;
    LispObj *object;

    if (CONSP(feature)) {
	LispObj *function = CAR(feature), *arguments = CDR(feature);

	if (!SYMBOLP(function))
	    READ_ERROR1("bad feature test function %s", STROBJ(function));
	if (!CONSP(arguments))
	    READ_ERROR1("bad feature test arguments %s", STROBJ(arguments));
	test = ATOMID(function);
	if (test == Sand) {
	    for (; CONSP(arguments); arguments = CDR(arguments)) {
		if (LispEvalFeature(CAR(arguments)) == NIL)
		    return (NIL);
	    }
	    return (T);
	}
	else if (test == Sor) {
	    for (; CONSP(arguments); arguments = CDR(arguments)) {
		if (LispEvalFeature(CAR(arguments)) == T)
		    return (T);
	    }
	    return (NIL);
	}
	else if (test == Snot) {
	    if (CONSP(CDR(arguments)))
		READ_ERROR0("too many arguments to NOT");

	    return (LispEvalFeature(CAR(arguments)) == NIL ? T : NIL);
	}
	else
	    READ_ERROR1("unimplemented feature test function %s", test);
    }

    if (KEYWORDP(feature))
	feature = feature->data.quote;
    else if (!SYMBOLP(feature))
	READ_ERROR1("bad feature specification %s", STROBJ(feature));

    test = ATOMID(feature);

    for (object = FEATURES; CONSP(object); object = CDR(object)) {
	/* paranoia check, elements in the feature list must ge keywords */
	if (!KEYWORDP(CAR(object)))
	    READ_ERROR1("%s is not a keyword", STROBJ(CAR(object)));
	if (ATOMID(CAR(object)) == test)
	    return (T);
    }

    /* unknown feature */
    return (NIL);
}
