/*  $Id: pl-wic.c,v 1.57 1997/08/07 07:58:56 jan Exp $

    Copyright (c) 1990 Jan Wielemaker. All rights reserved.
    See ../LICENCE to find out about your rights.
    jan@swi.psy.uva.nl

    Purpose: load and save intermediate code files
*/

/*#define O_DEBUG 1*/
#include "pl-incl.h"
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

forwards char *	getString(IOSTREAM *);
forwards long	getNum(IOSTREAM *);
forwards real	getReal(IOSTREAM *);
forwards bool	loadWicFd(char *, IOSTREAM *, int);
forwards bool	loadPredicate(IOSTREAM *, int skip);
forwards bool	loadImport(IOSTREAM *, int skip);
forwards void	putString(char *, IOSTREAM *);
forwards void	putAtom(atom_t, IOSTREAM *);
forwards void	putNum(long, IOSTREAM *);
forwards void	putReal(real, IOSTREAM *);
forwards void	saveWicClause(Clause, IOSTREAM *);
forwards void	closeProcedureWic(IOSTREAM *);
forwards bool	closeWic(void);
forwards bool	addDirectiveWic(term_t, IOSTREAM *fd);
forwards bool	importWic(Procedure, IOSTREAM *fd);
forwards bool	compileFile(char *);
forwards bool	putStates(IOSTREAM *);
forwards word	loadXR(IOSTREAM *);
forwards word   loadXRc(int c, IOSTREAM *fd);
forwards void	putstdw(word w, IOSTREAM *fd);
forwards word	getstdw(IOSTREAM *fd);
static bool	loadStatement(int c, IOSTREAM *fd, int skip);
static bool	loadPart(IOSTREAM *fd, Module *module, int skip);
static bool	loadInModule(IOSTREAM *fd, int skip);
static int	qlfVersion(IOSTREAM *s);
static bool	appendState(const char *name);

#define Qgetc(s) Snpgetc(s)		/* ignore position recording */

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SWI-Prolog can compile Prolog source files into intermediate code files, 
which can be loaded very  fast.   They  can  be  saved  as  stand  alone
executables using Unix #! magic number.

A wic file consists of the magic code and a version check code.  This is
followed by the command line option   defaults.  Then an optional series
of `include' statements follow.  Finally   the predicates and directives
are  described.   Predicates  are  described    close  to  the  internal
representation.  Directives are stored as  binary terms representing the
query.

The default options and include statements are written incrementally  in
each  wic  file.   In  the  normal  boot  cycle  first  the boot file is
determined.  Then the option structure is filled with the default option
found in this boot file.  Next the command line arguments are scanned to
obtain all options.  Then stacks, built  in's,  etc.   are  initialised.
The  the  boot  file is read again, but now only scanning for directives
and predicates.

IF YOU CHANGE ANYTHING TO THIS IOSTREAM, SO THAT OLD WIC-FILES CAN NO LONGER
BE READ, PLEASE DO NOT FORGET TO INCREMENT THE VERSION NUMBER!

Below is an informal description of the format of a `.qlf' file:

<wic-file>	::=	#!<path>
			<magic code>
			<version number>
			<localSize>			% a <word>
			<globalSize>			% a <word>
			<trailSize>			% a <word>
			<argumentSize>			% a <word>
			<heapSize>			% a <word>
			<goal>				% a <string>
			<topLevel>			% a <string>
			<initFile>			% a <string>
			<home>				% a <string>
			{<statement>}
			'T'
			<size>				% a stdword
			<QLFMAGICNUM>			% a stdword
----------------------------------------------------------------
<qlf-file>	::=	<qlf-magic>
			<version-number>
			'F' <string>			% path of qlf file
			'Q' <qlf-part>
<qlf-magic>	::=	<string>
<qlf-module>	::=	<qlf-header>
			<size>				% size in bytes
			{<statement>}
			'X'
<qlf-header>	::=	'M' <XR/modulename>		% module name
			<source>			% file + time
			{<qlf-export>}
			'X'
		      | <source>			% not a module
			<time>
<qlf-export>	::=	'E' <XR/functor>
<source>	::=	'F' <string> <time> <system>
		      | '-'
----------------------------------------------------------------
<magic code>	::=	<string>			% normally #!<path>
<version number>::=	<num>
<statement>	::=	'W' <string>			% include wic file
		      | 'P' <XR/functor>
			    {<clause>} <pattern>	% predicate
		      |	'O' <XR/modulename>
			    <XR/functor>		% pred out of module
			    {<clause>} <pattern>
		      | 'D' 
		        <lineno>			% source line number
			<term>				% directive
		      | 'E' <XR/functor>		% export predicate
		      | 'I' <XR/procedure>		% import predicate
		      | 'Q' <qlf-module>		% include module
		      | 'M' <XR/modulename>		% load-in-module
		            {<statement>}
			    'X'
<clause>	::=	'C' <line_no> <# var>
			    <#n subclause> <#codes> <codes>
		      | 'X' 				% end of list
<XR>		::=	XR_REF     <num>		% XR id from table
			XR_ATOM    <string>		% atom
			XR_INT     <num>		% number
			XR_BIGNUM  <word>		% big-number
			XR_FLOAT   <word>		% real (float)
			XR_STRING  <string>		% string
			XR_FUNCTOR <XR/name> <num>	% functor
			XR_PRED    <XR/fdef> <XR/module>% predicate
<term>		::=	<num>				% # variables in term
			<theterm>
<theterm>	::=	<XR/atomic>			% atomic data
		      | 'v' <num>			% variable
		      | 't' <XR/functor> {<theterm>}	% compound
<system>	::=	's'				% system source file
		      | 'u'				% user source file
<time>		::=	<word>				% time file was loaded
<pattern>	::=	<num>				% indexing pattern
<codes>		::=	<num> {<code>}
<string>	::=	{<non-zero byte>} <0>
<word>		::=	<4 byte entity>

