summaryrefslogblamecommitdiffstats
path: root/c/src/libnetworking/rtems_webserver/ejparse.c
blob: 8863c069ea49397a9581add3a1f9bc1202b383bc (plain) (tree)
1
2
3
4


                                   
                                                                       












                                                                                




                                        







                                                                                                                                  



                                               

                                                  
                                                                       
                                                              









                                                                              
                                                              

















                                                                                  

                                                                                












                                                                          
















































                                                                                


                                                               
                 
                                                                     
         
 
                                          
                                        





                                                     
                    




                                                                                












                                                        
 







                                                                
        
                                     
                           


                                                     
        
                                                                
                           


                                                               


                                                                    



                                                         
        
                                     
                   
 
                                                                       










                                                                   
                         




                                                                                
                                                       




                        
                            


                                       

         


                                                           
 

                                          
         













                                                                                
 


                                       
                                                 
                                                               
                 

 
 












                                                                                
                               







                                                                                
                                                                               








                                                                            



                                    



                             

          



                                        
                                           









                                                             


                               

                                                          

















                                                                                   


















                                                           
 


                                 
 
















                                                                                






                                                                               
                       



                                                                              


                        



                                                                               














































                                                                                
                                            
                                                                                            
                                          


















































                                                                                       
                                                               





                                                                                          
                                                                 




                                                                                               


                                                                                                  








                                                                                                        


                                                                                          


                                                                                                      
                                                                           



                                                                                                





                                                                                                








                                                                                        
                                                                                           







                                                                                                
                                                                                                 





                                                                                 
                                                                   













                                                                                 
                                                               













                                                                                           
                                                               

                                         
                                                           


























































                                                                                       
                                                                            
   





                                                                   

                                           



                                                               






                                                                      
                                                                   
   





                                                                   






























                                                                              
                                                                                      











                                                                                          
 












                                                                                         
                                                                              














                                                                                 






                                                                       

































































                                                                                          






                                                 


















                                                                                  
                                                 













                                                            
                                            











                                                     
 





















































































































                                                                                
                                           












                                                                   
 
                                   
                                                         




                                                                   
                                                 


















                                                                  
 

















                                                                                
                                           

                         
                





                                                                      






                                                                                            



                                                  
 














                                                                      
 

                                                                    








                                                              
                  
                                


                  
                                

         

















                                                                                
                                                         













                                                               
                                           




                                                                           
 
                                        
                                         





















                                                                                
                                          



                                    
 

                                           
                                                  




                                            
 
























































                                                


                                                











































                                                               
                                         
















                                                                                
 





                                                                            
                                                                      



















                                                                                
                                                   


                       
                                                                                      


























                                                                                
                                                          



                      
                                    
         
                                  



























                                                                                
                                              














                                                                                
                                                               









                                                                                














                                                                                











                                                                              
 








                                                                                    
                                                                              












































































































                                                                                
                                       
































                                                                                
 























                                                                                    
 









                                                                                
                                                                          












                                                                                  
 

























                                                                                




                                                                                  
                 
         


                                             


                                                                                










                                        
                              
 
 



























                                                                                
 






                                                                                
  














                                                                                














                                                                                
/*
 * ejparse.c -- Ejscript(TM) Parser
 *
 * Copyright (c) GoAhead Software Inc., 1995-2000. All Rights Reserved.
 *
 * See the file "license.txt" for usage and redistribution license requirements
 */

/******************************** Description *********************************/

/*
 *	Ejscript parser. This implementes a subset of the JavaScript language.
 *	Multiple Ejscript parsers can be opened at a time.
 */

/********************************** Includes **********************************/

#include	"ejIntrn.h"

#if CE
	#include	"CE/wincompat.h"
#endif

/********************************** Local Data ********************************/

ej_t			**ejHandles;							/* List of ej handles */
int				ejMax = -1;								/* Maximum size of	*/

/****************************** Forward Declarations **************************/

#ifndef B_STATS
#define	setString(a,b,c)	 setstring(b,c)
#endif

static ej_t		*ejPtr(int eid);
static void		clearString(char_t **ptr);
static void		setString(B_ARGS_DEC, char_t **ptr, char_t *s);
static void		appendString(char_t **ptr, char_t *s);
static int		parse(ej_t *ep, int state, int flags);
static int		parseStmt(ej_t *ep, int state, int flags);
static int		parseDeclaration(ej_t *ep, int state, int flags);
static int		parseArgs(ej_t *ep, int state, int flags);
static int		parseCond(ej_t *ep, int state, int flags);
static int		parseExpr(ej_t *ep, int state, int flags);
static int		evalExpr(ej_t *ep, char_t *lhs, int rel, char_t *rhs);
static int		evalCond(ej_t *ep, char_t *lhs, int rel, char_t *rhs);
static int		evalFunction(ej_t *ep);
static void		freeFunc(ejfunc_t *func);
static void		ejRemoveNewlines(ej_t *ep, int state);

/************************************* Code ***********************************/
/*
 *	Initialize a Ejscript engine
 */

int ejOpenEngine(sym_fd_t variables, sym_fd_t functions)
{
	ej_t	*ep;
	int		eid, vid;

	if ((eid = hAllocEntry((void***) &ejHandles, &ejMax, sizeof(ej_t))) < 0) {
		return -1;
	}
	ep = ejHandles[eid];
	ep->eid = eid;

/*
 *	Create a top level symbol table if one is not provided for variables and
 *	functions. Variables may create other symbol tables for block level
 *	declarations so we use hAlloc to manage a list of variable tables.
 */
	if ((vid = hAlloc((void***) &ep->variables)) < 0) {
		ejMax = hFree((void***) &ejHandles, ep->eid);
		return -1;
	}
	if (vid >= ep->variableMax) {
		ep->variableMax = vid + 1;
	}

	if (variables == -1) {
		ep->variables[vid] = symOpen(64) + EJ_OFFSET;
		ep->flags |= FLAGS_VARIABLES;
	} else {
		ep->variables[vid] = variables + EJ_OFFSET;
	}

	if (functions == -1) {
		ep->functions = symOpen(64);
		ep->flags |= FLAGS_FUNCTIONS;
	} else {
		ep->functions = functions;
	}

	ejLexOpen(ep);

/*
 *	Define standard constants
 */
	ejSetGlobalVar(ep->eid, T("null"), NULL);

#if EMF
	ejEmfOpen(ep->eid);
#endif
	return ep->eid;
}

