/*****************************************************************************
* Generic parser for the "Irit" solid modeller.				     *
*									     *
* Written by:  Gershon Elber				Ver 0.2, Sep. 1991   *
*****************************************************************************/

#ifdef USE_VARARGS
#include <varargs.h>
#else
#include <stdarg.h>
#endif /* USE_VARARGS */

#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <string.h>
#include <setjmp.h>
#include "irit_sm.h"
#include "iritprsr.h"
#include "allocate.h"
#include "attribut.h"
#include "irit_soc.h"

#define UNGET_STACK_SIZE	5		     /* Internal stack size. */
#define CONVEX_EPSILON		1e-3
#define NORMAL_MIN_VALID_LEN	0.03
#define ZERO_NUM_EPSILON	1e-15

#define MAX_NUM_OPEN_FILES	10	    /* Maximum number of open files. */

typedef enum {			  /* List of all possible tokens enumerated. */
    TOKEN_NONE,

    TOKEN_OPEN_PAREN,
    TOKEN_CLOSE_PAREN,

    TOKEN_E1,
    TOKEN_P1,
    TOKEN_E2,
    TOKEN_P2,
    TOKEN_E3,
    TOKEN_P3,
    TOKEN_E4,
    TOKEN_P4,
    TOKEN_E5,
    TOKEN_P5,

    TOKEN_NUMBER,
    TOKEN_STRING,
    TOKEN_POINT,
    TOKEN_VECTOR,
    TOKEN_MATRIX,
    TOKEN_CTLPT,
    TOKEN_VERTEX,
    TOKEN_POLYGON,
    TOKEN_POLYLINE,
    TOKEN_POINTLIST,
    TOKEN_OBJECT,
    TOKEN_COLOR,
    TOKEN_RGB,
    TOKEN_INTERNAL,
    TOKEN_NORMAL,
    TOKEN_PLANE,
    TOKEN_CURVE,
    TOKEN_SURFACE,

    TOKEN_OTHER	= 100,			/* Probably names & numbers. */
    TOKEN_QUOTED,			/* A quoted string. */

    TOKEN_EOF = -1
} TokenType;

struct OpenedFile {
    FILE *f;
    int IsPipe;
} OpenedFiles[MAX_NUM_OPEN_FILES];

static IritPrsrErrType
    GlblParserError = IP_NO_ERR;			/* Last err # found. */
static int
    GlblFlattenObjects = TRUE,	   /* If input list hierarchy is to be kept. */
    GlblReadOneObject = FALSE,		/* If only one object is to be read. */
    GlblToken =	0,		      /* Used by the parser, to unget token. */
    GlblLineCount = 1;		     /* Used to locate errors in input file. */
static char
    GlblTokenError[LINE_LEN_LONG],	      /* Last token error was found. */
    GlblStringToken[UNGET_STACK_SIZE][LINE_LEN_LONG],	     /* Unget tokens.*/
    GlblUnGetChar = 0,
    *GlblFloatFormat = "%lg";
static IPObjectStruct
    *GlblAllSrfs = NULL,
    *GlblAllCrvs = NULL,
    *GlblAllObjs = NULL;
static FILE
    *IritPrsrOutputFile = NULL;
static IritPrsrPrintFuncType
    IritPrsrPrintFunc = NULL;

jmp_buf _IritPrsrLongJumpBuffer;		   /* Used in error traping. */
int _IritPrsrPolyListCirc = FALSE,
    _IritPrsrReadSocket = FALSE,	      /* If TRUE - read from socket. */
    _IritPrsrWriteSocket = FALSE,	       /* If TRUE - write to socket. */
    _IritPrsrReadWriteBinary = FALSE, /* If TRUE - read/write data directly. */
    IritPrsrWasViewMat = FALSE,
    IritPrsrWasPrspMat = FALSE;
MatrixType IritPrsrViewMat = {		      /* Isometric view, by default. */
    { -0.707107, -0.408248, 0.577350, 0.000000 },
    {  0.707107, -0.408248, 0.577350, 0.000000 },
    {  0.000000,  0.816496, 0.577350, 0.000000 },
    {  0.000000,  0.000000, 0.000000, 1.000000 }
};
MatrixType IritPrsrPrspMat = {
    { 1, 0, 0, 0 },
    { 0, 1, 0, 0 },
    { 0, 0, 1, -0.35 },
    { 0, 0, 0, 1.0 }
};

static void UnGetToken(char *StringToken);
static int InputGetC(FILE *f);
static int InputEOF(FILE *f);
static int GetStringToken(FILE *f, char *StringToken, int *Quoted);
static TokenType GetToken(FILE *f, char *StringToken);
static void GetVertexAttributes(IPVertexStruct *PVertex, FILE *f);
static void GetPolygonAttributes(IPPolygonStruct *PPolygon, FILE *f);
static void GetObjectAttributes(IPObjectStruct *PObject, FILE *f);
static void GetGenericAttribute(IPAttributeStruct **Attrs, FILE *f, char *Name);
static void GetPointData(FILE *f, IPPolygonStruct *PPolygon, int IsPolygon);

static void FlattenTree(IPObjectStruct *PObj);
static void IritPrsrGetAllObjects(FILE *f, IPObjectStruct *PObjParent,
								int Level);
static void GetCloseParenToken(FILE *f);
static void SkipToCloseParenToken(FILE *f);
static void GetNumericToken(FILE *f, RealType *r);
static void IritPrsrGetAuxObject(FILE *f, IPObjectStruct *PObj);

static void IritPrsrPutAllObjects(IPObjectStruct *PObj, int Indent);
static char *Real2Str(RealType R);

#ifdef USE_VARARGS
static void IFprintf(int Indent, char *va_alist, ...);
#else
static void IFprintf(int Indent, char *Format, ...);
#endif /* USE_VARARGS */

/*****************************************************************************
* Open a data file for read/write.					     *
*****************************************************************************/
FILE *IritPrsrOpenDataFile(char *FileName, int Read, int Messages)
{
    FILE *f;
    int i,
	IsPipe = FALSE;
    char *p;

    if (strstr(FileName, ".bdt") || strstr(FileName, ".BDT"))
	_IritPrsrReadWriteBinary = TRUE;

    if (Read) {
	if (strcmp(FileName, "-") == 0) {
	    f = stdin;
	}
#if defined(__UNIX__) || defined(OS2GCC)
	else if ((p = strrchr(FileName, '.')) != NULL &&
		 strcmp(p, ".Z") == 0) {
	    char Cmd[LINE_LEN];

	    sprintf(Cmd, "zcat %s", FileName);
	    f = popen(Cmd, "r");
	    IsPipe = TRUE;
	}
#endif /* __UNIX__ || OS2GCC */
	else {
	    if ((f = fopen(FileName, "r")) == NULL) {
		if (Messages)
		    fprintf(stderr, "Can't open data file %s.\n", FileName);
		return NULL;
	    }
	}
    }
    else { /* Write */
	if (strcmp(FileName, "-") == 0) {
	    f = stdout;
	}
#if defined(__UNIX__) || defined(OS2GCC)
	else if ((p = strrchr(FileName, '.')) != NULL &&
		 strcmp(p, ".Z") == 0) {
	    char Cmd[LINE_LEN];

	    sprintf(Cmd, "compress > %s", FileName);
	    f = popen(Cmd, "w");
	    IsPipe = TRUE;
	}
#endif /* __UNIX__ || OS2GCC */
	else {
	    if ((f = fopen(FileName, "w")) == NULL) {
		if (Messages)
		    fprintf(stderr, "Can't open data file %s.\n", FileName);
		return NULL;
	    }
	}
    }

#if defined(__OS2GCC__) || defined(__WINNT__)
    if (_IritPrsrReadWriteBinary)
	setmode(FileHandle, O_BINARY);    /* Make sure it is in binary mode. */
#endif /* __OS2GCC__ || __WINNT__ */

    for (i = 0; i < MAX_NUM_OPEN_FILES; i++)
	if (OpenedFiles[i].f == NULL) {
	    OpenedFiles[i].f = f;
	    OpenedFiles[i].IsPipe = IsPipe;
	    break;
	}
    if (Messages && i >= MAX_NUM_OPEN_FILES)
	fprintf(stderr, "Opened file table is full.\n");

    return f;
}

