/*@z41.c:Object Input-Output:AppendToFile, ReadFromFile@**********************/
/*                                                                           */
/*  THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.43)                       */
/*  COPYRIGHT (C) 1991, 2008 Jeffrey H. Kingston                             */
/*                                                                           */
/*  Jeffrey H. Kingston (jeff@it.usyd.edu.au)                                */
/*  School of Information Technologies                                       */
/*  The University of Sydney 2006                                            */
/*  AUSTRALIA                                                                */
/*                                                                           */
/*  This program is free software; you can redistribute it and/or modify     */
/*  it under the terms of the GNU General Public License as published by     */
/*  the Free Software Foundation; either Version 3, or (at your option)      */
/*  any later version.                                                       */
/*                                                                           */
/*  This program is distributed in the hope that it will be useful,          */
/*  but WITHOUT ANY WARRANTY; without even the implied warranty of           */
/*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            */
/*  GNU General Public License for more details.                             */
/*                                                                           */
/*  You should have received a copy of the GNU General Public License        */
/*  along with this program; if not, write to the Free Software              */
/*  Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA   */
/*                                                                           */
/*  FILE:         z41.c                                                      */
/*  MODULE:       Object Input-Output                                        */
/*  EXTERNS:      ReadFromFile(), AppendToFile(), CloseFiles()               */
/*                                                                           */
/*****************************************************************************/
#include "externs.h"

static FILE_NUM	last_write_fnum;
static FILE	*last_write_fp;


/*****************************************************************************/
/*                                                                           */
/*  void ReadFromFileInit(void)                                              */
/*                                                                           */
/*  Initialize this module.                                                  */
/*                                                                           */
/*****************************************************************************/

void ReadFromFileInit(void)
{
  last_write_fnum = NO_FILE;
  last_write_fp  = null;
}


/*****************************************************************************/
/*                                                                           */
/*  OBJECT ReadFromFile(fnum, pos, lnum)                                     */
/*                                                                           */
/*  Read an object from file fnum starting at position pos.                  */
/*  The object may include @Env operators defining its environment, or       */
/*  not, but in any case ReadFromFile assumes that the correct scope is set. */
/*  lnum is the line number of the spot you end up at when you seek to pos.  */
/*                                                                           */
/*****************************************************************************/

OBJECT ReadFromFile(FILE_NUM fnum, long pos, int lnum)
{ OBJECT t, res;
#if DEBUG_ON
  int ipos = 0;
#endif
  ifdebug(DPP, D, ProfileOn("ReadFromFile"));
  ifdebug(DIO, D, ipos = (int) pos);
  debug3(DIO, D, "ReadFromFile(%s, %d, %d)", FileName(fnum), ipos, lnum);
  LexPush(fnum, (int) pos, DATABASE_FILE, lnum, FALSE);
  t = LexGetToken();
  if( type(t) != LBR )
  { debug1(DIO, D, "  following because type(t) = %s", Image(type(t)));
    Error(41, 1, "database index file seems to be out of date",
      FATAL, &fpos(t));
  }
  res = Parse(&t, StartSym, FALSE, FALSE);
  if( t != nilobj || type(res) != CLOSURE )
  { debug1(DIO, D, "  following because of %s", t!=nilobj ? "t" : "type(res)");
    Error(41, 2, "syntax error in database file", FATAL, &fpos(res));
  }
  LexPop();
  debug1(DIO, D, "ReadFromFile returning %s", EchoObject(res));
  ifdebug(DPP, D, ProfileOff("ReadFromFile"));
  return res;
} /* end ReadFromFile */


/*****************************************************************************/
/*                                                                           */
/*  static Optimize(x, env)                                                  */
/*                                                                           */
/*****************************************************************************/
static void OptimizeParameterList(OBJECT x, OBJECT env);

static void Optimize(OBJECT x, OBJECT env)
{ OBJECT tmp;
  if( Down(x) != x )
  { OptimizeParameterList(x, env);
  }
  tmp = ParameterCheck(x, env);
  if( tmp != nilobj )
  { ReplaceNode(tmp, x);
    DisposeObject(x);
  }
}


