CCL Home Page
Up Directory CCL ScianScripts
/*ScianScripts.c
  Stuff for scripts in scian
  Eric Pepke
*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianArrays.h"
#include "ScianIDs.h"
#include "ScianEvents.h"
#include "ScianRecorders.h"
#include "ScianWindows.h"
#include "ScianObjWindows.h"
#include "ScianVisWindows.h"
#include "ScianScripts.h"
#include "ScianSliders.h"
#include "ScianTimers.h"
#include "ScianMethods.h"
#include "ScianLists.h"
#include "ScianErrors.h"
#include "ScianGarbageMan.h"
#include "ScianSpaces.h"
#include "ScianIcons.h"

Bool runningScript = false;		/*True iff running script*/
WinInfoPtr scriptWindow = 0;		/*The current window of the script*/
extern ObjPtr perspecControlClass;	/*Class of perspecive controls*/
FILE *logFile = 0;			/*File that we're logging to*/
Bool logging = false;			/*True iff logging*/
long framesLeft = 0;			/*Frames left to record*/
Bool abortScript = false;		/*Abort script*/

/*IDs for running tasks*/
#define RT_NONE		0		/*No running task*/
#define RT_RECORDING	1		/*Recording some time*/
int runningTask = RT_NONE;		/*Current running task*/

#define MAXSTACKDEPTH	100		/*Maximum depth of block stack*/
#define ERROBJ	((ObjPtr) -1)	/*Evil Satanic test value*/

typedef struct
    {
	int command;
    } Block;

Block commandStack[MAXSTACKDEPTH];	/*Stack of commands*/
int nextStackElement = 0;		/*Next element on the stack*/

#define KWBEGIN		0	/*Begin a block*/
#define KWEND		1	/*End current modal command*/
#define KWRECORDER	2	/*(set) recorder*/
#define KWDESELECT	3	/*Deselect an icon*/
#define KWSELECT	4	/*Select an icon*/
#define KWVISUALIZE	5	/*Do the visualize menu item*/
#define KWALIGNMENT	6	/*(Set) alignment*/
#define KWLOCATION	7	/*(Set) location*/
#define KWVIDEOSCREEN	8	/*Resize current window to video screen*/
#define KWFULLSCREEN	9	/*Resize current window to full screen*/
#define KWROTATE	10	/*Rotate space*/
#define KWSHOW		11	/*Show something*/
#define KWPANEL		12	/*(Show/Hide) the panel of icons*/
#define KWRECORDING	13	/*Begin recording*/
#define KWSNAP		14	/*Snap a single frame*/
#define KWWINDOW	15	/*Select a new window*/
#define KWSET		16	/*Set something*/
#define KWCONTROLS	17	/*(Show) controls for a vis object*/
#define KWPUSHWINDOW	18	/*Push a window behind all others*/
#define KWQUIT		19	/*Quit*/
#define KWSHEAR		20	/*Shear the matrix*/
#define KWFPS		21	/*(Set) fps*/
#define KWEYEPOSN	22	/*Eye position*/
#define KWROLL		23	/*Roll of eyeball*/
#define KWPITCH		24	/*Pitch of eyeball*/
#define KWYAW		25	/*Yaw of eyeball*/
#define KWCLONE		26	/*Clone an icon*/
#define KWHIDE		27	/*Hide the frame of a window*/
#define KWEXIT		28	/*Exit on next loop*/
#define KWVALUE		29	/*(Set) value of a something*/
#define KWFONT		30	/*(Set) the font of something*/
#define KWSIZE		31	/*(Set) the size of something's font*/
#define KWCLOSE		32	/*Closes a window*/
#define KWDRAG		33	/*Drags something*/
#define KWDROP		34	/*Drops whatever is in the drag buffer*/
#define KWBOUNDS	35	/*(Set) the bounds of an object*/
#define KWTILE		36	/*Tile to a certain width and height*/
#define KWANNOTATION	37	/*Add annotation*/
#define KWFRAME		38	/*(Show/Hide) the frame of a window*/
#define KWLOCATE	39	/*Locate a window*/
#define KWDELETE	40	/*Delete stuff*/
#define KWSCRSAVE	41	/*Save the screen*/
#define KWRECORD	42	/*Record some number of seconds*/
#define KWSCREEN	43	/*(Set) screen*/
#define KWCOLOR		44	/*(Set) color*/
#define KWPREFERENCES	45	/*(Show) preferences*/
#define KWDUPLICATE	46	/*Duplicate an icon, just like clone*/
#define KWOFF		47	/*(Turn) off*/
#define KWTURN		48	/*Turn (on/off)*/
#define KWON		49	/*(Turn) on*/
#define KWVISOBJECTSAS	50	/*Visualize objects as*/
#define KWFUNCTIONBOX	51	/*(Set) Function box*/
#define KWFILESWINDOW	52	/*(Show) FilesWindow*/
#define KWSELECTALL	53	/*Selectall*/
#define KWDESELECTALL	54	/*Deselectall*/
#define KWHELP		55	/*(Show) help*/
#define KWFILEREADERS   56	/*(Show) fileReaders*/
#define KWDATASETS	57	/*(Show) datasets*/
#define KWROTATION	58	/*(Set) rotation*/
#define KWLOCALCOPY	59	/*Make a local copy*/
#define KWFRONT		60	/*(Show) front (panel controls)*/
#define KWBACK		61	/*(Show) back (panel controls)*/
#define NKEYWORDS	62	/*Number of keywords*/


char *keywords[NKEYWORDS] =
	{
	    "begin",
	    "end",
	    "recorder",
	    "deselect",
	    "select",
	    "visualize",
	    "alignment",
	    "location",
	    "videoscreen",
	    "fullscreen",
	    "rotate",
	    "show",
	    "panel",
	    "recording",
	    "snap",
	    "window",
	    "set",
	    "controls",
	    "pushwindow",
	    "quit",
	    "shear",
	    "fps",
	    "eyeposn",
	    "roll",
	    "pitch",
	    "yaw",
	    "clone",
	    "hide",
	    "exit",
	    "value",
	    "font",
	    "size",
	    "close",
	    "drag",
	    "drop",
	    "bounds",
	    "tile",
	    "annotation",
	    "frame",
	    "locate",
	    "delete",
	    "scrsave",
	    "record",
	    "screen",
	    "color",
	    "preferences",
	    "duplicate",
	    "off",
	    "turn",
	    "on",
	    "visobjectsas",
	    "functionbox",
	    "fileswindow",
	    "selectall",
	    "deselectall",
	    "help",
	    "filereaders",
	    "datasets",
	    "rotation",
	    "localcopy",
	    "front",
	    "back"
	};

