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;
}
@endThese are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.