/*
 * tclResolve.c --
 *
 *	Contains hooks for customized command/variable name resolution
 *	schemes. These hooks allow extensions like [incr Tcl] to add their own
 *	name resolution rules to the Tcl language. Rules can be applied to a
 *	particular namespace, to the interpreter as a whole, or both.
 *
 * Copyright © 1998 Lucent Technologies, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tclInt.h"

/*
 * Declarations for functions local to this file:
 */

static void		BumpCmdRefEpochs(Namespace *nsPtr);

/*
 *----------------------------------------------------------------------
 *
 * Tcl_AddInterpResolvers --
 *
 *	Adds a set of command/variable resolution functions to an interpreter.
 *	These functions are consulted when commands are resolved in
 *	Tcl_FindCommand, and when variables are resolved in TclLookupVar and
 *	LookupCompiledLocal. Each namespace may also have its own set of
 *	resolution functions which take precedence over those for the
 *	interpreter.
 *
 *	When a name is resolved, it is handled as follows. First, the name is
 *	passed to the resolution functions for the namespace. If not resolved,
 *	the name is passed to each of the resolution functions added to the
 *	interpreter. Finally, if still not resolved, the name is handled using
 *	the default Tcl rules for name resolution.
 *
 * Results:
 *	Returns pointers to the current name resolution functions in the
 *	cmdProcPtr, varProcPtr and compiledVarProcPtr arguments.
 *
 * Side effects:
 *	If a compiledVarProc is specified, this function bumps the
 *	compileEpoch for the interpreter, forcing all code to be recompiled.
 *	If a cmdProc is specified, this function bumps the cmdRefEpoch in all
 *	namespaces, forcing commands to be resolved again using the new rules.
 *
 *----------------------------------------------------------------------
 */

