/* interpreter.c --- Main functions for executing JACL code.
 * (C) 2008 Stuart Allen, distribute and use 
 * according to GNU GPL, see file COPYING for details.
 */

#include "jacl.h"
#include "language.h"
#include "types.h"
#include "prototypes.h"
#include "csv.h"
#include "errno.h"
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#ifdef WIN32
struct flock {
	short l_type;
	short l_whence;
	off_t l_start;
	off_t l_len;
	pid_t l_pid;
};

#define F_DUPFD  0
#define F_GETFD  1
#define F_SETFD  2
#define F_GETFL  3
#define F_SETFL  4
#define F_GETLK  5
#define F_SETLK  6
#define F_SETLKW 7

#define F_RDLCK  0
#define F_WRLCK  1
#define F_UNLCK  2

int fcntl(int __fd, int __cmd, ...) { return 0; }
#endif /* WIN32 */

#ifndef strcasestr
char *strcasestr(const char *s, const char *find)
{
	char c, sc;
	size_t len;

	if ((c = *find++) != 0) {
		c = (char)tolower((unsigned char)c);
		len = strlen(find);
		do {
			do {
				if ((sc = *s++) == 0)
					return (NULL);
			} while ((char)tolower((unsigned char)sc) != c);
		} while (strncasecmp(s, find, len) != 0);
		s--;
	}
	return ((char *)s);
}
#endif

#define MAX_TRY 10 

struct flock	read_lck;
int				read_fd;
struct flock	write_lck;
int				write_fd;

char *url_encode(char *str);
char to_hex(char code);

char *location_attributes[] = {
 "VISITED ", "DARK ", "ON_WATER ", "UNDER_WATER ", "WITHOUT_AIR ", "OUTDOORS ",
 "MID_AIR ", "TIGHT_ROPE ", "POLLUTED ", "SOLVED ", "MID_WATER ", "DARKNESS ",
 "MAPPED ", "KNOWN ",
 NULL };

char *object_attributes[] = {
 "CLOSED ", "LOCKED ", "DEAD ", "IGNITABLE ", "WORN ", "CONCEALING ",
 "LUMINOUS ", "WEARABLE ", "CLOSABLE ", "LOCKABLE ", "ANIMATE ", "LIQUID ",
 "CONTAINER ", "SURFACE ", "PLURAL ", "FLAMMABLE ", "BURNING ", "LOCATION ",
 "ON ", "DAMAGED ", "FEMALE ", "POSSESSIVE ", "OUT_OF_REACH ", "TOUCHED ",
 "SCORED ", "SITTING ", "NPC ", "DONE ", "GAS ", "NO_TAB ",
 "NOT_IMPORTANT ", NULL };

char *object_elements[] = {
 "parent", "capacity", "mass", "bearing", "velocity", "next", "previous",
 "child", "index", "status", "state", "counter", "points", "class", "x", "y", 
 NULL };

char *location_elements[] = {
 "north", "south", "east", "west", "northeast", "northwest", "southeast",
 "southwest", "up", "down", "in", "out", "points", "class", "x", "y", 
 NULL };

struct csv_parser				parser_csv;
char							in_name[1024];
char							out_name[1024];
FILE 							*infile, *outfile;

int             				stack = 0;
int             				proxy_stack = 0;

int             				field_no = 0;

struct stack_type				backup[STACK_SIZE];
struct proxy_type				proxy_backup[STACK_SIZE];

struct function_type *resolved_function = NULL;
struct string_type *resolved_string = NULL;

struct string_type *new_string = NULL;
struct string_type *current_cstring = NULL;
struct string_type *previous_cstring = NULL;

struct cinteger_type *new_cinteger = NULL;
struct cinteger_type *current_cinteger = NULL;
struct cinteger_type *previous_cinteger = NULL;

long							bit_mask;
extern int 						encrypted;
extern int						after_from;
extern int						last_exact;

extern char						temp_directory[];
extern char						data_directory[];
char							csv_buffer[1024];

int								resolved_attribute;

/* THE ITERATION VARIABLE USED FOR LOOPS */
int								*loop_integer = NULL;
int								*select_integer = NULL;

int								criterion_value = 0;
int								criterion_type = 0;
int								criterion_negate = FALSE;
int             				current_level;
int             				execution_level;
int             				*ask_integer;
int								new_x;
int								new_y;

int								interrupted = FALSE;
char 							string_buffer[2048];
char							argument_buffer[1024];
#ifdef GLK
extern schanid_t				sound_channel[];
extern strid_t					game_stream;
extern winid_t					mainwin;
extern winid_t 					statuswin;
extern winid_t 					current_window;

extern strid_t 					mainstr;
extern strid_t 					statusstr;
extern strid_t 					quotestr;
extern strid_t 					inputstr;
glsi32  						top_of_loop = 0;
glsi32  						top_of_select = 0;
glsi32							top_of_while = 0;
glsi32							top_of_iterate = 0;
glsi32							top_of_update = 0;
glsi32 							top_of_do_loop = 0;
#else
extern FILE                     *file;
char  					        option_buffer[2024];
int								style_stack[100];
int								style_index = 0;
long   							top_of_loop = 0;
long   							top_of_select = 0;
long   							top_of_while = 0;
long   							top_of_iterate = 0;
long   							top_of_update = 0;
long   							top_of_do_loop = 0;

#endif

#ifdef __NDS__
extern int						bold_mode;
extern int						pre_mode;
extern int						reverse_mode;
extern int						input_mode;
extern int						subheader_mode;
extern int						note_mode;
#endif

extern char						user_id[];
extern char						prefix[];
extern char						text_buffer[];
extern char						chunk_buffer[];
extern char						*word[];

extern char						bookmark[];
extern char						file_prompt[];

/* CONTAINED IN PARSER.C */
extern int						object_list[4][MAX_WORDS];
extern int						list_size[];
extern int						max_size[];

/* CONTAINED IN ENCAPSULATE.C */
extern int						quoted[];

extern struct object_type		*object[];
extern struct integer_type		*integer_table;
extern struct integer_type		*integer[];
extern struct cinteger_type		*cinteger_table;
extern struct attribute_type	*attribute_table;
extern struct string_type		*string_table;
extern struct string_type		*cstring_table;
extern struct function_type		*function_table;
extern struct function_type		*executing_function;
extern struct command_type		*completion_list;
extern struct word_type			*grammar_table;
extern struct synonym_type		*synonym_table;
extern struct filter_type		*filter_table;

extern char						function_name[];
extern char						temp_buffer[];
extern char						error_buffer[];
extern char						proxy_buffer[];

extern char						default_function[];
extern char						override[];

extern int						noun[];
extern int						wp;
extern int						start_of_this_command;
extern int						start_of_last_command;
extern int						buffer_index;
extern int						objects;
extern int						integers;
extern int						player;
extern int						oec;
extern int						*object_element_address;
extern int						*object_backup_address;
extern int						walkthru_running;

// VALUES FROM LOADER
extern int						value_resolved;

extern FILE           			*transcript;
extern char						margin_string[];

char  					        integer_buffer[16];
char							called_name[1024];
char							scope_criterion[24];
char							*output;

void
terminate(code)
	 int             code;
{
	// FREE ANY EXTRA RAM ALLOCATED BY THE CSV PARSER
	csv_free(&parser_csv);

#ifdef GLK
	int index;
    event_t			event;

	// FLUSH THE GLK WINDOW SO THE ERROR GETS DISPLAYED IMMEDIATELY.
    glk_select_poll(&event);

	/* CLOSE THE SOUND CHANNELS */
	for (index = 0; index < 8; index++) {
		if (sound_channel[index] != NULL) {
			glk_schannel_destroy(sound_channel[index]);
		}
	}	

    /* CLOSE THE STREAM */
	if (game_stream != NULL) {
    	glk_stream_close(game_stream, NULL);
	}
	
    glk_exit();
#else
    if (file != NULL)           /* CLOSE THE GAME FILE */
        fclose(file);

    exit(code);
#endif
}

void
build_proxy()
{
	int             index;

	proxy_buffer[0] = 0;

	/* LOOP THROUGH ALL THE PARAMETERS OF THE PROXY COMMAND
	   AND BUILD THE MOVE TO BE ISSUED ON THE PLAYER'S BEHALF */
	for (index = 1; word[index] != NULL; index++) {
		strcat(proxy_buffer, text_of_word(index));
	}

	for (index = 0; index < strlen(proxy_buffer); index++) {
		if (proxy_buffer[index] == '~') {
			proxy_buffer[index] = '\"';
		} 
	}

	//printf("--- proxy buffer = \"%s\"\n", proxy_buffer);
}

void 
cb1 (void *s, size_t i, void *not_used) {
	struct string_type *resolved_cstring;

	//sprintf (temp_buffer, "Trying to set field %d to equal %s^", field_no, (char *) s);
	//write_text(temp_buffer);

	sprintf (temp_buffer, "field[%d]", field_no);

	if ((resolved_cstring = cstring_resolve(temp_buffer)) != NULL) {
		//write_text("Resolved ");
		//write_text(temp_buffer);
        //write_text("^");
		strncpy (resolved_cstring->value, s, i);
		resolved_cstring->value[i] = 0;
		//sprintf(temp_buffer, "Setting field %d to ~%s~^", field_no, (char *) s);
		//write_text(temp_buffer);
		// INCREMENT THE FIELD NUMBER SO THE NEXT ONE GETS STORED IN THE RIGHT CONSTANT
		field_no++;	
	} else {
		write_text("Can't resolve ");
		write_text(temp_buffer);
        write_text("^");
	}

}

void 
cb2 (int c, void *not_used) {
	// THE END OF THE RECORD HAS BEEN REACHED, EXPORT THE NUMBER OF FIELDS READ
	struct cinteger_type *resolved_cinteger;

	if ((resolved_cinteger = cinteger_resolve("field_count")) != NULL) {
		resolved_cinteger->value = field_no;
	}
}