Numbers are stored in  a  packed  format  to  reduce  the  size  of  the
intermediate  code  file  as  99%  of  them  is  normally  small, but in
principle not limited (virtual  machine  codes,  arities,  table  sizes,
etc).   The  upper  two  bits  of  the  first byte contain the number of
additional bytes.  the bytes represent the number `most-significant part
first'.  See the functions putNum() and getNum()  for  details.   Before
you  don't  agree  to  this  schema,  you  should  remember it makes the
intermediate code files about 30% smaller  and  avoids  the  differences
between  16  and  32  bits  machines (arities on 16 bits machines are 16
bits) as well as machines with different byte order.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#define LOADVERSION 30			/* load all versions later >= 30 */
#define VERSION 31			/* save version number */
#define QLFMAGICNUM 0x716c7374		/* "qlst" on little-endian machine */

#define XR_REF     0			/* reference to previous */
#define XR_ATOM	   1			/* atom */
#define XR_FUNCTOR 2			/* functor */
#define XR_PRED	   3			/* procedure */
#define XR_INT     4			/* int */
#define XR_BIGNUM  5			/* 32-bit integer */
#define XR_FLOAT   6			/* float */
#define XR_STRING  7			/* string */

static char saveMagic[] = "SWI-Prolog (c) 1990 Jan Wielemaker\n";
static char qlfMagic[]  = "SWI-Prolog .qlf file\n";
static char *wicFile;			/* name of output file */
static char *mkWicFile;			/* Wic file under construction */
static IOSTREAM *wicFd;			/* file descriptor of wic file */
static Procedure currentProc;		/* current procedure */
static SourceFile currentSource;	/* current source file */

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
On tos, loading takes long; give the user  something  to  look  at.   On
workstations, it normally is so fast it is hardy noticable.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#if tos
static void
notifyLoad(file)
char *file;
{ Sfprintf(Soutput, "Loading %s ", file);
  Sflush(Soutput);
}

static void
notifyLoaded()
{ Sprintf(Soutput, "\r\033K");
}

static void
notifyPredicate(functor_t f)
{ static char cur[] = "|/-\\";
  static int  n = 0;

  Sprintf(Soutput, "%c\b", cur[n++ & 0x3]);
}

#else /*!tos*/

#define notifyLoad(file)
#define notifyLoaded()
#define notifyPredicate(f)

#endif /* tos */

		 /*******************************
		 *	     CLEANUP		*
		 *******************************/

void
qlfCleanup()
{ if ( mkWicFile )
  { warning("Removing incomplete Quick Load File %s", mkWicFile);
    RemoveFile(mkWicFile);
    mkWicFile = NULL;
  }
}


		 /*******************************
		 *     LOADED XR ID HANDLING	*
		 *******************************/

typedef struct xr_table *XrTable;

struct xr_table
{ int		id;			/* next id to give out */
  Word	       *table;			/* main table */
  int   	tablesize;		/* # sub-arrays */
  XrTable	previous;		/* stack */
};

static XrTable loadedXrs;		/* head pointer */

#define loadedXRTableId		(loadedXrs->id)
#define loadedXRTable		(loadedXrs->table)
#define loadedXRTableArrays	(loadedXrs->tablesize)

#define SUBENTRIES ((ALLOCSIZE)/sizeof(word))

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
XR reference handling during loading.  This   is arranged as an array-of
arrays.  These arrays are of size ALLOCSIZE,   so they will be reused on
perfect-fit basis the pl-alloc.c.  With ALLOCSIZE   = 64K, this requires
minimal 128K memory.   Maximum  allowed  references   is  16K^2  or  32M
references.  That will normally overflow other system limits first.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

static void
pushXrIdTable()
{ XrTable t = allocHeap(sizeof(struct xr_table));

  t->previous = loadedXrs;
  loadedXrs = t;

  if ( !(loadedXRTable = malloc(ALLOCSIZE)) )
    outOfCore();
  loadedXRTableArrays = 0;
  loadedXRTableId = 0;
}


static void
popXrIdTable()
{ int i;
  XrTable prev = loadedXrs->previous;

  for(i=0; i<loadedXRTableArrays; i++)
    free(loadedXRTable[i]);

  free(loadedXRTable);
  freeHeap(loadedXrs, sizeof(struct xr_table));

  loadedXrs = prev;
}


static word
lookupXrId(long id)
{ Word array = loadedXRTable[id/SUBENTRIES];

  return array[id%SUBENTRIES];
}


static void
storeXrId(long id, word value)
{ int i = id/SUBENTRIES;

  while ( i >= loadedXRTableArrays )
  { if ( !(loadedXRTable[loadedXRTableArrays++] = malloc(ALLOCSIZE)) )
      outOfCore();
  }
  
  loadedXRTable[i][id%SUBENTRIES] = value;
}


		 /*******************************
		 *	 PRIMITIVE LOADING	*
		 *******************************/

static int	qlf_has_moved;		/* file has moved: be careful */
static char *   qlf_save_dir;		/* dir of saved .qlf file */
static char *	qlf_load_dir;		/* dir of .qlf file now */

static bool
qlfLoadError(IOSTREAM *fd, char *ctx)
{ fatalError("%s: QLF format error at index = %ld", ctx, Stell(fd));

  fail;
}


static char *
getString(IOSTREAM *fd)
{ static char *tmp;
  static char *tmpend;
  static int  tmpsize = 512;
  char *s;
  Char c;

  if ( tmp == NULL )
  { if ( !(tmp = malloc(tmpsize)) )
      outOfCore();
    tmpend = &tmp[tmpsize-1];
  }

  for( s = tmp; (*s = c = Getc(fd)) != EOS; s++ )
  { if ( s == tmpend )
    { if ( !(tmp = realloc(tmp, tmpsize+512)) )
	outOfCore();
      s = &tmp[tmpsize-1];
      tmpsize += 512;
      tmpend = &tmp[tmpsize-1];
    }
    if ( c == EOF )
      fatalError("Unexpected EOF on intermediate code file at offset %d",
		 Stell(fd));
  }

  return tmp;
}


static char *
getMagicString(IOSTREAM *fd, char *buf, int maxlen)
{ char *s;
  int c;

  for( s = buf; --maxlen >= 0 && (*s = (c = Getc(fd))); s++ )
    if ( c == EOF )
      return NULL;

  if ( maxlen > 0 )
    return buf;

  return NULL;
}


static long
getNum(IOSTREAM *fd)
{ long first = Getc(fd);
  int bytes, shift, b;

  if ( !(first & 0xc0) )
    return (first << 26) >> 26;		/* 99% of them: speed up a bit */    

  bytes = (int) ((first >> 6) & 0x3);
  first &= 0x3f;

  for( b = 0; b < bytes; b++ )
  { first <<= 8;
    first |= Getc(fd) & 0xff;
  }

  shift = (3-bytes)*8 + 2;

  return (first << shift) >> shift;
}


static word
getstdw(IOSTREAM *fd)
{
#ifndef WORDS_BIGENDIAN
  union
  { word         l;
    unsigned char c[4];
  } cvrt;
  long rval;

  cvrt.l = Sgetw(fd);
  rval = (cvrt.c[0] << 24) |
         (cvrt.c[1] << 16) |
	 (cvrt.c[2] << 8) |
	  cvrt.c[3];
  return rval;
#else
  return Sgetw(fd);
#endif
}


static real
getReal(IOSTREAM *fd)
{ real f;
  word *s = (word *) &f;

#ifndef WORDS_BIGENDIAN
  s[0] = getstdw(fd);
  s[1] = getstdw(fd);
#else
  s[1] = getstdw(fd);
  s[0] = getstdw(fd);
#endif

  DEBUG(3, Sdprintf("getReal() --> %f\n", f));

  return f;
}


static word
loadXRc(int c, IOSTREAM *fd)
{ word xr;
  int id = 0;				/* make gcc happy! */

  switch( c )
  { case XR_REF:
    { return lookupXrId(getNum(fd));
    }
    case XR_ATOM:
      id = ++loadedXRTableId;
      xr = lookupAtom(getString(fd));
      DEBUG(3, Putf("XR(%d) = '%s'\n", id, stringAtom(xr)));
      break;
    case XR_FUNCTOR:
    { atom_t name;
      int arity;

      id = ++loadedXRTableId;
      name = loadXR(fd);
      arity = getNum(fd);
      xr = (word) lookupFunctorDef(name, arity);
      DEBUG(3, Putf("XR(%d) = %s/%d\n", id, stringAtom(name), arity));
      break;
    }
    case XR_PRED:
    { functor_t f;
      atom_t mname;

      id = ++loadedXRTableId;
      f = (functor_t) loadXR(fd);
      mname = loadXR(fd);
      xr = (word) lookupProcedure(f, lookupModule(mname));
      DEBUG(3, Putf("XR(%d) = proc %s\n", id, procedureName((Procedure)xr)));
      break;
    }
    case XR_INT:
      return consInt(getNum(fd));
    case XR_BIGNUM:
      return globalLong(getstdw(fd));
    case XR_FLOAT:
      return globalReal(getReal(fd));
#if O_STRING
    case XR_STRING:
      return globalString(getString(fd));
#endif
    default:
    { xr = 0;				/* make gcc happy */
      fatalError("Illegal XR entry at index %d: %c", Stell(fd)-1, c);
    }
  }

  storeXrId(id, xr);

  return xr;
}


static word
loadXR(IOSTREAM *fd)
{ return loadXRc(Qgetc(fd), fd);
}


static void
do_load_qlf_term(IOSTREAM *fd, term_t vars[], term_t term)
{ int c = Qgetc(fd);

  if ( c == 'v' )
  { int id = getNum(fd);
    
    if ( vars[id] )
      PL_unify(term, vars[id]);
    else
    { vars[id] = PL_new_term_ref();
      PL_put_term(vars[id], term);
    }
  } else if ( c == 't' )
  { functor_t f = (functor_t) loadXR(fd);
    term_t c2 = PL_new_term_ref();
    int arity = arityFunctor(f);
    int n;

    PL_unify_functor(term, f);
    for(n=0; n < arity; n++)
    { PL_get_arg(n+1, term, c2);
      do_load_qlf_term(fd, vars, c2);
    }
  } else
  { _PL_unify_atomic(term, loadXRc(c, fd));
  }
}


static void
loadQlfTerm(term_t term, IOSTREAM *fd)
{ int nvars;
  Word vars;

  DEBUG(3, Putf("Loading from %d ...", Stell(fd)));
  if ( (nvars = getNum(fd)) )
  { term_t *v;
    int n;

    vars = alloca(nvars * sizeof(term_t));
    for(n=nvars, v=vars; n>0; n--, v++)
      *v = 0L;
  } else
    vars = NULL;

  PL_put_variable(term);
  do_load_qlf_term(fd, vars, term);
  DEBUG(3, Putf("Loaded "); pl_write(term); Putf(" to %d\n", Stell(fd)));
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Load a complete `wic' file.  `toplevel' tells  us  whether  we  are  the
toplevel  file  opened,  and thus should include other `wic' files or we
should ignore the include statements.  `load_options' tells us  to  only
load the options of the toplevel file.

