Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add script expression parsing support for primitive functions with parameter lists #377

Merged
merged 8 commits into from May 6, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
229 changes: 175 additions & 54 deletions DRODLib/Character.cpp
Expand Up @@ -51,6 +51,12 @@ const UINT MAX_ANSWERS = 9;

#define SKIP_WHITESPACE(str, index) while (iswspace(str[index])) ++index

//*****************************************************************************
inline bool isVarCharValid(WCHAR wc)
{
return iswalnum(wc) || wc == W_t('_');
}

//*****************************************************************************
bool addWithClamp(int& val, const int operand)
//Multiplies two integers, ensuring the product doesn't overflow.
Expand Down Expand Up @@ -93,6 +99,15 @@ inline bool multWithClamp(int& val, const int operand)
return true;
}

void LogParseError(const WCHAR* pwStr, const char* message)
{
CFiles f;
string str = UnicodeToUTF8(pwStr);
str += ": ";
str += message;
f.AppendErrorLog(str.c_str());
}

inline bool bCommandHasData(const UINT eCommand)
//Returns: whether this script command has a data record attached to it
{
Expand Down Expand Up @@ -818,7 +833,7 @@ bool CCharacter::IsValidExpression(
//
//Params:
const WCHAR *pwStr, UINT& index, CDbHold *pHold,
const bool bExpectCloseParen) //[default=false] whether a close paren should mark the end of this (nested) expression
const char closingChar) ////[default=0] if set, indicates that the specified character (e.g., close paren) can mark the end of this (nested) expression
Hypexion marked this conversation as resolved.
Show resolved Hide resolved
{
ASSERT(pwStr);
ASSERT(pHold);
Expand All @@ -836,7 +851,7 @@ bool CCharacter::IsValidExpression(
//Parse another term.
if (pwStr[index] == W_t('+') || pwStr[index] == W_t('-'))
++index;
else if (bExpectCloseParen && pwStr[index] == W_t(')')) //closing nested expression
else if (closingChar && pwStr[index] == closingChar) //closing nested expression
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking:
In the original/source RPG code, I had pwStr[index] == W_t(closingChar).
Will leaving out the W_t cast always compile correctly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this. This macro doesn't do anything for a Windows build, but it looks like it's used for other platforms.

return true; //caller will parse the close paren
else
return false; //invalid symbol between terms
Expand Down Expand Up @@ -883,7 +898,7 @@ bool CCharacter::IsValidFactor(const WCHAR *pwStr, UINT& index, CDbHold *pHold)
if (pwStr[index] == W_t('('))
{
++index;
if (!IsValidExpression(pwStr, index, pHold, true)) //recursive call
if (!IsValidExpression(pwStr, index, pHold, ')')) //recursive call
return false;
if (pwStr[index] != W_t(')')) //should be parsing the close parenthesis at this point
return false; //missing close parenthesis
Expand Down Expand Up @@ -936,6 +951,11 @@ bool CCharacter::IsValidFactor(const WCHAR *pwStr, UINT& index, CDbHold *pHold)
if (pHold->GetVarID(wVarName.c_str()))
return true;

//Is it a function primitive?
ScriptVars::PrimitiveType ePrimitive = ScriptVars::parsePrimitive(wVarName);
if (ePrimitive != ScriptVars::NoPrimitive)
return IsValidPrimitiveParameters(ePrimitive, pwStr, index, pHold);

//Unrecognized identifier.
return false;
}
Expand All @@ -944,6 +964,52 @@ bool CCharacter::IsValidFactor(const WCHAR *pwStr, UINT& index, CDbHold *pHold)
return false;
}

//*****************************************************************************
bool CCharacter::IsValidPrimitiveParameters(
ScriptVars::PrimitiveType ePrimitive,
const WCHAR* pwStr, UINT& index, CDbHold* pHold)
{
SKIP_WHITESPACE(pwStr, index);

//Parse arguments surrounded by parens
if (pwStr[index] != W_t('('))
return false;
++index;

SKIP_WHITESPACE(pwStr, index);

UINT numArgs = 0;
if (pwStr[index] != W_t(')'))
{
UINT lookaheadIndex = index;
while (IsValidExpression(pwStr, lookaheadIndex, pHold, ',')) //recursive call
{
index = lookaheadIndex;
if (pwStr[index] != W_t(','))
return false;

++index;
++numArgs;
lookaheadIndex = index;
}

//no more commas; parse final argument
if (!IsValidExpression(pwStr, index, pHold, ')')) //recursive call
return false;
++numArgs;
}

if (pwStr[index] != W_t(')')) //should be parsing the close parenthesis at this point
return false; //missing close parenthesis
++index;

//Confirm correct parameter count for this primitive
if (numArgs != ScriptVars::getPrimitiveRequiredParameters(ePrimitive))
return false;

return true;
}

//*****************************************************************************
int CCharacter::parseExpression(
//Parse and evaluate a simple nested expression for the grammar
Expand All @@ -956,7 +1022,7 @@ int CCharacter::parseExpression(
//
//Params:
const WCHAR *pwStr, UINT& index, CCurrentGame *pGame, CCharacter *pNPC, //[default=NULL]
const bool bExpectCloseParen) //[default=false] whether a close paren should mark the end of this (nested) expression
const char closingChar) //[default=0] if set, indicates that the specified character (e.g., close paren) can mark the end of this (nested) expression
{
ASSERT(pwStr);
ASSERT(pGame);
Expand Down Expand Up @@ -988,15 +1054,12 @@ int CCharacter::parseExpression(
bAdd = false;
++index;
}
else if (bExpectCloseParen && pwStr[index] == W_t(')')) //closing nested expression
return val; //caller will parse the close paren
else if (closingChar && pwStr[index] == closingChar) //closing nested expression
return val; //caller will parse the closing char
else
{
//parse error -- return the current value
CFiles f;
string str = UnicodeToUTF8(pwStr + index);
str += ": Parse error (bad symbol)";
f.AppendErrorLog(str.c_str());
LogParseError(pwStr + index, "Parse error (bad symbol)");
return val;
}

Expand All @@ -1010,6 +1073,9 @@ int CCharacter::parseExpression(
//*****************************************************************************
int CCharacter::parseTerm(const WCHAR *pwStr, UINT& index, CCurrentGame *pGame, CCharacter *pNPC)
//Parse and evaluate a term in an expression.
//
// term = factor {("*"|"/"|"%") factor}
//
{
int val = parseFactor(pwStr, index, pGame, pNPC);

Expand Down Expand Up @@ -1057,56 +1123,63 @@ int CCharacter::parseTerm(const WCHAR *pwStr, UINT& index, CCurrentGame *pGame,
}

//*****************************************************************************
int CCharacter::parseFactor(const WCHAR *pwStr, UINT& index, CCurrentGame *pGame, CCharacter *pNPC)
//Parse and evaluate a term in an expression.
int CCharacter::parseNestedExpression(const WCHAR* pwStr, UINT& index, CCurrentGame* pGame, CCharacter* pNPC)
//Parse and evaluate a nested expression.
{
SKIP_WHITESPACE(pwStr, index);
ASSERT(pwStr[index] == W_t('('));
++index; //pass left paren

//A nested expression?
if (pwStr[index] == W_t('('))
{
int val = parseExpression(pwStr, index, pGame, pNPC, ')'); //recursive call
SKIP_WHITESPACE(pwStr, index);
if (pwStr[index] == W_t(')'))
++index;
int val = parseExpression(pwStr, index, pGame, pNPC, true); //recursive call
SKIP_WHITESPACE(pwStr, index);
if (pwStr[index] == W_t(')'))
++index;
else
{
//parse error -- return the current value
CFiles f;
string str = UnicodeToUTF8(pwStr);
str += ": Parse error (missing close parenthesis)";
f.AppendErrorLog(str.c_str());
}
return val;
else
{
//parse error -- return the current value
LogParseError(pwStr, "Parse error (missing close parenthesis)");
}
return val;
}

//Number?
if (iswdigit(pwStr[index]))
{
int val = _Wtoi(pwStr + index);
//*****************************************************************************
int CCharacter::parseNumber(const WCHAR* pwStr, UINT& index)
{
ASSERT(iswdigit(pwStr[index]));

//Parse past digits.
const int val = _Wtoi(pwStr + index);

//Parse past digits.
++index;
while (iswdigit(pwStr[index]))
++index;
while (iswdigit(pwStr[index]))

if (iswalpha(pwStr[index])) //i.e. of form <digits><alphas>
{
//Invalid var name -- skip to end of it and return zero value.
while (isVarCharValid(pwStr[index]))
++index;

if (iswalpha(pwStr[index])) //i.e. of form <digits><alphas>
{
//Invalid var name -- skip to end of it and return zero value.
while (CDbHold::IsVarCharValid(pwStr[index]))
++index;
LogParseError(pwStr, "Parse error (invalid var name)");

CFiles f;
string str = UnicodeToUTF8(pwStr);
str += ": Parse error (invalid var name)";
f.AppendErrorLog(str.c_str());
return 0;
}

return 0;
}
return val;
}

return val;
}
//*****************************************************************************
int CCharacter::parseFactor(const WCHAR *pwStr, UINT& index, CCurrentGame *pGame, CCharacter *pNPC)
//Parse and evaluate a term in an expression.
{
SKIP_WHITESPACE(pwStr, index);

//A nested expression?
if (pwStr[index] == W_t('('))
return parseNestedExpression(pwStr, index, pGame, pNPC);

//Number?
if (iswdigit(pwStr[index]))
return parseNumber(pwStr, index);

//Variable identifier?
if (pwStr[index] == W_t('_') || iswalpha(pwStr[index]) || pwStr[index] == W_t('.')) //valid first char
Expand Down Expand Up @@ -1139,24 +1212,72 @@ int CCharacter::parseFactor(const WCHAR *pwStr, UINT& index, CCurrentGame *pGame
}
} else if (ScriptVars::IsCharacterLocalVar(wVarName)) {
val = pNPC ? pNPC->getLocalVarInt(wVarName) : 0;
} else {
} else if (pGame->pHold->GetVarID(wVarName.c_str())) {
//Is it a local hold var?
char *varName = pGame->pHold->getVarAccessToken(wVarName.c_str());
const UNPACKEDVARTYPE vType = pGame->stats.GetVarType(varName);
const bool bValidInt = vType == UVT_int || vType == UVT_uint || vType == UVT_unknown;
if (bValidInt)
val = pGame->stats.GetVar(varName, (int)0);
//else: unrecognized identifier -- just return a zero value
}

//Is it a function primitive?
ScriptVars::PrimitiveType ePrimitive = ScriptVars::parsePrimitive(wVarName);
if (ePrimitive != ScriptVars::NoPrimitive)
return parsePrimitive(ePrimitive, pwStr, index, pGame, pNPC);

//else: unrecognized identifier -- just return a zero value
return val;
}

//Invalid identifier
CFiles f;
string str = UnicodeToUTF8(pwStr + index);
str += ": Parse error (invalid var name)";
f.AppendErrorLog(str.c_str());
LogParseError(pwStr, "Parse error(invalid var name)");

return 0;
}

//*****************************************************************************
//Parses and evaluates a primitive function call in the context of the game object.
//
//Returns: calculated value from call to primitive
int CCharacter::parsePrimitive(
ScriptVars::PrimitiveType ePrimitive,
const WCHAR* pwStr, UINT& index, CCurrentGame* pGame, CCharacter* pNPC)
{
SKIP_WHITESPACE(pwStr, index);

ASSERT(pwStr[index] == W_t('('));
++index; //pass left paren

SKIP_WHITESPACE(pwStr, index);

vector<int> params;

if (pwStr[index] != W_t(')')) {
UINT lookaheadIndex = index;
while (IsValidExpression(pwStr, lookaheadIndex, pGame->pHold, ',')) //recursive call
{
params.push_back(parseExpression(pwStr, index, pGame, pNPC, ',')); //recursive call
ASSERT(pwStr[index] == W_t(','));
++index;
lookaheadIndex = index;
}

//no more commas; parse final argument
params.push_back(parseExpression(pwStr, index, pGame, pNPC, ')')); //recursive call
}

if (pwStr[index] == W_t(')')) {
++index;
}
else {
LogParseError(pwStr, "Parse error in primitive parameter list (missing close parenthesis)");
}

if (params.size() == ScriptVars::getPrimitiveRequiredParameters(ePrimitive))
return pGame->EvalPrimitive(ePrimitive, params);

LogParseError(pwStr, "Parse error in primitive parameter list (incorrect argument count)");
return 0;
}

Expand Down
14 changes: 12 additions & 2 deletions DRODLib/Character.h
Expand Up @@ -169,19 +169,29 @@ class CCharacter : public CPlayerDouble
virtual bool IsTarget() const { return (this->IsVisible() && this->IsMonsterTarget()) || CanBeNPCBeethro(); }
bool IsTileAt(const CCharacterCommand& command) const;
virtual bool IsTileObstacle(const UINT wTileNo) const;
static bool IsValidExpression(const WCHAR *pwStr, UINT& index, CDbHold *pHold, const bool bExpectCloseParen=false);

static bool IsValidExpression(const WCHAR *pwStr, UINT& index, CDbHold *pHold, const char closingChar=0);
static bool IsValidTerm(const WCHAR *pwStr, UINT& index, CDbHold *pHold);
static bool IsValidFactor(const WCHAR *pwStr, UINT& index, CDbHold *pHold);
static bool IsValidPrimitiveParameters(ScriptVars::PrimitiveType ePrimitive,
const WCHAR* pwStr, UINT& index, CDbHold* pHold);

virtual bool IsVisible() const {return this->bVisible;}
bool JumpToCommandWithLabel(const WCHAR *pText);
bool JumpToCommandWithLabel(const UINT num);
static void LoadCommands(CDbPackedVars& ExtraVars, COMMAND_VECTOR& commands);
static void LoadCommands(CDbPackedVars& ExtraVars, COMMANDPTR_VECTOR& commands);
virtual bool OnAnswer(int nCommand, CCueEvents &CueEvents);
virtual bool OnStabbed(CCueEvents &CueEvents, const UINT /*wX*/=-1, const UINT /*wY*/=-1, WeaponType weaponType=WT_Sword);
static int parseExpression(const WCHAR *pwStr, UINT& index, CCurrentGame *pGame, CCharacter *pNPC=NULL, const bool bExpectCloseParen=false);

static int parseExpression(const WCHAR *pwStr, UINT& index, CCurrentGame *pGame, CCharacter *pNPC=NULL, const char closingChar=0);
static int parseNestedExpression(const WCHAR* pwStr, UINT& index, CCurrentGame* pGame, CCharacter* pNPC);
static int parseNumber(const WCHAR* pwStr, UINT& index);
static int parseTerm(const WCHAR *pwStr, UINT& index, CCurrentGame *pGame, CCharacter *pNPC);
static int parseFactor(const WCHAR *pwStr, UINT& index, CCurrentGame *pGame, CCharacter *pNPC);
static int parsePrimitive(ScriptVars::PrimitiveType ePrimitive,
const WCHAR* pwStr, UINT& index, CCurrentGame* pGame, CCharacter* pNPC);

virtual void Process(const int nLastCommand, CCueEvents &CueEvents);
virtual void PushInDirection(int dx, int dy, bool bStun, CCueEvents &CueEvents);

Expand Down