r5sdk/r5dev/tier1/exprevaluator.cpp
Kawe Mazidjatari ee82d5d8e0 Tier1: move KeyValues class to Tier1
The KeyValues class belongs here. Also reimplemented most loading methods for KeyValues, and adjusted the VPK building code to account for it. Pointers to the engine's implementation of KeyValues have been moved to a separate header ('keyvalues_iface.h'), as this allows external tools code to utilize the standalone KeyValues class implementation. Playlist utilities are completely separated from the KeyValues header; these have nothing to do with KeyValues other than manipulating a global KeyValues object for the playlists, and thus have been named as such and moved to rtech/playlists.
2024-04-05 17:42:05 +02:00

494 lines
11 KiB
C++
Raw Blame History

//===== Copyright <20> 1996-2006, Valve Corporation, All rights reserved. ======//
//
// Purpose: ExprSimplifier builds a binary tree from an infix expression (in the
// form of a character array). Evaluates C style infix parenthetic logical
// expressions. Supports !, ||, &&, (). Symbols are resolved via callback.
// Syntax is $<name>. $0 evaluates to false. $<number> evaluates to true.
// e.g: ( $1 || ( $FOO || $WHATEVER ) && !$BAR )
//===========================================================================//
#include <ctype.h>
#include "ikeyvaluessystem.h"
#include "tier1/exprevaluator.h"
#include "tier1/convar.h"
#include "tier1/fmtstr.h"
#include "tier1/strtools.h"
#include "tier0/dbg.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// Default conditional symbol handler callback. Symbols are the form $<name>.
// Return true or false for the value of the symbol.
//-----------------------------------------------------------------------------
bool DefaultConditionalSymbolProc( const char *pKey )
{
if ( pKey[0] == '$' )
{
pKey++;
}
if ( !V_stricmp( pKey, "WIN32" ) )
{
return IsPC();
}
if ( !V_stricmp( pKey, "WINDOWS" ) )
{
return IsPlatformWindowsPC();
}
if ( !V_stricmp( pKey, "X360" ) )
{
return IsX360();
}
if ( !V_stricmp( pKey, "PS3" ) )
{
return IsPS3();
}
if ( !V_stricmp( pKey, "OSX" ) )
{
return IsPlatformOSX();
}
if ( !V_stricmp( pKey, "LINUX" ) )
{
return IsPlatformLinux();
}
if ( !V_stricmp( pKey, "POSIX" ) )
{
return IsPlatformPosix();
}
if ( !V_stricmp( pKey, "GAMECONSOLE" ) )
{
return IsGameConsole();
}
if ( !V_stricmp( pKey, "DEMO" ) )
{
#if defined( _DEMO )
return true;
#else
return false;
#endif
}
if ( !V_stricmp( pKey, "LOWVIOLENCE" ) )
{
#if defined( _LOWVIOLENCE )
return true;
#endif
// If it is not a LOWVIOLENCE binary build, then fall through
// and check if there was a run-time symbol installed for it
}
// don't know it at compile time, so fall through to installed symbol values
return KeyValuesSystem()->GetKeyValuesExpressionSymbol( pKey );
}
void DefaultConditionalErrorProc( const char *pReason )
{
Warning( eDLL_T::COMMON, "Conditional Error: %s\n", pReason );
}
CExpressionEvaluator::CExpressionEvaluator()
{
m_ExprTree = NULL;
}
CExpressionEvaluator::~CExpressionEvaluator()
{
FreeTree( m_ExprTree );
}
//-----------------------------------------------------------------------------
// Sets mCurToken to the next token in the input string. Skips all whitespace.
//-----------------------------------------------------------------------------
char CExpressionEvaluator::GetNextToken( void )
{
// while whitespace, Increment CurrentPosition
while ( m_pExpression[m_CurPosition] == ' ' )
++m_CurPosition;
// CurrentToken = Expression[CurrentPosition]
m_CurToken = m_pExpression[m_CurPosition++];
return m_CurToken;
}
//-----------------------------------------------------------------------------
// Utility funcs
//-----------------------------------------------------------------------------
void CExpressionEvaluator::FreeNode( ExprNode *pNode )
{
delete pNode;
}
ExprNode *CExpressionEvaluator::AllocateNode( void )
{
return new ExprNode;
}
void CExpressionEvaluator::FreeTree( ExprTree& node )
{
if ( !node )
return;
FreeTree( node->left );
FreeTree( node->right );
FreeNode( node );
node = 0;
}
bool CExpressionEvaluator::IsConditional( bool &bConditional, const char token )
{
char nextchar = ' ';
if ( token == OR_OP || token == AND_OP )
{
// expect || or &&
nextchar = m_pExpression[m_CurPosition++];
if ( (token & nextchar) == token )
{
bConditional = true;
}
else if ( m_pSyntaxErrorProc )
{
m_pSyntaxErrorProc( CFmtStr( "Bad expression operator: '%c%c', expected C style operator", token, nextchar ) );
return false;
}
}
else
{
bConditional = false;
}
// valid
return true;
}
bool CExpressionEvaluator::IsNotOp( const char token )
{
if ( token == NOT_OP )
return true;
else
return false;
}
bool CExpressionEvaluator::IsIdentifierOrConstant( const char token )
{
bool success = false;
if ( token == '$' )
{
// store the entire identifier
int i = 0;
m_Identifier[i++] = token;
while( (V_isalnum( m_pExpression[m_CurPosition] ) || m_pExpression[m_CurPosition] == '_') && i < MAX_IDENTIFIER_LEN )
{
m_Identifier[i] = m_pExpression[m_CurPosition];
++m_CurPosition;
++i;
}
if ( i < MAX_IDENTIFIER_LEN - 1 )
{
m_Identifier[i] = '\0';
success = true;
}
}
else
{
if ( V_isdigit( token ) )
{
int i = 0;
m_Identifier[i++] = token;
while( V_isdigit( m_pExpression[m_CurPosition] ) && ( i < MAX_IDENTIFIER_LEN ) )
{
m_Identifier[i] = m_pExpression[m_CurPosition];
++m_CurPosition;
++i;
}
if ( i < MAX_IDENTIFIER_LEN - 1 )
{
m_Identifier[i] = '\0';
success = true;
}
}
}
return success;
}
bool CExpressionEvaluator::MakeExprNode( ExprTree &tree, char token, Kind kind, ExprTree left, ExprTree right )
{
tree = AllocateNode();
tree->left = left;
tree->right = right;
tree->kind = kind;
switch ( kind )
{
case CONDITIONAL:
tree->data.cond = token;
break;
case LITERAL:
if ( V_isdigit( m_Identifier[0] ) )
{
tree->data.value = ( atoi( m_Identifier ) != 0 );
}
else
{
tree->data.value = m_pGetSymbolProc( m_Identifier );
}
break;
case NOT:
break;
default:
if ( m_pSyntaxErrorProc )
{
Assert( 0 );
m_pSyntaxErrorProc( CFmtStr( "Logic Error in CExpressionEvaluator" ) );
}
return false;
}
return true;
}
//-----------------------------------------------------------------------------
// Makes a factor :: { <expression> } | <identifier>.
//-----------------------------------------------------------------------------
bool CExpressionEvaluator::MakeFactor( ExprTree &tree )
{
if ( m_CurToken == '(' )
{
// Get the next token
GetNextToken();
// Make an expression, setting Tree to point to it
if ( !MakeExpression( tree ) )
{
return false;
}
}
else if ( IsIdentifierOrConstant( m_CurToken ) )
{
// Make a literal node, set Tree to point to it, set left/right children to NULL.
if ( !MakeExprNode( tree, m_CurToken, LITERAL, NULL, NULL ) )
{
return false;
}
}
else if ( IsNotOp( m_CurToken ) )
{
// do nothing
return true;
}
else
{
// This must be a bad token
if ( m_pSyntaxErrorProc )
{
m_pSyntaxErrorProc( CFmtStr( "Bad expression token: %c", m_CurToken ) );
}
return false;
}
// Get the next token
GetNextToken();
return true;
}
//-----------------------------------------------------------------------------
// Makes a term :: <factor> { <not> }.
//-----------------------------------------------------------------------------
bool CExpressionEvaluator::MakeTerm( ExprTree &tree )
{
// Make a factor, setting Tree to point to it
if ( !MakeFactor( tree ) )
{
return false;
}
// while the next token is !
while ( IsNotOp( m_CurToken ) )
{
// Make an operator node, setting left child to Tree and right to NULL. (Tree points to new node)
if ( !MakeExprNode( tree, m_CurToken, NOT, tree, NULL ) )
{
return false;
}
// Get the next token.
GetNextToken();
// Make a factor, setting the right child of Tree to point to it.
if ( !MakeFactor( tree->right ) )
{
return false;
}
}
return true;
}
//-----------------------------------------------------------------------------
// Makes a complete expression :: <term> { <cond> <term> }.
//-----------------------------------------------------------------------------
bool CExpressionEvaluator::MakeExpression( ExprTree &tree )
{
// Make a term, setting Tree to point to it
if ( !MakeTerm( tree ) )
{
return false;
}
// while the next token is a conditional
while ( 1 )
{
bool bConditional = false;
bool bValid = IsConditional( bConditional, m_CurToken );
if ( !bValid )
{
return false;
}
if ( !bConditional )
{
break;
}
// Make a conditional node, setting left child to Tree and right to NULL. (Tree points to new node)
if ( !MakeExprNode( tree, m_CurToken, CONDITIONAL, tree, NULL ) )
{
return false;
}
// Get the next token.
GetNextToken();
// Make a term, setting the right child of Tree to point to it.
if ( !MakeTerm( tree->right ) )
{
return false;
}
}
return true;
}
//-----------------------------------------------------------------------------
// returns true for success, false for failure
//-----------------------------------------------------------------------------
bool CExpressionEvaluator::BuildExpression( void )
{
// Get the first token, and build the tree.
GetNextToken();
return ( MakeExpression( m_ExprTree ) );
}
//-----------------------------------------------------------------------------
// returns the value of the node after resolving all children
//-----------------------------------------------------------------------------
bool CExpressionEvaluator::SimplifyNode( ExprTree& node )
{
if ( !node )
return false;
// Simplify the left and right children of this node
bool leftVal = SimplifyNode(node->left);
bool rightVal = SimplifyNode(node->right);
// Simplify this node
switch( node->kind )
{
case NOT:
// the child of '!' is always to the right
node->data.value = !rightVal;
break;
case CONDITIONAL:
if ( node->data.cond == AND_OP )
{
node->data.value = leftVal && rightVal;
}
else // OR_OP
{
node->data.value = leftVal || rightVal;
}
break;
default: // LITERAL
break;
}
// This node has beed resolved
node->kind = LITERAL;
return node->data.value;
}
//-----------------------------------------------------------------------------
// Interface to solve a conditional expression. Returns false on failure, Result is undefined.
//-----------------------------------------------------------------------------
bool CExpressionEvaluator::Evaluate( bool &bResult, const char *pInfixExpression, GetSymbolProc_t pGetSymbolProc, SyntaxErrorProc_t pSyntaxErrorProc )
{
if ( !pInfixExpression )
{
return false;
}
// for caller simplicity, we strip of any enclosing braces
// strip the bracketing [] if present
char szCleanToken[512];
if ( pInfixExpression[0] == '[' )
{
size_t len = V_strlen( pInfixExpression );
// SECURITY: Bail on input buffers that are too large, they're used for RCEs and we don't
// need to support them.
if ( len + 1 > ARRAYSIZE( szCleanToken ) )
{
return false;
}
V_strncpy( szCleanToken, pInfixExpression + 1, len );
len--;
if ( szCleanToken[len-1] == ']' )
{
szCleanToken[len-1] = '\0';
}
pInfixExpression = szCleanToken;
}
// reset state
m_pExpression = pInfixExpression;
m_pGetSymbolProc = pGetSymbolProc ? pGetSymbolProc : DefaultConditionalSymbolProc;
m_pSyntaxErrorProc = pSyntaxErrorProc ? pSyntaxErrorProc : DefaultConditionalErrorProc;
m_ExprTree = 0;
m_CurPosition = 0;
m_CurToken = 0;
// Building the expression tree will fail on bad syntax
const bool bValid = BuildExpression();
if ( bValid )
{
bResult = SimplifyNode( m_ExprTree );
}
// don't leak
FreeTree( m_ExprTree );
m_ExprTree = NULL;
return bValid;
}