home *** CD-ROM | disk | FTP | other *** search
- /*
- * tclProc.c --
- *
- * This file contains routines that implement Tcl procedures and
- * variables.
- *
- * Copyright 1987 Regents of the University of California
- * Permission to use, copy, modify, and distribute this
- * software and its documentation for any purpose and without
- * fee is hereby granted, provided that the above copyright
- * notice appear in all copies. The University of California
- * makes no representations about the suitability of this
- * software for any purpose. It is provided "as is" without
- * express or implied warranty.
- */
-
- #ifndef lint
- static char rcsid[] = "$Header: /sprite/src/lib/tcl/RCS/tclProc.c,v 1.31 90/01/27 14:44:24 ouster Exp $ SPRITE (Berkeley)";
- #endif /* not lint */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <ctype.h>
- #include "tclInt.h"
-
- /*
- * Forward references to procedures defined later in this file:
- */
-
- extern Var * FindVar();
- extern int InterpProc();
- extern Var * NewVar();
- extern void ProcDeleteProc();
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_ProcCmd --
- *
- * This procedure is invoked to process the "proc" Tcl command.
- * See the user documentation for details on what it does.
- *
- * Results:
- * A standard Tcl result value.
- *
- * Side effects:
- * A new procedure gets created.
- *
- *----------------------------------------------------------------------
- */
-
- /* ARGSUSED */
- int
- Tcl_ProcCmd(dummy, interp, argc, argv)
- ClientData dummy; /* Not used. */
- Tcl_Interp *interp; /* Current interpreter. */
- int argc; /* Number of arguments. */
- char **argv; /* Argument strings. */
- {
- register Interp *iPtr = (Interp *) interp;
- register Proc *procPtr;
- int result, argCount, i;
- char **argArray;
-
- if (argc != 4) {
- sprintf(iPtr->result,
- "wrong # args: should be \"%.50s name args body\"",
- argv[0]);
- return TCL_ERROR;
- }
-
- procPtr = (Proc *) ckalloc(sizeof(Proc));
- procPtr->iPtr = iPtr;
- procPtr->command = (char *) ckalloc((unsigned) strlen(argv[3]) + 1);
- strcpy(procPtr->command, argv[3]);
- procPtr->argPtr = NULL;
- Tcl_CreateCommand(interp, argv[1], InterpProc,
- (ClientData) procPtr, ProcDeleteProc);
-
- /*
- * Break up the argument list into argument specifiers, then process
- * each argument specifier.
- */
-
- result = Tcl_SplitList(interp, argv[2], &argCount, &argArray);
- if (result != TCL_OK) {
- return result;
- }
- for (i = 0; i < argCount; i++) {
- int fieldCount, nameLength, valueLength;
- char **fieldValues;
- register Var *argPtr;
-
- /*
- * 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) {
- sprintf(iPtr->result,
- "too many fields in argument specifier \"%.50s\"",
- argArray[i]);
- result = TCL_ERROR;
- goto procError;
- }
- if ((fieldCount == 0) || (*fieldValues[0] == 0)) {
- sprintf(iPtr->result,
- "procedure \"%.50s\" has argument with no name", argv[1]);
- result = TCL_ERROR;
- goto procError;
- }
- nameLength = strlen(fieldValues[0]);
- if (fieldCount == 2) {
- valueLength = strlen(fieldValues[1]);
- } else {
- valueLength = 0;
- }
- if (procPtr->argPtr == NULL) {
- argPtr = (Var *) ckalloc(VAR_SIZE(nameLength, valueLength));
- procPtr->argPtr = argPtr;
- } else {
- argPtr->nextPtr = (Var *) ckalloc(VAR_SIZE(nameLength, valueLength));
- argPtr = argPtr->nextPtr;
- }
- strcpy(argPtr->name, fieldValues[0]);
- if (fieldCount == 2) {
- argPtr->value = argPtr->name + nameLength + 1;
- strcpy(argPtr->value, fieldValues[1]);
- } else {
- argPtr->value = NULL;
- }
- argPtr->valueLength = valueLength;
- argPtr->flags = 0;
- argPtr->nextPtr = NULL;
- ckfree((char *) fieldValues);
- }
-
- ckfree((char *) argArray);
- return TCL_OK;
-
- procError:
- ckfree((char *) argArray);
- return result;
- }
-
- /*1
- *----------------------------------------------------------------------
- *
- * Tcl_GetVar --
- *
- * Return the value of a Tcl variable.
- *
- * Results:
- * The return value points to the current value of varName. If
- * the variable is not defined in interp, either as a local or
- * global variable, then a NULL pointer is returned.
- *
- * Note: the return value is only valid up until the next call to
- * Tcl_SetVar; if you depend on the value lasting longer than that,
- * then make yourself a private copy.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
- char *
- Tcl_GetVar(interp, varName, global)
- Tcl_Interp *interp; /* Command interpreter in which varName is
- * to be looked up. */
- char *varName; /* Name of a variable in interp. */
- int global; /* If non-zero, use only a global variable */
- {
- Var *varPtr;
- Interp *iPtr = (Interp *) interp;
-
- if (global || (iPtr->varFramePtr == NULL)) {
- varPtr = FindVar(&iPtr->globalPtr, varName);
- } else {
- varPtr = FindVar(&iPtr->varFramePtr->varPtr, varName);
- }
- if (varPtr == NULL) {
- return NULL;
- }
- if (varPtr->flags & VAR_GLOBAL) {
- varPtr = varPtr->globalPtr;
- }
- if (varPtr->value == NULL) {
- return "";
- }
- return varPtr->value;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_SetVar --
- *
- * Change the value of a variable.
- *
- * Results:
- * None.
- *
- * Side effects:
- * If varName is defined as a local or global variable in interp,
- * its value is changed to newValue. If varName isn't currently
- * defined, then a new global variable by that name is created.
- *
- *----------------------------------------------------------------------
- */
-
- void
- Tcl_SetVar(interp, varName, newValue, global)
- Tcl_Interp *interp; /* Command interpreter in which varName is
- * to be looked up. */
- char *varName; /* Name of a variable in interp. */
- char *newValue; /* New value for varName. */
- int global; /* If non-zero, use only a global variable. */
- {
- register Var *varPtr, **varListPtr;
- register Interp *iPtr = (Interp *) interp;
- int valueLength;
-
- if (global || (iPtr->varFramePtr == NULL)) {
- varListPtr = &iPtr->globalPtr;
- } else {
- varListPtr = &iPtr->varFramePtr->varPtr;
- }
- varPtr = FindVar(varListPtr, varName);
- if (varPtr == NULL) {
- varPtr = NewVar(varName, newValue);
- varPtr->nextPtr = *varListPtr;
- *varListPtr = varPtr;
- } else {
- if (varPtr->flags & VAR_GLOBAL) {
- varPtr = varPtr->globalPtr;
- }
- valueLength = strlen(newValue);
- if (valueLength > varPtr->valueLength) {
- if (varPtr->flags & VAR_DYNAMIC) {
- ckfree(varPtr->value);
- }
- varPtr->value = (char *) ckalloc((unsigned) valueLength + 1);
- varPtr->flags |= VAR_DYNAMIC;
- varPtr->valueLength = valueLength;
- }
- strcpy(varPtr->value, newValue);
- }
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_ParseVar --
- *
- * Given a string starting with a $ sign, parse off a variable
- * name and return its value.
- *
- * Results:
- * The return value is the contents of the variable given by
- * the leading characters of string. If termPtr isn't NULL,
- * *termPtr gets filled in with the address of the character
- * just after the last one in the variable specifier. If the
- * variable doesn't exist, then the return value is NULL and
- * an error message will be left in interp->result.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
- char *
- Tcl_ParseVar(interp, string, termPtr)
- Tcl_Interp *interp; /* Context for looking up variable. */
- register char *string; /* String containing variable name.
- * First character must be "$". */
- char **termPtr; /* If non-NULL, points to word to fill
- * in with character just after last
- * one in the variable specifier. */
-
- {
- char *name, c, *result;
-
- /*
- * There are two cases:
- * 1. The $ sign is followed by an open curly brace. Then the variable
- * name is everything up to the next close curly brace.
- * 2. The $ sign is not followed by an open curly brace. Then the
- * variable name is everything up to the next character that isn't
- * a letter, digit, or underscore.
- */
-
- string++;
- if (*string == '{') {
- string++;
- name = string;
- while ((*string != '}') && (*string != 0)) {
- string++;
- }
- if (termPtr != 0) {
- if (*string != 0) {
- *termPtr = string+1;
- } else {
- *termPtr = string;
- }
- }
- } else {
- name = string;
- while (isalnum(*string) || (*string == '_')) {
- string++;
- }
- if (termPtr != 0) {
- *termPtr = string;
- }
- }
-
- c = *string;
- *string = 0;
- result = Tcl_GetVar(interp, name, 0);
- if (!result) {
- Tcl_Return(interp, (char *) NULL, TCL_STATIC);
- sprintf(interp->result, "couldn't find variable \"%.50s\"", name);
- }
- *string = c;
- return result;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_SetCmd --
- *
- * This procedure is invoked to process the "set" Tcl command.
- * See the user documentation for details on what it does.
- *
- * Results:
- * A standard Tcl result value.
- *
- * Side effects:
- * A variable's value may be changed.
- *
- *----------------------------------------------------------------------
- */
-
- /* ARGSUSED */
- int
- Tcl_SetCmd(dummy, interp, argc, argv)
- ClientData dummy; /* Not used. */
- register Tcl_Interp *interp; /* Current interpreter. */
- int argc; /* Number of arguments. */
- char **argv; /* Argument strings. */
- {
- if (argc == 2) {
- char *value;
-
- value = Tcl_GetVar(interp, argv[1], 0);
- if (value == 0) {
- sprintf(interp->result, "couldn't find variable \"%.50s\"",
- argv[1]);
- return TCL_ERROR;
- }
- interp->result = value;
- return TCL_OK;
- } else if (argc == 3) {
- Tcl_SetVar(interp, argv[1], argv[2], 0);
- return TCL_OK;
- } else {
- sprintf(interp->result,
- "wrong # args: should be \"%.50s varName [newValue]\"",
- argv[0]);
- return TCL_ERROR;
- }
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_GlobalCmd --
- *
- * This procedure is invoked to process the "global" Tcl command.
- * See the user documentation for details on what it does.
- *
- * Results:
- * A standard Tcl result value.
- *
- * Side effects:
- * See the user documentation.
- *
- *----------------------------------------------------------------------
- */
-
- /* ARGSUSED */
- int
- Tcl_GlobalCmd(dummy, interp, argc, argv)
- ClientData dummy; /* Not used. */
- Tcl_Interp *interp; /* Current interpreter. */
- int argc; /* Number of arguments. */
- char **argv; /* Argument strings. */
- {
- register Var *varPtr;
- register Interp *iPtr = (Interp *) interp;
- Var *gVarPtr;
-
- if (argc < 2) {
- sprintf(iPtr->result,
- "too few args: should be \"%.50s varName varName ...\"",
- argv[0]);
- return TCL_ERROR;
- }
- if (iPtr->varFramePtr == NULL) {
- return TCL_OK;
- }
-
- for (argc--, argv++; argc > 0; argc--, argv++) {
- gVarPtr = FindVar(&iPtr->globalPtr, *argv);
- if (gVarPtr == NULL) {
- gVarPtr = NewVar(*argv, "");
- gVarPtr->nextPtr = iPtr->globalPtr;
- iPtr->globalPtr = gVarPtr;
- }
- varPtr = NewVar(*argv, "");
- varPtr->flags |= VAR_GLOBAL;
- varPtr->globalPtr = gVarPtr;
- varPtr->nextPtr = iPtr->varFramePtr->varPtr;
- iPtr->varFramePtr->varPtr = varPtr;
- }
- return TCL_OK;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * Tcl_UplevelCmd --
- *
- * This procedure is invoked to process the "uplevel" Tcl command.
- * See the user documentation for details on what it does.
- *
- * Results:
- * A standard Tcl result value.
- *
- * Side effects:
- * See the user documentation.
- *
- *----------------------------------------------------------------------
- */
-
- /* ARGSUSED */
- int
- Tcl_UplevelCmd(dummy, interp, argc, argv)
- ClientData dummy; /* Not used. */
- Tcl_Interp *interp; /* Current interpreter. */
- int argc; /* Number of arguments. */
- char **argv; /* Argument strings. */
- {
- register Interp *iPtr = (Interp *) interp;
- int level, result;
- char *end;
- CallFrame *savedVarFramePtr, *framePtr;
-
- if (argc < 3) {
- sprintf(iPtr->result,
- "too few args: should be \"%.50s level command ...\"",
- argv[0]);
- return TCL_ERROR;
- }
- level = strtol(argv[1], &end, 10);
- if ((end == argv[1]) || (*end != '\0')) {
- levelError:
- sprintf(iPtr->result, "bad level \"%.50s\"", argv[1]);
- return TCL_ERROR;
- }
-
- /*
- * Figure out which frame to use, and modify the interpreter so
- * its variables come from that frame.
- */
-
- savedVarFramePtr = iPtr->varFramePtr;
- if (level < 0) {
- if (savedVarFramePtr == NULL) {
- goto levelError;
- }
- level += savedVarFramePtr->level;
- }
- if (level == 0) {
- iPtr->varFramePtr = NULL;
- } else {
- for (framePtr = savedVarFramePtr; framePtr != NULL;
- framePtr = framePtr->callerVarPtr) {
- if (framePtr->level == level) {
- break;
- }
- }
- if (framePtr == NULL) {
- goto levelError;
- }
- iPtr->varFramePtr = framePtr;
- }
-
- /*
- * Execute the residual arguments as a command.
- */
-
- if (argc == 3) {
- result = Tcl_Eval(interp, argv[2], 0, (char **) NULL);
- } else {
- char *cmd;
-
- cmd = Tcl_Concat(argc-2, argv+2);
- result = Tcl_Eval(interp, cmd, 0, (char **) NULL);
- }
- if (result == TCL_ERROR) {
- char msg[60];
- sprintf(msg, " (\"uplevel\" body line %d)", interp->errorLine);
- Tcl_AddErrorInfo(interp, msg);
- }
-
- /*
- * Restore the variable frame, and return.
- */
-
- iPtr->varFramePtr = savedVarFramePtr;
- return result;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * TclFindProc --
- *
- * Given the name of a procedure, return a pointer to the
- * record describing the procedure.
- *
- * Results:
- * NULL is returned if the name doesn't correspond to any
- * procedure. Otherwise the return value is a pointer to
- * the procedure's record.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
- Proc *
- TclFindProc(iPtr, procName)
- Interp *iPtr; /* Interpreter in which to look. */
- char *procName; /* Name of desired procedure. */
- {
- Command *cmdPtr;
-
- cmdPtr = TclFindCmd(iPtr, procName, 0);
- if (cmdPtr == NULL) {
- return NULL;
- }
- if (cmdPtr->proc != InterpProc) {
- return NULL;
- }
- return (Proc *) cmdPtr->clientData;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * TclIsProc --
- *
- * Tells whether a command is a Tcl procedure or not.
- *
- * Results:
- * If the given command is actuall a Tcl procedure, the
- * return value is the address of the record describing
- * the procedure. Otherwise the return value is 0.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
- Proc *
- TclIsProc(cmdPtr)
- Command *cmdPtr; /* Command to test. */
- {
- if (cmdPtr->proc == InterpProc) {
- return (Proc *) cmdPtr->clientData;
- }
- return (Proc *) 0;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * TclDeleteVars --
- *
- * This procedure is called as part of deleting an interpreter:
- * it recycles all the storage space associated with global
- * variables (the local ones should already have been deleted).
- *
- * Results:
- * None.
- *
- * Side effects:
- * Variables are deleted.
- *
- *----------------------------------------------------------------------
- */
-
- void
- TclDeleteVars(iPtr)
- Interp *iPtr; /* Interpreter to nuke. */
- {
- register Var *varPtr;
-
- for (varPtr = iPtr->globalPtr; varPtr != NULL; varPtr = varPtr->nextPtr) {
- if (varPtr->flags & VAR_DYNAMIC) {
- ckfree(varPtr->value);
- }
- ckfree((char *) varPtr);
- }
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * InterpProc --
- *
- * When a Tcl procedure gets invoked, this routine gets invoked
- * to interpret the procedure.
- *
- * Results:
- * A standard Tcl result value, usually TCL_OK.
- *
- * Side effects:
- * Depends on the commands in the procedure.
- *
- *----------------------------------------------------------------------
- */
-
- int
- InterpProc(procPtr, interp, argc, argv)
- register Proc *procPtr; /* Record describing procedure to be
- * interpreted. */
- Tcl_Interp *interp; /* Interpreter in which procedure was
- * invoked. */
- int argc; /* Count of number of arguments to this
- * procedure. */
- char **argv; /* Argument values. */
- {
- char **args;
- register Var *formalPtr, *argPtr;
- register Interp *iPtr = (Interp *) interp;
- CallFrame frame;
- char *value, *end;
- int result;
-
- /*
- * Set up a call frame for the new procedure invocation.
- */
-
- iPtr = procPtr->iPtr;
- frame.varPtr = NULL;
- if (iPtr->varFramePtr != NULL) {
- frame.level = iPtr->varFramePtr->level + 1;
- } else {
- frame.level = 1;
- }
- frame.argc = argc;
- frame.argv = argv;
- frame.callerPtr = iPtr->framePtr;
- frame.callerVarPtr = iPtr->varFramePtr;
- iPtr->framePtr = &frame;
- iPtr->varFramePtr = &frame;
-
- /*
- * Match the actual arguments against the procedure's formal
- * parameters to compute local variables.
- */
-
- for (formalPtr = procPtr->argPtr, args = argv+1, argc -= 1;
- formalPtr != NULL;
- formalPtr = formalPtr->nextPtr, args++, argc--) {
-
- /*
- * Handle the special case of the last formal being "args". When
- * it occurs, assign it a list consisting of all the remaining
- * actual arguments.
- */
-
- if ((formalPtr->nextPtr == NULL)
- && (strcmp(formalPtr->name, "args") == 0)) {
- if (argc < 0) {
- argc = 0;
- }
- value = Tcl_Merge(argc, args);
- argPtr = NewVar(formalPtr->name, value);
- ckfree(value);
- argPtr->nextPtr = frame.varPtr;
- frame.varPtr = argPtr;
- argc = 0;
- break;
- } else if (argc > 0) {
- value = *args;
- } else if (formalPtr->value != NULL) {
- value = formalPtr->value;
- } else {
- sprintf(iPtr->result,
- "no value given for parameter \"%s\" to \"%s\"",
- formalPtr->name, argv[0]);
- result = TCL_ERROR;
- goto procDone;
- }
- argPtr = NewVar(formalPtr->name, value);
- argPtr->nextPtr = frame.varPtr;
- frame.varPtr = argPtr;
- }
- if (argc > 0) {
- sprintf(iPtr->result, "called \"%s\" with too many arguments",
- argv[0]);
- result = TCL_ERROR;
- goto procDone;
- }
-
- /*
- * Invoke the commands in the procedure's body.
- */
-
- result = Tcl_Eval(interp, procPtr->command, 0, &end);
- if (result == TCL_RETURN) {
- result = TCL_OK;
- } else if (result == TCL_ERROR) {
- char msg[100];
-
- /*
- * Record information telling where the error occurred.
- */
-
- sprintf(msg, " (procedure \"%.50s\" line %d)", argv[0],
- iPtr->errorLine);
- Tcl_AddErrorInfo(interp, msg);
- } else if (result == TCL_BREAK) {
- iPtr->result = "invoked \"break\" outside of a loop";
- result = TCL_ERROR;
- } else if (result == TCL_CONTINUE) {
- iPtr->result = "invoked \"continue\" outside of a loop";
- result = TCL_ERROR;
- }
-
- /*
- * Delete the call frame for this procedure invocation.
- */
-
- procDone:
- for (argPtr = frame.varPtr; argPtr != NULL; argPtr = argPtr->nextPtr) {
- if (argPtr->flags & VAR_DYNAMIC) {
- ckfree(argPtr->value);
- }
- ckfree((char *) argPtr);
- }
- iPtr->framePtr = frame.callerPtr;
- iPtr->varFramePtr = frame.callerVarPtr;
- return result;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * ProcDeleteProc --
- *
- * This procedure is invoked just before a command procedure is
- * removed from an interpreter. Its job is to release all the
- * resources allocated to the procedure.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Memory gets freed.
- *
- *----------------------------------------------------------------------
- */
-
- void
- ProcDeleteProc(procPtr)
- register Proc *procPtr; /* Procedure to be deleted. */
- {
- register Var *argPtr;
-
- ckfree((char *) procPtr->command);
- for (argPtr = procPtr->argPtr; argPtr != NULL; argPtr = argPtr->nextPtr) {
- if (argPtr->flags & VAR_DYNAMIC) {
- ckfree(argPtr->value);
- }
- ckfree((char *) argPtr);
- }
- ckfree((char *) procPtr);
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * FindVar --
- *
- * Locate the Var structure corresponding to varName, if there
- * is one defined in a given list.
- *
- * Results:
- * The return value points to the Var structure corresponding to
- * the current value of varName in varListPtr, or NULL if varName
- * isn't currently defined in the list.
- *
- * Side effects:
- * If the variable is found, it is moved to the front of the list.
- *
- *----------------------------------------------------------------------
- */
-
- Var *
- FindVar(varListPtr, varName)
- Var **varListPtr; /* Pointer to head of list. The value pointed
- * to will be modified to bring the found
- * variable to the front of the list. */
- char *varName; /* Desired variable. */
- {
- register Var *prev, *cur;
- register char c;
-
- c = *varName;
-
- /*
- * Local variables take precedence over global ones. Check the
- * first character immediately, before wasting time calling strcmp.
- */
-
- for (prev = NULL, cur = *varListPtr; cur != NULL;
- prev = cur, cur = cur->nextPtr) {
- if ((cur->name[0] == c) && (strcmp(cur->name, varName) == 0)) {
- if (prev != NULL) {
- prev->nextPtr = cur->nextPtr;
- cur->nextPtr = *varListPtr;
- *varListPtr = cur;
- }
- return cur;
- }
- }
- return NULL;
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * NewVar --
- *
- * Create a new variable with the given name and initial value.
- *
- * Results:
- * The return value is a pointer to the new variable. The variable
- * will not have been linked into any particular list, and its
- * nextPtr field will be NULL.
- *
- * Side effects:
- * Storage gets allocated.
- *
- *----------------------------------------------------------------------
- */
-
- Var *
- NewVar(name, value)
- char *name; /* Name for variable. */
- char *value; /* Value for variable. */
- {
- register Var *varPtr;
- int nameLength, valueLength;
-
- nameLength = strlen(name);
- valueLength = strlen(value);
- if (valueLength < 20) {
- valueLength = 20;
- }
- varPtr = (Var *) ckalloc(VAR_SIZE(nameLength, valueLength));
- strcpy(varPtr->name, name);
- varPtr->value = varPtr->name + nameLength + 1;
- strcpy(varPtr->value, value);
- varPtr->valueLength = valueLength;
- varPtr->flags = 0;
- varPtr->globalPtr = NULL;
- varPtr->nextPtr = NULL;
- return varPtr;
- }
-