ftp.nice.ch/pub/next/developer/languages/cows/COWS.1.4.s.tar.gz#/COWS/Subprojects/COWS.subproj/COWSInterpreter.m

This is COWSInterpreter.m in view mode; [Download] [Up]

/*
	Copyright (C) 1994 Sean Luke

	COWSInterpreter.m
	Version 1.4
	Sean Luke
	
*/




#import "COWSInterpreter.h"
#import "COWSSymbolNode.h"
#import "COWSLibraryFunctionNode.h"
#import "COWSStateNode.h"
#import "COWSLibrary.h"
#import "COWSArgumentList.h"



// Convenience String Functions
// These functions are used by the interpreter and by other objects 
// (most notably COWSStringNode) to generate new strings from old ones.


char* newstr(const char* ct)		

/*	
New-string version of strcpy.  Assumes ct is properly \0-terminated.  Allocates enough memory (1 more than length of ct), then copies ct into the new memory area.  Returns a pointer to the new area.
*/
	{
	char* t=malloc(strlen(ct)+1);
	strcpy(t,ct);
	return t;
	}
	


char* newstrn(const char* ct,int size)

/*
New-string version of strncpy.  Allocates enough memory (1 more than length of ct), then copies size characters of ct into the new memory area, tacking on a \0 at the end to complete the new string.  Returns a pointer to the new area.
*/
	{
	char* t=malloc(size+1);
	strncpy(t,ct,size);
	t[size]='\0';
	return t;
	}





// Timed Entry

DPSTimedEntryProc _COWS_timer
	(DPSTimedEntry teNum, double now, void* interpreter)
	{
	COWSInterpreter* the_interpreter=(COWSInterpreter*) interpreter;
	[the_interpreter _go];
	return (void*) NULL;
	}










@implementation COWSInterpreter

- init
	{
	inited=YES;
	program=[[COWSStringNode alloc] init];
	stack=[[COWSStack alloc] init];
	running=NO;
	foreground=NO;
	locked=NO;
	printing_errors=YES;
	delegate=NULL;
	tempDelegate=NULL;
	function_completed=NO;
	repeats=100;
	teNum=0;
	te_speed=0.1;
	current_dictionary=NULL;
	current_function=[[COWSStringNode alloc] init];
	current_position=0;
	library_dictionary=[[HashTable alloc] initKeyDesc:"*"valueDesc:"@"];
 	function_dictionary=[[HashTable alloc] initKeyDesc:"*"valueDesc:"@"];
	global_dictionary=[[HashTable alloc] initKeyDesc:"*"valueDesc:"@"];
	library_delegates=[[COWSArgumentList alloc] init];
	return [super init];
	}
	
- awake
	{
	if (!inited) return [self init];
	return self;
	}	
	
	
- printDictionaries:sender
// prints each dictionary.  Internal function dictionary is printed along with
// variables and arguments for each function.

	{
	const void * key;
	const char *value;
	id value_node;
	NXHashState state=[global_dictionary initState];
	printf ("GLOBAL VARIABLES\n\n");
	while ([global_dictionary nextState: &state key: &key value: 
		(void*) &value_node])
		{
		value=[value_node string];
		printf ("KEY:   #%s#\nVALUE: #%s#\n",(char*) key, 
			(const char*) value);
		}
	state=[function_dictionary initState];
	printf ("\n\nINTERNAL FUNCTIONS\n\n");
	while ([function_dictionary nextState: &state key: &key value: 
		(void*) &value_node])
		{
		value=[value_node string];
		printf ("KEY:   #%s#\nVALUE: #%s#\n",(char*) key, 
			(const char*) value);
		[value_node printContents];
		}
	state=[library_dictionary initState];
	printf ("\n\nLIBRARY FUNCTIONS\n\n");
	while ([library_dictionary nextState: &state key: &key value: 
		(void*) &value_node])
		{
		printf ("KEY:   #%s#\n",(char*) key);
		}
	printf ("\n\nLIBRARY DELEGATES\n\n");
	while ([library_dictionary nextState: &state key: &key value: 
		(void*) &value_node])
		{
		printf ("KEY:   #%s#\n",(char*) key);
		}
	printf ("\n\n");
	return self;
	}
	
	
	
	
- printProgram:sender
// prints the current COWS program.

	{
	printf ("PROGRAM\n\n#%s#\n\n",[program string]);
	return self;
	}
	
	
	
	
- (int) setProgram:(const char*) this_string
	// clears out dictionaries, sets up COWS Program, and interprets it.
	// returns an error code if unable to at least initially parse the
	// program enough to break it into functions and global variables
	// for storage in dictionaries.  Must also stop any currently running
	// COWS program.
	{
	int error;
	
	// zeroth, stop any current program...
	
	[self stopInterpreting];
	
	// first, clear out dictionaries
	
	[function_dictionary freeObjects];
	[global_dictionary freeObjects];
	[function_dictionary empty];
	[global_dictionary empty];
	[program setString:this_string];	
		// Sets up program
	error=[self _program:[program string]:0];
		// Uses the new program string instead of this_string for safety.
	return error;
	}
	
	
	
	
- addLibrary:this_library
	{
	if (this_library!=NULL)
		//if ([this_library conformsTo:@protocol(InterpreterToLibrary)])
			{
			[this_library loadLibrary:self];
			return self;
			}
	return NULL;
	}
	

- makeMeALibraryDelegate:sender
	{
	id temp=[[COWSLibraryNode alloc] init];
	[temp setTarget:sender];
	[library_delegates push:temp];
	return self;
	}
	
	
- clearLibraryFunctions
	// clears out all library functions.  Must stop any current program.
	{
	[self stopInterpreting];
	[library_dictionary freeObjects];
	[library_dictionary empty];
	[library_delegates clear];
	return self;
	}




- setTimedEntrySpeed:(float) this_speed
	{
	if (this_speed>0) te_speed=this_speed;
	return self;
	}
	
- (float) timedEntrySpeed
	{
	return te_speed;
	}


- addLibraryFunction:(const char*) this_name
	selector: (SEL) this_selector
	target: this_target
	// adds a library function to the library dictionary.
	{
	id new_node=[[COWSLibraryFunctionNode alloc] init];
	[new_node setTarget:this_target];
	[new_node setSelector:this_selector];
	[library_dictionary insertKey:(const void*) this_name 
		value:(void*)new_node];
	return self;
	}
	
	
	
	
- interpretFunction:(const char*) this_name 
	arguments:(COWSArgumentList*)these_arguments
	// Starts interpreter up.  Currently, the interpreter is _not_
	// reentrant, so please don't call this function if the interpreter's
	// not stopped.  It is your responsibility to free your own argument
	// list after calling this method.
		
	{
	COWSStringNode* temp=[[COWSStringNode alloc] init];
	if (working)		// i.e., already working on something
		{
		[temp free];
		if (printing_errors) 
			printf ("Whoops!  Already working on something! Can't do %s\n",this_name);
		return NULL;
		}
	running=YES;
	working=YES;
	
	
	// remove old timed entry if any, then install new one.
	if (teNum) DPSRemoveTimedEntry(teNum);
	if (!foreground)						// else, don't need a timed entry
		teNum=DPSAddTimedEntry(te_speed, 
			(DPSTimedEntryProc) _COWS_timer,
			(void*) self, (int) NX_RUNMODALTHRESHOLD);
	
	// then clean out stack
	[stack clear];
	
	if (tempDelegate&&[tempDelegate respondsTo:@selector(interpreterStarted:)])
		[tempDelegate interpreterStarted:self];
	if (delegate&&[delegate respondsTo:@selector(interpreterStarted:)])
		[delegate interpreterStarted:self];
	
	[library_delegates first];
	while ([library_delegates now])
		{
		id targ=[[library_delegates now] target];
		if (targ&&[targ conformsTo:@protocol(InterpreterToLibraryDelegate)])
			[targ interpreterDidStart:self];
		[library_delegates next];
		} 
		
	[temp setString:this_name];
	[self _executeProgram:these_arguments:temp];
	return [self _go];	// this is just "one try" in the background
						// version, but the whole program execution
						// in the foreground version...
	}	