char *wholeLine;		/*Pointer to the whole line for error*/
char *begToken, *endToken;	/*Beginning and ending of current token*/
WinInfoPtr logWindow = 0;	/*Current log window*/
int logInhibit = 0;		/*Counter to inhibit log*/

#ifdef PROTO
Bool BeginCommand(char *);
Bool EndCommand(char *);
#else
Bool BeginCommand();
Bool EndCommand();
#endif

void Log(s)
char *s;
/*Logs command s*/
{
    if (logging && (logInhibit == 0))
    {
	if (selWinInfo && (logWindow != selWinInfo))
	{
	    /*Make a window command*/
	    char windowName[256];
	    int k;
	    char *s;

	    /*Copy window title*/
	    s = selWinInfo -> winTitle;
	    k = 0;
	    while (*s)
	    {
		if (!isalpha(*s) && ((k == 0) || !isdigit(*s)))
		{
		    windowName[k++] = '\\';
		}
		windowName[k++] = *s++;
	    }
	    windowName[k] = 0;
	    fprintf(logFile, "window %s\n", windowName);
	    logWindow = selWinInfo;
	}
	fputs(s, logFile);
    }
}

#ifdef PROTO
void InhibitLogging(Bool whether)
#else
void InhibitLogging(whether)
Bool whether;
#endif
/*Inhibits or disinhibits logging based on whether*/
{
    if (whether)
    {
	++logInhibit;
    }
    else
    {
	--logInhibit;
    }
}

void LogControl(object)
ObjPtr object;
{
    if (logging)
    {
	char cmd[256];
	char *s;

	sprintf(cmd, "set value ");
	s = &(cmd[0]);
	while (*s) ++s;
	MakeObjectName(s, object);
	while (*s) ++s;
	*s++ = ' ';
	PrintScriptObject(s, GetVar(object, VALUE));
	while (*s) ++s;
	*s++ = '\n';
	*s = 0;
	Log(cmd);
    }
}

void ScriptError(e)
char *e;
/*Prints out an error e*/
{
    char *runner;
    fprintf(stderr, "%s", wholeLine);

    runner = wholeLine;
    while (runner < begToken)
    {
	fprintf(stderr, *runner == '\t' ? "\t" : " ");
	++runner;
    }
    if (runner < endToken)
    {
	while (runner < endToken)
	{
	    fprintf(stderr, "-");
	    ++runner;
	}
    }
    else
    {
	fprintf(stderr, "^");
    }
    fprintf(stderr, "\n");
    fprintf(stderr, "%s\n", e);
    abortScript = true;
}

void SelectNamedWindow(name)
char *name;
/*Selects a window by name name*/
{
    WinInfoPtr namedWindow;
    namedWindow = GetWinFromTitle(name);
    if (!namedWindow)
    {
	ScriptError("Cannot find a unique window by that name.");
	return;
    }
    SelectWindow(namedWindow -> id);
    scriptWindow = namedWindow;
}

void MakeObjectName(dest, object)
char *dest;
ObjPtr object;
/*Makes a short object name from object and puts it into dest*/
{
    register char *s;
    ObjPtr name;

    name = GetStringVar("MakeObjectName", object, NAME);
    if (!name)
    {
	*dest = 0;
	return;
    }

    /*Copy name*/
    s = GetString(name);
    if (isdigit(*s))
    {
	*dest++ = '\\';
    }
    while (*s)
    {
	if (!isalpha(*s) && !isdigit(*s))
	{
	    *dest++ = '\\';
	}
	*dest++ = *s++;
    }
    *dest = 0;
}

void MakeFullObjectName(dest, window, object)
char *dest;
ObjPtr window, object;
/*Makes a full name for object within window and puts it into dest*/
{
    register char *s;
    ObjPtr name;

    name = GetStringVar("MakeFullObjectName", object, NAME);
    if (!name)
    {
	*dest = 0;
	return;
    }

    /*Copy title*/
    s = ((WinInfoPtr) window) -> winTitle;
    if (!isalpha(*s))
    {
	*dest++ = '\\';
    }
    while (*s)
    {
	if (!isalpha(*s) && !isdigit(*s))
	{
	    *dest++ = '\\';
	}
	*dest++ = *s++;
    }

    /*Add a colon*/
    *dest++ = ':';

    /*Copy name*/
    s = GetString(name);
    if (!isalpha(*s))
    {
	*dest++ = '\\';
    }
    while (*s)
    {
	if (!isalpha(*s) && !isdigit(*s))
	{
	    *dest++ = '\\';
	}
	*dest++ = *s++;
    }
    *dest = 0;
}

ObjPtr FindFullNamedObject(name)
char *name;
/*Finds the object with name name.  Syntax for name is
  [:]
  Does a complete depth-first search of the window for a single object with
  that name.  As a side effect, selects the window.
*/
{
    char littleName[256];
    int k;
    WinInfoPtr namedWindow;
    ObjPtr retVal, list;

    k = 0;
    while (*name && *name != ':')
    {
	if (*name == '\\')
	{
	    ++name;
	}
	littleName[k++] = *name++;
    }
    littleName[k] = 0;
    if (*name)
    {
	/*It's a full name Skip over colon*/
	++name;

	/*Find the window*/
	namedWindow = GetWinFromTitle(littleName);
	if (!namedWindow)
	{
	    ScriptError("Cannot find a unique window by that name.");
	    return NULLOBJ;
	}

	/*Select it*/
	SelectWindow(namedWindow -> id);
	scriptWindow = namedWindow;

	k = 0;
	while (*name && !isspace(*name))
	{
	    if (*name == '\\')
	    {
	        ++name;
	    }
	    littleName[k++] = *name++;
	}
	littleName[k] = 0;
    }
    else
    {
	/*It's only an object name.  Assume selected window*/
	namedWindow = scriptWindow;
	if (!namedWindow)
	{
	    ScriptError("There is no currently selected window.");
	    return ERROBJ;
	}
    }

    if (!strlen(littleName))
    {
	ScriptError("An object name is expected after the window name.");
	return ERROBJ;
    }
    list = FindNamedObject((ObjPtr) namedWindow, littleName);
    if (!list)
    {
	ScriptError("There is no object by that name in that window.");
	retVal = 0;
    }
    else
    {
	if (LISTOF(list) && !(LISTOF(list) -> next))
	{
	    retVal = LISTOF(list) -> thing;
	}
	else
	{
	    ScriptError("That name is not unique.");
	    retVal = 0;
	}
    }
    return retVal;
}