/******************************************************************************/
/*
 *	Close
 */

void ejCloseEngine(int eid)
{
	ej_t	*ep;
	int		i;

	if ((ep = ejPtr(eid)) == NULL) {
		return;
	}

#if EMF
	ejEmfClose(eid);
#endif

	bfreeSafe(B_L, ep->error);
	ep->error = NULL;
	bfreeSafe(B_L, ep->result);
	ep->result = NULL;

	ejLexClose(ep);

	for (i = ep->variableMax - 1; i >= 0; i--) {
		if (ep->flags & FLAGS_VARIABLES) {
			symClose(ep->variables[i] - EJ_OFFSET);
		}
		ep->variableMax = hFree((void***) &ep->variables, i);
	}

	if (ep->flags & FLAGS_FUNCTIONS) {
		symClose(ep->functions);
	}

	ejMax = hFree((void***) &ejHandles, ep->eid);
	bfree(B_L, ep);
}

#ifndef __NO_EJ_FILE
/******************************************************************************/
/*
 *	Evaluate a Ejscript file
 */

char_t *ejEvalFile(int eid, char_t *path, char_t **emsg)
{
	gstat_t sbuf;
	ej_t	*ep;
	char_t	*script, *rs;
	char	*fileBuf;
	int		fd;

	a_assert(path && *path);

	if (emsg) {
		*emsg = NULL;
	}

	if ((ep = ejPtr(eid)) == NULL) {
		return NULL;
	}

	if ((fd = gopen(path, O_RDONLY | O_BINARY, 0666)) < 0) {
		ejError(ep, T("Bad handle %d"), eid);
		return NULL;
	}
	
	if (gstat(path, &sbuf) < 0) {
		gclose(fd);
		ejError(ep, T("Cant stat %s"), path);
		return NULL;
	}
	
	if ((fileBuf = balloc(B_L, sbuf.st_size + 1)) == NULL) {
		gclose(fd);
		ejError(ep, T("Cant malloc %d"), sbuf.st_size);
		return NULL;
	}
	
	if (gread(fd, fileBuf, sbuf.st_size) != (int)sbuf.st_size) {
		gclose(fd);
		bfree(B_L, fileBuf);
		ejError(ep, T("Error reading %s"), path);
		return NULL;
	}
	
	fileBuf[sbuf.st_size] = '\0';
	gclose(fd);

	if ((script = ballocAscToUni(fileBuf, sbuf.st_size)) == NULL) {
		bfree(B_L, fileBuf);
		ejError(ep, T("Cant malloc %d"), sbuf.st_size + 1);
		return NULL;
	}
	bfree(B_L, fileBuf);

	rs = ejEvalBlock(eid, script, emsg);

	bfree(B_L, script);
	return rs;
}
#endif /* __NO_EJ_FILE */

/******************************************************************************/
/*
 *	Create a new variable scope block so that consecutive ejEval calls may
 *	be made with the same varible scope. This space MUST be closed with
 *	ejCloseBlock when the evaluations are complete.
 */

int ejOpenBlock(int eid)
{
	ej_t	*ep;
	int		vid;

	if((ep = ejPtr(eid)) == NULL) {
		return -1;
	}

	if ((vid = hAlloc((void***) &ep->variables)) < 0) {
		return -1;
	}

	if (vid >= ep->variableMax) {
		ep->variableMax = vid + 1;
	}
	ep->variables[vid] = symOpen(64) + EJ_OFFSET;
	return vid;

}

/******************************************************************************/
/*
 *	Close a variable scope block. The vid parameter is the return value from
 *	the call to ejOpenBlock
 */

int ejCloseBlock(int eid, int vid)
{
	ej_t	*ep;

	if((ep = ejPtr(eid)) == NULL) {
		return -1;
	}
	symClose(ep->variables[vid] - EJ_OFFSET);
	ep->variableMax = hFree((void***) &ep->variables, vid);
	return 0;

}

/******************************************************************************/
/*
 *	Create a new variable scope block and evaluate a script. All variables
 *	created during this context will be automatically deleted when complete.
 */

char_t *ejEvalBlock(int eid, char_t *script, char_t **emsg)
{
	char_t* returnVal;
	int		vid;

	a_assert(script);

	vid = ejOpenBlock(eid);
	returnVal = ejEval(eid, script, emsg);
	ejCloseBlock(eid, vid);

	return returnVal;
}

/******************************************************************************/
/*
 *	Parse and evaluate a Ejscript. The caller may provide a symbol table to
 *	use for variables and function definitions. Return char_t pointer on
 *	success otherwise NULL pointer is returned.
 */

char_t *ejEval(int eid, char_t *script, char_t **emsg)
{
	ej_t	*ep;
	ejinput_t	*oldBlock;
	int		state;
	void	*endlessLoopTest;
	int		loopCounter;
	
	
	a_assert(script);

	if (emsg) {
		*emsg = NULL;
	} 

	if ((ep = ejPtr(eid)) == NULL) {
		return NULL;
	}

	setString(B_L, &ep->result, T(""));

/*
 *	Allocate a new evaluation block, and save the old one
 */
	oldBlock = ep->input;
	ejLexOpenScript(ep, script);

/*
 *	Do the actual parsing and evaluation
 */
	loopCounter = 0;
	endlessLoopTest = NULL;

	do {
		state = parse(ep, STATE_BEGIN, FLAGS_EXE);

		if (state == STATE_RET) {
			state = STATE_EOF;
		}
/*
 *		prevent parser from going into infinite loop.  If parsing the same
 *		line 10 times then fail and report Syntax error.  Most normal error
 *		are caught in the parser itself.
 */
		if (endlessLoopTest == ep->input->script.servp) {
			if (loopCounter++ > 10) {
				state = STATE_ERR;
				ejError(ep, T("Syntax error"));
			}
		} else {
			endlessLoopTest = ep->input->script.servp;
			loopCounter = 0;
		}
	} while (state != STATE_EOF && state != STATE_ERR);

	ejLexCloseScript(ep);

/*
 *	Return any error string to the user
 */
	if (state == STATE_ERR && emsg) {
		*emsg = bstrdup(B_L, ep->error);
	}

/*
 *	Restore the old evaluation block
 */
	ep->input = oldBlock;

	if (state == STATE_EOF) {
		return ep->result;
	}

	if (state == STATE_ERR) {
		return NULL;
	}

	return ep->result;
}