/*****************************************************************************
* Close a data file for read/write.					     *
*****************************************************************************/
void IritPrsrCloseDataFile(FILE *f)
{
#ifdef __UNIX__
    int i,
	IsPipe = FALSE;

    for (i = 0; i < MAX_NUM_OPEN_FILES; i++)
	if (OpenedFiles[i].f == f) {
	    IsPipe = OpenedFiles[i].IsPipe;
	    OpenedFiles[i].f = NULL;
	    break;
	}
  
    if (IsPipe)
	pclose(f);
    else
#endif /* __UNIX__ */
	fclose(f);

    _IritPrsrReadWriteBinary = FALSE;
}

/*****************************************************************************
* Read data from an set of files specified by file names.		     *
* Messages and MoreMessages controls the level of printout to stderr.        *
*****************************************************************************/
IPObjectStruct *IritPrsrGetDataFiles(char **DataFileNames, int NumOfDataFiles,
				     int Messages, int MoreMessages)
{
    int	i;
    char *ErrorMsg;
    FILE *f;
    IPObjectStruct *PObj, *PObjTail,
	*PObjHead = NULL;

    for	(i = 0; i < NumOfDataFiles; i++) {
	if (MoreMessages)
	    fprintf(stderr, "Reading data file %s\n", *DataFileNames);

	if ((f = IritPrsrOpenDataFile(*DataFileNames, TRUE, Messages)) == NULL)
	    continue;

	if ((PObj = IritPrsrGetObjects(f)) != NULL) {  /* Get the data file. */
	    PObjTail = PObj;
	    while (PObjTail -> Pnext)
		PObjTail = PObjTail -> Pnext;
	    PObjTail -> Pnext = PObjHead;
	    PObjHead = PObj;
	}

	if (MoreMessages && IritPrsrParseError(&ErrorMsg))
	    fprintf(stderr, "File %s, %s\n", *DataFileNames, ErrorMsg);

	IritPrsrCloseDataFile(f);

	DataFileNames++;			  /* Skip to next file name. */
    }

    if (PObjHead == NULL) {
	if (Messages)
	    fprintf(stderr, "No data found.\n");
	return NULL;
    }

    return PObjHead;
}

/*****************************************************************************
* Routine to read the data from	a given	file.				     *
*   Returns NULL if EOF was reached or error occured.			     *
*****************************************************************************/
IPObjectStruct *IritPrsrGetObjects(FILE *f)
{
    IPObjectStruct *PObj;

    /* If the following gain control and is non zero - its from error! */
    if (setjmp(_IritPrsrLongJumpBuffer) != 0)
	return NULL;

    if (_IritPrsrReadWriteBinary)
        PObj = IritPrsrGetBinObject(f);
    else {
	PObj = IPAllocObject("", IP_OBJ_UNDEF, NULL);
	GlblToken = 0;			 /* Used in UnGetToken token buffer. */
	GlblParserError = IP_NO_ERR;			    /* Reset errors. */
	GlblLineCount = 1;			      /* Reset line counter. */

	IritPrsrGetAllObjects(f, PObj, 0);
	if (IP_IS_UNDEF_OBJ(PObj)) {
	    IPFreeObject(PObj);
	    return NULL;
	}
    }

    return IritPrsrProcessReadObject(PObj);
}

/*****************************************************************************
* Process a read object, before returning it to the caller.		     *
*****************************************************************************/
IPObjectStruct *IritPrsrProcessReadObject(IPObjectStruct *PObj)
{
    if (IP_IS_OLST_OBJ(PObj)) {
        if (ListObjectGet(PObj, 0) == NULL) {
	    /* Nothing read in. */
	    IPFreeObject(PObj);
	    PObj = NULL;
	    IritPrsrParserAbort(IP_ERR_FILE_EMPTY, "");
	}
	else if (ListObjectGet(PObj, 1) == NULL) {
	    IPObjectStruct
		*PTmp = ListObjectGet(PObj, 0);

	    /* Only one object in list - return the object instead. */
	    ListObjectInsert(PObj, 0, NULL);
	    IPFreeObject(PObj);
	    PObj = PTmp;
	}
    }

    IritPrsrPropagateAttrs(PObj, NULL);

    if (GlblFlattenObjects) {
	GlblAllSrfs = NULL;
	GlblAllCrvs = NULL;
	GlblAllObjs = NULL;

	FlattenTree(PObj);

	if (GlblAllCrvs != NULL || GlblAllSrfs != NULL) {
	    IPObjectStruct *PTmp, *PObjs;

	    if ((PObjs = IritPrsrProcessFreeForm(GlblAllCrvs,
						 GlblAllSrfs)) != NULL) {
		for (PTmp = PObjs;
		     PTmp -> Pnext != NULL;
		     PTmp = PTmp -> Pnext) {
		    if (AttrGetObjectColor(PTmp) == IP_ATTR_NO_COLOR)
		        AttrSetObjectColor(PTmp, IP_LOAD_COLOR);
		}
		PTmp -> Pnext = GlblAllObjs;
		GlblAllObjs = PObjs;
	    }
	}

	PObj = IritPrsrReverseObjList(GlblAllObjs);
    }

    return PObj;
}

/*****************************************************************************
* Control the flattening of an object.					     *
*****************************************************************************/
void IritPrsrSetPolyListCirc(int Circ)
{
    _IritPrsrPolyListCirc = Circ;
}

/*****************************************************************************
* Control the flattening of an object.					     *
*****************************************************************************/
void IritPrsrSetFlattenObjects(int Flatten)
{
    GlblFlattenObjects = Flatten;
}

/*****************************************************************************
* Control the flattening of an object.					     *
*****************************************************************************/
void IritPrsrSetReadOneObject(int OneObject)
{
    GlblReadOneObject = OneObject;
}

/*****************************************************************************
* Propagate attributes from list objects down into their items.		     *
*****************************************************************************/
void IritPrsrPropagateAttrs(IPObjectStruct *PObj, IPAttributeStruct *Attrs)
{
    IPAttributeStruct *Attr;

    if (IP_IS_OLST_OBJ(PObj)) {
	int i;
	IPObjectStruct *PTmp;

	/* Collect all attributes of this list (including inherited ones)    */
	/* and propagate them down to the list items.			     */
	if (Attrs != NULL)
	    Attrs = AttrCopyAttributes(Attrs);

	for (Attr = PObj -> Attrs; Attr != NULL; Attr = Attr -> Pnext) {
	    if (!AttrFindAttribute(Attrs, Attr -> Name)) {
		IPAttributeStruct
		    *TmpAttr = AttrCopyOneAttribute(Attr);

		TmpAttr -> Pnext = Attrs;
		Attrs = TmpAttr;
	    }
	}

	for (i = 0; (PTmp = ListObjectGet(PObj, i)) != NULL; i++)
	    IritPrsrPropagateAttrs(PTmp, Attrs);

	AttrFreeAttributes(&Attrs);
    }
    else {
	/* Regular object - add to its attribute list every attribute in     */
	/* Attrs that is not found in its attribute list.		     */
	for (Attr = Attrs; Attr != NULL; Attr = Attr -> Pnext) {
	    if (!AttrFindAttribute(PObj -> Attrs, Attr -> Name)) {
		IPAttributeStruct
		    *TmpAttr = AttrCopyOneAttribute(Attr);

		TmpAttr -> Pnext = PObj -> Attrs;
		PObj -> Attrs = TmpAttr;
	    }
	}
    }
}

/*****************************************************************************
* Flatten a tree hierarchy of objects and push them onto the global list for *
* curves surfaces and other objects.					     *
*****************************************************************************/
static void FlattenTree(IPObjectStruct *PObj)
{
    int i;
    IPObjectStruct *PTmp;

    if (IP_IS_OLST_OBJ(PObj)) {
	for (i = 0; (PTmp = ListObjectGet(PObj, i)) != NULL; i++)
	    FlattenTree(PTmp);

	ListObjectInsert(PObj, 0, NULL);
	IPFreeObject(PObj);
    }
    else if (IP_IS_CRV_OBJ(PObj)) {
	PObj -> Pnext = GlblAllCrvs;
	GlblAllCrvs = PObj;
    }
    else if (IP_IS_SRF_OBJ(PObj)) {
	PObj -> Pnext = GlblAllSrfs;
	GlblAllSrfs = PObj;
    }
    else {
	PObj -> Pnext = GlblAllObjs;
	GlblAllObjs = PObj;
    }
}