int ParseKeyword(s)
char *s;
/*Parses keyword s.  Returns its number or -1 == not found, -2 ==
  ambiguious.*/
{
    int match = -1;		/*Last found match*/
    int i, k;			/*Counters*/

    /*Seek an exact match*/
    for (k = 0; k < NKEYWORDS; ++k)
    {
	for (i = 0; s[i]; ++i)
	{
	    if (tolower(s[i]) != tolower(keywords[k][i]))
	    {
		break;
	    }
	}
	if (!s[i] && !keywords[k][i])
	{
	    /*It's a match!*/
	    return k;
	}
    }

    /*Seek a partial match*/
    for (k = 0; k < NKEYWORDS; ++k)
    {
	for (i = 0; s[i]; ++i)
	{
	    if (tolower(s[i]) != tolower(keywords[k][i]))
	    {
		break;
	    }
	}
	if (!s[i])
	{
	    /*It's a match!*/
	    if (match >= 0)
	    {
		/*Double match*/
		return -2;
	    }
	    match = k;
	}
    }
    return match;
}

#ifdef PROTO
char *ParseArray(char *s, ObjPtr *o)
#else
char *ParseArray(s, o)
char *s;
ObjPtr *o;
#endif
/*
    Parses an array at s
    Returns
	s if the object was syntactically bad or null
	the first character after the object if it was OK.
	Puts in o the object if it was semantically valid
        Leavs o alone if it was not
    Generates a script error if syntactically or semantically invalid.
    Doesn't yet do arrays of higher order than vectors by recursion
*/
{
    char *r;
    r = s;

    SKIPBLANKS(r);
    if (*r != '[')
    {
	ScriptError("This is not a valid array");
	return s;
    }

    ++r;
    SKIPBLANKS(r);

    /*Now, it's either some subvectors or a number*/
    if (*r == '[')
    {
	/*Subvector*/
	ScriptError("Arrays of higher order than vectors are not supported yet");
	return s;
    }
    else
    {
	/*Simple numerical vector.*/
	real *numBuf;		/*Temporary buffer for holding real numbers*/
	long bufSize;		/*Size of temporary buffer*/
	long vecSize;		/*Size of the vector so far*/
	char num[256];		/*Temporary number space*/

	/*Allocate some storage*/
	bufSize = 1000;
	vecSize = 0;
	numBuf = malloc(sizeof(real) * bufSize);
	if (!numBuf)
	{
	    OMErr();
	    return s;
	}

	while (*r != ']')
	{
	    SHIFTNUM(num, r);
	    if (1 != sscanf(num, "%g", numBuf + vecSize))
	    {
		ScriptError("This cannot be read as a number");
		free(numBuf);
		return s;
	    }
	    ++vecSize;
	    if (vecSize > bufSize)
	    {
		bufSize += 1000;
		numBuf = realloc(numBuf, bufSize);
		if (!numBuf)
		{
		    OMErr();
		    return s;
		}
	    }
	    SKIPBLANKS(r);
	}
	++r;

	/*Now make it into a vector*/
	*o = NewRealArray(1, vecSize);
	if (!o)
	{
	    free(numBuf);
	    return s;
	}
	CArray2Array(*o, numBuf);
	free(numBuf);

	return r;
    }
}

#ifdef PROTO
char *ParseObjectArg(char *s, ObjPtr *o)
#else
char *ParseObjectArg(s, o)
char *s;
ObjPtr *o;
#endif
/*
    Parses an object at s
    Returns
	s if the object was syntactically bad or null
	the first character after the object if it was OK.
	Puts in o the object if it was semantically valid
        Leavs o alone if it was not
    Generates a script error if syntactically or semantically invalid.
*/
{
    char arg[256];
    char *r;
    r = s;

    /*Get to the first character*/
    SKIPBLANKS(r);

    /*Based on one-char lookahead, parse the argument*/
    if (*r == '-' || *r == '+' || isdigit(*r))
    {
	double val;

	/*It's a number*/
	SHIFTFN(arg, r);
	if (1 == sscanf(arg, "%lg", &val))
	{
	    *o = NewReal((real) val);
	}
	else
	{
	    ScriptError("This number has bad syntax");
	    return s;
	}
    }
    else if (isalpha(*r) || (*r == '\\'))
    {
	/*It's an object name or NULLOBJ*/
	ObjPtr tempObj;
	SHIFTFN(arg, r);
	
	if (0 == strcmp2(arg, "NULLOBJ"))
	{
	    *o = NULLOBJ;
	}
	else
	{
	    tempObj = FindFullNamedObject(arg);
	    if (tempObj) *o = tempObj;
	}
    }
    else if (*r == '[')
    {
	return ParseArray(r, o);
    }
    else if (*r == '"')
    {
	int k;
	/*It's a string*/
	begToken = r;
	++r;
	k = 0;
	while (*r && *r != '"')
	{
	    if (*r == '\\')
	    {
		++r;
		switch (*r)
		{
		    case 'n':
		    case 'N':
			*r = '\n';
			break;
		    case 't':
		    case 'T':
			*r = '\t';
			break;
		}
	    }
	    tempStr[k++] = *r++;
	}
	if (*r == '"') ++r;
	endToken = r;
	tempStr[k] = 0;
	*o = NewString(tempStr);
    }
    else switch(*r)
    {
	case 0:		/*End of string, null object*/
	    *o = NULLOBJ;
	    break;
	default:	/*Who knows?  Error*/
	    ScriptError("This is an unrecognized object");
	    return s;
    }

    return r;
}  