- stopInterpreting
	// Stops interpreter.  This could happen from outside, if the user
	// wants to cancel the interpreter, or from inside, if the interpreter
	// is finished and wants to clean up.
	{
	running=NO;	
	working=NO;
	function_completed=NO;
	if (teNum) DPSRemoveTimedEntry(teNum);
	teNum=0;
	[current_function setString:""]; // since it's just a copy of a dictionary
	current_position=0;
	if (tempDelegate&&[tempDelegate respondsTo:@selector(interpreterStopped:)])
		[tempDelegate interpreterStopped:self];
	if (delegate&&[delegate respondsTo:@selector(interpreterStopped:)])
		[delegate interpreterStopped:self];

	[library_delegates first];
	while ([library_delegates now])
		{
		id targ=[[library_delegates now] target];
		if (targ&&[targ conformsTo:@protocol(InterpreterToLibraryDelegate)])
			[targ interpreterDidStop:self];
		[library_delegates next];
		} 
		

	return self;
	}




- pauseInterpreting
	// Pauses interpreter.  Why would you want to do this?  In case you had
	// a reentrant library function, which needed more information from a
	// COWS function, you could simulate reentrance by pausing this
	// interpreter, instantiating another, feeding that one the function
	// request, getting a response back, destroying it, and resuming
	// this interpreter with the appropriate value (to push on the stack).
	{
	if (foreground) return NULL;		// this function should not work
										// in foreground mode!
										
	if (teNum) DPSRemoveTimedEntry(teNum);
	teNum=0;
	running=NO;
	return self;
	}

- resumeInterpreting
	// Resumes the interpreter.  This would only be used in the rare case that
	// you're still in the scope of a function, but need to start the
	// interpreter back up.  For example, the NXRunAlertPanel function
	// reenters the App's main event loop and starts up event servicing before
	// returning an answer for the panel.  Thus a COWS library function using
	// the NXRunAlertPanel function might accidentally allow the interpreter
	// to process further down the line (timed entries are events, y'know),
	// before returning its answer!  This would, as you might guess, create
	// a real mess.  The solution is to surround the NXRunaAlertPanel call
	// with pauseInterpreting and resumeInterpreting functions, which remove
	// and replace the timed entry.
	{
	if (foreground) return NULL;		// this function should not work
										// in foreground mode!
	if (teNum) DPSRemoveTimedEntry(teNum);
	teNum=DPSAddTimedEntry(te_speed, 
		(DPSTimedEntryProc) _COWS_timer,
		(void*) self, (int) NX_RUNMODALTHRESHOLD);
	running=YES;				// added May 11.  How did I blow this?
	return self;
	}


- resumeInterpretingWithValue:(COWSStringNode*) this_value
	// See pauseInterpreting above.
	// resumeInterpretingWithValue is used when you need to release control
	// of the app but don't have an answer for the interpreter yet.  For
	// example, when the IPCLibrary receives a request to call a remote
	// application's function and get an answer back, to be safe it must
	// call the application's function and drop out, allowing the first
	// app to continue while waiting for the answer back through a timed-entry
	// pestering-function (to avoid true busy-waiting).  But if it releases
	// control, the interpreter will start up again.  So it must pause the
	// interpreter, release control, and when a timed entry calls it again,
	// start up the interpreter with the proper answer.
	
	// it is the responsibility of the calling function to destroy this_value
	{
	id push_val;
	if (foreground) return NULL;		// this function should not work
										// in foreground mode!
	if (running) return NULL;			// can't resume if not paused!
	if (!working) return NULL;			// can't resume if stopped!
										
	push_val=[[COWSStringNode init] alloc];
	[push_val copyValue:this_value];
	[push_val setError:[this_value error]];
	
	if ([push_val error])
		{
		[self _error:COWSLIBRARYFUNCTIONERROR:"":current_position:[push_val string]];
		[push_val free];
		return NULL;
		}
	if (working)	
		// not stopped-- note that this is different from library function calling
		{
		[stack push:push_val];
		
		// remove old timed entry if any, then install new one.
		if (teNum) DPSRemoveTimedEntry(teNum);
		teNum=DPSAddTimedEntry(te_speed, 
			(DPSTimedEntryProc) _COWS_timer,
			(void*) self, (int) NX_RUNMODALTHRESHOLD);
		running=YES;				// added May 11.  How did I blow this?
		[self _doKeywords];
		}
	return self;
	}



- free
	// frees interpreter, all dictionaries, and stack.  Removes timed entry.
	{
	running=NO;
	working=NO;
	function_completed=NO;
	if (teNum) DPSRemoveTimedEntry(teNum);
	teNum=0;
	
	if (tempDelegate&&[tempDelegate respondsTo:@selector(interpreterStopped:)])
		[tempDelegate interpreterStopped:self];
	if (delegate&&[delegate respondsTo:@selector(interpreterStopped:)])
		[delegate interpreterStopped:self];

	[library_delegates first];
	while ([library_delegates now])
		{
		id targ=[[library_delegates now] target];
		if (targ&&[targ conformsTo:@protocol(InterpreterToLibraryDelegate)])
			[targ interpreterDidStop:self];
		[library_delegates next];
		} 
		
	[function_dictionary freeObjects];
	[global_dictionary freeObjects];
	[library_dictionary freeObjects];
	[function_dictionary empty];
	[global_dictionary empty];
	[library_dictionary empty];
	[function_dictionary free];
	[global_dictionary free];
	[library_dictionary free];
	
	[library_delegates free];
	[stack free];
	return [super free];
	}



- setTempDelegate:this_delegate
	{
	// sets the delegate for the delegate message below.  The
	// delegate receives this message when the interpreter is done
	// performing an interpretFunction:arguments: loop.
	
	tempDelegate=this_delegate;
	return self;
	}
	
- tempDelegate
	// returns the current delegate
	{
	return tempDelegate;
	}
	
- setDelegate:this_delegate
	{
	delegate=this_delegate;
	return self;
	}
	
- delegate
	{
	return delegate;
	}

- setRepeats:(int) this_number
	{
	// sets the number of operations the interpreter will attempt to
	// do per timed entry.  A higher number is more efficient, but
	// takes more control away from the user.
	
	if (this_number)
		{
		repeats=this_number;
		return self;
		}
	return NULL;
	}




- (int) repeats
	// returns the number of operations the interpreter will attempt to
	// do per timed entry.  A higher number is more efficient, but
	// takes more control away from the user.
	{
	return repeats;
	}




- setForeground:(BOOL) yes_or_no
	{
	[self stopInterpreting];		// otherwise it could jump
									// into foreground mode with dire
									// consequences!
	foreground=yes_or_no;
	return self;
	}
	
	
- (BOOL) foreground
	{
	return foreground;
	}
	
- setLocked:(BOOL) yes_or_no
	{
	locked=yes_or_no;
	return self;
	}	
	
- (BOOL) locked
	{
	return locked;
	}
	
- (BOOL) running
	{
	return running;
	}
	
- (BOOL) working
	{
	return working;
	}





// TOKENIZER
// Chews through string starting at pos, placing the next token in string into 
// token, returning the new position in the string after token is out.  Skips 
// white space, comments.  Considers a "string" to be one token.  Parentheses 
// are also tokens, as are keywords.  Is not destructive to string.

// Returns Error or next token position.


- (int) _tokenize:(const char*) string:(int) pos:(COWSStringNode*) token
	{
	int x=pos;
	int length=strlen(string);
	int start;
	
	// Skip through white space and comments
	while (1)
		{
		if (x>=length) return COWSERRORNOMORETOKENS;
		if (string[x]=='[')
			{
			while (1)
				{
				x++;
				if (x>=length) return COWSERRORNOMORETOKENS;
				if (string[x]==']') break;
				}
			}
		else if (string[x]!=' '&&string[x]!='\t'&&string[x]!='\n') break;
		x++;
		}
	
	// Determine Type
	if (x>=length) return COWSERRORNOMORETOKENS;
	if (string[x]=='\"')
		{
		start=x;
		while (1)
			{
			x++;
			if (x>=length) return COWSERRORNOCLOSINGQUOTE;
			if (string[x]=='\"') break;
			}
		x++;
		[token setString:&string[start] size:x-start];
		return x;
		}
	else if (string[x]=='('||string[x]==')') 
		{
		[token setString:&string[x] size:1];
		x++;
		return x;
		}
	else		// symbol
		{
		start=x;
		while (1)
			{
			x++;
			if (x>=length) break;
			if (string[x]==' '||string[x]=='\t'||string[x]=='\n') break;
			if (string[x]=='['||string[x]=='\"') break;
			if (string[x]=='('||string[x]==')') break;
			}
		[token setString:&string[start] size:x-start];
		return x;
		}
	}