/******************************************************************************/
/*
 *	Recursive descent parser for Ejscript
 */

static int parse(ej_t *ep, int state, int flags)
{
	a_assert(ep);

	switch (state) {
/*
 *	Any statement, function arguments or conditional expressions
 */
	case STATE_STMT:
		if ((state = parseStmt(ep, state, flags)) != STATE_STMT_DONE &&
			state != STATE_EOF && state != STATE_STMT_BLOCK_DONE &&
			state != STATE_RET) {
			state = STATE_ERR;
		}
		break;

	case STATE_DEC:
		if ((state = parseStmt(ep, state, flags)) != STATE_DEC_DONE &&
			state != STATE_EOF) {
			state = STATE_ERR;
		}
		break;

	case STATE_EXPR:
		if ((state = parseStmt(ep, state, flags)) != STATE_EXPR_DONE &&
			state != STATE_EOF) {
			state = STATE_ERR;
		}
		break;

/*
 *	Variable declaration list
 */
	case STATE_DEC_LIST:
		state = parseDeclaration(ep, state, flags);
		break;

/*
 *	Function argument string
 */
	case STATE_ARG_LIST:
		state = parseArgs(ep, state, flags);
		break;

/*
 *	Logical condition list (relational operations separated by &&, ||)
 */
	case STATE_COND:
		state = parseCond(ep, state, flags);
		break;

/*
 *	Expression list
 */
	case STATE_RELEXP:
		state = parseExpr(ep, state, flags);
		break;
	}

	if (state == STATE_ERR && ep->error == NULL) {
		ejError(ep, T("Syntax error"));
	}
	return state;
}

/******************************************************************************/
/*
 *	Parse any statement including functions and simple relational operations
 */

