//
//      Compiler System
//

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#define ICODE_CACHE
#define FUDGE_CHARACTER -2	// This is for string munging
#define IFSTACK_SIZE 255	// I can't imagine code this complex
//#define LOG_FINDING		// Show all the variable finding for debugging

#include "opcodes.h"
#include "pe_api.hpp"
#include "../init.h"
#include "../ithelib.h"
#include "../console.h"
#include "../media.h"
#include "../sound.h"
#include "../textfile.h"

extern void CheckRefsPtr(void **o);

//char **pe_outputfuncnames;
static char buffer[2048];
static ICODE *icode=NULL;
static ICODE *iccache=NULL;
static KEYWORD *klist[255];
static char compiler_initlevel=0;
static char thinktype=0;
static char numcheckfatal=1;
static STRUCTURE *datatype; // band-aid for supporting multiple structs
int pe_numfuncs=0,pe_lineid=0;
int icode_len=0,srcline=-1;
OPCODE *vmp;
KEYWORD *labelpending=NULL;
char *curfunc=NULL; // Current function we're looking at (or NULL)
char *pe_parm=NULL;
char *pe_localfile=NULL;

static unsigned int pe_gptrs=0;		// List of globals for Garbage Collection
static void **pe_globalptrs;
static unsigned int pe_gints=0;		// List of integers to preserve
static void **pe_globalints;
OBJCODE *pe_output;

// variables global to the VM system
unsigned int pe_globals=0;
void **pe_global;

// Possible first characters for a keyword (optimisation)
static char _keylist[]="abcdefghijklmnopqrstuvwxyz_";


int start_icode();
int finish_icode();
void output_icode();

static void diagnose_keyword(char *name, char type);
ICODE *add_byte(unsigned char byte);
void add_opcode(unsigned int opcode);
ICODE *add_dword(int word);
ICODE *add_number(int word);
void add_string(char *string);
KEYWORD *add_keyword(char *name, char type, char *func);
KEYWORD *add_symbol_ptr(char *name, char type, void *value);
KEYWORD *add_symbol_val(char *name, char type, int value);
int get_keyword(char *name, char type, char *func);
KEYWORD *find_keyword(char *name, char type, char *func);
KEYWORD *find_keyword_global(char *name, char type, char *func);
KEYWORD *find_keyword_local(char *name, char type, char *func);
KEYWORD *find_datatype_global(char *name, char *func);
KEYWORD *find_datatype_local(char *name, char *func);
KEYWORD *find_variable_global(char *name, char *func);
KEYWORD *find_variable_local(char *name, char *func);

static int next_entry_same(int entry);
static int check_parm(char **line, unsigned int opcode);
static void find_fatalerror(char **line, int entry);
static char *diagnose_type(char t);
static void Purify();
static int wipe_keywords();
static int wipe_keywords_core(int list);
static int get_firstentry(char *ptr);
static int get_entry(char **line);
static ICODE *new_node();
static int fixup_locals();
static int count_locals();
static void fixup_jumps();
static int code_size();
static void find_orphans();
static int label_pos(KEYWORD *label);
static void show_labels();
static int ifstack[IFSTACK_SIZE];
static int if_sp=0;
static int pe_comment=0;

extern int pe_getnumber(char *r);        // Get a number (or constant)
int pe_isnumber(char *r);                // Is the string numeric?
char *pe_checkstruct(char *input);		// If it's not a valid struct say why
void pe_buildstruct(char *input, char type);
void PeDump(long lineno,char *error,char *help);
extern int count_quotes(char *line);
extern int srcline;

static struct TF_S script;
char *compilename="(sweet f-a)"; // You know what that stands for

extern void pe_predefined_symbols();
static void pe_resolve_globals();

//
//
// User-level API
//
//

// Start the compiler

void compiler_init()
{
if(compiler_initlevel)
	return;
compiler_initlevel=1;

//ilog_quiet("Last Opcode = %d (%x)\n",PEVM_LastOp,PEVM_LastOp);

datatype = NULL; // Against Demons

// Add these reserved symbols to the compiler

add_keyword("<>",'E',NULL);
add_keyword("!=",'E',NULL);
add_keyword("=",'e',NULL);
add_keyword("==",'e',NULL);
add_keyword("=",'=',NULL);

pe_predefined_symbols();
}

// open a file for compilation

void compiler_openfile(char *fname)
{
if(!fname)
	ithe_panic("compiler_openfile passed NULL filename",NULL);

TF_init(&script);
compilename=fname;
TF_load(&script,fname);
Purify();
TF_splitwords(&script,'#'); // Break into words for faster processing later
}

// pre-parser (get variable, function keywords, labels..)

int compiler_parse()
{
int lines,lineno,entry,words;
char **line;

//char ptr[2048];

pe_numfuncs=0;
pe_lineid=0;
pe_localfile=NULL;

lines = script.lines;
for(lineno=0;lineno<lines;lineno++)
	{
	pe_lineid++;
	srcline=lineno;
	words=script.linewords[lineno]; // Get no of words on this line
	line=script.lineword[lineno]; // pointer to line

	if(words == 0)
		continue; // skip blank line

	// Look for comment start
	if(strstr(line[0],"/*"))
		{
		pe_comment=1;
		continue;
		}

	if(pe_comment)
		{
		// Hack to deactivate comments
		if(strstr(line[0],"*/"))
			pe_comment=0;
		continue;
		}

/*
    quotes = count_quotes(line);
    if(quotes & 1)
        PeDump(lineno,"Missing \" in string",line);
*/

	entry = get_firstentry(line[0]); // No need for full syntax matching
	if(entry == -1)
		PeDump(lineno,"Unknown command",line[0]);
	if(vmspec[entry].parse)
		vmspec[entry].parse(line);
	}

if(pe_localfile != NULL)
	PeDump(lineno,"Missing endlocal statement in file",NULL);

return pe_numfuncs;
}

//      Compile a single file

void compiler_build(OBJCODE *pe)
{
int lines,lineno,entry,words;
char **line;

pe_output = pe;
pe_lineid=0;

pe_resolve_globals();	// only needs to be called once, but makes API cleaner

lines = script.lines;
for(lineno=0;lineno<lines;lineno++)
	{
	pe_lineid++;
	srcline=lineno;
	words=script.linewords[lineno]; // Get no of words on this line
	line=script.lineword[lineno]; // pointer to line

	if(words == 0)
		continue; // skip blank line

	if(strstr(line[0],"/*"))
		{
		pe_comment=1;
		continue;
		}
	if(pe_comment)
		{
		// Hack to deactivate comments
		if(strstr(line[0],"*/"))
			pe_comment=0;
		continue;
		}

	// Find the possible matches for this command and check them in turn
	if(PE_FastBuild)
		entry = get_firstentry(line[0]); // No need for full syntax matching
	else
		entry = get_entry(line);	// Full compilation
	if(entry == -1)
		PeDump(lineno,"Uh!",line[0]);

	// Safety check before we make the call

	pe_parm = vmspec[entry].parm;
	if(vmspec[entry].compile)
		vmspec[entry].compile(line);
	else
		PeDump(lineno,"Compiler error, command has no function",line[0]);
	}
}


// close a file that has been compiled or pre-parsed

void compiler_closefile()
{
TF_term(&script);
compilename="sweet f-a";
}

// Stop the compiler

void compiler_term()
{
wipe_keywords();
compiler_initlevel=0;
}


char *pe_get_function(unsigned int opcode)
{
int ctr;
char *msg;

for(ctr=0;vmspec[ctr].mnemonic;ctr++)
	if(vmspec[ctr].opcode == opcode)
		{
		msg = strLocalBuf();
		msg[0]=vmspec[ctr].operands;
		strcpy(&msg[1],vmspec[ctr].mnemonic);
		return msg;
		}

return "\0Invalid Opcode";
}


//
//
// Language developer API
//
//

// Prepare for code generation

int start_icode()
{
if(icode)       // Haven't finished!
    return 0;

#ifdef LOG_FINDING
ilog_quiet("Start function %s\n",curfunc);
#endif

return 1;
}

// Add a byte to the icode

ICODE *add_byte(unsigned char byte)
{
ICODE *i;
VMTYPE data;
// Format conversion
data.u8=byte;
i = new_node();

//i->len = sizeof(unsigned char);
i->len=sizeof(data);
i->data = (VMTYPE *)M_get(1,i->len);
memcpy(i->data,&data,i->len);
i->variable=0;
i->jump=0;

return i;
}

