tclproc.c

来自「tcl是工具命令语言」· C语言 代码 · 共 1,707 行 · 第 1/4 页

C
1,707
字号
/*  * tclProc.c -- * *	This file contains routines that implement Tcl procedures, *	including the "proc" and "uplevel" commands. * * Copyright (c) 1987-1993 The Regents of the University of California. * Copyright (c) 1994-1998 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * RCS: @(#) $Id: tclProc.c,v 1.44 2002/12/11 21:29:52 dgp Exp $ */#include "tclInt.h"#include "tclCompile.h"/* * Prototypes for static functions in this file */static void	ProcBodyDup _ANSI_ARGS_((Tcl_Obj *srcPtr, Tcl_Obj *dupPtr));static void	ProcBodyFree _ANSI_ARGS_((Tcl_Obj *objPtr));static int	ProcBodySetFromAny _ANSI_ARGS_((Tcl_Interp *interp,		Tcl_Obj *objPtr));static void	ProcBodyUpdateString _ANSI_ARGS_((Tcl_Obj *objPtr));static  int	ProcessProcResultCode _ANSI_ARGS_((Tcl_Interp *interp,		    char *procName, int nameLen, int returnCode));static int	TclCompileNoOp _ANSI_ARGS_((Tcl_Interp *interp,		    Tcl_Parse *parsePtr, struct CompileEnv *envPtr));/* * The ProcBodyObjType type */Tcl_ObjType tclProcBodyType = {    "procbody",			/* name for this type */    ProcBodyFree,		/* FreeInternalRep procedure */    ProcBodyDup,		/* DupInternalRep procedure */    ProcBodyUpdateString,	/* UpdateString procedure */    ProcBodySetFromAny		/* SetFromAny procedure */};/* *---------------------------------------------------------------------- * * Tcl_ProcObjCmd -- * *	This object-based procedure is invoked to process the "proc" Tcl  *	command. See the user documentation for details on what it does. * * Results: *	A standard Tcl object result value. * * Side effects: *	A new procedure gets created. * *---------------------------------------------------------------------- */	/* ARGSUSED */intTcl_ProcObjCmd(dummy, interp, objc, objv)    ClientData dummy;		/* Not used. */    Tcl_Interp *interp;		/* Current interpreter. */    int objc;			/* Number of arguments. */    Tcl_Obj *CONST objv[];	/* Argument objects. */{    register Interp *iPtr = (Interp *) interp;    Proc *procPtr;    char *fullName;    CONST char *procName, *procArgs, *procBody;    Namespace *nsPtr, *altNsPtr, *cxtNsPtr;    Tcl_Command cmd;    Tcl_DString ds;    if (objc != 4) {	Tcl_WrongNumArgs(interp, 1, objv, "name args body");	return TCL_ERROR;    }    /*     * Determine the namespace where the procedure should reside. Unless     * the command name includes namespace qualifiers, this will be the     * current namespace.     */        fullName = TclGetString(objv[1]);    TclGetNamespaceForQualName(interp, fullName, (Namespace *) NULL,	    0, &nsPtr, &altNsPtr, &cxtNsPtr, &procName);    if (nsPtr == NULL) {        Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),		"can't create procedure \"", fullName,		"\": unknown namespace", (char *) NULL);        return TCL_ERROR;    }    if (procName == NULL) {	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),		"can't create procedure \"", fullName,		"\": bad procedure name", (char *) NULL);        return TCL_ERROR;    }    if ((nsPtr != iPtr->globalNsPtr)	    && (procName != NULL) && (procName[0] == ':')) {	Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),		"can't create procedure \"", procName,		"\" in non-global namespace with name starting with \":\"",	        (char *) NULL);        return TCL_ERROR;    }    /*     *  Create the data structure to represent the procedure.     */    if (TclCreateProc(interp, nsPtr, procName, objv[2], objv[3],        &procPtr) != TCL_OK) {        return TCL_ERROR;    }    /*     * Now create a command for the procedure. This will initially be in     * the current namespace unless the procedure's name included namespace     * qualifiers. To create the new command in the right namespace, we     * generate a fully qualified name for it.     */    Tcl_DStringInit(&ds);    if (nsPtr != iPtr->globalNsPtr) {	Tcl_DStringAppend(&ds, nsPtr->fullName, -1);	Tcl_DStringAppend(&ds, "::", 2);    }    Tcl_DStringAppend(&ds, procName, -1);        Tcl_CreateCommand(interp, Tcl_DStringValue(&ds), TclProcInterpProc,	    (ClientData) procPtr, TclProcDeleteProc);    cmd = Tcl_CreateObjCommand(interp, Tcl_DStringValue(&ds),	    TclObjInterpProc, (ClientData) procPtr, TclProcDeleteProc);    Tcl_DStringFree(&ds);    /*     * Now initialize the new procedure's cmdPtr field. This will be used     * later when the procedure is called to determine what namespace the     * procedure will run in. This will be different than the current     * namespace if the proc was renamed into a different namespace.     */        procPtr->cmdPtr = (Command *) cmd;    /*     * Optimize for noop procs: if the body is not precompiled (like a TclPro     * procbody), and the argument list is just "args" and the body is empty,     * define a compileProc to compile a noop.     *     * Notes:      *   - cannot be done for any argument list without having different     *     compiled/not-compiled behaviour in the "wrong argument #" case,      *     or making this code much more complicated. In any case, it doesn't      *     seem to make a lot of sense to verify the number of arguments we      *     are about to ignore ...     *   - could be enhanced to handle also non-empty bodies that contain      *     only comments; however, parsing the body will slow down the      *     compilation of all procs whose argument list is just _args_ */    if (objv[3]->typePtr == &tclProcBodyType) {	goto done;    }    procArgs = Tcl_GetString(objv[2]);        while (*procArgs == ' ') {	procArgs++;    }        if ((procArgs[0] == 'a') && (strncmp(procArgs, "args", 4) == 0)) {	procArgs +=4;	while(*procArgs != '\0') {	    if (*procArgs != ' ') {		goto done;	    }	    procArgs++;	}			/* 	 * The argument list is just "args"; check the body	 */		procBody = Tcl_GetString(objv[3]);	while (*procBody != '\0') {	    if (!isspace(UCHAR(*procBody))) {		goto done;	    }	    procBody++;	}			/* 	 * The body is just spaces: link the compileProc	 */		((Command *) cmd)->compileProc = TclCompileNoOp;    } done:    return TCL_OK;}/* *---------------------------------------------------------------------- * * TclCreateProc -- * *	Creates the data associated with a Tcl procedure definition. *	This procedure knows how to handle two types of body objects: *	strings and procbody. Strings are the traditional (and common) value *	for bodies, procbody are values created by extensions that have *	loaded a previously compiled script. * * Results: *	Returns TCL_OK on success, along with a pointer to a Tcl *	procedure definition in procPtrPtr.  This definition should *	be freed by calling TclCleanupProc() when it is no longer *	needed.  Returns TCL_ERROR if anything goes wrong. * * Side effects: *	If anything goes wrong, this procedure returns an error *	message in the interpreter. * *---------------------------------------------------------------------- */intTclCreateProc(interp, nsPtr, procName, argsPtr, bodyPtr, procPtrPtr)    Tcl_Interp *interp;         /* interpreter containing proc */    Namespace *nsPtr;           /* namespace containing this proc */    CONST char *procName;       /* unqualified name of this proc */    Tcl_Obj *argsPtr;           /* description of arguments */    Tcl_Obj *bodyPtr;           /* command body */    Proc **procPtrPtr;          /* returns:  pointer to proc data */{    Interp *iPtr = (Interp*)interp;    CONST char **argArray = NULL;    register Proc *procPtr;    int i, length, result, numArgs;    CONST char *args, *bytes, *p;    register CompiledLocal *localPtr = NULL;    Tcl_Obj *defPtr;    int precompiled = 0;        if (bodyPtr->typePtr == &tclProcBodyType) {        /*         * Because the body is a TclProProcBody, the actual body is already         * compiled, and it is not shared with anyone else, so it's OK not to         * unshare it (as a matter of fact, it is bad to unshare it, because         * there may be no source code).         *         * We don't create and initialize a Proc structure for the procedure;         * rather, we use what is in the body object. Note that         * we initialize its cmdPtr field below after we've created the command         * for the procedure. We increment the ref count of the Proc struct         * since the command (soon to be created) will be holding a reference         * to it.         */            procPtr = (Proc *) bodyPtr->internalRep.otherValuePtr;        procPtr->iPtr = iPtr;        procPtr->refCount++;        precompiled = 1;    } else {        /*         * If the procedure's body object is shared because its string value is         * identical to, e.g., the body of another procedure, we must create a         * private copy for this procedure to use. Such sharing of procedure         * bodies is rare but can cause problems. A procedure body is compiled         * in a context that includes the number of compiler-allocated "slots"         * for local variables. Each formal parameter is given a local variable         * slot (the "procPtr->numCompiledLocals = numArgs" assignment         * below). This means that the same code can not be shared by two         * procedures that have a different number of arguments, even if their         * bodies are identical. Note that we don't use Tcl_DuplicateObj since         * we would not want any bytecode internal representation.         */        if (Tcl_IsShared(bodyPtr)) {            bytes = Tcl_GetStringFromObj(bodyPtr, &length);            bodyPtr = Tcl_NewStringObj(bytes, length);        }        /*         * Create and initialize a Proc structure for the procedure. Note that         * we initialize its cmdPtr field below after we've created the command         * for the procedure. We increment the ref count of the procedure's         * body object since there will be a reference to it in the Proc         * structure.         */            Tcl_IncrRefCount(bodyPtr);        procPtr = (Proc *) ckalloc(sizeof(Proc));        procPtr->iPtr = iPtr;        procPtr->refCount = 1;        procPtr->bodyPtr = bodyPtr;        procPtr->numArgs  = 0;	/* actual argument count is set below. */        procPtr->numCompiledLocals = 0;        procPtr->firstLocalPtr = NULL;        procPtr->lastLocalPtr = NULL;    }        /*     * Break up the argument list into argument specifiers, then process     * each argument specifier.     * If the body is precompiled, processing is limited to checking that     * the the parsed argument is consistent with the one stored in the     * Proc.     * THIS FAILS IF THE ARG LIST OBJECT'S STRING REP CONTAINS NULLS.     */    args = Tcl_GetStringFromObj(argsPtr, &length);    result = Tcl_SplitList(interp, args, &numArgs, &argArray);    if (result != TCL_OK) {        goto procError;    }    if (precompiled) {        if (numArgs > procPtr->numArgs) {            char buf[64 + TCL_INTEGER_SPACE + TCL_INTEGER_SPACE];            sprintf(buf, "\": arg list contains %d entries, precompiled header expects %d",                    numArgs, procPtr->numArgs);            Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),                    "procedure \"", procName,                    buf, (char *) NULL);            goto procError;        }        localPtr = procPtr->firstLocalPtr;    } else {        procPtr->numArgs = numArgs;        procPtr->numCompiledLocals = numArgs;    }    for (i = 0;  i < numArgs;  i++) {        int fieldCount, nameLength, valueLength;        CONST char **fieldValues;        /*         * Now divide the specifier up into name and default.         */        result = Tcl_SplitList(interp, argArray[i], &fieldCount,                &fieldValues);        if (result != TCL_OK) {            goto procError;        }        if (fieldCount > 2) {            ckfree((char *) fieldValues);            Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),                    "too many fields in argument specifier \"",                    argArray[i], "\"", (char *) NULL);            goto procError;        }        if ((fieldCount == 0) || (*fieldValues[0] == 0)) {            ckfree((char *) fieldValues);            Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),                    "procedure \"", procName,                    "\" has argument with no name", (char *) NULL);            goto procError;        }	        nameLength = strlen(fieldValues[0]);        if (fieldCount == 2) {            valueLength = strlen(fieldValues[1]);        } else {            valueLength = 0;        }        /*         * Check that the formal parameter name is a scalar.         */        p = fieldValues[0];        while (*p != '\0') {            if (*p == '(') {                CONST char *q = p;                do {		    q++;		} while (*q != '\0');		q--;		if (*q == ')') { /* we have an array element */		    Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),		            "procedure \"", procName,		            "\" has formal parameter \"", fieldValues[0],			    "\" that is an array element",			    (char *) NULL);		    ckfree((char *) fieldValues);		    goto procError;		}	    } else if ((*p == ':') && (*(p+1) == ':')) {		Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),		        "procedure \"", procName,		        "\" has formal parameter \"", fieldValues[0],			"\" that is not a simple name",			(char *) NULL);		ckfree((char *) fieldValues);		goto procError;	    }	    p++;	}	if (precompiled) {	    /*	     * Compare the parsed argument with the stored one.	     * For the flags, we and out VAR_UNDEFINED to support bridging	     * precompiled <= 8.3 code in 8.4 where this is now used as an	     * optimization indicator.	Yes, this is a hack. -- hobbs	     */	    if ((localPtr->nameLength != nameLength)		    || (strcmp(localPtr->name, fieldValues[0]))		    || (localPtr->frameIndex != i)		    || ((localPtr->flags & ~VAR_UNDEFINED)			    != (VAR_SCALAR | VAR_ARGUMENT))		    || ((localPtr->defValuePtr == NULL)			    && (fieldCount == 2))		    || ((localPtr->defValuePtr != NULL)			    && (fieldCount != 2))) {		char buf[80 + TCL_INTEGER_SPACE];		sprintf(buf, "\": formal parameter %d is inconsistent with precompiled body",			i);

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?