csgo-2018-source/utils/vpc/scriptsource.cpp
2021-07-24 21:11:47 -07:00

565 lines
12 KiB
C++

//===================== Copyright (c) Valve Corporation. All Rights Reserved. ======================
//
//
//
//==================================================================================================
#include "vpc.h"
#define MAX_SCRIPT_STACK_SIZE 32
CScript::CScript()
{
m_ScriptName = "(empty)";
m_nScriptLine = 0;
m_pScriptData = NULL;
m_pScriptLine = &m_nScriptLine;
m_Token[0] = '\0';
m_PeekToken[0] = '\0';
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
const char *CScript::SkipWhitespace( const char *data, bool *pHasNewLines, int* pNumLines )
{
int c;
while ( ( c = *data ) <= ' ' )
{
if ( c == '\n' )
{
if ( pNumLines )
{
(*pNumLines)++;
}
if ( pHasNewLines )
{
*pHasNewLines = true;
}
}
else if ( !c )
{
return ( NULL );
}
data++;
}
return data;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
const char *CScript::SkipToValidToken( const char *data, bool *pHasNewLines, int* pNumLines )
{
int c;
for ( ;; )
{
data = SkipWhitespace( data, pHasNewLines, pNumLines );
c = *data;
if ( !c )
{
break;
}
if ( c == '/' && data[1] == '/' )
{
// skip double slash comments
data += 2;
while ( *data && *data != '\n' )
{
data++;
}
if ( *data && *data == '\n' )
{
data++;
if ( pNumLines )
{
(*pNumLines)++;
}
if ( pHasNewLines )
{
*pHasNewLines = true;
}
}
}
else if ( c == '/' && data[1] == '*' )
{
// skip /* */ comments
data += 2;
while ( *data && ( *data != '*' || data[1] != '/' ) )
{
if ( *data == '\n' )
{
if ( pNumLines )
{
(*pNumLines)++;
}
if ( pHasNewLines )
{
*pHasNewLines = true;
}
}
data++;
}
if ( *data )
{
data += 2;
}
}
else
{
break;
}
}
return data;
}
//-----------------------------------------------------------------------------
// The next token should be an open brace.
// Skips until a matching close brace is found.
// Internal brace depths are properly skipped.
//-----------------------------------------------------------------------------
void CScript::SkipBracedSection( const char** dataptr, int* numlines )
{
const char* token;
int depth;
depth = 0;
do
{
token = GetToken( dataptr, true, numlines );
if ( token[1] == '\0' )
{
if ( token[0] == '{' )
depth++;
else if ( token[0] == '}' )
depth--;
}
}
while( depth && *dataptr );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CScript::SkipRestOfLine( const char** dataptr, int* numlines )
{
const char* p;
int c;
p = *dataptr;
while ( ( c = *p++ ) != '\0' )
{
if ( c == '\n' )
{
if ( numlines )
( *numlines )++;
break;
}
}
*dataptr = p;
}
//-----------------------------------------------------------------------------
// Does not corrupt results obtained with GetToken().
//-----------------------------------------------------------------------------
const char* CScript::PeekNextToken( const char *dataptr, bool bAllowLineBreaks )
{
// save the primary token, about to be corrupted
char savedToken[MAX_SYSTOKENCHARS];
V_strncpy( savedToken, m_Token, MAX_SYSTOKENCHARS );
const char *pSaved = dataptr;
const char *pToken = GetToken( &pSaved, bAllowLineBreaks, NULL );
// restore
V_strncpy( m_PeekToken, pToken, MAX_SYSTOKENCHARS );
V_strncpy( m_Token, savedToken, MAX_SYSTOKENCHARS );
return m_PeekToken;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
const char *CScript::GetToken( const char **dataptr, bool allowLineBreaks, int *pNumLines )
{
char c;
char endSymbol;
int len;
bool hasNewLines;
const char* data;
c = 0;
data = *dataptr;
len = 0;
m_Token[0] = 0;
hasNewLines = false;
// make sure incoming data is valid
if ( !data )
{
*dataptr = NULL;
return m_Token;
}
for ( ;; )
{
// skip whitespace
data = SkipWhitespace( data, &hasNewLines, pNumLines );
if ( !data )
{
*dataptr = NULL;
return m_Token;
}
if ( hasNewLines && !allowLineBreaks )
{
*dataptr = data;
return m_Token;
}
c = *data;
if ( c == '/' && data[1] == '/' )
{
// skip double slash comments
data += 2;
while ( *data && *data != '\n' )
{
data++;
}
if ( *data && *data == '\n' )
{
if ( !allowLineBreaks )
continue;
data++;
if ( pNumLines )
{
(*pNumLines)++;
}
}
}
else if ( c =='/' && data[1] == '*' )
{
// skip /* */ comments
data += 2;
while ( *data && ( *data != '*' || data[1] != '/' ) )
{
if ( *data == '\n' && pNumLines )
{
(*pNumLines)++;
}
data++;
}
if ( *data )
{
data += 2;
}
}
else
break;
}
// handle scoped strings "???" <???> [???]
if ( c == '\"' || c == '<' || c == '[')
{
bool bConditionalExpression = false;
endSymbol = '\0';
switch ( c )
{
case '\"':
endSymbol = '\"';
break;
case '<':
endSymbol = '>';
break;
case '[':
bConditionalExpression = true;
endSymbol = ']';
break;
}
// want to preserve entire conditional expession [blah...blah...blah]
// maintain a conditional's open/close scope characters
if ( !bConditionalExpression )
{
// skip past scope character
data++;
}
for ( ;; )
{
c = *data++;
if ( c == endSymbol || !c )
{
if ( c == endSymbol && bConditionalExpression )
{
// keep end symbol
m_Token[len++] = c;
}
m_Token[len] = 0;
*dataptr = (char*)data;
return m_Token;
}
if ( len < MAX_SYSTOKENCHARS-1 )
{
m_Token[len++] = c;
}
}
}
// parse a regular word
do
{
if ( len < MAX_SYSTOKENCHARS )
{
m_Token[len++] = c;
}
data++;
c = *data;
}
while ( c > ' ' );
if ( len >= MAX_SYSTOKENCHARS )
{
len = 0;
}
m_Token[len] = '\0';
*dataptr = (char*)data;
return m_Token;
}
void CScript::PushScript( const char *pFilename )
{
// parse the text script
if ( !Sys_Exists( pFilename ) )
{
g_pVPC->VPCError( "Cannot open %s", pFilename );
}
char *pScriptBuffer = NULL; // Sys_LoadTextFileWithIncludes does not unconditionally initialize this.
Sys_LoadTextFileWithIncludes( pFilename, &pScriptBuffer );
PushScript( pFilename, pScriptBuffer, 1, true );
}
void CScript::PushScript( const char *pScriptName, const char *pScriptData, int nScriptLine, bool bFreeScriptAtPop )
{
if ( m_ScriptStack.Count() > MAX_SCRIPT_STACK_SIZE )
{
g_pVPC->VPCError( "PushScript( scriptname=%s ) - stack overflow\n", pScriptName );
}
// Push the current state onto the stack.
m_ScriptStack.Push( GetCurrentScript() );
// Set their state as the current state.
m_ScriptName = pScriptName;
m_pScriptData = pScriptData;
m_nScriptLine = nScriptLine;
m_bFreeScriptAtPop = bFreeScriptAtPop;
}
void CScript::PushCurrentScript()
{
PushScript( m_ScriptName.Get(), m_pScriptData, m_nScriptLine, m_bFreeScriptAtPop );
}
CScriptSource CScript::GetCurrentScript()
{
return CScriptSource( m_ScriptName.Get(), m_pScriptData, m_nScriptLine, m_bFreeScriptAtPop );
}
void CScript::RestoreScript( const CScriptSource &scriptSource )
{
m_ScriptName = scriptSource.GetName();
m_pScriptData = scriptSource.GetData();
m_nScriptLine = scriptSource.GetLine();
m_bFreeScriptAtPop = scriptSource.IsFreeScriptAtPop();
}
void CScript::PopScript()
{
if ( m_ScriptStack.Count() == 0 )
{
g_pVPC->VPCError( "PopScript(): stack is empty" );
}
if ( m_bFreeScriptAtPop && m_pScriptData )
{
free( (void *)m_pScriptData );
}
// Restore the top entry on the stack and pop it off.
const CScriptSource &state = m_ScriptStack.Top();
m_ScriptName = state.GetName();
m_pScriptData = state.GetData();
m_nScriptLine = state.GetLine();
m_bFreeScriptAtPop = state.IsFreeScriptAtPop();
m_ScriptStack.Pop();
}
void CScript::EnsureScriptStackEmpty()
{
if ( m_ScriptStack.Count() != 0 )
{
g_pVPC->VPCError( "EnsureScriptStackEmpty(): script stack is not empty!" );
}
}
void CScript::SpewScriptStack()
{
if ( m_ScriptStack.Count() )
{
CUtlString str;
// emit stack with current at top
str += "Script Stack:\n";
str += CFmtStr( " %s Line:%d\n", m_ScriptName.String(), m_nScriptLine );
for ( int i = m_ScriptStack.Count() - 1; i >= 0; i-- )
{
if ( i == 0 && !m_ScriptStack[i].GetData() && m_ScriptStack[i].GetLine() <= 0 )
{
// ignore empty bottom of stack
break;
}
str += CFmtStr( " %s Line:%d\n", m_ScriptStack[i].GetName(), m_ScriptStack[i].GetLine() );
}
str += "\n";
Log_Msg( LOG_VPC, "%s", str.String() );
}
}
const char *CScript::GetToken( bool bAllowLineBreaks )
{
return GetToken( &m_pScriptData, bAllowLineBreaks, m_pScriptLine );
}
const char *CScript::PeekNextToken( bool bAllowLineBreaks )
{
return PeekNextToken( m_pScriptData, bAllowLineBreaks );
}
void CScript::SkipRestOfLine()
{
SkipRestOfLine( &m_pScriptData, m_pScriptLine );
}
void CScript::SkipBracedSection()
{
SkipBracedSection( &m_pScriptData, m_pScriptLine );
}
void CScript::SkipToValidToken()
{
m_pScriptData = SkipToValidToken( m_pScriptData, NULL, m_pScriptLine );
}
//-----------------------------------------------------------------------------
// Handles expressions of the form <$BASE> <xxx> ... <xxx> [condition]
// Output is a concatenated string.
//
// Returns true if expression should be used, false if it should be ignored due
// to an optional condition that evaluated false.
//-----------------------------------------------------------------------------
bool CScript::ParsePropertyValue( const char *pBaseString, char *pOutBuff, int outBuffSize )
{
const char **pScriptData = &m_pScriptData;
int *pScriptLine = m_pScriptLine;
const char *pToken;
const char *pNextToken;
char *pOut = pOutBuff;
int remaining = outBuffSize-1;
int len;
bool bAllowNextLine = false;
char buffer1[MAX_SYSTOKENCHARS];
char buffer2[MAX_SYSTOKENCHARS];
bool bResult = true;
while ( 1 )
{
pToken = GetToken( pScriptData, bAllowNextLine, pScriptLine );
if ( !pToken || !pToken[0] )
g_pVPC->VPCSyntaxError();
pNextToken = PeekNextToken( *pScriptData, false );
if ( !pNextToken || !pNextToken[0] )
{
// current token is last token
// last token can be optional conditional, need to identify
// backup and reparse up to last token
if ( pToken && pToken[0] == '[' )
{
// last token is an optional conditional
bResult = g_pVPC->EvaluateConditionalExpression( pToken );
break;
}
}
if ( !V_stricmp( pToken, "\\" ) )
{
bAllowNextLine = true;
continue;
}
else
{
bAllowNextLine = false;
}
if ( !V_stricmp( pToken, "\\n" ) )
{
pToken = "\n";
}
if ( pToken[0] )
{
// handle reserved macro
if ( !pBaseString )
pBaseString = "";
strcpy( buffer1, pToken );
Sys_ReplaceString( buffer1, "$base", pBaseString, buffer2, sizeof( buffer2 ) );
g_pVPC->ResolveMacrosInString( buffer2, buffer1, sizeof( buffer1 ) );
len = strlen( buffer1 );
if ( remaining < len )
len = remaining;
if ( len > 0 )
{
memcpy( pOut, buffer1, len );
pOut += len;
remaining -= len;
}
}
pToken = PeekNextToken( *pScriptData, false );
if ( !pToken || !pToken[0] )
break;
}
*pOut++ = '\0';
if ( !pOutBuff[0] )
g_pVPC->VPCSyntaxError();
return bResult;
}