// Add a dword to the icode

ICODE *add_dword_raw(int word)
{
ICODE *i;
VMTYPE data;
// Format conversion
data.i32=word;

i = new_node();

i->len = sizeof(data);
i->data = (VMTYPE *)M_get(1,i->len);
memcpy(i->data,&data,i->len);
i->variable=0;
i->jump=0;
return i;
}

// Add a dword to the icode

ICODE *add_dword(int word)
{
ICODE *i;
VMTYPE data;
// Format conversion
data.i32=word;

add_byte(ACC_IMMEDIATE); // Immediate address mode

i = new_node();

i->len = sizeof(data);
i->data = (VMTYPE *)M_get(1,i->len);
memcpy(i->data,&data,i->len);
i->variable=0;
i->jump=0;
return i;
}

// Add an opcode to the icode

void add_opcode(unsigned int opcode)
{
ICODE *i;
VMTYPE data;
// Format conversion
data.u32=opcode;

i = new_node();

i->len = sizeof(data);
i->data = (VMTYPE *)M_get(1,i->len);
memcpy(i->data,&data,i->len);
i->variable=0;
i->jump=0;
if(labelpending)
	{
	i->label = labelpending;
	labelpending = NULL;
	}
}

// Add a raw integer to the icode

ICODE *add_number(int word)
{
ICODE *i;
VMTYPE data;
// Format conversion
data.i32=word;

add_byte(ACC_IMMEDIATE); // Immediate address mode

i = new_node();

i->len = sizeof(data);
i->data = (VMTYPE *)M_get(1,i->len);
memcpy(i->data,&data,i->len);
i->variable=0;
i->jump=0;

return i;
}

// add_keyword - store a new label, function or variable in the list

KEYWORD *add_keyword(char *name, char type, char *func)
{
KEYWORD *k;
KEYWORD **newnode;
int idno=0,len,kl;

// Get list representing character
kl=tolower(name[0]);

// Find the place for the new node

if(!klist[kl])
	newnode = &klist[kl]; // Add new
else
	{
	newnode = NULL;
	for(k=klist[kl];k;k=k->next)
		{
		if(type == k->type || type == '?' || k->type == '?')
			if(!stricmp(k->name,name))
				if(k->local == func)
					return NULL; // Duplicate entry!
		if(k->next == NULL)
			newnode = &k->next;
		else
			idno++;
		}
	}

// Make the new node in the linked list

len=strlen(name)+1;

*newnode = (KEYWORD *)M_get(1,sizeof(KEYWORD));
k=*newnode;
k->next = NULL;
k->name = (char *)M_get(1,len);
strcpy(k->name,name);
k->value = (void *)idno;
k->type = type;
if(func)
	{
	k->local = func;
	k->refcount=0; // Check for unused shit corrupting the localtab
	}
k->localfile=pe_localfile;
return k;
}


// add_symbol_ptr - Create a predefined keyword

KEYWORD *add_symbol_ptr(char *name, char type, void *value)
{
KEYWORD *k;

k = add_keyword(name,type,NULL);
if(k)
	{
	k->id = -1;
	k->value=value;
//	ilog_quiet("Adding symbol %s which points to %x\n",name,value);
	}

return k;
}

// add_symbol_val - Create a predefined keyword

KEYWORD *add_symbol_val(char *name, char type, int value)
{
KEYWORD *k;

k = add_keyword(name,type,NULL);
if(k)
	{
	k->id = -1;
	k->value=(void *)value;
	}

return k;
}

// Add a string to the icode

void add_string(char *string)
{
ICODE *i;
int vstrlen; // virtual string length
int divstring;

// Get string length
vstrlen=strlen(string)+1;

// Now find out if it is divisible by a word and pad it if necessary
divstring = vstrlen/sizeof(VMTYPE);
if(vstrlen & (sizeof(VMTYPE)-1))
	{
	divstring++;
	vstrlen=divstring*sizeof(VMTYPE);
	}


add_byte(ACC_IMMSTR); // Immediate addressing mode
add_number(vstrlen); // Write padded string length
i = new_node();
i->len = vstrlen;
i->data = (VMTYPE *)M_get(1,i->len);
memcpy(i->data,string,strlen(string)+1);  // Copy unpadded string
i->variable=0;
i->jump=0;
}

// Reserve a variable

void add_variable(char *name)
{
ICODE *i;
KEYWORD *k_local,*k_global;
VMTYPE data;

add_byte(ACC_INDIRECT); // Indirect addressing mode

i = new_node();
i->jump=0;

// Find the keyword for this variable, so we can extract it's ID number,
// and store that in the i-code for fixup later on

k_local = find_variable_local(name,curfunc);
k_global = find_variable_global(name,curfunc);
//k_global = find_variable_global(name,NULL); // NULL will cause Bad Things

// This probably means that the symbol exists but in another scope
if(!k_local && !k_global)
	PeDump(srcline,"AV: Undefined symbol",name);

// Make adjustments for local/global type

if(k_local)
	{
	i->variable = k_local->id+1;
	data.ptr = k_local->value;
	k_local->refcount++;
	}
else
	{
	data.ptr = k_global->value;
	i->variable=0; // This is 'not a variable'
	}


// Ok, store the data

i->len = sizeof(data);
i->data = (VMTYPE *)M_get(1,i->len);
memcpy(i->data,&data,i->len);
}

// Reserve a member variable

void add_member(char *name, char type)
{
ICODE *i,*acc;
KEYWORD *k_local,*k_global;
char temp[1024];
char *ptr,isarray;
VMTYPE data,d;

acc = add_byte(ACC_MEMBER); // Member addressing mode
isarray=0;

// Find the keyword for this variable, so we can extract it's ID number,
// and store that in the i-code for fixup later on

k_local = NULL;

/*
 * Find the base object
 */

strcpy(temp,name);

ptr = strchr(temp,'.');
if(!ptr)
	ithe_panic("Parser fault in add_member",name);
*ptr=0;

// Is it an array?

ptr = strchr(temp,'[');
if(ptr)
	{
	*ptr=0;
	isarray=1;
	}

k_local = find_datatype_local(temp,curfunc);
k_global = find_datatype_global(temp,curfunc);

// Panic if nothing found

if(!k_local && !k_global)
	PeDump(srcline,"AM: Undefined symbol",temp);

// Otherwise, make some adjustments and store it

if(isarray)
	{
	// Set some flags up
	d.u8 = ACC_MEMBER | ACC_ARRAY;
	*acc->data=d; // Poke them in
	add_array(name,1);
	pe_buildstruct(name,type);
	return;
	}


i = new_node();
i->jump=0;

if(k_local)
	{
	i->variable = k_local->id+1;
	data.ptr = k_local->value;
	k_local->refcount++;
	}
else
	{
	data.ptr = k_global->value;
	i->variable=0; // This is 'not a variable'
	}

// Ok, store the data

i->len = sizeof(data);
i->data = (VMTYPE *)M_get(1,i->len);
memcpy(i->data,&data,i->len);

pe_buildstruct(name,type);
}


// Reserve a member variable

void add_intmember(char *name)
{
add_member(name,'i');
}

// Reserve a member variable

void add_objmember(char *name)
{
add_member(name,'o');
}

// Reserve a member variable

void add_strmember(char *name)
{
add_member(name,'s');
}

// Reserve an array element access