// RECURSIVE DECENT PARSER
// Used to break up globals and functions into dictionaries
// Entry is by passing _program the program string and 0 as a position
// The parser is straight forward but fairly uncommented.  Sorry!

// variablelist loads globals into global_dictionary
// localvariablelist loads variables into a local dictionary
// argumentlist loads arguments into a local argument list
// itemlist grabs the remainder of the function
// functionform loads arguments from the argument list into the local
//		dictionary and puts the local dictionary and item list into
//		function_dictionary


/* Parsing Grammar:

NonTerminals:

program				:-	<programlist>
programlist			:-	EOF | <globalform> <programlist> | <functionform>
								<programlist>
globalform			:-	( <globalkeyword> <variablelist> )
functionform		:- 	(function <functionname> <argumentlist> 
								variable <localvariablelist> begin <itemlist> |
						(function <functionname> <argumentlist> 
								begin <itemlist>
variablelist		:- <symbolname> <variablelist> | NULL
localvariablelist	:- <symbolname> <localvariablelist> | NULL
argumentlist		:- <symbolname> <argumentlist> | NULL
itemlist			:-	<item>* )	<--note I use a while here. Less recursive.
item				:-	<atom> | ( <itemlist>

Terminals:
globalkeyword is the word "variable"
functionkeyword	is the word "function"
symbolname is any non-string, non-truth, non-number, non-keyword, non-delimiter
atom is any non-delimiter

*/




- (int) _program:(const char*)string:(int)pos
	{
	return [self _programList:string:pos];
	}
	
	
	
	
- (int) _programList:(const char*)string:(int)pos
	{
	int rv;
	COWSStringNode* s=[[COWSStringNode alloc] init];
	
	// NULL
	
	rv=[self _tokenize:string:pos:s];
	if (rv==COWSERRORNOMORETOKENS) 
		{
		[s free];
		return COWSSUCCESS;
		}
	
	// <function-form> <program-list> 
	
	rv=[self _functionForm:string:pos];
	if (rv>=0)
		{
		rv=[self _programList:string:rv];
		if (rv>=0) 
			{
			[s free];
			return rv;
			}
		}

	// <global-form> <program-list> 
	
	rv=[self _globalForm:string:pos];
	if (rv>=0)
		{
		rv=[self _programList:string:rv];
		if (rv>=0) 
			{
			[s free];
			return rv;
			}
		}
 	[s free];	
	return rv;
	}
	
	
	
	
- (int) _functionForm:(const char*)string:(int)pos
	{
	int rv;
	int rvstart;
	COWSStringNode* s=[[COWSStringNode alloc] init];
	id func=[[COWSStateNode alloc] init];
	COWSArgumentList* arguments=[func arguments];
	HashTable* variables=[func dictionary];
	
	// (function <function-name> <argument-list> 
	//		variable <local-variable-list> begin <value-list>) 
	// this version has both variables and arguments
	
	rv=[self _openParen:string:pos];
	if (rv>=0)
		{
		rv=[self _functionKeyword:string:rv];
		if (rv>=0)
			{
			rv=[self _symbolName:string:s:rv];
			if (rv>=0)
				{
				rv=[self _argumentList:string:rv:arguments];
				if (rv>=0)
					{
					// load argument list into dictionary as well!
					[arguments first];
					while([arguments now]!=NULL)
						{
						const char* argstr=[[arguments now] string];
						id var;
						if ([variables isKey:(const void*)
								argstr])
							{
							[func free];
							[s free];
							return COWSERRORDUPLICATEARGUMENT;
							}
						var=[[COWSStringNode alloc] init];
						[var setString:""];
						[variables insertKey:newstr(argstr) value:var];						
						[arguments next];
						}
					[arguments first];						// just in case
					rv=[self _variableKeyword:string:rv];
					if (rv>=0)
						{
						rv=[self _localVariableList:string:rv:variables];
						if (rv>=0)
							{
							rv=[self _beginKeyword:string:rv];
							if (rv>=0)
								{
								rvstart=rv;
								rv=[self _itemList:string:rv];
								if (rv>=0)
									{
									if ([function_dictionary isKey:
											(const void*) [s string]])
										{
										[s free];
										[func free];
										return COWSERRORDUPLICATEFUNCTION;
										}
									[func setString:&string[rvstart]
										 size:rv-rvstart];
									[function_dictionary insertKey:
										newstr([s string]) value:func];
									[s free];						
									return rv;
									}
								}
							}
						}
					}
				}
			}
		}

	[arguments clear];
	[variables freeObjects];
	[variables empty];


	// (function <function-name> <argument-list> begin <value-list>) 
	// this version has no variables
	
	rv=[self _openParen:string:pos];
	if (rv>=0)
		{
		rv=[self _functionKeyword:string:rv];
		if (rv>=0)
			{
			rv=[self _symbolName:string:s:rv];
			if (rv>=0)
				{
				rv=[self _argumentList:string:rv:arguments];
				if (rv>=0)
					{
					// load argument list into dictionary as well!
					[arguments first];
					while([arguments now]!=NULL)
						{
						const char* argstr=[[arguments now] string];
						id var;
						if ([variables isKey:(const void*)
								argstr])
							{
							[func free];
							[s free];
							return COWSERRORDUPLICATEARGUMENT;
							}
						var=[[COWSStringNode alloc] init];
						[var setString:""];
						[variables insertKey:newstr(argstr) value:var];						
						[arguments next];
						}
					[arguments first];						// just in case
					rv=[self _beginKeyword:string:rv];
						if (rv>=0)
						{
						rvstart=rv;
						rv=[self _itemList:string:rv];
						if (rv>=0)
							{
							if ([function_dictionary isKey:
									(const void*) [s string]])
								{
								[s free];
								[func free];
								return COWSERRORDUPLICATEFUNCTION;
								}
							[func setString:&string[rvstart]
								 size:rv-rvstart];
							[function_dictionary insertKey:
								newstr([s string]) value:func];
							[s free];						
							return rv;
							}
						}
					}
				}
			}
		}



	// otherwise...
	[s free];
	[func free];
	return rv;
	}


	
		
- (int) _itemList:(const char*)string:(int)pos
	{
	int rv=pos;
	int temp;
	// an item list is lots of items ending with a )...
	
	while(1)
		{
		temp=[self _closeParen:string:rv];
		if (temp>=0) return temp;
		//else...
		rv=[self _item:string:rv];
		if (!(rv>=0)) return rv;
		}
	return rv;
	}
	
	
	

- (int) _item:(const char*)string:(int)pos
	{
	int rv;

	// either an item is an atom... 
	
	rv=[self _atom:string:pos];
	if (rv>=0) return rv;
	
	// or it is a list of items beginning with a (...

	rv=[self _openParen:string:pos];
	if (rv>=0) 
		{
		rv=[self _itemList:string:rv];
		return rv;
		}
	return rv;
	}
	
	
	
	
- (int) _globalForm:(const char*)string:(int)pos
	{
	int rv;
	
	// (variable <variable-name>) 
	
	rv=[self _openParen:string:pos];
	if (rv>=0)
		{
		rv=[self _variableKeyword:string:rv];
		if (rv>=0)
			{
			rv=[self _variableList:string:rv];
			if (rv>=0)
				{
				rv=[self _closeParen:string:rv];
				if (rv>=0) 
					{
					return rv;
					}
				}
			}
		}
	return rv;
	}
	
	
	

- (int) _variableList:(const char*) string:(int)pos
	{
	int rv;
	COWSStringNode* s=[[COWSStringNode alloc] init];
	id var;
	
	// NULL		variable lists always end with ")"
	
	rv=[self _tokenize:string:pos:s];
	if (!strcmp([s string],")")) 
		{
		[s free];
		return pos;			// note _not_ rv
		}
	
	// <variable-name> <variable-list>  
	
	rv=[self _symbolName:string:s:pos];	// a variable is identical to a symbol
	if (rv>=0)
		{
		rv=[self _variableList:string:rv];
		if (rv>=0) 
			{
			var=[[COWSStringNode alloc] init];
			[var setString:""];
			[global_dictionary insertKey:newstr([s string]) value:var];
			[s free];
			return rv;
			}
		}
	[s free];
	return rv;
	}
	
	
	
	
	