All wic files loaded are appended in the  right  order  to  a  chain  of
`states'.  They are written to a new toplevel wic file by openWic().
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

int
loadWicFile(char *file, int flags)
{ IOSTREAM *fd;
  bool rval = TRUE;
  bool tablealloced = FALSE;
  char *owf = wicFile;

  if ((fd = Sopen_file(file, "rbr")) == (IOSTREAM *) NULL)
  { if ( flags & QLF_EXESTATE )
      rval = -1;
    else
      fatalError("Can't open %s: %s", file, OsError());
    rval = FALSE;
    goto out;
  }

  if ( flags & QLF_EXESTATE )
  { if ( Sseek(fd, -2 * (long)sizeof(long), SIO_SEEK_END) > 0 )
    { long size, magic;

      size = getstdw(fd);
      magic = getstdw(fd);
      if ( magic == QLFMAGICNUM )
	Sseek(fd, -2 * (long)sizeof(long) - size, SIO_SEEK_END);
      else
	rval = -1;
    } else
      rval = -1;
  }

  if ( rval < 0 )
    goto out;

  wicFile = file;
  notifyLoad(file);

  if ( (flags & QLF_TOPLEVEL) && !(flags & QLF_OPTIONS) )
  { pushXrIdTable();
    tablealloced    = TRUE;
  }

  if ( loadWicFd(file, fd, flags) == FALSE )
  { rval = FALSE;
    goto out;
  }
  if ( (flags & QLF_TOPLEVEL) && !(flags & QLF_OPTIONS) )
  { if (appendState(file) == FALSE)
    { rval = FALSE;
      goto out;
    }
  }

out:
  if (fd != (IOSTREAM *) NULL)
    Sclose(fd);
  if ( tablealloced )
  { popXrIdTable();
  }

  wicFile = owf;
  notifyLoaded();

  return rval;
}

#define QLF_MAX_HEADER_LINES 100

static bool
loadWicFd(char *file, IOSTREAM *fd, int flags)
{ char *s;
  Char c;
  int n;
  char mbuf[100];
  char *savedhome;

  for(n=0; n<QLF_MAX_HEADER_LINES; n++)
  { char line[256];

    if ( Sfgets(line, sizeof(line), fd) == 0 )
      return fatalError("%s is not a SWI-Prolog intermediate code file", file);
    if ( streq(line, "# End Header\n") )
      break;
  }
  if ( n >= QLF_MAX_HEADER_LINES )
    return fatalError("%s: header script too long (> 100 lines)", file);

  s = getMagicString(fd, mbuf, sizeof(mbuf));
  if ( !s || !streq(s, saveMagic) )
    return fatalError("%s is not a SWI-Prolog intermediate code file", file);

  if ( getNum(fd) < LOADVERSION )
  { fatalError("Intermediate code file %s has incompatible save version",
	       file);
    fail;
  }

  if ( (flags & QLF_OPTIONS) && (flags & QLF_TOPLEVEL) )
  { GD->options.localSize    = getNum(fd);
    GD->options.globalSize   = getNum(fd);
    GD->options.trailSize    = getNum(fd);
    GD->options.argumentSize = getNum(fd);
    GD->options.heapSize	 = getNum(fd);
    DEBUG(2,
	  Sdprintf("local=%ld, global=%ld, trail=%ld, arg=%ld, heap=%ld\n",
		   GD->options.localSize, GD->options.globalSize,
		   GD->options.trailSize, GD->options.argumentSize,
		   GD->options.heapSize
		  ));
    GD->options.goal         = store_string(getString(fd) );
    GD->options.topLevel     = store_string(getString(fd) );
    GD->options.initFile     = store_string(getString(fd) );

    succeed;
  } else
  { int n;
    for(n=0; n<5; n++)   getNum(fd);
    for(n=0; n<3; n++)   getString(fd);
  }

					/* fix paths for changed home */
  savedhome = getString(fd);
  if ( !systemDefaults.home || streq(savedhome, systemDefaults.home) )
  { qlf_has_moved = FALSE;
  } else
  { qlf_has_moved = TRUE;
    qlf_save_dir = store_string(savedhome);
    qlf_load_dir = systemDefaults.home;
  }

  for(;;)
  { c = Getc(fd);

    switch( c )
    { case EOF:
      case 'T':				/* trailer */
	succeed;
      case 'W':
	{ char *name;

	  name = store_string(getString(fd) );
	  if ( (flags & QLF_TOPLEVEL) )
	  { appendState(name);
	    pushXrIdTable();		/* has it's own id table! */
	    loadWicFile(name, 0);
	    popXrIdTable();
	  }
	  continue;
	}
      case 'X':
        break;
      default:
        { loadStatement(c, fd, FALSE);
	  continue;
	}
    }
  }
}


static bool
loadStatement(int c, IOSTREAM *fd, int skip)
{ switch(c)
  { case 'P':
      return loadPredicate(fd, skip);

    case 'O':
    { word mname = loadXR(fd);
      Module om = LD->modules.source;
      bool rval;

      LD->modules.source = lookupModule(mname);
      rval = loadPredicate(fd, skip);
      LD->modules.source = om;

      return rval;
    }
    case 'I':
      return loadImport(fd, skip);

    case 'D':
    { fid_t       cid = PL_open_foreign_frame();
      term_t goal = PL_new_term_ref();
      atom_t osf         = source_file_name;
      int  oln         = source_line_no;

      source_file_name = (currentSource ? currentSource->name : NULL_ATOM);
      source_line_no   = getNum(fd);
      
      loadQlfTerm(goal, fd);
      DEBUG(1, Sdprintf("Directive: ");
	       pl_write(goal);
	       Sdprintf("\n"));
      if ( !skip )
      { if ( !callProlog(MODULE_user, goal, FALSE) )
	{ Sfprintf(Serror,
		   "[WARNING: %s:%d: (loading %s) directive failed: ",
		   stringAtom(source_file_name), source_line_no, wicFile);
	  pl_write(goal);
	  Sfprintf(Serror, "]\n");
	}
      }
      PL_discard_foreign_frame(cid);
      
      source_file_name = osf;
      source_line_no   = oln;

      succeed;
    }	  

    case 'Q':
      return loadPart(fd, NULL, skip);

    case 'M':
      return loadInModule(fd, skip);

    default:
      return qlfLoadError(fd, "loadStatement()");
  }
}



static bool
loadPredicate(IOSTREAM *fd, int skip)
{ Procedure proc;
  Definition def;
  Clause clause;
  functor_t f = (functor_t) loadXR(fd);

  notifyPredicate(f);
  proc = lookupProcedure(f, LD->modules.source);
  DEBUG(3, Putf("Loading %s ", procedureName(proc)));
  def = proc->definition;
  def->indexPattern |= NEED_REINDEX;
  if ( !skip )
  { if ( SYSTEM_MODE )
    { set(def, SYSTEM|HIDE_CHILDS|LOCKED);
    }
    if ( currentSource )
      addProcedureSourceFile(currentSource, proc);
  }

  for(;;)
  { switch(Getc(fd) )
    { case 'X':
      { unsigned long pattern = getNum(fd);

	def->indexPattern = (pattern | NEED_REINDEX);

	DEBUG(3, Putf("ok\n"));
	succeed;
      }
      case 'C':
      { Code bp, ep;

	DEBUG(3, Sdprintf("."));
	clause = (Clause) allocHeap(sizeof(struct clause));
	clause->line_no = (unsigned short) getNum(fd);
	clearFlags(clause);
	clause->prolog_vars = (short) getNum(fd);
	clause->variables = (short) getNum(fd);
	if ( getNum(fd) == 0 )		/* 0: fact */
	  set(clause, UNIT_CLAUSE);
	clause->procedure = proc;
	clause->source_no = (currentSource ? currentSource->index : 0);
	clause->code_size = (short) getNum(fd);
	GD->statistics.codes += clause->code_size;
	clause->codes = (Code) allocHeap(clause->code_size * sizeof(code));

	bp = clause->codes;
	ep = bp + clause->code_size;

	while( bp < ep )
	{ code op = getNum(fd);
	  int n = 0;
	  int narg = codeTable[op].arguments;
	  
	  *bp++ = encode(op);
	  switch(codeTable[op].argtype)
	  { case CA1_PROC:
	    { switch(op)
	      { case I_CALL:
		case I_DEPART:
		{ functor_t f = (functor_t)loadXR(fd);
		  *bp++ = (word) lookupProcedure(f, LD->modules.source);
		  break;
		}
		default:
		  *bp++ = loadXR(fd);
	      }
	      n++;
	      break;
	    }
	    case CA1_FUNC:
	    case CA1_DATA:
	      *bp++ = loadXR(fd);
	      n++;
	      break;
	    case CA1_INTEGER:
	      *bp++ = getstdw(fd);
	      n++;
	      break;
	    case CA1_FLOAT:
	    { union { word w[2]; double f; } v;
	      v.f = getReal(fd);
	      *bp++ = v.w[0];
	      *bp++ = v.w[1];
	      n += 2;
	      break;
	    }
	    case CA1_STRING:		/* <n> chars */
	    { int l = getNum(fd);
	      int lw = (l+sizeof(word))/sizeof(word);
	      int pad = (lw*sizeof(word) - l);
	      char *s = (char *)&bp[1];

	      DEBUG(3, Sdprintf("String of %ld bytes\n", l));
	      *bp = mkStrHdr(lw, pad);
	      bp += lw;
	      *bp++ = 0L;
	      while(--l >= 0)
		*s++ = Getc(fd);
	      n++;
	      break;
	    }
	  }
	  for( ; n < narg; n++ )
	    *bp++ = getNum(fd);
	}

	if ( skip )
	  freeClause(clause);
	else
	{ assertProcedure(proc, clause, CL_END);
	}
      }
    }
  }
}


static bool
loadImport(IOSTREAM *fd, int skip)
{ Procedure proc = (Procedure) loadXR(fd);
  functor_t functor = proc->definition->functor->functor;
  Procedure old;

  if ( !skip )
  { DEBUG(3, Sdprintf("loadImport(): %s into %s\n",
		      procedureName(proc), stringAtom(LD->modules.source->name)));

    if ( (old = isCurrentProcedure(functor, LD->modules.source)) )
    { if ( old->definition == proc->definition )
	succeed;			/* already done this! */
      
      if ( !isDefinedProcedure(old) )
      { old->definition = proc->definition;
	succeed;
      }

      return warning("Failed to import %s into %s", 
		     procedureName(proc), 
		     stringAtom(LD->modules.source->name) );
    }
    addHTable(LD->modules.source->procedures, (void *)functor, proc);
  }

  succeed;
}


static bool
qlfLoadSource(IOSTREAM *fd)
{ char *str = getString(fd);
  long time = getstdw(fd);
  int issys = (Qgetc(fd) == 's') ? TRUE : FALSE;
  atom_t fname;

  if ( qlf_has_moved && strprefix(str, qlf_save_dir) )
  { char buf[MAXPATHLEN];
    char *s;

    strcpy(buf, qlf_load_dir);
    s = &buf[strlen(buf)];
    *s++ = '/';
    strcpy(s, &str[strlen(qlf_save_dir)]);
    fname = lookupAtom(canonisePath(buf));
  } else
    fname = lookupAtom(canonisePath(str));

  DEBUG(1, if ( !streq(stringAtom(fname), str) )
	     Sdprintf("Replaced path %s --> %s\n", str, stringAtom(fname)));

  currentSource = lookupSourceFile(fname);
  currentSource->time = time;
  currentSource->system = issys;
  startConsult(currentSource);

  succeed;
}


static bool
loadPart(IOSTREAM *fd, Module *module, int skip)
{ Module om     = LD->modules.source;
  SourceFile of = currentSource;
  int stchk     = debugstatus.styleCheck;

  switch(Qgetc(fd))
  { case 'M':
    { atom_t mname = loadXR(fd);

      switch( Qgetc(fd) )
      { case '-':
	{ LD->modules.source = lookupModule(mname);
					/* TBD: clear module? */
	  break;
	}
	case 'F':
	{ atom_t fname;
	  Module m;

	  qlfLoadSource(fd);
	  fname = currentSource->name;

	  m = lookupModule(mname);
	  if ( m->file && m->file != currentSource )
	  { warning("%s:\n\tmodule \"%s\" already loaded from \"%s\" (skipped)",
		    wicFile, stringAtom(m->name), stringAtom(m->file->name));
	    skip = TRUE;
	    LD->modules.source = m;
	  } else
	  { if ( !declareModule(mname, currentSource) )
	      fail;
	  }

	  if ( module )
	    *module = LD->modules.source;

	  for(;;)
	  { switch(Qgetc(fd))
	    { case 'E':
	      { functor_t f = (functor_t) loadXR(fd);

		if ( !skip )
		{ Procedure proc = lookupProcedure(f, LD->modules.source);

		  addHTable(LD->modules.source->public, (void *)f, proc);
		} else
		{ if ( !lookupHTable(m->public, (void *)f) )
		  { FunctorDef fd = valueFunctor(f);

		    warning("%s: skipped module \"%s\" lacks %s/%d",
			    wicFile,
			    stringAtom(m->name),
			    stringAtom(fd->name),
			    fd->arity);
		  }
		}

		continue;
	      }
	      case 'X':
		break;
	      default:
		return qlfLoadError(fd, "loadPart()");
	    }
	    break;
	  }
	  break;
	}
	default:
	  qlfLoadError(fd, "loadPart()");
	  break;
      }
      break;
    }
    case 'F':
    { qlfLoadSource(fd);

      if ( module )
	*module = NULL;

      break;
    }
    default:
      return qlfLoadError(fd, "loadPart()");
  }

  for(;;)
  { int c = Qgetc(fd);

    switch(c)
    { case 'X':
      { LD->modules.source = om;
	currentSource  = of;
	debugstatus.styleCheck = stchk;
	systemMode(debugstatus.styleCheck & DOLLAR_STYLE);

	succeed;
      }
      default:
	loadStatement(c, fd, skip);
    }
  }
}


static bool
loadInModule(IOSTREAM *fd, int skip)
{ word mname = loadXR(fd);
  Module om = LD->modules.source;

  LD->modules.source = lookupModule(mname);
  
  for(;;)
  { int c = Qgetc(fd);

    switch(c)
    { case 'X':
      { LD->modules.source = om;
	succeed;
      }
      default:
	loadStatement(c, fd, skip);
    }
  }
}


		 /*******************************
		 *	WRITING .QLF FILES	*
		 *******************************/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The code below handles the creation of `wic' files.  It offers a  number