/*****************************************************************************
* Routine to read the geometry data from a given file. Reads "[OBJECT ..."   *
* prefixes only and invoke the auxiliary routine.			     *
* Note objects may be recursively defined.				     *
*****************************************************************************/
static void IritPrsrGetAllObjects(FILE *f, IPObjectStruct *PObjParent,
								int Level)
{
    char StringToken[LINE_LEN_LONG];
    TokenType Token;
    int	i,
	WasObjectToken = FALSE,
	ObjCount = 0,
	Quit = FALSE;
    IPObjectStruct *PObj;

    while (!Quit) {
    	while ((Token = GetToken(f, StringToken)) != TOKEN_OPEN_PAREN &&
	       Token != TOKEN_CLOSE_PAREN &&
	       Token != TOKEN_EOF);

	if (Token == TOKEN_CLOSE_PAREN || Token == TOKEN_EOF) {
	    if (Token == TOKEN_CLOSE_PAREN)
		UnGetToken(StringToken);
	    Quit = TRUE;
	    break;
	}

	switch (GetToken(f, StringToken)) {
	    case TOKEN_OBJECT:
		WasObjectToken = TRUE;

	        ReallocNewTypeObject(PObjParent, IP_OBJ_LIST_OBJ);
		PObj = IPAllocObject("", IP_OBJ_UNDEF, NULL);

		/* The following handle optional attributes in record. */
		if (GetToken(f, StringToken) == TOKEN_OPEN_PAREN)
		    GetObjectAttributes(PObj, f);
		else {
		    UnGetToken(StringToken);
		}

		if (AttrGetObjectColor(PObj) == IP_ATTR_NO_COLOR)
		    AttrSetObjectColor(PObj, IP_LOAD_COLOR);

		if (GetToken(f, StringToken) == TOKEN_OTHER &&
		    stricmp(StringToken, "NONE") != 0) {
		    for (i = 0; i < strlen(StringToken); i++)
		        PObj -> Name[i] =
			    islower(StringToken[i]) ? toupper(StringToken[i])
						    : StringToken[i];
		    PObj -> Name[i] = 0;
		}

		IritPrsrGetAllObjects(f, PObj, Level + 1);

		GetCloseParenToken(f);

		if (IP_IS_UNDEF_OBJ(PObj))
		    IritPrsrParserAbort(IP_ERR_OBJECT_EMPTY, "");

		ListObjectInsert(PObjParent, ObjCount++, PObj);
		break;
	    default:
		if (WasObjectToken) {
		    IritPrsrParserAbort(IP_ERR_OBJECT_EXPECTED, StringToken);
		}
		UnGetToken(StringToken);
		UnGetToken("[");
		IritPrsrGetAuxObject(f, PObjParent);
		Quit = TRUE;
		break;
	}

	if (Level == 0 && WasObjectToken && GlblReadOneObject)
	    Quit = TRUE;
    }

    if (IP_IS_OLST_OBJ(PObjParent)) {
	ListObjectInsert(PObjParent, ObjCount++, NULL);
    }
}

/*****************************************************************************
* Routine to get close paren token from f.				     *
*****************************************************************************/
static void GetCloseParenToken(FILE *f)
{
    char StringToken[LINE_LEN_LONG];

    if (GetToken(f, StringToken) != TOKEN_CLOSE_PAREN)
	IritPrsrParserAbort(IP_ERR_CLOSE_PAREN_EXPECTED, StringToken);
}

/*****************************************************************************
* Routine to get close paren token from f.				     *
*****************************************************************************/
static void SkipToCloseParenToken(FILE *f)
{
    char StringToken[LINE_LEN_LONG];

    while (!InputEOF(f) && GetToken(f, StringToken) != TOKEN_CLOSE_PAREN);
}

/*****************************************************************************
* Routine to get one numeric token into r.				     *
*****************************************************************************/
static void GetNumericToken(FILE *f, RealType *r)
{
    char StringToken[LINE_LEN_LONG];

    GetToken(f, StringToken);
#   ifdef DOUBLE
	if (sscanf(StringToken, "%lf", r) != 1)
#   else
	if (sscanf(StringToken, "%f", r) != 1)
#   endif /* DOUBLE */
	    IritPrsrParserAbort(IP_ERR_NUMBER_EXPECTED, StringToken);
}

/*****************************************************************************
* Routine to read the content of a single object. Return TRUE if data is     *
* useful for this parser, FALSE if data should be purged.		     *
*****************************************************************************/
static void IritPrsrGetAuxObject(FILE *f, IPObjectStruct *PObj)
{
    int	i, j, ErrLine;
    TokenType
	Token = TOKEN_NONE;
    char *ErrStr, StringToken[LINE_LEN_LONG];
    CagdRType *Coords;
    IPPolygonStruct *PPolygon;
    CagdCrvStruct *PCurve;
    CagdSrfStruct *PSurface;

    ReallocNewTypeObject(PObj, IP_OBJ_UNDEF);

    while (GetToken(f, StringToken) == TOKEN_OPEN_PAREN) {
	switch (Token = GetToken(f, StringToken)) {
	    case TOKEN_POLYGON:
	    case TOKEN_POLYLINE:
	    case TOKEN_POINTLIST:
		ReallocNewTypeObject(PObj, IP_OBJ_POLY);
		PPolygon = IPAllocPolygon(0, 0, NULL, NULL);
		switch (Token) {
		    case TOKEN_POLYGON:
			IP_SET_POLYGON_OBJ(PObj);
			break;
		    case TOKEN_POLYLINE:
			IP_SET_POLYLINE_OBJ(PObj);
			break;
		    case TOKEN_POINTLIST:
			IP_SET_POINTLIST_OBJ(PObj);
			break;
		    default:
			IritPrsrParserAbort(IP_ERR_UNDEF_EXPR_HEADER,
					    StringToken);
			break;
		}

		/* The following handle the optional attributes in struct.   */
		if (GetToken(f, StringToken) == TOKEN_OPEN_PAREN)
		    GetPolygonAttributes(PPolygon, f);
		else
		    UnGetToken(StringToken);

		/* The following handles reading the vertices. */
		GetPointData(f, PPolygon, IP_IS_POLYGON_OBJ(PObj));

		if (IP_IS_POLYGON_OBJ(PObj)) {
		    if (!IP_HAS_PLANE_POLY(PPolygon))
			IritPrsrUpdatePolyPlane(PPolygon);

		    IritPrsrUpdateVrtxNrml(PPolygon, PPolygon -> Plane);
		}

		PPolygon -> Pnext = PObj -> U.Pl;
		PObj -> U.Pl = PPolygon;
		break;
	    case TOKEN_SURFACE:
		ReallocNewTypeObject(PObj, IP_OBJ_SURFACE);
		ErrLine = GlblLineCount;
		PSurface = CagdSrfReadFromFile2(f, &ErrStr, &ErrLine);
		GlblLineCount = ErrLine;

		if (ErrStr != NULL) {
		    IritPrsrParserAbort(IP_ERR_CAGD_LIB_ERR, ErrStr);
		    break;
		}

		if (PSurface != NULL) {
		    PSurface -> Pnext = PObj -> U.Srfs;
		    PObj -> U.Srfs = PSurface;
		}
		break;
	    case TOKEN_CURVE:
		ReallocNewTypeObject(PObj, IP_OBJ_CURVE);
		ErrLine = GlblLineCount;
		PCurve = CagdCrvReadFromFile2(f, &ErrStr, &ErrLine);
		GlblLineCount = ErrLine;

		if (ErrStr != NULL) {
		    IritPrsrParserAbort(IP_ERR_CAGD_LIB_ERR, ErrStr);
		    break;
		}

		if (PCurve != NULL) {
		    PCurve -> Pnext = PObj -> U.Crvs;
		    PObj -> U.Crvs = PCurve;
		}
		break;
	    case TOKEN_NUMBER:
		ReallocNewTypeObject(PObj, IP_OBJ_NUMERIC);
		GetNumericToken(f, &PObj -> U.R);
		GetCloseParenToken(f);
		break;
	    case TOKEN_STRING:
		ReallocNewTypeObject(PObj, IP_OBJ_STRING);
		GetToken(f, PObj -> U.Str);
		GetCloseParenToken(f);
		break;
	    case TOKEN_POINT:
		ReallocNewTypeObject(PObj, IP_OBJ_POINT);
		for (i = 0; i < 3; i++)
		    GetNumericToken(f, &PObj -> U.Pt[i]);
		GetCloseParenToken(f);
		break;
	    case TOKEN_VECTOR:
		ReallocNewTypeObject(PObj, IP_OBJ_VECTOR);
		for (i = 0; i < 3; i++)
		    GetNumericToken(f, &PObj -> U.Vec[i]);
		GetCloseParenToken(f);
		break;
	    case TOKEN_PLANE:
		ReallocNewTypeObject(PObj, IP_OBJ_PLANE);
		for (i = 0; i < 4; i++)
		    GetNumericToken(f, &PObj -> U.Plane[i]);
		GetCloseParenToken(f);
		break;
	    case TOKEN_MATRIX:
		ReallocNewTypeObject(PObj, IP_OBJ_MATRIX);
		for (i = 0; i < 4; i++)
		    for (j = 0; j < 4; j++)
			GetNumericToken(f, &(*PObj -> U.Mat)[i][j]);
		GetCloseParenToken(f);

		if (stricmp(PObj -> Name, "VIEW_MAT") == 0) {
		    IritPrsrWasViewMat = TRUE;
		    MAT_COPY(IritPrsrViewMat, PObj -> U.Mat);
		}
		else if (stricmp(PObj -> Name, "PRSP_MAT") == 0) {
		    IritPrsrWasPrspMat = TRUE;
		    MAT_COPY(IritPrsrPrspMat, PObj -> U.Mat);
		}
		break;
	    case TOKEN_CTLPT:
		ReallocNewTypeObject(PObj, IP_OBJ_CTLPT);
		GetToken(f, StringToken);

		i = atoi(&StringToken[1]);
		if ((StringToken[0] == 'P' || StringToken[0] == 'E' ) &&
		    i > 0 && i < 6) {
		   j = StringToken[0] == 'E';
		   PObj -> U.CtlPt.PtType = CAGD_MAKE_PT_TYPE(!j, i);
		}
		else {
		    IritPrsrParserAbort(IP_ERR_PT_TYPE_EXPECTED, StringToken);
		    i = j = 0;
		    break;
		}

		Coords = PObj -> U.CtlPt.Coords;
		for ( i += 1 - j; i > 0; i--)
		    GetNumericToken(f, &Coords[j++]);
		GetCloseParenToken(f);
		break;
	    default:
		IritPrsrParserAbort(IP_ERR_UNDEF_EXPR_HEADER, StringToken);
		break;
	} /* Of switch. */
    } /* Of while. */

    UnGetToken(StringToken);
}