/*****************************************************************************/
/*                                                                           */
/*  OptimizeParameterList(x, env)                                            */
/*                                                                           */
/*  Optimize the space required to print the parameters of x by evaluating   */
/*  them in environment env if this is feasible.                             */
/*                                                                           */
/*****************************************************************************/

static void OptimizeParameterList(OBJECT x, OBJECT env)
{ OBJECT y, z, link, t, tlink;
  assert( type(x) == CLOSURE, "OptimizeParameterList: type(x) != CLOSURE!" );
  if( env == nilobj )  return;
  for( link = Down(x);  link != x;  link = NextDown(link) )
  { Child(y, link)
      ;
    if( type(y) == PAR )
    { Child(z, Down(y))
        ;
      if( type(z) == CLOSURE )
      {
	Optimize(z, env);
      }
      else if( type(z) == ACAT )
      { for( tlink = Down(z);  tlink != z;  tlink = NextDown(tlink) )
        { Child(t, Down(tlink))
            ;
          if( type(t) == CLOSURE )
	    Optimize(t, env);
        }
      }
    }
  }
} /* end OptimizeParameter */


/*@::WriteClosure()@**********************************************************/
/*                                                                           */
/*  static WriteClosure(x, linecount, fnum, env)                             */
/*                                                                           */
/*  Write closure x to file last_write_fp, without enclosing braces and      */
/*  without any environment attached.  If x happens to be a closure that     */
/*  was previously read as a @Use clause, write only @LUse and the name.     */
/*  Increment *linecount by the number of lines written.                     */
/*  The file being written to is fnum; the environment is env (for optim.)   */
/*                                                                           */
/*****************************************************************************/
static void WriteObject(OBJECT x, int outer_prec, int *linecount, FILE_NUM fnum);

static BOOLEAN need_lvis(OBJECT sym)	/* true if @LVis needed before sym */
{ return !visible(sym) &&
	 enclosing(sym) != StartSym &&
	 type(enclosing(sym)) == LOCAL;
} /* end need_lvis */

