home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Source Code 1992 March
/
Source_Code_CD-ROM_Walnut_Creek_March_1992.iso
/
usenet
/
altsrcs
/
0
/
0991
/
tclProc.c
next >
Wrap
C/C++ Source or Header
|
1990-12-28
|
23KB
|
896 lines
/*
* 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;
}