void add_array(char *name, char member)
{
ICODE *i;
KEYWORD *k_local,*k_global,*var;
VMTYPE data;
char temp[1024];
char *ptr;
char *idx;

if(!member)
	add_byte(ACC_ARRAY); // Array addressing mode if needed

i = new_node();
i->jump=0;

// Find the keyword for this variable, so we can extract it's ID number,
// and store that in the i-code for fixup later on

k_local = NULL;

// Find the base object

strcpy(temp,name);
ptr = strchr(temp,'[');
if(!ptr)
	ithe_panic("Parser fault in add_array: missing '['",name);
*ptr=0;

// Find the index

ptr++; // This must be the index of the array

idx = ptr;
ptr = strrchr(idx,']');
if(!ptr)
	ithe_panic("Parser fault in add_array: missing ']'",name);
*ptr=0;


k_local = find_keyword_local(temp,'?',curfunc);
k_global = find_keyword_global(temp,'?',curfunc);

// Panic if nothing found

if(!k_local && !k_global)
//	PeDump(srcline,"Can't find this array:",temp);
    PeDump(srcline,"Gollocks",temp);

// Otherwise, make some adjustments and store it

if(k_local)
	{
	i->variable = k_local->id+1;
	data.ptr = k_local->value;
//	ilog_quiet("%s: localval = %x, id = %d\n",k_local->name,k_local->value,k_local->id);
	k_local->refcount++;
	i->arraysize= k_local->arraysize;
	}
else
	{
	data.ptr = k_global->value;
//	ilog_quiet("Array base %x\n",data);
	i->variable=0; // This is 'not a variable'
	i->arraysize= k_global->arraysize;
	}

// Ok, store the base object

i->len = sizeof(data);
i->data = (VMTYPE *)M_get(1,i->len);
memcpy(i->data,&data,i->len);

/*
if(k_local)
	ilog_quiet("Writing %x for local array %s\n",&data,k_local->name);
else
	ilog_quiet("Writing %x for global array %s\n",&data,k_global->name);
*/

add_dword(i->arraysize); // Write the size of the array into the i-code

// Now we need to build the index data

// Is it a number?
if(pe_isnumber(idx))
	{
	add_number(atoi(idx));
	return;
	}

// Okay, assume it's a variable of some kind

k_local = find_keyword_local(idx,'?',curfunc);
k_global = find_keyword_global(idx,'?',curfunc);

// Panic if nothing found

if(!k_local && !k_global)
    PeDump(srcline,"gollocks",name);

// Otherwise, make some adjustments and store it

if(k_local)
	var = k_local;
else
	var = k_global;

switch(var->type)
	{
	case 'i':
	add_variable(var->name);
	break;

	case 'I':
	add_member(var->name,'i');
	break;

	case 'o':
	add_variable(var->name);
	break;

	case 'O':
	add_member(var->name,'o');
	break;

	case 'P':
	add_variable(var->name);
	break;

//	case 's':					// Shouldn't happen..?
//	add_variable(var->name);
//	break;

	case 'S':
	add_member(var->name,'s');
	break;

	// Oh wonderful
	case 'a':
	add_array(var->name,0);
	break;

	default:
	ilog_quiet("Unsupported datatype '%c'\n",var->type);
	PeDump(srcline,"Unsupported format: check bootlog and email me",var->name);
	break;
	};

//	ilog_quiet("added %s of type '%c'\n",var->name,var->type);

}


// Debug array if errors found

char *pe_checkarray(char *name)
{
KEYWORD *k_local,*k_global,*var;
char temp[1024];
char *ptr;
char *idx;
char *msg;

msg = strLocalBuf();

k_local = NULL;

// Find the base object

strcpy(temp,name);
ptr = strchr(temp,'[');
if(!ptr)
	{
	strcpy(msg,"Array is missing start bracket '['");
	return msg;
	}
*ptr=0;

// Find the index

ptr++; // This must be the index of the array

idx = ptr;
ptr = strrchr(idx,']');
if(!ptr)
	{
	strcpy(msg,"Array is missing end bracket ']'");
	return msg;
	}
*ptr=0;


k_local = find_datatype_local(temp,curfunc);
k_global = find_datatype_global(temp,curfunc);

// Panic if nothing found

if(!k_local && !k_global)
	{
	sprintf(msg,"Can't find base variable '%s' in array",temp);
	return msg;
	}

// Is it a number?
if(!pe_isnumber(idx))
	{
	sprintf(msg,"index '%s' in array is not a number",idx);
	return msg;
	}

// Okay, assume it's a variable of some kind

k_local = find_datatype_local(idx,curfunc);
k_global = find_datatype_global(idx,curfunc);

// Panic if nothing found

if(!k_local && !k_global)
	{
	sprintf(msg,"Can't understand array index '[%s]' in array",idx);
	return msg;
	}

if(k_local)
	var = k_local;
else
	var = k_global;

switch(var->type)
	{
	case 'i':
	case 'I':
	case 'o':
	case 'O':
	case 'P':
	case 'S':
	case 'a':
	; // Do Nothing
	break;

	default:
	sprintf(msg,"Array index is unsupported type '%s'\n",diagnose_type(var->type));
	return msg;
	break;
	};

return NULL;
}



// Reserve a jump (has to be patched later)

void add_jump(char *name)
{
ICODE *i;
KEYWORD *kptr;
VMTYPE data;

add_byte(ACC_JUMP);

i = new_node();
i->variable=0;

// Find the keyword for the appropriate label, so we can extract it's ID
// and store that in the i-code for fixup later on

kptr = find_keyword(name,'l',curfunc);
if(!kptr)
    PeDump(srcline,"Label not known",name);

data.i32 = 0; // No data yet
i->jump = 1;
i->label = kptr; // This is the label for future fixups

i->len = sizeof(data);
i->data = (VMTYPE *)M_get(1,i->len);
memcpy(i->data,&data,i->len);
}

// Get the opcode for a math operator

void add_operator(char *name)
{
KEYWORD *kptr;
unsigned char byte;

// Find the keyword for the appropriate symbol, so we can extract it's ID
kptr = find_keyword(name,'p',curfunc);
if(!kptr)
    PeDump(srcline,"operator not known",name);

byte = (*(unsigned char*)(&kptr->value)) | ACC_OPERATOR; // Add the high bit to mark it as an operator
add_byte(byte);
}


// extract the string from the quotes casing it

void extract_string(char *buf, char *line)
{
char *p;

p = strchr(line,'\"');
if(!p)
    PeDump(srcline,"Missing first \" in string",line);

strcpy(buf,p+1);
p = strchr(buf,'\"');
if(!p)
    PeDump(srcline,"Missing ending \" in string",line);
*p=0;
}

// pe_getnumber - Get the value of a number or a constant

int pe_getnumber(char *number)
{
KEYWORD *k;
int hex;

// Look for a constant
k = find_keyword(number,'n',curfunc);
if(k)
	return((unsigned int)k->value);

// Is it hexadecimal?
if(number[0] == '0')
	if(number[1] == 'x')
		{
		sscanf(&number[2],"%x",&hex);
		return hex;
		}

// Try decimal number
return(atoi(number));
}

// pe_isnumber - is the string a number?

int pe_isnumber(char *thing)
{
char *p,ok;
KEYWORD *k;
// List of valid characters for hexadecimal
char validhex[]="0123456789abcdefABCDEF";

if(!thing)
	return 0; // No it bloody well isn't!

thinktype=0;

// Is is a constant?
k = find_keyword(thing,'n',curfunc);
if(k)
	{
	thinktype='n';
	return(1);
	}
k = find_keyword_global(thing,'n',NULL);
if(k)
	PeDump(srcline,"Local constant used outside scope",thing);

// Is it a hexadecimal number?
if(thing[0] == '0')
	if(thing[1] == 'x')
		{
		ok=1;
		// Try each character of the word and see if it is valid
		for(p=&thing[2];*p;p++)
			if(!strchr(validhex,*p))
				ok=0;
		if(ok)
			{
			thinktype='n';
			return 1;
			}
		}

// Is it a valid number?
ok=1;
for(p=thing;*p;p++)
	if((*p<'0'||*p>'9')&&*p!='-'&&*p!='+')
		ok=0;

if(ok)
	{
	thinktype='n';
	return 1;
	}

// Is it a keyword of any form or kind at all?

k = find_keyword_global(thing,'?',NULL);
if(k)
	{
	thinktype=k->type;
	return 0;
	}

// Is it a string?

if(count_quotes(thing) == 2)
	{
	thinktype='s';
	return 0;
	}

// Is it a structure access?

if(strchr(thing,'.'))
	{
	thinktype='.';
	return 0;
	}

// Is it an array access?

if(strchr(thing,'['))
	{
	thinktype=']';
	return 0;
	}

// Is it a table?

if(getnum4table_slow(thing) != -1)
	{
	thinktype='T';
	return 0;
	}

// It must be a typo then

if(numcheckfatal)
	PeDump(srcline,"IN: Undefined symbol",thing);
return 0;
}

// check for valid structure member