- (int) _localVariableList:(const char*) string:(int)pos:(HashTable*) arguments
	{
	// arguments is hash table in which to place arguments
	int rv;
	COWSStringNode* s=[[COWSStringNode alloc] init];
	id var;
	
	// NULL		local variable lists always end with "do"
	
	rv=[self _tokenize:string:pos:s];
	if (!strcmp([s string], "do")) 
		{
		[s free];
		return pos;	// note _not_ rv
		}
	
	// <variable-name> <local-variable-list>  
	
	rv=[self _symbolName:string:s:pos];	// a variable is identical to a symbol
	if (rv>=0)
		{
		rv=[self _localVariableList:string:rv:arguments];
		if (rv>=0) 
			{
			if ([arguments valueForKey:(const void*)[s string]]!=nil)
				{ 
				[s free];
				return COWSERRORDUPLICATEVARIABLE;
				}
			var=[[COWSStringNode alloc] init];
			[var setString:""];
			[arguments insertKey:newstr([s string]) value:var];
			[s free];
			return rv;
			}
		}
	[s free];
	return rv;
	}




	
- (int) _argumentList:(const char*) string:(int)pos:
		(COWSArgumentList*) arguments
	{
	// arguments is an argument-list in which to place arguments
	int rv;
	COWSStringNode* s=[[COWSStringNode alloc] init];
	const char* token;
	
	// NULL		argument lists always end with "variable" or "do"
	
	rv=[self _tokenize:string:pos:s];
	token=[s string];
	if (!strcmp(token, "variable") || !strcmp(token, "do")) 
		{
		[s free];
		return pos;	// note _not_ rv
		}
	
	// <argument> <argument-list>  
	
	rv=[self _symbolName:string:s:pos];	// a variable is identical to a symbol
	if (rv>=0)
		{
		rv=[self _argumentList:string:rv:arguments];
		if (rv>=0) 
			{
			id arg=[[COWSStringNode alloc] init];
			[arg copyValue:s];
			[arguments push:arg];
			[s free];
			return rv;
			}
		}
	[s free];
	return rv;
	}
	
	
	
	

// TERMINAL HELPER FUNCTIONS
// Help terminals decide if a string is what it's supposed to be.

int anumber(const char* string)
	{
	// Numbers:
	// 1:  May have 1 e, 1 ., and up to 2 +s or -s.
	// 2:  May have any number of digits
	// 3:  May have an e only after a digit
	// 4:  May not have a . after an e or a .
	// 5:  May have a + or - only at the beginning or after an e
	// 6:  May not have any other characters but digits, +, -, ., or e
	// 7:  Must have at least one digit
	// 8:  Must have at least one digit after . or e
	
	// What a mess!  Is there an easier way to parse numbers?
	
	/*
	int x=0;
		// assumes that numbers begin with a digit, or a -,+, or . (or ")
	if (string[x]=='\0') return 0;
	if (string[x]=='\"') x++;
	if (string[x]=='\0') return 0;
	if (string[x]=='-'||string[x]=='+') x++;
	if (string[x]=='\0') return 0;
	if (string[x]=='.') x++;
	if (string[x]=='\0') return 0;
	if (string[x]>=0x30&&string[x]<=0x39) return 1;	
	return 0;
	*/
	
	int plusminusvalid=1;		// can start with a plus or minus
	int evalid=0;				// cannot start with an e
	int dotvalid=1;				// can start with a .
	int plusminuscount=0;
	int ecount=0;
	int dotcount=0;
	int edigit=1;
	int dotdigit=1;
	int digitcount=0;
	
	int x;
	int len;
	if (string)				// if string is not NULL
		{
		len=strlen(string);
		for (x=0;x<len;x++)
			{
			if (string[x]>=0x30&&string[x]<=0x39)		// a digit
				{
				plusminusvalid=0; // + or - only valid at beginning or after e	
				digitcount++;
				evalid=1;
				dotdigit=1;
				edigit=1;
				}
			else if (string[x]=='.')
				{
				if (!dotvalid) return 0;
				if (dotcount) return 0;		// only one dot
				plusminusvalid=0; // + or - only valid at beginning or after e	
				evalid=0;
				dotvalid=0;					// dots are not valid after a dot
				dotcount++;
				dotdigit=0;
				}
			else if (string[x]=='-')
				{
				if (!plusminusvalid) return 0;
				if (plusminuscount==2) return 0;	// only one plusminus
				evalid=0;
				plusminusvalid=0; // + or - only valid at beginning or after e	
				plusminuscount++;
				}
			else if (string[x]=='+')
				{
				if (!plusminusvalid) return 0;
				if (plusminuscount==2) return 0;	// only one plusminus
				evalid=0;
				plusminusvalid=0; // + or - only valid at beginning or after e	
				plusminuscount++;
				}
			else if (string[x]=='e')
				{
				if (!dotdigit) return 0;	// e encountered before dot digit
				if (!evalid) return 0;
				if (ecount) return 0;		// only one e
				plusminusvalid=1;			// +/- is valid after e
				dotvalid=0;					// dots are not valid after e
				ecount++;
				edigit=0;
				}
			else return 0;				// invalid character in string
			}
		if (dotdigit&&edigit&&digitcount) return 1;
		}
	return 0;
	}



int astring(const char* string)
	{
	int x=0;
		// assumes that strings begin with a "
	if (string[x]=='\0') return 0;
	if (string[x]=='\"') return 1;
	return 0;
	}



int atruth(const char* string)
	{
		// assumes that truths are t and f.  "t" and "" are considered strings.
	if (!strcmp(string,"t")||
		!strcmp(string,"f")) return 1;
	  // ||
		/*!strcmp(string,"\"t\"")||
		!strcmp(string,"\"\"")) return 1;*/		// ""
	return 0;
	}
	
int asymbol(const char* string)
	// a symbol isn't a number, a string, or a truth, nor ( or )
	{
	if (!atruth(string)
		&&!anumber(string)
		&&!astring(string)
		&&strcmp(string,"(")
		&&strcmp(string,")")) return 1;
	return 0;
	}



//  TERMINALS
//	Terminals check tokens for being a certain kind of token, returning
//	truth or not.  The only exception is _symbolName, which not only 
//	checks to see if a token is valid, but also returns the token to be
//	used elsewhere.

- (int) _symbolName:(const char*)string:(COWSStringNode*)s:(int)pos
	{
	// a symbol is anything that's not (, ), a keyword, a truth,
	// a number, or a string.  _symbolName returns the name
	// in s.
	int rv=pos;
	const char* temp;
	rv=[self _tokenize:string:pos:s];
	if (rv<0) return rv;
	temp=[s string];
	if (strcmp(temp,"(")&&
		strcmp(temp,")")&&
		strcmp(temp,"function")&&
		strcmp(temp,"variable")&&
		strcmp(temp,"set")&&
		strcmp(temp,"if")&&
		strcmp(temp,"else")&&
		strcmp(temp,"do")&&
		strcmp(temp,"for")&&
		strcmp(temp,"then")&&
		strcmp(temp,"while")&&
		!astring(temp)&&
		!atruth(temp)&&
		!anumber(temp))
		{
		return rv;
		}
	return COWSERRORSYNTAX;
	}




- (int) _openParen:(const char*)string:(int)pos
	{
	// an openparen is a "("
	
	int rv=pos;
	COWSStringNode*s=[[COWSStringNode alloc] init];
	rv=[self _tokenize:string:pos:s];
	if (rv<0) 
		{
		[s free];
		return rv;
		}
	if (!strcmp([s string],"(")) 
		{
		[s free];
		return rv;
		}
	[s free];
	return COWSERRORSYNTAX;
	}
	
	
			
			