of  predicates  which  enables  us  to write the compilation toplevel in
Prolog.

Note that we keep track of the `current procedure' to keep  all  clauses
of a predicate together.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

static Table savedXRTable;		/* saved XR entries */
static long  savedXRTableId;		/* next id */

static void
putString(register char *s, IOSTREAM *fd)
{ while(*s)
  { Putc(*s, fd);
    s++;
  }

  Putc(EOS, fd);
}


static void
putAtom(atom_t a, IOSTREAM *fd)
{ putString(stringAtom(a), fd);
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Number encoding:

	0 <= n <= 2^6	Direct storage in byte
	


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

static void
putNum(long int n, IOSTREAM *fd)
{ if ( n > (1L << 28) || n < -((1L << 28) - 1) )
    sysError("Argument to putNum() out of range: %ld", n);

  n &= ~0xc0000000;

  if ( n < (1L << 5) )
  { Putc((char) (n & 0x3f), fd);
  } else if ( n < (1L << 13) )
  { Putc((char) (((n >> 8) & 0x3f) | (1 << 6)), fd);
    Putc((char) (n & 0xff), fd);
  } else if ( n < (1L << 21) )
  { Putc((char) (((n >> 16) & 0x3f) | (2 << 6)), fd);
    Putc((char) ((n >> 8) & 0xff), fd);
    Putc((char) (n & 0xff), fd);
  } else
  { Putc((char) (((n >> 24) & 0x3f) | (3 << 6)), fd);
    Putc((char) ((n >> 16) & 0xff), fd);
    Putc((char) ((n >> 8) & 0xff), fd);
    Putc((char) (n & 0xff), fd);
    return;
  }
}


static void
putstdw(word w, IOSTREAM *fd)
{
#ifndef WORDS_BIGENDIAN
  union
  { word         l;
    unsigned char c[4];
  } cvrt;
  word rval;

  cvrt.l = w;
  rval = (cvrt.c[0] << 24) |
         (cvrt.c[1] << 16) |
	 (cvrt.c[2] << 8) |
	  cvrt.c[3];
  Sputw(rval, fd);
#else
  Sputw(w, fd);
#endif
}


static void
putReal(real f, IOSTREAM *fd)
{ word *s = (word *)&f;

  DEBUG(3, Sdprintf("putReal(%f)\n", f));

#ifndef WORDS_BIGENDIAN
  putstdw(s[0], fd);
  putstdw(s[1], fd);
#else
  putstdw(s[1], fd);
  putstdw(s[0], fd);
#endif
}


static void
saveXR(word xr, IOSTREAM *fd)
{ Symbol s;
  long id;

  if ( isTaggedInt(xr) )		/* TBD: switch */
  { Putc(XR_INT, fd);
    putNum(valInt(xr), fd);
    return;
  } else if ( isBignum(xr) )
  { Putc(XR_BIGNUM, fd);
    putstdw(valBignum(xr), fd);
    return;
  } else if ( isReal(xr) )
  { Putc(XR_FLOAT, fd);
    putReal(valReal(xr), fd);
    return;
#if O_STRING
  } else if ( isString(xr) )
  { Putc(XR_STRING, fd);
    putString(valString(xr), fd);
    return;
#endif /* O_STRING */
  }

  if ( (s = lookupHTable(savedXRTable, (void *)xr)) )
  { id = (int) s->value;
    Putc(XR_REF, fd);
    putNum(id, fd);
    return;
  }

  id = ++savedXRTableId;
  addHTable(savedXRTable, (void *)xr, (void *)id);

  if ( isAtom(xr) )
  { Putc(XR_ATOM, fd);
    putAtom(xr, fd);
    DEBUG(3, Putf("XR(%d) = '%s'\n", id, stringAtom(xr)));
    return;
  }

  assert(0);
}


static void
saveXRFunctor(functor_t f, IOSTREAM *fd)
{ Symbol s;
  long id;
  FunctorDef fdef;

  if ( (s = lookupHTable(savedXRTable, (void *)f)) )
  { id = (int) s->value;
    Putc(XR_REF, fd);
    putNum(id, fd);
    return;
  }

  id = ++savedXRTableId;
  addHTable(savedXRTable, (void *)f, (void *)id);
  fdef = valueFunctor(f);

  Putc(XR_FUNCTOR, fd);
  saveXR(fdef->name, fd);
  putNum(fdef->arity, fd);
  DEBUG(3, Putf("XR(%d) = %s/%d\n", id, stringAtom(fdef->name), fdef->arity));
}


static void
saveXRProc(Procedure p, IOSTREAM *fd)
{ Symbol s;
  long id;

  if ( (s = lookupHTable(savedXRTable, p)) )
  { id = (int) s->value;
    Putc(XR_REF, fd);
    putNum(id, fd);
    return;
  }

  id = ++savedXRTableId;
  addHTable(savedXRTable, p, (void *)id);

  Putc(XR_PRED, fd);
  saveXRFunctor(p->definition->functor->functor, fd);
  saveXR(p->definition->module->name, fd);
  DEBUG(3, Putf("XR(%d) = proc %s\n", id, procedureName(p)));
}


static void
do_save_qlf_term(Word t, IOSTREAM *fd)
{ deRef(t);

  if ( isTerm(*t) )
  { functor_t f = functorTerm(*t);

    if ( f == FUNCTOR_var1 )
    { int id = valInt(argTerm(*t, 0));

      Putc('v', fd);
      putNum(id, fd);
    } else
    { Word q = argTermP(*t, 0);
      int n, arity = arityFunctor(f);

      Putc('t', fd);
      saveXRFunctor(f, fd);
      for(n=0; n < arity; n++, q++)
	do_save_qlf_term(q, fd);
    }
  } else
  { assert(isAtomic(*t));
    saveXR(*t, fd);
  }
}


static void
saveQlfTerm(term_t t, IOSTREAM *fd)
{ int nvars;
  fid_t cid = PL_open_foreign_frame();

  DEBUG(3, Putf("Saving "); pl_write(t); Putf(" from %d ... ", Stell(fd)));
  nvars = numberVars(t, FUNCTOR_var1, 0);
  putNum(nvars, fd);
  do_save_qlf_term(valTermRef(t), fd);	/* TBD */
  DEBUG(3, Putf("to %d\n", Stell(fd)));

  PL_discard_foreign_frame(cid);
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
saveWicClause()  saves  a  clause  to  the  .qlf  file.   For  predicate
references of I_CALL and I_DEPART, we  cannot store the predicate itself
as this would lead to an inconsistency if   the .qlf file is loaded into
another context module.  Therefore we just   store the functor.  For now
this is ok as constructs of the   form  module:goal are translated using
the meta-call mechanism.  This needs consideration   if we optimise this
(which is not that likely as I	think  module:goal, where `module' is an
atom,  should  be  restricted  to  very    special  cases  and  toplevel
interaction.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