static void WriteClosure(OBJECT x, int *linecount, FILE_NUM fnum, OBJECT env)
{ OBJECT y, link, z, sym;
  BOOLEAN npar_written, name_printed;
  debug2(DIO, D, "[ WriteClosure(%s %s)", Image(type(x)), EchoObject(x));

  sym = actual(x);
  /* *** if( use_invocation(sym) == x ) *** */
  if( use_invocation(sym) != nilobj )
  { StringFPuts(KW_LUSE, last_write_fp);
    StringFPuts(STR_SPACE, last_write_fp);
    StringFPuts(SymName(sym), last_write_fp);
  }
  else
  { npar_written = FALSE;  name_printed = FALSE;
    OptimizeParameterList(x, env);
    for( link = Down(x);  link != x;  link = NextDown(link) )
    { Child(y, link)
        ;
      if( type(y) == PAR )  switch( type(actual(y)) )
      {
        case LPAR:
      
	  assert( Down(y) != y, "WriteObject/CLOSURE: LPAR!" );
	  Child(z, Down(y))
	    ;
	  WriteObject(z, (int) precedence(sym), linecount, fnum);
	  StringFPuts(STR_SPACE, last_write_fp);
	  break;


        case NPAR:
      
	  assert( Down(y) != y, "WriteObject/CLOSURE: NPAR!" );
	  Child(z, Down(y))
	    ;
	  if( !name_printed )
	  { if( need_lvis(sym) )
	    { StringFPuts(KW_LVIS, last_write_fp);
	      StringFPuts(STR_SPACE, last_write_fp);
	    }
	    StringFPuts(SymName(sym), last_write_fp);
	    name_printed = TRUE;
	  }
	  StringFPuts(STR_NEWLINE, last_write_fp);
	  *linecount += 1;
	  StringFPuts(STR_SPACE, last_write_fp);
	  StringFPuts(STR_SPACE, last_write_fp);

	  if( npar_code(actual(y)) != ' ' )
	  { StringFPuts(STR_ESCAPE, last_write_fp);
	    fprintf(last_write_fp, "%c", (char) npar_code(actual(y)));
	  }
	  else
	  { StringFPuts(SymName(actual(y)), last_write_fp);
	  }

	  StringFPuts(KW_LBR, last_write_fp);
	  WriteObject(z, NO_PREC, linecount, fnum);
	  StringFPuts(KW_RBR, last_write_fp);
	  npar_written = TRUE;
	  break;


        case RPAR:
      
	  assert( Down(y) != y, "WriteObject/CLOSURE: RPAR!" );
	  Child(z, Down(y))
	    ;
	  if( !name_printed )
	  { if( need_lvis(sym) )
	    { StringFPuts(KW_LVIS, last_write_fp);
	      StringFPuts(STR_SPACE, last_write_fp);
	    }
	    StringFPuts(SymName(sym), last_write_fp);
	    name_printed = TRUE;
	  }
	  if( npar_written )
	  { StringFPuts(STR_NEWLINE, last_write_fp);
	    *linecount += 1;
	  }
	  else
	  { StringFPuts(STR_SPACE, last_write_fp);
	  }
	  /* old version: if( filter(sym) != nilobj ) */
	  if( filter(sym) != nilobj && type(z) == FILTERED ) /* ??? */
	  {
	    debug1(DIO, D, "  filter(sym) != nilobj, type(z) == %s",
	      Image(type(z)));
	    assert( type(z) == FILTERED, "WriteClosure:  filter!" );
	    WriteObject(z, NO_PREC, linecount, fnum);
	  }
	  else if( has_body(sym) )
	  {
	    StringFPuts(KW_LBR, last_write_fp);
	    StringFPuts(STR_SPACE, last_write_fp);
	    WriteObject(z, NO_PREC, linecount, fnum);
	    StringFPuts(STR_SPACE, last_write_fp);
	    StringFPuts(KW_RBR, last_write_fp);
	  }
	  else WriteObject(z, (int) precedence(sym), linecount, fnum);
	  break;


        default:
      
	  assert1(FALSE, "WriteClosure:", Image(type(actual(y))));
	  break;

      } /* end switch */
    } /* end for each parameter */
    if( !name_printed )
    { if( need_lvis(sym) )
      { StringFPuts(KW_LVIS, last_write_fp);
        StringFPuts(STR_SPACE, last_write_fp);
      }
      StringFPuts(SymName(sym), last_write_fp);
      name_printed = TRUE;
    }
  }
  debug0(DIO, D, "] WriteClosure returning");
} /* end WriteClosure */


/*@::WriteObject()@***********************************************************/
/*                                                                           */
/*  static WriteObject(x, outer_prec, linecount, fnum)                       */
/*                                                                           */
/*  Write object x to file last_write_fp, assuming it is a subobject of an   */
/*  object and the precedence of operators enclosing it is outer_prec.       */
/*  Increment *linecount by the number of lines written.                     */
/*  The file being written to is fnum.                                       */
/*                                                                           */
/*****************************************************************************/