- (int) _closeParen:(const char*)string:(int)pos
	{
	// a closeparen is a ")"
	
	int rv=pos;
	COWSStringNode*s=[[COWSStringNode alloc] init];
	rv=[self _tokenize:string:pos:s];
	if (rv<0) 
		{
		[s free];
		return rv;
		}
	if (!strcmp([s string],")")) 
		{
		[s free];
		return rv;
		}
	[s free];
	return COWSERRORSYNTAX;
	}
			


			
- (int) _variableKeyword:(const char*)string:(int)pos
	{
	// a variablekeyword is the word "variable"
	
	int rv=pos;
	COWSStringNode*s=[[COWSStringNode alloc] init];
	rv=[self _tokenize:string:pos:s];
	if (rv<0) 
		{
		[s free];
		return rv;
		}
	if (!strcmp([s string],"variable")) 
		{
		[s free];
		return rv;
		}
	[s free];
	return COWSERRORSYNTAX;
	}
			


			
- (int) _functionKeyword:(const char*)string:(int)pos
	{
	// a functionkeyword is the word "function"
	
	int rv=pos;
	COWSStringNode*s=[[COWSStringNode alloc] init];
	rv=[self _tokenize:string:pos:s];
	if (rv<0) 
		{
		[s free];
		return rv;
		}
	if (!strcmp([s string],"function")) 
		{
		[s free];
		return rv;
		}
	[s free];
	return COWSERRORSYNTAX;
	}




- (int) _beginKeyword:(const char*)string:(int)pos
	{
	// a beginkeyword is the word "do"
	
	int rv=pos;
	COWSStringNode*s=[[COWSStringNode alloc] init];
	rv=[self _tokenize:string:pos:s];
	if (rv<0) 
		{
		[s free];
		return rv;
		}
	if (!strcmp([s string],"do")) 
		{
		[s free];
		return rv;
		}
	[s free];
	return COWSERRORSYNTAX;
	}




- (int) _atom:(const char*)string:(int)pos
	{
	// atoms are used only when doing 1st level parsing.
	// an atom is anything that's not a ( or ).
	
	int rv=pos;
	COWSStringNode*s=[[COWSStringNode alloc] init];
	rv=[self _tokenize:string:pos:s];
	if (rv<0) 
		{
		[s free];
		return rv;
		}
	if (strcmp([s string],"(")&&strcmp([s string],")")) 
		{
		[s free];
		return rv;
		}
	[s free];
	return COWSERRORSYNTAX;
	}
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	

	
// INTERPRETER	
	
	
- _executeProgram:(COWSArgumentList*) arguments:(COWSStringNode*) symbol
	// Starts executing function <symbol> in the current COWS Program, 
	// passing the function arguments <arguments>
	{
	id temp_sym=[[COWSSymbolNode alloc] init];
	id temp_val;
	interpretation=COWSINTERPRETATIONUNDEFINED;
	[temp_sym copyValue:symbol];
	[stack push:temp_sym];
	[arguments first];
	while ([arguments now])
		{
		temp_val=[[COWSStringNode alloc] init];
		[temp_val copyValue:[arguments now]];
		[stack push:temp_val];
		[arguments next];
		}
	return [self _performFunction];
	}
	
	

- _performFunction
	// Begins performing a function.  It is assumed that the Symbol Name
	// for the function is on the stack, as well as the arguments (which
	// are on top).
	{
	id arguments=[[COWSArgumentList alloc] init];
	id symbol_name;
	const char* string;
	while ([[stack top] isMemberOf:[COWSStringNode class]])
		// eats down to the nearest non-string node, which should be a
		// symbol node...
		// this should put the arguments in forward order...
		// that is, the first argument is on top, and the last
		// (rightmost) argument is on bottom.
		{
		[arguments push:[stack pop]];
		}
	symbol_name=[stack pop];	// this assumes that symbol_name is a Symbol
	string=[symbol_name string];
	if ([function_dictionary isKey:string])
		{
		id returnval=[self _performInternalFunction:arguments:symbol_name];
		if (returnval!=NULL) [symbol_name free];	// else it's on the stack!
		[arguments free];
		return returnval;
		}
	else if ([library_dictionary isKey:string])
		{
		id returnval=[self _performLibraryFunction:arguments:symbol_name];
		if (returnval!=NULL) [symbol_name free];	// else it's on the stack!
		[arguments free];
		return returnval;
		}
	else 						// missing function
		{
		[self _error:COWSERRORNOSUCHFUNCTION:
			[current_function string]:current_position:[symbol_name string]];
		[arguments free];
		return NULL;
		}
	}
	
	
	
- _performInternalFunction:
		(COWSArgumentList*) arguments:(COWSStringNode*) symbol
		
	// performs an internal function (a function defined in COWS script)
	// this is always called by _performFunction.
	
	{
	id function_node=(COWSStateNode*)
		[function_dictionary valueForKey:(const void*)[symbol string]];
	id new_state=[function_node copy];
	id new_args=[new_state arguments];
	id new_dict=[new_state dictionary];
	id val;
	id old_state=[stack topState];
	
	if (interpretation==COWSINTERPRETATIONUNDEFINED)
		interpretation=COWSINTERPRETATIONFUNCTION;
	
	if (old_state!=NULL)	// i.e., if it's not the beginning of the program
		{
		[old_state setPos:current_position];		// saves old state away...
		}
	[arguments first];
	[new_args first];
	while([arguments now])
		{
		if ([new_args now]==NULL)
			{
			[self _error:COWSERRORTOOMANYARGUMENTS
				:[current_function string]:current_position:[symbol string]];
			return NULL;
			}
		val=(COWSStringNode*)
			[new_dict valueForKey:(const void*)[[new_args now] string]];
		if (val==nil)
			{
			[self _error:COWSINTERNALERROR
				:[current_function string]:current_position:[symbol string]];
			return NULL;
			}
		else
			{
			[val copyValue:[arguments now]];
			}
		[arguments next];
		[[new_state arguments] next];
		}
	if ([new_args now]!=NULL)
		{
		[self _error: COWSERRORNOTENOUGHARGUMENTS
				:[current_function string]:current_position:[symbol string]];
		return NULL;
		}
	[stack push:new_state];
	[new_state setPos:0];
	[current_function copyValue:new_state];
	current_dictionary=[new_state dictionary];
	current_position=[new_state pos];		// which should always be 0, tho
	return self;
	}
	
	
	
	
- _performLibraryFunction:
		(COWSArgumentList*) arguments:(COWSStringNode*) symbol
		
	// performs a library function; that is, one defined by the application
	// before the COWS program was run.  Always called by _performFunction.
	
	{
	id temp_node=[library_dictionary valueForKey:[symbol string]];
	id returnval;

	if (temp_node==NULL)
		{
		[self _error:COWSINTERNALERROR
				:[current_function string]:current_position:[symbol string]];
		return NULL;
		}
		
	if (interpretation==COWSINTERPRETATIONUNDEFINED)
		interpretation=COWSINTERPRETATIONLIBRARY;

	returnval=[[temp_node target] perform:[temp_node selector] with:arguments];
	
	// since we didn't use arguments to pass to the target, but built our own
	// arguments, we must free our own arguments here.
	
	if ([returnval error])
		{
		[self _error:COWSLIBRARYFUNCTIONERROR
				:[current_function string]:current_position:[returnval string]];
		return NULL;
		}
	if (running)	// not paused...resumes push their own stuff on stack
		{
		
		[stack push:returnval];
		return [self _doKeywords];
		}
	return self;
	}
	
	
	
	
	
- _completeFunction
	// finishes out an internal function
	// and cleans up before interpreter continues parent function.
	{
	id return_value;
	id new_state;
	if ([[stack top] isMemberOf:[COWSStringNode class]])
		{
		return_value=[stack pop];
		}
	else return_value=[[COWSStringNode alloc] init];
	while ([[stack top] isMemberOf:[COWSStringNode class]])
		{
		[[stack pop] free];	// clears out non-return values
		}
	if ([stack top]!=NULL)		// else, could be a library function finishing
		[[stack pop] free];		// clears out Local State
	new_state=[stack topState];
	if (new_state!=NULL)
		{
		[current_function copyValue:new_state];
		current_position=[new_state pos];
		current_dictionary=[new_state dictionary];
		[stack push:return_value];
		return [self _doKeywords];
		}
	else			// program is finished!
		{
		if (tempDelegate&&[tempDelegate conformsTo:@protocol (InterpreterToDelegate)])
			[tempDelegate finishedInterpreting:
				[return_value string]:(int)COWSSUCCESS:self];
		if (delegate&&[delegate conformsTo:@protocol (InterpreterToDelegate)])
			[delegate finishedInterpreting:
				[return_value string]:(int)COWSSUCCESS:self];
		[return_value free];
		return [self stopInterpreting];
		}
	}	
	
	
	
	
	
	