/*****************************************************************************
*   Routine to unget one token (on stack of UNGET_STACK_SIZE levels!)	     *
*****************************************************************************/
static void UnGetToken(char *StringToken)
{
    if (GlblToken >= UNGET_STACK_SIZE)
	 IritPrsrParserAbort(IP_ERR_STACK_OVERFLOW, "");

    strcpy(GlblStringToken[GlblToken], StringToken);
    GlblToken++;  /* GlblToken exists - Something in it (no overflow check). */
}

/*****************************************************************************
*   Routine to unget a single character from input stream.		     *
*****************************************************************************/
void IritPrsrInputUnGetC(char c)
{
    GlblUnGetChar = c;
}

/*****************************************************************************
*   Routine to get a single character from input stream.		     *
* If input returns EOF wait until new inputs arrive (can happen if reading   *
* from a non io blocked socket).					     *
*****************************************************************************/
static int InputGetC(FILE *f)
{
    int c;

    if (GlblUnGetChar) {
	c = GlblUnGetChar;

	GlblUnGetChar = 0;
    }
    else if (_IritPrsrReadSocket) {
	while ((c = SocClientReadCharNonBlock()) == EOF)
	    IritSleep(10);
    }
    else
	c = getc(f);

    return c;
}

/*****************************************************************************
*   Routine to test for EOF condition in input stream.			     *
*****************************************************************************/
static int InputEOF(FILE *f)
{
    if (_IritPrsrReadSocket)
	return FALSE;
    else
	return feof(f);
}

/*****************************************************************************
*   Routine to request the parser to read from a socket instead of a file.   *
*****************************************************************************/
void IritPrsrReadSocket(int ReadSocket)
{
    _IritPrsrReadSocket = ReadSocket;
    CagdReadSocket(ReadSocket);
}

/*****************************************************************************
*   Routine to request the parser to read from a socket instead of a file.   *
*****************************************************************************/
void IritPrsrWriteSocket(int WriteSocket)
{
    _IritPrsrWriteSocket = WriteSocket;
    CagdWriteSocket(WriteSocket);
}

/*****************************************************************************
*   Routine to get the next token out of the input file	f.		     *
* Returns TRUE if !InputEOF and the next token found in StringToken.	     *
* Note:	StringToken must be allocated before calling this routine!	     *
*****************************************************************************/
static int GetStringToken(FILE *f, char *StringToken, int *Quoted)
{
    int	len;
    char *LocalStringToken,
	c = EOF;

    *Quoted = FALSE;

    if (GlblToken) { /*	get first the unget token */
	GlblToken--;
	strcpy(StringToken, GlblStringToken[GlblToken]);
	return TRUE;
    }
    /* skip white spaces: */
    while ((!InputEOF(f)) &&
	   (((c = InputGetC(f)) == ' ') || (c == '\t') || (c == '\n')) &&
	   (c != (char) EOF))
	if (c == '\n')
	    GlblLineCount++;				 /* Count the lines. */

    LocalStringToken = StringToken;
    if (c == '[')		      /* Its a token by	itself so return it. */
	*LocalStringToken++ = c;	      /* Copy the token	into string. */
    else {
	if (!InputEOF(f) && (c != (char) EOF)) {
	    if (c == '"') {
		*Quoted = TRUE;
		c = InputGetC(f);
		do {
		    *LocalStringToken++ = c;      /* Copy the quoted string. */
		    if (c == '\\') {
			/* Next character is quoted - copy verbatim. */
			*--LocalStringToken = c = InputGetC(f);
			LocalStringToken++;
		    }
		}
		while ((!InputEOF(f)) &&
		       ((c = InputGetC(f)) != '"') &&
		       (c != '\n') &&
		       (c != (char) EOF));
	    }
	    else {
		do
		    *LocalStringToken++ = c;  /* Copy the token into string. */
		while ((!InputEOF(f)) &&
		       ((c = InputGetC(f)) != ' ') &&
		       (c != '\t') &&
		       (c != '\n') &&
		       (c != (char) EOF));
	    }
	    if (!InputEOF(f) && c == '\n')
	        IritPrsrInputUnGetC(c);	 /* Save it to be counted next time. */
	}
    }
    *LocalStringToken =	0;					 /* Put	eos. */

    /* The following handles the spacial case were we have XXXX] - we must   */
    /* split it	into two token XXXX and	], UnGetToken(']') and return XXXX:  */
    if ((StringToken[len = strlen(StringToken) - 1] == ']') && (len > 0)) {
	/* Return CloseParan */
	UnGetToken(&StringToken[len]);			 /* Save next token. */
	StringToken[len] = 0;			/* Set end of string on	"]". */
    }

    return !InputEOF(f) && (c != (char) EOF);
}

/*****************************************************************************
*   Routine to get the next token out of the input file	f as token number.   *
* Note:	StringToken must be allocated before calling this routine!	     *
*****************************************************************************/
static TokenType GetToken(FILE *f, char *StringToken)
{
    static int IntTokens[] = {
	TOKEN_OPEN_PAREN,
	TOKEN_CLOSE_PAREN,
	TOKEN_VERTEX,
	TOKEN_POLYGON,
	TOKEN_POLYLINE,
	TOKEN_POINTLIST,
	TOKEN_OBJECT,
	TOKEN_COLOR,
	TOKEN_RGB,
	TOKEN_INTERNAL,
	TOKEN_NORMAL,
	TOKEN_PLANE,
	TOKEN_CURVE,
	TOKEN_SURFACE,
	TOKEN_E1,
	TOKEN_P1,
	TOKEN_E2,
	TOKEN_P2,
	TOKEN_E3,
	TOKEN_P3,
	TOKEN_E4,
	TOKEN_P4,
	TOKEN_E5,
	TOKEN_P5,
	TOKEN_NUMBER,
	TOKEN_STRING,
	TOKEN_VECTOR,
	TOKEN_POINT,
	TOKEN_MATRIX,
	TOKEN_CTLPT,
	0
    };
    static char *StrTokens[] = {
	"[",
	"]",
	"VERTEX",
	"POLYGON",
	"POLYLINE",
	"POINTLIST",
	"OBJECT",
	"COLOR",
	"RGB",
	"INTERNAL",
	"NORMAL",
	"PLANE",
	"CURVE",
	"SURFACE",
	"E1",
	"P1",
	"E2",
	"P2",
	"E3",
	"P3",
	"E4",
	"P4",
	"E5",
	"P5",
	"NUMBER",
	"STRING",
	"VECTOR",
	"POINT",
	"MATRIX",
	"CTLPT",
	NULL
    };
    int i, Quoted;

    if (!GetStringToken(f, StringToken, &Quoted))
	return TOKEN_EOF;

    if (Quoted)
	return TOKEN_QUOTED;

    for (i = 0; StrTokens[i] != NULL; i++)
	if (stricmp(StringToken, StrTokens[i]) == 0)
	    return IntTokens[i];

    return TOKEN_OTHER;				  /* Must be number or name. */
}