Bool MatrixToText(s, m)
char *s;
Matrix m;
/*Converts matrix in m to text in s*/
{
    sprintf(s, "[[%g %g %g %g][%g %g %g %g][%g %g %g %g][%g %g %g %g]]",
	m[0][0], m[0][1], m[0][2], m[0][3],
	m[1][0], m[1][1], m[1][2], m[1][3],
	m[2][0], m[2][1], m[2][2], m[2][3],
	m[3][0], m[3][1], m[3][2], m[3][3]);
}

char *PrintScriptString(dest, s)
char *dest, *s;
/*Prints a script string from s into dest*/
{
	*dest++ = '"';
	while (*s)
	{
	    if (*s == '"')
	    {
		*dest++ = '\\';
	    }
	    if (*s == '\n')
	    {
		*dest++ = '\\';
		*dest++ = 'n';
	    }
	    else if (*s == '\t')
	    {
		*dest++ = '\\';
		*dest++ = 't';
	    }
	    else
	    {
		*dest++ = *s;
	    }
	    ++s;
	}
	*dest++ = '"';
	*dest = 0;
	return dest;
}

char *PrintScriptObject(s, object)
char *s;
ObjPtr object;
/*Prints object into s as for inclusion into a script.  Returns a pointer
  to the null afterward*/
{
    char *retVal;
    retVal = s;
    if (object == 0)
    {
	sprintf(retVal, "NULLOBJ");
	while (*retVal) ++retVal;
    }
    else if (IsReal(object))
    {
	/*Real number*/
	sprintf(retVal, "%g", (float) GetReal(object));
	while (*retVal) ++retVal;
    }
    else if (IsInt(object))
    {
	sprintf(retVal, "%ld", (long) GetInt(object));
	while (*retVal) ++retVal;
    }
    else if (IsArray(object) && RANK(object) == 1)
    {
	/*Vector*/
	int k;
	real *meat;

	*retVal++ = '[';
	meat = ArrayMeat(object);
	for (k = 0; k < DIMS(object)[0]; ++k)
	{
	    sprintf(retVal, "%g", *meat++);
	    while (*retVal) ++retVal;
	    if (k < DIMS(object)[0] - 1) *retVal++ = ' ';
	}
	*retVal++ = ']';
    }
    else if (IsString(object))
    {
	/*String*/
	retVal = PrintScriptString(retVal, GetString(object));
    }
    *retVal = 0;
    return retVal;
}

int FindCommandInStack(command)
int command;
/*Finds the topmost command command in the stack.  Returns it's index or -1
  if not found.*/
{
    int k;
    for (k = nextStackElement - 1; k >= 0; --k)
    {
	if (commandStack[k] . command == command)
	{
	    break;
	}
    }
    return k;
}

Bool BeginCommand(args)
char *args;
/*Begins a begin-end block with command in args.*/
{
    char cmdNum;
    char arg[256];

    if (nextStackElement >= MAXSTACKDEPTH)
    {
	ScriptError("There are too many nested begin-end blocks.");
	return true;
    }

    SHIFTKW(arg, args);

    if (strlen(arg))
    {
	cmdNum = ParseKeyword(arg);
	if (cmdNum == -1)
	{
	    ScriptError("This command is unknown.");
	    return true;
	}
	else if (cmdNum == -2)
	{
	    ScriptError("This command is ambiguous.");
	    return true;
	}
    }
    else
    {
	/*It's an anonymous block*/
	cmdNum = KWBEGIN;
    }
    switch (cmdNum)
    {
	case KWBEGIN:
	    break;
	case KWRECORDING:
	    if (FindCommandInStack(KWRECORDING) >= 0)
	    {
		ScriptError("The begin recording command cannot be nested.");
		return true;
	    }
	    else
	    {
		real nSeconds;
		
		SHIFTKW(arg, args);
		if (1 != sscanf(arg, "%g", &nSeconds))
		{
		    ScriptError("A maximum time in seconds is expected here.");
		    return true;
		}
		if (nSeconds < 0.0) nSeconds = -nSeconds;
		if (ConnectRecorder())
		{
		    int nFrames;
		    nFrames = (int) (nSeconds * 30.0);
		    if (!PrepareToRecord(nFrames))
		    {
			ScriptError("There is a problem recording that many frames.");
			return true;
		    }
		}
		else
		{
		    ScriptError("Could not connect to the recorder.");
		    return true;
		}
	    }
	    break;
	default:
	    ScriptError("This command may not start a begin-end block.");
	    break;
    }
    commandStack[nextStackElement] . command = cmdNum;
    ++nextStackElement;
    return;
}

Bool EndCommand(args)
char *args;
/*Ends the top command on the stack, or up to command in args*/
{
    char arg[256];
    if (!nextStackElement)
    {
	ScriptError("There are too many ends and not enough begins.");
	return true;
    }
    SHIFTKW(arg, args);
    if (strlen(arg))
    {
	int cmdNum, cmdWhere;
	cmdNum = ParseKeyword(arg);
	cmdWhere = FindCommandInStack(cmdNum);
	if (cmdWhere < 0)
	{
	    ScriptError("There is no such block in force.");
	    return true;
	}
	else
	{
	    do
	    {
		EndCommand("");
	    } while (commandStack[nextStackElement] . command != cmdNum);
	}
    }
    else
    {
	/*End the top command*/
	--nextStackElement;
	switch(commandStack[nextStackElement] . command)
	{
	    case KWRECORDING:
		StopRecording();
		DisconnectRecorder();
	}
    }
    return true;
}