static void WriteObject(OBJECT x, int outer_prec, int *linecount, FILE_NUM fnum)
{ OBJECT link, y, z, gap_obj, sym, env;  FULL_CHAR *name; int offset, lnum;
  int prec, i, last_prec;  BOOLEAN braces_needed;
  debug2(DIO, D, "[ WriteObject(%s %s)", Image(type(x)), EchoObject(x));
  switch( type(x) )
  {

    case WORD:

      if( StringLength(string(x)) == 0 && outer_prec > ACAT_PREC )
      { StringFPuts(KW_LBR, last_write_fp);
	StringFPuts(KW_RBR, last_write_fp);
      }
      else StringFPuts(string(x), last_write_fp);
      break;

    
    case QWORD:

      StringFPuts(StringQuotedWord(x), last_write_fp);
      break;

    
    case VCAT:  prec = VCAT_PREC;  goto ETC;
    case HCAT:  prec = HCAT_PREC;  goto ETC;
    case ACAT:  prec = ACAT_PREC;  goto ETC;

      ETC:
      if( prec < outer_prec )  StringFPuts(KW_LBR, last_write_fp);
      last_prec = prec;
      for( link = Down(x);  link != x;  link = NextDown(link) )
      {	Child(y, link)
      	  ;
	if( type(y) == GAP_OBJ )
	{ if( Down(y) == y )
	  { assert( type(x) == ACAT, "WriteObject: Down(y) == y!" );
	    for( i = 1;  i <= vspace(y);  i++ )
	      StringFPuts(STR_NEWLINE, last_write_fp);
	    *linecount += vspace(y);
	    for( i = 1;  i <= hspace(y);  i++ )
	      StringFPuts(STR_SPACE,  last_write_fp);
	    last_prec = (vspace(y) + hspace(y) == 0) ? JUXTA_PREC : ACAT_PREC;
	  }
	  else
	  { Child(gap_obj, Down(y))
	      ;
	    if( type(x)==ACAT )
	      StringFPuts(STR_SPACE, last_write_fp);
	    else
	    { StringFPuts(STR_NEWLINE, last_write_fp);
	      *linecount += 1;
	    }
	    StringFPuts(EchoCatOp(type(x), mark(gap(y)), join(gap(y))),
	      last_write_fp);
	    if( !is_word(type(gap_obj)) || StringLength(string(gap_obj)) != 0 )
		WriteObject(gap_obj, FORCE_PREC, linecount, fnum);
	    StringFPuts(STR_SPACE, last_write_fp);
	    last_prec = prec;
	  }
	}
	else
	{ if( type(x) == ACAT )
	  { OBJECT next_gap;  int next_prec;
	    if( NextDown(link) != x )
	    { Child(next_gap, NextDown(link))
	        ;
	      assert( type(next_gap) == GAP_OBJ, "WriteObject: next_gap!" );
	      next_prec = (vspace(next_gap) + hspace(next_gap) == 0)
				? JUXTA_PREC : ACAT_PREC;
	    }
	    else next_prec = prec;
	    WriteObject(y, find_max(last_prec, next_prec), linecount, fnum);
	  }
	  else WriteObject(y, prec, linecount, fnum);
	}
      }
      if( prec < outer_prec )  StringFPuts(KW_RBR, last_write_fp);
      break;


    case ENV:

      if( Down(x) == x )
      {
	/* environment is empty */
	StringFPuts(KW_ENVC, last_write_fp);
	StringFPuts(STR_NEWLINE, last_write_fp);
	*linecount += 1;
      }
      else if( EnvWriteRetrieve(x, fnum, &offset, &lnum) )
      {
	/* environment was previously written to this file */
	StringFPuts(KW_ENVD, last_write_fp);
	fprintf(last_write_fp, " \"%d %d\"", offset, lnum);
	StringFPuts(STR_NEWLINE, last_write_fp);
	*linecount += 1;
      }
      else
      {
	/* record the position of this environment */
	EnvWriteInsert(x, fnum, (int) ftell(last_write_fp), *linecount);

	/* write the environment */
	if( Down(x) == LastDown(x) )
        {
	  /* envt contains just one closure (with its environment) */
	  Child(y, Down(x))
	    ;
	  assert( type(y) == CLOSURE, "WriteObject: ENV/CLOSURE!" );
	  StringFPuts(KW_LBR, last_write_fp);
	  StringFPuts(STR_SPACE, last_write_fp);
	  StringFPuts(KW_ENVA, last_write_fp);
	  StringFPuts(STR_SPACE, last_write_fp);
	  StringFPuts(KW_LBR, last_write_fp);
	  StringFPuts(STR_SPACE, last_write_fp);
	  WriteObject(y, NO_PREC, linecount, fnum);
	  StringFPuts(STR_SPACE, last_write_fp);
	  StringFPuts(KW_RBR, last_write_fp);
	  StringFPuts(STR_SPACE, last_write_fp);
	  StringFPuts(KW_RBR, last_write_fp);
	  StringFPuts(STR_NEWLINE, last_write_fp);
	  *linecount += 1;
        }
        else
        {
	  /* envt contains a closure (with envt) plus an environment */
	  Child(env, LastDown(x))
	    ;
	  assert( type(env) == ENV, "WriteObject: ENV/ENV!" );
	  StringFPuts(KW_LBR, last_write_fp);
	  StringFPuts(STR_SPACE, last_write_fp);
	  StringFPuts(KW_ENVB, last_write_fp);
	  StringFPuts(STR_SPACE, last_write_fp);
	  StringFPuts(KW_LBR, last_write_fp);
	  StringFPuts(STR_SPACE, last_write_fp);
	  WriteObject(env, NO_PREC, linecount, fnum);
	  StringFPuts(STR_SPACE, last_write_fp);
	  StringFPuts(KW_RBR, last_write_fp);
	  StringFPuts(STR_NEWLINE, last_write_fp);
	  *linecount += 1;
	  Child(y, Down(x))
	    ;
	  assert( type(y) == CLOSURE, "WriteObject: ENV/ENV+CLOSURE!" );
	  StringFPuts(KW_LBR, last_write_fp);
	  WriteObject(y, NO_PREC, linecount, fnum);
	  StringFPuts(KW_RBR, last_write_fp);
	  StringFPuts(STR_SPACE, last_write_fp);
	  StringFPuts(KW_RBR, last_write_fp);
	  StringFPuts(STR_NEWLINE, last_write_fp);
	  *linecount += 1;
        }
      }
      break;


    case CLOSURE:

      sym = actual(x);  env = nilobj;
      if( LastDown(x) != x )
      {	Child(y, LastDown(x))
          ;
	if( type(y) == ENV )  env = y;
      }

      braces_needed = env != nilobj ||
	(precedence(sym) <= outer_prec && (has_lpar(sym) || has_rpar(sym))) ||
	outer_prec >= JUXTA_PREC;

      /* print environment */
      if( env != nilobj )
      {	StringFPuts(KW_CENV, last_write_fp);
	StringFPuts(STR_SPACE, last_write_fp);
	StringFPuts(KW_LBR, last_write_fp);
      	StringFPuts(STR_NEWLINE, last_write_fp);
	*linecount += 1;
	WriteObject(env, NO_PREC, linecount, fnum);
        StringFPuts(KW_RBR, last_write_fp);
      	StringFPuts(STR_NEWLINE, last_write_fp);
	*linecount += 1;
      }

      /* print left brace if needed */
      if( braces_needed )  StringFPuts(KW_LBR, last_write_fp);
	
      /* print the closure proper */
      WriteClosure(x, linecount, fnum, env);

      /* print closing brace if needed */
      if( braces_needed )  StringFPuts(KW_RBR, last_write_fp);

      /* print closing environment if needed */
      /* ***
      if( env != nilobj )
      { StringFPuts(KW_RBR, last_write_fp);
	StringFPuts(STR_NEWLINE, last_write_fp);
	*linecount += 1;
      }
      *** */
      break;


    case CROSS:
    case FORCE_CROSS:

      Child(y, Down(x))
        ;
      assert( type(y) == CLOSURE, "WriteObject/CROSS: type(y) != CLOSURE!" );
      if( DEFAULT_PREC <= outer_prec )  StringFPuts(KW_LBR, last_write_fp);
      if( need_lvis(actual(y)) )
      { StringFPuts(KW_LVIS, last_write_fp);
        StringFPuts(STR_SPACE, last_write_fp);
      }
      StringFPuts(SymName(actual(y)), last_write_fp);
      StringFPuts(type(x) == CROSS ? KW_CROSS : KW_FORCE_CROSS, last_write_fp);
      Child(y, LastDown(x))
        ;
      WriteObject(y, FORCE_PREC, linecount, fnum);
      if( DEFAULT_PREC <= outer_prec )  StringFPuts(KW_RBR, last_write_fp);
      break;


    case NULL_CLOS:	name = KW_NULL;		goto SETC;
    case PAGE_LABEL:	name = KW_PAGE_LABEL;	goto SETC;
    case BEGIN_HEADER:	name = KW_BEGIN_HEADER;	goto SETC;
    case END_HEADER:	name = KW_END_HEADER;	goto SETC;
    case SET_HEADER:	name = KW_SET_HEADER;	goto SETC;
    case CLEAR_HEADER:	name = KW_CLEAR_HEADER;	goto SETC;
    case ONE_COL:	name = KW_ONE_COL;	goto SETC;
    case ONE_ROW:	name = KW_ONE_ROW;	goto SETC;
    case WIDE:		name = KW_WIDE;		goto SETC;
    case HIGH:		name = KW_HIGH;		goto SETC;
    case HSHIFT:	name = KW_HSHIFT;	goto SETC;
    case VSHIFT:	name = KW_VSHIFT;	goto SETC;
    case HMIRROR:	name = KW_HMIRROR;	goto SETC;
    case VMIRROR:	name = KW_VMIRROR;	goto SETC;
    case HSCALE:	name = KW_HSCALE;	goto SETC;
    case VSCALE:	name = KW_VSCALE;	goto SETC;
    case HCOVER:	name = KW_HCOVER;	goto SETC;
    case VCOVER:	name = KW_VCOVER;	goto SETC;
    case SCALE:		name = KW_SCALE;	goto SETC;
    case KERN_SHRINK:	name = KW_KERN_SHRINK;	goto SETC;
    case HCONTRACT:	name = KW_HCONTRACT;	goto SETC;
    case VCONTRACT:	name = KW_VCONTRACT;	goto SETC;
    case HLIMITED:	name = KW_HLIMITED;	goto SETC;
    case VLIMITED:	name = KW_VLIMITED;	goto SETC;
    case HEXPAND:	name = KW_HEXPAND;	goto SETC;
    case VEXPAND:	name = KW_VEXPAND;	goto SETC;
    case START_HVSPAN:	name = KW_STARTHVSPAN;	goto SETC;
    case START_HSPAN:	name = KW_STARTHSPAN;	goto SETC;
    case START_VSPAN:	name = KW_STARTVSPAN;	goto SETC;
    case HSPAN:		name = KW_HSPAN;	goto SETC;
    case VSPAN:		name = KW_VSPAN;	goto SETC;
    case PADJUST:	name = KW_PADJUST;	goto SETC;
    case HADJUST:	name = KW_HADJUST;	goto SETC;
    case VADJUST:	name = KW_VADJUST;	goto SETC;
    case ROTATE:	name = KW_ROTATE;	goto SETC;
    case BACKGROUND:	name = KW_BACKGROUND;	goto SETC;
    case CASE:		name = KW_CASE;		goto SETC;
    case YIELD:		name = KW_YIELD;	goto SETC;
    case BACKEND:	name = KW_BACKEND;	goto SETC;
    case XCHAR:		name = KW_XCHAR;	goto SETC;
    case FONT:		name = KW_FONT;		goto SETC;
    case SPACE:		name = KW_SPACE;	goto SETC;
    case YUNIT:		name = KW_YUNIT;	goto SETC;
    case ZUNIT:		name = KW_ZUNIT;	goto SETC;
    case SET_CONTEXT:	name = KW_SET_CONTEXT;	goto SETC;
    case GET_CONTEXT:	name = KW_GET_CONTEXT;	goto SETC;
    case BREAK:		name = KW_BREAK;	goto SETC;
    case UNDERLINE:	name = KW_UNDERLINE;	goto SETC;
    case UNDERLINE_COLOUR: name=KW_UNDERLINE_COLOUR ;goto SETC;
    case COLOUR:	name = KW_COLOUR;	goto SETC;
    case TEXTURE:	name = KW_TEXTURE;	goto SETC;
    case OUTLINE:	name = KW_OUTLINE;	goto SETC;
    case LANGUAGE:	name = KW_LANGUAGE;	goto SETC;
    case CURR_LANG:	name = KW_CURR_LANG;	goto SETC;
    case CURR_FAMILY:	name = KW_CURR_FAMILY;	goto SETC;
    case CURR_FACE:	name = KW_CURR_FACE;	goto SETC;
    case CURR_YUNIT:	name = KW_CURR_YUNIT;	goto SETC;
    case CURR_ZUNIT:	name = KW_CURR_ZUNIT;	goto SETC;
    case COMMON:	name = KW_COMMON;	goto SETC;
    case RUMP:		name = KW_RUMP;		goto SETC;
    case MELD:		name = KW_MELD;		goto SETC;
    case INSERT:	name = KW_INSERT;	goto SETC;
    case ONE_OF:	name = KW_ONE_OF;	goto SETC;
    case NEXT:		name = KW_NEXT;		goto SETC;
    case PLUS:		name = KW_PLUS;		goto SETC;
    case MINUS:		name = KW_MINUS;	goto SETC;
    case OPEN:		name = KW_OPEN;		goto SETC;
    case TAGGED:	name = KW_TAGGED;	goto SETC;
    case INCGRAPHIC:	name = KW_INCGRAPHIC;	goto SETC;
    case SINCGRAPHIC:	name = KW_SINCGRAPHIC;	goto SETC;
    case PLAIN_GRAPHIC:	name = KW_PLAINGRAPHIC;	goto SETC;
    case GRAPHIC:	name = KW_GRAPHIC;	goto SETC;
    case LINK_SOURCE:	name = KW_LINK_SOURCE;	goto SETC;
    case LINK_DEST:	name = KW_LINK_DEST;	goto SETC;
    case LINK_DEST_NULL:name = KW_LINK_DEST;	goto SETC;
    case LINK_URL:	name = KW_LINK_URL;	goto SETC;

      /* print left parameter, if present */
      SETC:
      if( DEFAULT_PREC <= outer_prec )  StringFPuts(KW_LBR, last_write_fp);
      if( Down(x) != LastDown(x) )
      {	Child(y, Down(x))
      	  ;
	WriteObject(y, DEFAULT_PREC, linecount, fnum);
	StringFPuts(STR_SPACE, last_write_fp);
      }

      /* print the name of the symbol */
      StringFPuts(name, last_write_fp);

      /* print right parameter, if present */
      if( LastDown(x) != x )
      {	Child(y, LastDown(x))
          ;
	StringFPuts(STR_SPACE, last_write_fp);
	if( type(x) == OPEN )
	{ StringFPuts(KW_LBR, last_write_fp);
	  WriteObject(y, NO_PREC, linecount, fnum);
	  StringFPuts(KW_RBR, last_write_fp);
	}
	else WriteObject(y, DEFAULT_PREC, linecount, fnum);
      }
      if( DEFAULT_PREC <= outer_prec )  StringFPuts(KW_RBR, last_write_fp);
      break;


    case RAW_VERBATIM:
    case VERBATIM:

      StringFPuts(type(x) == VERBATIM ? KW_VERBATIM : KW_RAWVERBATIM, last_write_fp);
      StringFPuts(STR_SPACE, last_write_fp);
      StringFPuts(KW_BEGIN, last_write_fp);
      StringFPuts(STR_NEWLINE, last_write_fp);
      Child(y, Down(x))
        ;
      if( type(y) == WORD )
      {
	StringFPuts(string(y), last_write_fp);
        StringFPuts(STR_SPACE, last_write_fp);
      }
      else
      {
	assert( type(y) == VCAT, "WriteObject/VERBATIM!" );
	for( link = Down(y);  link != y;  link = NextDown(link) )
	{ Child(z, link)
	    ;
	  if( type(z) == GAP_OBJ )  continue;
	  assert( type(z) == WORD, "WriteObject/VERBATIM/WORD!");
	  StringFPuts(string(z), last_write_fp);
	  StringFPuts(STR_NEWLINE, last_write_fp);
	  *linecount += 1;
	}
      }
      StringFPuts(KW_END, last_write_fp);
      StringFPuts(STR_SPACE, last_write_fp);
      StringFPuts(type(x) == VERBATIM ? KW_VERBATIM : KW_RAWVERBATIM, last_write_fp);
      break;


    case FILTERED:

      FilterWrite(x, last_write_fp, linecount);
      break;


    default:

      assert1(FALSE, "WriteObject:", Image(type(x)));
      break;

  } /* end switch */
  debug0(DIO, D, "] WriteObject returning");
} /* end WriteObject */