/*****************************************************************************
* Routine to read from input file f the	following [ATTR ...] [ATTR ...].     *
* Note the '[' was allready read.					     *
*****************************************************************************/
static void GetVertexAttributes(IPVertexStruct *PVertex, FILE *f)
{
    int i;
    RealType Len;
    char StringToken[LINE_LEN_LONG];

    do {
	switch (GetToken(f, StringToken)) {
	    case TOKEN_INTERNAL:
		GetCloseParenToken(f);
		IP_SET_INTERNAL_VRTX(PVertex);
		break;
	    case TOKEN_NORMAL:
		/* The following handles reading 3 coord. of vertex normal. */
		for (i = 0; i < 3; i++)
		    GetNumericToken(f, &PVertex -> Normal[i]);

		/* Make sure it is normalized. */
		Len = PT_LENGTH(PVertex -> Normal);
		if (Len > 0) {
		    for (i = 0; i < 3; i++)
			PVertex -> Normal[i] /= Len;
		    IP_SET_NORMAL_VRTX(PVertex);
		}
		GetCloseParenToken(f);
		break;
	    default:
		GetGenericAttribute(&PVertex -> Attrs, f, StringToken);
		break;
	}
    }
    while (GetToken(f, StringToken) == TOKEN_OPEN_PAREN);

    UnGetToken(StringToken);
}

/*****************************************************************************
* Routine to read from input file f the	following [ATTR ...] [ATTR ...].     *
* Note the '[' was allready read.					     *
*****************************************************************************/
static void GetPolygonAttributes(IPPolygonStruct *PPolygon, FILE *f)
{
    int i;
    RealType Len;
    char StringToken[LINE_LEN_LONG];

    do {
	switch (GetToken(f, StringToken)) {
	    case TOKEN_PLANE:
		/* The following handles reading of 4 coord. of plane eqn.. */
		for (i = 0; i < 4; i++)
		    GetNumericToken(f, &PPolygon -> Plane[i]);

		/* Make sure it is normalized. */
		Len = PT_LENGTH(PPolygon -> Plane);
		if (Len > 0)
		    for (i = 0; i < 4; i++)
			PPolygon -> Plane[i] /= Len;
		else
		    IritPrsrParserAbort(IP_ERR_DEGEN_NORMAL, "");

		GetCloseParenToken(f);
		IP_SET_PLANE_POLY(PPolygon);
		break;
	    default:
		GetGenericAttribute(&PPolygon -> Attrs, f, StringToken);
		break;
	}
    }
    while (GetToken(f, StringToken) == TOKEN_OPEN_PAREN);

    UnGetToken(StringToken);
}

/*****************************************************************************
* Routine to read from input file f the	following [ATTR ...] [ATTR ...].     *
* Note the '[' was allready read.					     *
*****************************************************************************/
static void GetObjectAttributes(IPObjectStruct *PObject, FILE *f)
{
    int	i;
    char StringToken[LINE_LEN_LONG];

    do {
	switch (GetToken(f, StringToken)) {
	    case TOKEN_COLOR:
		GetToken(f, StringToken);
		if (sscanf(StringToken, "%d", &i) != 1)
		    IritPrsrParserAbort(IP_ERR_NUMBER_EXPECTED, StringToken);
		GetCloseParenToken(f);
		AttrSetObjectColor(PObject, i);
		break;
	    default:
		GetGenericAttribute(&PObject -> Attrs, f, StringToken);
		break;
	}
    }
    while (GetToken(f, StringToken) == TOKEN_OPEN_PAREN);

    if (AttrGetObjectColor(PObject) == IP_ATTR_NO_COLOR)
    	AttrSetObjectColor(PObject, IP_LOAD_COLOR);

    UnGetToken(StringToken);
}

/*****************************************************************************
* Routine to read one generic attribute.				     *
*****************************************************************************/
static void GetGenericAttribute(IPAttributeStruct **Attrs, FILE *f, char *Name)
{
    int Token;
    char StringToken[LINE_LEN_LONG];

    if ((Token = GetToken(f, StringToken)) == TOKEN_CLOSE_PAREN) {
	AttrSetStrAttrib(Attrs, Name, "");
    }
    else if (Token == TOKEN_QUOTED) {
	AttrSetStrAttrib(Attrs, Name, StringToken);

	SkipToCloseParenToken(f);
    }
    else {
	int i;
	double d;

	for (i = strlen(StringToken) - 1; i >= 0; i--) {
	    if (!(isdigit(StringToken[i]) ||
		  StringToken[i] == 'e' ||
		  StringToken[i] == 'E' ||
		  StringToken[i] == '.' ||
		  StringToken[i] == '+' ||
		  StringToken[i] == '-'))
		break;
	}
	if (i < 0 && sscanf(StringToken, "%lf", &d) == 1) {
	    if (d == (int) d)
		AttrSetIntAttrib(Attrs, Name, (int) d);
	    else
		AttrSetRealAttrib(Attrs, Name, d);
	}
	else
	    AttrSetStrAttrib(Attrs, Name, StringToken);

	SkipToCloseParenToken(f);
    }
}

/*****************************************************************************
* Routine to read poly* vertex information.				     *
*****************************************************************************/
static void GetPointData(FILE *f, IPPolygonStruct *PPolygon, int IsPolygon)
{
    int i, j, Length;
    char StringToken[LINE_LEN_LONG];
    IPVertexStruct *V,
	*VTail = NULL;

    if (GetToken(f, StringToken) != TOKEN_OTHER ||
	sscanf(StringToken, "%d", &Length) != 1)
	IritPrsrParserAbort(IP_ERR_NUMBER_EXPECTED, StringToken);

    for (i = 0; i < Length; i++) {
	if (GetToken(f, StringToken) != TOKEN_OPEN_PAREN)
	    IritPrsrParserAbort(IP_ERR_OPEN_PAREN_EXPECTED, StringToken);

	V = IPAllocVertex(0, 0, NULL, NULL);

	/* The following handle the optional attributes in struct. */
	if (GetToken(f, StringToken) == TOKEN_OPEN_PAREN)
	    GetVertexAttributes(V, f);
	else
	    UnGetToken(StringToken);

	for (j = 0; j < 3; j++)				/* Read coordinates. */
	    GetNumericToken(f, &V -> Coord[j]);

	GetCloseParenToken(f);

	if (VTail == NULL)
	    PPolygon -> PVertex = VTail = V;
	else {
	    VTail -> Pnext = V;
	    VTail = V;
	}
    }

    if (_IritPrsrPolyListCirc && IsPolygon)
	VTail -> Pnext = PPolygon -> PVertex;

    GetCloseParenToken(f);
}

/*****************************************************************************
*   Routine to update the Plane equation of the given polygon by the order   *
* of the first 3 vertices of that polygon.				     *
*****************************************************************************/
void IritPrsrUpdatePolyPlane(IPPolygonStruct *PPoly)
{
    int i;
    RealType Len, V1[3], V2[3],
	MaxLen = SQR(EPSILON);
    IPVertexStruct
	*VLast = NULL,
	*V = PPoly -> PVertex;
    PlaneType Plane;

    if (V == NULL || V -> Pnext == NULL || V -> Pnext -> Pnext == NULL)
	IritPrsrParserAbort(IP_ERR_DEGEN_POLYGON, "");

    for (i = 0; i < 4; i++)
	PPoly -> Plane[i] = 0.0;

    /* Force list to be circular. Will be recovered immediately after. */
    if (!_IritPrsrPolyListCirc) {
	VLast = IritPrsrGetLastVrtx(V);
	VLast -> Pnext = V;
    }

    do {
	PT_SUB(V1, V -> Coord, V -> Pnext -> Coord);
	V = V -> Pnext;
	PT_SUB(V2, V -> Coord, V -> Pnext -> Coord);

	Plane[0] = V1[1] * V2[2] - V2[1] * V1[2];
	Plane[1] = V1[2] * V2[0] - V2[2] * V1[0];
	Plane[2] = V1[0] * V2[1] - V2[0] * V1[1];

	/* Normalize the plane such that the normal has length of 1: */
	Len = PT_LENGTH(Plane);
	if (Len > MaxLen) {
	    for (i = 0; i < 3; i++)
		PPoly -> Plane[i] = Plane[i] / Len;

	    MaxLen = Len;

	    if (MaxLen > SQR(NORMAL_MIN_VALID_LEN))
	        break;
	}

	V = V -> Pnext;
    }
    while (V != PPoly -> PVertex &&
	   V -> Pnext != NULL &&
	   V -> Pnext -> Pnext != NULL);

    if (VLast != NULL) /* Recover non circular list, if was non circular. */
	VLast -> Pnext = NULL;

    if (MaxLen < SQR(EPSILON))
	IritPrsrParserAbort(IP_ERR_DEGEN_NORMAL, "");

    PPoly -> Plane[3] =
	-DOT_PROD(PPoly -> Plane, PPoly -> PVertex -> Coord);

    IP_SET_PLANE_POLY(PPoly);
}