static int parseStmt(ej_t *ep, int state, int flags)
{
	ejfunc_t	func;
	ejfunc_t	*saveFunc;
	ejinput_t	condScript, endScript, bodyScript, incrScript;
	char_t		*value,	*identifier;
	int			done, expectSemi, thenFlags, elseFlags, tid, cond, forFlags;
	int			ejVarType;

	a_assert(ep);

/*
 *	Set these to NULL, else we try to free them if an error occurs.
 */
	endScript.putBackToken = NULL;
	bodyScript.putBackToken = NULL;
	incrScript.putBackToken = NULL;
	condScript.putBackToken = NULL;

	expectSemi = 0;
	saveFunc = NULL;

	for (done = 0; !done; ) {
		tid = ejLexGetToken(ep, state);

		switch (tid) {
		default:
			ejLexPutbackToken(ep, TOK_EXPR, ep->token);
			done++;
			break;

		case TOK_ERR:
			state = STATE_ERR;
			done++;
			break;

		case TOK_EOF:
			state = STATE_EOF;
			done++;
			break;

		case TOK_NEWLINE:
			break;

		case TOK_SEMI:
/*
 *			This case is when we discover no statement and just a lone ';'
 */
			if (state != STATE_STMT) {
				ejLexPutbackToken(ep, tid, ep->token);
			}
			done++;
			break;

		case TOK_ID:
/*
 *			This could either be a reference to a variable or an assignment
 */
			identifier = NULL;
			setString(B_L, &identifier, ep->token);
/*
 *			Peek ahead to see if this is an assignment
 */
			tid = ejLexGetToken(ep, state);
			if (tid == TOK_ASSIGNMENT) {
				if (parse(ep, STATE_RELEXP, flags) != STATE_RELEXP_DONE) {
					clearString(&identifier);
					goto error;
				}
				if (flags & FLAGS_EXE) {
					if ( state == STATE_DEC ) {
						ejSetLocalVar(ep->eid, identifier, ep->result);
					} else {
						ejVarType = ejGetVar(ep->eid, identifier, &value);
						if (ejVarType > 0) {
							ejSetLocalVar(ep->eid, identifier, ep->result);
						} else {
							ejSetGlobalVar(ep->eid, identifier, ep->result);
						}
					}
				}

			} else if (tid == TOK_INC_DEC ) {
				value = NULL;
				if (flags & FLAGS_EXE) {
					ejVarType = ejGetVar(ep->eid, identifier, &value);
					if (ejVarType < 0) {
						ejError(ep, T("Undefined variable %s\n"), identifier);
						goto error;
					}
					setString(B_L, &ep->result, value);
					if (evalExpr(ep, value, (int) *ep->token, T("1")) < 0) {
						state = STATE_ERR;
						break;
					}

					if (ejVarType > 0) {
						ejSetLocalVar(ep->eid, identifier, ep->result);
					} else {
						ejSetGlobalVar(ep->eid, identifier, ep->result);
					}
				}

			} else {
/*
 *				If we are processing a declaration, allow undefined vars
 */
				value = NULL;
				if (state == STATE_DEC) {
					if (ejGetVar(ep->eid, identifier, &value) > 0) {
						ejError(ep, T("Variable already declared"),
							identifier);
						clearString(&identifier);
						goto error;
					}
					ejSetLocalVar(ep->eid, identifier, NULL);
				} else {
					if ( flags & FLAGS_EXE ) {
						if (ejGetVar(ep->eid, identifier, &value) < 0) {
							ejError(ep, T("Undefined variable %s\n"),
								identifier);
							clearString(&identifier);
							goto error;
						}
					}
				}
				setString(B_L, &ep->result, value);
				ejLexPutbackToken(ep, tid, ep->token);
			}
			clearString(&identifier);

			if (state == STATE_STMT) {
				expectSemi++;
			}
			done++;
			break;

		case TOK_LITERAL:
/*
 *			Set the result to the literal (number or string constant)
 */
			setString(B_L, &ep->result, ep->token);
			if (state == STATE_STMT) {
				expectSemi++;
			}
			done++;
			break;

		case TOK_FUNCTION:
/*
 *			We must save any current ep->func value for the current stack frame
 */
			if (ep->func) {
				saveFunc = ep->func;
			}
			memset(&func, 0, sizeof(ejfunc_t));
			setString(B_L, &func.fname, ep->token);
			ep->func = &func;

			setString(B_L, &ep->result, T(""));
			if (ejLexGetToken(ep, state) != TOK_LPAREN) {
				freeFunc(&func);
				goto error;
			}

			if (parse(ep, STATE_ARG_LIST, flags) != STATE_ARG_LIST_DONE) {
				freeFunc(&func);
				ep->func = saveFunc;
				goto error;
			}
/*
 *			Evaluate the function if required
 */
			if (flags & FLAGS_EXE && evalFunction(ep) < 0) {
				freeFunc(&func);
				ep->func = saveFunc;
				goto error;
			}

			freeFunc(&func);
			ep->func = saveFunc;

			if (ejLexGetToken(ep, state) != TOK_RPAREN) {
				goto error;
			}
			if (state == STATE_STMT) {
				expectSemi++;
			}
			done++;
			break;

		case TOK_IF:
			if (state != STATE_STMT) {
				goto error;
			}
			if (ejLexGetToken(ep, state) != TOK_LPAREN) {
				goto error;
			}
/*
 *			Evaluate the entire condition list "(condition)"
 */
			if (parse(ep, STATE_COND, flags) != STATE_COND_DONE) {
				goto error;
			}
			if (ejLexGetToken(ep, state) != TOK_RPAREN) {
				goto error;
			}
/*
 *			This is the "then" case. We need to always parse both cases and
 *			execute only the relevant case.
 */
			if (*ep->result == '1') {
				thenFlags = flags;
				elseFlags = flags & ~FLAGS_EXE;
			} else {
				thenFlags = flags & ~FLAGS_EXE;
				elseFlags = flags;
			}
/*
 *			Process the "then" case.  Allow for RETURN statement
 */
			switch (parse(ep, STATE_STMT, thenFlags)) {
			case STATE_RET:
				return STATE_RET;
			case STATE_STMT_DONE:
				break;
			default:
				goto error;
			}
/*
 *			check to see if there is an "else" case
 */
			ejRemoveNewlines(ep, state);
			tid = ejLexGetToken(ep, state);
			if (tid != TOK_ELSE) {
				ejLexPutbackToken(ep, tid, ep->token);
				done++;
				break;
			}
/*
 *			Process the "else" case.  Allow for return.
 */
			switch (parse(ep, STATE_STMT, elseFlags)) {
			case STATE_RET:
				return STATE_RET;
			case STATE_STMT_DONE:
				break;
			default:
				goto error;
			}
			done++;
			break;

		case TOK_FOR:
/*
 *			Format for the expression is:
 *
 *				for (initial; condition; incr) {
 *					body;
 *				}
 */
			if (state != STATE_STMT) {
				goto error;
			}
			if (ejLexGetToken(ep, state) != TOK_LPAREN) {
				goto error;
			}

/*
 *			Evaluate the for loop initialization statement
 */
			if (parse(ep, STATE_EXPR, flags) != STATE_EXPR_DONE) {
				goto error;
			}
			if (ejLexGetToken(ep, state) != TOK_SEMI) {
				goto error;
			}

/*
 *			The first time through, we save the current input context just
 *			to each step: prior to the conditional, the loop increment and the
 *			loop body.
 */
			ejLexSaveInputState(ep, &condScript);
			if (parse(ep, STATE_COND, flags) != STATE_COND_DONE) {
				goto error;
			}
			cond = (*ep->result != '0');

			if (ejLexGetToken(ep, state) != TOK_SEMI) {
				goto error;
			}

/*
 *			Don't execute the loop increment statement or the body first time
 */
			forFlags = flags & ~FLAGS_EXE;
			ejLexSaveInputState(ep, &incrScript);
			if (parse(ep, STATE_EXPR, forFlags) != STATE_EXPR_DONE) {
				goto error;
			}
			if (ejLexGetToken(ep, state) != TOK_RPAREN) {
				goto error;
			}

/*
 *			Parse the body and remember the end of the body script
 */
			ejLexSaveInputState(ep, &bodyScript);
			if (parse(ep, STATE_STMT, forFlags) != STATE_STMT_DONE) {
				goto error;
			}
			ejLexSaveInputState(ep, &endScript);

/*
 *			Now actually do the for loop. Note loop has been rotated
 */
			while (cond && (flags & FLAGS_EXE) ) {
/*
 *				Evaluate the body
 */
				ejLexRestoreInputState(ep, &bodyScript);

				switch (parse(ep, STATE_STMT, flags)) {
				case STATE_RET:
					return STATE_RET;
				case STATE_STMT_DONE:
					break;
				default:
					goto error;
				}
/*
 *				Evaluate the increment script
 */
				ejLexRestoreInputState(ep, &incrScript);
				if (parse(ep, STATE_EXPR, flags) != STATE_EXPR_DONE) {
					goto error;
				}
/*
 *				Evaluate the condition
 */
				ejLexRestoreInputState(ep, &condScript);
				if (parse(ep, STATE_COND, flags) != STATE_COND_DONE) {
					goto error;
				}
				cond = (*ep->result != '0');
			}
			ejLexRestoreInputState(ep, &endScript);
			done++;
			break;

		case TOK_VAR:
			if (parse(ep, STATE_DEC_LIST, flags) != STATE_DEC_LIST_DONE) {
				goto error;
			}
			done++;
			break;

		case TOK_COMMA:
			ejLexPutbackToken(ep, TOK_EXPR, ep->token);
			done++;
			break;

		case TOK_LPAREN:
			if (state == STATE_EXPR) {
				if (parse(ep, STATE_RELEXP, flags) != STATE_RELEXP_DONE) {
					goto error;
				}
				if (ejLexGetToken(ep, state) != TOK_RPAREN) {
					goto error;
				}
				return STATE_EXPR_DONE;
			}
			done++;
			break;

		case TOK_RPAREN:
			ejLexPutbackToken(ep, tid, ep->token);
			return STATE_EXPR_DONE;

		case TOK_LBRACE:
/*
 *			This handles any code in braces except "if () {} else {}"
 */
			if (state != STATE_STMT) {
				goto error;
			}

/*
 *			Parse will return STATE_STMT_BLOCK_DONE when the RBRACE is seen
 */
			do {
				state = parse(ep, STATE_STMT, flags);
			} while (state == STATE_STMT_DONE);

/*
 *			Allow return statement.
 */
			if (state == STATE_RET) {
				return state;
			}

			if (ejLexGetToken(ep, state) != TOK_RBRACE) {
				goto error;
			}
			return STATE_STMT_DONE;

		case TOK_RBRACE:
			if (state == STATE_STMT) {
				ejLexPutbackToken(ep, tid, ep->token);
				return STATE_STMT_BLOCK_DONE;
			}
			goto error;

		case TOK_RETURN:
			if (parse(ep, STATE_RELEXP, flags) != STATE_RELEXP_DONE) {
				goto error;
			}
			if (flags & FLAGS_EXE) {
				while ( ejLexGetToken(ep, state) != TOK_EOF );
				done++;
				return STATE_RET;
			}
			break;
		}
	}

	if (expectSemi) {
		tid = ejLexGetToken(ep, state);
		if (tid != TOK_SEMI && tid != TOK_NEWLINE) {
			goto error;
		}

/*
 *		Skip newline after semi-colon
 */
		ejRemoveNewlines(ep, state);
	}

/*
 *	Free resources and return the correct status
 */
doneParse:
	if (tid == TOK_FOR) {
		ejLexFreeInputState(ep, &condScript);
		ejLexFreeInputState(ep, &incrScript);
		ejLexFreeInputState(ep, &endScript);
		ejLexFreeInputState(ep, &bodyScript);
	}

	if (state == STATE_STMT) {
		return STATE_STMT_DONE;
	} else if (state == STATE_DEC) {
		return STATE_DEC_DONE;
	} else if (state == STATE_EXPR) {
		return STATE_EXPR_DONE;
	} else if (state == STATE_EOF) {
		return state;
	} else {
		return STATE_ERR;
	}

/*
 *	Common error exit
 */
error:
	state = STATE_ERR;
	goto doneParse;
}