char *pe_checkstruct(char *input)
{
char temp[1024];
char base[1024];
char term[1024];
char *msg,*ptr;
int ctr,ok;
STRUCTURE *mystruct;
KEYWORD *k;

msg = strLocalBuf();

strcpy(temp,input);
if(!strchr(temp,'.'))
	{
	strcpy(msg,"Missing '.' or silly input");
	return msg;
	}

ptr=strchr(temp,'.');
*ptr=0; // Remove the separator
strcpy(base,temp);
strcpy(temp,&ptr[1]);

// If it's an array, get the base object

ptr = strchr(temp,'[');
if(ptr)
	*ptr=0;
ptr = strchr(base,'[');
if(ptr)
	*ptr=0;

// Make sure the variable which is being used as a struct is one

k = find_datatype_local(base,curfunc);
if(!k)
	{
	k = find_datatype_global(base,NULL);
	if(!k)
		{
		ilog_quiet("(%s).(%s) of (%s)\n",base,temp,input);
        sprintf(msg,"%s is being used as a structure",base);
		return msg;
		}
	}

mystruct=datatype;

do	{
	strcpy(term,temp);
	ptr=strchr(term,'.');
	if(ptr)
		*ptr=0;

	ok=0;
	for(ctr=1;mystruct[ctr].name;ctr++)
		{
		if(!stricmp(mystruct[ctr].name,term))
			{
			if(mystruct[ctr].type == '>')
				{
				if(strchr(temp,'.'))	// Check we're not the terminus
					{
					ok=1;
					mystruct=(STRUCTURE *)mystruct[ctr].newspec;
					break;
					}
				else	// Agh!  The user is accessing a struct as an integer
					return NULL; // They might be doing a NULL pointer check
				}
			return NULL;
			}
		}

	if(!ok)
		{
		sprintf(msg,"Unrecognised member '%s' of type '%s'",term,mystruct[0].name);
		return msg;
		}

	ptr=strchr(temp,'.');
	if(ptr)
		*ptr=0; // Remove the separator
	strcpy(temp,&ptr[1]);
	} while(ptr);

PeDump(srcline,"Gollocks!",input);

return NULL;
}

// Check the availability of the specified type for given keyword

int pe_checkstruct_type(char *input, char type)
{
char temp[1024];
char term[1024];

char *ptr;
int ctr,ok;
STRUCTURE *mystruct;

strcpy(temp,input);
if(!strchr(temp,'.'))
	ithe_panic("Parser fault at <1> in pe_checkstruct_type",input);

ptr=strchr(temp,'.');
*ptr=0; // Remove the separator
strcpy(temp,&ptr[1]);

mystruct=datatype;

do	{
	strcpy(term,temp);
	ptr=strchr(term,'.');
	if(ptr)
		*ptr=0;
	ok=0;
	for(ctr=1;mystruct[ctr].name;ctr++)
		{
		if(!stricmp(mystruct[ctr].name,term))
			{
			if(mystruct[ctr].type == type)
				return 1; // Found exact match
			if(mystruct[ctr].type == '>')
				if(strchr(temp,'.'))	// Check we're not the terminus
					{
					ok=1;
					mystruct=(STRUCTURE *)mystruct[ctr].newspec;
					break;
					}
			}
		}

	if(!ok)
		return 0;

	ptr=strchr(temp,'.');
	if(ptr)
		*ptr=0; // Remove the separator
	strcpy(temp,&ptr[1]);
	} while(ptr);

return 0;
}


// Build the structure dereference chain

void pe_buildstruct(char *input, char type)
{
char temp[1024];
char term[1024];
unsigned char refs;
ICODE *no;
STRUCTURE *mystruct;
VMTYPE d;

char *ptr;
int ctr,ok;


//ilog_quiet("[%s] '%c'\n",input,type);

mystruct = datatype; // Hack for supporting multiple struct formats

strcpy(temp,input);
if(!strchr(temp,'.'))
	ithe_panic("Parser fault at <1> in pe_buildstruct",input);

ptr=strchr(temp,'.');
*ptr=0; // Remove the separator
strcpy(temp,&ptr[1]);

no = add_byte(0); // Reserve space for the number of references

refs=0;

do	{
	strcpy(term,temp);
	ptr=strchr(term,'.');
	if(ptr)
		*ptr=0;
//	ilog_quiet("(%s)\n",term);
	ok=0;
	for(ctr=1;mystruct[ctr].name;ctr++)
		{
		if(!stricmp(mystruct[ctr].name,term))
			{
			if(mystruct[ctr].type == type)
				{
				if(!strchr(temp,'.'))	// Check we are the terminus
					{
					// Calculate offset and store it
					add_dword_raw((char *)mystruct[ctr].position - (char *)mystruct[0].position);
					if(refs>254)
						ithe_panic("Structure nested too deep",input);
					d=*no->data; // Get refs
					d.u8=++refs; // update refs
					*no->data=d;	// Poke it back in
					return;
					}
				}
			if(mystruct[ctr].type == '>')
				if(strchr(temp,'.'))	// Check we're not the terminus
					{
					ok=1;
					// Calculate offset and store it
					add_dword_raw((char *)mystruct[ctr].position - (char *)mystruct[0].position);
					refs++;
					if(refs>254)
						ithe_panic("Structure nested too deep",input);
					d=*no->data; // Get refs
					d.u8=refs; // update refs
					*no->data=d;	// Poke it back in
//					*(char *)no->data=refs;	// Update refs counter
					// Enter the new sub-structure
					mystruct=(STRUCTURE *)mystruct[ctr].newspec;
					break;
					}
			}
		}

	if(!ok)
		{
		ilog_quiet("input = '%s'\n",input);
		ilog_quiet("temp='%s' term='%s'\n",temp,term);
//		ithe_panic("Parser fault at <2> in pe_buildstruct",input);
		PeDump(srcline,"Member not known for this structure",term);
		}

	ptr=strchr(temp,'.');
	if(ptr)
		{
		*ptr=0; // Remove the separator
		strcpy(temp,&ptr[1]);
		}
	} while(ptr);

ithe_panic("Parser fault at <3> in pe_buildstruct",input);

return;
}

// return the ID of a keyword

int get_keyword(char *name, char type, char *func)
{
KEYWORD *k;
int idno=0;
char kl;

kl=tolower(name[0]);

if(!klist[kl])
	{
//	ilog_quiet("Nothing for '%c'\n",tolower(name[0]));
	return -1;
	}

for(k=klist[kl];k;k=k->next)
	{
	if(k->local == func || k->local == NULL)
		if(type == k->type || type == '?' || k->type == '?')
			if(!stricmp(k->name,name))
				return idno; // Found it!
	idno++;
	}

return -1;
}


// return the pointer to a keyword, local or global

KEYWORD *find_keyword(char *name, char type, char *func)
{
KEYWORD *k;
char kl;

kl=tolower(name[0]);

if(!klist[kl])
	return NULL;

for(k=klist[kl];k;k=k->next)
	if(type == k->type || type == '?' || k->type == '?')
		{
		// Check locals first..
		if(k->local == func)
			if(k->localfile == compilename || k->localfile == NULL)
				if(!stricmp(k->name,name))
					return k; // Found it!


		// Then look for a global that's local to this file

		if(k->local == NULL)
			{
			if(k->localfile == compilename)
				if(!stricmp(k->name,name))
					return k; // Found it!

			// Now search everywhere

			if(k->localfile == NULL)
				if(!stricmp(k->name,name))
					return k; // Found it!
			}
		}
// Oh well

return NULL;
}

// return the pointer to a keyword local to a specific function

KEYWORD *find_keyword_local(char *name, char type, char *func)
{
KEYWORD *k;
char kl;

if(!func)
	return NULL;

kl=tolower(name[0]);

if(!klist[kl])
	return NULL;

// Check locals first

for(k=klist[kl];k;k=k->next)
	if(type == k->type || type == '?' || k->type == '?')
		if(k->local == func)
			if(k->localfile == compilename || k->localfile == NULL)
				if(!stricmp(k->name,name))
					return k; // Found it!

// Oh well

return NULL;
}

// find the pointer to a keyword regardless of scope

KEYWORD *find_keyword_global(char *name, char type, char *func)
{
KEYWORD *k;
char kl;

kl=tolower(name[0]);

if(!klist[kl])
	return NULL;

// Look in this file alone first

for(k=klist[kl];k;k=k->next)
	if(type == k->type || type == '?' || k->type == '?')
		if(k->local == func || k->local == NULL || func == NULL)
			if(k->localfile == compilename)
				if(!stricmp(k->name,name))
					return k; // Found it!

// Now look in all files

for(k=klist[kl];k;k=k->next)
	if(type == k->type || type == '?' || k->type == '?')
		if(k->local == func || k->local == NULL || func == NULL)
			if(k->localfile==NULL)
				if(!stricmp(k->name,name))
					return k; // Found it!

return NULL;
}