static void
saveWicClause(Clause clause, IOSTREAM *fd)
{ Code bp, ep;

  Putc('C', fd);
  putNum(clause->line_no, fd);
  putNum(clause->prolog_vars, fd);
  putNum(clause->variables, fd);
  putNum(true(clause, UNIT_CLAUSE) ? 0 : 1, fd);
  putNum(clause->code_size, fd);

  bp = clause->codes;
  ep = bp + clause->code_size;

  while( bp < ep )
  { code op = decode(*bp++);
    int n = 0;

    putNum(op, fd);
    switch(codeTable[op].argtype)
    { case CA1_PROC:
      { Procedure p = (Procedure) *bp++;
	n++;
	switch(op)
	{ case I_CALL:
	  case I_DEPART:
	    saveXRFunctor(p->definition->functor->functor, fd);
	    break;
	  default:
	    saveXRProc(p, fd);
	}
	break;
      }
      case CA1_FUNC:
      { functor_t f = (functor_t) *bp++;
	n++;
	saveXRFunctor(f, fd);
	break;
      }
      case CA1_DATA:
      { word xr = (word) *bp++;
	n++;
	saveXR(xr, fd);
	break;
      }
      case CA1_INTEGER:
      { putstdw(*bp++, fd);
	n++;
	break;
      }
      case CA1_FLOAT:
      { union { word w[2]; double f; } v;
	v.w[0] = *bp++;
	v.w[1] = *bp++;
	n += 2;
	putReal(v.f, fd);
	break;
      }
      case CA1_STRING:
      { word m = *bp;
	char *s = (char *)++bp;
	int wn = wsizeofInd(m);
	int l = wn*sizeof(word) - padHdr(m);
	bp += wn;

	putNum(l, fd);
	while(--l >= 0)
	  Putc(*s++&0xff, fd);
	n++;
	break;
      }
    }
    for( ; n < codeTable[op].arguments; n++ )
      putNum(*bp++, fd);
  }
}


		/********************************
		*         COMPILATION           *
		*********************************/

static long emulator_size;

