/*  $Id: pl-proc.c,v 1.55 1998/02/18 13:57:16 jan Exp $

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

    Purpose: Procedure (re) allocation
*/

/*#define O_DEBUG 1*/
#include "pl-incl.h"

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
General  handling  of  procedures:  creation;  adding/removing  clauses;
finding source files, etc.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

forwards void		resetReferencesModule(Module);
forwards void		resetProcedure(Procedure proc);
forwards SourceFile	indexToSourceFile(int index);

SourceFile 	sourceFileTable = (SourceFile) NULL;
SourceFile 	tailSourceFileTable = (SourceFile) NULL;
static void	removeClausesProcedure(Procedure proc, int sfindex);

Procedure
lookupProcedure(functor_t f, Module m)
{ Procedure proc;
  register Definition def;
  Symbol s;
  
  if ( (s = lookupHTable(m->procedures, (void *)f)) )
    return (Procedure) s->value;

  proc = (Procedure)  allocHeap(sizeof(struct procedure));
  def  = (Definition) allocHeap(sizeof(struct definition));
  proc->type = PROCEDURE_TYPE;
  proc->definition = def;
  def->functor = valueFunctor(f);
  def->module  = m;
  addHTable(m->procedures, (void *)f, proc);
  GD->statistics.predicates++;

  def->definition.clauses = NULL;
  def->lastClause = NULL;
  def->hash_info = NULL;
#ifdef O_PROFILE
  def->profile_ticks = 0;
  def->profile_calls = 0;
  def->profile_redos = 0;
  def->profile_fails = 0;
#endif /* O_PROFILE */
  clearFlags(def);
  def->references = 0;
  resetProcedure(proc);

  return proc;
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
resetProcedure() is called  by  lookupProcedure()   for  new  ones,  and
abolishProcedure() by abolish/2. In the latter   case, abolish may leave
dirty clauses when called on a   running predicate. Hence, NEEDSCLAUSEGC
should be retained. Bug found by Paulo Moura, LogTalk developer.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

static void
resetProcedure(Procedure proc)
{ register Definition def = proc->definition;

  def->flags ^= def->flags & ~(SPY_ME|NEEDSCLAUSEGC);
  set(def, TRACE_ME);
  def->indexCardinality = 0;
  def->number_of_clauses = 0;
  if ( def->functor->arity == 0 )
  { def->indexPattern = 0x0;
  } else
  { def->indexPattern = (0x0 | NEED_REINDEX);
    set(def, AUTOINDEX);
  }
  
  if ( def->hash_info && def->references == 0 )
  { unallocClauseIndexTable(def->hash_info);
    def->hash_info = NULL;
  }
}

Procedure
isCurrentProcedure(functor_t f, Module m)
{ Symbol s;

  if ( (s = lookupHTable(m->procedures, (void *)f)) )
    return (Procedure) s->value;

  return NULL;
}

bool
isDefinedProcedure(Procedure proc)
{ Definition def = proc->definition;

  if ( true(def, DYNAMIC|FOREIGN) )
    succeed;

  if ( def->definition.clauses && false(def, FOREIGN) )
  { ClauseRef c;

    if ( false(def, NEEDSCLAUSEGC) )
      succeed;
    
    for(c = def->definition.clauses; c; c = c->next)
    { Clause cl = c->clause;

      if ( false(cl, ERASED) )
	succeed;
    }
  }

  fail;
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Find a procedure for defining it.  Here   we check whether the procedure
to be defined is a system predicate.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

Procedure
lookupProcedureToDefine(functor_t def, Module m)
{ Procedure proc;

  if ( (proc = isCurrentProcedure(def, m)) && false(proc->definition, SYSTEM) )
    return proc;

  if ( !SYSTEM_MODE &&
       MODULE_system &&
       (proc=isCurrentProcedure(def, MODULE_system)) &&
       true(proc->definition, LOCKED) &&
       false(proc->definition, DYNAMIC) )
  { warning("Attempt to redefine a system predicate: %s/%d\n"
	    "\tUse :- redefine_system_predicate(+Head) if this is intended",
	    stringAtom(proc->definition->functor->name),
	    proc->definition->functor->arity);
    return NULL;
  }
 
  return lookupProcedure(def, m);
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
get_functor() translates term  of  the   format  +Name/+Arity  into  the
internal functor represenation. It fails and  raises an exception on the
various possible format or represenation errors.  ISO compliant.

The return value is 1 normally, -1  if no functor exists and GF_EXISTING
is defined, and 0 if an error was raised.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#define GF_EXISTING	1
#define GF_PROCEDURE	2		/* check for max arity */

static int
get_functor(term_t descr, functor_t *fdef, Module *m, term_t h, int how)
{ term_t head = PL_new_term_ref();

  if ( !PL_strip_module(descr, m, head) )
    fail;

  if ( PL_is_functor(head, FUNCTOR_divide2) )
  { term_t a = PL_new_term_ref();
    atom_t name;
    int arity;

    PL_get_arg(1, head, a);
    if ( PL_get_atom(a, &name) )
    { PL_get_arg(2, head, a);
      if ( PL_get_integer(a, &arity) )
      { if ( arity < 0 )
	{ return PL_error(NULL, 0, NULL, ERR_DOMAIN,
			  ATOM_not_less_than_zero, a);
	} else if ( (how&GF_PROCEDURE) && arity > MAXARITY )
	{ char buf[100];

	  return PL_error(NULL, 0,
			  tostr(buf, "limit is %d, request = %d",
				MAXARITY, arity),
			  ERR_REPRESENTATION, ATOM_max_arity);
	} else
	{ *fdef = PL_new_functor(name, arity);
	  
	  if ( h )
	    PL_put_term(h, head);
	  
	  succeed;
	}
      } else
      { if ( PL_is_variable(a) )
	  goto ierror;

	return PL_error(NULL, 0, NULL, ERR_TYPE, ATOM_integer, a);
      }
    } else
    { if ( PL_is_variable(a) )
	goto ierror;

      return PL_error(NULL, 0, NULL, ERR_TYPE, ATOM_atom, a);
    }
  } else if ( PL_get_functor(head, fdef) )
  { if ( h )
      PL_put_term(h, head);
	  
    succeed;
  } else
  { if ( PL_is_variable(head) )
    { ierror:
      return PL_error(NULL, 0, NULL, ERR_INSTANTIATION);
    } else
      return PL_error(NULL, 0, NULL, ERR_TYPE,
		      ATOM_predicate_indicator, head);
  }
}

      
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Get the specified procedure from a   Prolog  argument.  This argument is
either a head or a term of the form module:head.  If `create' is TRUE, a
procedure is created in the module.  Otherwise, the system traverses the
module-inheritance chain to find the existing procedure.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

int
get_procedure(term_t descr, Procedure *proc, term_t h, int how)
{ Module m = (Module) NULL;
  functor_t fdef;
  Procedure p;

  if ( (how&GP_NAMEARITY) )
  { if ( !get_functor(descr, &fdef, &m, h, GF_PROCEDURE) )
      fail;
  } else
  { term_t head = PL_new_term_ref();
    int arity;

    if ( !PL_strip_module(descr, &m, head) )
      fail;

    if ( h )
      PL_put_term(h, head);

    if ( !PL_get_functor(head, &fdef) )
      return warning("Illegal predicate specification");
    if ( (arity=arityFunctor(fdef)) > MAXARITY )
    { char buf[100];
      return PL_error(NULL, 0,
			  tostr(buf, "limit is %d, request = %d",
				MAXARITY, arity),
			  ERR_REPRESENTATION, ATOM_max_arity);
    }
  }
  
  switch( how & GP_HOW_MASK )
  { case GP_CREATE:
      *proc = lookupProcedure(fdef, m);
      break;
    case GP_FINDHERE:
      if ( (p = isCurrentProcedure(fdef, m)) )
      { *proc = p;
        break;
      }
      fail;
    case GP_FIND:
      for( ; m; m = m->super )
      { if ( (p = isCurrentProcedure(fdef, m)) )
	{ *proc = p;
	  goto out;
	}
      }
      fail;
    case GP_DEFINE:
      if ( (p = lookupProcedureToDefine(fdef, m)) )
      { *proc = p;
        break;
      }
      fail;
    case GP_RESOLVE:
      if ( (p = resolveProcedure(fdef, m)) )
      { *proc = p;
        break;
      }
      fail;
    default:
      assert(0);
  }

out:

  succeed;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
This function  implements  $c_current_predicate/2.   current_predicate/2
itself  is  written  in  Prolog, based on this function.  Having dynamic
linking from super modules and dynamic loading from the  libraries,  the
definition  of current predicate has become a difficult issue.  Normally
it is used for meta-programming and program analysis.  I think it should
succeed  for  each  predicate  that  can   be   called.    The   current
implementation  is VERY slow due to all Prolog overhead.  This should be
reconsidered and probably a large part of this function should be  moved
to C.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

word
pl_current_predicate(term_t name, term_t spec, word h)
{ atom_t n;
  functor_t f;
  Module m = (Module) NULL;
  Procedure proc;
  Symbol symb;
  term_t functor = PL_new_term_ref();

  if ( ForeignControl(h) == FRG_CUTTED )
    succeed;

  if ( !PL_strip_module(spec, &m, functor) )
    fail;

  if ( !PL_get_atom(name, &n) )
  { if ( PL_is_variable(name) )
      n = NULL_ATOM;
    else
      fail;
  }
  if ( !PL_get_functor(functor, &f) )
  { if ( PL_is_variable(functor) )
      f = 0;
    else
      fail;
  }

  if ( ForeignControl(h) == FRG_FIRST_CALL)
  { if ( f ) 
    { if ( (proc = isCurrentProcedure(f, m)) )
	return PL_unify_atom(name, nameFunctor(f));
      fail;
    }
    symb = firstHTable(m->procedures);
  } else
    symb = ForeignContextPtr(h);

  for(; symb; symb = nextHTable(m->procedures, symb) )
  { FunctorDef fdef;
    
    proc = (Procedure) symb->value;
    fdef = proc->definition->functor;

    if ( (n && n != fdef->name) ||
	 !PL_unify_atom(name, fdef->name) ||
	 !PL_unify_functor(functor, fdef->functor) )
      continue;

    if ( (symb = nextHTable(m->procedures, symb)) )
      ForeignRedoPtr(symb);

    succeed;
  }

  fail;
}


ClauseRef
newClauseRef(Clause clause)
{ ClauseRef cref = allocHeap(sizeof(struct clause_ref));
  
  cref->clause = clause;
  cref->next   = NULL;

  return cref;
}


void
freeClauseRef(ClauseRef cref)
{ freeHeap(cref, sizeof(struct clause_ref));
}


/*  Assert a clause to a procedure. Where askes to assert either at the
    head or at the tail of the clause list.

 ** Fri Apr 29 12:44:08 1988  jan@swivax.UUCP (Jan Wielemaker)  */

bool
assertProcedure(Procedure proc, Clause clause, int where)
{ Definition def = proc->definition;
  ClauseRef cref = newClauseRef(clause);

  startCritical;

  if ( def->references && (debugstatus.styleCheck & DYNAMIC_STYLE) )
    warning("assert/[1,2]: %s has %d references",
	    predicateName(def), def->references);

  if ( !def->lastClause )
  { def->definition.clauses = def->lastClause = cref;
  } else if ( where == CL_START )
  { cref->next = def->definition.clauses;
    def->definition.clauses = cref;
  } else
  { ClauseRef last = def->lastClause;

    last->next = cref;
    def->lastClause = cref;
  }

  def->number_of_clauses++;

  if ( def->hash_info )
    addClauseToIndex(def, clause, where);
  else
  { if ( def->number_of_clauses == 25 && true(def, AUTOINDEX) )
      def->indexPattern |= NEED_REINDEX;
  }

  endCritical;  

  succeed;
}

/*  Abolish a procedure.  Referenced  clauses  are   unlinked  and left
    dangling in the dark until the procedure referencing it deletes it.

    Since we have a foreign language interface we will allow to  abolish
    foreign  predicates  as  well.  Permission testing should be done by
    the caller.

 ** Sun Apr 17 16:18:50 1988  jan@swivax.UUCP (Jan Wielemaker)  */

bool
abolishProcedure(Procedure proc, Module module)
{ register Definition def = proc->definition;

  if ( def->module != module )		/* imported predicate; remove link */
  { Definition ndef	     = allocHeap(sizeof(struct definition));

    proc->definition         = ndef;
    ndef->functor            = def->functor;
    ndef->module             = module;
    ndef->definition.clauses = NULL;
    ndef->lastClause         = NULL;
#ifdef O_PROFILE
    ndef->profile_ticks      = 0;
    ndef->profile_calls      = 0;
    ndef->profile_redos      = 0;
    ndef->profile_fails      = 0;
#endif /* O_PROFILE */
    resetProcedure(proc);

    succeed;
  }

  if ( true(def, FOREIGN) )
  { startCritical;
    def->definition.clauses = def->lastClause = NULL;
    resetProcedure(proc);
    endCritical;

    succeed;
  }

  removeClausesProcedure(proc, 0);
  resetProcedure(proc);

  succeed;
}


static void
removeClausesProcedure(Procedure proc, int sfindex)
{ Definition def = proc->definition;
  ClauseRef c;

  enterDefinition(def);

  for(c = def->definition.clauses; c; c = c->next)
  { Clause cl = c->clause;

    if ( (sfindex == 0 || sfindex == cl->source_no) && false(cl, ERASED) )
    { set(cl, ERASED);
      set(def, NEEDSCLAUSEGC);
      def->number_of_clauses--;
    } 
  }
  if ( def->hash_info )
    def->hash_info->alldirty = TRUE;

  leaveDefinition(def);
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Retract a clause from a procedure. When   a clause without references is
retracted it is actually removed from the  heap, otherwise the clause is
unlinked and marked as `erased'. Its next   pointer will not be changed.
to avoid the follow up clause  to  be   destroyed  it  is given an extra
reference.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

bool
retractClauseProcedure(Procedure proc, Clause clause)
{ Definition def = proc->definition;

  if ( true(clause, ERASED) )
    succeed;

  if ( def->references )
  { set(clause, ERASED);
    set(def, NEEDSCLAUSEGC);
    if ( def->hash_info )
      markDirtyClauseIndex(def->hash_info, clause);
    def->number_of_clauses--;
    succeed;
  } else
  { ClauseRef prev = NULL;
    ClauseRef c;
    bool rval = FALSE;

    startCritical;

    if ( def->hash_info )
      delClauseFromIndex(def->hash_info, clause);

    for(c = def->definition.clauses; c; prev = c, c = c->next)
    { if ( c->clause == clause )
      { if ( !prev )
	{ def->definition.clauses = c->next;
	  if ( !c->next )
	    def->lastClause = NULL;
	} else
	{ prev->next = c->next;
	  if ( c->next == NULL)
	    def->lastClause = prev;
	}


  	freeClauseRef(c);
#if O_DEBUGGER
	if ( PROCEDURE_event_hook1 &&
	     def != PROCEDURE_event_hook1->definition )
	  callEventHook(PLEV_ERASED, clause);
#endif
	freeClause(clause);
	def->number_of_clauses--;

	rval = TRUE;
	break;
      }
    }

    endCritical;

    return rval;
  }
}


void
freeClause(Clause c)
{
#if O_DEBUGGER
  if ( true(c, HAS_BREAKPOINTS) )
    clearBreakPointsClause(c);
#endif

  GD->statistics.codes -= c->code_size;
  freeHeap(c->codes, sizeof(code) * c->code_size);
  freeHeap(c, sizeof(struct clause));
}


void
gcClausesDefinition(Definition def)
{ ClauseRef cref = def->definition.clauses, prev = NULL;
  int rehash = 0;
#if O_DEBUG
  int left = 0, removed = 0;
#endif

  DEBUG(1, Sdprintf("gcClausesDefinition(%s) --> ", predicateName(def)));

  startCritical;

  if ( def->hash_info )
  { if ( false(def, NEEDSREHASH) )
      gcClauseIndex(def->hash_info);
    else
    { rehash = def->hash_info->size * 2;
      unallocClauseIndexTable(def->hash_info);
      def->hash_info = NULL;
    }
  }

  while( cref )
  { if ( true(cref->clause, ERASED) )
    { ClauseRef c = cref;
      
      cref = cref->next;
      if ( !prev )
      { def->definition.clauses = c->next;
	if ( !c->next )
	  def->lastClause = NULL;
      } else
      { prev->next = c->next;
	if ( c->next == NULL)
	  def->lastClause = prev;
      }

      DEBUG(0, removed++);
#if O_DEBUGGER
      if ( PROCEDURE_event_hook1 && def != PROCEDURE_event_hook1->definition )
	callEventHook(PLEV_ERASED, c->clause);
#endif
      freeClause(c->clause);
      freeClauseRef(c);
    } else
    { prev = cref;
      cref = cref->next;
      DEBUG(0, left++);
    }
  }

  DEBUG(1, Sdprintf("removed %d, left %d\n", removed, left));

  if ( rehash )
    hashDefinition(def, rehash);

  clear(def, NEEDSCLAUSEGC|NEEDSREHASH);

  endCritical;
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
resetReferences() is called by abort() to clear all predicate references.
Erased clauses will be removed as well.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

static void
resetReferencesModule(Module m)
{ Definition def;
  Symbol s;

  for_table(s, m->procedures)
  { def = ((Procedure) s->value)->definition;
#ifdef O_PROFILE
    clear(def, PROFILE_TICKED);
#endif /* O_PROFILE */
    def->references = 1;
    leaveDefinition(def);
  }
}

void
resetReferences(void)
{ Symbol s;

  for_table(s, GD->tables.modules)
    resetReferencesModule((Module) s->value);
}

		 /*******************************
		 *	    CHECKING		*
		 *******************************/

word
pl_check_definition(term_t spec)
{ Procedure proc;
  Definition def;
  int nclauses = 0;
  int nindexable = 0;

  ClauseRef cref;

  if ( !get_procedure(spec, &proc, 0, GP_FIND) )
    return warning("$check_definition/1: can't find definition");
  def = proc->definition;

  if ( true(def, FOREIGN) )
    succeed;
  for(cref = def->definition.clauses; cref; cref = cref->next)
  { Clause clause = cref->clause;

    if ( clause->index.varmask != 0 )
      nindexable++;

    if ( false(clause, ERASED) )
      nclauses++;
    else
    { if ( false(def, NEEDSCLAUSEGC) )
	warning("%s contains erased clauses and has no NEEDSCLAUSEGC",
		predicateName(def));
    }
  }

  if ( def->hash_info )
  { if ( def->hash_info->size != nindexable )
      warning("%s has inconsistent def->hash_info->size",
	      predicateName(def));
  }

  if ( def->number_of_clauses != nclauses )
    warning("%s has inconsistent number_of_clauses (%d, should be %d)",
	    predicateName(def), def->number_of_clauses, nclauses);

  succeed;
}


		/********************************
		*     UNDEFINED PROCEDURES      *
		*********************************/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
A dynamic call to `f' in `m' has to be made (via call/1, apply/2 or from
C). This procedure  returns  the  procedure  to  be  run.   If  no  such
procedure  exists  an  undefined  procedure is created and returned.  In
this case interpret() will later call  trapUndefined()  to  generate  an
error message (or link the procedure from the library via autoload).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

Procedure
resolveProcedure(functor_t f, Module module)
{ Procedure proc;
  Module m;

  for( m = module; m; m = m->super )
  { if ( (proc = isCurrentProcedure(f, m)) && isDefinedProcedure(proc) )
      return proc;
  }

  return lookupProcedure(f, module);
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
autoImport() tries to autoimport f into module `m' and  returns  success
if this is possible.

PROBLEM: I'm not entirely  sure  it  is  save  to  deallocated  the  old
definition  structure  in  all  cases.   It  is  not  member of any heap
structure, thus sofar everything  is  alright.   After  a  dynamic  link
interpret()  picks up the new definition pointer, thus this should be ok
as well.  Any other C-code that  does  nasty  things  (non-deterministic
code  perhaps,  calls  indirect via C? (I do recall once conciously have
decided its not save, but can't recall why ...)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

Definition
autoImport(functor_t f, Module m)
{ Procedure proc;
  Definition def;
					/* Defined: no problem */
  if ( (proc = isCurrentProcedure(f, m)) && isDefinedProcedure(proc) )
    return proc->definition;
  
  if ( !m->super )			/* No super: can't import */
    return NULL;

  if ( !(def = autoImport(f, m->super)) )
    return NULL;

  if ( proc == NULL )			/* Create header if not there */
    proc = lookupProcedure(f, m);
					/* safe? */
  freeHeap(proc->definition, sizeof(struct definition));
  proc->definition = def;

  return def;
}

static int undefined_nesting;

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
According to Paulo Moura, predicates defined either dynamic, multifile or
discontiguous should not cause an undefined predicate warning.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

Definition
trapUndefined(Definition def)
{ int retry_times = 0;
  Definition newdef;
  Module module = def->module;
  FunctorDef functor = def->functor;

  retry:
					/* Auto import */
  if ( (newdef = autoImport(functor->functor, module)) )
    return newdef;
					/* Pred/Module does not want to trap */
  if ( true(def, DYNAMIC|MULTIFILE|DISCONTIGUOUS) || false(module, UNKNOWN) )
    return def;

  DEBUG(5, Sdprintf("trapUndefined(%s)\n", predicateName(def)));

					/* Trap via exception/3 */
  if ( LD->autoload )
  { if ( undefined_nesting > 100 )
    { undefined_nesting = 1;
      sysError("trapUndefined(): undefined: %s", predicateName(def));

      return def;
    } else
    { fid_t  cid  = PL_open_foreign_frame();
      term_t argv = PL_new_term_refs(4);
      static predicate_t pred;
      qid_t qid;
      atom_t sfn = source_file_name;	/* needs better solution! */
      int  sln = source_line_no;
      atom_t answer = ATOM_nil;

      if ( !pred )
	pred = PL_pred(FUNCTOR_undefinterc4, MODULE_system);

      PL_put_atom(    argv+0, def->module->name);
      PL_put_atom(    argv+1, def->functor->name);
      PL_put_integer( argv+2, def->functor->arity);
      PL_put_variable(argv+3);

      undefined_nesting++;
      qid = PL_open_query(MODULE_system, PL_Q_NODEBUG, pred, argv);
      if ( PL_next_solution(qid) )
	PL_get_atom(argv+3, &answer);
      PL_close_query(qid);
      undefined_nesting--;
      source_file_name = sfn;
      source_line_no   = sln;
      PL_discard_foreign_frame(cid);

      def = lookupProcedure(functor->functor, module)->definition;

      if ( answer == ATOM_fail )
	return def;
      else if ( answer == ATOM_retry )
      { if ( retry_times++ )
	{ warning("exception handler failed to define predicate %s\n",
		  predicateName(def));
	  return def;
	}
	goto retry;
      }
    }
  }
				/* No one wants to intercept */
  warning("Undefined predicate: %s", predicateName(def) );

  return def;
}

		 /*******************************
		 *	  REQUIRE SUPPORT	*
		 *******************************/

word
pl_require(term_t pred)
{ Procedure proc;

  if ( !get_procedure(pred, &proc, 0, GP_RESOLVE) )
    return get_procedure(pred, &proc, 0, GP_DEFINE);

  succeed;
}


		/********************************
		*            RETRACT            *
		*********************************/

word
pl_retract(term_t term, word h)
{ if ( ForeignControl(h) == FRG_CUTTED )
  { ClauseRef cref = ForeignContextPtr(h);
    leaveDefinition(cref->clause->procedure->definition);

    succeed;
  } else
  { Procedure proc;
    Definition def;
    Module m = (Module) NULL;
    ClauseRef cref;
    term_t cl = PL_new_term_ref();
    term_t head = PL_new_term_ref();
    term_t body = PL_new_term_ref();

    if ( !PL_strip_module(term, &m, cl) )
      fail;
 
    if ( !get_head_and_body_clause(cl, head, body, NULL) )
      return warning("retract/1: illegal clause");

    if ( ForeignControl(h) == FRG_FIRST_CALL )
    { functor_t fd;

      if ( !PL_get_functor(head, &fd) )
	return warning("retract/1: illegal head");
      if ( !(proc = isCurrentProcedure(fd, m)) )
	fail;

      def = proc->definition;

      if ( true(def, FOREIGN) )
	return warning("retract/1: cannot retract from foreign predicate");
      if ( true(def, LOCKED) && false(def, DYNAMIC) )
	return warning("retract/1: Attempt to retract from system predicate");

      if ( def->references && (debugstatus.styleCheck & DYNAMIC_STYLE) )
	warning("retract/1: %s has %d references",
		predicateName(def), def->references);

      cref = def->definition.clauses;
      enterDefinition(def);			/* reference the predicate */
    } else
    { cref = ForeignContextPtr(h);
      proc = cref->clause->procedure;
      def  = proc->definition;
    }

    for(; cref; cref = cref->next)
    { bool det;
      Word argv;

      if ( PL_is_compound(head) )
      { argv = valTermRef(head);
	deRef(argv);
	argv = argTermP(*argv, 0);
      } else
	argv = NULL;

      if ( !(cref = findClause(cref, argv, def, &det)) )
      { leaveDefinition(def);
	fail;
      }

      { fid_t cid = PL_open_foreign_frame();

	if ( decompile(cref->clause, cl, 0) )
	{ retractClauseProcedure(proc, cref->clause);
	  PL_close_foreign_frame(cid);	/* necessary? */
	  if ( det == TRUE )
	  { leaveDefinition(def);
	    succeed;
	  }

	  ForeignRedoPtr(cref->next);
	}

	PL_discard_foreign_frame(cid);
      }

      continue;
    }

    leaveDefinition(def);
    fail;
  }
}


word
pl_retractall(term_t head)
{ term_t thehead = PL_new_term_ref();
  Procedure proc;
  Definition def;
  ClauseRef cref;

  if ( !get_procedure(head, &proc, thehead, GP_FINDHERE) )
    succeed;

  def = proc->definition;
  if ( true(def, FOREIGN) )
    return warning("retractall/1: cannot retract from a foreign predicate");
  if ( true(def, LOCKED) && false(def, DYNAMIC) )
    return warning("retractall/1: Attempt to retract from a system predicate");

  enterDefinition(def);
  for(cref = def->definition.clauses; cref; cref = cref->next)
  { bool det;
    Word argv;

    if ( PL_is_compound(thehead) )
    { argv = valTermRef(thehead);
      deRef(argv);
      argv = argTermP(*argv, 0);
    } else
      argv = NULL;

    cref = findClause(cref, argv, def, &det);

    if ( cref )
    { fid_t cid = PL_open_foreign_frame();
    
      if ( det )
	leaveDefinition(def);

      if ( decompileHead(cref->clause, thehead) )
	retractClauseProcedure(proc, cref->clause);

      PL_discard_foreign_frame(cid);

      if ( det )
	succeed;
    } else
      break;
  }
  leaveDefinition(def);

  succeed;
}


		/********************************
		*       PROLOG PREDICATES       *
		*********************************/

word
pl_abolish(term_t atom, term_t arity)
{ functor_t f;
  Procedure proc;
  Module m = (Module) NULL;
  term_t tmp = PL_new_term_ref();
  atom_t name;
  int a;

  if ( !PL_strip_module(atom, &m, tmp) )
    fail;
  if ( !PL_get_atom(tmp, &name) || !PL_get_integer(arity, &a) )
    return warning("abolish/2: instantiation fault");

  if ( !(f = isCurrentFunctor(name, a)) ||
       !(proc = isCurrentProcedure(f, m)) )
    succeed;

  if ( true(proc->definition, LOCKED) && !SYSTEM_MODE && m == MODULE_system )
    return PL_error("abolish", 2, NULL, ERR_MODIFY_STATIC_PROC, proc);

  return abolishProcedure(proc, m);
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
abolish(Name/Arity)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

word
pl_abolish1(term_t spec)
{ Procedure proc;
  functor_t f;
  Module m = NULL;

  switch( get_functor(spec, &f, &m, 0, GF_PROCEDURE|GF_EXISTING) )
  { case FALSE:				/* exception */
      fail;
    case -1:				/* no functor */
      succeed;
  }

  if ( !(proc = isCurrentProcedure(f, m)) )
    succeed;

  if ( true(proc->definition, LOCKED) && !SYSTEM_MODE && m == MODULE_system )
    return PL_error("abolish", 1, NULL, ERR_MODIFY_STATIC_PROC, proc);

  return abolishProcedure(proc, m);
}


static unsigned long
attribute_mask(atom_t key)
{
#define TRACE_ANY (TRACE_CALL|TRACE_REDO|TRACE_EXIT|TRACE_FAIL)

  if (key == ATOM_dynamic)	 return DYNAMIC;
  if (key == ATOM_multifile)	 return MULTIFILE;
  if (key == ATOM_system)	 return SYSTEM;
  if (key == ATOM_locked)	 return LOCKED;
  if (key == ATOM_spy)		 return SPY_ME;
  if (key == ATOM_trace)	 return TRACE_ME;
  if (key == ATOM_trace_call)	 return TRACE_CALL;
  if (key == ATOM_trace_redo)	 return TRACE_REDO;
  if (key == ATOM_trace_exit)	 return TRACE_EXIT;
  if (key == ATOM_trace_fail)	 return TRACE_FAIL;
  if (key == ATOM_trace_any)	 return TRACE_ANY;
  if (key == ATOM_hide_childs)	 return HIDE_CHILDS;
  if (key == ATOM_transparent)	 return METAPRED;
  if (key == ATOM_discontiguous) return DISCONTIGUOUS;
  if (key == ATOM_volatile)	 return VOLATILE;

  return 0;
}


word
pl_get_predicate_attribute(term_t pred,
			   term_t what, term_t value)
{ Procedure proc;
  Definition def;
  functor_t fd;
  atom_t key;
  Module module = (Module) NULL;
  unsigned long att;
  term_t head = PL_new_term_ref();
  
  if ( !PL_strip_module(pred, &module, head) ||
       !PL_get_functor(head, &fd) ||
       !(proc = resolveProcedure(fd, module)) )
    fail;

  def = proc->definition;

  if ( !PL_get_atom(what, &key) )
    return warning("$get_predicate_attribute/3: key should be an atom");

  if ( key == ATOM_imported )
  { if ( module == def->module )
      fail;
    return PL_unify_atom(value, def->module->name);
  } else if ( key == ATOM_indexed )
  { if ( def->indexPattern == 0x0 )
      fail;
    return unify_index_pattern(proc, value);
  } else if ( key == ATOM_exported )
  { return PL_unify_integer(value, isPublicModule(module, proc));
  } else if ( key == ATOM_defined )
  { int d;

    if ( isDefinedProcedure(proc) )
      d = 1;
    else
      d = 0;
      
    return PL_unify_integer(value, d);
  } else if ( key == ATOM_line_count )
  { int line;

    if ( false(def, FOREIGN) &&
	 def->definition.clauses &&
	 (line=def->definition.clauses->clause->line_no) )
      return PL_unify_integer(value, line);
    else
      fail;
  } else if ( key == ATOM_foreign )
  { return PL_unify_integer(value, (def->flags & FOREIGN) ? 1 : 0);
  } else if ( key == ATOM_hashed )
  { return PL_unify_integer(value, def->hash_info?def->hash_info->buckets:0);
  } else if ( key == ATOM_references )
  { return PL_unify_integer(value, def->references);
  } else if ( key == ATOM_number_of_clauses )
  { if ( def->flags & FOREIGN )
      fail;

    return PL_unify_integer(value, def->number_of_clauses);
  } else if ( (att = attribute_mask(key)) )
  { return PL_unify_integer(value, (def->flags & att) ? 1 : 0);
  } else
  { return warning("$get_predicate_attribute/3: unknown key: %s",
		   stringAtom(key));
  }
}
  

word
pl_set_predicate_attribute(term_t pred,
			   term_t what, term_t value)
{ Procedure proc;
  Definition def;
  atom_t key;
  int val;
  unsigned long att;

  if ( !PL_get_atom(what, &key) ||
       !PL_get_integer(value, &val) || val & ~1 )
    return warning("$set_predicate_attribute/3: instantiation fault");
  if ( !(att = attribute_mask(key)) )
    return warning("$set_predicate_attribute/4: unknown key: %s",
		   stringAtom(key));
  if ( att & (TRACE_ANY|SPY_ME) )
  { if ( !get_procedure(pred, &proc, 0, GP_RESOLVE) )
      fail;
  } else
  { if ( !get_procedure(pred, &proc, 0, GP_DEFINE|GP_NAMEARITY) )
      fail;
  }
  def = proc->definition;

  if ( !val )
  { clear(def, att);
  } else
  { set(def, att);
    if ( (att == DYNAMIC || att == MULTIFILE) && SYSTEM_MODE )
    { set(def, SYSTEM|HIDE_CHILDS);
    }
  }

  succeed;
}


word
pl_default_predicate(term_t d1, term_t d2)
{ Procedure p1, p2;

  if ( get_procedure(d1, &p1, 0, GP_FIND) &&
       get_procedure(d2, &p2, 0, GP_FIND) )
  { if ( p1->definition == p2->definition || !isDefinedProcedure(p1) )
      succeed;
  }

  fail;
}


void
reindexDefinition(Definition def)
{ ClauseRef cref;
  int do_hash = 0;

  DEBUG(2, if ( def->definition.clauses )
	   { Procedure proc = def->definition.clauses->clause->procedure;

	     Sdprintf("reindexDefinition(%s)\n", procedureName(proc));
	   });

  if ( true(def, AUTOINDEX) )
  { int canindex = 0;
    int cannotindex = 0;
    
    for(cref = def->definition.clauses; cref; cref = cref->next)
    { word key;

      if ( arg1Key(cref->clause, &key) )
	canindex++;
      else
	cannotindex++;
    }

    if ( canindex == 0 )
    { DEBUG(2, if ( def->definition.clauses )
	       { Procedure proc = def->definition.clauses->clause->procedure;

		 Sdprintf("not indexed: %s\n", procedureName(proc));
	       });
      def->indexPattern = 0x0;
    } else
    { def->indexPattern = 0x1;
      if ( canindex > 5 && cannotindex <= 2 )
	do_hash = canindex / 2;
    }
  }

  def->indexPattern &= ~NEED_REINDEX;
  def->indexCardinality = cardinalityPattern(def->indexPattern);
  for(cref = def->definition.clauses; cref; cref = cref->next)
    reindexClause(cref->clause);

  if ( do_hash )
  { DEBUG(1,
	  if ( def->definition.clauses )
	  { Procedure proc = def->definition.clauses->clause->procedure;

	    Sdprintf("hash(%s, %d)\n", procedureName(proc), do_hash);
	  });
    hashDefinition(def, do_hash);
  }
}


word
pl_index(term_t pred)
{ Procedure proc;
  term_t head = PL_new_term_ref();

  if ( get_procedure(pred, &proc, head, GP_CREATE) )
  { Definition def = proc->definition;
    int arity = def->functor->arity;

    if (true(def, FOREIGN))
      return warning("index/1: cannot index foreign predicate %s", 
		     procedureName(proc));

    if ( arity > 0 )
    { unsigned long pattern = 0x0;
      int n, card = 0;
      term_t a = PL_new_term_ref();

      for(n=0; n<arity && n < 31; n++)
      { int ia;

	if ( !PL_get_arg(n+1, head, a) ||
	     !PL_get_integer(a, &ia) || (ia & ~1) )
	  return warning("index/1: %s: illegal index specification", 
			 procedureName(proc));
	if ( ia )
	{ pattern |= 1 << n;
	  if (++card == 4)		/* maximal 4 indexed arguments */
	    break;
	}
      }
      
      clear(def, AUTOINDEX);
      if ( (def->indexPattern & ~NEED_REINDEX) == pattern)
	succeed;
      def->indexPattern = (pattern | NEED_REINDEX);
    }
    succeed;
  }

  fail;
}


word
pl_get_clause_attribute(term_t ref, term_t att, term_t value)
{ Clause clause;
  atom_t a;

  if ( !PL_get_pointer(ref, (void **)&clause)  ||
       !inCore(clause) || !isClause(clause) )
    return warning("$clause_attribute/3: illegal reference");
  if ( !PL_get_atom(att, &a) )
    return warning("$clause_attribute/3: instantiation fault");

  if ( a == ATOM_line_count )
  { if ( clause->line_no )
      return PL_unify_integer(value, clause->line_no);
  } else if ( a == ATOM_file )
  { SourceFile sf = indexToSourceFile(clause->source_no);
    
    if ( sf )
      return PL_unify_atom(value, sf->name);
  } else if ( a == ATOM_fact )
  { return PL_unify_atom(value,
			 true(clause, UNIT_CLAUSE) ? ATOM_true
			 			   : ATOM_false);
  } else if ( a == ATOM_erased )
  { return PL_unify_atom(value,
			 true(clause, ERASED) ? ATOM_true : ATOM_false);
  }

  fail;
}


		/********************************
		*         SOURCE FILE           *
		*********************************/

static int source_index = 0;
static Table sourceTable = NULL;

SourceFile
lookupSourceFile(atom_t name)
{ SourceFile file;
  Symbol s;

  if ( !sourceTable )
    sourceTable = newHTable(32);

  if ( (s=lookupHTable(sourceTable, (void*)name)) )
    return (SourceFile) s->value;

  file = (SourceFile) allocHeap(sizeof(struct sourceFile) );
  file->name = name;
  file->count = 0;
  file->time = 0L;
  file->index = ++source_index;
  file->system = GD->bootsession;
  file->procedures = NULL;
  file->next = NULL;

  if ( sourceFileTable == NULL )
  { sourceFileTable = tailSourceFileTable = file;
  } else
  { tailSourceFileTable->next = file;
    tailSourceFileTable = file;
  }

  addHTable(sourceTable, (void*)name, file);

  return file;
}


static SourceFile
indexToSourceFile(int index)
{ SourceFile file;

  for(file=sourceFileTable; file; file=file->next)
  { if (file->index == index)
      return file;
  }

  return NULL;
}


void
addProcedureSourceFile(SourceFile sf, Procedure proc)
{ ListCell cell;

  if ( true(proc->definition, FILE_ASSIGNED) )
  { for(cell=sf->procedures; cell; cell = cell->next)
      if ( cell->value == proc )
	return;
  }

  startCritical;
  cell = allocHeap(sizeof(struct list_cell));
  cell->value = proc;
  cell->next = sf->procedures;
  sf->procedures = cell;
  set(proc->definition, FILE_ASSIGNED);
  endCritical;
}


word
pl_make_system_source_files(void)
{ SourceFile file;

  for(file=sourceFileTable; file; file=file->next)
    file->system = TRUE;

  succeed;
}


word
pl_source_file(term_t descr, term_t file, control_t h)
{ Procedure proc;
  ClauseRef cref;
  SourceFile sf;
  atom_t name;
  ListCell cell;
  

  if ( ForeignControl(h) == FRG_FIRST_CALL &&
       !PL_is_variable(descr) )
  { if ( !get_procedure(descr, &proc, 0, GP_FIND) ||
	 !proc->definition ||
	 true(proc->definition, FOREIGN) ||
	 !(cref = proc->definition->definition.clauses) ||
	 !(sf = indexToSourceFile(cref->clause->source_no)) )
      fail;

    return PL_unify_atom(file, sf->name);
  }

  if ( ForeignControl(h) == FRG_CUTTED )
    succeed;

  if ( !PL_get_atom(file, &name) ||
       !(sf = lookupSourceFile(name)) )
    fail;

  switch( ForeignControl(h) )
  { case FRG_FIRST_CALL:
      cell = sf->procedures;
      break;
    case FRG_REDO:
      cell = ForeignContextPtr(h);
      break;
    default:
      cell = NULL;
      assert(0);
  }
  
  for( ; cell; cell = cell->next )
  { Procedure proc = cell->value;
    Definition def = proc->definition;
    fid_t cid = PL_open_foreign_frame();

    if ( unify_definition(descr, def, 0, 0) )
    { PL_close_foreign_frame(cid);

      if ( cell->next )
	ForeignRedoPtr(cell->next);
      else
	succeed;
    }

    PL_discard_foreign_frame(cid);
  }

  fail;
}


word
pl_time_source_file(term_t file, term_t time, control_t h)
{ SourceFile fr;

  switch( ForeignControl(h) )
  { case FRG_FIRST_CALL:
      fr = sourceFileTable;
      break;
    case FRG_REDO:
      fr = ForeignContextPtr(h);
      break;
    case FRG_CUTTED:
    default:
      succeed;
  }

  for(;fr != (SourceFile) NULL; fr = fr->next)
  { if ( fr->system == TRUE )
      continue;
    if ( PL_unify_atom(file, fr->name) &&
         unifyTime(time, fr->time) )
    { if (fr->next != (SourceFile) NULL)
	ForeignRedoPtr(fr->next);
      else
	succeed;
    }
  }

  fail;
}


void
startConsult(SourceFile f)
{ if ( f->count++ > 0 )
  { ListCell cell, next;

    for(cell = f->procedures; cell; cell = next)
    { Procedure proc = cell->value;

      next = cell->next;
      if ( proc->definition )
	removeClausesProcedure(proc, true(proc->definition, MULTIFILE)
						? f->index : 0);
      freeHeap(cell, sizeof(struct list_cell));
    }
    f->procedures = NULL;
  }

  f->current_procedure = NULL;
}


word
pl_start_consult(term_t file)
{ atom_t name;

  if ( PL_get_atom(file, &name) )
  { SourceFile f = lookupSourceFile(name);

    f->time = LastModifiedFile(stringAtom(name));
    startConsult(f);
    succeed;
  }

  fail;
}

		 /*******************************
		 *       DEBUGGER SUPPORT	*
		 *******************************/

word
pl_clause_from_source(term_t file, term_t line, term_t clause)
{ atom_t name;
  SourceFile f;
  int ln;
  ListCell cell;
  Clause c = NULL;

  if ( !PL_get_atom(file, &name) ||
       !(f = lookupSourceFile(name)) ||
       !PL_get_integer(line, &ln) )
    return warning("clause_from_source/3: instantiation fault");
  

  for(cell = f->procedures; cell; cell = cell->next)
  { Procedure proc = cell->value;
    Definition def = proc->definition;

    if ( def && false(def, FOREIGN) )
    { ClauseRef cref = def->definition.clauses;

      for( ; cref; cref = cref->next )
      { Clause cl = cref->clause;

	if ( cl->source_no == f->index )
	{ if ( ln >= cl->line_no )
	  { if ( !c || c->line_no < cl->line_no )
	      c = cl;
	  }
	}
      }
    }
  }

  if ( c )
    return PL_unify_pointer(clause, c);
  
  fail;
}