// return the pointer to a keyword local to a specific function

KEYWORD *find_datatype_local(char *name, char *func)
{
KEYWORD *k;
int ctr;

for(ctr=0;pe_datatypes[ctr];ctr++)
	{
	k = find_keyword_local(name, pe_datatypes[ctr]->type,func);
	if(k)
		{
		datatype=pe_datatypes[ctr];
		return k;
		}
	}
datatype=NULL;
return NULL;
}

KEYWORD *find_datatype_global(char *name, char *func)
{
KEYWORD *k;
int ctr;

for(ctr=0;pe_datatypes[ctr];ctr++)
	{
	k = find_keyword_global(name, pe_datatypes[ctr]->type,func);
	if(k)
		{
		datatype=pe_datatypes[ctr];
		return k;
		}
	}
datatype=NULL;
return NULL;
}

KEYWORD *find_variable_local(char *name, char *func)
{
KEYWORD *k;
int ctr;

for(ctr=0;pe_vartypes[ctr];ctr++)
	{
	k = find_keyword_local(name, pe_vartypes[ctr],func);
	if(k)
		return k;
	}
return NULL;
}

KEYWORD *find_variable_global(char *name, char *func)
{
KEYWORD *k;
int ctr;

for(ctr=0;pe_vartypes[ctr];ctr++)
	{
	k = find_keyword_global(name, pe_vartypes[ctr],func);
	if(k)
		return k;
	}
return NULL;
}


// output the generated code to pe_outputcode array

void output_icode()
{
ICODE *i;
VMTYPE *p,*p1;
VMTYPE d;
int icode_data,patches,locals;

// Calculate the length of the finished code

icode_len = code_size();

// Data starts after code finishes
icode_data=icode_len;

find_orphans(); // Abort if local variables are declared and never used

locals = count_locals(); // Get number of variable slots we'll need

patches = fixup_locals();
if(patches)
	{
	icode_len = code_size(); // Recalculate after data added
	// Reserve space for Dofus opcode, and 2 parameters
	icode_len+=(sizeof(VMTYPE)*3);
	icode_data+=(sizeof(VMTYPE)*3);
	icode_len+=(locals * sizeof(VMTYPE *)); // Allocate the slots
	}

fixup_jumps();

if(!icode_len)
	ithe_panic(curfunc,"No data!");

// Allocate space for the code+data
p = (VMTYPE *)M_get(1,icode_len);

pe_output->code = p;
pe_output->size = icode_len;
pe_output->codelen = icode_data;

// Insert the DOFUS linker into the output code if it is needed

if(patches)
	{
//	ilog_quiet("Building DOOFUS in %s: %d patches from %x onwards\n",compilename,patches,icode_data);
	// Insert Dofus opcode 
	d.u32=PEVM_Dofus;
	*p++=d;

	// Insert Dofus counter
	d.i32=patches;
	*p++=d;

	// Insert Dofus offset
	d.i32=icode_data;
	*p++=d;
	}

p1 = p;	// For debugging if necessary

// Copy the icode to the pointer

for(i=icode;i;i=i->next)
	{
	memcpy(p,i->data,i->len);
	p=(VMTYPE *)((char *)p+i->len); // advance pointer in bytes
//	p+=i->len;
//	ilog_quiet("%04x: [%x] (%d bytes long)\n",(unsigned)p-(unsigned)p1,*(int *)i->data,i->len);
	}
pe_output++;
}

// Delete the code after generation and output

int finish_icode()
{
ICODE *i,*in;

if(!icode)      // Haven't started!
	return 0;

i = icode;      // Traverse the list, erasing as you go

do {
	in = i->next;
	M_free(i->data);
	i->next = NULL;
	M_free(i);
	i = in;
	} while(i); // When i=NULL we've finished

icode = NULL;
iccache = NULL;
return 1;
}

//
//
// Internal System (not for external use)
//
//


//  Create a new node in the icode list

ICODE *new_node()
{
ICODE *i;
ICODE **newnode;

// Find the place for the new node

if(!icode)
	{
	newnode = (ICODE **)&icode;
	iccache=NULL;
	}
else
	{
#ifdef ICODE_CACHE
	if(!iccache)
		{
#endif
		newnode = NULL;
		for(i=icode;i;i=i->next)
			if(i->next == NULL)
				{
				newnode = &i->next;
				iccache = (ICODE *)newnode;
				}
#ifdef ICODE_CACHE
		}
	else
		newnode=&iccache->next;
#endif
	}


if(!newnode)
	ithe_panic("I have lost the unobtainable",NULL);

// Make the new node in the linked list

*newnode = (ICODE *)M_get(1,sizeof(ICODE));
i=*newnode;
i->next = NULL;
iccache=(ICODE *)i;
return i;
}

// Get the first instance of a keyword in the language definition

int get_firstentry(char *ptr)
{
int ctr;
for(ctr=0;vmspec[ctr].mnemonic;ctr++)
    if(!stricmp(vmspec[ctr].mnemonic,ptr))
        {
        vmp = &vmspec[ctr];
        return ctr;
        }
return -1;
}

// Find out if the next keyword is the same (with different parameters)

int next_entry_same(int entry)
{
// Is the next definition the end marker?
if(vmspec[entry+1].mnemonic == NULL)
    return 0;
// Is the next definition for the same name?
if(stricmp(vmspec[entry].mnemonic,vmspec[entry+1].mnemonic))
    return 0;
return 1;
}

// Get a keyword in the language definition and return the best fit

int get_entry(char **line)
{
int firstentry,lastentry,entry,len,ctr;
char *buf,buffer[1024];

// Find first matching entry, or die trying

firstentry = get_firstentry(line[0]);
if(firstentry == -1)
	PeDump(srcline,"Unknown command",line[0]);

// Now look for the last matching entry
for(lastentry=firstentry;next_entry_same(lastentry);lastentry++);

// Now find the best fit

for(entry = firstentry;entry<=lastentry;entry++)
	{
//	ilog_quiet("CheckEntry '%s' trying(%s)\n",line[1],vmspec[entry].parm);
	if(check_parm(line,entry))
		{
		vmp = &vmspec[entry];
//		ilog_quiet("Matched\n");
		return entry;
		}
	}

// No matches, crash out

if(firstentry == lastentry)
	find_fatalerror(line,entry-1);

ilog_quiet("No match found for '%s' at line %d\n",line[0],srcline);
ilog_quiet("Possible candidates:\n");
for(entry = firstentry;entry<=lastentry;entry++)
	{
	len=strlen(vmspec[entry].parm);
	ilog_quiet("    %s ",vmspec[entry].mnemonic);
	for(ctr=0;ctr<len;ctr++)
		ilog_quiet("%s ",diagnose_type(vmspec[entry].parm[ctr]));
	ilog_quiet("\n");
	}

ilog_quiet("\nGuessing type for input:\n");
numcheckfatal=0;
ilog_quiet("%s ",line[1]);
for(ctr=0;ctr<16;ctr++)
	{
	buf=line[ctr+1];
	if(buf != NULL)
		{
		strcpy(buffer,buf);
		strstrip(buffer);
		pe_isnumber(buffer);
		if(thinktype != 0)
			ilog_quiet("%s ",diagnose_type(thinktype));
		else
			ilog_quiet("??? ");
		}
	}

// Do it again, and see if we can deduce reason for failure

for(ctr=0;ctr<16;ctr++)
	{
	buf=line[ctr+1];
	if(buf != NULL)
		{
		strcpy(buffer,buf);
		strstrip(buffer);
		if(!pe_isnumber(buffer))
			if(thinktype != 0)
				diagnose_keyword(buffer,thinktype);
		}
	}

PeDump(srcline,"No match","Check bootlog.txt or details");
return -1;
}

// Check the syntax of a line, return 0 if error or doesn't match definition

