You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
565 lines
12 KiB
565 lines
12 KiB
//===================== 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;
|
|
}
|