/******************************************************************************/
/*
 *	Parse variable declaration list
 */

static int parseDeclaration(ej_t *ep, int state, int flags)
{
	int		tid;

	a_assert(ep);

/*
 *	Declarations can be of the following forms:
 *			var x;
 *			var x, y, z;
 *			var x = 1 + 2 / 3, y = 2 + 4;
 *
 *	We set the variable to NULL if there is no associated assignment.
 */

	do {
		if ((tid = ejLexGetToken(ep, state)) != TOK_ID) {
			return STATE_ERR;
		}
		ejLexPutbackToken(ep, tid, ep->token);

/*
 *		Parse the entire assignment or simple identifier declaration
 */
		if (parse(ep, STATE_DEC, flags) != STATE_DEC_DONE) {
			return STATE_ERR;
		}

/*
 *		Peek at the next token, continue if comma seen
 */
		tid = ejLexGetToken(ep, state);
		if (tid == TOK_SEMI) {
			return STATE_DEC_LIST_DONE;
		} else if (tid != TOK_COMMA) {
			return STATE_ERR;
		}
	} while (tid == TOK_COMMA);

	if (tid != TOK_SEMI) {
		return STATE_ERR;
	}
	return STATE_DEC_LIST_DONE;
}

/******************************************************************************/
/*
 *	Parse function arguments
 */

static int parseArgs(ej_t *ep, int state, int flags)
{
	int		tid, aid;

	a_assert(ep);

	do {
		state = parse(ep, STATE_RELEXP, flags);
		if (state == STATE_EOF || state == STATE_ERR) {
			return state;
		}
		if (state == STATE_RELEXP_DONE) {
			aid = hAlloc((void***) &ep->func->args);
			ep->func->args[aid] = bstrdup(B_L, ep->result);
			ep->func->nArgs++;
		}
/*
 *		Peek at the next token, continue if more args (ie. comma seen)
 */
		tid = ejLexGetToken(ep, state);
		if (tid != TOK_COMMA) {
			ejLexPutbackToken(ep, tid, ep->token);
		}
	} while (tid == TOK_COMMA);

	if (tid != TOK_RPAREN && state != STATE_RELEXP_DONE) {
		return STATE_ERR;
	}
	return STATE_ARG_LIST_DONE;
}

/******************************************************************************/
/*
 *	Parse conditional expression (relational ops separated by ||, &&)
 */

static int parseCond(ej_t *ep, int state, int flags)
{
	char_t	*lhs, *rhs;
	int		tid, operator;

	a_assert(ep);

	setString(B_L, &ep->result, T(""));
	rhs = lhs = NULL;
	operator = 0;

	do {
/*
 *	Recurse to handle one side of a conditional. Accumulate the
 *	left hand side and the final result in ep->result.
 */
		state = parse(ep, STATE_RELEXP, flags);
		if (state != STATE_RELEXP_DONE) {
			state = STATE_ERR;
			break;
		}

		if (operator > 0) {
			setString(B_L, &rhs, ep->result);
			if (evalCond(ep, lhs, operator, rhs) < 0) {
				state = STATE_ERR;
				break;
			}
		}
		setString(B_L, &lhs, ep->result);

		tid = ejLexGetToken(ep, state);
		if (tid == TOK_LOGICAL) {
			operator = (int) *ep->token;

		} else if (tid == TOK_RPAREN || tid == TOK_SEMI) {
			ejLexPutbackToken(ep, tid, ep->token);
			state = STATE_COND_DONE;
			break;

		} else {
			ejLexPutbackToken(ep, tid, ep->token);
		}

	} while (state == STATE_RELEXP_DONE);

	if (lhs) {
		bfree(B_L, lhs);
	}

	if (rhs) {
		bfree(B_L, rhs);
	}
	return state;
}