Bool DoSetCommand(kwn, args)
int kwn;
char *args;
/*Does a set command*/
{
    char arg[256];

    switch(kwn)
    {
	case KWRECORDER:
	    {
		ObjPtr object;
		if (FindCommandInStack(KWRECORDING) >= 0)
		{
		    ScriptError("You can't set the recorder while recording");
		    break;
		}
		object = ERROBJ;
		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsString(object))
		    {
			if (!SetRecorder(GetString(object)))
			{
			    ScriptError("This recorder does not exist");
			}
		    }
		    else
		    {
			ScriptError("A quoted name of a recorder is expected here");
		    }
		}
	    }
	    break;
	case KWVALUE:
	    {
		ObjPtr object, value;
		object = ERROBJ;

		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (object && IsObject(object))
		    {
			value = ERROBJ;
			args = ParseObjectArg(args, &value);
			if (value != ERROBJ)
			{
			    SetValue(object, value);
			}
			else
			{
			    ScriptError("A valid value is expected here");
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWROTATION:
	    {
		ObjPtr rotation, axis;
		rotation = ERROBJ;

		args = ParseObjectArg(args, &rotation);
		if (rotation != ERROBJ)
		{
		    if (rotation && IsReal(rotation))
		    {
			axis = ERROBJ;
			args = ParseObjectArg(args, &axis);
			if (axis != ERROBJ && axis &&
			    IsArray(axis) && RANK(axis) == 1 && DIMS(axis)[0] == 3)
			{
			    real *elements;
			    real degrees;
			    elements = ELEMENTS(axis);
			    degrees = GetReal(rotation);
			    SetRotationMotor(degrees * M_PI / 180.0, elements[0], elements[1], elements[2]);
			}
			else
			{
			    ScriptError("A rotation axis is expected here");
			}
		    }
		    else
		    {
			ScriptError("A rotation speed is expected here");
		    }
		}
	    }
	    break;
	case KWFUNCTIONBOX:
	    {
		ObjPtr object, functionBox;
		object = ERROBJ;

		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			functionBox = ERROBJ;
			args = ParseObjectArg(args, &functionBox);
			if (functionBox != ERROBJ)
			{
			    SetFunctionBox(object, functionBox);
			    InhibitLogging(true);
			    ImposeColorFunction(object);
			    ForAllVisWindows(ImInvalid);
			    InhibitLogging(false);
			}
			else
			{
			    ScriptError("A valid function box is expected here");
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWCOLOR:
	    {
		ObjPtr object, which, r, g, b;
		object = ERROBJ;

		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			if (4 == sscanf(args, " %d %d %d %d \n", &which, &r, &g, &b))
			{
			    SetColorBarColor(object, which, r, g, b);
			}
			else
			{
			    ScriptError("A color number and components are expected here");
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWFONT:
	    {
		ObjPtr object, value;
		object = ERROBJ;
		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			SHIFTFN(arg, args);
			SetFont(object, arg);
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWSIZE:
	    {
		ObjPtr object, value;
		object = ERROBJ;
		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			int size;
			SHIFTNUM(arg, args);
			if (1 == sscanf(arg, "%d", &size))
			{
			    SetSize(object, size);
			}
			else
			{
			    ScriptError("A font size is expected here");
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWALIGNMENT:
	    {
		ObjPtr object, value;
		object = ERROBJ;
		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			int alignment;
			SHIFTNUM(arg, args);
			if (1 == sscanf(arg, "%d", &alignment))
			{
			    SetAlignment(object, alignment);
			}
			else
			{
			    ScriptError("An integer text alignment is expected here");
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWLOCATION:
	    {
		ObjPtr object, value;
		object = ERROBJ;
		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object) && (object = GetVar(object, REPOBJ)))
		    {
			real location[3];
			if (3 == sscanf(args, " [%g %g %g]", 
				&(location[0]),
				&(location[1]),
				&(location[2])))
			{
			    ObjPtr var;
			    var = NewRealArray(1, 3L);
			    CArray2Array(var, location);
			    SetVar(object, LOCATION, var);
			    if (InClass(object, controllerClass))
			    {
				ResolveController(object);
			    }
			    else
			    {
				ImInvalid(object);
			    }
			    if (logging)
			    {
				char cmd[300];
				char *s;
				sprintf(cmd, "set location ");

				s = &(cmd[0]);
				while (*s) ++s;
				MakeObjectName(s, object);
				while (*s) ++s;
				sprintf(s, " [%g %g %g]\n",
					location[0],
					location[1],
					location[2]);
				Log(cmd);
			    }
			}
			else
			{
			    ScriptError("A vector location is expected here");
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWBOUNDS:
	    {
		ObjPtr object;
		ObjPtr bounds;
		char *oldArgs;
		object = ERROBJ;
		oldArgs = args;
		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			bounds = ERROBJ;
			args = ParseObjectArg(args, &bounds);
			if (bounds != ERROBJ)
			{
			    if (IsArray(bounds) && RANK(bounds) == 1)
			    {
				ObjPtr testBounds;
				testBounds = GetVar(object, BOUNDS);
				if (!testBounds ||
				    (IsArray(testBounds) && 
				     RANK(testBounds) == 1 &&
				     DIMS(testBounds)[0] == DIMS(bounds)[0]))
				{
				    if (logging)
				    {
					char cmd[256];
					sprintf(cmd, "set bounds %s", oldArgs);
					Log(cmd);
				    }
			    	    SetVar(object, BOUNDS, bounds);
				    ImInvalid(object);
				}
				else
				{
				    ScriptError("These bounds are of wrong size for the object");
				}
			    }
			    else
			    {
				ScriptError("A vector is expected here");
			    }
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWSCREEN:
	    {
		/*Set the size of the recording screen*/
		int x, y;
		if (2 != sscanf(args, " %d %d", &x, &y))
		{
		    ScriptError("A width and height of the recording screen is expected here");
		    break;
		}
		recScrWidth = x;
		recScrHeight = y;
	    }
	    break;
	case KWFPS:
	    {
		/*Set the frames per second*/
		real fps;
		if (1 != sscanf(args, " %g", &fps))
		{
		    ScriptError("A number of frames per second is expected here");
		    break;
		}
		SetFPS(fps);
	    }
	    break;
	default:
	    ScriptError("This is not a paramater that can be set");
	    break;
    }
    return true;
}

Bool DoShowHideCommand(showp, kwn, args)
Bool showp;
int kwn;
char *args;
/*Does a show or hide command, showp is true iff it's show*/
{
    char arg[256];

    switch(kwn)
    {
	case KWPANEL:
	    if (showp)
	    {
		DoShowPanel();
	    }
	    else
	    {
		DoHidePanel();
	    }
	    break;
	case KWCONTROLS:
	    if (showp)
	    {
		DoShowControls();
	    }
	    else
	    {
		ScriptError("You can't hide controls this way.  Close the window instead");
	    }
	    break;
	case KWPREFERENCES:
	    if (showp)
	    {
		DoShowPreferences();
	    }
	    else
	    {
		ScriptError("You can't hide preferences this way.  Close the window instead");
	    }
	    break;
	case KWHELP:
	    if (showp)
	    {
		DoShowHelp();
	    }
	    else
	    {
		ScriptError("You can't hide help this way.  Close the window instead");
	    }
	    break;
	case KWDATASETS:
	    if (showp)
	    {
		PopDatasetsWindow();
	    }
	    else
	    {
		ScriptError("You can't hide the datasets window this way.  Close the window instead");
	    }
	    break;
	case KWFILEREADERS:
	    if (showp)
	    {
		PopFileReadersWindow();
	    }
	    else
	    {
		ScriptError("You can't hide the file readers window this way.  Close the window instead");
	    }
	    break;
	case KWFILESWINDOW:
	    if (showp)
	    {
		DoNewFileWindow();
	    }
	    else
	    {
		ScriptError("You can't hide the file window this way.  Close the window instead");
	    }
	    break;
	case KWFRAME:
	    if (showp)
	    {
		DoShowFrame();
	    }
	    else
	    {
		DoHideFrame();
	    }
	    break;
	case KWFRONT:
	    if (showp)
	    {
		DoShowFrontPanelControls();
	    }
	    else
	    {
		ScriptError("You can't hide the front panel controls this way.  Close the window instead");
	    }
	    break;
	case KWBACK:
	    if (showp)
	    {
		DoShowBackPanelControls();
	    }
	    else
	    {
		ScriptError("You can't hide the back panel controls this way.  Close the window instead");
	    }
	    break;
	default:
	    ScriptError("This keyword is not a settable parameter");
	    break;
    }
    return true;
}

Bool DoScriptCommand(kwn, args)
int kwn;
char *args;
/*Does command kwn with args*/
{
    char arg[256];

    switch(kwn)
    {
	case KWDELETE:
	    DoDelete();
	    break;
	case KWTURN:
	    /*Turn on/off*/
	    {
		int kwn;
		SHIFTKW(arg, args);
		kwn = ParseKeyword(arg);
		if (kwn == KWON)
		{
		    DoTurnOn();
		}
		else if (kwn == KWOFF)
		{
		    DoTurnOff();
		}
		else
		{
		    ScriptError("Either 'on' or 'off' is expected here");
		}
	    }
	    break;
	case KWSELECT:
	    {
		ObjPtr object;
		object = ERROBJ;
		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			FuncTyp method;
			method = GetMethod(object, SELECT);
			if (method)
			{
			    (*method)(object, true);
			}
			else
			{
			    ScriptError("This object has no SELECT method");
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWDESELECT:
	    {
		ObjPtr object;
		object = ERROBJ;
		args = ParseObjectArg(args, &object);
		if (object != ERROBJ)
		{
		    if (IsObject(object))
		    {
			FuncTyp method;
			method = GetMethod(object, SELECT);
			if (method)
			{
			    (*method)(object, false);
			}
			else
			{
			    ScriptError("This object has no SELECT method");
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
		    }
		}
	    }
	    break;
	case KWSELECTALL:
	    DoSelectAllIcons();
	    break;
	case KWDESELECTALL:
	    DoDeselectAllIcons();
	    break;
	case KWANNOTATION:
	    {
		char name[256];
		ObjPtr bounds;
		SHIFTKW(name, args);
		if (strlen(name))
		{
		    bounds = ERROBJ;
		    args = ParseObjectArg(args, &bounds);
		    if (bounds != ERROBJ)
		    {
			if (IsArray(bounds) && RANK(bounds) == 1 &&
				DIMS(bounds)[0] == 4)
			{
			    AddAnnotation(name, ArrayMeat(bounds));
			}
			else
			{
			    ScriptError("A 4-vector is expected here");
			}
		    }
		}
		else
		{
		    ScriptError("A name is expected here");
		}
	    }
	    break;
	case KWDRAG:
	    {
		ObjPtr object;
		ObjPtr list;
		list = NewList();
		do
		{
		    object = ERROBJ;
		    args = ParseObjectArg(args, &object);
		    if (object != ERROBJ)
		    {
			if (object && IsObject(object))
			{
			    PostfixList(list, object);
			}
		    }
		    else
		    {
			ScriptError("An object name is expected here");
			break;
		    }
		} while (object);

		dragBuffer = list;
		AddToReferenceList(list);
		if (logging)
		{
		    char cmd[400];
		    char *s;
		    ThingListPtr runner;

		    sprintf(cmd, "drag ");
		    s = &(cmd[0]);
		    while (*s) ++s;
		    runner = LISTOF(list);
		    while (runner)
		    {
			*s++ = ' ';
			MakeObjectName(s, runner -> thing);
			while (*s) ++s;
			runner = runner -> next;
		    }
		    *s++ = '\n';
		    *s = 0;
		    Log(cmd);
		}
	    }
	    break;
	case KWSET:			/*Set something or other*/
	    {
		int k;
		SHIFTKW(arg, args);
		/*Find out what to set*/
		kwn = ParseKeyword(arg);
		if (kwn == -1)
		{
		    ScriptError("This parameter is unknown.");
		    break;
		}
		else if (kwn == -2)
		{
		    ScriptError("This parameter is ambiguous.");
		    break;
		}
		else
		{
		    return DoSetCommand(kwn, args);
		}
	    }
	    break;
	case KWSHOW:			/*Show something or other*/
	    {
		int k;
		SHIFTKW(arg, args);
		/*Find out what to set*/
		kwn = ParseKeyword(arg);
		if (kwn == -1)
		{
		    ScriptError("This parameter is unknown.");
		    break;
		}
		else if (kwn == -2)
		{
		    ScriptError("This parameter is ambiguous.");
		    break;
		}
		else
		{
		    return DoShowHideCommand(true, kwn, args);
		}
	    }
	    break;
	case KWHIDE:			/*Hide something or other*/
	    {
		int k;
		SHIFTKW(arg, args);
		/*Find out what to set*/
		kwn = ParseKeyword(arg);
		if (kwn == -1)
		{
		    ScriptError("This parameter is unknown.");
		    break;
		}
		else if (kwn == -2)
		{
		    ScriptError("This parameter is ambiguous.");
		    break;
		}
		else
		{
		    return DoShowHideCommand(false, kwn, args);
		}
	    }
	    break;
	case KWDROP:
	    {
		int dropX, dropY;
		
		SHIFTNUM(arg, args);
		if (1 != sscanf(arg, "%d", &dropX))
		{
		    ScriptError("A number is expected here");
		    break;
		}

		SHIFTNUM(arg, args);
		if (1 != sscanf(arg, "%d", &dropY))
		{
		    ScriptError("A number is expected here");
		    break;
		}

		if (scriptWindow && dragBuffer)
		{
		    FuncTyp method;
		    /*Drop*/
		    method = GetMethod((ObjPtr) scriptWindow, DROPOBJECTS);
		    if (method)
		    {
			iconXOff = 0;
			iconYOff = 0;
			if (logging)
			{
			    char cmd[256];
			    sprintf(cmd, "drop %d %d\n", dropX, dropY);
			    Log(cmd);
			    InhibitLogging(true);
			}
			(*method)(scriptWindow, dragBuffer, dropX, dropY);
			DeleteThing(dragBuffer);
			dragBuffer = NULLOBJ;
			if (logging)
			{
			    InhibitLogging(false);
			}
		    }
		}
		else
		{
		    ScriptError("There is no selected window");
		}
	    }
	    break;
	case KWVISUALIZE:
	    VisObjects();
	    break;
	case KWVISOBJECTSAS:
	    VisObjectsAs();
	    break;
#if 0
	case KWRGBMODE:
	    {
		ObjPtr radio = NULLOBJ;
		if (scriptWindow)
		{
		    radio = GetVar((ObjPtr) scriptWindow, CMODERADIO);
		}
		if (radio)
		{
		    SetValue(radio, NewInt(1));
		}
		else
		{
		    ScriptError("Only a visualization window can be changed to RGB mode");
		}
	    }
	    break;
	case KWCMAPMODE:
	    {
		ObjPtr radio = NULLOBJ;
		if (scriptWindow)
		{
		    radio = GetVar((ObjPtr) scriptWindow, CMODERADIO);
		}
		if (radio)
		{
		    SetValue(radio, NewInt(0));
		}
		else
		{
		    ScriptError("Only a visualization window can be changed to color map mode");
		}
	    }
	    break;
#endif
	case KWVIDEOSCREEN:
	    DoVideoScreen();
	    break;
	case KWFULLSCREEN:
	    DoMaxScreen();
	    break;
	case KWTILE:
	    {
		int width, height;

		SHIFTNUM(arg, args);
		if (1 != sscanf(arg, "%d", &width))
		{
		    ScriptError("A width is expected here");
		    break;
		}

		SHIFTNUM(arg, args);
		if (1 != sscanf(arg, "%d", &height))
		{
		    ScriptError("A height is expected here");
		    break;
		}

		Tile(width, height);
	    }
	    break;
	case KWLOCATE:
	    {
		int l, r, b, t;
		if (4 != sscanf(args, " %d %d %d %d", &l, &r, &b, &t))
		{
		    ScriptError("The bounds of the window are expected here.");
		    return true;
		}
		LocateWindow(l, r, b, t);
	    }
	    break;
	case KWEYEPOSN:
	    {
		real p[3];
		if (3 != sscanf(args, " [%g %g %g]",
			&(p[0]), &(p[1]), &(p[2])))
		{
		    ScriptError("Syntax error in matrix.");
		    return true;
		}
		if (scriptWindow)
		{
		    ObjPtr array, observer /*, observers*/;
		    observer = FindObserver(scriptWindow);
			if (!observer) break;

			array = NewRealArray(1, 3L);
			CArray2Array(array, p);
			SetVar(observer, LOCATION, array);
			if (logging)
			{
			    char cmd[256];
			    sprintf(cmd, "eyeposn [%g %g %g]\n",
				p[0], p[1], p[2]);
			    Log(cmd);
			}
			ChangeFocus(observer);
		}
	    }
	    break;
	case KWROLL:
	    {
		float value;
		SHIFTKW(arg, args);
		if (strlen(arg) && 1 == sscanf(arg, " %g", &value))
		{
		    if (scriptWindow)
		    {
			ObjPtr observer /*, observers*/;
			observer = FindObserver(scriptWindow);
			    if (!observer) break;

			    SetVar(observer, ROLL, NewReal(value));
			    ChangeFocus(observer);
		    }
		}
		else
		{
		    ScriptError("An amount to roll is expected here.");
		}
	    }
	    break;
	case KWPITCH:
	    {
		float value;
		SHIFTKW(arg, args);
		if (strlen(arg) && 1 == sscanf(arg, " %g", &value))
		{
		    if (scriptWindow)
		    {
			ObjPtr observer/*, observers*/;
			observer = FindObserver(scriptWindow);
			    if (!observer) break;

			    SetVar(observer, PITCH, NewReal(value));
			    ChangeFocus(observer);
		    }
		}
		else
		{
		    ScriptError("An amount to pitchis expected here.");
		}
	    }
	    break;
	case KWYAW:
	    {
		float value;
		SHIFTKW(arg, args);
		if (strlen(arg) && 1 == sscanf(arg, " %g", &value))
		{
		    if (scriptWindow)
		    {
			ObjPtr observer/*, observers*/;
			observer = FindObserver(scriptWindow);
			    if (!observer) break;

			    if (!observer) break;

			    SetVar(observer, YAW, NewReal(value));
			    ChangeFocus(observer);
		    }
		}
		else
		{
		    ScriptError("An amount to yaw is expected here.");
		}
	    }
	    break;
	case KWROTATE:
	    {
		char axis;
		float amount;

		SHIFTKW(arg, args);
		if (strlen(arg) == 1 && (*arg == 'x' || *arg == 'y' || *arg == 'z'))
		{
		    axis = *arg;	

		    SHIFTKW(arg, args);
		    if (!strlen(arg) || 1 != sscanf(arg, "%f", &amount))
		    {
			ScriptError("An amount to rotate is expected here.");
			return true;
		    }

		    if (scriptWindow)
		    {
			RotateOrthoWindow(scriptWindow, axis, amount);
		    }
		}
		else if (0 == strcmp2(arg, "to"))
		{
		    /*Rotate to a matrix*/
		    Matrix m;
		    if (16 != sscanf(args, " [[%g %g %g %g][%g %g %g %g][%g %g %g %g][%g %g %g %g]]",
			&(m[0][0]), &(m[0][1]), &(m[0][2]), &(m[0][3]),
			&(m[1][0]), &(m[1][1]), &(m[1][2]), &(m[1][3]),
			&(m[2][0]), &(m[2][1]), &(m[2][2]), &(m[2][3]),
			&(m[3][0]), &(m[3][1]), &(m[3][2]), &(m[3][3])))
		    {
			ScriptError("Syntax error in matrix.");
			return true;
		    }
		    if (scriptWindow)
		    {
			RotateWindowTo(scriptWindow, m);
		    }
		    else
		    {
			ScriptError("No current window.");
		    } 
		}
		else
		{
		    ScriptError("Unrecognized keyword.  An axis or 'to' is expected.");
		}
		return true;
	    }
	    break;
	case KWSHEAR:
	    {
		char axis;
		float amount;

		SHIFTKW(arg, args);
		if (!strlen(arg) || 1 != sscanf(arg, "%f", &amount))
		{
		    ScriptError("An amount to shear is expected here.");
		    return true;
		}

		if (scriptWindow)
		{
		    ShearOrthoWindow(scriptWindow, amount);
		}
	    }
	    break;
	case KWRECORD:
	    {
		real nSecs;
		if (1 != sscanf(args, " %g", &nSecs))
		{
		    
		    ScriptError("A number of seconds to record is expected here.");
		}
		else
		{
		    runningTask = RT_RECORDING;
		    framesLeft = nSecs * 30 + 0.5;
		}
	    }
	    return false;
	case KWBEGIN:
	    return BeginCommand(args);
	case KWEND:
	    return EndCommand(args);
	case KWSNAP:
	    if (!SnapOneFrame())
	    {
		ScriptError("Could not snap a single frame.");
	    }
	    ++TrashDayFlag;
	    break;
	case KWWINDOW:
	    SHIFTKW(arg, args);
	    if (!strlen(arg))
	    {
		ScriptError("The name of a window is expected here");
		return true;
	    }
	    SelectNamedWindow(arg);
	    break;
	case KWCLONE:
	case KWDUPLICATE:
	    DoCloneIcon();
	    break;
	case KWLOCALCOPY:
	    DoMakeLocalCopy();
	    break;
	case KWEXIT:
	case KWQUIT:
	    DoQuit();
	    break;
	case KWCLOSE:
	    DisposeWindow(scriptWindow -> id);
	    SelectWindow(0);
	    scriptWindow = 0;
	    break;
	case KWPUSHWINDOW:
	    if (scriptWindow && scriptWindow -> id)
	    {
		winset(scriptWindow -> id);
	        winpush();
	    }
	    else
	    {
		ScriptError("There is no current window");
	    }
	    break;
	case KWSCRSAVE:
	    {
		char fileName[256];
		int l, r, b, t;
		int n;
		n = sscanf(args, " %s %d %d %d %d", fileName, &l, &r, &b, &t);
		if (n == 5)
		{
		    sprintf(tempStr, "scrsave %s %d %d %d %d", fileName, l, r, b, t);
		    system(tempStr);
		}
		else if (n == 1)
		{
		    sprintf(tempStr, "scrsave %s", fileName);
		    system(tempStr);
		}
		else
		{
		    ScriptError("A filename and four sides are expected here");
		    return true;
		}
	    }
	    break;
	default:
	    ScriptError("This keyword is not a command");
	    break;
    }
    return true;
}

void BeginScript()
/*Begins a new script*/
{
    if (nextStackElement)
    {
	EndScript();
    }
}

void EndScript()
/*Ends a script*/
{
    while (nextStackElement)
    {
	EndCommand("");
    }
}

static Bool ScriptTask()
/*Run the current script task*/
{
    switch(runningTask)
    {
	case RT_RECORDING:
	    if (framesLeft <= 0)
	    {
		runningTask = 0;
		return true;
	    }
	    if (!SnapOneFrame())
	    {
		ScriptError("Could not snap a single frame.");
	    }
	    ++TrashDayFlag;
	    --framesLeft;
	    break;
    }
    return false;
}

Bool InterpretScriptLine(s)
char *s;
/*Interprets a script line in character string s.  Returns true iff the script
  line needs to be thrown away*/
{
    char curKeyword[256];
    int kwn;
    Bool retVal;

    if (runningTask != RT_NONE)
    {
	/*There's a running task*/
	return ScriptTask();
    }

    /*Set up whole line for script stuff*/
    wholeLine = s;

    /*Look at first keyword*/
    SKIPBLANKS(s);
    if (*s == '#')
    {
	if (logging)
	{
	    Log(wholeLine);
	}
	return true;
    }
    SHIFTKW(curKeyword, s);

    if (!strlen(curKeyword))
    {
	retVal = true;
    }
    else
    {
	runningScript = true;
	if (scriptWindow)
	{
	    SelectWindow(scriptWindow -> id);
	}

	/*Find out what it is*/
	kwn = ParseKeyword(curKeyword);
	if (kwn == -1)
	{
	    ScriptError("This command is unknown.");
	    retVal = true;
	}
	else if (kwn == -2)
	{
	    ScriptError("This command is ambiguous.");
	    retVal = true;
	}
	else
	{
	    retVal = DoScriptCommand(kwn, s);
	}
	runningScript = false;
    }
    return retVal;
}

  
Modified: Sun Nov 17 17:00:00 1996 GMT
Page accessed 2607 times since Sat Apr 17 21:54:17 1999 GMT