dhewm3/neo/game/script/Script_Compiler.cpp
dhewg 736ec20d4d Untangle the epic precompiled.h mess
Don't include the lazy precompiled.h everywhere, only what's
required for the compilation unit.
platform.h needs to be included instead to provide all essential
defines and types.
All includes use the relative path to the neo or the game
specific root.
Move all idlib related includes from idlib/Lib.h to precompiled.h.
precompiled.h still exists for the MFC stuff in tools/.
Add some missing header guards.
2011-12-19 23:21:47 +01:00

2649 lines
71 KiB
C++

/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
Doom 3 Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Doom 3 Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "sys/platform.h"
#include "idlib/Timer.h"
#include "script/Script_Thread.h"
#include "Game_local.h"
#include "script/Script_Compiler.h"
#define FUNCTION_PRIORITY 2
#define INT_PRIORITY 2
#define NOT_PRIORITY 5
#define TILDE_PRIORITY 5
#define TOP_PRIORITY 7
bool idCompiler::punctuationValid[ 256 ];
const char *idCompiler::punctuation[] = {
"+=", "-=", "*=", "/=", "%=", "&=", "|=", "++", "--",
"&&", "||", "<=", ">=", "==", "!=", "::", ";", ",",
"~", "!", "*", "/", "%", "(", ")", "-", "+",
"=", "[", "]", ".", "<", ">" , "&", "|", ":", NULL
};
const opcode_t idCompiler::opcodes[] = {
{ "<RETURN>", "RETURN", -1, false, &def_void, &def_void, &def_void },
{ "++", "UINC_F", 1, true, &def_float, &def_void, &def_void },
{ "++", "UINCP_F", 1, true, &def_object, &def_field, &def_float },
{ "--", "UDEC_F", 1, true, &def_float, &def_void, &def_void },
{ "--", "UDECP_F", 1, true, &def_object, &def_field, &def_float },
{ "~", "COMP_F", -1, false, &def_float, &def_void, &def_float },
{ "*", "MUL_F", 3, false, &def_float, &def_float, &def_float },
{ "*", "MUL_V", 3, false, &def_vector, &def_vector, &def_float },
{ "*", "MUL_FV", 3, false, &def_float, &def_vector, &def_vector },
{ "*", "MUL_VF", 3, false, &def_vector, &def_float, &def_vector },
{ "/", "DIV", 3, false, &def_float, &def_float, &def_float },
{ "%", "MOD_F", 3, false, &def_float, &def_float, &def_float },
{ "+", "ADD_F", 4, false, &def_float, &def_float, &def_float },
{ "+", "ADD_V", 4, false, &def_vector, &def_vector, &def_vector },
{ "+", "ADD_S", 4, false, &def_string, &def_string, &def_string },
{ "+", "ADD_FS", 4, false, &def_float, &def_string, &def_string },
{ "+", "ADD_SF", 4, false, &def_string, &def_float, &def_string },
{ "+", "ADD_VS", 4, false, &def_vector, &def_string, &def_string },
{ "+", "ADD_SV", 4, false, &def_string, &def_vector, &def_string },
{ "-", "SUB_F", 4, false, &def_float, &def_float, &def_float },
{ "-", "SUB_V", 4, false, &def_vector, &def_vector, &def_vector },
{ "==", "EQ_F", 5, false, &def_float, &def_float, &def_float },
{ "==", "EQ_V", 5, false, &def_vector, &def_vector, &def_float },
{ "==", "EQ_S", 5, false, &def_string, &def_string, &def_float },
{ "==", "EQ_E", 5, false, &def_entity, &def_entity, &def_float },
{ "==", "EQ_EO", 5, false, &def_entity, &def_object, &def_float },
{ "==", "EQ_OE", 5, false, &def_object, &def_entity, &def_float },
{ "==", "EQ_OO", 5, false, &def_object, &def_object, &def_float },
{ "!=", "NE_F", 5, false, &def_float, &def_float, &def_float },
{ "!=", "NE_V", 5, false, &def_vector, &def_vector, &def_float },
{ "!=", "NE_S", 5, false, &def_string, &def_string, &def_float },
{ "!=", "NE_E", 5, false, &def_entity, &def_entity, &def_float },
{ "!=", "NE_EO", 5, false, &def_entity, &def_object, &def_float },
{ "!=", "NE_OE", 5, false, &def_object, &def_entity, &def_float },
{ "!=", "NE_OO", 5, false, &def_object, &def_object, &def_float },
{ "<=", "LE", 5, false, &def_float, &def_float, &def_float },
{ ">=", "GE", 5, false, &def_float, &def_float, &def_float },
{ "<", "LT", 5, false, &def_float, &def_float, &def_float },
{ ">", "GT", 5, false, &def_float, &def_float, &def_float },
{ ".", "INDIRECT_F", 1, false, &def_object, &def_field, &def_float },
{ ".", "INDIRECT_V", 1, false, &def_object, &def_field, &def_vector },
{ ".", "INDIRECT_S", 1, false, &def_object, &def_field, &def_string },
{ ".", "INDIRECT_E", 1, false, &def_object, &def_field, &def_entity },
{ ".", "INDIRECT_BOOL", 1, false, &def_object, &def_field, &def_boolean },
{ ".", "INDIRECT_OBJ", 1, false, &def_object, &def_field, &def_object },
{ ".", "ADDRESS", 1, false, &def_entity, &def_field, &def_pointer },
{ ".", "EVENTCALL", 2, false, &def_entity, &def_function, &def_void },
{ ".", "OBJECTCALL", 2, false, &def_object, &def_function, &def_void },
{ ".", "SYSCALL", 2, false, &def_void, &def_function, &def_void },
{ "=", "STORE_F", 6, true, &def_float, &def_float, &def_float },
{ "=", "STORE_V", 6, true, &def_vector, &def_vector, &def_vector },
{ "=", "STORE_S", 6, true, &def_string, &def_string, &def_string },
{ "=", "STORE_ENT", 6, true, &def_entity, &def_entity, &def_entity },
{ "=", "STORE_BOOL", 6, true, &def_boolean, &def_boolean, &def_boolean },
{ "=", "STORE_OBJENT", 6, true, &def_object, &def_entity, &def_object },
{ "=", "STORE_OBJ", 6, true, &def_object, &def_object, &def_object },
{ "=", "STORE_OBJENT", 6, true, &def_entity, &def_object, &def_object },
{ "=", "STORE_FTOS", 6, true, &def_string, &def_float, &def_string },
{ "=", "STORE_BTOS", 6, true, &def_string, &def_boolean, &def_string },
{ "=", "STORE_VTOS", 6, true, &def_string, &def_vector, &def_string },
{ "=", "STORE_FTOBOOL", 6, true, &def_boolean, &def_float, &def_boolean },
{ "=", "STORE_BOOLTOF", 6, true, &def_float, &def_boolean, &def_float },
{ "=", "STOREP_F", 6, true, &def_pointer, &def_float, &def_float },
{ "=", "STOREP_V", 6, true, &def_pointer, &def_vector, &def_vector },
{ "=", "STOREP_S", 6, true, &def_pointer, &def_string, &def_string },
{ "=", "STOREP_ENT", 6, true, &def_pointer, &def_entity, &def_entity },
{ "=", "STOREP_FLD", 6, true, &def_pointer, &def_field, &def_field },
{ "=", "STOREP_BOOL", 6, true, &def_pointer, &def_boolean, &def_boolean },
{ "=", "STOREP_OBJ", 6, true, &def_pointer, &def_object, &def_object },
{ "=", "STOREP_OBJENT", 6, true, &def_pointer, &def_object, &def_object },
{ "<=>", "STOREP_FTOS", 6, true, &def_pointer, &def_float, &def_string },
{ "<=>", "STOREP_BTOS", 6, true, &def_pointer, &def_boolean, &def_string },
{ "<=>", "STOREP_VTOS", 6, true, &def_pointer, &def_vector, &def_string },
{ "<=>", "STOREP_FTOBOOL", 6, true, &def_pointer, &def_float, &def_boolean },
{ "<=>", "STOREP_BOOLTOF", 6, true, &def_pointer, &def_boolean, &def_float },
{ "*=", "UMUL_F", 6, true, &def_float, &def_float, &def_void },
{ "*=", "UMUL_V", 6, true, &def_vector, &def_float, &def_void },
{ "/=", "UDIV_F", 6, true, &def_float, &def_float, &def_void },
{ "/=", "UDIV_V", 6, true, &def_vector, &def_float, &def_void },
{ "%=", "UMOD_F", 6, true, &def_float, &def_float, &def_void },
{ "+=", "UADD_F", 6, true, &def_float, &def_float, &def_void },
{ "+=", "UADD_V", 6, true, &def_vector, &def_vector, &def_void },
{ "-=", "USUB_F", 6, true, &def_float, &def_float, &def_void },
{ "-=", "USUB_V", 6, true, &def_vector, &def_vector, &def_void },
{ "&=", "UAND_F", 6, true, &def_float, &def_float, &def_void },
{ "|=", "UOR_F", 6, true, &def_float, &def_float, &def_void },
{ "!", "NOT_BOOL", -1, false, &def_boolean, &def_void, &def_float },
{ "!", "NOT_F", -1, false, &def_float, &def_void, &def_float },
{ "!", "NOT_V", -1, false, &def_vector, &def_void, &def_float },
{ "!", "NOT_S", -1, false, &def_vector, &def_void, &def_float },
{ "!", "NOT_ENT", -1, false, &def_entity, &def_void, &def_float },
{ "<NEG_F>", "NEG_F", -1, false, &def_float, &def_void, &def_float },
{ "<NEG_V>", "NEG_V", -1, false, &def_vector, &def_void, &def_vector },
{ "int", "INT_F", -1, false, &def_float, &def_void, &def_float },
{ "<IF>", "IF", -1, false, &def_float, &def_jumpoffset, &def_void },
{ "<IFNOT>", "IFNOT", -1, false, &def_float, &def_jumpoffset, &def_void },
// calls returns REG_RETURN
{ "<CALL>", "CALL", -1, false, &def_function, &def_argsize, &def_void },
{ "<THREAD>", "THREAD", -1, false, &def_function, &def_argsize, &def_void },
{ "<THREAD>", "OBJTHREAD", -1, false, &def_function, &def_argsize, &def_void },
{ "<PUSH>", "PUSH_F", -1, false, &def_float, &def_float, &def_void },
{ "<PUSH>", "PUSH_V", -1, false, &def_vector, &def_vector, &def_void },
{ "<PUSH>", "PUSH_S", -1, false, &def_string, &def_string, &def_void },
{ "<PUSH>", "PUSH_ENT", -1, false, &def_entity, &def_entity, &def_void },
{ "<PUSH>", "PUSH_OBJ", -1, false, &def_object, &def_object, &def_void },
{ "<PUSH>", "PUSH_OBJENT", -1, false, &def_entity, &def_object, &def_void },
{ "<PUSH>", "PUSH_FTOS", -1, false, &def_string, &def_float, &def_void },
{ "<PUSH>", "PUSH_BTOF", -1, false, &def_float, &def_boolean, &def_void },
{ "<PUSH>", "PUSH_FTOB", -1, false, &def_boolean, &def_float, &def_void },
{ "<PUSH>", "PUSH_VTOS", -1, false, &def_string, &def_vector, &def_void },
{ "<PUSH>", "PUSH_BTOS", -1, false, &def_string, &def_boolean, &def_void },
{ "<GOTO>", "GOTO", -1, false, &def_jumpoffset, &def_void, &def_void },
{ "&&", "AND", 7, false, &def_float, &def_float, &def_float },
{ "&&", "AND_BOOLF", 7, false, &def_boolean, &def_float, &def_float },
{ "&&", "AND_FBOOL", 7, false, &def_float, &def_boolean, &def_float },
{ "&&", "AND_BOOLBOOL", 7, false, &def_boolean, &def_boolean, &def_float },
{ "||", "OR", 7, false, &def_float, &def_float, &def_float },
{ "||", "OR_BOOLF", 7, false, &def_boolean, &def_float, &def_float },
{ "||", "OR_FBOOL", 7, false, &def_float, &def_boolean, &def_float },
{ "||", "OR_BOOLBOOL", 7, false, &def_boolean, &def_boolean, &def_float },
{ "&", "BITAND", 3, false, &def_float, &def_float, &def_float },
{ "|", "BITOR", 3, false, &def_float, &def_float, &def_float },
{ "<BREAK>", "BREAK", -1, false, &def_float, &def_void, &def_void },
{ "<CONTINUE>", "CONTINUE", -1, false, &def_float, &def_void, &def_void },
{ NULL }
};
/*
================
idCompiler::idCompiler()
================
*/
idCompiler::idCompiler() {
const char **ptr;
int id;
// make sure we have the right # of opcodes in the table
assert( ( sizeof( opcodes ) / sizeof( opcodes[ 0 ] ) ) == ( NUM_OPCODES + 1 ) );
eof = true;
parserPtr = &parser;
callthread = false;
loopDepth = 0;
eof = false;
braceDepth = 0;
immediateType = NULL;
basetype = NULL;
currentLineNumber = 0;
currentFileNumber = 0;
errorCount = 0;
console = false;
scope = &def_namespace;
memset( &immediate, 0, sizeof( immediate ) );
memset( punctuationValid, 0, sizeof( punctuationValid ) );
for( ptr = punctuation; *ptr != NULL; ptr++ ) {
id = parserPtr->GetPunctuationId( *ptr );
if ( ( id >= 0 ) && ( id < 256 ) ) {
punctuationValid[ id ] = true;
}
}
}
/*
============
idCompiler::Error
Aborts the current file load
============
*/
void idCompiler::Error( const char *message, ... ) const {
va_list argptr;
char string[ 1024 ];
va_start( argptr, message );
vsprintf( string, message, argptr );
va_end( argptr );
throw idCompileError( string );
}
/*
============
idCompiler::Warning
Prints a warning about the current line
============
*/
void idCompiler::Warning( const char *message, ... ) const {
va_list argptr;
char string[ 1024 ];
va_start( argptr, message );
vsprintf( string, message, argptr );
va_end( argptr );
parserPtr->Warning( "%s", string );
}
/*
============
idCompiler::VirtualFunctionConstant
Creates a def for an index into a virtual function table
============
*/
ID_INLINE idVarDef *idCompiler::VirtualFunctionConstant( idVarDef *func ) {
eval_t eval;
memset( &eval, 0, sizeof( eval ) );
eval._int = func->scope->TypeDef()->GetFunctionNumber( func->value.functionPtr );
if ( eval._int < 0 ) {
Error( "Function '%s' not found in scope '%s'", func->Name(), func->scope->Name() );
}
return GetImmediate( &type_virtualfunction, &eval, "" );
}
/*
============
idCompiler::SizeConstant
Creates a def for a size constant
============
*/
ID_INLINE idVarDef *idCompiler::SizeConstant( int size ) {
eval_t eval;
memset( &eval, 0, sizeof( eval ) );
eval._int = size;
return GetImmediate( &type_argsize, &eval, "" );
}
/*
============
idCompiler::JumpConstant
Creates a def for a jump constant
============
*/
ID_INLINE idVarDef *idCompiler::JumpConstant( int value ) {
eval_t eval;
memset( &eval, 0, sizeof( eval ) );
eval._int = value;
return GetImmediate( &type_jumpoffset, &eval, "" );
}
/*
============
idCompiler::JumpDef
Creates a def for a relative jump from one code location to another
============
*/
ID_INLINE idVarDef *idCompiler::JumpDef( int jumpfrom, int jumpto ) {
return JumpConstant( jumpto - jumpfrom );
}
/*
============
idCompiler::JumpTo
Creates a def for a relative jump from current code location
============
*/
ID_INLINE idVarDef *idCompiler::JumpTo( int jumpto ) {
return JumpDef( gameLocal.program.NumStatements(), jumpto );
}
/*
============
idCompiler::JumpFrom
Creates a def for a relative jump from code location to current code location
============
*/
ID_INLINE idVarDef *idCompiler::JumpFrom( int jumpfrom ) {
return JumpDef( jumpfrom, gameLocal.program.NumStatements() );
}
/*
============
idCompiler::Divide
============
*/
ID_INLINE float idCompiler::Divide( float numerator, float denominator ) {
if ( denominator == 0 ) {
Error( "Divide by zero" );
return 0;
}
return numerator / denominator;
}
/*
============
idCompiler::FindImmediate
tries to find an existing immediate with the same value
============
*/
idVarDef *idCompiler::FindImmediate( const idTypeDef *type, const eval_t *eval, const char *string ) const {
idVarDef *def;
etype_t etype;
etype = type->Type();
// check for a constant with the same value
for( def = gameLocal.program.GetDefList( "<IMMEDIATE>" ); def != NULL; def = def->Next() ) {
if ( def->TypeDef() != type ) {
continue;
}
switch( etype ) {
case ev_field :
if ( *def->value.intPtr == eval->_int ) {
return def;
}
break;
case ev_argsize :
if ( def->value.argSize == eval->_int ) {
return def;
}
break;
case ev_jumpoffset :
if ( def->value.jumpOffset == eval->_int ) {
return def;
}
break;
case ev_entity :
if ( *def->value.intPtr == eval->entity ) {
return def;
}
break;
case ev_string :
if ( idStr::Cmp( def->value.stringPtr, string ) == 0 ) {
return def;
}
break;
case ev_float :
if ( *def->value.floatPtr == eval->_float ) {
return def;
}
break;
case ev_virtualfunction :
if ( def->value.virtualFunction == eval->_int ) {
return def;
}
break;
case ev_vector :
if ( ( def->value.vectorPtr->x == eval->vector[ 0 ] ) &&
( def->value.vectorPtr->y == eval->vector[ 1 ] ) &&
( def->value.vectorPtr->z == eval->vector[ 2 ] ) ) {
return def;
}
break;
default :
Error( "weird immediate type" );
break;
}
}
return NULL;
}
/*
============
idCompiler::GetImmediate
returns an existing immediate with the same value, or allocates a new one
============
*/
idVarDef *idCompiler::GetImmediate( idTypeDef *type, const eval_t *eval, const char *string ) {
idVarDef *def;
def = FindImmediate( type, eval, string );
if ( def ) {
def->numUsers++;
} else {
// allocate a new def
def = gameLocal.program.AllocDef( type, "<IMMEDIATE>", &def_namespace, true );
if ( type->Type() == ev_string ) {
def->SetString( string, true );
} else {
def->SetValue( *eval, true );
}
}
return def;
}
/*
============
idCompiler::OptimizeOpcode
try to optimize when the operator works on constants only
============
*/
idVarDef *idCompiler::OptimizeOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ) {
eval_t c;
idTypeDef *type;
if ( var_a && var_a->initialized != idVarDef::initializedConstant ) {
return NULL;
}
if ( var_b && var_b->initialized != idVarDef::initializedConstant ) {
return NULL;
}
idVec3 &vec_c = *reinterpret_cast<idVec3 *>( &c.vector[ 0 ] );
memset( &c, 0, sizeof( c ) );
switch( op - opcodes ) {
case OP_ADD_F: c._float = *var_a->value.floatPtr + *var_b->value.floatPtr; type = &type_float; break;
case OP_ADD_V: vec_c = *var_a->value.vectorPtr + *var_b->value.vectorPtr; type = &type_vector; break;
case OP_SUB_F: c._float = *var_a->value.floatPtr - *var_b->value.floatPtr; type = &type_float; break;
case OP_SUB_V: vec_c = *var_a->value.vectorPtr - *var_b->value.vectorPtr; type = &type_vector; break;
case OP_MUL_F: c._float = *var_a->value.floatPtr * *var_b->value.floatPtr; type = &type_float; break;
case OP_MUL_V: c._float = *var_a->value.vectorPtr * *var_b->value.vectorPtr; type = &type_float; break;
case OP_MUL_FV: vec_c = *var_b->value.vectorPtr * *var_a->value.floatPtr; type = &type_vector; break;
case OP_MUL_VF: vec_c = *var_a->value.vectorPtr * *var_b->value.floatPtr; type = &type_vector; break;
case OP_DIV_F: c._float = Divide( *var_a->value.floatPtr, *var_b->value.floatPtr ); type = &type_float; break;
case OP_MOD_F: c._float = (int)*var_a->value.floatPtr % (int)*var_b->value.floatPtr; type = &type_float; break;
case OP_BITAND: c._float = ( int )*var_a->value.floatPtr & ( int )*var_b->value.floatPtr; type = &type_float; break;
case OP_BITOR: c._float = ( int )*var_a->value.floatPtr | ( int )*var_b->value.floatPtr; type = &type_float; break;
case OP_GE: c._float = *var_a->value.floatPtr >= *var_b->value.floatPtr; type = &type_float; break;
case OP_LE: c._float = *var_a->value.floatPtr <= *var_b->value.floatPtr; type = &type_float; break;
case OP_GT: c._float = *var_a->value.floatPtr > *var_b->value.floatPtr; type = &type_float; break;
case OP_LT: c._float = *var_a->value.floatPtr < *var_b->value.floatPtr; type = &type_float; break;
case OP_AND: c._float = *var_a->value.floatPtr && *var_b->value.floatPtr; type = &type_float; break;
case OP_OR: c._float = *var_a->value.floatPtr || *var_b->value.floatPtr; type = &type_float; break;
case OP_NOT_BOOL: c._int = !*var_a->value.intPtr; type = &type_boolean; break;
case OP_NOT_F: c._float = !*var_a->value.floatPtr; type = &type_float; break;
case OP_NOT_V: c._float = !var_a->value.vectorPtr->x && !var_a->value.vectorPtr->y && !var_a->value.vectorPtr->z; type = &type_float; break;
case OP_NEG_F: c._float = -*var_a->value.floatPtr; type = &type_float; break;
case OP_NEG_V: vec_c = -*var_a->value.vectorPtr; type = &type_vector; break;
case OP_INT_F: c._float = ( int )*var_a->value.floatPtr; type = &type_float; break;
case OP_EQ_F: c._float = ( *var_a->value.floatPtr == *var_b->value.floatPtr ); type = &type_float; break;
case OP_EQ_V: c._float = var_a->value.vectorPtr->Compare( *var_b->value.vectorPtr ); type = &type_float; break;
case OP_EQ_E: c._float = ( *var_a->value.intPtr == *var_b->value.intPtr ); type = &type_float; break;
case OP_NE_F: c._float = ( *var_a->value.floatPtr != *var_b->value.floatPtr ); type = &type_float; break;
case OP_NE_V: c._float = !var_a->value.vectorPtr->Compare( *var_b->value.vectorPtr ); type = &type_float; break;
case OP_NE_E: c._float = ( *var_a->value.intPtr != *var_b->value.intPtr ); type = &type_float; break;
case OP_UADD_F: c._float = *var_b->value.floatPtr + *var_a->value.floatPtr; type = &type_float; break;
case OP_USUB_F: c._float = *var_b->value.floatPtr - *var_a->value.floatPtr; type = &type_float; break;
case OP_UMUL_F: c._float = *var_b->value.floatPtr * *var_a->value.floatPtr; type = &type_float; break;
case OP_UDIV_F: c._float = Divide( *var_b->value.floatPtr, *var_a->value.floatPtr ); type = &type_float; break;
case OP_UMOD_F: c._float = ( int ) *var_b->value.floatPtr % ( int )*var_a->value.floatPtr; type = &type_float; break;
case OP_UOR_F: c._float = ( int )*var_b->value.floatPtr | ( int )*var_a->value.floatPtr; type = &type_float; break;
case OP_UAND_F: c._float = ( int )*var_b->value.floatPtr & ( int )*var_a->value.floatPtr; type = &type_float; break;
case OP_UINC_F: c._float = *var_a->value.floatPtr + 1; type = &type_float; break;
case OP_UDEC_F: c._float = *var_a->value.floatPtr - 1; type = &type_float; break;
case OP_COMP_F: c._float = ( float )~( int )*var_a->value.floatPtr; type = &type_float; break;
default: type = NULL; break;
}
if ( !type ) {
return NULL;
}
if ( var_a ) {
var_a->numUsers--;
if ( var_a->numUsers <= 0 ) {
gameLocal.program.FreeDef( var_a, NULL );
}
}
if ( var_b ) {
var_b->numUsers--;
if ( var_b->numUsers <= 0 ) {
gameLocal.program.FreeDef( var_b, NULL );
}
}
return GetImmediate( type, &c, "" );
}
/*
============
idCompiler::EmitOpcode
Emits a primitive statement, returning the var it places it's value in
============
*/
idVarDef *idCompiler::EmitOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ) {
statement_t *statement;
idVarDef *var_c;
var_c = OptimizeOpcode( op, var_a, var_b );
if ( var_c ) {
return var_c;
}
if ( var_a && !strcmp( var_a->Name(), RESULT_STRING ) ) {
var_a->numUsers++;
}
if ( var_b && !strcmp( var_b->Name(), RESULT_STRING ) ) {
var_b->numUsers++;
}
statement = gameLocal.program.AllocStatement();
statement->linenumber = currentLineNumber;
statement->file = currentFileNumber;
if ( ( op->type_c == &def_void ) || op->rightAssociative ) {
// ifs, gotos, and assignments don't need vars allocated
var_c = NULL;
} else {
// allocate result space
// try to reuse result defs as much as possible
var_c = gameLocal.program.FindFreeResultDef( op->type_c->TypeDef(), RESULT_STRING, scope, var_a, var_b );
// set user count back to 1, a result def needs to be used twice before it can be reused
var_c->numUsers = 1;
}
statement->op = op - opcodes;
statement->a = var_a;
statement->b = var_b;
statement->c = var_c;
if ( op->rightAssociative ) {
return var_a;
}
return var_c;
}
/*
============
idCompiler::EmitOpcode
Emits a primitive statement, returning the var it places it's value in
============
*/
ID_INLINE idVarDef *idCompiler::EmitOpcode( int op, idVarDef *var_a, idVarDef *var_b ) {
return EmitOpcode( &opcodes[ op ], var_a, var_b );
}
/*
============
idCompiler::EmitPush
Emits an opcode to push the variable onto the stack.
============
*/
bool idCompiler::EmitPush( idVarDef *expression, const idTypeDef *funcArg ) {
const opcode_t *op;
const opcode_t *out;
out = NULL;
for( op = &opcodes[ OP_PUSH_F ]; op->name && !strcmp( op->name, "<PUSH>" ); op++ ) {
if ( ( funcArg->Type() == op->type_a->Type() ) && ( expression->Type() == op->type_b->Type() ) ) {
out = op;
break;
}
}
if ( !out ) {
if ( ( expression->TypeDef() != funcArg ) && !expression->TypeDef()->Inherits( funcArg ) ) {
return false;
}
out = &opcodes[ OP_PUSH_ENT ];
}
EmitOpcode( out, expression, 0 );
return true;
}
/*
==============
idCompiler::NextToken
Sets token, immediateType, and possibly immediate
==============
*/
void idCompiler::NextToken( void ) {
int i;
// reset our type
immediateType = NULL;
memset( &immediate, 0, sizeof( immediate ) );
// Save the token's line number and filename since when we emit opcodes the current
// token is always the next one to be read
currentLineNumber = token.line;
currentFileNumber = gameLocal.program.GetFilenum( parserPtr->GetFileName() );
if ( !parserPtr->ReadToken( &token ) ) {
eof = true;
return;
}
if ( currentFileNumber != gameLocal.program.GetFilenum( parserPtr->GetFileName() ) ) {
if ( ( braceDepth > 0 ) && ( token != "}" ) ) {
// missing a closing brace. try to give as much info as possible.
if ( scope->Type() == ev_function ) {
Error( "Unexpected end of file inside function '%s'. Missing closing braces.", scope->Name() );
} else if ( scope->Type() == ev_object ) {
Error( "Unexpected end of file inside object '%s'. Missing closing braces.", scope->Name() );
} else if ( scope->Type() == ev_namespace ) {
Error( "Unexpected end of file inside namespace '%s'. Missing closing braces.", scope->Name() );
} else {
Error( "Unexpected end of file inside braced section" );
}
}
}
switch( token.type ) {
case TT_STRING:
// handle quoted strings as a unit
immediateType = &type_string;
return;
case TT_LITERAL: {
// handle quoted vectors as a unit
immediateType = &type_vector;
idLexer lex( token, token.Length(), parserPtr->GetFileName(), LEXFL_NOERRORS );
idToken token2;
for( i = 0; i < 3; i++ ) {
if ( !lex.ReadToken( &token2 ) ) {
Error( "Couldn't read vector. '%s' is not in the form of 'x y z'", token.c_str() );
}
if ( token2.type == TT_PUNCTUATION && token2 == "-" ) {
if ( !lex.CheckTokenType( TT_NUMBER, 0, &token2 ) ) {
Error( "expected a number following '-' but found '%s' in vector '%s'", token2.c_str(), token.c_str() );
}
immediate.vector[ i ] = -token2.GetFloatValue();
} else if ( token2.type == TT_NUMBER ) {
immediate.vector[ i ] = token2.GetFloatValue();
} else {
Error( "vector '%s' is not in the form of 'x y z'. expected float value, found '%s'", token.c_str(), token2.c_str() );
}
}
return;
}
case TT_NUMBER:
immediateType = &type_float;
immediate._float = token.GetFloatValue();
return;
case TT_PUNCTUATION:
// entity names
if ( token == "$" ) {
immediateType = &type_entity;
parserPtr->ReadToken( &token );
return;
}
if ( token == "{" ) {
braceDepth++;
return;
}
if ( token == "}" ) {
braceDepth--;
return;
}
if ( punctuationValid[ token.subtype ] ) {
return;
}
Error( "Unknown punctuation '%s'", token.c_str() );
break;
case TT_NAME:
return;
default:
Error( "Unknown token '%s'", token.c_str() );
}
}
/*
=============
idCompiler::ExpectToken
Issues an Error if the current token isn't equal to string
Gets the next token
=============
*/
void idCompiler::ExpectToken( const char *string ) {
if ( token != string ) {
Error( "expected '%s', found '%s'", string, token.c_str() );
}
NextToken();
}
/*
=============
idCompiler::CheckToken
Returns true and gets the next token if the current token equals string
Returns false and does nothing otherwise
=============
*/
bool idCompiler::CheckToken( const char *string ) {
if ( token != string ) {
return false;
}
NextToken();
return true;
}
/*
============
idCompiler::ParseName
Checks to see if the current token is a valid name
============
*/
void idCompiler::ParseName( idStr &name ) {
if ( token.type != TT_NAME ) {
Error( "'%s' is not a name", token.c_str() );
}
name = token;
NextToken();
}
/*
============
idCompiler::SkipOutOfFunction
For error recovery, pops out of nested braces
============
*/
void idCompiler::SkipOutOfFunction( void ) {
while( braceDepth ) {
parserPtr->SkipBracedSection( false );
braceDepth--;
}
NextToken();
}
/*
============
idCompiler::SkipToSemicolon
For error recovery
============
*/
void idCompiler::SkipToSemicolon( void ) {
do {
if ( CheckToken( ";" ) ) {
return;
}
NextToken();
} while( !eof );
}
/*
============
idCompiler::CheckType
Parses a variable type, including functions types
============
*/
idTypeDef *idCompiler::CheckType( void ) {
idTypeDef *type;
if ( token == "float" ) {
type = &type_float;
} else if ( token == "vector" ) {
type = &type_vector;
} else if ( token == "entity" ) {
type = &type_entity;
} else if ( token == "string" ) {
type = &type_string;
} else if ( token == "void" ) {
type = &type_void;
} else if ( token == "object" ) {
type = &type_object;
} else if ( token == "boolean" ) {
type = &type_boolean;
} else if ( token == "namespace" ) {
type = &type_namespace;
} else if ( token == "scriptEvent" ) {
type = &type_scriptevent;
} else {
type = gameLocal.program.FindType( token.c_str() );
if ( type && !type->Inherits( &type_object ) ) {
type = NULL;
}
}
return type;
}
/*
============
idCompiler::ParseType
Parses a variable type, including functions types
============
*/
idTypeDef *idCompiler::ParseType( void ) {
idTypeDef *type;
type = CheckType();
if ( !type ) {
Error( "\"%s\" is not a type", token.c_str() );
}
if ( ( type == &type_scriptevent ) && ( scope != &def_namespace ) ) {
Error( "scriptEvents can only defined in the global namespace" );
}
if ( ( type == &type_namespace ) && ( scope->Type() != ev_namespace ) ) {
Error( "A namespace may only be defined globally, or within another namespace" );
}
NextToken();
return type;
}
/*
============
idCompiler::ParseImmediate
Looks for a preexisting constant
============
*/
idVarDef *idCompiler::ParseImmediate( void ) {
idVarDef *def;
def = GetImmediate( immediateType, &immediate, token.c_str() );
NextToken();
return def;
}
/*
============
idCompiler::EmitFunctionParms
============
*/
idVarDef *idCompiler::EmitFunctionParms( int op, idVarDef *func, int startarg, int startsize, idVarDef *object ) {
idVarDef *e;
const idTypeDef *type;
const idTypeDef *funcArg;
idVarDef *returnDef;
idTypeDef *returnType;
int arg;
int size;
int resultOp;
type = func->TypeDef();
if ( func->Type() != ev_function ) {
Error( "'%s' is not a function", func->Name() );
}
// copy the parameters to the global parameter variables
arg = startarg;
size = startsize;
if ( !CheckToken( ")" ) ) {
do {
if ( arg >= type->NumParameters() ) {
Error( "too many parameters" );
}
e = GetExpression( TOP_PRIORITY );
funcArg = type->GetParmType( arg );
if ( !EmitPush( e, funcArg ) ) {
Error( "type mismatch on parm %i of call to '%s'", arg + 1, func->Name() );
}
if ( funcArg->Type() == ev_object ) {
size += type_object.Size();
} else {
size += funcArg->Size();
}
arg++;
} while( CheckToken( "," ) );
ExpectToken( ")" );
}
if ( arg < type->NumParameters() ) {
Error( "too few parameters for function '%s'", func->Name() );
}
if ( op == OP_CALL ) {
EmitOpcode( op, func, 0 );
} else if ( ( op == OP_OBJECTCALL ) || ( op == OP_OBJTHREAD ) ) {
EmitOpcode( op, object, VirtualFunctionConstant( func ) );
// need arg size seperate since script object may be NULL
statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 );
statement.c = SizeConstant( func->value.functionPtr->parmTotal );
} else {
EmitOpcode( op, func, SizeConstant( size ) );
}
// we need to copy off the result into a temporary result location, so figure out the opcode
returnType = type->ReturnType();
if ( returnType->Type() == ev_string ) {
resultOp = OP_STORE_S;
returnDef = gameLocal.program.returnStringDef;
} else {
gameLocal.program.returnDef->SetTypeDef( returnType );
returnDef = gameLocal.program.returnDef;
switch( returnType->Type() ) {
case ev_void :
resultOp = OP_STORE_F;
break;
case ev_boolean :
resultOp = OP_STORE_BOOL;
break;
case ev_float :
resultOp = OP_STORE_F;
break;
case ev_vector :
resultOp = OP_STORE_V;
break;
case ev_entity :
resultOp = OP_STORE_ENT;
break;
case ev_object :
resultOp = OP_STORE_OBJ;
break;
default :
Error( "Invalid return type for function '%s'", func->Name() );
// shut up compiler
resultOp = OP_STORE_OBJ;
break;
}
}
if ( returnType->Type() == ev_void ) {
// don't need result space since there's no result, so just return the normal result def.
return returnDef;
}
// allocate result space
// try to reuse result defs as much as possible
statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 );
idVarDef *resultDef = gameLocal.program.FindFreeResultDef( returnType, RESULT_STRING, scope, statement.a, statement.b );
// set user count back to 0, a result def needs to be used twice before it can be reused
resultDef->numUsers = 0;
EmitOpcode( resultOp, returnDef, resultDef );
return resultDef;
}
/*
============
idCompiler::ParseFunctionCall
============
*/
idVarDef *idCompiler::ParseFunctionCall( idVarDef *funcDef ) {
assert( funcDef );
if ( funcDef->Type() != ev_function ) {
Error( "'%s' is not a function", funcDef->Name() );
}
if ( funcDef->initialized == idVarDef::uninitialized ) {
Error( "Function '%s' has not been defined yet", funcDef->GlobalName() );
}
assert( funcDef->value.functionPtr );
if ( callthread ) {
if ( ( funcDef->initialized != idVarDef::uninitialized ) && funcDef->value.functionPtr->eventdef ) {
Error( "Built-in functions cannot be called as threads" );
}
callthread = false;
return EmitFunctionParms( OP_THREAD, funcDef, 0, 0, NULL );
} else {
if ( ( funcDef->initialized != idVarDef::uninitialized ) && funcDef->value.functionPtr->eventdef ) {
if ( ( scope->Type() != ev_namespace ) && ( scope->scope->Type() == ev_object ) ) {
// get the local object pointer
idVarDef *thisdef = gameLocal.program.GetDef( scope->scope->TypeDef(), "self", scope );
if ( !thisdef ) {
Error( "No 'self' within scope" );
}
return ParseEventCall( thisdef, funcDef );
} else {
Error( "Built-in functions cannot be called without an object" );
}
}
return EmitFunctionParms( OP_CALL, funcDef, 0, 0, NULL );
}
}
/*
============
idCompiler::ParseObjectCall
============
*/
idVarDef *idCompiler::ParseObjectCall( idVarDef *object, idVarDef *func ) {
EmitPush( object, object->TypeDef() );
if ( callthread ) {
callthread = false;
return EmitFunctionParms( OP_OBJTHREAD, func, 1, type_object.Size(), object );
} else {
return EmitFunctionParms( OP_OBJECTCALL, func, 1, 0, object );
}
}
/*
============
idCompiler::ParseEventCall
============
*/
idVarDef *idCompiler::ParseEventCall( idVarDef *object, idVarDef *funcDef ) {
if ( callthread ) {
Error( "Cannot call built-in functions as a thread" );
}
if ( funcDef->Type() != ev_function ) {
Error( "'%s' is not a function", funcDef->Name() );
}
if ( !funcDef->value.functionPtr->eventdef ) {
Error( "\"%s\" cannot be called with object notation", funcDef->Name() );
}
if ( object->Type() == ev_object ) {
EmitPush( object, &type_entity );
} else {
EmitPush( object, object->TypeDef() );
}
return EmitFunctionParms( OP_EVENTCALL, funcDef, 0, type_object.Size(), NULL );
}
/*
============
idCompiler::ParseSysObjectCall
============
*/
idVarDef *idCompiler::ParseSysObjectCall( idVarDef *funcDef ) {
if ( callthread ) {
Error( "Cannot call built-in functions as a thread" );
}
if ( funcDef->Type() != ev_function ) {
Error( "'%s' is not a function", funcDef->Name() );
}
if ( !funcDef->value.functionPtr->eventdef ) {
Error( "\"%s\" cannot be called with object notation", funcDef->Name() );
}
if ( !idThread::Type.RespondsTo( *funcDef->value.functionPtr->eventdef ) ) {
Error( "\"%s\" is not callable as a 'sys' function", funcDef->Name() );
}
return EmitFunctionParms( OP_SYSCALL, funcDef, 0, 0, NULL );
}
/*
============
idCompiler::LookupDef
============
*/
idVarDef *idCompiler::LookupDef( const char *name, const idVarDef *baseobj ) {
idVarDef *def;
idVarDef *field;
etype_t type_b;
etype_t type_c;
const opcode_t *op;
// check if we're accessing a field
if ( baseobj && ( baseobj->Type() == ev_object ) ) {
const idVarDef *tdef;
def = NULL;
for( tdef = baseobj; tdef != &def_object; tdef = tdef->TypeDef()->SuperClass()->def ) {
def = gameLocal.program.GetDef( NULL, name, tdef );
if ( def ) {
break;
}
}
} else {
// first look through the defs in our scope
def = gameLocal.program.GetDef( NULL, name, scope );
if ( !def ) {
// if we're in a member function, check types local to the object
if ( ( scope->Type() != ev_namespace ) && ( scope->scope->Type() == ev_object ) ) {
// get the local object pointer
idVarDef *thisdef = gameLocal.program.GetDef( scope->scope->TypeDef(), "self", scope );
field = LookupDef( name, scope->scope->TypeDef()->def );
if ( !field ) {
Error( "Unknown value \"%s\"", name );
}
// type check
type_b = field->Type();
if ( field->Type() == ev_function ) {
type_c = field->TypeDef()->ReturnType()->Type();
} else {
type_c = field->TypeDef()->FieldType()->Type(); // field access gets type from field
if ( CheckToken( "++" ) ) {
if ( type_c != ev_float ) {
Error( "Invalid type for ++" );
}
def = EmitOpcode( OP_UINCP_F, thisdef, field );
return def;
} else if ( CheckToken( "--" ) ) {
if ( type_c != ev_float ) {
Error( "Invalid type for --" );
}
def = EmitOpcode( OP_UDECP_F, thisdef, field );
return def;
}
}
op = &opcodes[ OP_INDIRECT_F ];
while( ( op->type_a->Type() != ev_object )
|| ( type_b != op->type_b->Type() ) || ( type_c != op->type_c->Type() ) ) {
if ( ( op->priority == FUNCTION_PRIORITY ) && ( op->type_a->Type() == ev_object ) && ( op->type_c->Type() == ev_void ) &&
( type_c != op->type_c->Type() ) ) {
// catches object calls that return a value
break;
}
op++;
if ( !op->name || strcmp( op->name, "." ) ) {
Error( "no valid opcode to access type '%s'", field->TypeDef()->SuperClass()->Name() );
}
}
if ( ( op - opcodes ) == OP_OBJECTCALL ) {
ExpectToken( "(" );
def = ParseObjectCall( thisdef, field );
} else {
// emit the conversion opcode
def = EmitOpcode( op, thisdef, field );
// field access gets type from field
def->SetTypeDef( field->TypeDef()->FieldType() );
}
}
}
}
return def;
}
/*
============
idCompiler::ParseValue
Returns the def for the current token
============
*/
idVarDef *idCompiler::ParseValue( void ) {
idVarDef *def;
idVarDef *namespaceDef;
idStr name;
if ( immediateType == &type_entity ) {
// if an immediate entity ($-prefaced name) then create or lookup a def for it.
// when entities are spawned, they'll lookup the def and point it to them.
def = gameLocal.program.GetDef( &type_entity, "$" + token, &def_namespace );
if ( !def ) {
def = gameLocal.program.AllocDef( &type_entity, "$" + token, &def_namespace, true );
}
NextToken();
return def;
} else if ( immediateType ) {
// if the token is an immediate, allocate a constant for it
return ParseImmediate();
}
ParseName( name );
def = LookupDef( name, basetype );
if ( !def ) {
if ( basetype ) {
Error( "%s is not a member of %s", name.c_str(), basetype->TypeDef()->Name() );
} else {
Error( "Unknown value \"%s\"", name.c_str() );
}
// if namespace, then look up the variable in that namespace
} else if ( def->Type() == ev_namespace ) {
while( def->Type() == ev_namespace ) {
ExpectToken( "::" );
ParseName( name );
namespaceDef = def;
def = gameLocal.program.GetDef( NULL, name, namespaceDef );
if ( !def ) {
Error( "Unknown value \"%s::%s\"", namespaceDef->GlobalName(), name.c_str() );
}
}
//def = LookupDef( name, basetype );
}
return def;
}
/*
============
idCompiler::GetTerm
============
*/
idVarDef *idCompiler::GetTerm( void ) {
idVarDef *e;
int op;
if ( !immediateType && CheckToken( "~" ) ) {
e = GetExpression( TILDE_PRIORITY );
switch( e->Type() ) {
case ev_float :
op = OP_COMP_F;
break;
default :
Error( "type mismatch for ~" );
// shut up compiler
op = OP_COMP_F;
break;
}
return EmitOpcode( op, e, 0 );
}
if ( !immediateType && CheckToken( "!" ) ) {
e = GetExpression( NOT_PRIORITY );
switch( e->Type() ) {
case ev_boolean :
op = OP_NOT_BOOL;
break;
case ev_float :
op = OP_NOT_F;
break;
case ev_string :
op = OP_NOT_S;
break;
case ev_vector :
op = OP_NOT_V;
break;
case ev_entity :
op = OP_NOT_ENT;
break;
case ev_function :
Error( "Invalid type for !" );
// shut up compiler
op = OP_NOT_F;
break;
case ev_object :
op = OP_NOT_ENT;
break;
default :
Error( "type mismatch for !" );
// shut up compiler
op = OP_NOT_F;
break;
}
return EmitOpcode( op, e, 0 );
}
// check for negation operator
if ( !immediateType && CheckToken( "-" ) ) {
// constants are directly negated without an instruction
if ( immediateType == &type_float ) {
immediate._float = -immediate._float;
return ParseImmediate();
} else if ( immediateType == &type_vector ) {
immediate.vector[0] = -immediate.vector[0];
immediate.vector[1] = -immediate.vector[1];
immediate.vector[2] = -immediate.vector[2];
return ParseImmediate();
} else {
e = GetExpression( NOT_PRIORITY );
switch( e->Type() ) {
case ev_float :
op = OP_NEG_F;
break;
case ev_vector :
op = OP_NEG_V;
break;
default :
Error( "type mismatch for -" );
// shut up compiler
op = OP_NEG_F;
break;
}
return EmitOpcode( &opcodes[ op ], e, 0 );
}
}
if ( CheckToken( "int" ) ) {
ExpectToken( "(" );
e = GetExpression( INT_PRIORITY );
if ( e->Type() != ev_float ) {
Error( "type mismatch for int()" );
}
ExpectToken( ")" );
return EmitOpcode( OP_INT_F, e, 0 );
}
if ( CheckToken( "thread" ) ) {
callthread = true;
e = GetExpression( FUNCTION_PRIORITY );
if ( callthread ) {
Error( "Invalid thread call" );
}
// threads return the thread number
gameLocal.program.returnDef->SetTypeDef( &type_float );
return gameLocal.program.returnDef;
}
if ( !immediateType && CheckToken( "(" ) ) {
e = GetExpression( TOP_PRIORITY );
ExpectToken( ")" );
return e;
}
return ParseValue();
}
/*
==============
idCompiler::TypeMatches
==============
*/
bool idCompiler::TypeMatches( etype_t type1, etype_t type2 ) const {
if ( type1 == type2 ) {
return true;
}
//if ( ( type1 == ev_entity ) && ( type2 == ev_object ) ) {
// return true;
//}
//if ( ( type2 == ev_entity ) && ( type1 == ev_object ) ) {
// return true;
//}
return false;
}
/*
==============
idCompiler::GetExpression
==============
*/
idVarDef *idCompiler::GetExpression( int priority ) {
const opcode_t *op;
const opcode_t *oldop;
idVarDef *e;
idVarDef *e2;
const idVarDef *oldtype;
etype_t type_a;
etype_t type_b;
etype_t type_c;
if ( priority == 0 ) {
return GetTerm();
}
e = GetExpression( priority - 1 );
if ( token == ";" ) {
// save us from searching through the opcodes unneccesarily
return e;
}
while( 1 ) {
if ( ( priority == FUNCTION_PRIORITY ) && CheckToken( "(" ) ) {
return ParseFunctionCall( e );
}
// has to be a punctuation
if ( immediateType ) {
break;
}
for( op = opcodes; op->name; op++ ) {
if ( ( op->priority == priority ) && CheckToken( op->name ) ) {
break;
}
}
if ( !op->name ) {
// next token isn't at this priority level
break;
}
// unary operators act only on the left operand
if ( op->type_b == &def_void ) {
e = EmitOpcode( op, e, 0 );
return e;
}
// preserve our base type
oldtype = basetype;
// field access needs scope from object
if ( ( op->name[ 0 ] == '.' ) && e->TypeDef()->Inherits( &type_object ) ) {
// save off what type this field is part of
basetype = e->TypeDef()->def;
}
if ( op->rightAssociative ) {
// if last statement is an indirect, change it to an address of
if ( gameLocal.program.NumStatements() > 0 ) {
statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 );
if ( ( statement.op >= OP_INDIRECT_F ) && ( statement.op < OP_ADDRESS ) ) {
statement.op = OP_ADDRESS;
type_pointer.SetPointerType( e->TypeDef() );
e->SetTypeDef( &type_pointer );
}
}
e2 = GetExpression( priority );
} else {
e2 = GetExpression( priority - 1 );
}
// restore type
basetype = oldtype;
// type check
type_a = e->Type();
type_b = e2->Type();
// field access gets type from field
if ( op->name[ 0 ] == '.' ) {
if ( ( e2->Type() == ev_function ) && e2->TypeDef()->ReturnType() ) {
type_c = e2->TypeDef()->ReturnType()->Type();
} else if ( e2->TypeDef()->FieldType() ) {
type_c = e2->TypeDef()->FieldType()->Type();
} else {
// not a field
type_c = ev_error;
}
} else {
type_c = ev_void;
}
oldop = op;
while( !TypeMatches( type_a, op->type_a->Type() ) || !TypeMatches( type_b, op->type_b->Type() ) ||
( ( type_c != ev_void ) && !TypeMatches( type_c, op->type_c->Type() ) ) ) {
if ( ( op->priority == FUNCTION_PRIORITY ) && TypeMatches( type_a, op->type_a->Type() ) && TypeMatches( type_b, op->type_b->Type() ) ) {
break;
}
op++;
if ( !op->name || strcmp( op->name, oldop->name ) ) {
Error( "type mismatch for '%s'", oldop->name );
}
}
switch( op - opcodes ) {
case OP_SYSCALL :
ExpectToken( "(" );
e = ParseSysObjectCall( e2 );
break;
case OP_OBJECTCALL :
ExpectToken( "(" );
if ( ( e2->initialized != idVarDef::uninitialized ) && e2->value.functionPtr->eventdef ) {
e = ParseEventCall( e, e2 );
} else {
e = ParseObjectCall( e, e2 );
}
break;
case OP_EVENTCALL :
ExpectToken( "(" );
if ( ( e2->initialized != idVarDef::uninitialized ) && e2->value.functionPtr->eventdef ) {
e = ParseEventCall( e, e2 );
} else {
e = ParseObjectCall( e, e2 );
}
break;
default:
if ( callthread ) {
Error( "Expecting function call after 'thread'" );
}
if ( ( type_a == ev_pointer ) && ( type_b != e->TypeDef()->PointerType()->Type() ) ) {
// FIXME: need to make a general case for this
if ( ( op - opcodes == OP_STOREP_F ) && ( e->TypeDef()->PointerType()->Type() == ev_boolean ) ) {
// copy from float to boolean pointer
op = &opcodes[ OP_STOREP_FTOBOOL ];
} else if ( ( op - opcodes == OP_STOREP_BOOL ) && ( e->TypeDef()->PointerType()->Type() == ev_float ) ) {
// copy from boolean to float pointer
op = &opcodes[ OP_STOREP_BOOLTOF ];
} else if ( ( op - opcodes == OP_STOREP_F ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) {
// copy from float to string pointer
op = &opcodes[ OP_STOREP_FTOS ];
} else if ( ( op - opcodes == OP_STOREP_BOOL ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) {
// copy from boolean to string pointer
op = &opcodes[ OP_STOREP_BTOS ];
} else if ( ( op - opcodes == OP_STOREP_V ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) {
// copy from vector to string pointer
op = &opcodes[ OP_STOREP_VTOS ];
} else if ( ( op - opcodes == OP_STOREP_ENT ) && ( e->TypeDef()->PointerType()->Type() == ev_object ) ) {
// store an entity into an object pointer
op = &opcodes[ OP_STOREP_OBJENT ];
} else {
Error( "type mismatch for '%s'", op->name );
}
}
if ( op->rightAssociative ) {
e = EmitOpcode( op, e2, e );
} else {
e = EmitOpcode( op, e, e2 );
}
if ( op - opcodes == OP_STOREP_OBJENT ) {
// statement.b points to type_pointer, which is just a temporary that gets its type reassigned, so we store the real type in statement.c
// so that we can do a type check during run time since we don't know what type the script object is at compile time because it
// comes from an entity
statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 );
statement.c = type_pointer.PointerType()->def;
}
// field access gets type from field
if ( type_c != ev_void ) {
e->SetTypeDef( e2->TypeDef()->FieldType() );
}
break;
}
}
return e;
}
/*
================
idCompiler::PatchLoop
================
*/
void idCompiler::PatchLoop( int start, int continuePos ) {
int i;
statement_t *pos;
pos = &gameLocal.program.GetStatement( start );
for( i = start; i < gameLocal.program.NumStatements(); i++, pos++ ) {
if ( pos->op == OP_BREAK ) {
pos->op = OP_GOTO;
pos->a = JumpFrom( i );
} else if ( pos->op == OP_CONTINUE ) {
pos->op = OP_GOTO;
pos->a = JumpDef( i, continuePos );
}
}
}
/*
================
idCompiler::ParseReturnStatement
================
*/
void idCompiler::ParseReturnStatement( void ) {
idVarDef *e;
etype_t type_a;
etype_t type_b;
const opcode_t *op;
if ( CheckToken( ";" ) ) {
if ( scope->TypeDef()->ReturnType()->Type() != ev_void ) {
Error( "expecting return value" );
}
EmitOpcode( OP_RETURN, 0, 0 );
return;
}
e = GetExpression( TOP_PRIORITY );
ExpectToken( ";" );
type_a = e->Type();
type_b = scope->TypeDef()->ReturnType()->Type();
if ( TypeMatches( type_a, type_b ) ) {
EmitOpcode( OP_RETURN, e, 0 );
return;
}
for( op = opcodes; op->name; op++ ) {
if ( !strcmp( op->name, "=" ) ) {
break;
}
}
assert( op->name );
while( !TypeMatches( type_a, op->type_a->Type() ) || !TypeMatches( type_b, op->type_b->Type() ) ) {
op++;
if ( !op->name || strcmp( op->name, "=" ) ) {
Error( "type mismatch for return value" );
}
}
idTypeDef *returnType = scope->TypeDef()->ReturnType();
if ( returnType->Type() == ev_string ) {
EmitOpcode( op, e, gameLocal.program.returnStringDef );
} else {
gameLocal.program.returnDef->SetTypeDef( returnType );
EmitOpcode( op, e, gameLocal.program.returnDef );
}
EmitOpcode( OP_RETURN, 0, 0 );
}
/*
================
idCompiler::ParseWhileStatement
================
*/
void idCompiler::ParseWhileStatement( void ) {
idVarDef *e;
int patch1;
int patch2;
loopDepth++;
ExpectToken( "(" );
patch2 = gameLocal.program.NumStatements();
e = GetExpression( TOP_PRIORITY );
ExpectToken( ")" );
if ( ( e->initialized == idVarDef::initializedConstant ) && ( *e->value.intPtr != 0 ) ) {
//FIXME: we can completely skip generation of this code in the opposite case
ParseStatement();
EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 );
} else {
patch1 = gameLocal.program.NumStatements();
EmitOpcode( OP_IFNOT, e, 0 );
ParseStatement();
EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 );
gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 );
}
// fixup breaks and continues
PatchLoop( patch2, patch2 );
loopDepth--;
}
/*
================
idCompiler::ParseForStatement
Form of for statement with a counter:
a = 0;
start: << patch4
if ( !( a < 10 ) ) {
goto end; << patch1
} else {
goto process; << patch3
}
increment: << patch2
a = a + 1;
goto start; << goto patch4
process:
statements;
goto increment; << goto patch2
end:
Form of for statement without a counter:
a = 0;
start: << patch2
if ( !( a < 10 ) ) {
goto end; << patch1
}
process:
statements;
goto start; << goto patch2
end:
================
*/
void idCompiler::ParseForStatement( void ) {
idVarDef *e;
int start;
int patch1;
int patch2;
int patch3;
int patch4;
loopDepth++;
start = gameLocal.program.NumStatements();
ExpectToken( "(" );
// init
if ( !CheckToken( ";" ) ) {
do {
GetExpression( TOP_PRIORITY );
} while( CheckToken( "," ) );
ExpectToken( ";" );
}
// condition
patch2 = gameLocal.program.NumStatements();
e = GetExpression( TOP_PRIORITY );
ExpectToken( ";" );
//FIXME: add check for constant expression
patch1 = gameLocal.program.NumStatements();
EmitOpcode( OP_IFNOT, e, 0 );
// counter
if ( !CheckToken( ")" ) ) {
patch3 = gameLocal.program.NumStatements();
EmitOpcode( OP_IF, e, 0 );
patch4 = patch2;
patch2 = gameLocal.program.NumStatements();
do {
GetExpression( TOP_PRIORITY );
} while( CheckToken( "," ) );
ExpectToken( ")" );
// goto patch4
EmitOpcode( OP_GOTO, JumpTo( patch4 ), 0 );
// fixup patch3
gameLocal.program.GetStatement( patch3 ).b = JumpFrom( patch3 );
}
ParseStatement();
// goto patch2
EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 );
// fixup patch1
gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 );
// fixup breaks and continues
PatchLoop( start, patch2 );
loopDepth--;
}
/*
================
idCompiler::ParseDoWhileStatement
================
*/
void idCompiler::ParseDoWhileStatement( void ) {
idVarDef *e;
int patch1;
loopDepth++;
patch1 = gameLocal.program.NumStatements();
ParseStatement();
ExpectToken( "while" );
ExpectToken( "(" );
e = GetExpression( TOP_PRIORITY );
ExpectToken( ")" );
ExpectToken( ";" );
EmitOpcode( OP_IF, e, JumpTo( patch1 ) );
// fixup breaks and continues
PatchLoop( patch1, patch1 );
loopDepth--;
}
/*
================
idCompiler::ParseIfStatement
================
*/
void idCompiler::ParseIfStatement( void ) {
idVarDef *e;
int patch1;
int patch2;
ExpectToken( "(" );
e = GetExpression( TOP_PRIORITY );
ExpectToken( ")" );
//FIXME: add check for constant expression
patch1 = gameLocal.program.NumStatements();
EmitOpcode( OP_IFNOT, e, 0 );
ParseStatement();
if ( CheckToken( "else" ) ) {
patch2 = gameLocal.program.NumStatements();
EmitOpcode( OP_GOTO, 0, 0 );
gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 );
ParseStatement();
gameLocal.program.GetStatement( patch2 ).a = JumpFrom( patch2 );
} else {
gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 );
}
}
/*
============
idCompiler::ParseStatement
============
*/
void idCompiler::ParseStatement( void ) {
if ( CheckToken( ";" ) ) {
// skip semicolons, which are harmless and ok syntax
return;
}
if ( CheckToken( "{" ) ) {
do {
ParseStatement();
} while( !CheckToken( "}" ) );
return;
}
if ( CheckToken( "return" ) ) {
ParseReturnStatement();
return;
}
if ( CheckToken( "while" ) ) {
ParseWhileStatement();
return;
}
if ( CheckToken( "for" ) ) {
ParseForStatement();
return;
}
if ( CheckToken( "do" ) ) {
ParseDoWhileStatement();
return;
}
if ( CheckToken( "break" ) ) {
ExpectToken( ";" );
if ( !loopDepth ) {
Error( "cannot break outside of a loop" );
}
EmitOpcode( OP_BREAK, 0, 0 );
return;
}
if ( CheckToken( "continue" ) ) {
ExpectToken( ";" );
if ( !loopDepth ) {
Error( "cannot contine outside of a loop" );
}
EmitOpcode( OP_CONTINUE, 0, 0 );
return;
}
if ( CheckType() != NULL ) {
ParseDefs();
return;
}
if ( CheckToken( "if" ) ) {
ParseIfStatement();
return;
}
GetExpression( TOP_PRIORITY );
ExpectToken(";");
}
/*
================
idCompiler::ParseObjectDef
================
*/
void idCompiler::ParseObjectDef( const char *objname ) {
idTypeDef *objtype;
idTypeDef *type;
idTypeDef *parentType;
idTypeDef *fieldtype;
idStr name;
const char *fieldname;
idTypeDef newtype( ev_field, NULL, "", 0, NULL );
idVarDef *oldscope;
int i;
oldscope = scope;
if ( scope->Type() != ev_namespace ) {
Error( "Objects cannot be defined within functions or other objects" );
}
// make sure it doesn't exist before we create it
if ( gameLocal.program.FindType( objname ) != NULL ) {
Error( "'%s' : redefinition; different basic types", objname );
}
// base type
if ( !CheckToken( ":" ) ) {
parentType = &type_object;
} else {
parentType = ParseType();
if ( !parentType->Inherits( &type_object ) ) {
Error( "Objects may only inherit from objects." );
}
}
objtype = gameLocal.program.AllocType( ev_object, NULL, objname, parentType == &type_object ? 0 : parentType->Size(), parentType );
objtype->def = gameLocal.program.AllocDef( objtype, objname, scope, true );
scope = objtype->def;
// inherit all the functions
for( i = 0; i < parentType->NumFunctions(); i++ ) {
const function_t *func = parentType->GetFunction( i );
objtype->AddFunction( func );
}
ExpectToken( "{" );
do {
if ( CheckToken( ";" ) ) {
// skip semicolons, which are harmless and ok syntax
continue;
}
fieldtype = ParseType();
newtype.SetFieldType( fieldtype );
fieldname = va( "%s field", fieldtype->Name() );
newtype.SetName( fieldname );
ParseName( name );
// check for a function prototype or declaraction
if ( CheckToken( "(" ) ) {
ParseFunctionDef( newtype.FieldType(), name );
} else {
type = gameLocal.program.GetType( newtype, true );
assert( !type->def );
gameLocal.program.AllocDef( type, name, scope, true );
objtype->AddField( type, name );
ExpectToken( ";" );
}
} while( !CheckToken( "}" ) );
scope = oldscope;
ExpectToken( ";" );
}
/*
============
idCompiler::ParseFunction
parse a function type
============
*/
idTypeDef *idCompiler::ParseFunction( idTypeDef *returnType, const char *name ) {
idTypeDef newtype( ev_function, NULL, name, type_function.Size(), returnType );
idTypeDef *type;
if ( scope->Type() != ev_namespace ) {
// create self pointer
newtype.AddFunctionParm( scope->TypeDef(), "self" );
}
if ( !CheckToken( ")" ) ) {
idStr parmName;
do {
type = ParseType();
ParseName( parmName );
newtype.AddFunctionParm( type, parmName );
} while( CheckToken( "," ) );
ExpectToken( ")" );
}
return gameLocal.program.GetType( newtype, true );
}
/*
================
idCompiler::ParseFunctionDef
================
*/
void idCompiler::ParseFunctionDef( idTypeDef *returnType, const char *name ) {
idTypeDef *type;
idVarDef *def;
idVarDef *oldscope;
int i;
int numParms;
const idTypeDef *parmType;
function_t *func;
statement_t *pos;
if ( ( scope->Type() != ev_namespace ) && !scope->TypeDef()->Inherits( &type_object ) ) {
Error( "Functions may not be defined within other functions" );
}
type = ParseFunction( returnType, name );
def = gameLocal.program.GetDef( type, name, scope );
if ( !def ) {
def = gameLocal.program.AllocDef( type, name, scope, true );
type->def = def;
func = &gameLocal.program.AllocFunction( def );
if ( scope->TypeDef()->Inherits( &type_object ) ) {
scope->TypeDef()->AddFunction( func );
}
} else {
func = def->value.functionPtr;
assert( func );
if ( func->firstStatement ) {
Error( "%s redeclared", def->GlobalName() );
}
}
// check if this is a prototype or declaration
if ( !CheckToken( "{" ) ) {
// it's just a prototype, so get the ; and move on
ExpectToken( ";" );
return;
}
// calculate stack space used by parms
numParms = type->NumParameters();
func->parmSize.SetNum( numParms );
for( i = 0; i < numParms; i++ ) {
parmType = type->GetParmType( i );
if ( parmType->Inherits( &type_object ) ) {
func->parmSize[ i ] = type_object.Size();
} else {
func->parmSize[ i ] = parmType->Size();
}
func->parmTotal += func->parmSize[ i ];
}
// define the parms
for( i = 0; i < numParms; i++ ) {
if ( gameLocal.program.GetDef( type->GetParmType( i ), type->GetParmName( i ), def ) ) {
Error( "'%s' defined more than once in function parameters", type->GetParmName( i ) );
}
gameLocal.program.AllocDef( type->GetParmType( i ), type->GetParmName( i ), def, false );
}
oldscope = scope;
scope = def;
func->firstStatement = gameLocal.program.NumStatements();
// check if we should call the super class constructor
if ( oldscope->TypeDef()->Inherits( &type_object ) && !idStr::Icmp( name, "init" ) ) {
idTypeDef *superClass;
function_t *constructorFunc = NULL;
// find the superclass constructor
for( superClass = oldscope->TypeDef()->SuperClass(); superClass != &type_object; superClass = superClass->SuperClass() ) {
constructorFunc = gameLocal.program.FindFunction( va( "%s::init", superClass->Name() ) );
if ( constructorFunc ) {
break;
}
}
// emit the call to the constructor
if ( constructorFunc ) {
idVarDef *selfDef = gameLocal.program.GetDef( type->GetParmType( 0 ), type->GetParmName( 0 ), def );
assert( selfDef );
EmitPush( selfDef, selfDef->TypeDef() );
EmitOpcode( &opcodes[ OP_CALL ], constructorFunc->def, 0 );
}
}
// parse regular statements
while( !CheckToken( "}" ) ) {
ParseStatement();
}
// check if we should call the super class destructor
if ( oldscope->TypeDef()->Inherits( &type_object ) && !idStr::Icmp( name, "destroy" ) ) {
idTypeDef *superClass;
function_t *destructorFunc = NULL;
// find the superclass destructor
for( superClass = oldscope->TypeDef()->SuperClass(); superClass != &type_object; superClass = superClass->SuperClass() ) {
destructorFunc = gameLocal.program.FindFunction( va( "%s::destroy", superClass->Name() ) );
if ( destructorFunc ) {
break;
}
}
if ( destructorFunc ) {
if ( func->firstStatement < gameLocal.program.NumStatements() ) {
// change all returns to point to the call to the destructor
pos = &gameLocal.program.GetStatement( func->firstStatement );
for( i = func->firstStatement; i < gameLocal.program.NumStatements(); i++, pos++ ) {
if ( pos->op == OP_RETURN ) {
pos->op = OP_GOTO;
pos->a = JumpDef( i, gameLocal.program.NumStatements() );
}
}
}
// emit the call to the destructor
idVarDef *selfDef = gameLocal.program.GetDef( type->GetParmType( 0 ), type->GetParmName( 0 ), def );
assert( selfDef );
EmitPush( selfDef, selfDef->TypeDef() );
EmitOpcode( &opcodes[ OP_CALL ], destructorFunc->def, 0 );
}
}
// Disabled code since it caused a function to fall through to the next function when last statement is in the form "if ( x ) { return; }"
#if 0
// don't bother adding a return opcode if the "return" statement was used.
if ( ( func->firstStatement == gameLocal.program.NumStatements() ) || ( gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ).op != OP_RETURN ) ) {
// emit an end of statements opcode
EmitOpcode( OP_RETURN, 0, 0 );
}
#else
// always emit the return opcode
EmitOpcode( OP_RETURN, 0, 0 );
#endif
// record the number of statements in the function
func->numStatements = gameLocal.program.NumStatements() - func->firstStatement;
scope = oldscope;
}
/*
================
idCompiler::ParseVariableDef
================
*/
void idCompiler::ParseVariableDef( idTypeDef *type, const char *name ) {
idVarDef *def, *def2;
bool negate;
def = gameLocal.program.GetDef( type, name, scope );
if ( def ) {
Error( "%s redeclared", name );
}
def = gameLocal.program.AllocDef( type, name, scope, false );
// check for an initialization
if ( CheckToken( "=" ) ) {
// if a local variable in a function then write out interpreter code to initialize variable
if ( scope->Type() == ev_function ) {
def2 = GetExpression( TOP_PRIORITY );
if ( ( type == &type_float ) && ( def2->TypeDef() == &type_float ) ) {
EmitOpcode( OP_STORE_F, def2, def );
} else if ( ( type == &type_vector ) && ( def2->TypeDef() == &type_vector ) ) {
EmitOpcode( OP_STORE_V, def2, def );
} else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_string ) ) {
EmitOpcode( OP_STORE_S, def2, def );
} else if ( ( type == &type_entity ) && ( ( def2->TypeDef() == &type_entity ) || ( def2->TypeDef()->Inherits( &type_object ) ) ) ) {
EmitOpcode( OP_STORE_ENT, def2, def );
} else if ( ( type->Inherits( &type_object ) ) && ( def2->TypeDef() == &type_entity ) ) {
EmitOpcode( OP_STORE_OBJENT, def2, def );
} else if ( ( type->Inherits( &type_object ) ) && ( def2->TypeDef()->Inherits( type ) ) ) {
EmitOpcode( OP_STORE_OBJ, def2, def );
} else if ( ( type == &type_boolean ) && ( def2->TypeDef() == &type_boolean ) ) {
EmitOpcode( OP_STORE_BOOL, def2, def );
} else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_float ) ) {
EmitOpcode( OP_STORE_FTOS, def2, def );
} else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_boolean ) ) {
EmitOpcode( OP_STORE_BTOS, def2, def );
} else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_vector ) ) {
EmitOpcode( OP_STORE_VTOS, def2, def );
} else if ( ( type == &type_boolean ) && ( def2->TypeDef() == &type_float ) ) {
EmitOpcode( OP_STORE_FTOBOOL, def2, def );
} else if ( ( type == &type_float ) && ( def2->TypeDef() == &type_boolean ) ) {
EmitOpcode( OP_STORE_BOOLTOF, def2, def );
} else {
Error( "bad initialization for '%s'", name );
}
} else {
// global variables can only be initialized with immediate values
negate = false;
if ( token.type == TT_PUNCTUATION && token == "-" ) {
negate = true;
NextToken();
if ( immediateType != &type_float ) {
Error( "wrong immediate type for '-' on variable '%s'", name );
}
}
if ( immediateType != type ) {
Error( "wrong immediate type for '%s'", name );
}
// global variables are initialized at start up
if ( type == &type_string ) {
def->SetString( token, false );
} else {
if ( negate ) {
immediate._float = -immediate._float;
}
def->SetValue( immediate, false );
}
NextToken();
}
} else if ( type == &type_string ) {
// local strings on the stack are initialized in the interpreter
if ( scope->Type() != ev_function ) {
def->SetString( "", false );
}
} else if ( type->Inherits( &type_object ) ) {
if ( scope->Type() != ev_function ) {
def->SetObject( NULL );
}
}
}
/*
================
idCompiler::GetTypeForEventArg
================
*/
idTypeDef *idCompiler::GetTypeForEventArg( char argType ) {
idTypeDef *type;
switch( argType ) {
case D_EVENT_INTEGER :
// this will get converted to int by the interpreter
type = &type_float;
break;
case D_EVENT_FLOAT :
type = &type_float;
break;
case D_EVENT_VECTOR :
type = &type_vector;
break;
case D_EVENT_STRING :
type = &type_string;
break;
case D_EVENT_ENTITY :
case D_EVENT_ENTITY_NULL :
type = &type_entity;
break;
case D_EVENT_VOID :
type = &type_void;
break;
case D_EVENT_TRACE :
// This data type isn't available from script
type = NULL;
break;
default:
// probably a typo
type = NULL;
break;
}
return type;
}
/*
================
idCompiler::ParseEventDef
================
*/
void idCompiler::ParseEventDef( idTypeDef *returnType, const char *name ) {
const idTypeDef *expectedType;
idTypeDef *argType;
idTypeDef *type;
int i;
int num;
const char *format;
const idEventDef *ev;
idStr parmName;
ev = idEventDef::FindEvent( name );
if ( !ev ) {
Error( "Unknown event '%s'", name );
}
// set the return type
expectedType = GetTypeForEventArg( ev->GetReturnType() );
if ( !expectedType ) {
Error( "Invalid return type '%c' in definition of '%s' event.", ev->GetReturnType(), name );
}
if ( returnType != expectedType ) {
Error( "Return type doesn't match internal return type '%s'", expectedType->Name() );
}
idTypeDef newtype( ev_function, NULL, name, type_function.Size(), returnType );
ExpectToken( "(" );
format = ev->GetArgFormat();
num = strlen( format );
for( i = 0; i < num; i++ ) {
expectedType = GetTypeForEventArg( format[ i ] );
if ( !expectedType || ( expectedType == &type_void ) ) {
Error( "Invalid parameter '%c' in definition of '%s' event.", format[ i ], name );
}
argType = ParseType();
ParseName( parmName );
if ( argType != expectedType ) {
Error( "The type of parm %d ('%s') does not match the internal type '%s' in definition of '%s' event.",
i + 1, parmName.c_str(), expectedType->Name(), name );
}
newtype.AddFunctionParm( argType, "" );
if ( i < num - 1 ) {
if ( CheckToken( ")" ) ) {
Error( "Too few parameters for event definition. Internal definition has %d parameters.", num );
}
ExpectToken( "," );
}
}
if ( !CheckToken( ")" ) ) {
Error( "Too many parameters for event definition. Internal definition has %d parameters.", num );
}
ExpectToken( ";" );
type = gameLocal.program.FindType( name );
if ( type ) {
if ( !newtype.MatchesType( *type ) || ( type->def->value.functionPtr->eventdef != ev ) ) {
Error( "Type mismatch on redefinition of '%s'", name );
}
} else {
type = gameLocal.program.AllocType( newtype );
type->def = gameLocal.program.AllocDef( type, name, &def_namespace, true );
function_t &func = gameLocal.program.AllocFunction( type->def );
func.eventdef = ev;
func.parmSize.SetNum( num );
for( i = 0; i < num; i++ ) {
argType = newtype.GetParmType( i );
func.parmTotal += argType->Size();
func.parmSize[ i ] = argType->Size();
}
// mark the parms as local
func.locals = func.parmTotal;
}
}
/*
================
idCompiler::ParseDefs
Called at the outer layer and when a local statement is hit
================
*/
void idCompiler::ParseDefs( void ) {
idStr name;
idTypeDef *type;
idVarDef *def;
idVarDef *oldscope;
if ( CheckToken( ";" ) ) {
// skip semicolons, which are harmless and ok syntax
return;
}
type = ParseType();
if ( type == &type_scriptevent ) {
type = ParseType();
ParseName( name );
ParseEventDef( type, name );
return;
}
ParseName( name );
if ( type == &type_namespace ) {
def = gameLocal.program.GetDef( type, name, scope );
if ( !def ) {
def = gameLocal.program.AllocDef( type, name, scope, true );
}
ParseNamespace( def );
} else if ( CheckToken( "::" ) ) {
def = gameLocal.program.GetDef( NULL, name, scope );
if ( !def ) {
Error( "Unknown object name '%s'", name.c_str() );
}
ParseName( name );
oldscope = scope;
scope = def;
ExpectToken( "(" );
ParseFunctionDef( type, name.c_str() );
scope = oldscope;
} else if ( type == &type_object ) {
ParseObjectDef( name.c_str() );
} else if ( CheckToken( "(" ) ) { // check for a function prototype or declaraction
ParseFunctionDef( type, name.c_str() );
} else {
ParseVariableDef( type, name.c_str() );
while( CheckToken( "," ) ) {
ParseName( name );
ParseVariableDef( type, name.c_str() );
}
ExpectToken( ";" );
}
}
/*
================
idCompiler::ParseNamespace
Parses anything within a namespace definition
================
*/
void idCompiler::ParseNamespace( idVarDef *newScope ) {
idVarDef *oldscope;
oldscope = scope;
if ( newScope != &def_namespace ) {
ExpectToken( "{" );
}
while( !eof ) {
scope = newScope;
callthread = false;
if ( ( newScope != &def_namespace ) && CheckToken( "}" ) ) {
break;
}
ParseDefs();
}
scope = oldscope;
}
/*
============
idCompiler::CompileFile
compiles the 0 terminated text, adding definitions to the program structure
============
*/
void idCompiler::CompileFile( const char *text, const char *filename, bool toConsole ) {
idTimer compile_time;
bool error;
compile_time.Start();
scope = &def_namespace;
basetype = NULL;
callthread = false;
loopDepth = 0;
eof = false;
braceDepth = 0;
immediateType = NULL;
currentLineNumber = 0;
console = toConsole;
memset( &immediate, 0, sizeof( immediate ) );
parser.SetFlags( LEXFL_ALLOWMULTICHARLITERALS );
parser.LoadMemory( text, strlen( text ), filename );
parserPtr = &parser;
// unread tokens to include script defines
token = SCRIPT_DEFAULTDEFS;
token.type = TT_STRING;
token.subtype = token.Length();
token.line = token.linesCrossed = 0;
parser.UnreadToken( &token );
token = "include";
token.type = TT_NAME;
token.subtype = token.Length();
token.line = token.linesCrossed = 0;
parser.UnreadToken( &token );
token = "#";
token.type = TT_PUNCTUATION;
token.subtype = P_PRECOMP;
token.line = token.linesCrossed = 0;
parser.UnreadToken( &token );
// init the current token line to be the first line so that currentLineNumber is set correctly in NextToken
token.line = 1;
error = false;
try {
// read first token
NextToken();
while( !eof && !error ) {
// parse from global namespace
ParseNamespace( &def_namespace );
}
}
catch( idCompileError &err ) {
idStr error;
if ( console ) {
// don't print line number of an error if were calling script from the console using the "script" command
sprintf( error, "Error: %s\n", err.error );
} else {
sprintf( error, "Error: file %s, line %d: %s\n", gameLocal.program.GetFilename( currentFileNumber ), currentLineNumber, err.error );
}
parser.FreeSource();
throw idCompileError( error );
}
parser.FreeSource();
compile_time.Stop();
if ( !toConsole ) {
gameLocal.Printf( "Compiled '%s': %.1f ms\n", filename, compile_time.Milliseconds() );
}
}