/*@::AppendToFile(), CloseFiles()@********************************************/
/*                                                                           */
/*  AppendToFile(x, fnum, pos, lnum)                                         */
/*                                                                           */
/*  Append object x to file fnum, returning its fseek position in *pos.      */
/*  and its line number in lnum.  Record that this file has been updated.    */
/*                                                                           */
/*****************************************************************************/

void AppendToFile(OBJECT x, FILE_NUM fnum, int *pos, int *lnum)
{ FULL_CHAR buff[MAX_BUFF], *str;  int linecount;
  debug2(DIO, D, "[ AppendToFile( %s, %s )", EchoObject(x), FileName(fnum));

  /* open file fnum for writing */
  if( last_write_fnum != fnum )
  { if( last_write_fnum != NO_FILE )  fclose(last_write_fp);
    str = FileName(fnum);
    if( StringLength(str) + StringLength(NEW_DATA_SUFFIX) >= MAX_BUFF )
      Error(41, 3, "file name %s%s is too long",
	FATAL, PosOfFile(fnum), str, NEW_DATA_SUFFIX);
    StringCopy(buff, str);  StringCat(buff, NEW_DATA_SUFFIX);
    last_write_fp = StringFOpen(buff,
      FileTestUpdated(fnum) ? APPEND_FILE : WRITE_FILE);
    if( last_write_fp == null )
      Error(41, 4, "cannot append to database file %s", FATAL, no_fpos, buff);
    last_write_fnum = fnum;
    (void) fseek(last_write_fp, 0L, SEEK_END);
  }

  /* write x out and record the fact that fnum has changed */
  *pos = (int) ftell(last_write_fp);
  StringFPuts(KW_LBR, last_write_fp);
  linecount = FileGetLineCount(fnum);
  *lnum = linecount + 1;
  WriteObject(x, NO_PREC, &linecount, fnum);
  StringFPuts(KW_RBR, last_write_fp);
  StringFPuts(STR_NEWLINE, last_write_fp);
  StringFPuts(STR_NEWLINE, last_write_fp);
  FileSetUpdated(fnum, linecount + 2);
  debug0(DIO, D, "] AppendToFile returning.");
} /* end AppendToFile */