- _evaluateVariable:(COWSStringNode*) symbol
	// evaluates the variable <symbol> to its value, either in the current
	// dictionary or in the global dictionary
	
	{
	// this assumes current dictionary is proper...
	id temp_val=[current_dictionary valueForKey:(void*) [symbol string]];
	if (temp_val==NULL) temp_val=
		[global_dictionary valueForKey:(void*) [symbol string]];
	if (temp_val==NULL)
		{
		[self _error:COWSERRORNOSUCHVARIABLE
				:[current_function string]:current_position:[symbol string]];
		return NULL;		
		}
	else
		{
		id new_val=[[COWSStringNode alloc] init];
		[new_val copyValue:temp_val];
		[stack push:new_val];
		[self _doKeywords];
		return self;
		}
	}	
	
	
	
	
	
- _doKeywords
	// continues keywords, if there are any.
	// this happens every time a value if finished out; a keyword may need
	// to use it to set something up (like <for> needs 4 values)
	{
	id top=[stack topSymbol];
	const char* string;
	if (top==NULL) return self;
	string=[top string];
	if (!strcmp(string,"if"))
		{
		return [self _doIf];
		}
	if (!strcmp(string,"set"))
		{
		return [self _doSet];
		}
	if (!strcmp(string,"for"))
		{
		return [self _doFor];
		}
	if (!strcmp(string,"while"))
		{
		return [self _doWhile];
		}
	return self;
	}
	
	

	
- _go
	// Drives _readEval, the main interpreter function
	// this superfunction allows the interpreter to perform <repeats> number
	// of _readEvals each clock cycle.  If the interpreter has stopped before
	// the clock did, _go breaks out automatically so it doesn't waste time.
	
	// NEW IN 1.2:  If the interpreter is foreground, _go works until it
	// encounters a command-period.  If it's background, _go works until
	// it encounters an event.  The event stuff is provided by Don Yacktman.
	
	// Foreground mode does _not_ work properly with pause and resume!
	// An interpreter _must_ be background for this to work.
	
	{
	int x;
	
	if (foreground)
		{
		while (running)
			{
			if (NXUserAborted())
				{
				[self stopInterpreting];
				return self;
				}
			else for (x=0;x<repeats;x++)
				{
				if (!running) break;
				[self _readEval];
				}
			}
		}
	else		// background
		{
		if (!running) return self;
		for (x=0;x<repeats;x++)
			{
			if (!running) break;
			[self _readEval];
			}
		}
	return self;
	}	



- _readEval
	// Reads a token and evaluates it by calling one of several subordinate
	// functions.  This function is ultimately driven by the clock through _go.
	
	{
	id token=[[COWSStringNode alloc] init];
	const char* string;
	current_position=[self _tokenize:[current_function string]:
		current_position:token];
	string=[token string];
	if (!strcmp(string,""))		// token is empty...
		{
		if (interpretation==COWSINTERPRETATIONLIBRARY)	
			// it was a library function
			{
			return [self _completeFunction];
			}
		[self _error:COWSERRORNOMORETOKENS
				:[current_function string]:current_position:""];
		[token free];
		return NULL;		
		}
	else if (atruth(string))
		{
		// prepare string
		[self _makeTruth:token];
		[stack push:token];
		return [self _doKeywords];
		}
	else if (anumber(string))
		{
		// prepare string
		[self _makeNumber:token];
		[stack push:token];
		return [self _doKeywords];
		}
	else if (astring(string))
		{
		// prepare string
		[self _strip:token];
		[stack push:token];
		return [self _doKeywords];
		}
	else if (asymbol(string))			// token is a variable...
		{
		[self _evaluateVariable:token];
		[token free];
		return self;
		}
	else if (!strcmp(string,"("))
		{
		id new_node;
		current_position=[self _tokenize:[current_function string]:
			current_position:token];
		string=[token string];
		if (!strcmp(string,""))		// token is empty...
			{
			[self _error:COWSERRORNOMORETOKENS
				:[current_function string]:current_position:""];
			[token free];
			return NULL;		
			}
		else if (!asymbol(string))		// token ain't a symbol
			{
			[self _error:COWSERRORNOFUNCTIONNAME
				:[current_function string]:current_position:[token string]];
			[token free];
			return NULL;
			}
		else if (!strcmp(string,"if"))
			{
			[self _startIf];
			[token free];
			return self;
			}
		else if (!strcmp(string,"set"))
			{
			[self _startSet];
			[token free];
			return self;
			}
		else if (!strcmp(string,"for"))
			{
			[self _startFor];
			[token free];
			return self;
			}
		else if (!strcmp(string,"while"))
			{
			[self _startWhile];
			[token free];
			return self;
			}
		// otherwise, token is a symbol but isn't a keyword, so it's a function
		// so push it for use later
		new_node=[[COWSSymbolNode alloc] init];
		[new_node copyValue:token];
		[stack push:new_node];
		[token free];
		return self;
		}
	else if (!strcmp(string,")"))
		{
		id symbol=[stack topSymbol];
		if (symbol==NULL)
			{
			[self _completeFunction];
			[token free];
			return self;
			}
		string=[symbol string];
		if (!strcmp(string,"if"))
			{
			id temp=[self _finishIf];
			[token free];
			if (temp!=NULL) [self _doKeywords];
			return self;
			}
		else if (!strcmp(string,"set"))
			{
			id temp=[self _finishSet];
			[token free];
			if (temp!=NULL) [self _doKeywords];
			return self;
			}
		else if (!strcmp(string,"for"))
			{
			id temp=[self _finishFor];
			[token free];
			if (temp!=NULL) [self _doKeywords];
			return self;
			}
		else if (!strcmp(string,"while"))
			{
			id temp=[self _finishWhile];
			[token free];
			if (temp!=NULL) [self _doKeywords];
			return self;
			}
		// else function is ready to be performed
		[self _performFunction];
		}
	[token free];
	return self;
	}


-_startSet
	// starts processing a "set" token
	{
	const char* string;
	id token=[[COWSStringNode alloc] init];
	id node;
	current_position=[self _tokenize:[current_function string]:
		current_position:token];
	string=[token string];
	if (asymbol(string))
		{
		node=[[COWSSymbolNode alloc] init];
		[node setState:COWSSTARTSET];
		[node setString:"set"];
		[[node variable] copyValue:token];
		[token free];
		[stack push:node];
		return self;
		}
	[self _error:COWSERRORSETNOTVARIABLE
				:[current_function string]:current_position:[token string]];
	[token free];
	return NULL;		
	}



-_doSet
	// continues processing a "set" token
	{
	id node=[stack topSymbol];
	if ([node state]==COWSSTARTSET)
		{
		[node setState:COWSDONESET];
		return self;
		}
	[self _error:COWSERRORSETTOOMANYVALUES
				:[current_function string]:current_position:""];
	return NULL;		
	}


-_finishSet
	// finishes processing a "set" token
	{
	id node=[stack topSymbol];
	if ([node state]==COWSDONESET)
		{
		const char* string=[[node variable] string];
		id value=[stack pop];

		if ([current_dictionary isKey: (const void*) string])
			{
			[(COWSStringNode*)[current_dictionary 
					valueForKey:(const void*)string] copyValue:value];
			[[stack pop] free];		// pops and frees set node
			[stack push:value];		// returns value
			return self;
			}
		else if ([global_dictionary isKey: (const void*) string])
			{
			[(COWSStringNode*)[global_dictionary 
					valueForKey:(const void*)string] copyValue:value];
			[[stack pop] free];		// pops and frees set node
			[stack push:value];		// returns value
			return self;
			}
		else
			{
			[self _error:COWSERRORSETNOSUCHVARIABLE
				:[current_function string]:current_position:string];
			[value free];
			return NULL;		
			}
		}
	[self _error:COWSERRORSETNOVALUE
				:[current_function string]:current_position:""];
	return NULL;		
	}