/*****************************************************************************
*   Routine to update the Plane equation of the given polygon such that the  *
* Vin vertex will be in the positive side of it.			     *
*   It is assumed the polygon has at list 3 points...			     *
*****************************************************************************/
void IritPrsrUpdatePolyPlane2(IPPolygonStruct *PPoly, VectorType Vin)
{
    int i;

    IritPrsrUpdatePolyPlane(PPoly);

    if (DOT_PROD(PPoly -> Plane, Vin) + PPoly -> Plane[3] < 0) {
	/* Flip plane normal and reverse the vertex list. */
	IritPrsrReverseVrtxList(PPoly);
	for (i = 0; i < 4; i++)
	    PPoly -> Plane[i] = (-PPoly -> Plane[i]);
    }
}

/*****************************************************************************
*   Routine to update all vertices in polygon to hold a default normal if    *
* have none already.							     *
*****************************************************************************/
void IritPrsrUpdateVrtxNrml(IPPolygonStruct *PPoly, VectorType DefNrml)
{
    IPVertexStruct
	*V = PPoly -> PVertex;

    do {
	if (!IP_HAS_NORMAL_VRTX(V)) {
	    PT_COPY(V -> Normal, DefNrml);
	    IP_SET_NORMAL_VRTX(V);
	}
	V = V -> Pnext;
    }
    while (V != NULL && V != PPoly -> PVertex);
}

/*****************************************************************************
*   Reverse a list of objects.						     *
*****************************************************************************/
IPObjectStruct *IritPrsrReverseObjList(IPObjectStruct *PObj)
{
    IPObjectStruct
	*NewPObjs = NULL;

    while (PObj) {
	IPObjectStruct
	    *Pnext = PObj -> Pnext;

	PObj -> Pnext = NewPObjs;
	NewPObjs = PObj;

	PObj = Pnext;
    }

    return NewPObjs;
}

/*****************************************************************************
*   Reverse the vertex list of a given polygon. This is used mainly to       *
* reverse polygons such that cross product of consecutive edges which form   *
* a convex corner will point in the polygon normal direction.		     *
*****************************************************************************/
void IritPrsrReverseVrtxList(IPPolygonStruct *Pl)
{
    ByteType Tags, Count;
    IPVertexStruct *VNextNext, *VLast,
	*V = Pl -> PVertex,
	*VNext = V -> Pnext;

    /* Force list to be circular. Will be recovered immediately after. */
    if (!_IritPrsrPolyListCirc) {
	VLast = IritPrsrGetLastVrtx(V);
	VLast -> Pnext = V;
    }

    do {
	VNextNext = VNext -> Pnext;
	VNext -> Pnext = V;			     /* Reverse the pointer! */

	V = VNext;			   /* Advance all 3 pointers by one. */
	VNext = VNextNext;
	VNextNext = VNextNext -> Pnext;
    }
    while (V != Pl -> PVertex);

    V = Pl -> PVertex;	  /* Move the Tags/Count by one - to the right edge. */
    Tags = V -> Tags;
    Count = V -> Count;
    do {
	if (V -> Pnext == Pl -> PVertex) {
	    V -> Tags = Tags;
	    V -> Count = Count;
	}
	else {
	    V -> Tags = V -> Pnext -> Tags;
	    V -> Count = V -> Pnext -> Count;
	}

	V = V -> Pnext;
    }
    while (V != Pl -> PVertex);

    /* Recover non circular list, if needs to. */
    if (!_IritPrsrPolyListCirc) {
	VLast = IritPrsrGetLastVrtx(Pl -> PVertex);
	VLast -> Pnext = NULL;
    }
}

/*****************************************************************************
* Routine to abort parsing operation and save error reported.		     *
*****************************************************************************/
void IritPrsrParserAbort(IritPrsrErrType ErrNum, char *Msg)
{
    GlblLineCount = GlblLineCount;
    GlblParserError = ErrNum;
    strcpy(GlblTokenError, Msg);	/* Keep the message in safe place... */

    longjmp(_IritPrsrLongJumpBuffer, 1);		       /* Jump to... */
}

/*****************************************************************************
* Returns TRUE if error happened, FALSE otherwise.			     *
*   If error, then ErrorMsg is updated to point on static str describing it. *
*****************************************************************************/
int IritPrsrParseError(char **ErrorMsg)
{
    IritPrsrErrType Temp;
    char TempCopy[LINE_LEN_LONG];

    if ((Temp = GlblParserError) == IP_NO_ERR)
	return FALSE;

    strcpy(TempCopy, GlblTokenError);
    GlblParserError = IP_NO_ERR;

    switch (Temp) {
	case IP_ERR_NUMBER_EXPECTED:
	    sprintf(GlblTokenError, "Line %d: Numeric data expected - found %s",
		    GlblLineCount, TempCopy);
	    break;
	case IP_ERR_OPEN_PAREN_EXPECTED:
	    sprintf(GlblTokenError, "Line %d: '[' expected - found '%s'",
		    GlblLineCount, TempCopy);
	    break;
	case IP_ERR_CLOSE_PAREN_EXPECTED:
	    sprintf(GlblTokenError, "Line %d: ']' expected - found '%s'",
		    GlblLineCount, TempCopy);
	    break;
	case IP_ERR_LIST_COMP_UNDEF:
	    sprintf(GlblTokenError, "Line %d: Undefined list element - \"%s\"",
		    GlblLineCount, TempCopy);
	    break;
	case IP_ERR_UNDEF_EXPR_HEADER:
	    sprintf(GlblTokenError, "Line %d: Undefined TOKEN - \"%s\"",
		    GlblLineCount, TempCopy);
	    break;
	case IP_ERR_PT_TYPE_EXPECTED:
	    sprintf(GlblTokenError, "Line %d: Point type expected",
		    GlblLineCount);
	    break;
	case IP_ERR_OBJECT_EMPTY:
	    sprintf(GlblTokenError, "Line %d: Empty object found",
		    GlblLineCount);
	    break;
	case IP_ERR_FILE_EMPTY:
	    sprintf(GlblTokenError, "Line %d: Empty object found",
		    GlblLineCount);
	    break;
	case IP_ERR_MIXED_TYPES:
	    sprintf(GlblTokenError,
		    "Line %d: Mixed data types in same object",
		    GlblLineCount);
	    break;
	case IP_STR_NOT_IN_QUOTES:
	    sprintf(GlblTokenError,
		    "Line %d: String not in quotes (%s)",
		    GlblLineCount, TempCopy);
	    break;
	case IP_ERR_OBJECT_EXPECTED:
	    sprintf(GlblTokenError,
		    "Line %d: 'OBJECT' expected, found '%s'",
		    GlblLineCount, TempCopy);
	    break;
	case IP_ERR_CAGD_LIB_ERR:
	    sprintf(GlblTokenError, "Line %d: %s",
		    GlblLineCount, TempCopy);
	    break;
	case IP_ERR_STACK_OVERFLOW:
	    sprintf(GlblTokenError, "Line %d: Parser Stack overflow",
		    GlblLineCount);
	    break;
	case IP_ERR_DEGEN_POLYGON:
	    sprintf(GlblTokenError, "Line %d: Degenerate polygon",
		    GlblLineCount);
	    break;
	case IP_ERR_DEGEN_NORMAL:
	    sprintf(GlblTokenError, "Line %d: Degenerate normal",
		    GlblLineCount);
	    break;
	case IP_ERR_SOCKET_BROKEN:
	    sprintf(GlblTokenError, "Line %d: Socket connection is broken",
		    GlblLineCount);
	    break;
	case IP_ERR_SOCKET_TIME_OUT:
	    sprintf(GlblTokenError, "Line %d: Socket connection is broken",
		    GlblLineCount);
	    break;
	case IP_ERR_BIN_UNDEF_OBJ:
	    sprintf(GlblTokenError, "Binary stream: Undefined object");
	    break;
	default:
	    sprintf(GlblTokenError,
		    "Line %d: Data file parser - undefined error",
		    GlblLineCount);
	    break;
    }

    *ErrorMsg = GlblTokenError;

    return TRUE;
}

