/*
....[@@@..[@@@..............[@.................. MUD++ is a written from
....[@..[@..[@..[@..[@..[@@@@@....[@......[@.... scratch multi-user swords and
....[@..[@..[@..[@..[@..[@..[@..[@@@@@..[@@@@@.. sorcery game written in C++.
....[@......[@..[@..[@..[@..[@....[@......[@.... This server is an ongoing
....[@......[@..[@@@@@..[@@@@@.................. development project. All
................................................ contributions are welcome.
....Copyright(C).1995.Melvin.Smith.............. Enjoy.
------------------------------------------------------------------------------
Melvin Smith (aka Fusion) msmith@hom.net
MUD++ development mailing list mudpp@van.ml.org
------------------------------------------------------------------------------
asmparser.cc
*/
#include "config.h"
#include "string.h"
#include "io.h"
#include "vmopcodes.h"
#include "asmobjfile.h"
#include "asmparser.h"
#include "erratum.h"
struct _parser_table AsmParser::parser_table[] =
{
{"aadd", VMOPC_AADD , &AsmParser::paramless, NULL },
{"abort", VMOPC_ABORT, &AsmParser::paramless, NULL },
{"add", VMOPC_ADD , &AsmParser::paramless, NULL },
{"alen", VMOPC_ALEN , &AsmParser::paramless, NULL },
{"apop", VMOPC_APOP , &AsmParser::paramless, NULL },
{"apush", VMOPC_APUSH , &AsmParser::paramless, NULL },
{"call", VMOPC_CALL, &AsmParser::read_unknown_call, NULL },
{"cast2f", VMOPC_CAST2F, &AsmParser::paramless, NULL },
{"cast2i", VMOPC_CAST2I, &AsmParser::paramless, NULL },
{"checkcast", VMOPC_CHECKCAST, &AsmParser::paramless, NULL},
{"clone", VMOPC_CLONE , &AsmParser::paramless, NULL },
{"dec", VMOPC_DEC , &AsmParser::paramless, NULL },
{"decl", VMOPC_DECL , &AsmParser::read_local_var, NULL },
{"div", VMOPC_DIV , &AsmParser::paramless, NULL },
{"exit", VMOPC_EXIT , &AsmParser::read_u8, NULL },
{"fcall", VMOPC_FCALL , &AsmParser::read_fun_call, NULL },
{"fcmp", VMOPC_FCMP, &AsmParser::read_cond_jump, NULL },
{"feval", VMOPC_FEVAL,&AsmParser::read_cond_jump, NULL },
{"getfield", VMOPC_GETFIELD,&AsmParser::read_field, NULL },
{"icall", VMOPC_ICALL , &AsmParser::read_interface_call, NULL },
{"icmp", VMOPC_ICMP, &AsmParser::read_cond_jump, NULL },
{"ieval", VMOPC_IEVAL,&AsmParser::read_cond_jump, NULL },
{"ilookupswitch", VMOPC_ILOOKUPSWITCH,&AsmParser::read_ilookupswitch, NULL },
{"inc", VMOPC_INC , &AsmParser::paramless, NULL },
{"incl", VMOPC_INCL , &AsmParser::read_local_var, NULL },
{"initl", VMOPC_INITL , &AsmParser::read_local_var, NULL },
{"initp", VMOPC_INITP , &AsmParser::read_local_var, NULL },
{"isnull", VMOPC_ISNULL, &AsmParser::read_cond_jump, NULL },
{"itableswitch", VMOPC_ITABLESWITCH,&AsmParser::read_itableswitch, NULL },
{"jmp", VMOPC_JMP , &AsmParser::read_jump, NULL },
{"licmp", VMOPC_LICMP, &AsmParser::read_cond_local_var_jump, NULL },
{"lieval", VMOPC_LIEVAL,&AsmParser::read_cond_local_var_jump, NULL },
{"mod", VMOPC_MOD , &AsmParser::paramless, NULL },
{"movc", VMOPC_MOVI , &AsmParser::read_local_var_constant, NULL },
{"movf", VMOPC_MOVI , &AsmParser::read_local_var_float, NULL },
{"movi", VMOPC_MOVI , &AsmParser::read_local_var_int, NULL },
{"mpop", VMOPC_MPOP , &AsmParser::read_u8, NULL },
{"mul", VMOPC_MUL , &AsmParser::paramless, NULL },
{"nop", VMOPC_NOP , &AsmParser::paramless, NULL },
{"ocmp", VMOPC_OCMP, &AsmParser::read_cond_jump, NULL },
{"pop", VMOPC_POP , &AsmParser::paramless, NULL },
{"popl", VMOPC_POPL , &AsmParser::read_local_var, NULL },
{"popstat", VMOPC_POPSTAT, &AsmParser::read_static, NULL },
{"pushc", VMOPC_PUSHC , &AsmParser::read_const, NULL },
{"pushf", VMOPC_PUSHF , &AsmParser::read_float, NULL },
{"pushfz", VMOPC_PUSHFZ, &AsmParser::paramless, NULL },
{"pushi", VMOPC_PUSHI, &AsmParser::read_s32, NULL },
{"pushiz", VMOPC_PUSHIZ, &AsmParser::paramless, NULL },
{"pushl", VMOPC_PUSHL, &AsmParser::read_local_var, NULL },
{"pushstat",VMOPC_PUSHSTAT, &AsmParser::read_static, NULL },
{"pushthis",VMOPC_PUSHTHIS, &AsmParser::paramless, NULL },
{"reducel", VMOPC_REDUCEL, &AsmParser::read_u16, NULL },
{"ret", VMOPC_RET , &AsmParser::paramless, NULL },
{"scharat", VMOPC_SCHARAT, &AsmParser::paramless, NULL },
{"scharatl", VMOPC_SCHARAT, &AsmParser::read_local_var, NULL },
{"scmp", VMOPC_SCMP, &AsmParser::read_cond_jump, NULL },
{"sconcat", VMOPC_SCONCAT , &AsmParser::paramless, NULL },
{"setfield", VMOPC_SETFIELD , &AsmParser::read_field, NULL },
{"sfloatcat", VMOPC_SFLOATCAT , &AsmParser::paramless, NULL },
{"sgetarg", VMOPC_SGETARG, &AsmParser::paramless, NULL },
{"sgetargrest", VMOPC_SGETARGREST, &AsmParser::paramless, NULL },
{"sintcat", VMOPC_SINTCAT , &AsmParser::paramless, NULL },
{"sisempty", VMOPC_SISEMPTY, &AsmParser::read_cond_jump, NULL },
{"sisnumber", VMOPC_SISNUMBER, &AsmParser::read_cond_jump, NULL },
{"sleep", VMOPC_SLEEP, &AsmParser::paramless, NULL },
{"slookupswitch", VMOPC_SLOOKUPSWITCH, &AsmParser::read_slookupswitch, NULL },
{"spushempty", VMOPC_SPUSHEMPTY, &AsmParser::paramless, NULL },
{"sstartargs", VMOPC_SSTARTARGS, &AsmParser::paramless, NULL },
{"stoint", VMOPC_STOINT, &AsmParser::paramless, NULL },
{"sub", VMOPC_SUB , &AsmParser::paramless, NULL },
{"swap", VMOPC_SWAP , &AsmParser::paramless, NULL },
{"upcast", VMOPC_UPCAST, &AsmParser::read_classname, NULL },
{ NULL, VMOPC_ABORT, &AsmParser::paramless, NULL }
};
const int parse_table_size = (sizeof( AsmParser::parser_table ) / sizeof( _parser_table ))-1;
struct _parser_table * lookupOpcodeTable(char * mnem)
{
/*
int i;
for ( i =0; AsmParser::parser_table[i].name; i++)
{
if ( !strcmp(mnem, AsmParser::parser_table[i].name) )
return &AsmParser::parser_table[i];
}
*/
// Binary search
int cmp, k, l,p;
l = 0;
p = parse_table_size;
while ( l <= p )
{
k = (l+p) >> 1;
cmp = strcmp( mnem, AsmParser::parser_table[k].name );
if ( cmp < 0 )
p = k-1;
else if ( cmp > 0 )
l = k+1;
else
return &AsmParser::parser_table[k];
}
return NULL;
}
const u16 ADRESS_UNKNOWN = 0xffff;
int AsmParser::parse( StaticInput & infr, Output & outfr )
{
obj = new AsmObjFile();
char buf[BUF];
int asmerr = 0;
int cnumber;
infun = false;
label * lab;
String str;
struct _parser_table * tbl;
inf = &infr;
outf = &outfr;
memorycell cell;
while ( true )
{
inf->getword(buf);
// Support for comments
if( buf[0] == '#' )
{
inf->skipline();
continue;
}
else if ( buf[0] == '!' )
{
// CONTROL COMMANDS
if ( !strcmp(buf, "!A") ) // Argument definition
{
if ( !infun )
{
inf->error("AsmParser: '!A' outside of fun");
asmerr = -1;
break;
}
readVariable();
args_count++;
}
else if ( !strcmp(buf, "!L") ) // Local variable definition
{
if ( !infun )
{
inf->error("AsmParser: '!L' outside of fun");
asmerr = -1;
break;
}
readVariable();
vars_count++;
}
else if ( !strcmp(buf, "!funstart") )
{
if (infun)
{
inf->error( "AsmParser: '!funstart' inside another fun." );
asmerr = -1;
break;
}
inf->getword(buf);
infun = true;
funstart = obj->getOffset();
strcpy(funname, buf);
cell.vmint = 0;
obj->putCell(cell); // for INITP
obj->putCell(cell); // for INITL
args_count = 0;
vars_count = 0;
}
else if ( !strcmp( buf, "!funend") )
{
int i;
int reduced_length;
if (!infun)
{
Cout << "AsmParser: '!funend' without matching funstart" <<endl;
asmerr = -1;
break;
}
infun = false;
// check if all labels are resolved
for_each( labels, lab)
{
if ( lab->adress == ADRESS_UNKNOWN )
{
str <<"AsmParser:: fun ended but there is at least" <<
" one unresolved label: " << lab->name;
inf->error(str);
asmerr = -1;
break;
}
}
if ( vars_count && args_count )
{
cell.vmint =0;
cell.s.opcode = VMOPC_INITP;
cell.s.u.number = args_count;
obj->setCell(funstart, cell);
cell.s.opcode = VMOPC_INITL;
cell.s.u.number = vars_count;
obj->setCell(funstart+1, cell);
reduced_length = 0;
}
else if ( !vars_count )
{
cell.vmint =0;
cell.s.opcode = VMOPC_INITP;
cell.s.u.number = args_count;
obj->setCell(funstart+1, cell);
reduced_length = -1;
}
else if ( !args_count )
{
cell.vmint =0;
cell.s.opcode = VMOPC_INITL;
cell.s.u.number = vars_count;
obj->setCell(funstart+1, cell);
reduced_length = -1;
}
else // both arguments an locals missing
{
reduced_length = -2;
}
if ( reduced_length )
{
for( i = shortjumps.length() - 1; i >=0; i-- )
{
/* Cout << "Relocated cell " << shortjumps[i] <<
" by " << reduced_length << " in fun " <<
funname << endl;*/
obj->relocCell(shortjumps[i]+ funstart, reduced_length );
}
}
// reduced_length <= 0 so need -= instead of +=
funstart -= reduced_length;
// it has to be before fun entry - AsmLoader 'feature'
cnumber = obj->addConstant( VMT_CONST_STRING, funname,
strlen(funname) + 1 );
vmfilefun vmff;
vmff.start = funstart;
vmff.end = obj->getOffset();
vmff.name = cnumber;
obj->addConstant( VMT_EXPORT_FUN, &vmff, sizeof(vmfilefun) );
clearLabels();
locals.clr();
}
else if ( !strcmp(buf, "!constant" ) )
{
if (readConstant() < 0)
{
asmerr = -1;
break;
}
}
else if ( !strcmp(buf, "!end" ) )
{
if ( infun )
{
inf->error("AsmParser: '!end' occured inside of fun.");
asmerr = -1;
break;
}
asmerr = 0;
break;
}
else
{
str << "AsmParser: unrecognized command " << buf;
inf->error(str);
asmerr = -1;
break;
}
}
else if ( buf[0] == ':' )
{
// LABEL
if ( !infun )
{
inf->error("AsmParser: Label outside function.");
asmerr = -1;
break;
}
setLabel(&buf[1], obj->getOffset() - funstart);
}
else
{
// MNEMONIC
tbl = lookupOpcodeTable(buf);
if (!tbl)
{
str<< "Asmparser: - unknown mnemonic " << buf;
inf->error(str);
asmerr = -1;
break;
}
// zero cell
cell.vmint = 0;
cell.s.opcode = tbl->opcode;
if (! (this->*(tbl->mnem2code)) (cell))
{
inf->error("Asmparser: error occured during mnemonic parsing");
asmerr = -1;
break;
}
}
}
if ( !asmerr )
obj->writeTo(*outf);
delete obj;
clearLabels();
locals.clr();
return (asmerr);
}
// Params: name - name of label we want jump to
// adress - point at which u16 absolute jump will be placed
// Return: absolute offset of label ( or ADRESS_UNKNOWN(0xffff)
// if unknown at time)
// absolute offsets are counted from start of function
u16 AsmParser::getLabel(char * name, int adress)
{
label * lab;
for_each(labels, lab)
{
if ( lab->name == name )
{
// Label already known ?
if ( lab->adress != ADRESS_UNKNOWN )
{
// Cout << "Set absolute jump " << name << " at " << (int) adress << " to " << (int) lab->adress <<endl;
shortjumps.add(adress);
return (lab->adress);
}
lab->to_resolve.add(adress);
shortjumps.add(adress);
return ADRESS_UNKNOWN;
}
}
lab = new label;
lab->name = name;
lab->adress = ADRESS_UNKNOWN;
lab->to_resolve.add(adress);
labels.add(lab);
shortjumps.add(adress);
return ADRESS_UNKNOWN;
}
// Params: name - name of label we want to define
// adress - place in code that label describes
void AsmParser::setLabel(char * name, int adress)
{
label * lab;
int i,size;
if ( adress == ADRESS_UNKNOWN )
Error::dump("ADRESS_UNKNOWN passed to AsmParser::setLabel");
for_each(labels, lab)
{
if (lab->name == name)
{
// later redefine to local error not global
if ( lab->adress != ADRESS_UNKNOWN )
Error::dump("Tried to redefine label at AsmParser::setLabel");
lab->adress = (u16)adress;
size = lab->to_resolve.length();
for( i=0; i < size; i++ )
{
// Cout << "Resolved one " << lab->name << " of " << size << endl;
setAbsoluteJump( lab->to_resolve[i], adress );
}
lab->to_resolve.clr();
return;
}
}
lab = new label;
lab->name = name;
lab->adress = (u16)adress;
labels.add(lab);
}
void AsmParser::clearLabels()
{
label * lab;
labels.reset();
while ( (lab = labels.remove()) != NULL )
{
delete lab;
}
shortjumps.clr();
}
/*
void AsmParser::setRelativeJump( int point, int to )
{
// Cout << "Set relative jump at " << (int) point << " to " << (int) to <<endl;
(*obj)[point].s.u.reloc = (s16) (to - point - 1);
}
*/
void AsmParser::setAbsoluteJump( int point, int to )
{
// Cout << "Set absolute jump at " << (int) point << " to " << (int) to <<endl;
(*obj)[point+funstart].s.u.number = (u16) (to);
}
int AsmParser::readConstant()
{
char buf[BUF];
int i;
inf->getword(buf);
if ( !strcmp(buf, "string") )
{
inf->getstring(buf);
return obj->addConstant(VMT_CONST_STRING, buf, strlen(buf)+1);
}
else if ( !strcmp(buf, "funcall") )
{
inf->getword(buf);
return obj->addConstant(VMT_IMPORT_FUN, buf, strlen(buf)+1);
}
else if ( !strcmp(buf, "interfacecall") )
{
inf->getword(buf);
return obj->addConstant(VMT_IMPORT_INTERFACE, buf, strlen(buf)+1);
}
else if ( !strcmp(buf, "constant" ) )
{
i = inf->getnum();
if ( i >= obj->numberOfConstants() )
{
inf->error("AsmParser::readConstant - constant keyword with undefined const.");
return -1;
}
return i;
}
else
{
inf->error("AsmParser::readConstant - only strings and imports supported for now.");
return -1;
}
}
void AsmParser::readVariable()
{
char buf[BUF];
inf->getword(buf); // type - ignored for now
inf->getword(buf); // name
locals.add(String(buf));
}
bool AsmParser::read_float(memorycell cell)
{
obj->putCell(cell);
obj->putFloat(inf->getfloat());
return true;
}
bool AsmParser::read_const(memorycell cell)
{
int i = readConstant();
if ( i < 0 )
return false;
cell.s.u.number = i;
obj->addRelocation();
obj->putCell(cell);
return true;
}
u8 AsmParser::parseConditions(const char * txt)
{
int i;
int len = strlen(txt);
u8 answer = 0;
for ( i=0; i < len; i++)
{
switch (txt[i])
{
case 'a':
answer = VMFLAG_EQUAL | VMFLAG_GREATER | VMFLAG_LESSER;
break;
case 'e':
answer |= VMFLAG_EQUAL;
break;
case 'g':
answer |= VMFLAG_GREATER;
break;
case 'l':
answer |= VMFLAG_LESSER;
break;
default:
answer = 255;
inf->error("AsmParser::parseConditions - unknown letter");
break;
}
}
return answer;
}
bool AsmParser::read_cond_jump(memorycell cell)
{
char buf[256];
inf->getword(buf);
cell.s.conds = parseConditions(buf);
inf->getword(buf);
cell.s.u.number = getLabel(buf, obj->getOffset() -funstart);
obj->putCell(cell);
return true;
}
bool AsmParser::read_jump(memorycell cell)
{
char buf[256];
inf->getword(buf);
cell.s.u.number = getLabel(buf, obj->getOffset() -funstart);
obj->putCell(cell);
return true;
}
bool AsmParser::read_fun_call(memorycell cell)
{
char buf[256];
inf->getword(buf);
obj->addRelocation();
cell.s.u.number = obj->addConstant( VMT_IMPORT_FUN, buf, strlen(buf) + 1);
obj->putCell(cell);
return true;
}
bool AsmParser::read_interface_call(memorycell cell)
{
char buf[256];
inf->getword(buf);
obj->addRelocation();
cell.s.u.number = obj->addConstant( VMT_IMPORT_INTERFACE, buf, strlen(buf) + 1);
obj->putCell(cell);
return true;
}
bool AsmParser::read_unknown_call(memorycell cell)
{
char buf[256];
inf->getword(buf);
obj->addRelocation();
cell.s.u.number = obj->addConstant( VMT_IMPORT_FUN_OR_INTERFACE, buf, strlen(buf) + 1);
obj->putCell(cell);
return true;
}
bool AsmParser::read_static( memorycell cell )
{
char buf[256];
inf->getword(buf);
obj->addRelocation();
cell.s.u.number = obj->addConstant( VMT_IMPORT_STATIC, buf, strlen(buf) +1);
obj->putCell(cell);
return true;
}
// Later add checks for boundaries
bool AsmParser::read_u8(memorycell cell)
{
cell.s.conds = inf->getnum();
obj->putCell(cell);
return true;
}
bool AsmParser::read_u16(memorycell cell)
{
cell.s.u.number = inf->getnum();
obj->putCell(cell);
return true;
}
bool AsmParser::read_s16(memorycell cell)
{
cell.s.u.reloc = inf->getnum();
obj->putCell(cell);
return true;
}
bool AsmParser::read_s32(memorycell cell)
{
obj->putCell(cell);
obj->putInt(inf->getnum());
return true;
}
bool AsmParser::paramless( memorycell cell )
{
obj->putCell(cell);
return true;
}
int AsmParser::lookupLocal( const char * txt )
{
if ( *txt == '@' )
{
int size = locals.length();
int i;
const char * buf = txt +1;
for ( i=0; i < size; i++ )
{
if ( locals[i] == buf )
return i;
}
return -1;
}
if ( !isdigit(*txt) )
return -1;
return atoi( txt );
}
bool AsmParser::read_local_var( memorycell cell )
{
char buf[256];
int number;
number = lookupLocal(inf->getword(buf));
if ( number == -1 )
return false;
cell.s.u.number = number;
obj->putCell(cell);
return true;
}
bool AsmParser::read_cond_local_var_jump( memorycell cell )
{
char buf[256];
int number;
inf->getword(buf);
cell.s.conds = parseConditions(buf);
number = lookupLocal(inf->getword(buf));
if ( number == -1 )
return false;
cell.s.u.number = number;
obj->putCell(cell);
cell.vmint = 0;
inf->getword(buf);
cell.s.u.number = getLabel(buf, obj->getOffset() -funstart);
obj->putCell(cell);
return true;
}
bool AsmParser::read_local_var_int( memorycell cell )
{
char buf[256];
int number;
number = lookupLocal(inf->getword(buf));
if ( number == -1 )
return false;
cell.s.u.number = number;
obj->putCell(cell);
obj->putInt(inf->getnum());
return true;
}
bool AsmParser::read_local_var_float( memorycell cell )
{
char buf[256];
int number;
number = lookupLocal(inf->getword(buf));
if ( number == -1 )
return false;
cell.s.u.number = number;
obj->putCell(cell);
obj->putFloat(inf->getfloat());
return true;
}
bool AsmParser::read_local_var_constant( memorycell cell )
{
char buf[256];
int number;
number = lookupLocal(inf->getword(buf));
if ( number == -1 )
return false;
cell.s.u.number = number;
obj->putCell(cell);
number = readConstant();
if ( number < 0 )
return false;
obj->addRelocation();
cell.vmint =0;
cell.s.u.number = number;
obj->putCell(cell);
return true;
}
bool AsmParser::read_ilookupswitch( memorycell cell )
{
char buf[256];
int length = inf->getnum();
if ( length > 255 )
return false;
cell.s.conds = length;
inf->getword(buf);
cell.s.u.number = getLabel( buf, obj->getOffset() - funstart );
obj->putCell(cell);
for ( ; length > 0; length-- )
{
cell.vmint = inf->getnum();
obj->putCell(cell);
inf->getword(buf);
cell.vmint = 0;
cell.s.u.number = getLabel(buf, obj->getOffset() - funstart );
obj->putCell(cell);
}
return true;
}
bool AsmParser::read_slookupswitch( memorycell cell )
{
char buf[256];
int length = inf->getnum();
if ( length > 255 )
return false;
cell.s.conds = length;
inf->getword(buf);
cell.s.u.number = getLabel( buf, obj->getOffset() - funstart );
obj->putCell(cell);
for ( ; length > 0; length-- )
{
cell.vmint =0;
inf->getstring(buf);
cell.s.u.number = obj->addConstant(VMT_CONST_STRING, buf, strlen(buf)+1);
obj->addRelocation();
obj->putCell(cell);
inf->getword(buf);
cell.vmint = 0;
cell.s.u.number = getLabel(buf, obj->getOffset() - funstart );
obj->putCell(cell);
}
return true;
}
bool AsmParser::read_itableswitch( memorycell cell )
{
int min,max,i;
char buf[256];
char defaul[256];
inf->getword(defaul);
cell.s.u.number = getLabel( defaul, obj->getOffset() - funstart );
obj->putCell(cell);
min = inf->getnum();
max = inf->getnum();
if ( min > max )
return false;
cell.vmint = min;
obj->putCell(cell);
cell.vmint = max;
obj->putCell(cell);
cell.vmint = 0;
while ( min <= max )
{
i = inf->getnum();
while (min < i)
{
cell.s.u.number = getLabel( defaul, obj->getOffset() - funstart );
obj->putCell(cell);
min++;
}
inf->getword(buf);
cell.s.u.number = getLabel( buf, obj->getOffset() - funstart );
obj->putCell(cell);
min++;
}
return true;
}
bool AsmParser::read_field( memorycell cell )
{
char buf1[256];
char buf2[256];
int s1, s2;
inf->getword( buf1 );
inf->getword( buf2 );
s1 = strlen(buf1);
s2 = strlen(buf2);
memcpy( &buf1[s1+1], buf2, s2 +1 );
cell.s.u.number = obj->addConstant( VMT_FIELD_DATA, buf1, s1 + s2 + 2);
obj->addRelocation();
obj->putCell(cell);
return true;
}
bool AsmParser::read_classname( memorycell cell )
{
char buf[128];
inf->getword(buf);
cell.s.u.number = obj->addConstant(VMT_CLASS_TYPE, buf, strlen(buf)+1);
obj->addRelocation();
obj->putCell(cell);
return true;
}
// If I ever write run-time disassember of VM code this code will change
// Table containg opcode -> parser_table mapping will be constructed at boot
const char * lookupOpcodeName( u8 opcode )
{
int i;
for ( i =0; AsmParser::parser_table[i].name; i++)
{
if ( opcode == AsmParser::parser_table[i].opcode )
return AsmParser::parser_table[i].name;
}
return "UNKNOWN";
}