mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-12-02 09:13:51 +00:00
736ec20d4d
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.
2649 lines
71 KiB
C++
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() );
|
|
}
|
|
}
|