/******************************************************************************/
/*
 *	Parse expression (leftHandSide operator rightHandSide)
 */

static int parseExpr(ej_t *ep, int state, int flags)
{
	char_t	*lhs, *rhs;
	int		rel, tid;

	a_assert(ep);

	setString(B_L, &ep->result, T(""));
	rhs = lhs = NULL;
	rel = 0;
	tid = 0;

	do {
/*
 *	This loop will handle an entire expression list. We call parse
 *	to evalutate each term which returns the result in ep->result.
 */
		if (tid == TOK_LOGICAL) {
			if ((state = parse(ep, STATE_RELEXP, flags)) != STATE_RELEXP_DONE) {
				state = STATE_ERR;
				break;
			}
		} else {
			if ((state = parse(ep, STATE_EXPR, flags)) != STATE_EXPR_DONE) {
				state = STATE_ERR;
				break;
			}
		}

		if (rel > 0) {
			setString(B_L, &rhs, ep->result);
			if (tid == TOK_LOGICAL) {
				if (evalCond(ep, lhs, rel, rhs) < 0) {
					state = STATE_ERR;
					break;
				}
			} else {
				if (evalExpr(ep, lhs, rel, rhs) < 0) {
					state = STATE_ERR;
					break;
				}
			}
		}
		setString(B_L, &lhs, ep->result);

		if ((tid = ejLexGetToken(ep, state)) == TOK_EXPR ||
			 tid == TOK_INC_DEC || tid == TOK_LOGICAL) {
			rel = (int) *ep->token;

		} else {
			ejLexPutbackToken(ep, tid, ep->token);
			state = STATE_RELEXP_DONE;
		}

	} while (state == STATE_EXPR_DONE);

	if (rhs) {
		bfree(B_L, rhs);
	}

	if (lhs) {
		bfree(B_L, lhs);
	}

	return state;
}

/******************************************************************************/
/*
 *	Evaluate a condition. Implements &&, ||, !
 */

static int evalCond(ej_t *ep, char_t *lhs, int rel, char_t *rhs)
{
	char_t	buf[16];
	int		l, r, lval;

	a_assert(lhs);
	a_assert(rhs);
	a_assert(rel > 0);

	lval = 0;
	if (gisdigit((int)*lhs) && gisdigit((int)*rhs)) {
		l = gatoi(lhs);
		r = gatoi(rhs);
		switch (rel) {
		case COND_AND:
			lval = l && r;
			break;
		case COND_OR:
			lval = l || r;
			break;
		default:
			ejError(ep, T("Bad operator %d"), rel);
			return -1;
		}
	} else {
		if (!gisdigit((int)*lhs)) {
			ejError(ep, T("Conditional must be numeric"), lhs);
		} else {
			ejError(ep, T("Conditional must be numeric"), rhs);
		}
	}

	stritoa(lval, buf, sizeof(buf));
	setString(B_L, &ep->result, buf);
	return 0;
}

/******************************************************************************/
/*
 *	Evaluate an operation
 */

static int evalExpr(ej_t *ep, char_t *lhs, int rel, char_t *rhs)
{
	char_t	*cp, buf[16];
	int		numeric, l, r, lval;

	a_assert(lhs);
	a_assert(rhs);
	a_assert(rel > 0);

/*
 *	All of the characters in the lhs and rhs must be numeric
 */
	numeric = 1;
	for (cp = lhs; *cp; cp++) {
		if (!gisdigit((int)*cp)) {
			numeric = 0;
			break;
		}
	}

	if (numeric) {
		for (cp = rhs; *cp; cp++) {
			if (!gisdigit((int)*cp)) {
				numeric = 0;
				break;
			}
		}
	}

	if (numeric) {
		l = gatoi(lhs);
		r = gatoi(rhs);
		switch (rel) {
		case EXPR_PLUS:
			lval = l + r;
			break;
		case EXPR_INC:
			lval = l + 1;
			break;
		case EXPR_MINUS:
			lval = l - r;
			break;
		case EXPR_DEC:
			lval = l - 1;
			break;
		case EXPR_MUL:
			lval = l * r;
			break;
		case EXPR_DIV:
			if (r != 0) {
				lval = l / r;
			} else {
				lval = 0;
			}
			break;
		case EXPR_MOD:
			if (r != 0) {
				lval = l % r;
			} else {
				lval = 0;
			}
			break;
		case EXPR_LSHIFT:
			lval = l << r;
			break;
		case EXPR_RSHIFT:
			lval = l >> r;
			break;
		case EXPR_EQ:
			lval = l == r;
			break;
		case EXPR_NOTEQ:
			lval = l != r;
			break;
		case EXPR_LESS:
			lval = (l < r) ? 1 : 0;
			break;
		case EXPR_LESSEQ:
			lval = (l <= r) ? 1 : 0;
			break;
		case EXPR_GREATER:
			lval = (l > r) ? 1 : 0;
			break;
		case EXPR_GREATEREQ:
			lval = (l >= r) ? 1 : 0;
			break;
		case EXPR_BOOL_COMP:
			lval = (r == 0) ? 1 : 0;
			break;
		default:
			ejError(ep, T("Bad operator %d"), rel);
			return -1;
		}

	} else {
		switch (rel) {
		case EXPR_PLUS:
			clearString(&ep->result);
			appendString(&ep->result, lhs);
			appendString(&ep->result, rhs);
			return 0;
		case EXPR_LESS:
			lval = gstrcmp(lhs, rhs) < 0;
			break;
		case EXPR_LESSEQ:
			lval = gstrcmp(lhs, rhs) <= 0;
			break;
		case EXPR_GREATER:
			lval = gstrcmp(lhs, rhs) > 0;
			break;
		case EXPR_GREATEREQ:
			lval = gstrcmp(lhs, rhs) >= 0;
			break;
		case EXPR_EQ:
			lval = gstrcmp(lhs, rhs) == 0;
			break;
		case EXPR_NOTEQ:
			lval = gstrcmp(lhs, rhs) != 0;
			break;
		case EXPR_INC:
		case EXPR_DEC:
		case EXPR_MINUS:
		case EXPR_DIV:
		case EXPR_MOD:
		case EXPR_LSHIFT:
		case EXPR_RSHIFT:
		default:
			ejError(ep, T("Bad operator"));
			return -1;
		}
	}

	stritoa(lval, buf, sizeof(buf));
	setString(B_L, &ep->result, buf);
	return 0;
}