-_startIf
	// begins processing "if" token
	{
	id node;
	node=[[COWSSymbolNode alloc] init];
	[node setState:COWSSTARTIF];
	[node setString:"if"];
	[stack push:node];
	return self;
	}
	
	

-_doIf
	// continues processing "if" token
	{
	id node=[stack topSymbol];
	int state=[node state];
	if (state==COWSSTARTIF)
		{
		id value=[stack pop];
		if ([value booleanVal])
			{
			[node setState:COWSSTARTTHEN];
			[value free];
			return self;
			}
		else		// f or other
			{
			int x=current_position;
			[node setState:COWSSTARTELSE];
			[value free];
			[self _skip];
			if (x==current_position)	// couldn't skip over the then!
				{
				[self _error:COWSERRORIFNOTHENCLAUSE
					:[current_function string]:current_position:""];
				return NULL;		
				} 
			return self;
			}
		}
	else if (state==COWSSTARTTHEN)
		{
		[node setState:COWSDONEIF];
		[self _skip];
		return self;
		}
	else if (state==COWSSTARTELSE)
		{
		[node setState:COWSDONEIF];
		return self;
		}
	else			// COWSDONEIF
		{
		[self _error:COWSERRORIFTOOMANYVALUES
				:[current_function string]:current_position:""];
		return NULL;		
		}
	}
	
	
-_finishIf
	// completes processing "if" token
	{
	id node=[stack topSymbol];
	id value;
	int state=[node state];
	if (state==COWSSTARTIF||state==COWSSTARTTHEN)
		{
		[self _error:COWSERRORIFNOTENOUGHVALUES
				:[current_function string]:current_position:""];
		return NULL;		
		}
	else if (state==COWSDONEIF)	// finished a value	
		{
		value=[stack pop];
		}
	else // state= COWSSTARTELSE; no value
		{
		value=[[COWSStringNode alloc] init];		// empty value to push
		}
		
	while(1) 
		{
		id top=[stack top];
		if ([top isMemberOf: [COWSSymbolNode class]])
			if (!strcmp([top string],"if"))
				break;
		[[stack pop] free];		// pops and frees to if node
		}
	[[stack pop] free];			// pops if node
	[stack push:value];
	return self;
	}
	



- _startWhile
	// begins processing "while" token
	{
	id node;
	node=[[COWSSymbolNode alloc] init];
	[node setState:COWSSTARTWHILE];
	[node setString:"while"];
	[node setPos:current_position];
	[stack push:node];
	return self;
	}
	
	
	
	
-_doWhile
	// continues processing "while" token
	{
	id node=[stack topSymbol];
	int state=[node state];
	if (state==COWSSTARTWHILE)
		{
		id test=[stack pop];
		if ([test booleanVal])
			{
			[node setState:COWSTRUEWHILE];
			}
		else		// false...
			{
			[node setState:COWSFALSEWHILE];
			[self _skip];
			}
		[stack push:test];	// placed back on stack in case need to return it
		return self;
		}
	else if (state==COWSTRUEWHILE)
		{
		[node setState:COWSDONEWHILE];
		return self;
		}
	else	// state==COWSDONEWHILE or COWSFALSEWHILE
		{
		[self _error:COWSERRORWHILETOOMANYVALUES
				:[current_function string]:current_position:""];
		return NULL;		
		}
	}
	
	
	
	
-_finishWhile
	// finishes processing "while" token
	{
	id node=[stack topSymbol];
	int state=[node state];
	if (state==COWSTRUEWHILE||state==COWSDONEWHILE)
		{
		while(1) 
			{
			id top=[stack top];
			if ([top isMemberOf: [COWSSymbolNode class]])
				if (!strcmp([top string],"while"))
					break;
			[[stack pop] free];		// pops and frees to while node
			}
		current_position=[node pos];
		[node setState:COWSSTARTWHILE];
		return NULL;				// we don't want _doKeywords called again.
		}
	else if (state==COWSSTARTWHILE)
		{
		[self _error:COWSERRORWHILENOTENOUGHVALUES
				:[current_function string]:current_position:""];
		return NULL;		
		}
	else					// ==COWSFALSEWHILE
		{
		id return_val=[stack pop];
		while(1) 
			{
			id top=[stack top];
			if ([top isMemberOf: [COWSSymbolNode class]])
				if (!strcmp([top string],"while"))
					break;
			[[stack pop] free];		// pops and frees to while node
			}
		[[stack pop] free];			// pops and frees while node
		[stack push:return_val];
		return self;
		}
	}
	


- _startFor
	// begins processing "for" token
	{
	const char* string;
	id token=[[COWSStringNode alloc] init];
	id node;
	current_position=[self _tokenize:[current_function string]:
		current_position:token];
	string=[token string];
	if (asymbol(string))
		{
		node=[[COWSSymbolNode alloc] init];
		[node setState:COWSSTARTFOR];
		[node setString:"for"];
		[[node variable] copyValue:token];
		[token free];
		[stack push:node];
		return self;
		}
	[self _error:COWSERRORFORNOTAVARIABLE
				:[current_function string]:current_position:[token string]];
	[token free];
	return NULL;		
	}
	
	
	
-_doFor
	// continues processing "for" token
	{
	id node=[stack topSymbol];
	int state=[node state];
	if (state==COWSSTARTFOR)
		{
		id val=[stack pop];
		if (anumber([val string]))
			{
			[node setStart:[val floatVal]];
			[node setState:COWSENDFOR];
			[val free];
			return self;
			}
		else
			{
			[self _error:COWSERRORFORSTARTNOTNUMBER
				:[current_function string]:current_position:[val string]];
			[val free];
			return NULL;
			}	
		}
	else if (state==COWSENDFOR)
		{
		id val=[stack pop];
		if (anumber([val string]))
			{
			[node setEnd:[val floatVal]];
			[node setState:COWSSTEPFOR];
			[val free];
			return self;
			}
		else
			{
			[self _error:COWSERRORFORSTOPNOTNUMBER
				:[current_function string]:current_position:[val string]];
			[val free];
			return NULL;
			}	
		}
	else if (state==COWSSTEPFOR)
		{
		id val=[stack pop];
		if (anumber([val string]))
			{
			BOOL test;
			id var=[node variable];
			const char* var_str=[var string];
			[node setStep:[val floatVal]];
			[node setState:COWSDOFOR];
			if ([current_dictionary isKey: (const void*) var_str])
				{
				char start_val[COWSLARGENUMBER];
				sprintf(start_val,"%f",[node start]);
				[(COWSStringNode*)[current_dictionary 
						valueForKey:(const void*)var_str] setString:
						start_val];
				[node setPos:current_position];
				}
			else if ([global_dictionary isKey: (const void*) var_str])
				{
				char start_val[COWSLARGENUMBER];
				sprintf(start_val,"%f",[node start]);
				[(COWSStringNode*)[global_dictionary 
						valueForKey:(const void*)var_str] setString:
						start_val];
				[node setPos:current_position];
				}
			else
				{
				[self _error:COWSERRORFORNOSUCHVARIABLE
					:[current_function string]:current_position:[val string]];
				[val free];
				return NULL;		
				}

			// now test limits
			
			if ([node step]>=0)
				{
				if ([node start] > [node end]) 
					{
					test=YES;
					}
				else test=NO;
				}
			else
				{
				if ([node start] < [node end])
					{
					test=YES;
					}
				else test=NO;
				}
			// and act on test by skipping ahead to finish
			if (test)
				{
				[node setState:COWSFALSEFOR];
				[self _skip];
				}
			[val free];
			return self;
			}
		else
			{
			[self _error:COWSERRORFORSTEPNOTNUMBER
				:[current_function string]:current_position:[val string]];
			[val free];
			return NULL;
			}	
		}
	else if (state==COWSDOFOR)
		{
		[node setState:COWSTESTFOR];
		return self;
		}
	else if (state==COWSCONTINUEFOR)
		{
		[node setState:COWSTESTFOR];
		return self;
		}
	else		// state==COWSFALSEFOR||state==COWSTESTFOR
		{
		[self _error:COWSERRORFORTOOMANYVALUES
				:[current_function string]:current_position:""];
		return NULL;
		}
	}
	