int check_parm(char **line, unsigned int entry)
{
int no,len,ctr,ok;
char *errbuf;

len=strlen(vmspec[entry].parm);
for(ctr=0;line[ctr];ctr++);
no=ctr-1;

// Wrong number of parameters

if(len != no)
    {
//    printf("<%s/%s>\n",line,vmspec[entry].parm);
    return 0;
    }

// Is there something we don't understand.. an invalid keyword?

for(ctr=0;ctr<len;ctr++)
    {
    strcpy(buffer,line[ctr+1]);
    strstrip(buffer);

    // side effect of this is to locate undefined symbol, but we mustn't if
    // the symbol is the wildcard '?'.
    if(vmspec[entry].parm[ctr] != '?')
        pe_isnumber(buffer);
    }

// Ok, check the parameters

for(ctr=0;ctr<len;ctr++)
    {
    strcpy(buffer,line[ctr+1]);
    strstrip(buffer);
//	ilog_quiet("trying %s as a %c\n",buffer,vmspec[entry].parm[ctr]);

    switch(vmspec[entry].parm[ctr])
        {
        case 'T':
		if(getnum4table_slow(buffer) == -1)
            return 0;
        break;

        case 'o':
		ok=0;
        if(get_keyword(buffer,'o',NULL) != -1)
			ok=1;
        if(get_keyword(buffer,'o',curfunc) != -1)
			ok=1;
		if(!ok)
			return 0;
        break;

        case 't':
		ok=0;
        if(get_keyword(buffer,'t',NULL) != -1)
			ok=1;
        if(get_keyword(buffer,'t',curfunc) != -1)
			ok=1;
		if(!ok)
            {
            return 0;
            }
        break;

        case 'P':
		ok=0;
        if(get_keyword(buffer,'s',NULL) != -1)
			ok=1;
        if(get_keyword(buffer,'s',curfunc) != -1)
			ok=1;
		if(!ok)
            {
            return 0;
            }
        break;

        case 's':
        if(count_quotes(buffer) == 0)
            {
            return 0;
            }
        break;

        case 'n':
        if(!pe_isnumber(buffer))
            {
            return 0;
            }
        break;

        case 'f':
		ok=0;
        if(get_keyword(buffer,'f',NULL) != -1)
			ok=1;
        if(get_keyword(buffer,'f',curfunc) != -1)
			ok=1;
		if(!ok)
            {
            return 0;
            }
        break;

        case 'i':
		ok=0;
        if(get_keyword(buffer,'i',NULL) != -1)
			ok=1;
        if(get_keyword(buffer,'i',curfunc) != -1)
			ok=1;
		if(!ok)
            {
            return 0;
            }
        break;

        case 'l':
//        if(get_keyword(buffer,'?',curfunc)) // Duplication
//            return 0;
        break;

        case 'p':
		if(get_keyword(buffer,'p',NULL) == -1)
            {
            return 0;
            }
        break;

		case '=':
		case 'e':
		if(get_keyword(buffer,'e',NULL) == -1)
			{
			return 0;
			}
		break;

		case 'E':
		if(get_keyword(buffer,'E',NULL) == -1)
			{
			return 0;
			}
		break;

        case 'I':
        case 'O':
        case 'S':
		// It claims to be a member of a structure, so look for '.'
		if(!strchr(buffer,'.'))
            {
            return 0;
            }

		errbuf = pe_checkstruct(buffer);
		//  Quit if we get this kind of error
		if(errbuf)
			{
			PeDump(srcline,errbuf,NULL);
			}
		//  Make sure it's the right type
		if(!pe_checkstruct_type(buffer,tolower(vmspec[entry].parm[ctr])))
			{
			datatype=NULL; // prevent subtle errors
			return 0;
			}
		datatype=NULL;
/*
		// OK, make sure the base structure is known
		strcpy(temp,buffer);
		ptr=strchr(buffer,'.');
		*ptr=0; // Kill the . and get the base pointer

        if(get_keyword(temp,'o',NULL) == -1)
            {
            return 0;
            }
*/
        break;

        case 'a':
        case 'b':
        case 'c':
		// It claims to be an array.  Look for the brackets
		if(!strchr(buffer,'['))
            {
            return 0;
            }
		if(!strchr(buffer,']'))
            {
            return 0;
            }
        break;

        case '0':
        if(stricmp(buffer,"null") && stricmp(buffer,"nil"))
            {
            return 0;
            }
		break;

        case '?':
        break;

        default:
        ilog_quiet("Failed on parameter type '%c' from '%s'\n",vmspec[entry].parm[ctr],vmspec[entry].parm);
        PeDump(srcline,"Internal error, unknown parameter type for command",buffer);
        break;
        }
    }

// All OK!

return 1;
}

// Ok, we couldn't find a match or we had an error.  Explain it.

void diagnose_keyword(char *name, char type)
{
char *msg;
int ok;

// Display appropriate message

switch(type)
	{
	case 'T':
	if(getnum4table_slow(name) == -1)
		PeDump(srcline,"Could not find data table",name);
	break;

	case 'o':
	ok=0;
	if(get_keyword(name,'o',NULL) != -1)
		ok=1;
	if(get_keyword(name,'o',curfunc) != -1)
		ok=1;
	if(!ok)
		PeDump(srcline,"Could not find object",name);
	break;

	case 't':
	ok=0;
	if(get_keyword(name,'t',NULL) != -1)
		ok=1;
	if(get_keyword(name,'t',curfunc) != -1)
		ok=1;
	if(!ok)
		PeDump(srcline,"Could not find tile",name);
	break;

	case 'P':
	ok=0;
	if(get_keyword(name,'s',NULL) != -1)
		ok=1;
	if(get_keyword(name,'s',curfunc) != -1)
		ok=1;
	if(!ok)
		PeDump(srcline,"Could not find string variable",name);
	break;

	case 's':
	if(count_quotes(name) == 0)
		PeDump(srcline,"Quotes don't match up",name);
	break;

	case 'n':
	if(!pe_isnumber(name))
		PeDump(srcline,"Not a number",name);
	break;

	case 'f':
	ok=0;
	if(get_keyword(name,'f',NULL) != -1)
		ok=1;
	if(get_keyword(name,'f',curfunc) != -1)
		ok=1;
	if(!ok)
		PeDump(srcline,"Could not find function",name);
	break;

	case 'i':
	ok=0;
	if(get_keyword(name,'i',NULL) != -1)
	ok=1;
	if(get_keyword(name,'i',curfunc) != -1)
		ok=1;
	if(!ok)
		PeDump(srcline,"Could not find integer variable",name);
	break;

	case 'l':
	ok=0;
	if(get_keyword(name,'l',NULL) != -1)
		ok=1;
	if(get_keyword(name,'l',curfunc) != -1)
		ok=1;
	if(!ok)
		PeDump(srcline,"Could not find label",name);
	break;

	case 'p':
	if(get_keyword(name,'p',NULL) == -1)
		PeDump(srcline,"Unknown operator [should be +,-,/,*,==,!=,<,> etc]",name);
	break;

	case '=':
	case 'e':
	if(get_keyword(name,'e',NULL) == -1)
		PeDump(srcline,"Inappropriate symbol [should be '=']",name);
	break;

	case 'E':
	if(get_keyword(name,'E',NULL) == -1)
		PeDump(srcline,"Inappropriate symbol [should be '!=' or '<>']",name);
	break;

	case 'I':
	case 'O':
	case 'S':
	msg=pe_checkstruct(name);
	datatype=NULL; // prevent subtle errors
	if(msg)
		PeDump(srcline,msg,name);
//		PeDump(srcline,"Unknown error in structure access",name);
	break;

	case 'a':
	case 'b':
	case 'c':
	msg=pe_checkarray(name);
	if(msg)
		PeDump(srcline,msg,name);
	break;

	case '0':
	if(stricmp(buffer,"null") && stricmp(buffer,"nil"))
		PeDump(srcline,"Inappropriate symbol, should have been 'NULL'",name);
	break;

	case '?':
	//PeDump(srcline,"Undefined symbol [type 'any']",name);
	break;

	default:
	ilog_quiet("Failed on parameter type '%c' for keyword '%s'\n",type,name);
	PeDump(srcline,"Internal error, unknown parameter type for command",buffer);
	break;
	}

return;
}

// Return a string describing each of the syntax codes from the lang. def.

char *diagnose_type(char type)
{
switch(type)
    {
    case 'T':
	return "<table>";
	break;

    case 's':
    return "<string>";
    break;

    case 'P':
    return "<string variable>";
    break;

    case 'n':
    return "<number>";
    break;

    case 'f':
    return "<function>";
    break;

    case 'o':
    return "<object>";
    break;

    case 't':
    return "<tile>";
    break;

    case 'i':
    return "<variable>";
    break;

    case 'I':
    return "<object.integer>";
    break;

    case 'O':
    return "<object.object>";
    break;

    case 'S':
    return "<object.string>";
    break;

    case 'a':
    return "<integer_array[member]>";
    break;

    case 'b':
    return "<string_array[member]>";
    break;

    case 'c':
    return "<object_array[member]>";
    break;

    case 'l':
    return "<user label>";
    break;

    case 'p':
    return "<expression>";
    break;

    case 'e':
    return "==";
    break;

    case 'E':
    return "!=";
    break;

    case '0':
    return "NULL";
    break;

    case '=':
    return "=";
    break;

    case '.':
    return "<structure access>";
    break;

    case ']':
    return "<array access>";
    break;

    default:
    return "<unknown>";
    break;
    }

return "AIEEE!";
}