/******************************************************************************/
/*
 *	Evaluate a function
 */

static int evalFunction(ej_t *ep)
{
	sym_t	*sp;
	int		(*fn)(int eid, void *handle, int argc, char_t **argv);

	if ((sp = symLookup(ep->functions, ep->func->fname)) == NULL) {
		ejError(ep, T("Undefined procedure %s"), ep->func->fname);
		return -1;
	}

	fn = (int (*)(int, void*, int, char_t**)) sp->content.value.integer;
	if (fn == NULL) {
		ejError(ep, T("Undefined procedure %s"), ep->func->fname);
		return -1;
	}

	return (*fn)(ep->eid, (void*) ep->userHandle, ep->func->nArgs,
		ep->func->args);
}

/******************************************************************************/
/*
 *	Output a parse ej_error message
 */

void ejError(ej_t* ep, char_t* fmt, ...)
{
	va_list		args;
	ejinput_t	*ip;
	char_t		*errbuf, *msgbuf;

	a_assert(ep);
	a_assert(fmt);
	ip = ep->input;

	va_start(args, fmt);
	msgbuf = NULL;
	fmtValloc(&msgbuf, E_MAX_ERROR, fmt, args);
	va_end(args);

	if (ep && ip) {
		fmtAlloc(&errbuf, E_MAX_ERROR, T("%s\n At line %d, line => \n\n%s\n"),
			msgbuf, ip->lineNumber, ip->line);
		bfreeSafe(B_L, ep->error);
		ep->error = errbuf;
	}
	bfreeSafe(B_L, msgbuf);
}

/******************************************************************************/
/*
 *	Clear a string value
 */

static void clearString(char_t **ptr)
{
	a_assert(ptr);

	if (*ptr) {
		bfree(B_L, *ptr);
	}
	*ptr = NULL;
}

/******************************************************************************/
/*
 *	Set a string value
 */

static void setString(B_ARGS_DEC, char_t **ptr, char_t *s)
{
	a_assert(ptr);

	if (*ptr) {
		bfree(B_ARGS, *ptr);
	}
	*ptr = bstrdup(B_ARGS, s);
}

/******************************************************************************/
/*
 *	Append to the pointer value
 */

static void appendString(char_t **ptr, char_t *s)
{
	int	len, oldlen;

	a_assert(ptr);

	if (*ptr) {
		len = gstrlen(s);
		oldlen = gstrlen(*ptr);
		*ptr = brealloc(B_L, *ptr, (len + oldlen + 1) * sizeof(char_t));
		gstrcpy(&(*ptr)[oldlen], s);
	} else {
		*ptr = bstrdup(B_L, s);
	}
}

/******************************************************************************/
/*
 *	Define a function
 */

int ejSetGlobalFunction(int eid, char_t *name,
	int (*fn)(int eid, void *handle, int argc, char_t **argv))
{
	ej_t	*ep;

	if ((ep = ejPtr(eid)) == NULL) {
		return -1;
	}
	return ejSetGlobalFunctionDirect(ep->functions, name, fn);
}

/******************************************************************************/
/*
 *	Define a function directly into the function symbol table.
 */

int ejSetGlobalFunctionDirect(sym_fd_t functions, char_t *name,
	int (*fn)(int eid, void *handle, int argc, char_t **argv))
{
	if (symEnter(functions, name, valueInteger((long) fn), 0) == NULL) {
		return -1;
	}
	return 0;
}

/******************************************************************************/
/*
 *	Remove ("undefine") a function
 */

int ejRemoveGlobalFunction(int eid, char_t *name)
{
	ej_t	*ep;

	if ((ep = ejPtr(eid)) == NULL) {
		return -1;
	}
	return symDelete(ep->functions, name);
}

/******************************************************************************/
/*
 *	Get a function definition
 */

void *ejGetGlobalFunction(int eid, char_t *name)
{
	ej_t	*ep;
	sym_t	*sp;
	int		(*fn)(int eid, void *handle, int argc, char_t **argv);

	if ((ep = ejPtr(eid)) == NULL) {
		return NULL;
	}

	if ((sp = symLookup(ep->functions, name)) != NULL) {
		fn = (int (*)(int, void*, int, char_t**)) sp->content.value.integer;
		return (void*) fn;
	}
	return NULL;
}

/******************************************************************************/
/*
 *	Utility routine to crack Ejscript arguments. Return the number of args
 *	seen. This routine only supports %s and %d type args.
 *
 *	Typical usage:
 *
 *		if (ejArgs(argc, argv, "%s %d", &name, &age) < 2) {
 *			error("Insufficient args\n");
 *			return -1;
 *		}
 */