static void
closeProcedureWic(IOSTREAM *fd)
{ if ( currentProc != (Procedure) NULL )
  { Putc('X', fd);
    putNum(currentProc->definition->indexPattern & ~NEED_REINDEX, fd);
    currentProc = (Procedure) NULL;
  }
}


static int
copyEmulator(IOSTREAM *out, IOSTREAM *in)
{ long emsize = -1;
  long sizepos;
  int n = 0, c;

  if ( (sizepos = Sseek(in, -2 * (long)sizeof(long), SIO_SEEK_END)) >= 0 )
  { long size, magic;

    size = getstdw(in);
    magic = getstdw(in);
    if ( magic == QLFMAGICNUM )
      emsize = sizepos - size;
    Sseek(in, 0, SIO_SEEK_SET);
  }

  while((c=Sgetc(in)) != EOF && n++ != emsize)
    Sputc(c, out);

  emulator_size = n;

  succeed;
}

static const opt_spec save_options[] = 
{ { ATOM_local,      OPT_INT },
  { ATOM_global,     OPT_INT },
  { ATOM_trail,	     OPT_INT },
  { ATOM_argument,   OPT_INT },
  { ATOM_goal,       OPT_STRING },
  { ATOM_toplevel,   OPT_STRING },
  { ATOM_init_file,  OPT_STRING },
  { ATOM_tty,	     OPT_BOOL },
  { ATOM_stand_alone,OPT_BOOL },
  { NULL_ATOM,	     0 }
};


static bool
openWic(const char *file, term_t args)
{ char *exec;
  char tmp[MAXPATHLEN];

  int   localSize    = GD->options.localSize;
  int   globalSize   = GD->options.globalSize;
  int   trailSize    = GD->options.trailSize;
  int   argumentSize = GD->options.argumentSize;
  int	heapSize     = GD->options.heapSize;
  char *goal         = GD->options.goal;
  char *topLevel     = GD->options.topLevel;
  char *initFile     = GD->options.initFile;
  bool  standalone   = FALSE;

  if ( args )
  { TRY(scan_options(args, 0, ATOM_save_option, save_options,
		     &localSize,
		     &globalSize,
		     &trailSize,
		     &argumentSize,
		     &goal,
		     &topLevel,
		     &initFile,
		     NULL,
		     &standalone));
  }

  wicFile = (char *) file;

  DEBUG(1, Sdprintf("Open compiler output file %s\n", file));
  if ( (wicFd = Sopen_file(file, "wbr")) == (IOSTREAM *)NULL )
    return warning("Can not open %s: %s", file, OsError());
  mkWicFile = wicFile;
  DEBUG(1, Sdprintf("Searching for executable\n"));
  if ( loaderstatus.restored_state )
  { exec = stringAtom(loaderstatus.restored_state);
  } else
  { TRY( getSymbols() );
    exec = stringAtom(loaderstatus.orgsymbolfile);
  }
  DEBUG(1, Sdprintf("Executable = %s\n", exec));
  if ( !(exec = OsPath(AbsoluteFile(exec, tmp), tmp)) )
    fail;
  emulator_size = 0;
  if ( standalone )
  { IOSTREAM *exefd;

    DEBUG(1, Sdprintf("Including executable\n", exec));
    if ( (exefd = Sopen_file(exec, "rbr")) != NULL )
    { copyEmulator(wicFd, exefd);
    } else
      warning("Can not read emulator %s --- ignoring stand_alone(on)", exec);
  }      

  DEBUG(1, Sdprintf("Expanded executable = %s\n", exec));
/*Sfprintf(wicFd, "#!%s -x\n", exec);*/
#if OS2
  Sfprintf(wicFd, "/* Compiled SWI-Prolog Program */\r\n'@ECHO OFF'\r\nparse source . . name\r\n\"%s -x \" name arg(1)\r\nexit\r\n", exec);
#else
  Sfprintf(wicFd, "#!/bin/sh\n");
  Sfprintf(wicFd, "# SWI-Prolog version: %d.%d.%d\n",
	   PLVERSION/10000,
	   (PLVERSION/100)%100,
	   PLVERSION%100);
  Sfprintf(wicFd, "# SWI-Prolog save-version: %d\n", VERSION);
  Sfprintf(wicFd, "exec ${SWIPL-%s} -x $0 \"$@\"\n", exec);
  Sfprintf(wicFd, "# End Header\n");
#endif /* OS2 */
  DEBUG(2, Sdprintf("Magic  ...\n"));
  putString( saveMagic,            wicFd);
  DEBUG(2, Sdprintf("Numeric options ...\n"));
  putNum(   VERSION,              wicFd);
  putNum(   localSize,    	  wicFd);
  putNum(   globalSize,   	  wicFd);
  putNum(   trailSize,    	  wicFd);
  putNum(   argumentSize, 	  wicFd);
  putNum(   heapSize,		  wicFd);
  DEBUG(2, Sdprintf("String options ...\n"));
  putString(goal,          	  wicFd);
  putString(topLevel,      	  wicFd);
  putString(initFile, 	   	  wicFd);
  if ( systemDefaults.home )
    putString(systemDefaults.home,  wicFd);
  else
    putString("<no home>",  wicFd);

  currentProc    = (Procedure) NULL;
  currentSource  = (SourceFile) NULL;
  savedXRTable   = newHTable(256);
  savedXRTableId = 0;

  DEBUG(2, Sdprintf("Header complete ...\n"));
  succeed;
}  


static void
writeTrailer(IOSTREAM *fd)
{ long size = Stell(fd) - emulator_size;

  Putc('T', fd);
  putstdw(size, fd);
  putstdw(QLFMAGICNUM, fd);
}


static bool
closeWic()
{ bool rval;

  if (wicFd == (IOSTREAM *) NULL)
    fail;

  closeProcedureWic(wicFd);
  Putc('X', wicFd);
  destroyHTable(savedXRTable);
  savedXRTable = NULL;
  writeTrailer(wicFd);
  Sclose(wicFd);
  rval = MarkExecutable(wicFile);

  wicFd = NULL;
  wicFile = NULL;
  mkWicFile = NULL;

  return rval;
}

static bool
addClauseWic(term_t term, atom_t file)
{ Clause clause;
  sourceloc loc;

  loc.file = file;
  loc.line = source_line_no;

  if ( (clause = assert_term(term, CL_END, &loc)) )
  { IOSTREAM *s = wicFd;

    DEBUG(3, Sdprintf("WAM code:\n");
	     wamListClause(clause));

    if (clause->procedure != currentProc)
    { closeProcedureWic(s);
      currentProc = clause->procedure;

      if ( clause->procedure->definition->module != LD->modules.source )
      { Putc('O', s);
	saveXR(clause->procedure->definition->module->name, s);
      } else
      { Putc('P', s);
      }

      saveXRFunctor(currentProc->definition->functor->functor, s);
    }
    saveWicClause(clause, s);
    succeed;
  }

  Sdprintf("Failed to compile: "); pl_write(term); Sdprintf("\n");
  fail;
}

static bool
addDirectiveWic(term_t term, IOSTREAM *fd)
{ closeProcedureWic(fd);
  Putc('D', fd);
  putNum(source_line_no, fd);
  saveQlfTerm(term, fd);

  succeed;
}  


static bool
importWic(Procedure proc, IOSTREAM *fd)
{ closeProcedureWic(fd);

  Putc('I', fd);
  saveXRProc(proc, fd);

  succeed;
}

		 /*******************************
		 *	    PART MARKS		*
		 *******************************/

typedef struct source_mark *SourceMark;

struct source_mark
{ long	   file_index;
  SourceMark next;
};

static SourceMark source_mark_head = NULL;
static SourceMark source_mark_tail = NULL;

static void
initSourceMarks()
{ source_mark_head = source_mark_tail = NULL;
}


static void
sourceMark(IOSTREAM *s)
{ SourceMark pm = allocHeap(sizeof(struct source_mark));

  pm->file_index = Stell(s);
  pm->next = NULL;
  if ( source_mark_tail )
  { source_mark_tail->next = pm;
    source_mark_tail = pm;
  } else
  { source_mark_tail = source_mark_head = pm;
  }
}