// Analyse a line and return help for the error

void find_fatalerror(char **line, int entry)
{
int no,len,ctr,ok;
char msg[128];
char *ptr;

len=strlen(vmspec[entry].parm);
for(ctr=0;line[ctr];ctr++);
no=ctr-1;

// If we don't have the right no of parameters, halt the compilation

if(len != no)
    {
    sprintf(msg,"Expected %d but found %d\n",len,no);
    PeDump(srcline,"Wrong number of parameters",msg);
    }

// If we can't match the parameters, halt the compilation

for(ctr=0;ctr<len;ctr++)
    {
    strcpy(buffer,line[ctr+1]);
    strstrip(buffer);

    switch(vmspec[entry].parm[ctr])
        {
		case 'T':
		if(getnum4table_slow(buffer) == -1)
            {
            PeDump(srcline,"Undefined table",buffer);
            }
		break;

        case 'o':
		ok=0;
        if(get_keyword(buffer,'o',NULL) != -1)
			ok=1;
        if(get_keyword(buffer,'o',curfunc) != -1)
			ok=1;
		if(!ok)
            {
            PeDump(srcline,"Undefined object",buffer);
            }
        break;

        case 't':
		ok=0;
        if(get_keyword(buffer,'t',NULL) != -1)
			ok=1;
        if(get_keyword(buffer,'t',curfunc) != -1)
			ok=1;
		if(!ok)
            {
            PeDump(srcline,"Undefined tile",buffer);
            }
        break;

        case 'P':
		ok=0;
        if(get_keyword(buffer,'s',NULL) != -1)
			ok=1;
        if(get_keyword(buffer,'s',curfunc) != -1)
			ok=1;
		if(!ok)
            {
            PeDump(srcline,"Undefined string",buffer);
            }
        break;

        case 's':
        if(count_quotes(buffer) == 0)
             {
             PeDump(srcline,"Not a valid string",buffer);
             }
        break;

        case 'n':
        if(!pe_isnumber(buffer))
            {
            sprintf(msg,"Parameter %d should be a number\n",(len-ctr));
            PeDump(srcline,msg,buffer); // Oh no, we're through
            }
        break;

        case 'f':
		ok=0;
        if(get_keyword(buffer,'f',NULL) != -1)
			ok=1;
        if(get_keyword(buffer,'f',curfunc) != -1)
			ok=1;
		if(!ok)
            {
            PeDump(srcline,"Undefined function",buffer);
            }
        break;

        case 'i':
		ok=0;
        if(get_keyword(buffer,'i',NULL) != -1)
			ok=1;
        if(get_keyword(buffer,'i',curfunc) != -1)
			ok=1;
		if(!ok)
            {
            PeDump(srcline,"Undefined integer variable",buffer);
            }
        break;

        case 'l':
//        if(get_keyword(buffer,'?',curfunc) == -1)
//            {
//            unfix_quotes(line);
//            PeDump(srcline,"Label name already used",buffer);
//            }
        break;

        case 'I':
        case 'O':
        case 'S':
		ptr=pe_checkstruct(buffer);
		if(ptr)
            {
            PeDump(srcline,ptr,buffer);
            }
		if(!pe_checkstruct_type(buffer,tolower(vmspec[entry].parm[ctr])))
			PeDump(srcline,"No match for this member",buffer);
		datatype=NULL; // prevent subtle errors
        break;

        case 'a':
        case 'b':
        case 'c':
		ptr=pe_checkarray(buffer);
		if(ptr)
            {
            PeDump(srcline,ptr,buffer);
            }
//		if(!pe_checkstruct_type(buffer,tolower(vmspec[entry].parm[ctr])))
//			PeDump(srcline,"No match for this member",buffer);
        break;

        case 'p':
        if(get_keyword(buffer,'p',NULL) == -1)
            {
            PeDump(srcline,"Missing operator (+,-,/,*,<,>,==,!= etc) in expression",buffer);
            }
        break;

        case '=':
        if(stricmp(buffer,"="))
            {
            PeDump(srcline,"Missing = in expression",buffer);
            }
        break;

        case '0':
        if(stricmp(buffer,"null") && stricmp(buffer,"nil"))
            {
            PeDump(srcline,"Missing NULL in expression",buffer);
            }
        break;

        case '?':
        break;

        default:
        PeDump(srcline,"Internal error in find_fatal, unknown parameter type",buffer);
        break;
        }
    }

PeDump(srcline,"Transient fatal error detected",buffer);
}

// wipe_keywords

int wipe_keywords()
{
int ctr;
for(ctr=0;ctr<255;ctr++)
	wipe_keywords_core(ctr);
return 1;
}

static int wipe_keywords_core(int list)
{
KEYWORD *k,*kn;
int ctr;

if(!klist[list])      // Haven't started!
	return 0;

k = klist[list];      // Traverse the list, erasing as you go

ctr=0;
do {
	kn = k->next;
//#define SHOW_KEYWORDS
#ifdef SHOW_KEYWORDS
	printf("[%d=\"%s\"]\n",ctr++,k->name);
#endif
	M_free(k->name);
	k->next = NULL;
	k->local = NULL;
	M_free(k);
	k = kn;
	} while(k); // When k=NULL we've finished

klist[list] = NULL;
return 1;
}

// Resolve the global variables to pointers

void pe_resolve_globals()
{
KEYWORD *k;
static char didglobals=0;
int gptr,iptr,ctr,lc,kctr;

if(!pe_globals)
	return;
if(didglobals)
	return;
didglobals=1;

// Allocate space for the globals to live in.
//ilog_quiet("Allocating space for %d globals\n",pe_globals);
pe_global = (void **)M_get(pe_globals, sizeof(void *));

/*
 Find out how many are pointers that must be subject to Garbage Collection
 This is only for variables that can be dynamically allocated in the
 script, currently only Objects ('o') have this capability
 */

pe_gptrs=0;
pe_gints=0;
for(kctr=0;_keylist[kctr];kctr++)
	{
	lc=_keylist[kctr];
	for(k=klist[lc];k;k=k->next)
		if(k->local == NULL)
			switch(k->type)
				{
				case 'i':
				if(k->id != -1) // Is it a System variable?  If so it doesn't count
					pe_gints++;
				break;

				case 'o':
				if(k->id != -1) // Is it a System variable?  If so it doesn't count
					pe_gptrs++;
				break;

				case 'a':
				if(k->id != -1)
					{
//					ilog_quiet("Adding %d array members to the list\n",k->arraysize);
					pe_gints+=k->arraysize; // Add the whole array to the list
					}

				case 'c':
				if(k->id != -1)
					{
//					ilog_quiet("Adding %d array members to the list\n",k->arraysize);
					pe_gptrs+=k->arraysize; // Add the whole array to the list
					}
				break;

				default:
				break;
				}
	}
// Allocate the list of globals for garbage collection (at least one)
pe_globalptrs = (void **)M_get(pe_gptrs+1,sizeof(void *));
pe_globalints = (void **)M_get(pe_gints+1,sizeof(void *));
gptr=0;
iptr=0;

for(kctr=0;_keylist[kctr];kctr++)
	{
	lc=_keylist[kctr];
	for(k=klist[lc];k;k=k->next)
		{
		if(k->local == NULL)
			switch(k->type)
				{
				case 'i':
				case 'o':
				case 't':
				case 's':
				case 'b':
				case 'c':

				// Copy the current default value to the globals array
				// Then change the value so it points to the global instead

				if(k->id != -1)	// -1 is for system variables, pre-resolved
					{
					if(k->arraysize>0)
						{
						for(ctr=0;ctr<k->arraysize;ctr++)
							{
							pe_global[k->id+ctr] = NULL;//(void *)k->value;
							}
						k->value = (void *)&pe_global[k->id]; // Point to [0]
						}
					else
						{
//						ilog_quiet("fixup %s = id:%d\n",k->name,k->id);
						pe_global[k->id] = (void *)k->value;
						k->value = (void *)&pe_global[k->id];
						}
					}

				// Register the pointer for Garbage Collection
				if(k->type == 'o' && k->id != -1)
					pe_globalptrs[gptr++]=(void *)&pe_global[k->id];

				// Register the int for logging
				if(k->type == 'i' && k->id != -1)
					pe_globalints[iptr++]=(void *)&pe_global[k->id];

				// Register the array for GC
				if(k->type == 'c' && k->id != -1)
					{
//					ilog_quiet("Array at %x\n",&pe_global[k->id]);
					for(ctr=0;ctr<k->arraysize;ctr++)
						pe_globalptrs[gptr++]=(void *)&pe_global[k->id+ctr];
					}

				// Register the array for logging
				if(k->type == 'a' && k->id != -1)
					{
//					ilog_quiet("Array at %x\n",&pe_global[k->id]);
					for(ctr=0;ctr<k->arraysize;ctr++)
						pe_globalints[iptr++]=(void *)&pe_global[k->id+ctr];
					}

				break;

				default:
//				ilog_quiet("Not adding %s: type %c\n",k->name,k->type);
				break;
				};
		}
	}
}

