/* =========================================================================== Copyright (C) 2015 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA 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 2 of the License, or (at your option) any later version. OpenMoHAA 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 OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // compiler.cpp : Parse, then compile to op-codes. #include "scriptcompiler.h" #include "scriptvm.h" #include "../fgame/level.h" #include "../fgame/parm.h" #include "../fgame/game.h" #include "../fgame/scriptmaster.h" #include "../fgame/scriptthread.h" #include "scriptclass.h" #include "scriptexception.h" #include "../parser/parsetree.h" #include "../parser/yyParser.h" #include "../parser/yyLexer.h" ScriptCompiler Compiler; int ScriptCompiler::current_label; ScriptCompiler::ScriptCompiler() { Reset(); } void ScriptCompiler::Reset() { code_pos = NULL; code_ptr = NULL; prog_ptr = NULL; prog_end_ptr = NULL; for( int i = 0; i < BREAK_JUMP_LOCATION_COUNT; i++ ) { apucBreakJumpLocations[ i ] = 0; apucContinueJumpLocations[ i ] = 0; prev_opcodes[ i ].opcode = 0; prev_opcodes[ i ].VarStackOffset = 0; } iBreakJumpLocCount = 0; iContinueJumpLocCount = 0; m_iHasExternal = 0; m_iInternalMaxVarStackOffset = 0; m_iMaxCallStackOffset = 0; m_iMaxExternalVarStackOffset = 0; m_iVarStackOffset = 0; bCanBreak = false; bCanContinue = false; prev_opcode_pos = 0; } unsigned char ScriptCompiler::PrevOpcode() { return prev_opcodes[ prev_opcode_pos ].opcode; } char ScriptCompiler::PrevVarStackOffset() { return prev_opcodes[ prev_opcode_pos ].VarStackOffset; } void ScriptCompiler::AbsorbPrevOpcode() { m_iVarStackOffset -= PrevVarStackOffset(); code_pos -= OpcodeLength( PrevOpcode() ); if( !prev_opcode_pos ) { prev_opcode_pos = 100; } prev_opcode_pos--; } void ScriptCompiler::ClearPrevOpcode() { prev_opcodes[ prev_opcode_pos ].opcode = OP_PREVIOUS; } void ScriptCompiler::AddBreakJumpLocation( unsigned char *pos ) { if( iBreakJumpLocCount < BREAK_JUMP_LOCATION_COUNT ) { apucBreakJumpLocations[ iBreakJumpLocCount++ ] = pos; } else { iBreakJumpLocCount = 0; CompileError( -1, "Increase BREAK_JUMP_LOCATION_COUNT and recompile.\n" ); } } void ScriptCompiler::AddContinueJumpLocation( unsigned char *pos ) { if( iContinueJumpLocCount < CONTINUE_JUMP_LOCATION_COUNT ) { apucContinueJumpLocations[ iContinueJumpLocCount++ ] = pos; } else { iContinueJumpLocCount = 0; CompileError( -1, "Increase CONTINUE_JUMP_LOCATION_COUNT and recompile.\n" ); } } void ScriptCompiler::AddJumpLocation( unsigned char *pos ) { unsigned int offset = code_pos - sizeof( unsigned int ) - pos; SetOpcodeValue(offset); ClearPrevOpcode(); } void ScriptCompiler::AddJumpBackLocation( unsigned char *pos ) { int offset = ( code_pos - pos ); EmitOpcodeValue(offset, sizeof(unsigned int)); ClearPrevOpcode(); } void ScriptCompiler::AddJumpToLocation( unsigned char *pos ) { int offset = ( pos - code_pos - 1 ); EmitOpcodeValue(offset, sizeof(unsigned int)); ClearPrevOpcode(); } bool ScriptCompiler::BuiltinReadVariable( unsigned int sourcePos, int type, int eventnum ) { ClassDef *c; EventDef *def; switch( type ) { case method_game: c = Game::classinfostatic(); break; case method_level: c = Level::classinfostatic(); break; case method_local: c = ScriptThread::classinfostatic(); break; case method_parm: c = Parm::classinfostatic(); break; case method_group: c = ScriptClass::classinfostatic(); break; default: return true; } def = c->GetDef( eventnum ); if( def ) { if( def->type == EV_GETTER ) { return true; } else { CompileError( sourcePos, "Cannot get a write-only variable" ); return false; } } else { return false; } } bool ScriptCompiler::BuiltinWriteVariable( unsigned int sourcePos, int type, int eventnum ) { ClassDef *c; EventDef *def; switch( type ) { case method_game: c = Game::classinfostatic(); break; case method_level: c = Level::classinfostatic(); break; case method_local: c = ScriptThread::classinfostatic(); break; case method_parm: c = Parm::classinfostatic(); break; case method_group: c = ScriptClass::classinfostatic(); break; default: return true; } def = c->GetDef( eventnum ); if( def ) { if( def->type == EV_SETTER ) { return true; } else { CompileError( sourcePos, "Cannot get a read-only variable" ); return false; } } else { return false; } } void ScriptCompiler::EmitAssignmentStatement( sval_t lhs, unsigned int sourcePos ) { int eventnum; sval_t listener_val; char *name = lhs.node[ 2 ].stringValue; str name2 = name; name2.tolower(); if( lhs.node[ 0 ].type != sval_field ) { if( lhs.node[ 0 ].type == sval_array ) { EmitRef( lhs.node[ 1 ], sourcePos ); EmitValue( lhs.node[ 2 ] ); EmitOpcode( OP_LOAD_ARRAY_VAR, lhs.node[ 3 ].sourcePosValue ); return; } else { CompileError( sourcePos, "bad lvalue: %d (expecting field or array)", lhs.node[ 0 ].type ); } } unsigned int index = Director.AddString( name ); eventnum = Event::FindSetterEventNum( name2 ); if( eventnum ) { index = Director.GetString( name2 ); } listener_val = lhs.node[ 1 ]; if( listener_val.node[ 0 ].type != sval_store_method || ( eventnum && BuiltinWriteVariable( sourcePos, listener_val.node[ 1 ].byteValue, eventnum ) ) ) { EmitValue( listener_val ); EmitOpcode( OP_LOAD_FIELD_VAR, sourcePos ); } else { EmitOpcode( OP_LOAD_GAME_VAR + listener_val.node[ 1 ].byteValue, sourcePos ); } EmitOpcodeValue(index, sizeof(unsigned int)); } void ScriptCompiler::EmitBoolJumpFalse( unsigned int sourcePos ) { if( PrevOpcode() == OP_UN_CAST_BOOLEAN ) { AbsorbPrevOpcode(); EmitOpcode( OP_VAR_JUMP_FALSE4, sourcePos ); } else { EmitOpcode( OP_BOOL_JUMP_FALSE4, sourcePos ); } } void ScriptCompiler::EmitBoolJumpTrue( unsigned int sourcePos ) { if( PrevOpcode() == OP_UN_CAST_BOOLEAN ) { AbsorbPrevOpcode(); EmitOpcode( OP_VAR_JUMP_TRUE4, sourcePos ); } else { EmitOpcode( OP_BOOL_JUMP_TRUE4, sourcePos ); } } void ScriptCompiler::EmitBoolNot( unsigned int sourcePos ) { int prev = PrevOpcode(); if( prev == OP_BOOL_STORE_TRUE ) { AbsorbPrevOpcode(); return EmitOpcode( OP_BOOL_STORE_FALSE, sourcePos ); } else if( prev > OP_BOOL_STORE_TRUE && prev == OP_BOOL_UN_NOT ) { AbsorbPrevOpcode(); return EmitNil( sourcePos ); } else if( prev == OP_BOOL_STORE_FALSE ) { AbsorbPrevOpcode(); return EmitOpcode( OP_BOOL_STORE_TRUE, sourcePos ); } return EmitOpcode( OP_BOOL_UN_NOT, sourcePos ); } void ScriptCompiler::EmitBoolToVar( unsigned int sourcePos ) { if( PrevOpcode() == OP_UN_CAST_BOOLEAN ) { AbsorbPrevOpcode(); EmitOpcode( OP_UN_CAST_BOOLEAN, sourcePos ); } else { if( showopcodes->integer ) { glbs.DPrintf( "\t\t%08d: %s (%d)\n", code_pos - code_ptr, OpcodeName( OP_BOOL_TO_VAR ), m_iVarStackOffset ); } int pos = ( prev_opcode_pos + 1 ) % 100; prev_opcode_pos = pos; prev_opcodes[ pos ].opcode = OP_BOOL_TO_VAR; prev_opcodes[ pos ].VarStackOffset = 0; prev_opcodes[ ( pos + 1 ) % 100 ].opcode = OP_PREVIOUS; } } void ScriptCompiler::EmitBreak( unsigned int sourcePos ) { if( bCanBreak ) { EmitOpcode( OP_JUMP4, sourcePos ); unsigned char *pos = code_pos; code_pos += 4; ClearPrevOpcode(); AddBreakJumpLocation( pos ); } else { CompileError( sourcePos, "illegal break\n" ); } } void ScriptCompiler::EmitCatch( sval_t val, unsigned char *try_begin_code_pos, unsigned int sourcePos ) { unsigned char *old_code_pos; StateScript *m_oldStateScript; EmitOpcode( OP_JUMP4, sourcePos ); old_code_pos = code_pos; code_pos += sizeof( unsigned int ); ClearPrevOpcode(); m_oldStateScript = stateScript; stateScript = script->CreateCatchStateScript( try_begin_code_pos, code_pos ); EmitValue( val ); stateScript = m_oldStateScript; AddJumpLocation( old_code_pos ); } void ScriptCompiler::EmitConstArray( sval_t lhs, sval_t rhs, unsigned int sourcePos ) { int iCount = 1; while (1) { iCount++; EmitValue(lhs); if (rhs.node[0].type != sval_constarray) { break; } lhs = rhs.node[1]; rhs = rhs.node[2]; } EmitValue(rhs); EmitConstArrayOpcode(iCount); } void ScriptCompiler::EmitConstArrayOpcode( int iCount ) { SetOpcodeVarStackOffset( OP_LOAD_CONST_ARRAY1, 1 - iCount ); EmitOpcode( OP_LOAD_CONST_ARRAY1, -1 ); EmitOpcodeValue(iCount, sizeof(short)); } void ScriptCompiler::EmitContinue( unsigned int sourcePos ) { if( bCanContinue ) { EmitOpcode( OP_JUMP4, sourcePos ); unsigned char* pos = code_pos; code_pos += sizeof(unsigned int); ClearPrevOpcode(); AddContinueJumpLocation( pos ); } else { CompileError( sourcePos, "illegal continue\n" ); } } void ScriptCompiler::EmitDoWhileJump( sval_t while_stmt, sval_t while_expr, unsigned int sourcePos ) { unsigned char *pos = code_pos; int label1, label2; if( showopcodes->integer ) { label1 = current_label++; glbs.DPrintf( ":\n", label1 ); } ClearPrevOpcode(); bool old_bCanBreak = bCanBreak; bool old_bCanContinue = bCanContinue; int breakCount = iBreakJumpLocCount; int continueCount = iContinueJumpLocCount; bCanBreak = true; bCanContinue = true; EmitValue( while_stmt ); ProcessContinueJumpLocations( continueCount ); bCanContinue = old_bCanContinue; EmitValue( while_expr ); EmitVarToBool( sourcePos ); label2 = EmitNot( sourcePos ); unsigned char *jmp = code_pos; code_pos += sizeof( unsigned int ); if( showopcodes->integer ) { glbs.DPrintf( "JUMP_BACK4 \n", label1 ); } EmitJumpBack( pos, sourcePos ); ClearPrevOpcode(); if( showopcodes->integer ) { glbs.DPrintf( ":\n", label2 ); } AddJumpLocation( jmp ); ProcessBreakJumpLocations( breakCount ); bCanBreak = old_bCanBreak; } void ScriptCompiler::EmitEof( unsigned int sourcePos ) { if( PrevOpcode() ) { EmitOpcode( OP_DONE, -1 ); } } void ScriptCompiler::EmitField( sval_t listener_val, sval_t field_val, unsigned int sourcePos ) { unsigned int eventnum = 0; unsigned int index = -1; unsigned int prev_index; if( field_val.node[ 0 ].stringValue ) { str name = field_val.stringValue; str name2 = field_val.stringValue; name2.tolower(); index = Director.AddString( name ); eventnum = Event::FindGetterEventNum( name2 ); if( eventnum ) { index = Director.GetString( name2 ); } } prev_index = GetOpcodeValue(sizeof(unsigned int), sizeof(unsigned int)); if( listener_val.node[ 0 ].type != sval_store_method || ( eventnum && BuiltinReadVariable( sourcePos, listener_val.node[ 1 ].byteValue , eventnum ) ) ) { EmitValue( listener_val ); EmitOpcode( OP_STORE_FIELD, sourcePos ); EmitOpcodeValue(index, sizeof(unsigned int)); } else if( PrevOpcode() != ( OP_LOAD_GAME_VAR + listener_val.node[ 1 ].byteValue ) || prev_index != index ) { EmitOpcode(OP_STORE_GAME_VAR + listener_val.node[1].byteValue, sourcePos); EmitOpcodeValue(index, sizeof(unsigned int)); } else { AbsorbPrevOpcode(); EmitOpcode( OP_LOAD_STORE_GAME_VAR + listener_val.node[ 1 ].byteValue, sourcePos ); } } void ScriptCompiler::EmitFloat( float value, unsigned int sourcePos ) { // ley0k: optimization if( value == ( float )( int )value ) { return EmitInteger( ( unsigned int )value, sourcePos ); } EmitOpcode( OP_STORE_FLOAT, sourcePos ); EmitOpcodeValue(value, sizeof(float)); } void ScriptCompiler::EmitFunc1( int opcode, unsigned int sourcePos ) { if( opcode == OP_UN_MINUS ) { ScriptVariable var; if( EvalPrevValue( var ) ) { AbsorbPrevOpcode(); var.minus(); return EmitValue( var, sourcePos ); } } EmitOpcode( opcode, sourcePos ); } void ScriptCompiler::EmitFunction( int iParamCount, sval_t val, unsigned int sourcePos ) { char *p = val.stringValue; str filename; str label; bool found = false; while( *p ) { if( p[ 0 ] == ':' && p[ 1 ] == ':' ) { p[ 0 ] = 0; filename = val.stringValue; label = p + 2; found = true; break; } p++; } SetOpcodeVarStackOffset( OP_FUNC, -iParamCount ); EmitOpcode( OP_FUNC, sourcePos ); if( !found ) { label = val.stringValue; EmitOpcodeValue(false, sizeof(bool)); EmitOpcodeValue(Director.AddString(label), sizeof(unsigned int)); } else { EmitOpcodeValue(true, sizeof(bool)); EmitOpcodeValue(Director.AddString(filename), sizeof(unsigned int)); EmitOpcodeValue(Director.AddString(label), sizeof(unsigned int)); } EmitOpcodeValue(iParamCount, sizeof(uint8_t)); } void ScriptCompiler::EmitIfElseJump( sval_t if_stmt, sval_t else_stmt, unsigned int sourcePos ) { unsigned char *jmp1, *jmp2; int label1, label2; label1 = EmitNot( sourcePos ); jmp1 = code_pos; code_pos += sizeof( unsigned int ); ClearPrevOpcode(); EmitValue( if_stmt ); if( showopcodes->integer ) { label2 = current_label++; glbs.DPrintf( "JUMP \n", label2 ); } EmitOpcode( OP_JUMP4, sourcePos ); jmp2 = code_pos; code_pos += sizeof( unsigned int ); ClearPrevOpcode(); if( showopcodes->integer ) { glbs.DPrintf( ":\n", label1 ); } AddJumpLocation( jmp1 ); EmitValue( else_stmt ); if( showopcodes->integer ) { glbs.DPrintf( ":\n", label1 ); } AddJumpLocation( jmp2 ); } void ScriptCompiler::EmitIfJump( sval_t if_stmt, unsigned int sourcePos ) { unsigned char *jmp; int label = EmitNot( sourcePos ); jmp = code_pos; code_pos += sizeof( unsigned int ); ClearPrevOpcode(); EmitValue( if_stmt ); if( showopcodes->integer ) { glbs.DPrintf( ":\n", label ); } AddJumpLocation( jmp ); } void ScriptCompiler::EmitInteger( unsigned int value, unsigned int sourcePos ) { if( value == 0 ) { EmitOpcode( OP_STORE_INT0, sourcePos ); } else if( value < 127 ) { EmitOpcode( OP_STORE_INT1, sourcePos ); EmitOpcodeValue(value, sizeof(byte)); } else if( value < 32767 ) { EmitOpcode(OP_STORE_INT2, sourcePos); EmitOpcodeValue(value, sizeof(short)); } else if( value < 8388607 ) { EmitOpcode(OP_STORE_INT3, sourcePos); EmitOpcodeValue(value, sizeof(short3)); } else { EmitOpcode(OP_STORE_INT4, sourcePos); EmitOpcodeValue(value, sizeof(int)); } } void ScriptCompiler::EmitJump( unsigned char *pos, unsigned int sourcePos ) { EmitOpcode( OP_JUMP4, sourcePos ); AddJumpToLocation( pos ); } void ScriptCompiler::EmitJumpBack( unsigned char *pos, unsigned int sourcePos ) { EmitOpcode( OP_JUMP_BACK4, sourcePos ); AddJumpBackLocation( pos ); } void ScriptCompiler::EmitLabel( str name, unsigned int sourcePos ) { if( showopcodes->integer ) { glbs.DPrintf( "<%s>:\n", name.c_str() ); } if( stateScript->AddLabel( name, code_pos ) ) { CompileError( sourcePos, "Duplicate label '%s'", name.c_str() ); } } void ScriptCompiler::EmitLabelParameterList( sval_t parameter_list, unsigned int sourcePos ) { sval_u param; ClearPrevOpcode(); param = parameter_list.node[ 0 ]; if( param.node && param.node[ 0 ].node[ 0 ].type != sval_none ) { EmitOpcode( OP_MARK_STACK_POS, sourcePos ); while( param.node ) { EmitParameter( param.node[ 0 ], param.node[ 2 ].sourcePosValue ); param = param.node[ 1 ]; } EmitOpcode( OP_RESTORE_STACK_POS, sourcePos ); } } void ScriptCompiler::EmitLabelPrivate( str name, unsigned int sourcePos ) { if( showopcodes->integer ) { glbs.DPrintf( "<%s>:\n", name.c_str() ); } if( stateScript->AddLabel( name, code_pos, true ) ) { CompileError( sourcePos, "Duplicate label '%s'", name.c_str() ); } } void ScriptCompiler::EmitLogicJump( sval_t logic_stmt, bool isOr, unsigned int sourcePos ) { unsigned char *jmp; int label; if( showopcodes->integer ) { label = current_label++; if( isOr ) { glbs.DPrintf( "BOOL_LOGICAL_OR \n", label ); } else { glbs.DPrintf( "BOOL_LOGICAL_AND \n", label ); } } EmitOpcode( OP_BOOL_LOGICAL_AND + isOr, sourcePos ); jmp = code_pos; code_pos += sizeof( unsigned int ); ClearPrevOpcode(); EmitValue( logic_stmt ); EmitVarToBool( sourcePos ); if( showopcodes->integer ) { glbs.DPrintf( ":\n", label ); } AddJumpLocation( jmp ); EmitBoolToVar( sourcePos ); } void ScriptCompiler::EmitMakeArray( sval_t val ) { int iCount = 0; sval_t *node; for( node = val.node[ 0 ].node; node != NULL; iCount++, node = node[ 1 ].node ) { EmitValue( node[ 0 ] ); } EmitConstArrayOpcode( iCount ); } void ScriptCompiler::EmitMethodExpression( int iParamCount, int eventnum, unsigned int sourcePos ) { if( iParamCount > 5 ) { SetOpcodeVarStackOffset( OP_EXEC_METHOD_COUNT1, -iParamCount ); EmitOpcode( OP_EXEC_METHOD_COUNT1, sourcePos ); EmitOpcodeValue(iParamCount, sizeof(byte)); } else { EmitOpcode( OP_EXEC_METHOD0 + iParamCount, sourcePos ); } EmitOpcodeValue(eventnum, sizeof(unsigned int)); } void ScriptCompiler::EmitNil( unsigned int sourcePos ) { if( showopcodes->integer ) { glbs.DPrintf( "\t\t%08d:\n", code_pos - code_ptr ); } } void ScriptCompiler::EmitNop() { EmitOpcode( OP_NOP, -1 ); } int ScriptCompiler::EmitNot( unsigned int sourcePos ) { int label = 0; if( PrevOpcode() == OP_BOOL_UN_NOT ) { AbsorbPrevOpcode(); if( showopcodes->integer ) { label = current_label++; glbs.DPrintf( "BOOL_JUMP_TRUE \n", label ); } EmitBoolJumpTrue( sourcePos ); } else { if( showopcodes->integer ) { label = current_label++; glbs.DPrintf( "BOOL_JUMP_FALSE \n", label ); } EmitBoolJumpFalse( sourcePos ); } return label; } void ScriptCompiler::EmitOpcode( int opcode, unsigned int sourcePos ) { int IsExternal; int iVarStackOffset; if( code_pos == NULL ) { Com_Printf( "Compiler not initialized !\n" ); return; } if( script->m_ProgToSource ) { str sourceLine; sourceinfo_t info; info.sourcePos = sourcePos; script->GetSourceAt( sourcePos, sourceLine, info.column, info.line ); script->m_ProgToSource->addKeyValue( code_pos ) = info; } IsExternal = IsExternalOpcode( opcode ); iVarStackOffset = OpcodeVarStackOffset( opcode ); if( IsExternal ) { if( m_iVarStackOffset > m_iMaxExternalVarStackOffset ) { m_iMaxExternalVarStackOffset = m_iVarStackOffset; } m_iHasExternal = 1; } m_iVarStackOffset += iVarStackOffset; if( !IsExternal ) { if( m_iVarStackOffset > m_iInternalMaxVarStackOffset ) { m_iInternalMaxVarStackOffset = m_iVarStackOffset; } } /*if( m_iInternalMaxVarStackOffset + 9 * m_iMaxExternalVarStackOffset + 1 > 255 ) { CompileError( sourcePos, "The required variable stack size of %d exceeds the statically allocated variable stack of size %d.\nIncrease SCRIPTTHREAD_VARSTACK_SIZE to at least %d and recompile.\n", m_iInternalMaxVarStackOffset + 9 * m_iMaxExternalVarStackOffset + 1, 255, m_iInternalMaxVarStackOffset + 9 * m_iMaxExternalVarStackOffset + 1 ); }*/ if( showopcodes->integer ) { glbs.DPrintf( "\t\t%08d: %s (%d) %s\n", code_pos - code_ptr, OpcodeName( opcode ), m_iVarStackOffset, IsExternal ? "[external]" : "" ); } prev_opcode_pos = ( prev_opcode_pos + 1 ) % 100; prev_opcodes[ prev_opcode_pos ].opcode = opcode; prev_opcodes[ prev_opcode_pos ].VarStackOffset = iVarStackOffset; prev_opcodes[ ( prev_opcode_pos + 1 ) % 100 ].opcode = OP_PREVIOUS; EmitOpcodeValue(opcode, sizeof(byte)); } void ScriptCompiler::EmitParameter( sval_t lhs, unsigned int sourcePos ) { if( lhs.node[ 0 ].type != sval_field ) { CompileError( sourcePos, "bad parameter lvalue: %d (expecting field)", lhs.node[ 0 ].type ); } sval_u listener_val = lhs.node[ 1 ]; const char *name = lhs.node[ 2 ].stringValue; unsigned int index = Director.AddString( name ); int eventnum = Event::FindSetterEventNum( name ); if( listener_val.node[ 0 ].type != sval_store_method || ( eventnum && BuiltinWriteVariable( sourcePos, listener_val.node[ 1 ].byteValue, eventnum ) ) ) { CompileError( sourcePos, "built-in field '%s' not allowed", name ); } else { EmitOpcode( OP_STORE_PARAM, sourcePos ); EmitOpcode( OP_LOAD_GAME_VAR + listener_val.node[ 1 ].byteValue, sourcePos ); EmitOpcodeValue(index, sizeof(unsigned int)); } } int ScriptCompiler::EmitParameterList( sval_t event_parameter_list ) { sval_t *node; int iParamCount = 0; for( node = event_parameter_list.node[ 1 ].node[ 0 ].node; node != NULL; node = node[ 1 ].node ) { if( node[ 0 ].node[ 0 ].type == sval_none ) { continue; } EmitValue( *node ); iParamCount++; } return iParamCount; } void ScriptCompiler::EmitRef( sval_t val, unsigned int sourcePos ) { unsigned int index; if( val.node[ 0 ].type != sval_field ) { if( val.node[ 0 ].type == sval_array ) { EmitRef( val.node[ 1 ], sourcePos ); EmitValue( val.node[ 2 ] ); EmitOpcode( OP_STORE_ARRAY_REF, val.node[ 3 ].sourcePosValue ); return; } else { CompileError( sourcePos, "bad lvalue: %d (expecting field or array)", val.node[ 0 ].type ); return; } } index = Director.AddString( val.node[ 2 ].stringValue ); EmitValue( val.node[ 1 ] ); EmitOpcode(OP_STORE_FIELD_REF, sourcePos); EmitOpcodeValue(index, sizeof(unsigned int)); } void ScriptCompiler::EmitStatementList( sval_t val ) { sval_t *node; for( node = val.node[ 0 ].node; node != NULL; node = node[ 1 ].node ) { EmitValue( *node ); } } void ScriptCompiler::EmitString( str value, unsigned int sourcePos ) { unsigned int index = Director.AddString( value ); if( showopcodes->integer ) { glbs.DPrintf( "\t\tSTRING \"%s\"\n", value.c_str() ); } EmitOpcode( OP_STORE_STRING, sourcePos ); EmitOpcodeValue(index, sizeof(unsigned int)); } void ScriptCompiler::EmitSwitch( sval_t val, unsigned int sourcePos ) { bool bStartCanBreak; int iStartBreakJumpLocCount; StateScript *m_oldStateScript; m_oldStateScript = stateScript; stateScript = script->CreateSwitchStateScript(); EmitOpcode( OP_SWITCH, sourcePos ); EmitOpcodeValue(stateScript, sizeof(StateScript*)); bStartCanBreak = bCanBreak; iStartBreakJumpLocCount = iBreakJumpLocCount; bCanBreak = true; EmitBreak( sourcePos ); EmitValue( val ); ProcessBreakJumpLocations( iStartBreakJumpLocCount ); bCanBreak = bStartCanBreak; stateScript = m_oldStateScript; } void ScriptCompiler::EmitValue( ScriptVariable &var, unsigned int sourcePos ) { if( var.GetType() == VARIABLE_INTEGER ) { EmitInteger( var.intValue(), sourcePos ); } else if( var.GetType() == VARIABLE_FLOAT ) { EmitFloat( var.floatValue(), sourcePos ); } } void ScriptCompiler::EmitValue( sval_t val ) { __emit: switch( val.node[ 0 ].type ) { case sval_none: break; case sval_and: case sval_or: EmitValue( val.node[ 1 ] ); EmitVarToBool( val.node[ 3 ].sourcePosValue ); EmitLogicJump( val.node[ 2 ], val.node[ 0 ].type == sval_or, val.node[ 3 ].sourcePosValue ); break; case sval_array: EmitValue( val.node[ 1 ] ); EmitValue( val.node[ 2 ] ); EmitOpcode( OP_STORE_ARRAY, val.node[ 3 ].sourcePosValue ); break; case sval_assignment: EmitValue( val.node[ 2 ] ); EmitAssignmentStatement( val.node[ 1 ], val.node[ 3 ].sourcePosValue ); break; case sval_break: return EmitBreak( val.node[ 1 ].sourcePosValue ); case sval_calc_vector: EmitValue( val.node[ 1 ] ); EmitValue( val.node[ 2 ] ); EmitValue( val.node[ 3 ] ); EmitOpcode( OP_CALC_VECTOR, val.node[ 4 ].sourcePosValue ); break; case sval_negative: val.node[ 1 ].intValue = -val.node[ 1 ].intValue; case sval_case: { sval_u case_parm = val.node[ 1 ]; if( case_parm.node[ 0 ].type == sval_store_integer ) { EmitLabel( case_parm.node[ 1 ].intValue, val.node[ 3 ].sourcePosValue ); } else if( case_parm.node[ 0 ].type == sval_store_string ) { EmitLabel( case_parm.node[ 1 ].stringValue, val.node[ 3 ].sourcePosValue ); } else if( case_parm.node[ 0 ].type == sval_func1 && case_parm.node[ 1 ].byteValue == OP_UN_MINUS ) { EmitLabel( -case_parm.node[ 2 ].node[ 1 ].intValue, val.node[ 3 ].sourcePosValue ); } else { CompileError( val.node[ 3 ].sourcePosValue, "bad case value: %d (expected integer or string)", case_parm.node[ 0 ].type ); } EmitLabelParameterList( val.node[ 2 ], val.node[ 3 ].sourcePosValue ); break; } case sval_catch: { unsigned char *old_code_pos = code_pos; ClearPrevOpcode(); EmitValue( val.node[ 1 ] ); EmitCatch( val.node[ 2 ], old_code_pos, val.node[ 3 ].sourcePosValue ); break; } case sval_cmd: { str cmd = val.node[ 1 ].stringValue; cmd.tolower(); int eventnum = Event::FindNormalEventNum( cmd ); int iParamCount = EmitParameterList( val.node[ 2 ] ); if( !eventnum ) { CompileError( val.node[ 3 ].sourcePosValue, "unknown command: %s", val.node[ 1 ].stringValue ); /*EmitOpcode( OP_STORE_LOCAL, val.node[ 3 ].sourcePosValue ); EmitFunction( iParamCount, val.node[ 1 ], val.node[ 3 ].sourcePosValue ); EmitOpcode( OP_LOAD_LOCAL_VAR, val.node[ 3 ].sourcePosValue ); *reinterpret_cast< unsigned int * >( code_pos ) = Director.AddString( "" ); code_pos += sizeof( unsigned int );*/ } else { if( iParamCount > 5 ) { SetOpcodeVarStackOffset( OP_EXEC_CMD_COUNT1, -iParamCount ); EmitOpcode( OP_EXEC_CMD_COUNT1, val.node[ 3 ].sourcePosValue ); EmitOpcodeValue(iParamCount, sizeof(byte)); } else { EmitOpcode( OP_EXEC_CMD0 + iParamCount, val.node[ 3 ].sourcePosValue ); } EmitOpcodeValue(eventnum, sizeof(unsigned int)); } break; } case sval_cmd_default_ret: { str cmd = val.node[ 1 ].stringValue; cmd.tolower(); int eventnum = Event::FindReturnEventNum( cmd ); int iParamCount = EmitParameterList( val.node[ 2 ] ); EmitOpcode( OP_STORE_LOCAL, val.node[ 3 ].sourcePosValue ); if( !eventnum ) { CompileError( val.node[ 3 ].sourcePosValue, "unknown return command: %s", val.node[ 1 ].stringValue ); //EmitFunction( iParamCount, val.node[ 1 ], val.node[ 3 ].sourcePosValue ); } else { EmitMethodExpression( iParamCount, eventnum, val.node[ 3 ].sourcePosValue ); } break; } case sval_cmd_method: { str cmd = val.node[ 2 ].stringValue; cmd.tolower(); int eventnum = Event::FindNormalEventNum( cmd ); int iParamCount = EmitParameterList( val.node[ 3 ] ); EmitValue( val.node[ 1 ] ); if( !eventnum ) { CompileError( val.node[ 4 ].sourcePosValue, "unknown command: %s", val.node[ 2 ].stringValue ); /*EmitFunction( iParamCount, val.node[ 2 ], val.node[ 4 ].sourcePosValue ); EmitOpcode( OP_LOAD_LOCAL_VAR, val.node[ 3 ].sourcePosValue ); *reinterpret_cast< unsigned int * >( code_pos ) = Director.AddString( "" ); code_pos += sizeof( unsigned int );*/ } else { if( iParamCount > 5 ) { SetOpcodeVarStackOffset( OP_EXEC_CMD_METHOD_COUNT1, -iParamCount - 1 ); EmitOpcode( OP_EXEC_CMD_METHOD_COUNT1, val.node[ 4 ].sourcePosValue ); EmitOpcodeValue(iParamCount, sizeof(byte)); } else { EmitOpcode( OP_EXEC_CMD_METHOD0 + iParamCount, val.node[ 4 ].sourcePosValue ); } EmitOpcodeValue(eventnum, sizeof(unsigned int)); } break; } case sval_cmd_method_ret: { str cmd = val.node[ 2 ].stringValue; cmd.tolower(); int eventnum = Event::FindReturnEventNum( cmd ); int iParamCount = EmitParameterList( val.node[ 3 ] ); EmitValue( val.node[ 1 ] ); if( !eventnum ) { CompileError( val.node[ 4 ].sourcePosValue, "unknown return command: %s", val.node[ 2 ].stringValue ); //EmitFunction( iParamCount ,val.node[ 2 ], val.node[ 4 ].sourcePosValue ); } else { EmitMethodExpression( iParamCount, eventnum, val.node[ 4 ].sourcePosValue ); } break; } case sval_constarray: return EmitConstArray( val.node[ 1 ], val.node[ 2 ], val.node[ 3 ].sourcePosValue ); case sval_continue: return EmitContinue( val.node[ 1 ].sourcePosValue ); case sval_do: return EmitDoWhileJump( val.node[ 1 ], val.node[ 2 ], val.node[ 3 ].sourcePosValue ); case sval_field: return EmitField( val.node[ 1 ], val.node[ 2 ], val.node[ 3 ].sourcePosValue ); case sval_func1: EmitValue( val.node[ 2 ] ); EmitFunc1( val.node[ 1 ].byteValue, val.node[ 3 ].sourcePosValue ); break; case sval_if: EmitValue( val.node[ 1 ] ); EmitVarToBool( val.node[ 4 ].sourcePosValue ); EmitIfJump( val.node[ 2 ], val.node[ 3 ].sourcePosValue ); break; case sval_ifelse: EmitValue( val.node[ 1 ] ); EmitVarToBool( val.node[ 4 ].sourcePosValue ); EmitIfElseJump( val.node[ 2 ], val.node[ 3 ], val.node[ 4 ].sourcePosValue ); break; case sval_label: EmitLabel( val.node[ 1 ].stringValue, val.node[ 3 ].sourcePosValue ); EmitLabelParameterList( val.node[ 2 ], val.node[ 3 ].sourcePosValue ); break; case sval_privatelabel: EmitLabelPrivate( val.node[ 1 ].stringValue, val.node[ 3 ].sourcePosValue ); EmitLabelParameterList( val.node[ 2 ], val.node[ 3 ].sourcePosValue ); break; case sval_makearray: return EmitMakeArray( val.node[ 1 ] ); case sval_not: EmitValue( val.node[ 1 ] ); EmitVarToBool( val.node[ 2 ].sourcePosValue ); EmitBoolNot( val.node[ 2 ].sourcePosValue ); EmitBoolToVar( val.node[ 2 ].sourcePosValue ); break; case sval_operation: EmitValue( val.node[ 2 ] ); EmitValue( val.node[ 3 ] ); EmitOpcode( val.node[ 1 ].byteValue, val.node[ 4 ].sourcePosValue ); break; case sval_statement_list: return EmitStatementList( val.node[ 1 ] ); case sval_store_float: EmitFloat( val.node[ 1 ].floatValue, val.node[ 2 ].sourcePosValue ); break; case sval_store_integer: EmitInteger( val.node[ 1 ].intValue, val.node[ 2 ].sourcePosValue ); break; case sval_store_method: EmitOpcode( OP_STORE_GAME + val.node[ 1 ].byteValue, val.node[ 2 ].sourcePosValue ); break; case sval_store_nil: EmitOpcode( OP_STORE_NIL, val.node[ 1 ].sourcePosValue ); break; case sval_store_null: EmitOpcode( OP_STORE_NULL, val.node[ 1 ].sourcePosValue ); break; case sval_store_string: EmitString( val.node[ 1 ].stringValue, val.node[ 2 ].sourcePosValue ); break; case sval_switch: EmitValue( val.node[ 1 ] ); EmitSwitch( val.node[ 2 ], val.node[ 3 ].sourcePosValue ); break; case sval_next: val = val.node[ 1 ]; goto __emit; // prevent stack overflow case sval_while: return EmitWhileJump( val.node[ 1 ], val.node[ 2 ], val.node[ 3 ], val.node[ 4 ].sourcePosValue ); default: CompileError( -1, "unknown type %d\n", val.node[ 0 ].type ); break; } } void ScriptCompiler::EmitVarToBool( unsigned int sourcePos ) { int prev = PrevOpcode(); if( prev == OP_STORE_INT0 ) { AbsorbPrevOpcode(); return EmitOpcode( OP_BOOL_STORE_FALSE, sourcePos ); } else if( prev > OP_STORE_INT0 ) { if( prev <= OP_STORE_INT4 ) { AbsorbPrevOpcode(); return EmitOpcode( OP_BOOL_STORE_TRUE, sourcePos ); } } else if( prev == OP_BOOL_TO_VAR ) { AbsorbPrevOpcode(); return EmitNil( sourcePos ); } return EmitOpcode( OP_UN_CAST_BOOLEAN, sourcePos ); } void ScriptCompiler::EmitWhileJump( sval_t while_expr, sval_t while_stmt, sval_t inc_stmt, unsigned int sourcePos ) { unsigned char *pos = code_pos; int label1, label2; if( showopcodes->integer ) { label1 = current_label++; glbs.DPrintf( ":\n", label1 ); } ClearPrevOpcode(); EmitValue( while_expr ); EmitVarToBool( sourcePos ); label2 = EmitNot( sourcePos ); unsigned char *jmp = code_pos; code_pos += sizeof( unsigned int ); ClearPrevOpcode(); bool old_bCanBreak = bCanBreak; bool old_bCanContinue = bCanContinue; int breakCount = iBreakJumpLocCount; int continueCount = iContinueJumpLocCount; bCanBreak = true; bCanContinue = true; EmitValue( while_stmt ); ProcessContinueJumpLocations( continueCount ); bCanContinue = old_bCanContinue; EmitValue( inc_stmt ); if( showopcodes->integer ) { glbs.DPrintf( "JUMP_BACK4 \n", label1 ); } EmitJumpBack( pos, sourcePos ); ClearPrevOpcode(); if( showopcodes->integer ) { glbs.DPrintf( ":\n", label2 ); } AddJumpLocation( jmp ); ProcessBreakJumpLocations( breakCount ); bCanBreak = old_bCanBreak; } bool ScriptCompiler::EvalPrevValue( ScriptVariable& var ) { int intValue = 0; float floatValue = 0.0f; switch( PrevOpcode() ) { case OP_STORE_INT0: intValue = 0; break; case OP_STORE_INT1: intValue = GetOpcodeValue(sizeof(byte), sizeof(byte)); break; case OP_STORE_INT2: intValue = GetOpcodeValue(sizeof(short), sizeof(short)); break; case OP_STORE_INT3: intValue = GetOpcodeValue(sizeof(short3), sizeof(short3)); break; case OP_STORE_INT4: intValue = GetOpcodeValue(sizeof(int), sizeof(int)); break; case OP_STORE_FLOAT: intValue = GetOpcodeValue(sizeof(float), sizeof(float)); var.setFloatValue( floatValue ); return true; default: return false; } var.setIntValue( intValue ); return true; } void ScriptCompiler::ProcessBreakJumpLocations( int iStartBreakJumpLocCount ) { if( iBreakJumpLocCount > iStartBreakJumpLocCount ) { do { iBreakJumpLocCount--; unsigned int offset = code_pos - sizeof( unsigned int ) - apucBreakJumpLocations[ iBreakJumpLocCount ]; EmitAt(apucBreakJumpLocations[iBreakJumpLocCount], offset, sizeof(unsigned int)); } while( iBreakJumpLocCount > iStartBreakJumpLocCount ); ClearPrevOpcode(); } } void ScriptCompiler::ProcessContinueJumpLocations( int iStartContinueJumpLocCount ) { if( iContinueJumpLocCount > iStartContinueJumpLocCount ) { do { iContinueJumpLocCount--; unsigned int offset = code_pos - sizeof( unsigned int ) - apucContinueJumpLocations[ iContinueJumpLocCount ]; EmitAt(apucContinueJumpLocations[iContinueJumpLocCount], offset, sizeof(unsigned int)); } while( iContinueJumpLocCount > iStartContinueJumpLocCount ); ClearPrevOpcode(); } } unsigned char *ScriptCompiler::GetPosition() { return code_pos; } void ScriptCompiler::CompileError( unsigned int sourcePos, const char *format, ... ) { char buffer[ 4000 ]; va_list va; va_start( va, format ); vsprintf( buffer, format, va ); va_end( va ); compileSuccess = false; glbs.DPrintf( "%s\n", buffer ); script->PrintSourcePos( sourcePos, false ); throw ScriptException( buffer ); } int yyparse(); char *ScriptCompiler::Preprocess( char *sourceBuffer ) { // FIXME (ley0k): Preprocessor (#defines and #includes) return sourceBuffer; } void ScriptCompiler::Preclean( char *processedBuffer ) { } extern int prev_yylex; int yyerror( const char *msg ) { //parsedata.pos -= yyleng; parsedata.exc.yylineno = prev_yylex != TOKEN_EOL ? yylineno : yylineno - 1; parsedata.exc.yytext = yytext; parsedata.exc.yytoken = msg; //str line = ScriptCompiler::GetLine( parsedata.sourceBuffer, parsedata.exc.yylineno ); glbs.Printf( "parse error:\n%s:\n", parsedata.exc.yytoken.c_str() ); parsedata.gameScript->PrintSourcePos( parsedata.pos - yyleng, false ); parsedata.pos++; return 1; } size_t ScriptCompiler::Parse( GameScript *gameScript, char *sourceBuffer ) { parsedata = yyparsedata(); parsedata.sourceBuffer = sourceBuffer; parsedata.gameScript = gameScript; parsedata.braces_count = 0; prev_yylex = 0; script = gameScript; stateScript = &gameScript->m_State; yy_scan_string( sourceBuffer ); parsetree_init(); try { if( yyparse() != 0 || parsedata.exc.yytoken != "" ) { // an error occured yylex_destroy(); if( !parsedata.exc.yytext ) { if( parsedata.braces_count ) { glbs.DPrintf( "unmatching {} pair\n" ); } else { glbs.DPrintf( "unexpected end of file found\n" ); } } return 0; } } catch( ScriptException& exc ) { exc; return 0; } yylex_destroy(); return parsedata.total_length; } size_t ScriptCompiler::Compile( GameScript *gameScript, unsigned char *progBuffer ) { size_t length; if( progBuffer == NULL ) { glbs.DPrintf( "Invalid program buffer\n" ); return 0; } code_pos = progBuffer; code_ptr = progBuffer; prog_ptr = progBuffer; gameScript->m_ProgToSource = new con_set < unsigned char *, sourceinfo_t >; compileSuccess = true; prev_opcodes[ prev_opcode_pos ].opcode = OP_PREVIOUS; try { EmitValue( parsedata.val ); EmitEof( -1 ); if( compileSuccess ) { stateScript->AddLabel( "", code_ptr ); length = code_pos - code_ptr; } else { length = 0; } prog_end_ptr = code_pos; } catch( ScriptException& exc ) { exc; length = 0; prog_end_ptr = code_pos; } parsetree_freeall(); return length; } str ScriptCompiler::GetLine( str content, int line ) { char *p; str found; int i = 1; p = ( char * )content.c_str(); while( *p ) { if( *p == '\n' ) { i++; } if( i >= line ) { found = strtok( p, "\n" ); break; } p++; } return found; } template void ScriptCompiler::EmitOpcodeValue(const Value& value, size_t size) { Com_Memcpy(code_pos, &value, size); code_pos += size; } template void ScriptCompiler::EmitAt(unsigned char* location, const Value& value, size_t size) { Com_Memcpy(location, &value, size); } template void ScriptCompiler::SetOpcodeValue(const Value& value) { Com_Memcpy(code_pos, &value, sizeof(value)); } template Value ScriptCompiler::GetOpcodeValue(size_t size) const { Value val{ 0 }; Com_Memcpy(&val, code_pos, size); return val; } template Value ScriptCompiler::GetOpcodeValue(size_t offset, size_t size) const { Value val{ 0 }; Com_Memcpy(&val, code_pos - offset, size); return val; } void CompileAssemble( const char *filename, const char *outputfile ) { GameScript *gameScript = Director.GetGameScript( filename ); Archiver arc; if( !gameScript->m_ProgBuffer || !gameScript->m_ProgLength ) { return; } arc.Create( outputfile ); if( !arc.NoErrors() ) { glbs.DPrintf( "Error saving to file '%s'\n", filename ); return; } gameScript->m_bPrecompiled = true; gameScript->Archive( arc ); // dump the compiled script to file arc.Close(); } bool GetCompiledScript( GameScript *scr ) { Archiver arc; arc.SetSilent( true ); arc.Read( scr->Filename(), false ); if( !arc.NoErrors() ) { return false; } arc.SetSilent( false ); scr->Archive( arc ); arc.Close(); return true; }