/*****************************************************************************
*   Routine to test if the given polygon is convex or not.		     *
* Algorithm: The polygon is convex iff the normals generated from cross      *
* products of two consecutive edges points to the same direction. The same   *
* direction is tested by a positive dot product.			     *
*****************************************************************************/
int IritPrsrIsConvexPolygon(IPPolygonStruct *Pl)
{
    RealType Size, V1[3], V2[3], LastNormal[3], Normal[3];
    IPVertexStruct *VNext, *VNextNext,
	*V = Pl -> PVertex;

    LastNormal[0] = LastNormal[1] = LastNormal[2] = 0.0;

    do {
	if ((VNext = V -> Pnext) == NULL)
	    VNext = Pl -> PVertex;
	if ((VNextNext = VNext -> Pnext) == NULL)
	    VNextNext = Pl -> PVertex;

	PT_SUB(V1, VNext -> Coord, V -> Coord);
	if ((Size = PT_LENGTH(V1)) > EPSILON) {
	    Size = 1.0 / Size;
	    PT_SCALE(V1, Size);
	}
	PT_SUB(V2, VNextNext -> Coord, VNext -> Coord);
	if ((Size = PT_LENGTH(V2)) > EPSILON) {
	    Size = 1.0 / Size;
	    PT_SCALE(V2, Size);
	}
	CROSS_PROD(Normal, V1, V2);

	if (V != Pl -> PVertex) {
	    if (PT_LENGTH(Normal) > CONVEX_EPSILON &&
		DOT_PROD(Normal, LastNormal) < -CONVEX_EPSILON)
		return FALSE;
	}

	PT_COPY(LastNormal, Normal);

	V = VNext;
    }
    while (V != Pl -> PVertex && V != NULL);

    return TRUE;
}

/*****************************************************************************
* Routine to print the data from given object into stdout.		     *
*****************************************************************************/
void IritPrsrStdoutObject(IPObjectStruct *PObj)
{
    IritPrsrOutputFile = stdout;

    IritPrsrPutAllObjects(PObj, 0);
}

/*****************************************************************************
* Routine to print the data from given object into given file FileName.	     *
* If FileName is NULL or empty, print to screen using IritPrsrPrintFunc      *
*****************************************************************************/
void IritPrsrPutObject(FILE *f, IPObjectStruct *PObj)
{
    /* If the following gain control and is non zero - its from error! */
    if (setjmp(_IritPrsrLongJumpBuffer) != 0)
	return;

    IritPrsrOutputFile = f;

    if (_IritPrsrReadWriteBinary)
        IritPrsrPutBinObject(f, PObj);
    else
	IritPrsrPutAllObjects(PObj, 0);
}