-_finishFor
	// finishes processing "for" token
	{
	id node=[stack topSymbol];
	int state=[node state];
/*	if (state==COWSTRUEFOR)
		{
		[[stack pop] free];		// removes for value
		current_position=[node pos];
		[node setState:COWSCONTINUEFOR];
		return NULL;				// we don't want _doKeywords running again.
		}
	else*/ if (state==COWSFALSEFOR)
		{
		id return_val=[stack pop];
		[[stack pop] free];				// removes for node
		[stack push:return_val];
		return self;
		}
	else if (state==COWSTESTFOR)
		{
		//char new_val[COWSLARGENUMBER];
		BOOL test;
		float nv;
		id variable;
		id var=[node variable];
		const char* var_str=[var string];
		
		[node setState:COWSCONTINUEFOR];
		
		// first, find variable
		
		if ([current_dictionary isKey: (const void*) var_str])
			{
			variable=(COWSStringNode*)[current_dictionary 
					valueForKey:(const void*)var_str];
			}
		else if ([global_dictionary isKey: (const void*) var_str])
			{
			variable=(COWSStringNode*)[global_dictionary 
					valueForKey:(const void*)var_str];
			}
		else
			{
			[self _error:COWSERRORFORNOSUCHVARIABLE
				:[current_function string]:current_position:var_str];
			return NULL;		
			}
		
		// then, increment variable
		//sprintf(new_val,"%f",atof([variable string])+[node step]);
		//[variable setString:new_val];
		[variable setDoubleVal:[variable doubleVal]+[node step]];
		nv=[variable doubleVal];
			
		// now test limits
		
		if ([node step]>=0)
			{
			if (nv > [node end]) 
				{
				test=YES;
				}
			else test=NO;
			}
		else
			{
			if (nv < [node end])
				{
				test=YES;
				}
			else test=NO;
			}
			
		// and act on test by killing for or continuing
		if (test)
			{
			id return_val=[stack pop];
			[[stack pop] free];				// removes for node
			[stack push:return_val];
			return self;
			}
		else 
			{
			[[stack pop] free];		// removes for value
			current_position=[node pos];
			return NULL;		// we don't want _doKeywords running again.
			}
		}
	else	// state== anything else
		{
		[self _error:COWSERRORFORNOTENOUGHVALUES
				:[current_function string]:current_position:""];
		return NULL;
		}
	}



- _makeTruth:(COWSStringNode*) string
	// converts t to "t" and f to ""
	
	{
	char* str=newstr([string string]);
	/*
	if (str[0]=='\"')
		{
		str[0]=str[1];
		str[1]='\0';
		}
	*/
	if (!strcmp(str,"\"t\"")) strcpy(str,"t");   // ""t"" -> "t"
	if (!strcmp(str,"f")) strcpy(str,"");		 // f -> ""
	[string setString:str];
	free(str);
	return self;
	}
	
	
	
	
			
- _makeNumber:(COWSStringNode*) string
	// prepares a number.  If the number's bad, it may turn out zero
	// or something different than the user expected
	
	{
	/*
	char str[256];					// a number big enough to hold any float
	float x=atof([string string]);
	sprintf(str,"%f",x);
	[string setString:str];
	*/
	return self;

	// this function is now defunct--it never worked right anyway,
	// and now serves no purpose.
	
	}
			
			
			
			
- _strip:(COWSStringNode*) string
	// strips a string of its "s
	
	{
	const char* str=[string string];
	char* new_str=newstr(&str[1]);			// gets rid of first "
	int x=strlen(new_str);
	if (x>0) new_str[x-1]='\0';				// gets rid of second "
	[string setString:new_str];
	free(new_str);
	return self;
	}
	
	
	
	
- (int) _skip		
	// skips one value, if there is one	
	// returns new position if successful, error if not.
	
	{
	int result=[self _item:[current_function string]:current_position];
	if (result>=0)		// not an error
		{
		current_position=result;
		}
	return result;
	}
	
	
	
- (int) _error:(int) this_error:(const char*) this_function:
	(int) this_position: (const char*) this_string
	
	// performs error-reporting
	// this may entail sending an error message to a delegate
	// returns the error
	
	{
	// need to stop also...
	
	[self stopInterpreting];
		
	if (printing_errors)
		{
		printf("\n================ERROR================\n");
		switch (this_error)
			{
			case COWSERRORNOSUCHFUNCTION : 
				printf ("No Such Function\n"); break;
			case COWSERRORNOTENOUGHARGUMENTS : 
				printf ("Not Enough Arguments\n"); break;
			case COWSERRORTOOMANYARGUMENTS : 
				printf ("Too Many Arguments\n"); break;
			case COWSLIBRARYFUNCTIONERROR : 
				printf ("Library Function Error\n"); break;
			case COWSERRORNOSUCHVARIABLE : 
				printf ("No Such Variable\n"); break;
			case COWSERRORNOMORETOKENS : 
				printf ("No More Tokens\n"); break;
			case COWSERRORNOFUNCTIONNAME : 
				printf ("No Function Name\n"); break;
			case COWSERRORSETNOTVARIABLE : 
				printf ("Set: Not a Variable\n"); break;
			case COWSERRORSETTOOMANYVALUES : 
				printf ("Set: Too Many Values\n"); break;
			case COWSERRORSETNOVALUE : 
				printf ("Set: No Value to Set\n"); break;
			case COWSERRORSETNOSUCHVARIABLE : 
				printf ("Set: No Such Variable\n"); break;
			case COWSERRORIFNOTHENCLAUSE : 
				printf ("If: No Then Clause\n"); break;
			case COWSERRORIFTOOMANYVALUES : 
				printf ("If: Too Many Values\n"); break;
			case COWSERRORIFNOTENOUGHVALUES : 
				printf ("If: Not Enough Values\n"); break;
			case COWSERRORWHILETOOMANYVALUES : 
				printf ("While: Too Many Values\n"); break;
			case COWSERRORWHILENOTENOUGHVALUES : 
				printf ("While: Not Enough Values\n"); break;
			case COWSERRORFORNOTAVARIABLE : 
				printf ("For: Not a Variable\n"); break;
			case COWSERRORFORSTARTNOTNUMBER : 
				printf ("For: Start Value Not a Number\n"); break;
			case COWSERRORFORSTOPNOTNUMBER : 
				printf ("For: Stop Value Not a Number\n"); break;
			case COWSERRORFORSTEPNOTNUMBER : 
				printf ("For: Step Value Not a Number\n"); break;
			case COWSERRORFORNOSUCHVARIABLE : 
				printf ("For: No Such Variable\n"); break;
			case COWSERRORFORTOOMANYVALUES : 
				printf ("For: Too Many Values\n"); break;
			case COWSERRORFORNOTENOUGHVALUES : 
				printf ("For: Not Enough Values\n"); break;
			case COWSINTERNALERROR : 
				printf ("Interpreter Internal Error\n"); break;
			default : 
				printf ("Unknown Error\n"); break;
			}
		if (strlen(this_string) && this_error!=COWSLIBRARYFUNCTIONERROR)
			{
			printf ("Offending Symbol:  %s\n",this_string);
			}
		if (this_error==COWSLIBRARYFUNCTIONERROR)
			{
			printf ("Message:  %s\n",this_string);
			}
		printf ("In Function: %s\n", this_function);
		printf ("At Position: %d\n",this_position);
		printf ("=====================================\n\n");
	 	}
		
	if (tempDelegate!=NULL&&[tempDelegate conformsTo:@protocol (InterpreterToDelegate)])
			[tempDelegate errorInterpreting:this_error:this_function:
		this_position:this_string:self];
	if (delegate!=NULL&&[delegate conformsTo:@protocol (InterpreterToDelegate)])
			[delegate errorInterpreting:this_error:this_function:
		this_position:this_string:self];
	
	return this_error;
	}
		

- (BOOL) canPause		// returns YES if the interpreter can be paused.
						// in the case of this interpreter, it cannot be
						// paused unless it is in background mode only.
	{
	return (![self foreground]);
	}		

- printErrors:(BOOL) yes_or_no
	{
	printing_errors=yes_or_no;
	return self;
	}
	
- (BOOL) printingErrors
	{
	return printing_errors;
	}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.