static int
writeSourceMarks(IOSTREAM *s)
{ int n = 0;
  SourceMark pn, pm = source_mark_head;

  DEBUG(1, Sdprintf("Writing source marks: "));

  for( ; pm; pm = pn )
  { pn = pm->next;

    DEBUG(1, Sdprintf(" %d", pm->file_index));
    putstdw(pm->file_index, s);
    freeHeap(pm, sizeof(*pm));
    n++;
  }
  
  DEBUG(1, Sdprintf("Written %d marks\n", n));
  putstdw(n, s);

  return 0;
}


static int
qlfSourceInfo(IOSTREAM *s, long offset, term_t list)
{ char *str;
  term_t head = PL_new_term_ref();

  if ( Sseek(s, offset, SIO_SEEK_SET) != offset )
    return warning("%s: seek failed: %s", wicFile, OsError());
  if ( Getc(s) != 'F' || !(str=getString(s)) )
    return warning("QLF format error");
  
  return PL_unify_list(list, head, list) &&
         PL_unify_atom_chars(head, str);
}


static word
qlfInfo(const char *file,
	term_t cversion, term_t version,
	term_t files0)
{ IOSTREAM *s = NULL;
  int lversion;
  int nqlf, i;
  long *qlfstart = NULL;
  word rval = TRUE;
  term_t files = PL_copy_term_ref(files0);

  TRY(PL_unify_integer(cversion, VERSION));

  wicFile = (char *)file;

  if ( !(s = Sopen_file(file, "rbr")) )
    return warning("Can't open %s: %s", file, OsError());

  if ( !(lversion = qlfVersion(s)) )
  { Sclose(s);
    fail;
  }
    
  TRY(PL_unify_integer(version, lversion));

  if ( Sseek(s, -(int)sizeof(long), SIO_SEEK_END) < 0 )
    return warning("qlf_info/3: seek failed: %s", OsError());
  nqlf = getstdw(s);
  DEBUG(1, Sdprintf("Found %d sources at %d starting at", nqlf, rval));
  qlfstart = (long *)allocHeap(sizeof(long) * nqlf);
  Sseek(s, -(int)sizeof(long) * (nqlf+1), SIO_SEEK_END);
  for(i=0; i<nqlf; i++)
  { qlfstart[i] = getstdw(s);
    DEBUG(1, Sdprintf(" %d", qlfstart[i]));
  }
  DEBUG(1, Sdprintf("\n"));

  for(i=0; i<nqlf; i++)
  { if ( !qlfSourceInfo(s, qlfstart[i], files) )
    { rval = FALSE;
      goto out;
    }
  }

  rval = PL_unify_nil(files);

out:
  if ( qlfstart )
    freeHeap(qlfstart, sizeof(long) * nqlf);
  if ( s )
    Sclose(s);

  return rval;
}



word
pl_qlf_info(term_t file,
	    term_t cversion, term_t version,
	    term_t files)
{ char *name;
  char buf[MAXPATHLEN];

  if ( !(name = PL_get_filename(file, buf, sizeof(buf))) )
    return warning("qlf_info/3: instantiation fault");

   return qlfInfo(name, cversion, version, files);
}



		 /*******************************
		 *	NEW MODULE SUPPORT	*
		 *******************************/

static bool
qlfOpen(atom_t name)
{ char *absname;
  char tmp[MAXPATHLEN];

  wicFile = stringAtom(name);
  if ( !(absname = AbsoluteFile(wicFile, tmp)) )
    fail;

  if ( !(wicFd = Sopen_file(wicFile, "wbr")) )
    return warning("qlf_open/1: can't open %s: %s", wicFile, OsError());

  mkWicFile = wicFile;

  putString(qlfMagic, wicFd);
  putNum(VERSION, wicFd);
  putString(absname, wicFd);

  currentProc    = (Procedure) NULL;
  currentSource  = (SourceFile) NULL;
  savedXRTable   = newHTable(256);
  savedXRTableId = 0;
  initSourceMarks();

  succeed;
}


static bool
qlfClose()
{ IOSTREAM *fd = wicFd;

  closeProcedureWic(fd);
  writeSourceMarks(fd);
  Sclose(fd);
  wicFd = NULL;
  mkWicFile = NULL;

  destroyHTable(savedXRTable);
  savedXRTable = NULL;
  
  succeed;
}


static int
qlfVersion(IOSTREAM *s)
{ char mbuf[100];
  char *magic;

  if ( !(magic = getMagicString(s, mbuf, sizeof(mbuf))) ||
       !streq(magic, qlfMagic) )
  { Sclose(s);
    return warning("%s: not a SWI-Prolog .qlf file", wicFile);
  }

  return getNum(s);
}



static bool
qlfLoad(char *file, Module *module)
{ IOSTREAM *fd;
  bool rval;
  int lversion;
  char *absloadname;
  char *abssavename;
  char tmp[MAXPATHLEN];
  
  wicFile = file;
  if ( !(absloadname = AbsoluteFile(wicFile, tmp)) )
    fail;
  
  if ( !(fd = Sopen_file(file, "rbr")) )
    return warning("$qlf_load/1: can't open %s: %s", file, OsError());
  if ( !(lversion = qlfVersion(fd)) || lversion < LOADVERSION )
  { Sclose(fd);
    if ( lversion )
      warning("$qlf_load/1: %s bad version (file version = %d, prolog = %d)",
	      wicFile, lversion, VERSION);
    fail;
  }

  abssavename = getString(fd);
  if ( streq(absloadname, abssavename) )
  { qlf_has_moved = FALSE;
    qlf_load_dir = qlf_save_dir = NULL;
  } else
  { char tmp[MAXPATHLEN];
    qlf_has_moved = TRUE;
    qlf_load_dir = stringAtom(lookupAtom(DirName(absloadname, tmp)));
    qlf_save_dir = stringAtom(lookupAtom(DirName(abssavename, tmp)));
  }

  if ( Qgetc(fd) != 'Q' )
    return qlfLoadError(fd, "qlfLoad()");

  pushXrIdTable();
  rval = loadPart(fd, module, FALSE);
  popXrIdTable();

  Sclose(fd);

  return rval;
}


static bool
qlfSaveSource(SourceFile f, IOSTREAM *fd)
{ sourceMark(fd);
  Putc('F', fd);
  putAtom(f->name, fd);
  putstdw(f->time, fd);
  Putc(f->system ? 's' : 'u', fd);

  currentSource = f;

  succeed;
}


static bool
qlfStartModule(Module m, IOSTREAM *fd)
{ Symbol s;

  closeProcedureWic(fd);
  Putc('Q', fd);
  Putc('M', fd);
  saveXR(m->name, fd);
  if ( m->file )
    qlfSaveSource(m->file, fd);
  else
    Putc('-', fd);

  for_table(s, m->public)
  { functor_t f = (functor_t)s->name;

    Putc('E', fd);
    saveXRFunctor(f, fd);
  } 

  Putc('X', fd);

  succeed;
}


static bool
qlfStartSubModule(Module m, IOSTREAM *fd)
{ closeProcedureWic(fd);
  Putc('M', fd);
  saveXR(m->name, fd);

  succeed;
}


static bool
qlfStartFile(SourceFile f, IOSTREAM *fd)
{ closeProcedureWic(fd);
  Putc('Q', fd);
  qlfSaveSource(f, fd);

  succeed;
}


static bool
qlfEndPart(IOSTREAM  *fd)
{ closeProcedureWic(fd);
  Putc('X', fd);

  succeed;
}


word
pl_qlf_start_module(term_t name)
{ if ( wicFd )
  { Module m;

    if ( !PL_get_module(name, &m) )
      return warning("qlf_start_module/1: argument must be an atom");
  
    return qlfStartModule(m, wicFd);
  }

  succeed;
}


word
pl_qlf_start_sub_module(term_t name)
{ if ( wicFd )
  { Module m;

    if ( !PL_get_module(name, &m) )
      return warning("qlf_start_sub_module/1: argument must be an atom");
  
    return qlfStartSubModule(m, wicFd);
  }

  succeed;
}