/*****************************************************************************/
/*                                                                           */
/*  CloseFiles()                                                             */
/*                                                                           */
/*  Close all files and move new versions to the names of old versions.      */
/*                                                                           */
/*****************************************************************************/

void CloseFiles(void)
{ FILE_NUM fnum;  FULL_CHAR oldname[MAX_BUFF], newname[MAX_BUFF];
  FILE *fp;
  ifdebug(DPP, D, ProfileOn("CloseFiles"));
  debug0(DIO, D, "CloseFiles()");

  /* close off last file opened by AppendToFile above */
  if( last_write_fnum != NO_FILE )  fclose(last_write_fp);

  /* get rid of old database files */
  for( fnum=FirstFile(SOURCE_FILE);  fnum != NO_FILE;  fnum = NextFile(fnum) )
  { StringCopy(oldname, FileName(fnum));
    StringCat(oldname, DATA_SUFFIX);
    debug1(DIO, D, "remove(%s)", oldname);
    StringRemove(oldname);
  }

  /* move any new database files to the old names, if updated */
  /* just to avoid confusion: the "new name" means the ".ldx" */
  /* temporary file name; the "old name" means the permanent  */
  /* name, i.e. ".ld".  So we have to move the new name to    */
  /* the old name.                                            */

  for( fnum=FirstFile(DATABASE_FILE); fnum != NO_FILE; fnum = NextFile(fnum) )
  { if( FileTestUpdated(fnum) )
    {
      /* construct new and old file names */
      StringCopy(oldname, FileName(fnum));
      StringCopy(newname, oldname);
      StringCat(newname, NEW_DATA_SUFFIX);

      /* guaranteed portable algorithm for changing the name of file	*/
      /* "newname" to "oldname": if "oldname" exists already, then	*/
      /* remove it (avoids removing a non-existent file, which can	*/
      /* be a problem); then rename "newname" to be "oldname" (avoids	*/
      /* overwriting an existing file "oldname", another problem)	*/

      if( (fp = StringFOpen(oldname, READ_FILE)) != NULL )
      { fclose(fp);
	StringRemove(oldname);
      }
      debug2(DIO, D, "rename(%s, %s)", newname, oldname);
      if( StringRename(newname, oldname) != 0 )
	Error(41, 5, "rename(%s, %s) failed", INTERN, no_fpos,newname,oldname);
    }
  }
  debug0(DIO, D, "CloseFiles returning.");
  ifdebug(DPP, D, ProfileOff("CloseFiles"));
} /* end CloseFiles */