/*****************************************************************************
* Same as fprintf but with indentation. Prints into global		     *
* IritPrsrOutputFile unless it is NULL in which it prints to the specified   *
* function IritPrsrPrintFunc.						     *
*****************************************************************************/
#ifdef USE_VARARGS
static void IFprintf(int Indent, char *va_alist, ...)
{
    char *Format, Line[LINE_LEN_LONG];
    int i;
    va_list ArgPtr;

    va_start(ArgPtr);
    Format = va_arg(ArgPtr, char *);
#else
static void IFprintf(int Indent, char *Format, ...)
{
    char Line[LINE_LEN_LONG];
    int i;
    va_list ArgPtr;

    va_start(ArgPtr, Format);
#endif /* USE_VARARGS */

    if (_IritPrsrWriteSocket) {
	/* No need for indentation if writing to a socket. */
	vsprintf(Line, Format, ArgPtr);
	SocServerWriteLine(Line, strlen(Line));
    }
    else
    {
	for (i = 0; Indent >= 8; i++, Indent -= 8)
	    Line[i] = '\t';
	for ( ; i < Indent; i++)
	    Line[i] = ' ';
	vsprintf(&Line[i], Format, ArgPtr);

	if (IritPrsrOutputFile != NULL)
	    fputs(Line, IritPrsrOutputFile);
	else
	    IritPrsrPrintFunc(Line);
    }

    va_end(ArgPtr);
}

/*****************************************************************************
* Routine to print the data from given geometry object.			     *
*****************************************************************************/
static void IritPrsrPutAllObjects(IPObjectStruct *PObj, int Indent)
{
    int i, IsRational, NumCoords;
    char Str[LINE_LEN],
	*ErrStr = NULL;
    CagdRType *Coords;
    IPObjectStruct *PObjTmp;
    IPPolygonStruct *PPolygon;
    IPVertexStruct *PVertex;
    IPAttributeStruct
	*Attr = AttrTraceAttributes(PObj -> Attrs);

    if (Attr) {
	IFprintf(Indent, "[OBJECT\n");

	while (Attr) {
	    IFprintf(Indent + 4, "%s\n", Attr2String(Attr));
	    Attr = AttrTraceAttributes(NULL);
	}

	IFprintf(Indent + 4,
		 "%s\n", strlen(PObj -> Name) ? PObj -> Name : "NONE");
    }
    else {
	IFprintf(Indent, "[OBJECT %s\n",
	         strlen(PObj -> Name) ? PObj -> Name : "NONE");
    }
    Indent += 4;

    switch (PObj -> ObjType) {
	case IP_OBJ_POLY:
	    for (PPolygon = PObj -> U.Pl;
		 PPolygon != NULL;
		 PPolygon = PPolygon -> Pnext) {
		if (PPolygon -> PVertex == NULL)
		    continue;

		if (IP_IS_POLYLINE_OBJ(PObj))
		    IFprintf(Indent, "[POLYLINE ");
		else if (IP_IS_POINTLIST_OBJ(PObj))
		    IFprintf(Indent, "[POINTLIST ");
		else
		    IFprintf(Indent, "[POLYGON [PLANE %s %s %s %s] ",
			Real2Str(PPolygon -> Plane[0]),
			Real2Str(PPolygon -> Plane[1]),
			Real2Str(PPolygon -> Plane[2]),
			Real2Str(PPolygon -> Plane[3]));

		for (Attr = AttrTraceAttributes(PPolygon -> Attrs);
		     Attr != NULL;
		     Attr = AttrTraceAttributes(NULL))
		    IFprintf(Indent + 4, "%s\n", Attr2String(Attr));

		for (PVertex = PPolygon -> PVertex -> Pnext, i = 1;
		     PVertex != PPolygon -> PVertex && PVertex != NULL;
		     PVertex = PVertex -> Pnext, i++);
		IFprintf(Indent + 4, "%d\n", i);

		PVertex = PPolygon -> PVertex;
		do {		     /* Assume at least one edge in polygon! */
		    IFprintf(Indent + 4, "[");
		    
		    for (Attr = AttrTraceAttributes(PVertex -> Attrs);
			 Attr != NULL;
			 Attr = AttrTraceAttributes(NULL))
			IFprintf(Indent + 4, "%s\n", Attr2String(Attr));

		    if (IP_IS_POLYLINE_OBJ(PObj) ||
			(IP_IS_POLYGON_OBJ(PObj) &&
			 PT_APX_EQ(PPolygon -> Plane, PVertex -> Normal)))
			IFprintf(0, "%s%s %s %s]\n",
			    IP_IS_INTERNAL_VRTX(PVertex) ? "[INTERNAL] " : "",
			    Real2Str(PVertex -> Coord[0]),
			    Real2Str(PVertex -> Coord[1]),
			    Real2Str(PVertex -> Coord[2]));
		    else if (IP_IS_POINTLIST_OBJ(PObj))
			IFprintf(0, "%s %s %s]\n",
			    Real2Str(PVertex -> Coord[0]),
			    Real2Str(PVertex -> Coord[1]),
			    Real2Str(PVertex -> Coord[2]));
		    else
			IFprintf(0, "%s[NORMAL %s %s %s] %s %s %s]\n",
			    IP_IS_INTERNAL_VRTX(PVertex) ? "[INTERNAL] " : "",
			    Real2Str(PVertex -> Normal[0]),
			    Real2Str(PVertex -> Normal[1]),
			    Real2Str(PVertex -> Normal[2]),
			    Real2Str(PVertex -> Coord[0]),
			    Real2Str(PVertex -> Coord[1]),
			    Real2Str(PVertex -> Coord[2]));

		    PVertex = PVertex -> Pnext;
		}
		while (PVertex != PPolygon -> PVertex && PVertex != NULL);
		IFprintf(Indent, "]\n");	       /* Close the polygon. */
	    }
	    break;
	case IP_OBJ_NUMERIC:
	    IFprintf(Indent, "[NUMBER %s]\n", Real2Str(PObj -> U.R));
	    break;
	case IP_OBJ_POINT:
	    IFprintf(Indent, "[POINT %s %s %s]\n",
		     Real2Str(PObj -> U.Pt[0]),
		     Real2Str(PObj -> U.Pt[1]),
		     Real2Str(PObj -> U.Pt[2]));
	    break;
	case IP_OBJ_VECTOR:
	    IFprintf(Indent, "[VECTOR %s %s %s]\n",
		     Real2Str(PObj -> U.Vec[0]),
		     Real2Str(PObj -> U.Vec[1]),
		     Real2Str(PObj -> U.Vec[2]));
	    break;
	case IP_OBJ_PLANE:
	    IFprintf(Indent, "[PLANE %s %s %s %s]\n",
		     Real2Str(PObj -> U.Plane[0]),
		     Real2Str(PObj -> U.Plane[1]),
		     Real2Str(PObj -> U.Plane[2]),
		     Real2Str(PObj -> U.Plane[3]));
	    break;
	case IP_OBJ_CTLPT:
	    Coords = PObj -> U.CtlPt.Coords;
	    IsRational = CAGD_IS_RATIONAL_PT(PObj -> U.CtlPt.PtType);
	    NumCoords = CAGD_NUM_OF_PT_COORD(PObj -> U.CtlPt.PtType);

	    sprintf(Str, "[CTLPT %c%d %s", IsRational ? 'P' : 'E', NumCoords,
		    IsRational ? Real2Str(Coords[0]) : "");
	    
	    for (i = 1; i <= NumCoords; i++) {
		strcat(Str, " ");
	        strcat(Str, Real2Str(Coords[i]));
	    }
	    strcat(Str,"]\n");
	    IFprintf(Indent, Str);
	    break;
	case IP_OBJ_MATRIX:
	    IFprintf(Indent, "[MATRIX\n");
	    for (i = 0; i < 4; i++)
		IFprintf(Indent + 8, "%s %s %s %s%s\n",
			 Real2Str((*PObj -> U.Mat)[i][0]),
			 Real2Str((*PObj -> U.Mat)[i][1]),
			 Real2Str((*PObj -> U.Mat)[i][2]),
			 Real2Str((*PObj -> U.Mat)[i][3]),
			 i == 3 ? "]" : "");
	    break;
	case IP_OBJ_STRING:
	    IFprintf(Indent, "[STRING \"%s\"]\n", PObj -> U.Str);
	    break;
	case IP_OBJ_LIST_OBJ:
	    for (i = 0; (PObjTmp = ListObjectGet(PObj, i)) != NULL; i++)
		IritPrsrPutAllObjects(PObjTmp, Indent);
	    break;
	case IP_OBJ_CURVE:
    	    CagdCrvWriteToFile2(PObj -> U.Crvs, IritPrsrOutputFile,
				Indent, NULL, &ErrStr);
	    break;
	case IP_OBJ_SURFACE:
	    CagdSrfWriteToFile2(PObj -> U.Srfs, IritPrsrOutputFile,
				Indent, NULL, &ErrStr);
	    break;
	default:
	    IritPrsrFatalError("Attemp to print undefine object type.");
	    break;
    }

    Indent -= 4;
    IFprintf(Indent, "]\n");				/* Close the object. */

    if (ErrStr != NULL)
	IritPrsrParserAbort(IP_ERR_CAGD_LIB_ERR, ErrStr);
}

/*****************************************************************************
* Convert a real number into a string.					     *
* The routine maintains 6 different buffers simultanuously so up to 6 calls  *
* can be issued from same printf...					     *
*****************************************************************************/
static char *Real2Str(RealType R)
{
    static int j, k,
	i = 0;
    static char Buffer[6][LINE_LEN_SHORT], Line[LINE_LEN];

    if (ABS(R) < ZERO_NUM_EPSILON)
	R = 0.0;	 		   /* Round off very small numbers. */

    sprintf(Buffer[i], GlblFloatFormat, R);

    for (k = 0; !isdigit(Buffer[i][k]) && k < LINE_LEN; k++);
    if (k >= LINE_LEN) {
	sprintf(Line, "Conversion of real number (%f) failed.", R);
	IritPrsrFatalError(Line);
    }

    for (j = strlen(Buffer[i]) - 1; Buffer[i][j] == ' ' && j > k; j--);
    if (strchr(Buffer[i], '.') != NULL)
	for (; Buffer[i][j] == '0' && j > k; j--);
    Buffer[i][j+1] = 0;

    j = i;
    i = (i + 1) % 6;
    return Buffer[j];
}

/*****************************************************************************
* Returns a pointer to last vertex of a list.				     *
*****************************************************************************/
IPVertexStruct *IritPrsrGetLastVrtx(IPVertexStruct *VList)
{
    return IritPrsrGetPrevVrtx(VList, NULL);
}

/*****************************************************************************
* Returns a pointer to previous vertex in VList to V.			     *
*****************************************************************************/
IPVertexStruct *IritPrsrGetPrevVrtx(IPVertexStruct *VList, IPVertexStruct *V)
{
    IPVertexStruct
	*VHead = VList;

    if (VList == NULL || VList == V)
	return NULL;

    for ( ;
	  VList != NULL && VList -> Pnext != V && VList -> Pnext != VHead;
	  VList = VList -> Pnext);

    return VList;
}

/*****************************************************************************
* Returns a pointer to last polygon/line of a list.			     *
*****************************************************************************/
IPPolygonStruct *IritPrsrGetLastPoly(IPPolygonStruct *PList)
{
    return IritPrsrGetPrevPoly(PList, NULL);
}

/*****************************************************************************
* Returns a pointer to previous poly in PList to P.			     *
*****************************************************************************/
IPPolygonStruct *IritPrsrGetPrevPoly(IPPolygonStruct *PList, IPPolygonStruct *P)
{
    if (PList == NULL || PList == P)
	return NULL;

    for ( ; PList != NULL && PList -> Pnext != P; PList = PList -> Pnext);

    return PList;
}

/*****************************************************************************
* Returns a pointer to last polygon/line of an object.			     *
*****************************************************************************/
IPObjectStruct *IritPrsrGetLastObj(IPObjectStruct *OList)
{
    return IritPrsrGetPrevObj(OList, NULL);
}

/*****************************************************************************
* Returns a pointer to previous object in OList to O.			     *
*****************************************************************************/
IPObjectStruct *IritPrsrGetPrevObj(IPObjectStruct *OList, IPObjectStruct *O)
{
    if (OList == NULL || OList == O)
	return NULL;

    for ( ; OList != NULL && OList -> Pnext != O; OList = OList -> Pnext);
	if (OList == NULL)
	    return NULL;

    return OList;
}

/*****************************************************************************
* Returns the length of a list of vertices.				     *
*****************************************************************************/
int IritPrsrVrtxListLen(IPVertexStruct *V)
{
    int i;

    for (i = 0; V != NULL; i++, V = V -> Pnext);

    return i;
}

/*****************************************************************************
* Returns the length of a list of polys.				     *
*****************************************************************************/
int IritPrsrPolyListLen(IPPolygonStruct *P)
{
    int i;

    for (i = 0; P != NULL; i++, P = P -> Pnext);

    return i;
}

/*****************************************************************************
* Returns the length of a list of objects.				     *
*****************************************************************************/
int IritPrsrObjListLen(IPObjectStruct *O)
{
    int i;

    for (i = 0; O != NULL; i++, O = O -> Pnext);

    return i;
}

/*****************************************************************************
* Sets the printing function to call if FileName to print to is NULL.	     *
*****************************************************************************/
void IritPrsrSetPrintFunc(IritPrsrPrintFuncType PrintFunc)
{
    IritPrsrPrintFunc = PrintFunc;
    CagdSetCagdFprintf(PrintFunc);
}

/*****************************************************************************
* Sets the floating point printing format.				     *
*****************************************************************************/
void IritPrsrSetFloatFormat(char *FloatFormat)
{
    GlblFloatFormat = FloatFormat;
    CagdSetFloatFormat(FloatFormat);
}

/*****************************************************************************
* Sets the file format to binary.					     *
*****************************************************************************/
void IritPrsrSetBinaryFormat(int BinFormat)
{
    _IritPrsrReadWriteBinary = BinFormat;
}