word
pl_qlf_start_file(term_t name)
{ if ( wicFd )
  { atom_t a;

    if ( !PL_get_atom(name, &a) )
      return warning("qlf_start_file/1: argument must be an atom");
  
    return qlfStartFile(lookupSourceFile(a), wicFd);
  }

  succeed;
}


word
pl_qlf_end_part()
{ if ( wicFd )
  { return qlfEndPart(wicFd);
  }

  succeed;
}


word
pl_qlf_open(term_t file)
{ atom_t a;

  if ( PL_get_atom(file, &a) )
    return qlfOpen(a);

  return warning("qlf_open/1: instantiation fault");
}


word
pl_qlf_close()
{ return qlfClose();
}


word
pl_qlf_load(term_t file, term_t module)
{ Module m, oldsrc = LD->modules.source;
  char fbuf[MAXPATHLEN];
  char *fn;
  bool rval;
  term_t name = PL_new_term_ref();

  if ( !PL_strip_module(file, &m, name) )
    fail;
  if ( !(fn = PL_get_filename(name, fbuf, sizeof(fbuf))) )
    return warning("$qlf_load/2: instantiation fault");

  rval = qlfLoad(fn, &m);
  LD->modules.source = oldsrc;

  if ( !rval )
    fail;

  if ( m )
    return PL_unify_atom(module, m->name);
  else
    return PL_unify_integer(module, 0);
}


		/********************************
		*        PROLOG SUPPORT         *
		*********************************/

word
pl_open_wic(term_t name, term_t options)
{ char *file;
  atom_t fname;

  if ( !(file = PL_get_filename(name, NULL, 0)) )
    fail;
  fname = lookupAtom(file);	/* ensure persistency */

  return openWic(stringAtom(fname), options);
}

word
pl_qlf_put_states()
{ if ( wicFd )
    putStates(wicFd);

  succeed;
}


word
pl_close_wic()
{ return closeWic();
}


word
pl_add_directive_wic(term_t term)
{ if ( wicFd )
  { if ( PL_is_variable(term) )
      return warning("$add_directive_wic/1: directive is a variable");

    return addDirectiveWic(term, wicFd);
  }

  succeed;
}


word
pl_import_wic(term_t module, term_t head)
{ if ( wicFd )
  { Module m;
    functor_t f;

    if ( !PL_get_module(module, &m) ||
	 !PL_get_functor(head, &f) )
      return warning("$import_wic/3: instantiation fault");

    return importWic(lookupProcedure(f, m), wicFd);
  }

  succeed;
}


word
pl_qlf_assert_clause(term_t ref)
{ if ( wicFd )
  { Clause clause;
    IOSTREAM *s = wicFd;

    if ( !PL_get_pointer(ref, (void **)&clause) ||
	 !inCore(clause) || !isClause(clause) )
      return warning("$qlf_assert_clause/1: Invalid clause reference");

    if ( clause->procedure != currentProc )
    { closeProcedureWic(s);
      currentProc = clause->procedure;

      if ( clause->procedure->definition->module != LD->modules.source )
      { Putc('O', s);
	saveXR(clause->procedure->definition->module->name, s);
      } else
      { Putc('P', s);
      }

      saveXRFunctor(currentProc->definition->functor->functor, s);
    }

    saveWicClause(clause, s);
  }

  succeed;
}


		/********************************
		*     BOOTSTRAP COMPILATION     *
		*********************************/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The code below offers a restricted compilation  toplevel  used  for  the
bootstrap  compilation  (-b  option).  It handles most things the Prolog
defined compiler handles as well, except:

  - Be carefull to define  a  predicate  first  before  using  it  as  a
    directive
  - It does not offer `consult', `ensure_loaded' or the  list  notation.
    (there is no way to include other files).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Check whether clause is  of  the  form   :-  directive.  If  so, put the
directive in directive and succeed. If the   term has no explicit module
tag, add one from the current source-module.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

static int
directiveClause(term_t directive, term_t clause, const char *functor)
{ atom_t name;
  int arity;
  term_t d0 = PL_new_term_ref();
  functor_t f;

  if ( !PL_get_name_arity(clause, &name, &arity) ||
       arity != 1 ||
       !streq(stringAtom(name), functor) )
    fail;

  PL_get_arg(1, clause, d0);
  if ( PL_get_functor(d0, &f) && f == FUNCTOR_module2 )
    PL_put_term(directive, d0);
  else
  { term_t m = PL_new_term_ref();

    PL_put_atom(m, LD->modules.source->name);
    PL_cons_functor(directive, FUNCTOR_module2, m, d0);
  }

  succeed;
}

/*  Compile an entire file into intermediate code.

 ** Thu Apr 28 13:44:43 1988  jan@swivax.UUCP (Jan Wielemaker)  */

static bool
compileFile(char *file)
{ char tmp[MAXPATHLEN];
  char *path;
  term_t f = PL_new_term_ref();
  atom_t nf;

  DEBUG(1, Sdprintf("Boot compilation of %s\n", file));
  if ( !(path = AbsoluteFile(file, tmp)) )
    fail;
  DEBUG(2, Sdprintf("Expanded to %s\n", path));

  nf = lookupAtom(path);
  PL_put_atom(f, nf);
  DEBUG(2, Sdprintf("Opening\n"));
  if ( !pl_see(f) )
    fail;
  DEBUG(2, Sdprintf("pl_start_consult()\n"));
  pl_start_consult(f);
  qlfStartFile(lookupSourceFile(nf), wicFd);
  
  for(;;)
  { fid_t            cid = PL_open_foreign_frame();
    term_t         t = PL_new_term_ref();
    term_t directive = PL_new_term_ref();
    atom_t eof;

    DEBUG(2, Sdprintf("pl_read_clause() -> "));
    PL_put_variable(t);
    if ( !pl_read_clause(t) )		/* syntax error */
      continue;
    if ( PL_get_atom(t, &eof) && eof == ATOM_end_of_file )
      break;

    DEBUG(2, pl_write(t); pl_nl());

    if ( directiveClause(directive, t, ":-") )
    { DEBUG(1, Putf(":- "); pl_write(directive); Putf(".\n") );
      addDirectiveWic(directive, wicFd);
      callProlog(MODULE_user, directive, FALSE);
    } else if ( directiveClause(directive, t, "$:-") )
    { DEBUG(1, Putf("$:- "); pl_write(directive); Putf(".\n") );
      callProlog(MODULE_user, directive, FALSE);
    } else
      addClauseWic(t, nf);

    PL_discard_foreign_frame(cid);
  }

  qlfEndPart(wicFd);
  pl_seen();

  succeed;
}

bool
compileFileList(char *out, int argc, char **argv)
{ newOp("$:-", OP_FX, 1200);
  TRY(openWic(out, 0) );
  
  systemMode(TRUE);

  for(;argc > 0; argc--, argv++)
  { if (streq(argv[0], "-c") )
      break;
    compileFile(argv[0]);
  }

  LD->autoload = TRUE;
  systemMode(FALSE);

  { predicate_t pred = PL_predicate("$load_additional_boot_files", 0, "user");

    PL_call_predicate(MODULE_user, TRUE, pred, 0);
  }

  return closeWic();
}


		/********************************
		*         STATE LISTS           *
		*********************************/

/*  Add a new state to the chain of states this Prolog session is build
    from. The file name is made absolute to avoid directory problems
    with incremental loading.
*/

static bool
appendState(const char *name)
{ State state, st;
  char *absolute;
  char tmp[MAXPATHLEN];

  if ((absolute = AbsoluteFile(name, tmp)) == (char *) NULL)
    return warning("invalid file specification: %s", name);

  state = (State) allocHeap(sizeof(struct state) );
  state->next = (State) NULL;
  state->name = store_string(absolute);

  if ( !GD->stateList )
  { GD->stateList = state;
    succeed;
  }
  for(st = GD->stateList; st->next; st = st->next) ;
  st->next = state;

  succeed;
}

/*  Add 'W' statements to the WIC file for each file in the state list.
*/

static bool
putStates(IOSTREAM *fd)
{ State st;

  for(st = GD->stateList; st; st = st->next)
  { Putc('W', fd);
    putString(st->name, fd);
  }

  succeed;
}