int ejArgs(int argc, char_t **argv, char_t *fmt, ...)
{
	va_list	vargs;
	char_t	*cp, **sp;
	int		*ip;
	int		argn;

	va_start(vargs, fmt);

	if (argv == NULL) {
		return 0;
	}

	for (argn = 0, cp = fmt; cp && *cp && argv[argn]; ) {
		if (*cp++ != '%') {
			continue;
		}

		switch (*cp) {
		case 'd':
			ip = va_arg(vargs, int*);
			*ip = gatoi(argv[argn]);
			break;

		case 's':
			sp = va_arg(vargs, char_t**);
			*sp = argv[argn];
			break;

		default:
/*
 *			Unsupported
 */
			a_assert(0);
		}
		argn++;
	}

	va_end(vargs);
	return argn;
}

/******************************************************************************/
/*
 *	Define the user handle
 */

void ejSetUserHandle(int eid, int handle)
{
	ej_t	*ep;

	if ((ep = ejPtr(eid)) == NULL) {
		return;
	}
	ep->userHandle = handle;
}

/******************************************************************************/
/*
 *	Get the user handle
 */

int ejGetUserHandle(int eid)
{
	ej_t	*ep;

	if ((ep = ejPtr(eid)) == NULL) {
		return -1;
	}
	return ep->userHandle;
}

/******************************************************************************/
/*
 *	Get the current line number
 */

int ejGetLineNumber(int eid)
{
	ej_t	*ep;

	if ((ep = ejPtr(eid)) == NULL) {
		return -1;
	}
	return ep->input->lineNumber;
}

/******************************************************************************/
/*
 *	Set the result
 */

void ejSetResult(int eid, char_t *s)
{
	ej_t	*ep;

	if ((ep = ejPtr(eid)) == NULL) {
		return;
	}
	setString(B_L, &ep->result, s);
}

/******************************************************************************/
/*
 *	Get the result
 */

char_t *ejGetResult(int eid)
{
	ej_t	*ep;

	if ((ep = ejPtr(eid)) == NULL) {
		return NULL;
	}
	return ep->result;
}

/******************************************************************************/
/*
 *	Set a variable. Note: a variable with a value of NULL means declared but
 *	undefined. The value is defined in the top-most variable frame.
 */

void ejSetVar(int eid, char_t *var, char_t *value)
{
	ej_t	*ep;
	value_t	v;

	a_assert(var && *var);

	if ((ep = ejPtr(eid)) == NULL) {
		return;
	}

	if (value == NULL) {
		v = valueString(value, 0);
	} else {
		v = valueString(value, VALUE_ALLOCATE);
	}
	symEnter(ep->variables[ep->variableMax - 1] - EJ_OFFSET, var, v, 0);
}

/******************************************************************************/
/*
 *	Set a local variable. Note: a variable with a value of NULL means
 *	declared but undefined. The value is defined in the top-most variable frame.
 */

void ejSetLocalVar(int eid, char_t *var, char_t *value)
{
	ej_t	*ep;
	value_t	v;

	a_assert(var && *var);

	if ((ep = ejPtr(eid)) == NULL) {
		return;
	}

	if (value == NULL) {
		v = valueString(value, 0);
	} else {
		v = valueString(value, VALUE_ALLOCATE);
	}
	symEnter(ep->variables[ep->variableMax - 1] - EJ_OFFSET, var, v, 0);
}

/******************************************************************************/
/*
 *	Set a global variable. Note: a variable with a value of NULL means
 *	declared but undefined. The value is defined in the global variable frame.
 */

void ejSetGlobalVar(int eid, char_t *var, char_t *value)
{
	ej_t	*ep;
	value_t v;

	a_assert(var && *var);

	if ((ep = ejPtr(eid)) == NULL) {
		return;
	}

	if (value == NULL) {
		v = valueString(value, 0);
	} else {
		v = valueString(value, VALUE_ALLOCATE);
	}
	symEnter(ep->variables[0] - EJ_OFFSET, var, v, 0);
}

/******************************************************************************/
/*
 *	Get a variable
 */

int ejGetVar(int eid, char_t *var, char_t **value)
{
	ej_t	*ep;
	sym_t	*sp;
	int		i;

	a_assert(var && *var);
	a_assert(value);

	if ((ep = ejPtr(eid)) == NULL) {
		return -1;
	}

	i = ep->variableMax - 1;
	if ((sp = symLookup(ep->variables[i] - EJ_OFFSET, var)) == NULL) {
		i = 0;
		if ((sp = symLookup(ep->variables[0] - EJ_OFFSET, var)) == NULL) {
			return -1;
		}
	}
	a_assert(sp->content.type == string);
	*value = sp->content.value.string;
	return i;
}

/******************************************************************************/
/*
 *	Get the variable symbol table
 */

sym_fd_t ejGetVariableTable(int eid)
{
	ej_t	*ep;

	if ((ep = ejPtr(eid)) == NULL) {
		return -1;
	}
	return *ep->variables;
}

/******************************************************************************/
/*
 *	Get the functions symbol table
 */

sym_fd_t ejGetFunctionTable(int eid)
{
	ej_t	*ep;

	if ((ep = ejPtr(eid)) == NULL) {
		return -1;
	}
	return ep->functions;
}

/******************************************************************************/
/*
 *	Free an argument list
 */

static void freeFunc(ejfunc_t *func)
{
	int	i;

	for (i = func->nArgs - 1; i >= 0; i--) {
		bfree(B_L, func->args[i]);
		func->nArgs = hFree((void***) &func->args, i);
	}

	if (func->fname) {
		bfree(B_L, func->fname);
		func->fname = NULL;
	}
}

/******************************************************************************/
/*
 *	Get Ejscript pointer
 */

static ej_t *ejPtr(int eid)
{
	a_assert(0 <= eid && eid < ejMax);

	if (eid < 0 || eid >= ejMax || ejHandles[eid] == NULL) {
		ejError(NULL, T("Bad handle %d"), eid);
		return NULL;
	}
	return ejHandles[eid];
}

/******************************************************************************/
/*
 *	This function removes any new lines.  Used for else	cases, etc.
 */
static void ejRemoveNewlines(ej_t *ep, int state)
{
	int tid;

	do {
		tid = ejLexGetToken(ep, state);
	} while (tid == TOK_NEWLINE);

	ejLexPutbackToken(ep, tid, ep->token);
}

/******************************************************************************/