Implement type-safe expression functions
This commit is contained in:
parent
3ab89502cd
commit
d6c168c25e
|
@ -3,6 +3,7 @@
|
|||
#include "console.h"
|
||||
#include "variable.h"
|
||||
#include "expressionfunctions.h"
|
||||
#include <algorithm>
|
||||
|
||||
ExpressionParser::Token::Associativity ExpressionParser::Token::associativity() const
|
||||
{
|
||||
|
@ -169,6 +170,11 @@ void ExpressionParser::tokenize()
|
|||
size_t stateMemory = 0;
|
||||
auto stateQuote = false;
|
||||
auto len = mExpression.length();
|
||||
auto push_token = [this, &stateQuote](char ch)
|
||||
{
|
||||
mCurToken.push_back(ch);
|
||||
mCurTokenQuoted.push_back(stateQuote);
|
||||
};
|
||||
for(size_t i = 0; i < len; i++)
|
||||
{
|
||||
auto ch = mExpression[i];
|
||||
|
@ -183,7 +189,7 @@ void ExpressionParser::tokenize()
|
|||
case '[':
|
||||
{
|
||||
stateMemory++;
|
||||
mCurToken.push_back(ch);
|
||||
push_token(ch);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -193,14 +199,14 @@ void ExpressionParser::tokenize()
|
|||
stateMemory--;
|
||||
else
|
||||
mIsValidExpression = false;
|
||||
mCurToken.push_back(ch);
|
||||
push_token(ch);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
if(stateMemory || stateQuote)
|
||||
mCurToken.push_back(ch);
|
||||
push_token(ch);
|
||||
else
|
||||
{
|
||||
switch(ch)
|
||||
|
@ -341,7 +347,7 @@ void ExpressionParser::tokenize()
|
|||
case '\t': //ignore tabs
|
||||
break;
|
||||
default:
|
||||
mCurToken.push_back(ch);
|
||||
push_token(ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -350,28 +356,42 @@ void ExpressionParser::tokenize()
|
|||
}
|
||||
}
|
||||
if(mCurToken.length() != 0) //make sure the last token is added
|
||||
mTokens.push_back(Token(mCurToken, Token::Type::Data));
|
||||
{
|
||||
mTokens.push_back(Token(mCurToken, resolveQuotedData()));
|
||||
mCurToken.clear();
|
||||
mCurTokenQuoted.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionParser::addOperatorToken(const String & data, Token::Type type)
|
||||
{
|
||||
if(mCurToken.length()) //add a new data token when there is data in the buffer
|
||||
{
|
||||
mTokens.push_back(Token(mCurToken, type == Token::Type::OpenBracket ? Token::Type::Function : Token::Type::Data));
|
||||
mTokens.push_back(Token(mCurToken, type == Token::Type::OpenBracket ? Token::Type::Function : resolveQuotedData()));
|
||||
mCurToken.clear();
|
||||
mCurTokenQuoted.clear();
|
||||
}
|
||||
mTokens.push_back(Token(data, type)); //add the operator token
|
||||
}
|
||||
|
||||
ExpressionParser::Token::Type ExpressionParser::resolveQuotedData() const
|
||||
{
|
||||
auto allQuoted = std::all_of(mCurTokenQuoted.begin(), mCurTokenQuoted.end(), [](bool b)
|
||||
{
|
||||
return b;
|
||||
});
|
||||
return allQuoted ? Token::Type::QuotedData : Token::Type::Data;
|
||||
}
|
||||
|
||||
bool ExpressionParser::isUnaryOperator() const
|
||||
{
|
||||
if(mCurToken.length()) //data before the operator means it is no unary operator
|
||||
return false;
|
||||
if(!mTokens.size()) //no tokens before the operator means it is an unary operator
|
||||
return true;
|
||||
auto lastType = mTokens[mTokens.size() - 1].type();
|
||||
auto lastType = mTokens.back().type();
|
||||
//if the previous token is not data or a close bracket, this operator is a unary operator
|
||||
return lastType != Token::Type::Data && lastType != Token::Type::CloseBracket;
|
||||
return lastType != Token::Type::Data && lastType != Token::Type::QuotedData && lastType != Token::Type::CloseBracket;
|
||||
}
|
||||
|
||||
void ExpressionParser::shuntingYard()
|
||||
|
@ -389,6 +409,7 @@ void ExpressionParser::shuntingYard()
|
|||
switch(token.type())
|
||||
{
|
||||
case Token::Type::Data: //If the token is a number, then push it to the output queue.
|
||||
case Token::Type::QuotedData:
|
||||
queue.push_back(token);
|
||||
break;
|
||||
case Token::Type::Function: //If the token is a function token, then push it onto the stack.
|
||||
|
@ -402,7 +423,7 @@ void ExpressionParser::shuntingYard()
|
|||
mIsValidExpression = false;
|
||||
return;
|
||||
}
|
||||
const auto & curToken = stack[stack.size() - 1];
|
||||
const auto & curToken = stack.back();
|
||||
if(curToken.type() == Token::Type::OpenBracket)
|
||||
break;
|
||||
queue.push_back(curToken);
|
||||
|
@ -421,7 +442,7 @@ void ExpressionParser::shuntingYard()
|
|||
mIsValidExpression = false;
|
||||
return;
|
||||
}
|
||||
auto curToken = stack[stack.size() - 1];
|
||||
auto curToken = stack.back();
|
||||
stack.pop_back(); //Pop the left parenthesis from the stack, but not onto the output queue.
|
||||
if(curToken.type() == Token::Type::OpenBracket) //the bracket is already popped here
|
||||
break;
|
||||
|
@ -439,7 +460,7 @@ void ExpressionParser::shuntingYard()
|
|||
const auto & o1 = token;
|
||||
while(!stack.empty()) //while there is an operator token o2, at the top of the operator stack and either
|
||||
{
|
||||
const auto & o2 = stack[stack.size() - 1];
|
||||
const auto & o2 = stack.back();
|
||||
if(o2.isOperator() &&
|
||||
(o1.associativity() == Token::Associativity::LeftToRight && o1.precedence() >= o2.precedence()) || //o1 is left-associative and its precedence is less than or equal to that of o2, or
|
||||
(o1.associativity() == Token::Associativity::RightToLeft && o1.precedence() > o2.precedence())) //o1 is right associative, and has precedence less than that of o2,
|
||||
|
@ -457,7 +478,7 @@ void ExpressionParser::shuntingYard()
|
|||
//When there are no more tokens to read:
|
||||
while(!stack.empty()) //While there are still operator tokens in the stack:
|
||||
{
|
||||
const auto & curToken = stack[stack.size() - 1];
|
||||
const auto & curToken = stack.back();
|
||||
if(curToken.type() == Token::Type::OpenBracket || curToken.type() == Token::Type::CloseBracket) //If the operator token on the top of the stack is a parenthesis, then there are mismatched parentheses.
|
||||
{
|
||||
mIsValidExpression = false;
|
||||
|
@ -802,9 +823,9 @@ bool ExpressionParser::Calculate(duint & value, bool signedcalc, bool allowassig
|
|||
case Token::Type::OperatorPrefixDec:
|
||||
case Token::Type::OperatorSuffixInc:
|
||||
case Token::Type::OperatorSuffixDec:
|
||||
if(stack.size() < 1)
|
||||
if(stack.empty())
|
||||
return false;
|
||||
op1 = stack[stack.size() - 1];
|
||||
op1 = stack.back();
|
||||
stack.pop_back();
|
||||
if(signedcalc)
|
||||
operationSuccess = signedOperation(type, op1, op2, result, silent, baseonly, allowassign);
|
||||
|
@ -852,9 +873,9 @@ bool ExpressionParser::Calculate(duint & value, bool signedcalc, bool allowassig
|
|||
case Token::Type::OperatorAssignOr:
|
||||
if(stack.size() < 2)
|
||||
return false;
|
||||
op2 = stack[stack.size() - 1];
|
||||
op2 = stack.back();
|
||||
stack.pop_back();
|
||||
op1 = stack[stack.size() - 1];
|
||||
op1 = stack.back();
|
||||
stack.pop_back();
|
||||
if(signedcalc)
|
||||
operationSuccess = signedOperation(type, op1, op2, result, silent, baseonly, allowassign);
|
||||
|
@ -883,45 +904,47 @@ bool ExpressionParser::Calculate(duint & value, bool signedcalc, bool allowassig
|
|||
argv.resize(argTypes.size());
|
||||
for(auto i = 0; i < argTypes.size(); i++)
|
||||
{
|
||||
/*duint arg;
|
||||
if(!stack[stack.size() - 1].DoEvaluate(arg, silent, baseonly))
|
||||
return false;*/
|
||||
// TODO: put in EvalValue -> ExpressionValue
|
||||
auto & top = stack[stack.size() - i - 1];
|
||||
ExpressionValue arg;
|
||||
if(top.evaluated)
|
||||
if(top.isString)
|
||||
{
|
||||
arg = { ValueTypeString, 0, StringValue{ top.data.c_str(), false } };
|
||||
}
|
||||
else if(top.evaluated)
|
||||
{
|
||||
arg = { ValueTypeNumber, top.value };
|
||||
}
|
||||
else
|
||||
{
|
||||
arg = { ValueTypeString, 0, StringValue{top.data.c_str(), false} };
|
||||
duint result;
|
||||
if(!top.DoEvaluate(result, silent, baseonly, value_size, isvar, hexonly))
|
||||
return false;
|
||||
arg = { ValueTypeNumber, result };
|
||||
}
|
||||
|
||||
if(arg.type != argTypes[i])
|
||||
{
|
||||
if(arg.type == ValueTypeString)
|
||||
if(!silent)
|
||||
{
|
||||
duint result;
|
||||
|
||||
if(!top.DoEvaluate(result, silent, baseonly, value_size, isvar, hexonly))
|
||||
return false;
|
||||
|
||||
arg.number = result;
|
||||
if(arg.string.isOwner)
|
||||
BridgeFree((void*)arg.string.ptr);
|
||||
|
||||
arg.string.ptr = nullptr;
|
||||
auto typeName = [](ValueType t) -> String
|
||||
{
|
||||
switch(t)
|
||||
{
|
||||
case ValueTypeNumber:
|
||||
return GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "number"));
|
||||
case ValueTypeString:
|
||||
return GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "string"));
|
||||
}
|
||||
return GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "invalid"));
|
||||
};
|
||||
dprintf(QT_TRANSLATE_NOOP("DBG", "Expression function %s argument %d type mismatch (expected %s, got %s)!\n"),
|
||||
name.c_str(),
|
||||
argTypes.size() - i,
|
||||
typeName(argTypes[i]).c_str(),
|
||||
typeName(arg.type).c_str()
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
char str[32] = "";
|
||||
sprintf_s(str, "0x%p", arg.number);
|
||||
void* ptr = BridgeAlloc(sizeof(str));
|
||||
strcpy_s((char*)ptr, _countof(str), str);
|
||||
arg.string.ptr = (const char*)ptr;
|
||||
arg.string.isOwner = true;
|
||||
}
|
||||
|
||||
arg.type = argTypes[i];
|
||||
return false;
|
||||
}
|
||||
|
||||
argv[argTypes.size() - i - 1] = arg;
|
||||
|
@ -937,7 +960,7 @@ bool ExpressionParser::Calculate(duint & value, bool signedcalc, bool allowassig
|
|||
}
|
||||
if(result.type == ValueTypeString)
|
||||
{
|
||||
stack.push_back(EvalValue(result.string.ptr));
|
||||
stack.push_back(EvalValue(result.string.ptr, true));
|
||||
if(result.string.isOwner)
|
||||
BridgeFree((void*)result.string.ptr);
|
||||
}
|
||||
|
@ -945,9 +968,16 @@ bool ExpressionParser::Calculate(duint & value, bool signedcalc, bool allowassig
|
|||
stack.push_back(EvalValue(result.number));
|
||||
}
|
||||
else
|
||||
stack.push_back(EvalValue(token.data()));
|
||||
stack.push_back(EvalValue(token.data(), token.type() == Token::Type::QuotedData));
|
||||
}
|
||||
if(stack.size() != 1) //there should only be one value left on the stack
|
||||
return false;
|
||||
return stack[stack.size() - 1].DoEvaluate(value, silent, baseonly, value_size, isvar, hexonly);
|
||||
auto top = stack.back();
|
||||
if(top.isString)
|
||||
{
|
||||
if(!silent)
|
||||
dprintf(QT_TRANSLATE_NOOP("DBG", "Expression evaluated to a string: \"%s\"\n"), StringUtils::Escape(top.data).c_str());
|
||||
return false;
|
||||
}
|
||||
return top.DoEvaluate(value, silent, baseonly, value_size, isvar, hexonly);
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ public:
|
|||
{
|
||||
Error,
|
||||
Data,
|
||||
QuotedData,
|
||||
Function,
|
||||
Comma,
|
||||
OpenBracket,
|
||||
|
@ -112,15 +113,16 @@ public:
|
|||
|
||||
struct EvalValue
|
||||
{
|
||||
bool evaluated;
|
||||
bool evaluated = false;
|
||||
bool isString = false;
|
||||
duint value = 0;
|
||||
String data;
|
||||
|
||||
explicit EvalValue(duint value)
|
||||
: evaluated(true), value(value) {}
|
||||
|
||||
explicit EvalValue(const String & data)
|
||||
: evaluated(false), data(data) {}
|
||||
explicit EvalValue(const String & data, bool isString)
|
||||
: evaluated(false), data(data), isString(isString) {}
|
||||
|
||||
bool DoEvaluate(duint & result, bool silent = true, bool baseonly = false, int* value_size = nullptr, bool* isvar = nullptr, bool* hexonly = nullptr) const
|
||||
{
|
||||
|
@ -141,6 +143,7 @@ public:
|
|||
|
||||
private:
|
||||
static String fixClosingBrackets(const String & expression);
|
||||
Token::Type resolveQuotedData() const;
|
||||
bool isUnaryOperator() const;
|
||||
void tokenize();
|
||||
void shuntingYard();
|
||||
|
@ -168,6 +171,7 @@ private:
|
|||
std::vector<Token> mTokens;
|
||||
std::vector<Token> mPrefixTokens;
|
||||
String mCurToken;
|
||||
std::vector<bool> mCurTokenQuoted;
|
||||
};
|
||||
|
||||
#endif //_EXPRESSION_PARSER_H
|
Loading…
Reference in New Issue