int
execute(funcname)
	 char           *funcname;
{
	int             index;
	int             counter;
	int            *container;

	int             object_1,
	                object_2;

    /* THESE VARIABLE KEEP TRACK OF if AND endif COMMANDS TO DECIDE WHETHER
     *THE CURRENT LINE OF CODE SHOULD BE EXECUTED OR NOT */
    int             current_level = 0;
    int             execution_level = 0;

    /* THESE ARE USED AS FILE POINTER OFFSETS TO RETURN TO FIXED
     * POINTS IN THE GAME FILE */
#ifdef GLK
	int				result;
	glsi32			before_command = 0;
#else
	long			before_command = 0;
#endif


	strncpy (called_name, funcname, 1023);

	/* GET THE FUNCTION OBJECT BY THE FUNCTION NAME */
	resolved_function = function_resolve(called_name);

	if (resolved_function == NULL) {
		//printf("--- failed to find %s\n", called_name);
		return (FALSE);
	}

#ifdef GLK
	push_stack(glk_stream_get_position(game_stream));
#else
    push_stack(ftell(file));
#endif

	top_of_loop = 0;
	top_of_select = 0;
	top_of_while = 0;
	top_of_iterate = 0;
	top_of_update = 0;
	top_of_do_loop = 0;

	executing_function = resolved_function;
	executing_function->call_count++;

	// CREATE ALL THE PASSED ARGUMENTS AS JACL INTEGER CONSTANTS
	set_arguments(called_name);

	// SET function_name TO THE CORE NAME STORED IN THE FUNCTION OBJECT
	// LEAVING called_name TO CONTAIN THE FULL ARGUMENT LIST
	strncpy (function_name, executing_function->name, 80);
	strncpy (cstring_resolve("function_name")->value, executing_function->name, 80);

	//sprintf(temp_buffer, "--- starting to execute %s^", function_name);
	//write_text(temp_buffer);

	// JUMP TO THE POINT IN THE PROCESSED GAME FILE WHERE THIS FUNCTION STARTS 
#ifdef GLK
	glk_stream_set_position(game_stream, executing_function->position, seekmode_Start);
    before_command = executing_function->position;
	result = glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
#else
    fseek(file, executing_function->position, SEEK_SET);
    before_command = executing_function->position;
    fgets(text_buffer, 1024, file);
#endif

	if (encrypted) jacl_decrypt(text_buffer);

	while (text_buffer[0] != 125 && !interrupted) {
		encapsulate();
		if (word[0] == NULL);
		else if (!strcmp(word[0], "endwhile")) {
			current_level--;
			if (current_level < execution_level) {
				// THIS ENDWHILE COMMAND WAS BEING EXECUTED, 
				// NOT JUST COUNTED.
				if (top_of_while == FALSE) {
					sprintf(error_buffer, NO_WHILE, executing_function->name);
					log_error(error_buffer, PLUS_STDOUT);
				} else {
#ifdef GLK
					glk_stream_set_position(game_stream, top_of_while, seekmode_Start);
#else
                    fseek(file, top_of_while, SEEK_SET);
#endif
					execution_level = current_level;
				}
			}
		} else if (!strcmp(word[0], "enditerate")) {
			current_level--;
			if (current_level < execution_level) {
				// THIS ENDITERATE COMMAND WAS BEING EXECUTED, 
				// NOT JUST COUNTED.
				if (top_of_iterate == FALSE) {
					sprintf(error_buffer, NO_ITERATE, executing_function->name);
					log_error(error_buffer, PLUS_STDOUT);
				} else {
#ifdef GLK
					glk_stream_set_position(game_stream, top_of_iterate, seekmode_Start);
#else
                    fseek(file, top_of_iterate, SEEK_SET);
#endif
					execution_level = current_level;
				}
			}
		} else if (!strcmp(word[0], "endupdate")) {
			current_level--;
			if (current_level < execution_level) {
				// THIS ENDUPDATE COMMAND WAS BEING EXECUTED, 
				// NOT JUST COUNTED.
				if (top_of_update == FALSE) {
					sprintf(error_buffer, NO_UPDATE, executing_function->name);
					log_error(error_buffer, PLUS_STDOUT);
				} else {
#ifdef GLK
					glk_stream_set_position(game_stream, top_of_update, seekmode_Start);
#else
                    fseek(file, top_of_update, SEEK_SET);
#endif
					execution_level = current_level;
				}
			}
		} else if (!strcmp(word[0], "print") && current_level != execution_level) {
			// SKIP THIS BLOCK OF PLAIN TEXT UNTIL IT FINDS A 
			// LINE THAT STARTS WITH A '.' OR A '}'
#ifdef GLK
			glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
#else
			fgets(text_buffer, 1024, file);
#endif

			if (encrypted) jacl_decrypt(text_buffer);

			while (text_buffer[0] != '.') {
				if (text_buffer[0] == '}') {
					// HIT THE END OF THE FUNCTION, JUST BAIL OUT
					return (exit_function(TRUE));
				}

				// GET THE NEXT LINE
#ifdef GLK
				glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
#else
				fgets(text_buffer, 1024, file);
#endif

				if (encrypted) jacl_decrypt(text_buffer);

			}
		} else if (!strcmp(word[0], "endif")) {
			current_level--;
			if (current_level < execution_level) {
				/* THIS SHOULD NEVER HAPPEN */	
				execution_level = current_level;
			}
		} else if (!strcmp(word[0], "endall")) {
			current_level = 0;
			execution_level = 0;
		} else if (!strcmp(word[0], "else")) {
			if (current_level == execution_level) {
				execution_level--;
			} else if (current_level == execution_level + 1) {
				execution_level++;
			}
		} else if (current_level == execution_level) {
			if (!strcmp(word[0], "look")) {
				// THIS IS JUST HERE FOR BACKWARDS COMPATIBILITY
				object[HERE]->attributes &= ~1L;
				look_around();
			} else if (!strcmp(word[0], "repeat")) {
#ifdef GLK
				top_of_do_loop = glk_stream_get_position(game_stream);
#else
                top_of_do_loop = ftell(file);
#endif
			} else if (!strcmp(word[0], "until")) {
				if (word[3] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return(exit_function (TRUE));
				} else {
					if (top_of_do_loop == FALSE) {
						sprintf(error_buffer, NO_REPEAT, executing_function->name);
						log_error(error_buffer, PLUS_STDOUT);
					} else if (!condition()) {
#ifdef GLK
						glk_stream_set_position(game_stream, top_of_do_loop, seekmode_Start);
#else
                        fseek(file, top_of_do_loop, SEEK_SET);
#endif
					}
				}
			} else if (!strcmp(word[0], "untilall")) {
				if (word[3] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return(exit_function (TRUE));
				} else {
					if (top_of_do_loop == FALSE) {
						sprintf(error_buffer, NO_REPEAT, executing_function->name);
						log_error(error_buffer, PLUS_STDOUT);
					} else if (!and_condition()) {
#ifdef GLK
						glk_stream_set_position(game_stream, top_of_do_loop, seekmode_Start);
#else
						fseek(file, top_of_do_loop, SEEK_SET);
#endif

					}
				}
			} else if (!strcmp(word[0], "iterate")) {
				int i;

				// A NEW iterate LOOP MEANS STARTING BACK AT THE FIRST FIELD
				field_no = 0;

				current_level++;
				/* THIS LOOP COMES BACK TO THE START OF THE LINE CURRENTLY
				   EXECUTING, NOT THE LINE AFTER */

				top_of_iterate = before_command;

				// infile REMAINS OPEN DURING THE ITERATION, ONLY NEEDS 
				// OPENING THE FIRST TIME
				if (infile == NULL) {
					strcpy (temp_buffer, data_directory);
					strcat (temp_buffer, prefix);
					strcat (temp_buffer, "-");
					strcat (temp_buffer, text_of_word(1));
					strcat (temp_buffer, ".csv");

					infile = fopen(temp_buffer, "rb");
	
					if (word[2] != NULL && !strcmp(word[2], "skip_header")) {
						fgets(csv_buffer, 1024, infile);
					}
				}

				if (infile == NULL) {
    				sprintf(error_buffer, "Failed to open file %s: %s\n", temp_buffer, strerror(errno));
					log_error(error_buffer, LOG_ONLY);
    				infile = NULL;
  				} else {
					if (word[1] == NULL) {
						/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
						noproprun();
						return(exit_function (TRUE));
					} else {
						// IF THERE IS ANOTHER RECORD TO READ FROM THE CSV FILE THEN
						// SET THE field[] CONSTANTS AND INCREMENT THE execution_level
						fgets(csv_buffer, 1024, infile);
            			if (!feof(infile)) {
  							i = strlen(csv_buffer);
							//sprintf (temp_buffer, "Read ~%s~ with %d bytes.^", csv_buffer, i);
							//write_text(temp_buffer);
    						if (csv_parse(&parser_csv, csv_buffer, i, cb1, cb2, (void *) NULL) != i) {
      							sprintf(error_buffer, "Error parsing file: %s\n", csv_strerror(csv_error(&parser_csv)));
								log_error(error_buffer, PLUS_STDOUT);
      							fclose(infile);
      							infile = NULL;
    						} else {
								// A LINE HAS BEEN SUCCESSFULLY READ, EXECUTE THE CONTENTS OF THE LOOP
								execution_level++;
							}
						} else {
							fclose(infile);
							infile = NULL;
						}
					}
				}
			} else if (!strcmp(word[0], "update")) {
				int i;
				int try = 0; 

				// SET UP THE RECORD LOCKING STRUCTURE, THE ADDRESS OF WHICH
				// IS PASSED TO THE fcntl() SYSTEM CALL
				write_lck.l_type = F_WRLCK;	// SETTING A WRITE LOCK
				write_lck.l_whence = 0;		// OFFSET l_start FROM BEGINNING OF FILE
				write_lck.l_start = 0LL; 
				write_lck.l_len = 0LL;		// UNTIL THE END OF THE FILE ADDRESS SPACE

				read_lck.l_type = F_RDLCK;	// SETTING A READ LOCK
				read_lck.l_whence = 0;		// OFFSET l_start FROM BEGINNING OF FILE
				read_lck.l_start = 0LL; 
				read_lck.l_len = 0LL;		// UNTIL THE END OF THE FILE ADDRESS SPACE

				// A NEW iterate LOOP MEANS STARTING BACK AT THE FIRST FIELD
				field_no = 0;

				current_level++;
				// THIS LOOP COMES BACK TO THE START OF THE LINE CURRENTLY
				// EXECUTING, NOT THE LINE AFTER 

				top_of_update = before_command;

				// infile REMAINS OPEN DURING THE ITERATION, ONLY NEEDS 
				// OPENING THE FIRST TIME
				if (infile == NULL) {
					strcpy (in_name, data_directory);
					strcat (in_name, prefix);
					strcat (in_name, "-");
					strcat (in_name, text_of_word(1));
					strcat (in_name, ".csv");

					infile = fopen(in_name, "rb");
				}

				if (outfile == NULL) {
					// OPEN A TEMPORARY OUTPUT FILE TO WRITE THE MODIFICATIONS TO
					strcpy (out_name, data_directory);
					strcat (out_name, prefix);
					strcat (out_name, "-");
					strcat (out_name, text_of_word(1));
					strcat (out_name, "-");
					strcat (out_name, user_id);
					strcat (out_name, ".csv");

					outfile = fopen(out_name, "wb");
				}

				if (infile == NULL) {
    				sprintf(error_buffer, "Failed to open input CSV file ~%s~: %s\n", in_name, strerror(errno));
					log_error(error_buffer, LOG_ONLY);
					if (outfile != NULL) {
						fclose(outfile);
						outfile = NULL;
					}
					return(exit_function (TRUE));
  				} else {
					if (outfile == NULL) {
    					sprintf(error_buffer, "Failed to open output CSV file ~%s~: %s\n", out_name, strerror(errno));
						log_error(error_buffer, LOG_ONLY);
						if (infile != NULL) {
	    					fclose(infile);
							infile = NULL;
						}
						return(exit_function (TRUE));
					} else {
						write_fd = fileno(outfile);
						// ATTEMPT LOCKING OUTPUT FILE MAX_TRY TIMES BEFORE GIVING UP. 
  						while (fcntl(write_fd, F_SETLK, &write_lck)<0) {  
     						if (errno == EAGAIN || errno == EACCES) { 
         						// THERE MIGHT BE OTHER ERROR CASES IN WHICH 
          						// USERS MIGHT TRY AGAIN 
          						if (++try < MAX_TRY) { 
               						jacl_sleep(1000); 
               						continue; 
          						} 
         						sprintf(error_buffer, "File busy unable to get lock on output file.\n"); 
								log_error(error_buffer, PLUS_STDOUT);
								return(exit_function (TRUE));
      						} 
  						} 

						try = 0;

						read_fd = fileno(infile);
						// ATTEMPT LOCKING OUTPUT FILE MAX_TRY TIMES BEFORE GIVING UP. 
  						while (fcntl(read_fd, F_SETLK, &read_lck)<0) {  
     						if (errno == EAGAIN || errno == EACCES) { 
         						// THERE MIGHT BE OTHER ERROR CASES IN WHICH 
          						// USERS MIGHT TRY AGAIN 
          						if (++try < MAX_TRY) { 
               						jacl_sleep(1000); 
               						continue; 
          						} 
         						sprintf(error_buffer, "File busy unable to get lock on input file.\n"); 
								log_error(error_buffer, PLUS_STDOUT);
								return(exit_function (TRUE));
      						} 
  						} 

						if (word[1] == NULL) {
							/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
							noproprun();
							return(exit_function (TRUE));
						} else {
							// IF THERE IS ANOTHER RECORD TO READ FROM THE CSV FILE THEN
							// SET THE field[] CONSTANTS AND INCREMENT THE execution_level
							fgets(csv_buffer, 1024, infile);
	            			if (!feof(infile)) {
	  							i = strlen(csv_buffer);
	    						if (csv_parse(&parser_csv, csv_buffer, i, cb1, cb2, (int *) &field_no) != i) {
	      							sprintf(error_buffer, "Error parsing file: %s\n", csv_strerror(csv_error(&parser_csv)));
									log_error(error_buffer, PLUS_STDOUT);
									read_lck.l_type = F_UNLCK;	// SETTING A READ LOCK
  									fcntl(read_fd, F_SETLK, &read_lck);
	      							fclose(infile);
	      							infile = NULL;
	    						} else {
									// A LINE HAS BEEN SUCCESSFULLY READ, EXECUTE THE CONTENTS OF THE LOOP
									execution_level++;
								}
							} else {
								write_lck.l_type = F_UNLCK;	// REMOVE THE WRITE LOCK
  								fcntl(write_fd, F_SETLK, &write_lck);
								fclose(outfile);

								read_lck.l_type = F_UNLCK;	// REMOVE THE READ LOCK
  								fcntl(read_fd, F_SETLK, &read_lck);
								fclose(infile);

								rename (out_name, in_name);

								outfile = NULL;
								infile = NULL;
							}
						}
					}
				}
			} else if (!strcmp(word[0], "while")) {
				current_level++;
				/* THIS LOOP COMES BACK TO THE START OF THE LINE CURRENTLY
				   EXECUTING, NOT THE LINE AFTER */
				top_of_while = before_command;
				if (word[3] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return(exit_function (TRUE));
				} else if (condition()) {
					execution_level++;
				}
			} else if (!strcmp(word[0], "whileall")) {
				current_level++;
				/* THIS LOOP COMES BACK TO THE START OF THE LINE CURRENTLY
				   EXECUTING, NOT THE LINE AFTER */
				top_of_while = before_command;
				if (word[3] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return(exit_function (TRUE));
				} else if (and_condition()) {
					execution_level++;
				}
			} else if (!strcmp(word[0], "loop")) {
				/* THE LOOP COMMAND LOOPS ONCE FOR EACH DEFINED 
				 * OBJECT (FOREACH) */
#ifdef GLK
				top_of_loop = glk_stream_get_position(game_stream);
#else
                top_of_loop = ftell(file);
#endif
				if (word[1] == NULL) {
					// IF NONE IS SUPPLIED DEFAULT TO noun3
					loop_integer = &noun[2];
				} else {
					// STORE THE CONTAINER TO PUT THE CURRENT OBJECT IN
					loop_integer = container_resolve(word[1]);

					// IF THE SUPPLIED CONTAINER CAN'T BE RESOLVED
					// DEFAULT TO noun3
					if (loop_integer == NULL)
						loop_integer = &noun[2];
				}

				// SET THE VALUE OF THE LOOP INDEX TO POINT TO THE FIRST OBJECT
				*loop_integer = 1;

			} else if (!strcmp(word[0], "endloop")) {
				if (top_of_loop == FALSE) {
					sprintf(error_buffer, NO_LOOP, executing_function->name);
					log_error(error_buffer, PLUS_STDOUT);
				} else {
					*loop_integer += 1;
					if (*loop_integer > objects) {
						top_of_loop = FALSE;
						*loop_integer = 0;
					} else {
#ifdef GLK
						glk_stream_set_position(game_stream, top_of_loop, seekmode_Start);
#else
                        fseek(file, top_of_loop, SEEK_SET);
#endif
					}
				}
			} else if (!strcmp(word[0], "select")) {
				/* THE SELECT COMMAND LOOPS ONCE FOR EACH DEFINED 
				 * OBJECT THAT MATCHES THE SUPPLIED CRITERION */
#ifdef GLK
				top_of_select = glk_stream_get_position(game_stream);
#else
                top_of_select = ftell(file);
#endif
				if (word[1] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return(exit_function (TRUE));
				} else if (word[2] == NULL) {
					// IF NONE IS SUPPLIED DEFAULT TO noun3
					select_integer = &noun[2];
				} else {
					// STORE THE CONTAINER TO PUT THE CURRENT OBJECT IN
					select_integer = container_resolve(word[2]);

					// IF THE SUPPLIED CONTAINER CAN'T BE RESOLVED
					// DEFAULT TO noun3
					if (select_integer == NULL) {
						select_integer = &noun[2];
					}
				}
				
				// SET THE VALUE OF THE SELECT INDEX TO ONE BEFORE THE 
                // FIRST OBJECT. THE NEXT FUNCTION AUTOMATICALLY INCREMENTS
				// THE INDEX BY ONE AT THE START OF THE WHILE LOOP.
				*select_integer = 0;

				if (word[1][0] == '!') {
					criterion_negate = TRUE;
					strcpy(argument_buffer, &word[1][1]);
				} else {
					criterion_negate = FALSE;
					strcpy(argument_buffer, word[1]);
				}

				// DETERMINE THE CRITERION FOR SELETION
			    if (!strcmp(argument_buffer, "*held")
    						|| !strcmp(argument_buffer, "*here")
    						|| !strcmp(argument_buffer, "*anywhere")
    						|| !strcmp(argument_buffer, "*present")) {
					criterion_type = CRI_SCOPE;
					strncpy(scope_criterion, argument_buffer, 20);
				} else if ((criterion_value = attribute_resolve(argument_buffer))) {
					criterion_type = CRI_ATTRIBUTE;
				} else if ((criterion_value = user_attribute_resolve(argument_buffer))) {
					criterion_type = CRI_USER_ATTRIBUTE;
				} else {
					// USE VALUE OF AS A CATCH ALL IF IT IS NOT AN ATTRIBUTE OR SCOPE
					criterion_value = value_of(argument_buffer);

					if (value_resolved) {
						criterion_type = CRI_PARENT;
					} else {
						// CAN'T RESOLVE CRITERION
						criterion_type = CRI_NONE;	
					}
				}

				if (criterion_type != CRI_NONE) {
					if (select_next() == FALSE) {
						*select_integer = 0;
						top_of_select = 0;
					}
				} else {
					*select_integer = 0;
				}

				if (*select_integer == 0) {
					// THERE ARE NO MATCHING OBJECTS SO JUMP TO THE endselect
#ifdef GLK
					glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
#else
					fgets(text_buffer, 1024, file);
#endif

					if (encrypted) jacl_decrypt(text_buffer);

					while (text_buffer[0] != '}') {
						encapsulate();
						if (word[0] != NULL && !strcmp(word[0], "endselect")) {
							break;
						}
#ifdef GLK
						glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
#else
						fgets(text_buffer, 1024, file);
#endif
					}
				}
			} else if (!strcmp(word[0], "endselect")) {
				if (top_of_select == FALSE) {
					sprintf(error_buffer, NO_LOOP, executing_function->name);
					log_error(error_buffer, PLUS_STDOUT);
				} else {
					if (select_next(select_integer, criterion_type, criterion_value, scope_criterion)) {
#ifdef GLK
						glk_stream_set_position(game_stream, top_of_select, seekmode_Start);
#else
                        fseek(file, top_of_select, SEEK_SET);
#endif
					} else {
						*select_integer = 0;
						top_of_select = 0;
					}
				}
			} else if (!strcmp(word[0], "break")) {
				current_level++;
				execution_level--;
#ifdef GLK
			} else if (!strcmp(word[0], "cursor")) {
				if (word[2] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun(0);
					return(exit_function (TRUE));
				} else {
					if (current_window == statuswin) {
						glk_window_move_cursor(statuswin, value_of(word[1], TRUE), value_of(word[2], TRUE));
					} else {
						log_error(BAD_CURSOR, PLUS_STDOUT);
					}
				}
			} else if (!strcmp(word[0], "stop")) {
				int channel;

				if (SOUND_SUPPORTED->value) {
					/* SET THE CHANNEL TO STOP, IF SUPPLIED */
					if (word[1] == NULL) {
						channel = 0;
					} else {
						channel = value_of(word[1], TRUE);

						/* SANITY CHECK THE CHANNEL SELECTED */
						if (channel < 0 || channel > 7) {
							channel = 0;
						}
					}
					glk_schannel_stop(sound_channel[channel]);
				}
			} else if (!strcmp(word[0], "volume")) {
				int channel, volume;

				if (SOUND_SUPPORTED->value) {
					/* SET THE CHANNEL TO STOP, IF SUPPLIED */
					if (word[2] == NULL) {
						channel = 0;
					} else {
						channel = value_of(word[2], TRUE);

						/* SANITY CHECK THE CHANNEL SELECTED */
						if (channel < 0 || channel > 7) {
							channel = 0;
						}
					}

					if (word[1] == NULL) {
						/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
						noproprun();
						return(exit_function (TRUE));
					} else {
						volume = value_of(word[1], TRUE);

						/* SANITY CHECK THE CHANNEL SELECTED */
						if (volume < 0) {
							volume = 0;
						}

						if (volume > 100) {
							volume = 100;
						}

						/* STORE A COPY OF THE CURRENT VOLUME FOR ACCESS
						 * FROM JACL CODE */
						sprintf(temp_buffer, "volume[%d]", channel);
						cinteger_resolve(temp_buffer)->value = volume;

						/* NOW SCALE THE 0-100 VOLUME TO THE 0-65536 EXPECTED
						 * BY Glk */
						volume = volume * 655;

						/* SET THE VOLUME */
						glk_schannel_set_volume(sound_channel[channel], (glui32) volume);
					}
				}
			} else if (!strcmp(word[0], "timer")) {
				if (TIMER_SUPPORTED->value && TIMER_ENABLED->value) {
					if (word[1] == NULL) {
						/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
						noproprun();
						return(exit_function (TRUE));
					} else {
						index = value_of(word[1], TRUE);
						/* DON'T ALLOW NEGATIVE VALUES, BUT NO UPPER LIMIT */
						if (index < 0) index = 0;

						/* SET THE GLK TIMER */
						glk_request_timer_events((glui32) index);

						/* EXPOSE THE CURRENT VALUE THROUGH A JACL CONSTANT
						   SO THAT GAME CODE CAN READ THE IT */
						cinteger_resolve("timer")->value = index;
					}
				}
			} else if (!strcmp(word[0], "sound")) {
				int channel;
				glui32 repeats;

				if (SOUND_SUPPORTED->value && SOUND_ENABLED->value) {
					/* SET THE CHANNEL TO USE, IF SUPPLIED */
					if (word[2] == NULL) {
						channel = 0;
					} else {
						channel = value_of(word[2], TRUE);

						/* SANITY CHECK THE CHANNEL SELECTED */
						if (channel < 0 || channel > 7) {
							channel = 0;
						}
					}

					/* SET THE NUMBER OF REPEATS, IF SUPPLIED */
					if (word[3] == NULL) {
						repeats = 1;
					} else {
						repeats = value_of(word[3], TRUE);
					}

					if (word[1] == NULL) {
						/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
						noproprun();
						return(exit_function (TRUE));
					} else {
						if (glk_schannel_play_ext(sound_channel[channel], (glui32) value_of(word[1], TRUE), repeats, channel + 1) == 0) {
							/* THE CHANNEL NUMBER IS PASSED SO THAT THE SOUND
							 * NOTIFICATION EVENT CAN USE THE INFORMATION
							 * IT HAS 1 ADDED TO IT SO THAT IT IS A NON-ZERO
							 * NUMBER AND THE EVENT IS ACTIVATED */
							sprintf(error_buffer, "Unable to play sound: %ld", value_of(word[1], FALSE));
							log_error(error_buffer, PLUS_STDERR);
						}
					}
				}
			} else if (!strcmp(word[0], "image")) {
				if (GRAPHICS_SUPPORTED->value && GRAPHICS_ENABLED->value) {
					if (word[1] == NULL) {
						/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
						noproprun();
						return(exit_function (TRUE));
					} else {
						if (glk_image_draw(mainwin, (glui32) value_of(word[1], TRUE), imagealign_InlineDown, 0) == 0) {
							sprintf(error_buffer, "Unable to draw image: %ld", value_of(word[1], FALSE));
							log_error(error_buffer, PLUS_STDERR);
						}
					}
				}
			} else if (!strcmp(word[0], "askstring") || !strcmp(word[0], "getstring")) {
				if (word[1] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun(0);
					return (exit_function(TRUE));
				} else {
					/* GET A POINTER TO THE STRING BEING MODIFIED */
					if ((resolved_string = string_resolve(word[1])) == NULL) {
						unkstrrun(word[1]);
						return (exit_function(TRUE));
					}

					// PROMPT THE USER TO INPUT A STRING AND STORE IT IN THE
					// RESOLVED VARIABLE
					get_string(resolved_string->value);
				}
				
			} else if (!strcmp(word[0], "asknumber") || !strcmp(word[0], "getnumber")) {
				int low, high;

				int insist = FALSE;

				/* THE ONLY DIFFERENCE WITH THE getnumber COMMAND IS THAT
				 * IT INSISTS THE PLAYER GIVES A LEGAL RESPONSE */
				if (!strcmp(word[0], "getnumber")) {
					insist = TRUE;
				}

				if (word[3] != NULL) {
					ask_integer = container_resolve(word[1]);
					if (ask_integer == NULL) {
						unkvarrun(word[1]);
						return(exit_function (TRUE));
					}

					low = value_of(word[2], TRUE);
					high = value_of(word[3], TRUE);
				
					if (high == -1 || low == -1) {
						return(exit_function (TRUE));
					}

					*ask_integer = get_number(insist, low, high);
				} else {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return(exit_function (TRUE));
				}
			} else if (!strcmp(word[0], "getyesorno")) {
				if (word[1] != NULL) {
					ask_integer = container_resolve(word[1]);
					if (ask_integer == NULL) {
						unkvarrun(word[1]);
						return(exit_function (TRUE));
					}

					*ask_integer = get_yes_or_no();
				} else {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return(exit_function (TRUE));
				}
			} else if (!strcmp(word[0], "clear")) {
				if (!walkthru_running) {
					glk_window_clear(current_window);
				}
			} else if (!strcmp(word[0], "terminate")) {
				terminate(0);
			} else if (!strcmp(word[0], "more")) {
				if (word[1] == NULL) {
					more ("[MORE]");
				} else {
					more (word[1]);
				}
			} else if (!strcmp(word[0], "style")) {
				/* THIS COMMAND IS USED TO OUTPUT ANSI CODES OR SET GLK 
				 * STREAM STYLES */
				if (word[1] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return(exit_function (TRUE));
				} else {
					if (!strcmp(word[1], "bold") 
								|| !strcmp(word[1], "emphasised")) {
						glk_set_style(style_Emphasized);
					} else if (!strcmp(word[1], "note")) {
						glk_set_style(style_Note);
					} else if (!strcmp(word[1], "input")) {
						glk_set_style(style_Input);
					} else if (!strcmp(word[1], "header")) {
						glk_set_style(style_Header);
					} else if (!strcmp(word[1], "subheader")) {
						glk_set_style(style_Subheader);
					} else if (!strcmp(word[1], "reverse")
								|| !strcmp(word[1], "inverse")) {
						if (current_window == mainwin) {
							glk_set_style(style_User2);
						} else {
							glk_set_style(style_User1);
						}
					} else if (!strcmp(word[1], "pre") 
								|| !strcmp(word[1], "preformatted")) {
						glk_set_style(style_Preformatted);
					} else if (!strcmp(word[1], "normal")) {
						glk_set_style(style_Normal);
					} 
				}
			} else if (!strcmp(word[0], "flush")) {
			} else if (!strcmp(word[0], "hyperlink")) {
				/* OUTPUT LINK TEXT AS PLAIN TEXT UNDER Glk */
				if (word[2] == NULL) {
					noproprun();
					pop_stack();
					return (TRUE);
				} else {
					write_text(text_of_word(1));
				}
#else
#ifdef __NDS__
			} else if (!strcmp(word[0], "flush")) {
				jflush();
			} else if (!strcmp(word[0], "cursor")) {
				if (word[2] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun(0);
					return(exit_function (TRUE));
				} else {
					printf("\x1b[%d;%dH", (int) value_of(word[1], TRUE), (int) value_of(word[2], TRUE));
				}
			} else if (!strcmp(word[0], "stop")) {
			} else if (!strcmp(word[0], "volume")) {
			} else if (!strcmp(word[0], "timer")) {
			} else if (!strcmp(word[0], "sound")) {
			} else if (!strcmp(word[0], "image")) {
			} else if (!strcmp(word[0], "askstring") || !strcmp(word[0], "getstring")) {
				if (word[1] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun(0);
					return (exit_function(TRUE));
				} else {
					/* GET A POINTER TO THE STRING BEING MODIFIED */
					if ((resolved_string = string_resolve(word[1])) == NULL) {
						unkstrrun(word[1]);
						return (exit_function(TRUE));
					}

					// PROMPT THE USER TO INPUT A STRING AND STORE IT IN THE
					// RESOLVED VARIABLE
					get_string(resolved_string->value);
				}
				
			} else if (!strcmp(word[0], "asknumber") || !strcmp(word[0], "getnumber")) {
				int low, high;

				int insist = FALSE;

				/* THE ONLY DIFFERENCE WITH THE getnumber COMMAND IS THAT
				 * IT INSISTS THE PLAYER GIVES A LEGAL RESPONSE */
				if (!strcmp(word[0], "getnumber")) {
					insist = TRUE;
				}

				if (word[3] != NULL) {
					ask_integer = container_resolve(word[1]);
					if (ask_integer == NULL) {
						unkvarrun(word[1]);
						return(exit_function (TRUE));
					}

					low = value_of(word[2], TRUE);
					high = value_of(word[3], TRUE);
				
					if (high == -1 || low == -1) {
						return(exit_function (TRUE));
					}

					*ask_integer = get_number(insist, low, high);
				} else {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return(exit_function (TRUE));
				}
			} else if (!strcmp(word[0], "getyesorno")) {
				if (word[1] != NULL) {
					ask_integer = container_resolve(word[1]);
					if (ask_integer == NULL) {
						unkvarrun(word[1]);
						return(exit_function (TRUE));
					}

					*ask_integer = get_yes_or_no();
				} else {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return(exit_function (TRUE));
				}
			} else if (!strcmp(word[0], "clear")) {
				clrscrn();
			} else if (!strcmp(word[0], "terminate")) {
				terminate(0);
			} else if (!strcmp(word[0], "more")) {
				if (word[1] == NULL) {
					more ("[MORE]");
				} else {
					more (word[1]);
				}
			} else if (!strcmp(word[0], "style")) {
				/* THIS COMMAND IS USED TO OUTPUT ANSI CODES OR SET GLK 
				 * STREAM STYLES */
				if (word[1] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return(exit_function (TRUE));
				} else {
					if (!strcmp(word[1], "bold") 
								|| !strcmp(word[1], "emphasised")) {
						printf("\x1b[37;1m"); 	// SET TO BRIGHT WHITE
						bold_mode = TRUE;
					} else if (!strcmp(word[1], "note")) {
						printf("\x1b[34;1m"); 	// SET TO BRIGHT BLUE
						note_mode = TRUE;
					} else if (!strcmp(word[1], "input")) {
						printf("\x1b[32;0m"); 	// SET TO DIM GREEN
						input_mode = TRUE;
					} else if (!strcmp(word[1], "header")) {
						printf("\x1b[37;0m"); 	// SET TO DIM WHITE
					} else if (!strcmp(word[1], "subheader")) {
						printf("\x1b[33;1m"); 	// SET TO BRIGHT YELLOW
						subheader_mode = TRUE;
					} else if (!strcmp(word[1], "reverse")
								|| !strcmp(word[1], "inverse")) {
						printf("\x1b[7m");	// SET TO DIM WHITE
						reverse_mode = TRUE;
					} else if (!strcmp(word[1], "pre") 
								|| !strcmp(word[1], "preformatted")) {
						printf("\x1b[37;0m"); 	// SET TO DIM WHITE
						pre_mode = TRUE;
					} else if (!strcmp(word[1], "normal")) {
						printf("\x1b[37;0m"); 	// SET TO DIM WHITE
						bold_mode = FALSE;
						pre_mode = FALSE;
						reverse_mode = FALSE;
						input_mode = FALSE;
						subheader_mode = FALSE;
						note_mode = FALSE;
					} 
				}
			} else if (!strcmp(word[0], "hyperlink")) {
				/* OUTPUT LINK TEXT AS PLAIN TEXT UNDER Glk */
				if (word[2] == NULL) {
					noproprun();
					pop_stack();
					return (TRUE);
				} else {
					write_text(text_of_word(1));
				}
#else
/* HERE STARTS THE CGIJACL-ONLY FUNCTIONS */
			} else if (!strcmp(word[0], "option")) {
				/* USED TO ADD AN OPTION TO AN HTML LIST */
				if (word[1] == NULL) {
					noproprun();
					pop_stack();
					return (TRUE);
				} else {
					index = value_of(word[1]);
					if (word[2] != NULL) {
						sprintf(option_buffer, "<option value=\"%d\">",
								index);
					} else {
						object_names(index, temp_buffer);
						sprintf(option_buffer, "<option value=\"%s\">", temp_buffer);
					}

					write_text(option_buffer);
					list_output(index, TRUE);
					write_text(temp_buffer);

				}
			} else if (!strcmp(word[0], "getenv")) {
				struct string_type *resolved_setstring = NULL;

				if (word[2] == NULL) {
					noproprun();
					pop_stack();
					return (TRUE);
				} else {
					// GET A POINTER TO THE STRING BEING MODIFIED
					if ((resolved_setstring = string_resolve(word[1])) == NULL) {
						unkstrrun(word[1]);
						return (exit_function(TRUE));
					}

					// COPY THE VARIABLE OF THE CGI VARIABLE INTO THE SPECIFIED STRING VARIABLE
					if (getenv(text_of_word(2)) != NULL) {
						strncpy (resolved_setstring->value, getenv(text_of_word(2)), 255);
					} else {
						strncpy (resolved_setstring->value, "", 255);
					}
				}
			} else if (!strcmp(word[0], "button")) {
				/* USED TO CREATE AN HTML BUTTON */
				if (word[1] == NULL) {
					noproprun();
					pop_stack();
					return (TRUE);
				} if (word[2] != NULL) {
					sprintf (option_buffer, "<input class=~button~ type=~image~ src=~%s~ name=~verb~ value=~", text_of_word(2));
					strcat (option_buffer, text_of_word(1));
					strcat (option_buffer, "~>");
					write_text(option_buffer);
				} else {
					sprintf (option_buffer, "<input class=~button~ type=~submit~ style=~width: 90px; margin: 5px;~ name=~verb~ value=~%s~>", text_of_word(1));
					write_text(option_buffer);
				}
			} else if (!strcmp(word[0], "hidden")) {
				sprintf (temp_buffer, "<INPUT TYPE=\"hidden\" NAME=\"user_id\" VALUE=\"%s\">", user_id);
				write_text(temp_buffer);
			} else if (!strcmp(word[0], "control")) {
				/* USED TO CREATE A HYPERLINK THAT IS AN IMAGE */
				if (word[2] == NULL) {
					noproprun();
					pop_stack();
					return (TRUE);
				} else {
					sprintf(option_buffer, "<a href=\"?command=%s&amp;user_id=%s\"><img border=0 SRC=\"", text_of_word(2), user_id);
					strcat(option_buffer, text_of_word(1));
					strcat(option_buffer, "\"></a>");
					write_text(option_buffer);
				}
			} else if (!strcmp(word[0], "hyperlink") || !strcmp(word[0], "hyperlinkNE")) {
				string_buffer[0] = 0;

				/* USED TO CREATE A HYPERLINK WITH SESSION INFORMATION INCLUDED */
				if (word[2] == NULL) {
					noproprun();
					pop_stack();
					return (TRUE);
				} else {
					char * encoded;

					if (!strcmp(word[0], "hyperlink")) {
						encoded = url_encode(text_of_word(2));
					} else {
						encoded = text_of_word(2);
					}

					if (word[3] == NULL) {
						sprintf (string_buffer, "<a href=\"?command=%s&amp;user_id=%s\">", encoded, user_id);
						strcat (string_buffer, text_of_word(1));
						strcat (string_buffer, "</a>");
					} else {
						sprintf (string_buffer, "<a class=\"%s\" href=\"?command=", text_of_word(3));
						strcat (string_buffer, encoded);
						sprintf (option_buffer, "&amp;user_id=%s\">%s</a>", user_id, text_of_word(1));
						strcat (string_buffer, option_buffer);
					}

					if (!strcmp(word[0], "hyperlink")) {
                    	free (encoded); 
					}

					write_text(string_buffer);
				}
			} else if (!strcmp(word[0], "prompt")) {
				/* USED TO OUTPUT A HTML INPUT CONTROL THAT CONTAINS SESSION INFORMATION */
				if (word[1] != NULL) {
					sprintf(temp_buffer, "<input id=\"JACLCommandPrompt\" type=text name=~command~ onKeyPress=~%s~>\n", word[1]);
					write_text(temp_buffer);
				} else {
					sprintf(temp_buffer, "<input id=\"JACLCommandPrompt\" type=text name=~command~>\n");
					write_text(temp_buffer);
				}
				sprintf(temp_buffer, "<input type=hidden name=\"user_id\" value=\"%s\">", user_id);
				write_text(temp_buffer);
			} else if (!strcmp(word[0], "style")) {
				/* THIS COMMAND IS USED TO OUTPUT ANSI CODES OR SET GLK 
				 * STREAM STYLES */
				if (word[1] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return(exit_function (TRUE));
				} else {
					if (!strcmp(word[1], "bold") 
								|| !strcmp(word[1], "emphasised")) {
						write_text("<b>");
						style_stack[style_index++] = BOLD;
					} else if (!strcmp(word[1], "note")) {
						write_text("<i>");
						style_stack[style_index++] = NOTE;
					} else if (!strcmp(word[1], "input")) {
						write_text("<i>");
						style_stack[style_index++] = INPUT;
					} else if (!strcmp(word[1], "header")) {
						write_text("<h1>");
						style_stack[style_index++] = HEADER;
					} else if (!strcmp(word[1], "subheader")) {
						write_text("<h2>");
						style_stack[style_index++] = SUBHEADER;
					} else if (!strcmp(word[1], "reverse")
								|| !strcmp(word[1], "inverse")) {
						write_text("<b>");
						style_stack[style_index++] = REVERSE;
					} else if (!strcmp(word[1], "pre") 
								|| !strcmp(word[1], "preformatted")) {
						write_text("<pre>");
						style_stack[style_index++] = PRE;
					} else if (!strcmp(word[1], "normal")) {
						style_index--;
						for(; style_index > -1; style_index--) {
							switch(style_stack[style_index]) {
								case BOLD:
									write_text("</b>");
									break;
								case NOTE:
									write_text("</i>");
									break;
								case INPUT:
									write_text("</i>");
									break;
								case HEADER:
									write_text("</h1>");
									break;
								case SUBHEADER:
									write_text("</h2>");
									break;
								case REVERSE:
									write_text("</b>");
									break;
								case PRE:
									write_text("</pre>");
									break;
							}
						} 
						style_index = 0;
					} 
				}
			/* THESE FINAL COMMANDS HAVE NO EFFECT UNDER CGIJACL 
			   AND THERE IS NO HARM IN IGNORING THEM */
			} else if (!strcmp(word[0], "flush")) {
			} else if (!strcmp(word[0], "image")) {
				if (word[1] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun(0);
					return (exit_function(TRUE));
				} else {
					if (word[2] == NULL) {
						sprintf(option_buffer, "<img src=~%s~>", text_of_word(1));
					} else {
						sprintf(option_buffer, "<img class=~%s~ src=~%s~>", text_of_word(2), text_of_word(1));
					}

					write_text(option_buffer);
				}
			} else if (!strcmp(word[0], "sound")) {
				if (word[2] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun(0);
					return (exit_function(TRUE));
				} else {
					write_text("<audio autoplay=~autoplay~>");
					if (word[3] == NULL) {
						sprintf(option_buffer, "<source src=~%s~ type=~%s~>", text_of_word(1), text_of_word(2));
						write_text(option_buffer);
					}
					write_text("</audio>");
				}
			} else if (!strcmp(word[0], "cursor")) {
			} else if (!strcmp(word[0], "timer")) {
			} else if (!strcmp(word[0], "volume")) {
			} else if (!strcmp(word[0], "askstring") || !strcmp(word[0], "getstring")) {
			} else if (!strcmp(word[0], "asknumber") || !strcmp(word[0], "getnumber")) {
			} else if (!strcmp(word[0], "getyesorno")) {
			} else if (!strcmp(word[0], "clear")) {
			} else if (!strcmp(word[0], "more")) {
			} else if (!strcmp(word[0], "terminate")) {
#endif
#endif
			} else if (!strcmp(word[0], "proxy")) {
				/* THE PROXY COMMAND ISSUES A MOVE ON THE PLAYER'S BEHALF
				 * ALL STATE MUST BE SAVED SO THE CURRENT MOVE CAN CONTINUE
				 * ONCE THE PROXIED MOVE IS COMPLETE */
#ifdef GLK
				push_stack(glk_stream_get_position(game_stream));
#else
                push_stack(ftell(file));
#endif
				push_proxy();

				build_proxy();

				// TEXT BUFFER IS THE NORMAL ARRAY FOR HOLDING THE PLAYERS
				// MOVE FOR PROCESSING
				strncpy(text_buffer, proxy_buffer, 1024);

				command_encapsulate();

				jacl_truncate();

				preparse();
				
				pop_proxy();

				pop_stack();
			} else if (!strcmp(word[0], "override")) {
				/* TELLS THE INTERPRETER TO LOOK FOR AN _override FUNCTION 
				 * TO EXECUTE IN PLACE OF ANY CODE THAT FOLLOWS THIS LINE. 
				 * THIS COMMAND IS USED EXCLUSIVELY IN GLOBAL FUNCTIONS 
				 * ASSOCIATED WITH GRAMMAR LINES */
				if (execute(override) == TRUE) {
					return(exit_function (TRUE));
				} else {
					if (execute(default_function) == TRUE) {
						return(exit_function (TRUE));
					}
				}
			} else if (!strcmp(word[0], "execute") || !strcmp(word[0], "call")) {
				/* CALLS ANOTHER JACL FUNCTION */
				if (word[1] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return(exit_function (TRUE));
				} else {
					/* RESOLVE ALL THE TEXT AND STORE IT IN A TEMPORARY BUFFER*/
					string_buffer[0] = 0;

					for (counter = 1; word[counter] != NULL && counter < MAX_WORDS; counter++) {
						strcat(string_buffer, arg_text_of_word(counter));
					}

					if (function_resolve(string_buffer) == NULL && !strcmp(word[0], "execute")) {
						char * argstart;

						/* REMOVE ANY PARAMETERS FROM FUNCTION NAME
						   BEFORE DISPLAYING ERROR MESSAGE */
						argstart = strchr(string_buffer, '<');
						if (argstart != NULL)
							*argstart = 0;
						
						sprintf(error_buffer, UNDEFINED_FUNCTION, executing_function->name, string_buffer);
						log_error(error_buffer, PLUS_STDOUT);
					} else {
						execute(string_buffer);
					}
				}
			} else if (!strcmp(word[0], "points")) {
				/* INCREASE THE PLAYER'S SCORE AND POTENTIALLY INFORM THEM OF THE INCREASE */
				if (word[1] != NULL) {
					SCORE->value += value_of(word[1], TRUE);
					if (NOTIFY->value) {
#ifdef GLK
						glk_set_style(style_Note);
#else
#ifdef __NDS__
						printf("\x1b[34;1m"); 	// SET TO BRIGHT BLUE
						note_mode = TRUE;
#else
						write_text("<b><i>");
#endif
#endif
						write_text(cstring_resolve("SCORE_UP")->value);
						sprintf(temp_buffer, "%ld", value_of(word[1], TRUE));
						write_text(temp_buffer);
						if (value_of(word[1], TRUE) == 1) {
							write_text(cstring_resolve("POINT")->value);
						} else {
							write_text(cstring_resolve("POINTS")->value);
						}
#ifdef GLK
						glk_set_style(style_Normal);
#else
#ifdef __NDS__
						printf("\x1b[37;0m"); 	// SET TO DIM WHITE
						note_mode = FALSE;
#else
						write_text("</i></b>");
#endif
#endif
					}
				} else {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return(exit_function (TRUE));
				}
			} else if (!strcmp(word[0], "print")) {
				int non_space = FALSE;

				// DISPLAYS A BLOCK OF PLAIN TEXT UNTIL IT FINDS A 
				// LINE THAT STARTS WITH A '.' OR A '}'
#ifdef GLK
				glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
#else
                fgets(text_buffer, 1024, file);
#endif

				if (encrypted) jacl_decrypt(text_buffer);

				while (text_buffer[0] != '.' && text_buffer[0] != '}') {
					index = 0;
					non_space = FALSE;

					/* REMOVE ANY NEWLINE CHARACTERS */
					while (text_buffer[index] != 0) {
						if (text_buffer[index] == '|' && non_space == FALSE) {
							/* THE BAR CHARACTER IS CHANGED TO A SPACE TO 
							 * ALLOW INDENTING OF NEW PARAGRAPHS ETC */
							text_buffer[index] = ' ';
						} else if (text_buffer[index] == '\r') {
							text_buffer[index] = 0;
							break;
						} else if (text_buffer[index] == '\n') {
							text_buffer[index] = 0;
							break;
						} else if (text_buffer[index] != ' ' && text_buffer[index] != '\t') {
							non_space = TRUE;
						}

						index++;
					}

					if (text_buffer[0] != 0) {
						// CHECK IF THERE IS THE NEED TO ADD AN 
						// IMPLICIT SPACE
						index = strlen(text_buffer);
	
						if (text_buffer[index - 1] == '\\') {	
							// A BACKSLASH IS USED TO INDICATE AN IMPLICIT 
							// SPACE SHOULD NOT BE PRINTED
							text_buffer[index - 1] = 0;
						} else if (text_buffer[index - 1] != '^') {
							// ADD AN IMPLICIT SPACE IF THE PREVIOUS LINE
							// DIDN'T END WITH A CARRIAGE RETURN
							strcat(text_buffer, " ");
						}
	
						// OUTPUT THE LINE READ AS PLAIN TEXT
						write_text(text_buffer);
					}

					// GET THE NEXT LINE
#ifdef GLK
					glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
#else
                    fgets(text_buffer, 1024, file);
#endif

					if (encrypted) jacl_decrypt(text_buffer);
				}
			} else if (!strcmp(word[0], "mesg")) {
				for (counter = 1; word[counter] != NULL && counter < MAX_WORDS; counter++) {
					fprintf(stderr, "%s", text_of_word(counter));
				}
			} else if (!strcmp(word[0], "error")) {
				write_text("ERROR: In function ~");
				write_text(executing_function->name);
				write_text("~, ");
				for (counter = 1; word[counter] != NULL && counter < MAX_WORDS; counter++) {
					write_text(text_of_word(counter));
				}
			} else if (!strcmp(word[0], "debug") && DEBUG->value) {
				write_text("DEBUG: ");
				for (counter = 1; word[counter] != NULL && counter < MAX_WORDS; counter++) {
					write_text(text_of_word(counter));
				}
			} else if (!strcmp(word[0], "write")) {
				for (counter = 1; word[counter] != NULL && counter < MAX_WORDS; counter++) {
					output = text_of_word(counter);			
					if (*output != 0) {
						// IF THE OUTPUT ISN'T AN EMPTY STRING, DISPLAY IT
						write_text(output);
					}
				}
			} else if (!strcmp(word[0], "length")) {
				if (word[2] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun(0);
					return(exit_function (TRUE));
				} else {
					if ((container = container_resolve(word[1])) == NULL) {
						unkvarrun(word[1]);
						return(exit_function (TRUE));
					}
					
					*container = strlen(text_of(word[2]));	
				}
			} else if (!strcmp(word[0], "savegame")) {
				if (word[1] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return (exit_function(TRUE));
				} else {
					if ((container = container_resolve(word[1])) == NULL) {
						unkvarrun(word[1]);
						return (exit_function(TRUE));
					} else {
						if (word[2] == NULL) {
							*container = save_interaction(NULL);
						} else {
							*container = save_interaction(arg_text_of_word(2));
						}
					}
				} 
			} else if (!strcmp(word[0], "restoregame")) {
				if (word[1] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return (exit_function(TRUE));
				} else {
					if ((container = container_resolve(word[1])) == NULL) {
						unkvarrun(word[1]);
						return (exit_function(TRUE));
					} else {
						if (word[2] == NULL) {
							*container = restore_interaction(NULL);
						} else {
							*container = restore_interaction(arg_text_of_word(2));
						}
					}
				} 
			} else if (!strcmp(word[0], "restartgame")) {
				restart_game();
				execute ("+intro");
				eachturn();
#ifdef GLK
			} else if (!strcmp(word[0], "undomove")) {
				undoing();
			} else if (!strcmp(word[0], "updatestatus")) {
				status_line();
#else
			} else if (!strcmp(word[0], "undomove")) {
			} else if (!strcmp(word[0], "updatestatus")) {
#endif
			} else if (!strcmp(word[0], "split")) {

				// 0     1       2      3         4
				// split counter source delimiter destination

				int *split_container;
				char split_buffer[256] = "";
				char container_buffer[256] = "";
				char delimiter[256] = ""; 
				char *match = NULL;
				struct string_type *resolved_splitstring = NULL;

				strcpy (split_buffer, text_of_word(2));
				strcpy (delimiter, text_of_word(3));

				char *source = split_buffer;

				if (word[4] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun(0);
					return (exit_function(TRUE));
				} else {
					split_container = container_resolve(var_text_of_word(1));

					if (split_container == NULL) {
						unkvarrun(var_text_of_word(1));
						return (exit_function(TRUE));
					} else {
						*split_container = 0;
						match = source;		// THERE IS ALWAYS ONE MATCH, EVEN IF
											// NO DELIMETERS ARE FOUND

						while ((match = strstr(source, delimiter))) {
							*match = 0;
							strcpy(container_buffer, var_text_of_word(4));
							strcat(container_buffer, "[");
							sprintf(integer_buffer, "%d", *split_container);
							strcat(container_buffer, integer_buffer);
							strcat(container_buffer, "]");

							if ((resolved_splitstring = string_resolve(container_buffer)) == NULL) {
								unkstrrun(var_text_of_word(4));
								return (exit_function(TRUE));
							} else {
								strcpy(resolved_splitstring->value, source);
								source = match + strlen(delimiter);
								(*split_container)++;
							}
						}
						strcpy(container_buffer, var_text_of_word(4));
						strcat(container_buffer, "[");
						sprintf(integer_buffer, "%d", *split_container);
						strcat(container_buffer, integer_buffer);
						strcat(container_buffer, "]");

						if ((resolved_splitstring = string_resolve(container_buffer)) == NULL) {
							unkstrrun(word[1]);
							return (exit_function(TRUE));
						} else {
							strcpy(resolved_splitstring->value, source);
							(*split_container)++;
						}
					}
				}
			} else if (!strcmp(word[0], "setstring") ||
						!strcmp(word[0], "addstring")) {
				char setstring_buffer[2048] = "";
				struct string_type *resolved_setstring = NULL;

				if (word[2] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun(0);
					return (exit_function(TRUE));
				} else {
					/* GET A POINTER TO THE STRING BEING MODIFIED */
					if ((resolved_setstring = string_resolve(var_text_of_word(1))) == NULL) {
						unkstrrun(word[1]);
						return (exit_function(TRUE));
					}

					/* RESOLVE ALL THE TEXT AND STORE IT IN A TEMPORARY BUFFER*/
					for (counter = 2; word[counter] != NULL && counter < MAX_WORDS; counter++) {
						strcat(setstring_buffer, text_of_word(counter));
					}

					/* setstring_buffer IS NOW FILLED, COPY THE UP TO 256 BYTES OF
					 * IT INTO THE STRING */
					if (!strcmp(word[0], "setstring")) {
						strncpy (resolved_setstring->value, setstring_buffer, 255);
					} else {
						/* CALCULATE HOW MUCH SPACE IS LEFT IN THE STRING */
						counter = 255 - strlen(resolved_setstring->value);
						/* THIS IS A addstring COMMAND, SO USE STRNCAT INSTEAD */
						strncat (resolved_setstring->value, setstring_buffer, counter);
					}
				}
			} else if (!strcmp(word[0], "padstring")) {
				char setstring_buffer[2048] = "";
				struct string_type *resolved_setstring = NULL;
				string_buffer[0] = 0;
	
				if (word[3] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun(0);
					return (exit_function(TRUE));
				} else {
					/* GET A POINTER TO THE STRING BEING MODIFIED */
					if ((resolved_setstring = string_resolve(word[1])) == NULL) {
						unkstrrun(word[1]);
						return (exit_function(TRUE));
					}

					index = value_of(word[3], TRUE);	

					for (counter = 0; counter < index; counter++) {
					     strcat(setstring_buffer, text_of_word(2));
					}

					/* setstring_buffer IS NOW FILLED, COPY THE UP TO 256 BYTES OF
					 * IT INTO THE STRING */
					strncpy (resolved_setstring->value, setstring_buffer, 255);
				}
			} else if (!strcmp(word[0], "return")) {
				/* RETURN FROM THIS FUNCTION, POSSIBLY RETURNING AN INTEGER VALUE */
				if (word[1] == NULL) {
					return (exit_function(TRUE));
				} else {
					index = value_of(word[1], TRUE);
					return (exit_function(index));
				}
			} else if (!strcmp(word[0], "position")) {
				/* MOVE AN OBJECT TO ITS NEW X,Y COORDINATES BASED ON ITS CURRENT VALUES
				 * FOR x, y, bearing, velocity */
				if (word[1] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return (exit_function(TRUE));
				} else {
					object_1 = value_of(word[1], TRUE);

					if (object_1 < 1 || object_1 > objects) {
						badptrrun(word[1], object_1);
						return (exit_function(TRUE));
					} else {
						new_position((double) object[object_1]->X,
									 (double) object[object_1]->Y,
									 (double) object[object_1]->BEARING,
									 (double) object[object_1]->VELOCITY);

						object[object_1]->X = new_x;
						object[object_1]->Y = new_y;
					}
				}
			} else if (!strcmp(word[0], "bearing")) {
				/* CALCULATE THE BEARING BETWEEN TWO OBJECTS */
				if (word[3] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return (exit_function(TRUE));
				} else {
					if ((container = container_resolve(word[1])) == NULL) {
						unkvarrun(word[1]);
						return (exit_function(TRUE));
					}

					object_1 = value_of(word[2], TRUE);

					if (object_1 < 1 || object_1 > objects) {
						badptrrun(word[2], object_1);
						return (exit_function(TRUE));
					} else {
						object_2 = value_of(word[3], TRUE);

						if (object_2 < 1 || object_2 > objects) {
							badptrrun(word[3], object_2);
							return (exit_function(TRUE));
						} else {
							if (container != NULL
								&& object_1 != FALSE
								&& object_2 != FALSE) {
								*container = bearing((double) object[object_1]->X,
													 (double) object[object_1]->Y,
													 (double) object[object_2]->X,
													 (double) object[object_2]->Y);
							}
						}
					}
				}
			} else if (!strcmp(word[0], "distance")) {
				/* CALCULATE THE DISTANCE BETWEEN TWO OBJECTS */
				if (word[3] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return (exit_function(TRUE));
				} else {
					container = container_resolve(word[1]);

					object_1 = value_of(word[2], TRUE);

					if (object_1 < 1 || object_1 > objects) {
						badptrrun(word[2], object_1);
						return (exit_function(TRUE));
					} else {
						object_2 = value_of(word[3], TRUE);

						if (object_2 < 1 || object_2 > objects) {
							badptrrun(word[3], object_2);
							return (exit_function(TRUE));
						} else {
							if (container != NULL
								&& object_1 != FALSE
								&& object_2 != FALSE) {
								*container = distance((double)
													  object[object_1]->X,
													  (double)
													  object[object_1]->Y,
													  (double)
													  object[object_2]->X,
													  (double)
													  object[object_2]->Y);
							}
						}
					}
				}
			} else if (!strcmp(word[0], "dir_to") ||
					   !strcmp(word[0], "npc_to")) {
				/* CALCULATE THE FIRST DIRECTION TO TRAVEL IN GET TO 
				 * A SPECIFIED LOCATION */
				if (word[3] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return (exit_function(TRUE));
				} else {
					container = container_resolve(word[1]);

					object_1 = value_of(word[2], TRUE);

					if (object_1 < 1 || object_1 > objects) {
						badptrrun(word[2], object_1);
						return (exit_function(TRUE));
					} else {
						object_2 = value_of(word[3], TRUE);

						if (object_2 < 1 || object_2 > objects) {
							badptrrun(word[3], object_2);
							return (exit_function(TRUE));
						} else {
							if (container != NULL
								&& object_1 != FALSE
								&& object_2 != FALSE) {
								if (!strcmp(word[0], "dir_to")) {
									*container = find_route(object_1, object_2, TRUE);
								} else {
									*container = find_route(object_1, object_2, FALSE);
								}
							}
						}
					}
				}
			} else if (!strcmp(word[0], "set")) {
				/* SET THE VALUE OF AN ELEMENT TO A SUPPLIED INTEGER */
				if (word[3] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return (exit_function(TRUE));
				} else {
					container = container_resolve(var_text_of_word(1));

					if (container == NULL) {
						unkvarrun(word[1]);
						return (exit_function(TRUE));
					} else {
						int mark = 2; // SET mark TO POINT TO THE FIRST OPERATOR
						while (word[mark + 1] != NULL) {
							counter = value_of(word[mark + 1], TRUE);
	
							if (word[mark][0] == '+')
								*container += counter;
							else if (word[mark][0] == '-')
								*container -= counter;
							else if (word[mark][0] == '*')
								*container = *container * counter;
							else if (word[mark][0] == '%')
								*container = *container % counter;
							else if (word[mark][0] == '/') {
								if (counter == 0) {
									sprintf(error_buffer, DIVIDE_BY_ZERO,
											executing_function->name);
									log_error(error_buffer, PLUS_STDOUT);
								} else
									*container = *container / counter;
							} else if (!strcmp(word[mark], "locationof")) {
								*container = grand_of(counter, FALSE);
							} else if (!strcmp(word[mark], "grandof")) {
								*container = grand_of(counter, TRUE);
							} else if (word[mark][0] == '=') {
								*container = counter; 
							} else {
								sprintf(error_buffer, ILLEGAL_OPERATOR,
										executing_function->name,
										word[2]);
								log_error(error_buffer, PLUS_STDOUT);
							}

							mark += 2;
						}
					}
				}
			} else if (!strcmp(word[0], "ensure")) {
				/* USED TO GIVE OR TAKE AN ATTRIBUTE TO OR FROM AND OBJECT */
				if (word[3] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return (exit_function(TRUE));
				} else {
					if ((bit_mask = attribute_resolve(arg_text_of(word[3])))) {
						index = value_of(word[1], TRUE);
						if (index < 1 || index > objects) {
							badptrrun(word[1], index);
							return (exit_function(TRUE));
						} else {
							if (!strcmp(word[2], "has")) {
								object[index]->attributes =
									object[index]->attributes | bit_mask;
							} else if (!strcmp(word[2], "hasnt")) {
								bit_mask = ~bit_mask;
								object[index]->attributes =
									object[index]->attributes & bit_mask;
							}
						}
					} else if ((bit_mask = user_attribute_resolve(arg_text_of(word[3])))) {
						index = value_of(word[1], TRUE);
						if (index < 1 || index > objects) {
							badptrrun(word[1], index);
							return (exit_function(TRUE));
						} else {
							if (!strcmp(word[2], "has")) {
								object[index]->user_attributes =
									object[index]->user_attributes | bit_mask;
							} else if (!strcmp(word[2], "hasnt")) {
								bit_mask = ~bit_mask;
								object[index]->user_attributes =
									object[index]->user_attributes & bit_mask;
							}
						}
					} else {
						unkattrun(3);
						return (exit_function(TRUE));
					}
				}
			} else if (!strcmp(word[0], "append")) {
				int first = TRUE;

				if (word[2] == NULL) {
					// NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND
					noproprun();
					return (exit_function(TRUE));
				} else {
					strcpy (temp_buffer, data_directory);
					strcat (temp_buffer, prefix);
					strcat (temp_buffer, "-");
					strcat (temp_buffer, text_of_word(1));
					strcat (temp_buffer, ".csv");

					outfile = fopen(temp_buffer, "ab");

					if (outfile == NULL) {
    					sprintf(error_buffer, "Failed to open file %s: %s\n", temp_buffer, strerror(errno));
						log_error(error_buffer, PLUS_STDOUT);
  					} else {
						for (counter = 2; word[counter] != NULL && counter < MAX_WORDS; counter++) {
							output = text_of_word(counter);			
							if (*output != 0) {
								if (first == FALSE) {
	  								fputc(',', outfile);
								}
	  							csv_fwrite(outfile, output, (size_t) strlen(output));
								first = FALSE;
							}
						}
	
						// TERMINATE THE LINE
						fputc('\n', outfile);
						
						// FLUSH AND CLOSE THE FILE
						fflush(outfile);
					}

					fclose(outfile);
					outfile = NULL;
				}
			} else if (!strcmp(word[0], "insert")) {
				int first = TRUE;

				if (word[1] == NULL) {
					// NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND
					noproprun();
					return (exit_function(TRUE));
				} else {
					if (outfile == NULL) {
						log_error("Insert statement not inside an 'update' loop.", PLUS_STDOUT);
  					} else {
						for (counter = 1; word[counter] != NULL && counter < MAX_WORDS; counter++) {
							output = text_of_word(counter);			
							if (*output != 0) {
								if (first == FALSE) {
	  								fputc(',',(FILE *)outfile);
								}
	  							csv_fwrite((FILE *)outfile, output, (size_t) strlen(output));
								first = FALSE;
							}
						}
	
						// TERMINATE THE LINE
						fputc('\n', (FILE *)outfile);
					}
				}
			} else if (!strcmp(word[0], "inspect")) {
				if (word[1] == NULL) {
					// NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND
					noproprun();
					return (exit_function(TRUE));
				} else {
					inspect(value_of(word[1], TRUE));
				}
			} else if (!strcmp(word[0], "move")) {
				/* THIS COMMAND IS USED TO MOVE AN OBJECT TO HAVE ANOTHER PARENT
				 * INCLUDING MODIFYING ALL QUANTITY VALUES BASED ON THE OBJECTS MASS */
				if (word[3] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun();
					return (exit_function(TRUE));
				}

				index = value_of(word[1], TRUE);
				if (index < 1 || index > objects) {
					badptrrun(word[1], index);
					return (exit_function(TRUE));
				} else {
					object_2 = object[index]->PARENT;
					if (object_2 && !(object[object_2]->attributes & LOCATION)) {
						object[object_2]->QUANTITY += object[index]->MASS;
					}
					object_1 = value_of(word[3], TRUE);
					if (object_1 < 1 || object_1 > objects) {
						badptrrun(word[1], object_1);
						return (exit_function(TRUE));
					} else {
						object[index]->PARENT = object_1;
						if (!(object[object_1]->attributes & LOCATION))
							object[object_1]->QUANTITY -= object[index]->MASS;
					} 
				}
			} else if (!strcmp(word[0], "ifstringall")) {
				/* CHECK IF A STRING EQUALS OR CONTAINS ANOTHER STRING */
				current_level++;
				if (word[3] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun(0);
					return (exit_function(TRUE));
				} else if (and_strcondition()) {
					execution_level++;
				}
			} else if (!strcmp(word[0], "ifstring")) {
				/* CHECK IF A STRING EQUALS OR CONTAINS ANOTHER STRING */
				current_level++;
				if (word[3] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun(0);
					return (exit_function(TRUE));
				} else if (strcondition()) {
					execution_level++;
				}
			} else if (!strcmp(word[0], "ifexecute")) {
				current_level++;
				if (word[1] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun(0);
					return (exit_function(TRUE));
				} else {
					/* RESOLVE ALL THE TEXT AND STORE IT IN A TEMPORARY BUFFER*/
					string_buffer[0] = 0;

					for (counter = 1; word[counter] != NULL && counter < MAX_WORDS; counter++) {
						strcat(string_buffer, arg_text_of_word(counter));
					}

					if (execute(string_buffer)) {
						execution_level++;
					}
				}
			} else if (!strcmp(word[0], "if")) {
				current_level++;
				if (word[3] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun(0);
					return (exit_function(TRUE));
				} else if (condition()) {
					execution_level++;
				}
			} else if (!strcmp(word[0], "ifall")) {
				current_level++;
				if (word[3] == NULL) {
					/* NOT ENOUGH PARAMETERS SUPPLIED FOR THIS COMMAND */
					noproprun(0);
					return (exit_function(TRUE));
				} else if (and_condition()) {
					execution_level++;
				}
			} else {
				sprintf(error_buffer, UNKNOWN_COMMAND,
						executing_function->name, word[0]);
				log_error(error_buffer, PLUS_STDOUT);
			}
		} else if (!strcmp(word[wp], "if") 
				|| !strcmp(word[wp], "ifall")
				|| !strcmp(word[wp], "ifstring")
				|| !strcmp(word[wp], "ifstringall")
				|| !strcmp(word[wp], "ifexecute")
				|| !strcmp(word[wp], "iterate")
				|| !strcmp(word[wp], "update")
				|| !strcmp(word[wp], "while")
				|| !strcmp(word[wp], "whileall")) {
			current_level++;
		}

#ifdef GLK
		before_command = glk_stream_get_position(game_stream);
		glk_get_bin_line_stream(game_stream, text_buffer, (glui32) 1024);
#else
        before_command = ftell(file);
        fgets(text_buffer, 1024, file);
#endif
		if (encrypted) jacl_decrypt(text_buffer);
	};

	return (exit_function(TRUE));
}

int
exit_function(return_code)
	int			return_code;
{
	if (infile != NULL) {
		read_lck.l_type = F_UNLCK;	// SETTING A READ LOCK
  		fcntl(read_fd, F_SETLK, &read_lck);
    	fclose(infile);
		infile = NULL;
	}

	if (outfile != NULL) {
		write_lck.l_type = F_UNLCK;	// SETTING A WRITE LOCK
  		fcntl(write_fd, F_SETLK, &write_lck);
		fclose(outfile);
		outfile = NULL;
	}
	
	/* POP THE STACK REGARDLESS OF THE RETURN CODE */
	pop_stack();

	return (return_code);
}

char *
object_names(object_index, names_buffer)
	 int             object_index;
	 char			*names_buffer;
{
	/* THIS FUNCTION CREATES A LIST OF ALL AN OBJECT'S NAMES.
	   THE escape ARGUMENT INDICATES WHETHER A + SIGN SHOULD BE
	   USED IN PLACE OF A SPACE BETWEEN EACH OF THE NAMES */
	struct name_type *current_name = object[object_index]->first_name;
	names_buffer[0] = 0;

	while (current_name != NULL) {
		strcat(names_buffer, " ");
		strcat(names_buffer, current_name->name);
		current_name = current_name->next_name;
	}

	return names_buffer;
}

int
distance(x1, y1, x2, y2)
	 double          x1,
	                 y1,
	                 x2,
	                 y2;
{
	/* THIS FUNCTION CALCULATES THE DISTANCE BETWEEN TWO POINTS IN A 
	   TWO-DIMENSIONAL PLANE */
	double          delta_x,
	                delta_y;
	double          distance,
	                total;

	/*
	 * Object two in which quadrant compared to object one? 0 x = opp, y = 
	 * ajd + 0 degrees 1 x = adj, y = opp + 90 degrees 2 x = opp, y = ajd
	 * + 180 degrees 3 x = adj, y = opp + 270 degrees 
	 */

	/*
	 * DETERMINE WHICH QUADRANT OBJECT TWO IS IN 
	 */

	if (x2 > x1) {
		/*
		 * OBJECT TWO IS IN 1 OR 2 
		 */
		delta_x = x2 - x1;
		if (y2 > y1) {
			delta_y = y2 - y1;
		} else {
			delta_y = y1 - y2;
		}
	} else {
		/*
		 * OBJECT TWO IS IN 3 OR 4 
		 */
		delta_x = x1 - x2;
		if (y2 > y1) {
			delta_y = y2 - y1;
		} else {
			delta_y = y1 - y2;
		}
	}

	delta_y = delta_y * delta_y;
	delta_x = delta_x * delta_x;

	total = delta_y + delta_x;

	distance = sqrt(total);

	return ((int) distance);
}

void
new_position(x1, y1, bearing, velocity)
	 double          x1,
	                 y1,
	                 bearing,
	                 velocity;
{
	double          delta_x,
	                delta_y;
	double          radians;

	/*
	 * Object two in which quadrant compared to object one? 0 x = opp, y = 
	 * ajd + 0 degrees 1 x = adj, y = opp + 90 degrees 2 x = opp, y = ajd
	 * + 180 degrees 3 x = adj, y = opp + 270 degrees 
	 */

	/*
	 * sin finds opp, cos finds adj 
	 */

	if (bearing < 91) {
		radians = bearing * 2.0 * M_PI / 360.;
		delta_x = velocity * sin(radians);
		delta_y = velocity * cos(radians);
		new_x = x1 + delta_x;
		new_y = y1 + delta_y;
	} else if (bearing < 181) {
		bearing -= 90;
		radians = bearing * 2.0 * M_PI / 360.;
		delta_y = velocity * sin(radians);
		delta_x = velocity * cos(radians);
		new_x = x1 + delta_x;
		new_y = y1 - delta_y;
	} else if (bearing < 271) {
		bearing -= 180;
		radians = bearing * 2.0 * M_PI / 360.;
		delta_x = velocity * sin(radians);
		delta_y = velocity * cos(radians);
		new_x = x1 - delta_x;
		new_y = y1 - delta_y;
	} else {
		bearing -= 270;
		radians = bearing * 2.0 * M_PI / 360.;
		delta_y = velocity * sin(radians);
		delta_x = velocity * cos(radians);
		new_x = x1 - delta_x;
		new_y = y1 + delta_y;
	}
}

int
bearing(x1, y1, x2, y2)
	 double          x1,
	                 y1,
	                 x2,
	                 y2;
{
	int             quadrant;
	double          delta_x,
	                delta_y;
	double          oppoadj;
	double          bearing;

	/*
	 * Object two in which quadrant compared to object one? 0 x = opp, y = 
	 * ajd + 0 degrees 1 x = adj, y = opp + 90 degrees 2 x = opp, y = ajd
	 * + 180 degrees 3 x = adj, y = opp + 270 degrees 
	 */

	if (x2 > x1) {
		delta_x = x2 - x1;
		if (y2 > y1) {
			quadrant = 0;
			delta_y = y2 - y1;
			oppoadj = delta_x / delta_y;
		} else {
			quadrant = 1;
			delta_y = y1 - y2;
			oppoadj = delta_y / delta_x;
		}
	} else {
		delta_x = x1 - x2;
		if (y2 > y1) {
			quadrant = 3;
			delta_y = y2 - y1;
			oppoadj = delta_y / delta_x;
		} else {
			quadrant = 2;
			delta_y = y1 - y2;
			oppoadj = delta_x / delta_y;
		}
	}

	bearing = atan(oppoadj);
	bearing = bearing / (2.0 * M_PI) * 360.;
	bearing = bearing + (90 * quadrant);

	return ((int) bearing);
}

void
set_arguments(function_call)
  char		*function_call;
{
	/* THIS FUNCTION CREATES AN ARRAY OF JACL INTEGER CONSTANTS TO
	   REPRESENT THE ARGUMENTS PASSED TO A JACL FUNCTION */
	int             index,
					counter,
	                length;
	int             position = 0; /* STORE THE INDEX OF THE WORD */
					/* SETTING new_word TO FALSE SKIPS THE FIRST */
					/* WORD WHICH IS THE FUNCTION NAME */
	int             new_word = FALSE;

	char 			*arg_ptr[MAX_WORDS];
	int 			arg_value[MAX_WORDS];

	struct integer_type *resolved_integer;
	struct cinteger_type *resolved_cinteger;

	/* SPLIT UP THE FUNCTION CALL STRING AND EXTRACT THE ARGUMENTS */
	length = strlen(function_call);

	for (index = 0; index < length; index++) {
		if (function_call[index] == '<') {
			argument_buffer[index] = 0;
			new_word = TRUE;
		} else {
			// COPY THE CHARACTER FROM THE CALLED NAME INTO THE CURRENT
			// ARGUMENT BUFFER        
			argument_buffer[index] = function_call[index];
			if (new_word) {
				// THIS IS THE FIRST CHARACTER OF A NEW ARGUMENT SO STORE
				// THE ADDRESS OF THIS CHARACTER IN THE ARGUMENT BUFFER
				arg_ptr[position] = &argument_buffer[index];
				new_word = FALSE;
				if (position < MAX_WORDS)
					position++;
			}
		}
	}

	argument_buffer[index] = 0;

	/* CLEAR THE NEXT ARGUMENT POINTER */
	arg_ptr[position] = NULL;

	/* STORE THE INTEGER VALUE OF EACH ARGUMENT PASSED*/
	index = 0;
	while (arg_ptr[index] != NULL) {
		//arg_value[index] = value_of(arg_ptr[index], TRUE);

		if ((resolved_integer = integer_resolve(arg_ptr[index])) != NULL) {
			arg_value[index] = resolved_integer->value;
		} else if ((resolved_cinteger = cinteger_resolve(arg_ptr[index])) != NULL) {
			arg_value[index] = resolved_cinteger->value;
		} else if (object_element_resolve(arg_ptr[index])) {
			arg_value[index] = oec;
		} else if ((counter = object_resolve(arg_ptr[index])) != -1) {
			if (counter < 1 || counter > objects) {
				badptrrun(arg_ptr[index], counter);
				pop_stack();
				return;
			} else {
				arg_value[index] = counter;
			}
		} else if (validate(arg_ptr[index])) {
			arg_value[index] = atoi(arg_ptr[index]);
		} else {
			arg_value[index] = -1;
		}

        index++;
	}

	/* THE CURRENT ARGUMENTS HAVE ALREADY BEEN PUSHED ONTO THE STACK
	 * AND STORED IF PASSED AS AN ARGUMENT TO THIS FUNCTION SO IT IS 
	 * OKAY TO CLEAR THEM AND SET THE NEW VALUES */
	clear_cinteger("arg");
	clear_cstring("string_arg");

	/* CREATE A CONSTANT FOR EACH ARGUMENT AFTER THE CORE FUNCTION NAME */
	index = 0;
	while (arg_ptr[index] != NULL) {
		if (index == 0) noun[3] = arg_value[index];
		add_cinteger ("arg", arg_value[index]);
		//printf("--- %s = %s\n", arg_ptr[index], arg_text_of(arg_ptr[index]));
		add_cstring ("string_arg", arg_text_of(arg_ptr[index]));
        index++;
	}
}

void
pop_stack()
{
	int index, counter;

	stack--;

	clear_cinteger ("arg");
	clear_cstring ("string_arg");

	/* RECREATE THE arg ARRAY FOR THIS STACK FRAME */
	for (index = 0; index < backup[stack].argcount; index++) {
		if (index == 0) noun[3] = backup[stack].arguments[0];
		add_cinteger ("arg", backup[stack].arguments[index]);
	}

    /* RECREATE THE string_arg ARRAY FOR THIS STACK FRAME */
    for (index = 0; index < backup[stack].argcount; index++) {
        add_cstring ("string_arg", backup[stack].str_arguments[index]);
    }

	/* RESTORE THE CONTENTS OF text_buffer */
	for (counter = 0; counter < 1024; counter++)
		text_buffer[counter] = backup[stack].text_buffer[counter];

	/* RESTORE THE CONTENTS OF called_name */
	//for (counter = 0; counter < 256; counter++)
	//called_name[counter] = backup[stack].called_name[counter];
	strncpy(called_name, backup[stack].called_name, 1024);

	/* RESTORE THE CONTENTS OF scope_criterion */
	//for (counter = 0; counter < 21; counter++)
	//	scope_criterion[counter] = backup[stack].scope_criterion[counter];
	strncpy(scope_criterion, backup[stack].scope_criterion, 20);

	/* RESTORE THE STORED FUNCTION NAMES THAT ARE USED WHEN AN
	 * 'override' COMMAND IS ENCOUNTERED IN THE CURRENT FUNCTION */
	strncpy (override, backup[stack].override, 80);
	strncpy (default_function, backup[stack].default_function, 80);

	/* RESTORE ALL THE WORD POINTERS */
	for (counter = 0; counter < MAX_WORDS; counter++) {
		word[counter] = backup[stack].word[counter];
		quoted[counter] = backup[stack].quoted[counter];
	}

	executing_function = backup[stack].function;

	if (executing_function != NULL) {
		strncpy (function_name, executing_function->name, 80);
		strncpy (cstring_resolve("function_name")->value, executing_function->name, 80);
	}

	wp = backup[stack].wp;
	top_of_loop = backup[stack].top_of_loop;
	outfile = backup[stack].outfile;
	infile = backup[stack].infile;
	top_of_select = backup[stack].top_of_select;
	top_of_while = backup[stack].top_of_while;
	top_of_iterate = backup[stack].top_of_iterate;
	top_of_update = backup[stack].top_of_update;
	top_of_do_loop = backup[stack].top_of_do_loop;
	criterion_value = backup[stack].criterion_value;
	criterion_type = backup[stack].criterion_type;
	criterion_negate = backup[stack].criterion_negate;
	current_level = backup[stack].current_level;
	execution_level = backup[stack].execution_level;
	loop_integer = backup[stack].loop_integer;
	select_integer = backup[stack].select_integer;

#ifdef GLK
	glk_stream_set_position(game_stream, backup[stack].address, seekmode_Start);
#else
    fseek(file, backup[stack].address, SEEK_SET);
#endif

}

void
push_stack(file_pointer)
#ifdef GLK
	 glsi32          file_pointer;
#else
	 long          file_pointer;
#endif
{
	/* COPY ALL THE CURRENT SYSTEM DATA ONTO THE STACK */
	int index;
	int counter = 0;

	if (stack == STACK_SIZE) {
		log_error("Stack overflow.", PLUS_STDERR);
		terminate(45);
	} else {
		backup[stack].infile = infile;
		infile = NULL;
		backup[stack].outfile = outfile;
		outfile = NULL;
		backup[stack].function = executing_function;
		backup[stack].address = file_pointer;
		backup[stack].wp = wp;
		backup[stack].top_of_loop = top_of_loop;
		backup[stack].top_of_select = top_of_select;
		backup[stack].top_of_while = top_of_while;
		backup[stack].top_of_iterate = top_of_iterate;
		backup[stack].top_of_update = top_of_update;
		backup[stack].top_of_do_loop = top_of_do_loop;
		backup[stack].criterion_value = criterion_value;
		backup[stack].criterion_type = criterion_type;
		backup[stack].criterion_negate = criterion_negate;
		backup[stack].current_level = current_level;
		backup[stack].execution_level = execution_level;
		backup[stack].loop_integer = loop_integer;
		backup[stack].select_integer = select_integer;
		
		/* MAKE A COPY OF THE CURRENT CONTENTS OF text_buffer */
		for (counter = 0; counter < 1024; counter++)
			backup[stack].text_buffer[counter] = text_buffer[counter];

		/* MAKE A COPY OF THE CURRENT CONTENTS OF called_name */
		strncpy(backup[stack].called_name, called_name, 1024);

		// MAKE A COPY OF THE CURRENT CONTENTS OF scope_criterion
		strncpy(backup[stack].scope_criterion, scope_criterion, 20);

		/* COPY THE STORED FUNCTION NAMES THAT ARE USED WHEN AN
		 * 'override' COMMAND IS ENCOUNTERED IN THE CURRENT FUNCTION */
		strncpy (backup[stack].override, override, 80);
		strncpy (backup[stack].default_function, default_function, 80);

		/* PUSH ALL THE WORD POINTERS ONTO THE STACK */
		for (counter = 0; counter < MAX_WORDS; counter++) {
			backup[stack].word[counter] = word[counter];
			backup[stack].quoted[counter] = quoted[counter];
		}

		// PUSH ALL THE ARGUMENTS AS INTEGERS ONTO THE STACK
		index = 0;
		current_cinteger = cinteger_table;

        if (current_cinteger != NULL) {
            do {
                if (!strcmp(current_cinteger->name, "arg")) {
                    backup[stack].arguments[index++] = current_cinteger->value;
                }
                current_cinteger = current_cinteger->next_cinteger;
            }
            while (current_cinteger != NULL);
        }

		// STORE THE NUMBER OF ARGUMENTS PASSED TO THIS FUNCTION
		// THIS IS THE SAME NUMBER FOR STRINGS AND INTEGERS
		backup[stack].argcount = index;

        // PUSH ALL THE ARGUMENTS AS STRINGS STRING ONTO THE STACK
        index = 0;
		current_cstring = cstring_table;

        if (current_cstring != NULL) {
            do {
                if (!strcmp(current_cstring->name, "string_arg")) {
                    strncpy(backup[stack].str_arguments[index++], current_cstring->value, 255);
                }

                current_cstring = current_cstring->next_string;
            }
            while (current_cstring != NULL);
        }
	}

	// PUSH ON TO THE NEXT STACK FRAME
	stack++;
}

void
pop_proxy()
{
	int index, counter;

	proxy_stack--;

	clear_cinteger ("$integer");
	clear_cstring ("$string");
	clear_cstring ("$word");

	/* RECREATE THE integer ARRAY FOR THIS STACK FRAME */
	for (index = 0; index < proxy_backup[proxy_stack].integercount; index++) {
		add_cinteger ("$integer", proxy_backup[proxy_stack].integer[index]);
	}

	/* RECREATE THE text ARRAY FOR THIS STACK FRAME */
	for (index = 0; index < proxy_backup[proxy_stack].textcount; index++) {
		add_cstring ("$string", proxy_backup[proxy_stack].text[index]);
	}

    /* RECREATE THE $word ARRAY FOR THIS STACK FRAME */
	for (index = 0; index < proxy_backup[proxy_stack].commandcount; index++) {
        add_cstring ("$word", proxy_backup[proxy_stack].command[index]);
    }

	/* RESTORE ALL THE NOUN POINTERS */
	for (counter = 0; counter < 4; counter++)
		noun[counter] = proxy_backup[proxy_stack].object_pointers[counter];

	/* PUSH ALL THE RESOLVED OBJECTS ONTO THE STACK */
	for (index = 0; index < 4; index++) {
		list_size[index] = proxy_backup[proxy_stack].list_size[index];
		max_size[index] = proxy_backup[proxy_stack].max_size[index];
		for (counter = 0; counter < max_size[index]; counter++) {
			object_list[index][counter] = proxy_backup[proxy_stack].object_list[index][counter];
		}
	}

	start_of_this_command = proxy_backup[proxy_stack].start_of_this_command;
	start_of_last_command = proxy_backup[proxy_stack].start_of_last_command;
	after_from = proxy_backup[proxy_stack].after_from;
	last_exact = proxy_backup[proxy_stack].last_exact;
}

void
push_proxy()
{
	/* COPY ALL THE CURRENT SYSTEM DATA ONTO THE STACK */
	int index;
	int	counter = 0;
	int	command = 0;
	int text = 0;

	current_cinteger = cinteger_table;
	current_cstring = cstring_table;

	if (proxy_stack == STACK_SIZE) {
		log_error("Stack overflow.", PLUS_STDERR);
		terminate(45);
	} else {
		proxy_backup[proxy_stack].start_of_this_command = start_of_this_command;
		proxy_backup[proxy_stack].start_of_last_command = start_of_last_command;
		
		/* PUSH ALL THE OBJECT POINTERS ONTO THE STACK */
		for (counter = 0; counter < 4; counter++)
			proxy_backup[proxy_stack].object_pointers[counter] = noun[counter];

		/* PUSH ALL THE RESOLVED OBJECTS ONTO THE STACK */
		for (index = 0; index < 4; index++) {
			for (counter = 0; counter < max_size[index]; counter++) {
				proxy_backup[proxy_stack].object_list[index][counter] 
					=	object_list[index][counter];
			}
			proxy_backup[proxy_stack].list_size[index] = list_size[index];
			proxy_backup[proxy_stack].max_size[index] = max_size[index];
		}

		/* PUSH ALL THE CURRENT COMMAND INTEGERS ONTO THE STACK */
		counter = 0;

        if (current_cinteger != NULL) {
            do {
                if (!strcmp(current_cinteger->name, "$integer")) {
                    proxy_backup[proxy_stack].integer[counter++] = current_cinteger->value;                }
                current_cinteger = current_cinteger->next_cinteger;
            }
            while (current_cinteger != NULL);
        }

		proxy_backup[proxy_stack].integercount = counter;

        // PUSH ALL THE TEXT STRING SUPPLIED BY THE CURRENT COMMAND ONTO THE STACK
        text = 0;
		command = 0;

        if (current_cstring != NULL) {
            do {
                if (!strcmp(current_cstring->name, "$string")) {
                    strncpy(proxy_backup[proxy_stack].text[text++], current_cstring->value, 255);
                    proxy_backup[proxy_stack].text[counter++][255] = 0;
                } else if (!strcmp(current_cstring->name, "$word")) {
                    strncpy(proxy_backup[proxy_stack].command[command++], current_cstring->value, 255);
                }

                current_cstring = current_cstring->next_string;
            }
            while (current_cstring != NULL);
        }

		proxy_backup[proxy_stack].textcount = counter;
		proxy_backup[proxy_stack].commandcount = command;
		proxy_backup[proxy_stack].after_from = after_from;
		proxy_backup[proxy_stack].last_exact = last_exact;
	}

	// PUSH ON TO THE NEXT STACK FRAME
	proxy_stack++;
}

int
condition()
{
	/* COMPARE GROUPS OF TWO ELEMENTS. RETURN TRUE IF ANY ONE GROUP OF 
	 * ELEMENTS COMPARE 'TRUE' */
	int             first;

	first = 1;

	while (word[first + 2] != NULL && ((first +2) < MAX_WORDS)) {
		if (logic_test(first))
			return (TRUE);
		else
			first = first + 3;
	}
	return (FALSE);
}

int
and_condition()
{
	/* COMPARE GROUPS OF TWO ELEMENTS. RETURN FALSE IF ANY ONE GROUP OF 
	 * ELEMENTS COMPARE 'FALSE' */
	int             first;

	first = 1;

	while (word[first + 2] != NULL && ((first +2) < MAX_WORDS)) {
		if (logic_test(first) == FALSE)
			return (FALSE);
		else
			first = first + 3;
	}
	return (TRUE);
}

int
logic_test(first)
	 int             first;
{
	long            index,
	                compare;

	resolved_attribute = FALSE;

	index = value_of(word[first], TRUE);
	compare = value_of(word[first + 2], TRUE);

	if (!strcmp(word[first + 1], "=") || !strcmp(word[first + 1], "==")) {
		if (index == compare)
			return (TRUE);
		else
			return (FALSE);
	} else if (!strcmp(word[first + 1], ">")) {
		if (index > compare)
			return (TRUE);
		else
			return (FALSE);
	} else if (!strcmp(word[first + 1], "<")) {
		if (index < compare)
			return (TRUE);
		else
			return (FALSE);
	} else if (!strcmp(word[first + 1], "is")) {
		if (index < 1 || index > objects) {
			unkobjrun(first);
			return (FALSE);
		} else
			return (scope(index, word[first + 2]));
	} else if (!strcmp(word[first + 1], "isnt")) {
		if (index < 1 || index > objects) {
			unkobjrun(first);
			return (FALSE);
		} else
			return (!scope(index, word[first + 2]));
	} else if (!strcmp(word[first + 1], "has"))
		if (index < 1 || index > objects) {
			unkobjrun(first);
			return (FALSE);
		} else {
			if (resolved_attribute == SYSTEM_ATTRIBUTE) {
				return (object[index]->attributes & compare);
			} else {
				return (object[index]->user_attributes & compare);
			}
		}
	else if (!strcmp(word[first + 1], "hasnt"))
		if (index < 1 || index > objects) {
			unkobjrun(first);
			return (FALSE);
		} else {
			if (resolved_attribute == SYSTEM_ATTRIBUTE) {
				return (!(object[index]->attributes & compare));
			} else {
				return (!(object[index]->user_attributes & compare));
			}
		}
	else if (!strcmp(word[first + 1], "!=")
			 || !strcmp(word[first + 1], "<>")) {
		if (index != compare)
			return (TRUE);
		else
			return (FALSE);
	} else if (!strcmp(word[first + 1], ">=")
			   || !strcmp(word[first + 1], "=>")) {
		if (index >= compare)
			return (TRUE);
		else
			return (FALSE);
	} else if (!strcmp(word[first + 1], "<=")
			   || !strcmp(word[first + 1], "=<")) {
		if (index <= compare)
			return (TRUE);
		else
			return (FALSE);
	} else if (!strcmp(word[first + 1], "grandof")) {
		/* GRANDOF SAYS THAT AN OBJECT IS THE EVENTUAL PARENT OF ANOTHER OBJECT, NOT
		 * NECESSARILY IMMEDIATE */
		if (index < 1 || index > objects) {
			unkobjrun(first);
			return (FALSE);
		} else {
			if (compare < 1 || compare > objects) {
				unkobjrun(first + 2);
				return (FALSE);
			} else {
				if (parent_of(index, compare, UNRESTRICT))
					return (TRUE);
				else
					return (FALSE);
			}
		}
	} else if (!strcmp(word[first + 1], "!grandof")) {
		if (index < 1 || index > objects) {
			unkobjrun(first);
			return (FALSE);
		} else {
			if (compare < 1 || compare > objects) {
				unkobjrun(first + 2);
				return (FALSE);
			} else {
				if (parent_of(index, compare, UNRESTRICT))
					return (FALSE);
				else
					return (TRUE);
			}
		}
	} else {
		sprintf(error_buffer,
				"ERROR: In function \"%s\", illegal operator \"%s\".^",
				executing_function->name, word[2]);
		write_text(error_buffer);
		return (FALSE);
	}
}

int
strcondition()
{
	int             first;

	first = 1;

	while (word[first + 2] != NULL && ((first +2) < MAX_WORDS)) {
		if (str_test(first))
			return (TRUE);
		else
			first = first + 3;
	}
	return (FALSE);
}

int
and_strcondition()
{
	int             first;

	first = 1;

	while (word[first + 2] != NULL && ((first +2) < MAX_WORDS)) {
		if (str_test(first) == FALSE)
			return (FALSE);
		else
			first = first + 3;
	}
	return (TRUE);
}

int
str_test(first)
	 int             first;
{
	char  *index;
	char  *compare;

	// GET THE TWO STRING VALUES TO COMPARE

	index = arg_text_of_word(first);
	compare = arg_text_of_word(first + 2);

	if (!strcmp(word[first + 1], "==") || !strcmp(word[first + 1], "=")) {
		if (!strcasecmp(index, compare)) {
			return (TRUE);
		} else {
			return (FALSE);
		}
	} else if (!strcmp(word[first + 1], "!contains")) {
		if (strcasestr(index, compare))
			return (FALSE);
		else
			return (TRUE);
	} else if (!strcmp(word[first + 1], "contains")) {
		if (strcasestr(index, compare))
			return (TRUE);
		else
			return (FALSE);
	} else if (!strcmp(word[first + 1], "<>") || !strcmp(word[first + 1], "!=")) {
		if (strcasecmp(index, compare))
			return (TRUE);
		else
			return (FALSE);
	} else if (!strcmp(word[first + 1], "==C") || !strcmp(word[first + 1], "=C")) {
		if (!strcmp(index, compare)) {
			return (TRUE);
		} else {
			return (FALSE);
		}
	} else if (!strcmp(word[first + 1], "!containsC")) {
		if (strstr(index, compare))
			return (FALSE);
		else
			return (TRUE);
	} else if (!strcmp(word[first + 1], "containsC")) {
		if (strstr(index, compare))
			return (TRUE);
		else
			return (FALSE);
	} else if (!strcmp(word[first + 1], "<>C") || !strcmp(word[first + 1], "!=C")) {
		if (strcmp(index, compare))
			return (TRUE);
		else
			return (FALSE);
	} else {
		sprintf(error_buffer,
				"ERROR: In function \"%s\", illegal operator \"%s\".^",
				executing_function->name, word[2]);
		write_text(error_buffer);
		return (FALSE);
	}
}

void
add_cinteger(name, value)
  char *name;
  int   value;
{
	/* THIS FUNCTION ADDS A NEW JACL CONSTANT TO THE LIST */

	if ((new_cinteger = (struct cinteger_type *)
		malloc(sizeof(struct cinteger_type))) == NULL)
		outofmem();
	else {
		if (cinteger_table == NULL) {
			cinteger_table = new_cinteger;
		} else {
			/* FIND LAST CONSTANT IN LIST */	
			current_cinteger = cinteger_table;
			while (current_cinteger->next_cinteger != NULL) {
				current_cinteger = current_cinteger->next_cinteger;
			}
			current_cinteger->next_cinteger = new_cinteger;
		}
		strncpy(new_cinteger->name, name, 40);
		new_cinteger->name[40] = 0;
		new_cinteger->value = value;
		new_cinteger->next_cinteger = NULL;
	}
}

void
clear_cinteger(name)
  char *name;
{
    /* FREE CONSTANTS THAT HAVE SUPPLIED NAME*/

	//printf("--- clear integer %s\n", name);
	if (cinteger_table != NULL) {
		current_cinteger = cinteger_table;
		previous_cinteger = cinteger_table;
		while (current_cinteger != NULL) {
			//sprintf(temp_buffer, "--- checking integer %s^", current_cinteger->name);
			//write_text(temp_buffer);
			if (!strcmp(current_cinteger->name, name)) {
				//sprintf(temp_buffer, "--- found integer %s^", name);
				//write_text(temp_buffer);
				/* FREE THIS CONSTANT */
				if (previous_cinteger == current_cinteger) {
					// THE INTEGER BEING CLEARED IS THE FIRST INTEGER IN THE LIST
					cinteger_table = current_cinteger->next_cinteger;
					previous_cinteger = current_cinteger->next_cinteger;
					free(current_cinteger);
					current_cinteger = previous_cinteger;
				} else {
					previous_cinteger->next_cinteger = current_cinteger->next_cinteger;
					free(current_cinteger);
					current_cinteger = previous_cinteger->next_cinteger;
				}
			} else {
				previous_cinteger = current_cinteger;
				current_cinteger = current_cinteger->next_cinteger;
			}
		}
	}
	//printf("--- leaving clear integer\n");
}

void
add_cstring(name, value)
  char *name;
  char *value;
{
	/* ADD A STRING CONSTANT WITH THE SUPPLIED NAME AND VALUE */

	if ((new_string = (struct string_type *)
		 malloc(sizeof(struct string_type))) == NULL)
		outofmem();
	else {
		if (cstring_table == NULL) {
			cstring_table = new_string;
		} else {
			/* FIND LAST STRING IN LIST */	
			current_cstring = cstring_table;
			while (current_cstring->next_string != NULL) {
				current_cstring = current_cstring->next_string;
			}
			current_cstring->next_string = new_string;
		}
		strncpy(new_string->name, name, 40);
		new_string->name[40] = 0;
		strncpy(new_string->value, value, 255);
		new_string->value[255] = 0;
		new_string->next_string = NULL;
	}
}

void
clear_cstring(name)
  char *name;
{
    /* FREE CONSTANTS THAT HAVE SUPPLIED NAME*/
	if (cstring_table != NULL) {
		current_cstring = cstring_table;
		previous_cstring = cstring_table;
		while (current_cstring != NULL) {
			if (!strcmp(current_cstring->name, name)) {
				/* FREE THIS STRING */
				if (previous_cstring == current_cstring) {
					cstring_table = current_cstring->next_string;
					previous_cstring = current_cstring->next_string;
					free(current_cstring);
					current_cstring = previous_cstring;
				} else {
					previous_cstring->next_string = current_cstring->next_string;
					free(current_cstring);
					current_cstring = previous_cstring->next_string;
				}
			} else {
				previous_cstring = current_cstring;
				current_cstring = current_cstring->next_string;
			}
		}
	}
}

void
inspect (object_num) 
	int		object_num;
{
	// THIS FUNCTION DISPLAYS THE STATE OF A JACL OBJECT FOR DEBUGGING

	int index, attribute_value;

	struct attribute_type *pointer = attribute_table;

	if (object_num < 1 || object_num > objects) {
		badptrrun(word[1], object_num);
		return;
	}

	write_text("label: ");
	write_text(object[object_num]->label);

	if (object[object_num]->attributes & LOCATION) {
		// OUTPUT ALL THE ATTRIBUTES WITH LOCATION ATTRIBUTE TEXT
		write_text("^has location attributes: ");
		index = 0;
		attribute_value = 1;
		while (location_attributes[index] != NULL) {
			if (object[object_num]->attributes & attribute_value) {
				write_text(location_attributes[index]);
			}
			index++;
			attribute_value *= 2;
		}
	} else {
		// OUTPUT ALL THE ATTRIBUTES WITH OBJECT ATTRIBUTE TEXT
		write_text("^has object attributes: ");
		index = 0;
		attribute_value = 1;
		while (object_attributes[index] != NULL) {
			if (object[object_num]->attributes & attribute_value) {
				write_text(object_attributes[index]);
			}
			index++;
			attribute_value *= 2;
		}

		write_text("^has user attributes: ");
		attribute_value = 1;
	}

	if (pointer != NULL) {
		// THERE ARE USER ATTRIBUTES, SO CHECK IF THIS OBJECT OR LOCATION 
		// HAS ANY OF THEM
		do {
			if (object[object_num]->user_attributes & pointer->value) { 
				write_text(pointer->name);
				write_text(" ");
			}

			pointer = pointer->next_attribute;
		} while (pointer != NULL);
	}

	write_text("^");

	index = 0;
	if (object[object_num]->attributes & LOCATION) {
		while (location_elements[index] != NULL) {
			if (index < 12) {
				if (object[object_num]->integer[index] < 1 || object[object_num]->integer[index] > objects) {
					sprintf(temp_buffer, "%s: nowhere (%d)^", location_elements[index], object[object_num]->integer[index]);
				} else {
					sprintf(temp_buffer, "%s: %s (%d)^", location_elements[index], object[object[object_num]->integer[index]]->label, object[object_num]->integer[index]);
				}
			} else {
				sprintf(temp_buffer, "%s: %d^", location_elements[index], object[object_num]->integer[index]);
			}
			write_text(temp_buffer);
			index++;
		}
	} else {
		while (object_elements[index] != NULL) {
			if (index == 0) {
				sprintf(temp_buffer, "%s: %s (%d)^", object_elements[index], object[object[object_num]->integer[index]]->label, object[object_num]->integer[index]);
			} else {
				sprintf(temp_buffer, "%s: %d^", object_elements[index], object[object_num]->integer[index]);
			}
			write_text(temp_buffer);
			index++;
		}
	}
}

int
grand_of(child, objs_only)
     int             child,
                     objs_only;
{
    /* THIS FUNCTION WILL CLIMB THE OBJECT TREE STARTING AT 'CHILD' UNTIL
     * A 'PARENT' IS REACHED */
   
    /* objs_only ARGUMENT TELLS FUNCTION TO IGNORE OBJECT IF IT IS IN A
     * LOCATION */

    int             parent;

    if (object[child]->PARENT != NOWHERE) {
        /* STORE THE CHILDS PARENT OBJECT */
        parent = object[child]->PARENT;

        if (object[parent]->attributes & LOCATION) {
			if (objs_only) {
            	/* THE CHILDS PARENT IS LOCATION AND SEARCH IS RESTRICTED TO 
			 	 * OBJECTS */
            	return (child);
			} else {
				return (parent);
			}
        } else {
            /* KEEP LOOKING UP THE TREE UNTIL THE CHILD HAS NO
             * PARENT */
            return (grand_of(parent, objs_only));
        }
    } else {
        /* THE SPECIFIED OBJECT HAS NO PARENT */
        return (child);
    }
}

int
select_next()
{

	while (++*select_integer <= objects) {
		switch (criterion_type) {
			case CRI_ATTRIBUTE:
				if (object[*select_integer]->attributes & criterion_value) {
					if (!criterion_negate) {
						return TRUE;
					}
				} else {
					if (criterion_negate) {
						return TRUE;
					}
				}
				break;
			case CRI_USER_ATTRIBUTE:
				if (object[*select_integer]->user_attributes & criterion_value) {
					if (!criterion_negate) {
						return TRUE;
					}
				} else {
					if (criterion_negate) {
						return TRUE;
					}
				}
				break;
			case CRI_PARENT:
				if (object[*select_integer]->PARENT == criterion_value) {
					if (!criterion_negate) {
						return TRUE;
					}
				} else {
					if (criterion_negate) {
						return TRUE;
					}
				}
				break;
			case CRI_SCOPE:
				if (scope(*select_integer, scope_criterion)) {
					if (!criterion_negate) {
						return TRUE;
					}
				} else {
					if (criterion_negate) {
						return TRUE;
					}
				}
				break;
		}
	}

	return (FALSE);
}

/* Converts an integer value to its hex character*/
char to_hex(char code) {
  static char hex[] = "0123456789abcdef";
  return hex[code & 15];
}

/* Returns a url-encoded version of str */
/* IMPORTANT: be sure to free() the returned string after use */
char *url_encode(char *str) {
  char *pstr = str, *buf = malloc(strlen(str) * 3 + 1), *pbuf = buf;
  while (*pstr) {
    if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~') 
      *pbuf++ = *pstr;
    else if (*pstr == ' ') 
      *pbuf++ = '+';
    else 
      *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
    pstr++;
  }
  *pbuf = '\0';
  return buf;
}