void wipe_refs()
{
int ctr;
for(ctr=0;ctr<pe_gptrs;ctr++)
	CheckRefsPtr((void **)pe_globalptrs[ctr]);
}

void GetGlobalPtrs(void ***ptr, unsigned int *num)
{
*ptr = pe_globalptrs;
*num = pe_gptrs;
}

void GetGlobalInts(void ***ptr, unsigned int *num)
{
*ptr = pe_globalints;
*num = pe_gints;
}


// Return number of local variables

int count_locals()
{
KEYWORD *k;
int locals=0,lc,ctr;

for(ctr=0;_keylist[ctr];ctr++)
	{
	lc=_keylist[ctr];
	for(k=klist[lc];k;k=k->next)
		if(k->local == curfunc)
			switch(k->type)
				{
				case 'i':
				case 'o':
				case 't':
				case 's':
				locals++;
				break;

				case 'a':
				case 'b':
				case 'c':
				locals+=k->arraysize; // An array is lots of variables
				break;

				default:
				break;
				};
	}
return locals;
}

// Fix up the local variables

int fixup_locals()
{
ICODE *i,*in;
int pos,locals;

locals = count_locals();
if(!locals)
    return 0;

i=icode;
pos = 0;

locals=0;

if(i)
	do {
		in=i->next; // Store next in list (because it might change!)
		if(i->variable)
			{
			add_dword_raw(pos);            // Store code offset to patch
			add_dword_raw(i->variable-1);  // Followed by which slot to use
			locals++;
/*
			if(i->arraysize)
				{
				ilog_quiet("ARP: pos = %x\n",pos);
				}
*/
//			pos+=(i->arraysize*sizeof(int)); // Skip length of array
			}
		pos+=i->len; // Update position counter
		i=in;  // Next, please
		} while(i);
return locals;
}

// Fix up the jumps: gotos, if statements etc..

void fixup_jumps()
{
ICODE *i;
int lpos;
VMTYPE d;

for(i=icode;i;i=i->next)
	if(i->jump)
		{
		lpos = label_pos(i->label);
		if(lpos == -1)
			{
			ilog_quiet("Cannot find label 0x%x\n",i->label);
			ilog_quiet("It is called %s\n",i->label->name);
			show_labels();
			PeDump(srcline,"Cannot find label",i->label->name);
			}
		// Poke in new position
		d.i32=lpos;
		*i->data = d;
		}
return;
}

// FInd size of code and data

int code_size()
{
ICODE *i;
int pos;

pos = 0;
for(i=icode;i;i=i->next)
	pos+=i->len;

return pos;
}

// Find i-code offset for a jump label

int label_pos(KEYWORD *label)
{
ICODE *i;
int pos;

pos = 0;
if(count_locals())
	pos=(sizeof(unsigned int)*3);  // Take the 'DOFUS' linker into account

for(i=icode;i;i=i->next)
	{
	if(!i->jump) // We need the label itself not any jumps to it
		{
		if(i->label == label)
			return pos;
		}
	pos+=i->len;
	}

return -1;
}

// Debug the label engine

void show_labels()
{
ICODE *i;

for(i=icode;i;i=i->next)
	if(i->label)
		ilog_quiet("> %s (%c)\n",i->label->name,i->jump?'J':' ');

return;
}


// Find orphaned local variables

void find_orphans()
{
char msg[128];
KEYWORD *k;
int lc,ctr;

for(ctr=0;_keylist[ctr];ctr++)
	{
	lc=_keylist[ctr];
	for(k=klist[lc];k;k=k->next)
		if(k->local == curfunc)
			if(k->type != 'l' && k->type != 'n')
				if(!k->refcount)
					{
					sprintf(msg,"Local variable '%s' defined but never used",k->name);
					PeDump(srcline,msg,NULL);
					}
	}
return;
}

//
//
// Generic support functions
//
//


// Return the number of quotes.  If odd, there's a problem

int count_quotes(char *line)
{
int quotes;
char *p;

quotes=0;
for(p=line;*p;p++)
	if(*p=='\"')
		quotes++;

return quotes;
}

/*
 *      PeDump - Crash out and show a fragment of the code
 */

void PeDump(long lineno,char *error,char *help)
{
char errbuf[1024];
char errstr[1024];
short ctr;
long l2,Lctr;

ilog_printf("\n\n");
irecon_colour(0,200,0); // Green
sprintf(errstr,"Error in %s at line %ld:\n",compilename,lineno+1);
ilog_printf(errstr);
ilog_printf("%s\n",error);

l2=lineno+5;
if(l2>script.lines)
	l2=script.lines;
irecon_colour(200,0,0); // Red
for(Lctr=lineno;Lctr<l2;Lctr++)
	if(script.line[Lctr]!=NOTHING&&script.line[Lctr]!=NULL)
		{
		strcpy(errbuf,"");
		for(ctr=0;ctr<script.linewords[Lctr];ctr++)
			{
			strcat(errbuf,script.lineword[Lctr][ctr]);
			strcat(errbuf," ");
			}

	sprintf(errstr,"%6ld: %s\n",Lctr+1,errbuf);
	ilog_printf(errstr);
	}
irecon_colour(0,200,0); // Green
if(help)
	{
	sprintf(errstr,"\n%s\n",help);
	ilog_printf(errstr);
	}

S_Term(); // Shut down sound system.. may take a while

ilog_printf("Press a key to exit\n");
WaitForAscii();
//KillGFX();
exit(1);
}

/*
 *      Purify - Remove comments from the script to ease compilation
 */

void Purify()
{
int ctr,len,pos;
char *ptr;

// Scan each line for comments

for(ctr=0;ctr<script.lines;ctr++)
	{
	ptr = strchr(script.line[ctr],'%');         // Look for a %
	if(ptr)
		{
		switch(*(ptr+1))
			{
			case 'd':
			case 's':
			case 'i':
			case 'u':
			case 'l':
			case 'f':
			case 'x':
			*ptr='#';
			break;

			default:
			break;
			};
		}

	// Look for CR/LF codes and neutralise them
	// This probably isn't strictly necessary

	ptr = strchr(script.line[ctr],13);
	if(ptr)
		*ptr=' ';
	ptr = strchr(script.line[ctr],10);
	if(ptr)
		*ptr=' ';
	}

// Scan each line for whitespace and turn it into space

for(ctr=0;ctr<script.lines;ctr++)
	{
	len = strlen(script.line[ctr]);    // Got length of line
	for(pos=0;pos<len;pos++)
		if(isxspace(script.line[ctr][pos]))
			script.line[ctr][pos] = ' ';    // Whitespace becomes space
	strstrip(script.line[ctr]);
	}

return;
}

void mk_lineno(char *str,int line,char *end)
{
sprintf(str,"@@%d@@%s",line,end);
}

void push_ifstack(int id)
{
if(if_sp == IFSTACK_SIZE)
    PeDump(srcline,"Too many nested IF statements",NULL);

ifstack[++if_sp]=id;
}

void pop_ifstack()
{
if(if_sp == 0)
    PeDump(srcline,"Internal error: IF stack underflow",NULL);

ifstack[if_sp]=0;
if_sp--;
}

int read_ifstack()
{
return ifstack[if_sp];
}

int explore_ifstack(int offset)
{
if(if_sp-offset < 0)
	return -1;
return ifstack[if_sp-offset];
}