void
Tcl_AddInterpResolvers(
    Tcl_Interp *interp,		/* Interpreter whose name resolution rules are
				 * being modified. */
    const char *name,		/* Name of this resolution scheme. */
    Tcl_ResolveCmdProc *cmdProc,/* New function for command resolution. */
    Tcl_ResolveVarProc *varProc,/* Function for variable resolution at
				 * runtime. */
    Tcl_ResolveCompiledVarProc *compiledVarProc)
				/* Function for variable resolution at compile
				 * time. */
{
    Interp *iPtr = (Interp *) interp;
    ResolverScheme *resPtr;
    unsigned len;

    /*
     * Since we're adding a new name resolution scheme, we must force all code
     * to be recompiled to use the new scheme. If there are new compiled
     * variable resolution rules, bump the compiler epoch to invalidate
     * compiled code. If there are new command resolution rules, bump the
     * cmdRefEpoch in all namespaces.
     */

    if (compiledVarProc) {
	iPtr->compileEpoch++;
    }
    if (cmdProc) {
	BumpCmdRefEpochs(iPtr->globalNsPtr);
    }

    /*
     * Look for an existing scheme with the given name. If found, then replace
     * its rules.
     */

    for (resPtr=iPtr->resolverPtr ; resPtr!=NULL ; resPtr=resPtr->nextPtr) {
	if (*name == *resPtr->name && strcmp(name, resPtr->name) == 0) {
	    resPtr->cmdResProc = cmdProc;
	    resPtr->varResProc = varProc;
	    resPtr->compiledVarResProc = compiledVarProc;
	    return;
	}
    }

    /*
     * Otherwise, this is a new scheme. Add it to the FRONT of the linked
     * list, so that it overrides existing schemes.
     */

    resPtr = (ResolverScheme *)Tcl_Alloc(sizeof(ResolverScheme));
    len = strlen(name) + 1;
    resPtr->name = (char *)Tcl_Alloc(len);
    memcpy(resPtr->name, name, len);
    resPtr->cmdResProc = cmdProc;
    resPtr->varResProc = varProc;
    resPtr->compiledVarResProc = compiledVarProc;
    resPtr->nextPtr = iPtr->resolverPtr;
    iPtr->resolverPtr = resPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_GetInterpResolvers --
 *
 *	Looks for a set of command/variable resolution functions with the
 *	given name in an interpreter. These functions are registered by
 *	calling Tcl_AddInterpResolvers.
 *
 * Results:
 *	If the name is recognized, this function returns non-zero, along with
 *	pointers to the name resolution functions in the Tcl_ResolverInfo
 *	structure. If the name is not recognized, this function returns zero.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Tcl_GetInterpResolvers(
    Tcl_Interp *interp,		/* Interpreter whose name resolution rules are
				 * being queried. */
    const char *name,		/* Look for a scheme with this name. */
    Tcl_ResolverInfo *resInfoPtr)
				/* Returns pointers to the functions, if
				 * found */
{
    Interp *iPtr = (Interp *) interp;
    ResolverScheme *resPtr;

    /*
     * Look for an existing scheme with the given name. If found, then return
     * pointers to its functions.
     */

    for (resPtr=iPtr->resolverPtr ; resPtr!=NULL ; resPtr=resPtr->nextPtr) {
	if (*name == *resPtr->name && strcmp(name, resPtr->name) == 0) {
	    resInfoPtr->cmdResProc = resPtr->cmdResProc;
	    resInfoPtr->varResProc = resPtr->varResProc;
	    resInfoPtr->compiledVarResProc = resPtr->compiledVarResProc;
	    return 1;
	}
    }

    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_RemoveInterpResolvers --
 *
 *	Removes a set of command/variable resolution functions previously
 *	added by Tcl_AddInterpResolvers. The next time a command/variable name
 *	is resolved, these functions won't be consulted.
 *
 * Results:
 *	Returns non-zero if the name was recognized and the resolution scheme
 *	was deleted. Returns zero otherwise.
 *
 * Side effects:
 *	If a scheme with a compiledVarProc was deleted, this function bumps
 *	the compileEpoch for the interpreter, forcing all code to be
 *	recompiled. If a scheme with a cmdProc was deleted, this function
 *	bumps the cmdRefEpoch in all namespaces, forcing commands to be
 *	resolved again using the new rules.
 *
 *----------------------------------------------------------------------
 */

int
Tcl_RemoveInterpResolvers(
    Tcl_Interp *interp,		/* Interpreter whose name resolution rules are
				 * being modified. */
    const char *name)		/* Name of the scheme to be removed. */
{
    Interp *iPtr = (Interp *) interp;
    ResolverScheme **prevPtrPtr, *resPtr;

    /*
     * Look for an existing scheme with the given name.
     */

    prevPtrPtr = &iPtr->resolverPtr;
    for (resPtr=iPtr->resolverPtr ; resPtr!=NULL ; resPtr=resPtr->nextPtr) {
	if (*name == *resPtr->name && strcmp(name, resPtr->name) == 0) {
	    break;
	}
	prevPtrPtr = &resPtr->nextPtr;
    }

    /*
     * If we found the scheme, delete it.
     */

    if (resPtr) {
	/*
	 * If we're deleting a scheme with compiled variable resolution rules,
	 * bump the compiler epoch to invalidate compiled code. If we're
	 * deleting a scheme with command resolution rules, bump the
	 * cmdRefEpoch in all namespaces.
	 */

	if (resPtr->compiledVarResProc) {
	    iPtr->compileEpoch++;
	}
	if (resPtr->cmdResProc) {
	    BumpCmdRefEpochs(iPtr->globalNsPtr);
	}

	*prevPtrPtr = resPtr->nextPtr;
	Tcl_Free(resPtr->name);
	Tcl_Free(resPtr);

	return 1;
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * BumpCmdRefEpochs --
 *
 *	This function is used to bump the cmdRefEpoch counters in the
 *	specified namespace and all of its child namespaces. It is used
 *	whenever name resolution schemes are added/removed from an
 *	interpreter, to invalidate all command references.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Bumps the cmdRefEpoch in the specified namespace and its children,
 *	recursively.
 *
 *----------------------------------------------------------------------
 */

static void
BumpCmdRefEpochs(
    Namespace *nsPtr)		/* Namespace being modified. */
{
    Tcl_HashEntry *entry;
    Tcl_HashSearch search;

    nsPtr->cmdRefEpoch++;

#ifndef BREAK_NAMESPACE_COMPAT
    for (entry = Tcl_FirstHashEntry(&nsPtr->childTable, &search);
	    entry != NULL; entry = Tcl_NextHashEntry(&search)) {
	Namespace *childNsPtr = (Namespace *)Tcl_GetHashValue(entry);

	BumpCmdRefEpochs(childNsPtr);
    }
#else
    if (nsPtr->childTablePtr != NULL) {
	for (entry = Tcl_FirstHashEntry(nsPtr->childTablePtr, &search);
		entry != NULL; entry = Tcl_NextHashEntry(&search)) {
	    Namespace *childNsPtr = (Namespace *)Tcl_GetHashValue(entry);

	    BumpCmdRefEpochs(childNsPtr);
	}
    }
#endif
    TclInvalidateNsPath(nsPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_SetNamespaceResolvers --
 *
 *	Sets the command/variable resolution functions for a namespace,
 *	thereby changing the way that command/variable names are interpreted.
 *	This allows extension writers to support different name resolution
 *	schemes, such as those for object-oriented packages.
 *
 *	Command resolution is handled by a function of the following type:
 *
 *	  typedef int (Tcl_ResolveCmdProc)(Tcl_Interp *interp,
 *		  const char *name, Tcl_Namespace *context,
 *		  int flags, Tcl_Command *rPtr);
 *
 *	Whenever a command is executed or Tcl_FindCommand is invoked within
 *	the namespace, this function is called to resolve the command name. If
 *	this function is able to resolve the name, it should return the status
 *	code TCL_OK, along with the corresponding Tcl_Command in the rPtr
 *	argument. Otherwise, the function can return TCL_CONTINUE, and the
 *	command will be treated under the usual name resolution rules. Or, it
 *	can return TCL_ERROR, and the command will be considered invalid.
 *
 *	Variable resolution is handled by two functions. The first is called
 *	whenever a variable needs to be resolved at compile time:
 *
 *	  typedef int (Tcl_ResolveCompiledVarProc)(Tcl_Interp *interp,
 *		  const char *name, Tcl_Namespace *context,
 *		  Tcl_ResolvedVarInfo *rPtr);
 *
 *	If this function is able to resolve the name, it should return the
 *	status code TCL_OK, along with variable resolution info in the rPtr
 *	argument; this info will be used to set up compiled locals in the call
 *	frame at runtime. The function may also return TCL_CONTINUE, and the
 *	variable will be treated under the usual name resolution rules. Or, it
 *	can return TCL_ERROR, and the variable will be considered invalid.
 *
 *	Another function is used whenever a variable needs to be resolved at
 *	runtime but it is not recognized as a compiled local. (For example,
 *	the variable may be requested via Tcl_FindNamespaceVar.) This function
 *	has the following type:
 *
 *	  typedef int (Tcl_ResolveVarProc)(Tcl_Interp *interp,
 *		  const char *name, Tcl_Namespace *context,
 *		  int flags, Tcl_Var *rPtr);
 *
 *	This function is quite similar to the compile-time version. It returns
 *	the same status codes, but if variable resolution succeeds, this
 *	function returns a Tcl_Var directly via the rPtr argument.
 *
 * Results:
 *	Nothing.
 *
 * Side effects:
 *	Bumps the command epoch counter for the namespace, invalidating all
 *	command references in that namespace. Also bumps the resolver epoch
 *	counter for the namespace, forcing all code in the namespace to be
 *	recompiled.
 *
 *----------------------------------------------------------------------
 */

void
Tcl_SetNamespaceResolvers(
    Tcl_Namespace *namespacePtr,/* Namespace whose resolution rules are being
				 * modified. */
    Tcl_ResolveCmdProc *cmdProc,/* Function for command resolution */
    Tcl_ResolveVarProc *varProc,/* Function for variable resolution at
				 * run-time */
    Tcl_ResolveCompiledVarProc *compiledVarProc)
				/* Function for variable resolution at compile
				 * time. */
{
    Namespace *nsPtr = (Namespace *) namespacePtr;

    /*
     * Plug in the new command resolver, and bump the epoch counters so that
     * all code will have to be recompiled and all commands will have to be
     * resolved again using the new policy.
     */

    nsPtr->cmdResProc = cmdProc;
    nsPtr->varResProc = varProc;
    nsPtr->compiledVarResProc = compiledVarProc;

    nsPtr->cmdRefEpoch++;
    nsPtr->resolverEpoch++;
    TclInvalidateNsPath(nsPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_GetNamespaceResolvers --
 *
 *	Returns the current command/variable resolution functions for a
 *	namespace. By default, these functions are NULL. New functions can be
 *	installed by calling Tcl_SetNamespaceResolvers, to provide new name
 *	resolution rules.
 *
 * Results:
 *	Returns non-zero if any name resolution functions have been assigned
 *	to this namespace; also returns pointers to the functions in the
 *	Tcl_ResolverInfo structure. Returns zero otherwise.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Tcl_GetNamespaceResolvers(
    Tcl_Namespace *namespacePtr,/* Namespace whose resolution rules are being
				 * modified. */
    Tcl_ResolverInfo *resInfoPtr)
				/* Returns: pointers for all name resolution
				 * functions assigned to this namespace. */
{
    Namespace *nsPtr = (Namespace *) namespacePtr;

    resInfoPtr->cmdResProc = nsPtr->cmdResProc;
    resInfoPtr->varResProc = nsPtr->varResProc;
    resInfoPtr->compiledVarResProc = nsPtr->compiledVarResProc;

    if (nsPtr->cmdResProc != NULL || nsPtr->varResProc != NULL ||
	    nsPtr->compiledVarResProc != NULL) {
	return 1;
    }
    return 0;
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */
