mirror of https://github.com/cutefishos/calculator
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1470 lines
47 KiB
C++
1470 lines
47 KiB
C++
/* Copyright (C) 2004 Ariya Hidayat <ariya@kde.org>
|
|
2005-2006 Johan Thelin <e8johan@gmail.com>
|
|
2007 Helder Correia <helder.pereira.correia@gmail.com>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; see the file COPYING. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "evaluator.h"
|
|
#include "functions.h"
|
|
#include "opcode.h"
|
|
|
|
#include <float.h>
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
|
|
#include <QCoreApplication>
|
|
#include <QLocale>
|
|
#include <QMap>
|
|
#include <QString>
|
|
#include <QStringList>
|
|
#include <QVector>
|
|
#include <QStack>
|
|
|
|
#define qApp QCoreApplication::instance()
|
|
|
|
class EvaluatorPrivate
|
|
{
|
|
public:
|
|
Evaluator *evaluator;
|
|
bool dirty;
|
|
bool valid;
|
|
QString expression;
|
|
|
|
QString error;
|
|
QMap<QString,Variable> variables;
|
|
Evaluator::AngleMode angleMode;
|
|
QString decimalPoint;
|
|
|
|
QString assignId;
|
|
QVector<Opcode> codes;
|
|
QStringList identifiers;
|
|
QVector<HNumber> constants;
|
|
};
|
|
|
|
class TokenStack : public QVector<Token>
|
|
{
|
|
public:
|
|
TokenStack();
|
|
bool isEmpty() const;
|
|
unsigned itemCount() const;
|
|
void push(const Token& token);
|
|
Token pop();
|
|
const Token& top();
|
|
const Token& top(unsigned index);
|
|
|
|
private:
|
|
void ensureSpace();
|
|
int topIndex;
|
|
};
|
|
|
|
// for null token
|
|
const Token Token::null;
|
|
|
|
// helper function: return operator of given token text
|
|
// e.g. "*" yields Operator::Asterisk, and so on
|
|
static Token::Op matchOperator(const QString& text)
|
|
{
|
|
Token::Op result = Token::InvalidOp;
|
|
|
|
if (text.length() == 1) {
|
|
QChar p = text[0];
|
|
switch (p.unicode())
|
|
{
|
|
case '+': result = Token::Plus ; break;
|
|
case '-': result = Token::Minus ; break;
|
|
case '*': result = Token::Asterisk ; break;
|
|
case '/': result = Token::Slash ; break;
|
|
case '^': result = Token::Caret ; break;
|
|
case ';': result = Token::Semicolon ; break;
|
|
case '(': result = Token::LeftPar ; break;
|
|
case ')': result = Token::RightPar ; break;
|
|
case '%': result = Token::Percent ; break;
|
|
case '!': result = Token::Exclamation; break;
|
|
case '=': result = Token::Equal ; break;
|
|
default : result = Token::InvalidOp ; break;
|
|
}
|
|
}
|
|
|
|
else if (text.length() == 2)
|
|
{
|
|
if (text == "**") result = Token::Caret;
|
|
}
|
|
#if 0
|
|
else if (text.length() == 3)
|
|
{
|
|
if (text == "mod") result = Token::Modulo;
|
|
else if (text == "div") result = Token::Div;
|
|
}
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
// helper function: give operator precedence
|
|
// e.g. "+" is 1 while "*" is 3
|
|
static int opPrecedence(Token::Op op)
|
|
{
|
|
int prec = -1;
|
|
switch (op)
|
|
{
|
|
case Token::Exclamation : prec = 8; break;
|
|
case Token::Percent : prec = 8; break;
|
|
case Token::Caret : prec = 7; break;
|
|
case Token::Asterisk : prec = 5; break;
|
|
case Token::Slash : prec = 6; break;
|
|
case Token::Modulo : prec = 6; break;
|
|
case Token::Div : prec = 6; break;
|
|
case Token::Plus : prec = 3; break;
|
|
case Token::Minus : prec = 3; break;
|
|
case Token::RightPar : prec = 0; break;
|
|
case Token::LeftPar : prec = -1; break;
|
|
case Token::Semicolon : prec = 0; break;
|
|
default : prec = -1; break;
|
|
}
|
|
return prec;
|
|
}
|
|
|
|
// creates a token
|
|
Token::Token(Type type, const QString& text, int pos)
|
|
{
|
|
m_type = type;
|
|
m_text = text;
|
|
m_pos = pos;
|
|
}
|
|
|
|
// copy constructor
|
|
Token::Token(const Token& token)
|
|
{
|
|
m_type = token.m_type;
|
|
m_text = token.m_text;
|
|
m_pos = token.m_pos;
|
|
}
|
|
|
|
// assignment operator
|
|
Token& Token::operator=(const Token& token)
|
|
{
|
|
m_type = token.m_type;
|
|
m_text = token.m_text;
|
|
m_pos = token.m_pos;
|
|
return *this;
|
|
}
|
|
|
|
HNumber Token::asNumber() const
|
|
{
|
|
if (isNumber())
|
|
return HNumber((const char*) m_text.toLatin1());
|
|
else
|
|
return HNumber(0);
|
|
}
|
|
|
|
Token::Op Token::asOperator() const
|
|
{
|
|
if (isOperator())
|
|
return matchOperator(m_text);
|
|
else
|
|
return InvalidOp;
|
|
}
|
|
|
|
QString Token::description() const
|
|
{
|
|
QString desc;
|
|
|
|
switch (m_type)
|
|
{
|
|
case Number : desc = "Number" ; break;
|
|
case Identifier : desc = "Identifier"; break;
|
|
case Operator : desc = "Operator" ; break;
|
|
default : desc = "Unknown" ; break;
|
|
}
|
|
|
|
while (desc.length() < 10)
|
|
desc.prepend(' ');
|
|
desc.prepend(" ");
|
|
desc.prepend(QString::number(m_pos));
|
|
desc.append(" : ").append(m_text);
|
|
|
|
return desc;
|
|
}
|
|
|
|
|
|
TokenStack::TokenStack(): QVector<Token>()
|
|
{
|
|
topIndex = 0;
|
|
ensureSpace();
|
|
}
|
|
|
|
bool TokenStack::isEmpty() const
|
|
{
|
|
return topIndex == 0;
|
|
}
|
|
|
|
unsigned TokenStack::itemCount() const
|
|
{
|
|
return topIndex;
|
|
}
|
|
|
|
void TokenStack::push(const Token& token)
|
|
{
|
|
ensureSpace();
|
|
(*this)[ topIndex++ ] = token;
|
|
}
|
|
|
|
Token TokenStack::pop()
|
|
{
|
|
return (topIndex > 0) ? Token(at(--topIndex)) : Token();
|
|
}
|
|
|
|
const Token& TokenStack::top()
|
|
{
|
|
return top(0);
|
|
}
|
|
|
|
const Token& TokenStack::top(unsigned index)
|
|
{
|
|
if (topIndex > (int)index)
|
|
return at(topIndex-index-1);
|
|
return Token::null;
|
|
}
|
|
|
|
void TokenStack::ensureSpace()
|
|
{
|
|
while (topIndex >= size())
|
|
resize(size() + 10);
|
|
}
|
|
|
|
|
|
// helper function: return true for valid identifier character
|
|
static bool isIdentifier(QChar ch)
|
|
{
|
|
return (ch.unicode() == '_') || (ch.unicode() == '$') || (ch.isLetter());
|
|
}
|
|
|
|
|
|
// Constructor
|
|
|
|
Evaluator::Evaluator()
|
|
{
|
|
d = new EvaluatorPrivate;
|
|
clear();
|
|
}
|
|
|
|
// Destructor
|
|
|
|
Evaluator::~Evaluator()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
// Sets a new expression
|
|
// note that both the real lex and parse processes will happen later on
|
|
// when needed (i.e. "lazy parse"), for example during evaluation.
|
|
|
|
void Evaluator::setExpression(const QString& expr)
|
|
{
|
|
d->expression = expr;
|
|
d->dirty = true;
|
|
d->valid = false;
|
|
d->error = QString();
|
|
}
|
|
|
|
// Returns the expression
|
|
|
|
QString Evaluator::expression() const
|
|
{
|
|
return d->expression;
|
|
}
|
|
|
|
// Returns the validity
|
|
// note: empty expression is always invalid.
|
|
|
|
bool Evaluator::isValid() const
|
|
{
|
|
if (d->dirty)
|
|
{
|
|
Tokens tokens = scan(d->expression, d->decimalPoint);
|
|
if (!tokens.valid())
|
|
compile(tokens);
|
|
else
|
|
d->valid = false;
|
|
}
|
|
return d->valid;
|
|
}
|
|
|
|
// Clears everything, also mark it as invalid.
|
|
|
|
void Evaluator::clear()
|
|
{
|
|
d->expression = QString();
|
|
d->dirty = true;
|
|
d->valid = false;
|
|
|
|
d->error = QString();
|
|
d->angleMode = Degree;
|
|
d->decimalPoint = QString();
|
|
|
|
d->constants.clear();
|
|
d->codes.clear();
|
|
d->assignId = QString();
|
|
|
|
clearVariables();
|
|
}
|
|
|
|
// Returns error message
|
|
|
|
QString Evaluator::error() const
|
|
{
|
|
return d->error;
|
|
}
|
|
|
|
// Returns list of token for the expression.
|
|
// this triggers again the lexical analysis step. it is however preferable
|
|
// (even when there's small performance penalty) because otherwise we need to
|
|
// store parsed tokens all the time which serves no good purpose.
|
|
|
|
Tokens Evaluator::tokens() const
|
|
{
|
|
return scan(d->expression, d->decimalPoint);
|
|
}
|
|
|
|
Tokens Evaluator::scan(const QString& expr, const QString& settingsDecimal)
|
|
{
|
|
// to hold the result
|
|
Tokens tokens;
|
|
|
|
// auto-detect, always dot, or always comma
|
|
|
|
QChar decimalPoint;
|
|
QChar wrongDecimalPoint;
|
|
|
|
if (settingsDecimal.length() == 1)
|
|
decimalPoint = settingsDecimal[0];
|
|
else
|
|
decimalPoint = QLocale().decimalPoint();
|
|
|
|
// sanity check for wrong decimal separator usage
|
|
|
|
if (decimalPoint == ',')
|
|
wrongDecimalPoint = '.';
|
|
else
|
|
wrongDecimalPoint = ',';
|
|
|
|
int size = expr.size();
|
|
for (int i = 0; i < size; i++)
|
|
if (expr[i] == wrongDecimalPoint)
|
|
return tokens;
|
|
|
|
// parsing state
|
|
enum { Start, Finish, Bad, InNumber, InHexa, InOctal, InBinary, InDecimal, InExpIndicator,
|
|
InExponent, InIdentifier } state;
|
|
|
|
// initialize variables
|
|
state = Start;
|
|
int i = 0;
|
|
QString ex = expr;
|
|
QString tokenText;
|
|
int tokenStart = 0;
|
|
|
|
// force a terminator
|
|
ex.append(QChar());
|
|
|
|
// main loop
|
|
while ((state != Bad) && (state != Finish) && (i < ex.length()))
|
|
{
|
|
QChar ch = ex.at(i);
|
|
|
|
switch (state)
|
|
{
|
|
|
|
case Start:
|
|
|
|
tokenStart = i;
|
|
|
|
// skip any whitespaces
|
|
if (ch.isSpace()) i++;
|
|
|
|
// check for number
|
|
else if (ch.isDigit())
|
|
{
|
|
state = InNumber;
|
|
}
|
|
else if (ch == '#') // simple hexadec notation
|
|
{
|
|
tokenText.append("0x");
|
|
state = InHexa;
|
|
i++;
|
|
}
|
|
|
|
// decimal dot ?
|
|
else if (ch == decimalPoint)
|
|
{
|
|
tokenText.append(ex.at(i++));
|
|
state = InDecimal;
|
|
}
|
|
|
|
// terminator character
|
|
else if (ch.isNull())
|
|
state = Finish;
|
|
|
|
// look for operator match
|
|
else
|
|
{
|
|
int op;
|
|
QString s;
|
|
|
|
#if 0
|
|
// check for three-chars operator
|
|
s.append(ch).append(ex.at(i+1)).append(ex.at(i+2));
|
|
op = matchOperator(s);
|
|
|
|
// check for two-chars operator
|
|
if (op == Token::InvalidOp)
|
|
#endif
|
|
{
|
|
|
|
s = QString(ch).append(ex.at(i+1));
|
|
op = matchOperator(s);
|
|
}
|
|
|
|
// check for one-char operator
|
|
if (op == Token::InvalidOp)
|
|
{
|
|
s = QString(ch);
|
|
op = matchOperator(s);
|
|
}
|
|
|
|
// any matched operator ?
|
|
if (op != Token::InvalidOp)
|
|
{
|
|
int len = s.length();
|
|
i += len;
|
|
tokens.append(Token(Token::Operator, s.left(len), tokenStart));
|
|
}
|
|
else state = Bad;
|
|
}
|
|
|
|
// beginning with unknown alphanumeric ?
|
|
// could be identifier, or function...
|
|
if (state == Bad && isIdentifier(ch))
|
|
{
|
|
state = InIdentifier;
|
|
}
|
|
break;
|
|
|
|
case InIdentifier:
|
|
|
|
// consume as long as alpha, dollar sign, underscore, or digit
|
|
if (isIdentifier(ch) || ch.isDigit())
|
|
tokenText.append(ex.at(i++));
|
|
|
|
// we're done with identifier
|
|
else
|
|
{
|
|
tokens.append(Token(Token::Identifier, tokenText, tokenStart));
|
|
tokenStart = i;
|
|
tokenText = "";
|
|
state = Start;
|
|
}
|
|
break;
|
|
|
|
|
|
case InNumber:
|
|
|
|
// consume as long as it's digit
|
|
if (ch.isDigit()) tokenText.append(ex.at(i++));
|
|
|
|
// convert decimal separator to '.'
|
|
else if (ch == decimalPoint)
|
|
{
|
|
tokenText.append('.');
|
|
i++;
|
|
state = InDecimal;
|
|
}
|
|
|
|
// exponent ?
|
|
else if (ch.toUpper() == 'E')
|
|
{
|
|
tokenText.append('E');
|
|
i++;
|
|
state = InExpIndicator;
|
|
}
|
|
else if (ch.toUpper() == 'X' && tokenText == "0") // normal hexadec notation
|
|
{
|
|
tokenText.append('x'); i++; state = InHexa;
|
|
}
|
|
else if (ch.toUpper() == 'B' && tokenText == "0") // binary notation
|
|
{
|
|
tokenText.append('b'); i++; state = InBinary;
|
|
}
|
|
else if (ch.toUpper() == 'O' && tokenText == "0") // octal notation
|
|
{
|
|
tokenText.append('o'); i++; state = InOctal;
|
|
}
|
|
else if (ch.toUpper() == 'D' && tokenText == "0") // explicit decimal notation
|
|
{
|
|
// we also need to get rid of the leading zero
|
|
tokenText = ""; i++;
|
|
}
|
|
|
|
// we're done with integer number
|
|
else
|
|
{
|
|
tokens.append(Token(Token::Number, tokenText, tokenStart));
|
|
tokenText = ""; state = Start;
|
|
}
|
|
break;
|
|
|
|
case InHexa:
|
|
if (ch.isDigit() || (ch >= 'A' && ch < 'G') || (ch >= 'a' && ch < 'g'))
|
|
tokenText.append(ex.at(i++).toUpper());
|
|
else // we're done with hexa number
|
|
{
|
|
tokens.append(Token(Token::Number, tokenText, tokenStart));
|
|
tokenText = ""; state = Start;
|
|
}
|
|
break;
|
|
case InBinary:
|
|
if (ch == '0' || ch == '1') // very strict rule ;)
|
|
tokenText.append(ex.at(i++));
|
|
else // we're done with binary number
|
|
{
|
|
tokens.append(Token(Token::Number, tokenText, tokenStart));
|
|
tokenText = "";
|
|
state = Start;
|
|
}
|
|
break;
|
|
case InOctal:
|
|
if (ch >= '0' && ch < '8') // octal has only 8 digits, 8 & 9 are invalid
|
|
tokenText.append(ex.at(i++));
|
|
else // we're done with octal number
|
|
{
|
|
tokens.append(Token(Token::Number, tokenText, tokenStart));
|
|
tokenText = ""; state = Start;
|
|
}
|
|
break;
|
|
|
|
case InDecimal:
|
|
|
|
// consume as long as it's digit
|
|
if (ch.isDigit()) tokenText.append(ex.at(i++));
|
|
|
|
// exponent ?
|
|
else if (ch.toUpper() == 'E')
|
|
{
|
|
tokenText.append('E');
|
|
i++;
|
|
state = InExpIndicator;
|
|
}
|
|
|
|
// we're done with floating-point number
|
|
else
|
|
{
|
|
tokens.append(Token(Token::Number, tokenText, tokenStart));
|
|
tokenText = "";
|
|
state = Start;
|
|
};
|
|
break;
|
|
|
|
case InExpIndicator:
|
|
|
|
// possible + or - right after E, e.g 1.23E+12 or 4.67E-8
|
|
if ((ch == '+') || (ch == '-')) tokenText.append(ex.at(i++));
|
|
|
|
// consume as long as it's digit
|
|
else if (ch.isDigit()) state = InExponent;
|
|
|
|
// invalid thing here
|
|
else state = Bad;
|
|
|
|
break;
|
|
|
|
case InExponent:
|
|
|
|
// consume as long as it's digit
|
|
if (ch.isDigit()) tokenText.append(ex.at(i++));
|
|
|
|
// we're done with floating-point number
|
|
else
|
|
{
|
|
tokens.append(Token(Token::Number, tokenText, tokenStart));
|
|
tokenText = "";
|
|
state = Start;
|
|
};
|
|
break;
|
|
|
|
case Bad: // bad bad bad
|
|
tokens.setValid(false);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
};
|
|
};
|
|
|
|
return tokens;
|
|
}
|
|
|
|
|
|
void Evaluator::compile(const Tokens& tokens) const
|
|
{
|
|
// initialize variables
|
|
d->dirty = false;
|
|
d->valid = false;
|
|
d->codes.clear();
|
|
d->constants.clear();
|
|
d->identifiers.clear();
|
|
d->error = QString();
|
|
|
|
// sanity check
|
|
if (tokens.count() == 0) return;
|
|
|
|
TokenStack syntaxStack;
|
|
QStack<int> argStack;
|
|
unsigned argCount = 1;
|
|
|
|
for (int i = 0; i <= tokens.count(); i++)
|
|
{
|
|
// helper token: InvalidOp is end-of-expression
|
|
Token token = (i < tokens.count()) ? tokens[i] : Token(Token::Operator);
|
|
Token::Type tokenType = token.type();
|
|
|
|
// unknown token is invalid
|
|
if (tokenType == Token::Unknown) break;
|
|
|
|
// for constants, push immediately to stack
|
|
// generate code to load from a constant
|
|
if (tokenType == Token::Number)
|
|
{
|
|
syntaxStack.push(token);
|
|
d->constants.append(token.asNumber());
|
|
d->codes.append(Opcode(Opcode::Load, d->constants.count()-1));
|
|
}
|
|
|
|
// for identifier, push immediately to stack
|
|
// generate code to load from reference
|
|
if (tokenType == Token::Identifier)
|
|
{
|
|
syntaxStack.push(token);
|
|
d->identifiers.append(token.text());
|
|
d->codes.append(Opcode(Opcode::Ref, d->identifiers.count()-1));
|
|
}
|
|
|
|
// special case for percentage
|
|
if (tokenType == Token::Operator)
|
|
if (token.asOperator() == Token::Percent)
|
|
if (syntaxStack.itemCount() >= 1)
|
|
if (!syntaxStack.top().isOperator())
|
|
{
|
|
d->constants.append(HNumber("0.01"));
|
|
d->codes.append(Opcode(Opcode::Load, d->constants.count()-1));
|
|
d->codes.append(Opcode(Opcode::Mul));
|
|
}
|
|
|
|
// for any other operator, try to apply all parsing rules
|
|
if (tokenType == Token::Operator)
|
|
if (token.asOperator() != Token::Percent)
|
|
{
|
|
// repeat until no more rule applies
|
|
bool argHandled = false;
|
|
for (; ;)
|
|
{
|
|
bool ruleFound = false;
|
|
|
|
// rule for function last argument:
|
|
// id (arg) -> arg
|
|
if (!ruleFound)
|
|
if (syntaxStack.itemCount() >= 4)
|
|
{
|
|
Token par2 = syntaxStack.top();
|
|
Token arg = syntaxStack.top(1);
|
|
Token par1 = syntaxStack.top(2);
|
|
Token id = syntaxStack.top(3);
|
|
if (par2.asOperator() == Token::RightPar)
|
|
if (!arg.isOperator())
|
|
if (par1.asOperator() == Token::LeftPar)
|
|
if (id.isIdentifier())
|
|
{
|
|
ruleFound = true;
|
|
syntaxStack.pop();
|
|
syntaxStack.pop();
|
|
syntaxStack.pop();
|
|
syntaxStack.pop();
|
|
syntaxStack.push(arg);
|
|
d->codes.append(Opcode(Opcode::Function, argCount));
|
|
argCount = argStack.empty() ? 0 : argStack.pop();
|
|
}
|
|
}
|
|
|
|
// are we entering a function ?
|
|
// if token is operator, and stack already has: id (arg
|
|
if (!ruleFound)
|
|
if (!argHandled)
|
|
if (tokenType == Token::Operator)
|
|
if (syntaxStack.itemCount() >= 3)
|
|
{
|
|
Token arg = syntaxStack.top();
|
|
Token par = syntaxStack.top(1);
|
|
Token id = syntaxStack.top(2);
|
|
if (!arg.isOperator())
|
|
if (par.asOperator() == Token::LeftPar)
|
|
if (id.isIdentifier())
|
|
{
|
|
ruleFound = true;
|
|
argStack.push(argCount);
|
|
argCount = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// rule for simplified syntax for function e.g. "sin pi" or "cos 1.2"
|
|
// i.e no need for parentheses like "sin(pi)" or "cos(1.2)"
|
|
if (!ruleFound)
|
|
if (syntaxStack.itemCount() >= 2)
|
|
{
|
|
Token arg = syntaxStack.top();
|
|
Token id = syntaxStack.top(1);
|
|
if (!arg.isOperator())
|
|
if (id.isIdentifier())
|
|
if (FunctionRepository::self()->function(id.text()))
|
|
{
|
|
ruleFound = true;
|
|
d->codes.append(Opcode(Opcode::Function, 1));
|
|
syntaxStack.pop();
|
|
syntaxStack.pop();
|
|
syntaxStack.push(arg);
|
|
}
|
|
}
|
|
|
|
// rule for unary operator in simplified function syntax
|
|
// this handles case like "sin -90"
|
|
if (!ruleFound)
|
|
if (syntaxStack.itemCount() >= 3)
|
|
{
|
|
Token x = syntaxStack.top();
|
|
Token op = syntaxStack.top(1);
|
|
Token id = syntaxStack.top(2);
|
|
if (!x.isOperator())
|
|
if (op.isOperator())
|
|
if (id.isIdentifier())
|
|
if (FunctionRepository::self()->function(id.text()))
|
|
if ((op.asOperator() == Token::Plus) ||
|
|
(op.asOperator() == Token::Minus))
|
|
{
|
|
ruleFound = true;
|
|
syntaxStack.pop();
|
|
syntaxStack.pop();
|
|
syntaxStack.push(x);
|
|
if (op.asOperator() == Token::Minus)
|
|
d->codes.append(Opcode(Opcode::Neg));
|
|
}
|
|
}
|
|
|
|
// rule for unary postfix operator in simplified function syntax
|
|
// this handles case like "sin 90!"
|
|
if (!ruleFound)
|
|
if (syntaxStack.itemCount() >= 3)
|
|
{
|
|
Token op = syntaxStack.top();
|
|
Token x = syntaxStack.top(1);
|
|
Token id = syntaxStack.top(2);
|
|
if (id.isIdentifier() && FunctionRepository::self()->function(id.text()))
|
|
{
|
|
if (!x.isOperator() && op.isOperator() &&
|
|
op.asOperator() == Token::Exclamation)
|
|
{
|
|
ruleFound = true;
|
|
syntaxStack.pop();
|
|
syntaxStack.pop();
|
|
syntaxStack.push(x);
|
|
d->codes.append(Opcode(Opcode::Fact));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// rule for function arguments, if token is , or)
|
|
// id (arg1 ; arg2 -> id (arg
|
|
if (!ruleFound)
|
|
if (syntaxStack.itemCount() >= 5)
|
|
if ((token.asOperator() == Token::RightPar) ||
|
|
(token.asOperator() == Token::Semicolon))
|
|
{
|
|
Token arg2 = syntaxStack.top();
|
|
Token sep = syntaxStack.top(1);
|
|
Token arg1 = syntaxStack.top(2);
|
|
Token par = syntaxStack.top(3);
|
|
Token id = syntaxStack.top(4);
|
|
if (!arg2.isOperator())
|
|
if (sep.asOperator() == Token::Semicolon)
|
|
if (!arg1.isOperator())
|
|
if (par.asOperator() == Token::LeftPar)
|
|
if (id.isIdentifier())
|
|
{
|
|
ruleFound = true;
|
|
argHandled = true;
|
|
syntaxStack.pop();
|
|
syntaxStack.pop();
|
|
argCount++;
|
|
}
|
|
}
|
|
|
|
// rule for function call with parentheses, but without argument
|
|
// e.g. "2*PI()"
|
|
if (!ruleFound)
|
|
if (syntaxStack.itemCount() >= 3)
|
|
{
|
|
Token par2 = syntaxStack.top();
|
|
Token par1 = syntaxStack.top(1);
|
|
Token id = syntaxStack.top(2);
|
|
if (par2.asOperator() == Token::RightPar)
|
|
if (par1.asOperator() == Token::LeftPar)
|
|
if (id.isIdentifier())
|
|
{
|
|
ruleFound = true;
|
|
syntaxStack.pop();
|
|
syntaxStack.pop();
|
|
syntaxStack.pop();
|
|
syntaxStack.push(Token(Token::Number));
|
|
d->codes.append(Opcode(Opcode::Function, 0));
|
|
}
|
|
}
|
|
|
|
// rule for parenthesis: (Y) -> Y
|
|
if (!ruleFound)
|
|
if (syntaxStack.itemCount() >= 3)
|
|
{
|
|
Token right = syntaxStack.top();
|
|
Token y = syntaxStack.top(1);
|
|
Token left = syntaxStack.top(2);
|
|
if (right.isOperator())
|
|
if (!y.isOperator())
|
|
if (left.isOperator())
|
|
if (right.asOperator() == Token::RightPar)
|
|
if (left.asOperator() == Token::LeftPar)
|
|
{
|
|
ruleFound = true;
|
|
syntaxStack.pop();
|
|
syntaxStack.pop();
|
|
syntaxStack.pop();
|
|
syntaxStack.push(y);
|
|
}
|
|
}
|
|
|
|
// rule for binary operator: A (op) B -> A
|
|
// conditions: precedence of op >= precedence of token
|
|
// action: push (op) to result
|
|
// e.g. "A * B" becomes "A" if token is operator "+"
|
|
// exception: for caret (power operator), if op is another caret
|
|
// then the rule doesn't apply, e.g. "2^3^2" is evaluated as "2^(3^2)"
|
|
if (!ruleFound)
|
|
if (syntaxStack.itemCount() >= 3)
|
|
{
|
|
Token b = syntaxStack.top();
|
|
Token op = syntaxStack.top(1);
|
|
Token a = syntaxStack.top(2);
|
|
if (!a.isOperator())
|
|
if (!b.isOperator())
|
|
if (op.isOperator())
|
|
if (token.asOperator() != Token::LeftPar)
|
|
if (token.asOperator() != Token::Caret)
|
|
if (opPrecedence(op.asOperator()) >= opPrecedence(token.asOperator()))
|
|
{
|
|
ruleFound = true;
|
|
syntaxStack.pop();
|
|
syntaxStack.pop();
|
|
syntaxStack.pop();
|
|
syntaxStack.push(b);
|
|
switch (op.asOperator())
|
|
{
|
|
// simple binary operations
|
|
case Token::Plus: d->codes.append(Opcode::Add); break;
|
|
case Token::Minus: d->codes.append(Opcode::Sub); break;
|
|
case Token::Asterisk: d->codes.append(Opcode::Mul); break;
|
|
case Token::Slash: d->codes.append(Opcode::Div); break;
|
|
case Token::Caret: d->codes.append(Opcode::Pow); break;
|
|
case Token::Modulo: d->codes.append(Opcode::Modulo); break;
|
|
case Token::Div: d->codes.append(Opcode::IntDiv); break;
|
|
default: break;
|
|
};
|
|
}
|
|
}
|
|
|
|
// rule for unary operator: (op1) (op2) X -> (op1) X
|
|
// conditions: op2 is unary, token is not '('
|
|
// action: push (op2) to result
|
|
// e.g. "* - 2" becomes "*"
|
|
if (!ruleFound)
|
|
if (token.asOperator() != Token::LeftPar)
|
|
if (syntaxStack.itemCount() >= 3)
|
|
{
|
|
Token x = syntaxStack.top();
|
|
Token op2 = syntaxStack.top(1);
|
|
Token op1 = syntaxStack.top(2);
|
|
if (!x.isOperator() && op1.isOperator() && op2.isOperator() &&
|
|
(op2.asOperator() == Token::Plus || op2.asOperator() == Token::Minus))
|
|
{
|
|
ruleFound = true;
|
|
if (op2.asOperator() == Token::Minus)
|
|
d->codes.append(Opcode(Opcode::Neg));
|
|
}
|
|
else // maybe postfix
|
|
{
|
|
x = op2; op2 = syntaxStack.top();
|
|
if (!x.isOperator() && op1.isOperator() && op2.isOperator() &&
|
|
op2.asOperator() == Token::Exclamation)
|
|
{
|
|
ruleFound = true;
|
|
d->codes.append(Opcode(Opcode::Fact));
|
|
}
|
|
}
|
|
if (ruleFound)
|
|
{
|
|
syntaxStack.pop();
|
|
syntaxStack.pop();
|
|
syntaxStack.push(x);
|
|
}
|
|
}
|
|
|
|
// auxiliary rule for unary operator: (op) X -> X
|
|
// conditions: op is unary, op is first in syntax stack, token is not '(' or '^' or '!'
|
|
// action: push (op) to result
|
|
if (!ruleFound)
|
|
if (token.asOperator() != Token::LeftPar)
|
|
if (token.asOperator() != Token::Caret)
|
|
if (token.asOperator() != Token::Exclamation)
|
|
if (syntaxStack.itemCount() == 2)
|
|
{
|
|
Token x = syntaxStack.top();
|
|
Token op = syntaxStack.top(1);
|
|
if (!x.isOperator() && op.isOperator() &&
|
|
(op.asOperator() == Token::Plus || op.asOperator() == Token::Minus))
|
|
{
|
|
ruleFound = true;
|
|
if (op.asOperator() == Token::Minus)
|
|
d->codes.append(Opcode(Opcode::Neg));
|
|
}
|
|
else
|
|
{
|
|
x = op; op = syntaxStack.top();
|
|
if (!x.isOperator() && op.isOperator() &&
|
|
(op.asOperator() == Token::Exclamation))
|
|
{
|
|
ruleFound = true;
|
|
d->codes.append(Opcode(Opcode::Fact));
|
|
}
|
|
}
|
|
if (ruleFound)
|
|
{
|
|
syntaxStack.pop();
|
|
syntaxStack.pop();
|
|
syntaxStack.push(x);
|
|
}
|
|
}
|
|
|
|
if (!ruleFound) break;
|
|
}
|
|
|
|
// can't apply rules anymore, push the token
|
|
if (token.asOperator() != Token::Percent)
|
|
syntaxStack.push(token);
|
|
}
|
|
}
|
|
|
|
// syntaxStack must left only one operand and end-of-expression (i.e. InvalidOp)
|
|
d->valid = false;
|
|
if (syntaxStack.itemCount() == 2)
|
|
if (syntaxStack.top().isOperator())
|
|
if (syntaxStack.top().asOperator() == Token::InvalidOp)
|
|
if (!syntaxStack.top(1).isOperator())
|
|
d->valid = true;
|
|
|
|
// bad parsing ? clean-up everything
|
|
if (!d->valid)
|
|
{
|
|
d->constants.clear();
|
|
d->codes.clear();
|
|
d->identifiers.clear();
|
|
}
|
|
}
|
|
|
|
Evaluator::AngleMode Evaluator::angleMode() const
|
|
{
|
|
return d->angleMode;
|
|
}
|
|
|
|
void Evaluator::setAngleMode(AngleMode am)
|
|
{
|
|
d->angleMode = am;
|
|
}
|
|
|
|
QString Evaluator::decimalPoint() const
|
|
{
|
|
return d->decimalPoint;
|
|
}
|
|
|
|
void Evaluator::setDecimalPoint(const QString& dp)
|
|
{
|
|
d->decimalPoint = dp;
|
|
}
|
|
|
|
HNumber Evaluator::eval()
|
|
{
|
|
QStack<HNumber> stack;
|
|
QStack<QString> refs;
|
|
int index;
|
|
HNumber val1, val2;
|
|
QVector<HNumber> args;
|
|
QString fname;
|
|
Function* function;
|
|
|
|
if (d->dirty)
|
|
{
|
|
Tokens tokens = scan(d->expression, d->decimalPoint);
|
|
|
|
// invalid expression?
|
|
if (!tokens.valid())
|
|
{
|
|
d->error = QObject::tr("Invalid expression.");
|
|
return HNumber(0);
|
|
}
|
|
|
|
// variable assignment?
|
|
d->assignId = QString();
|
|
if (tokens.count() > 2)
|
|
if (tokens[0].isIdentifier())
|
|
if (tokens[1].asOperator() == Token::Equal)
|
|
{
|
|
d->assignId = tokens[0].text();
|
|
tokens.erase(tokens.begin());
|
|
tokens.erase(tokens.begin());
|
|
}
|
|
|
|
compile(tokens);
|
|
}
|
|
|
|
// can not overwrite pi
|
|
if (d->assignId == QString("pi"))
|
|
{
|
|
d->error = d->assignId + ": " + QObject::tr("Variable cannot be overwritten.");
|
|
return HNumber(0);
|
|
}
|
|
// can not overwrite phi
|
|
if (d->assignId == QString("phi"))
|
|
{
|
|
d->error = d->assignId + ": " + QObject::tr("Variable cannot be overwritten.");
|
|
return HNumber(0);
|
|
}
|
|
// can not overwrite ans
|
|
if (d->assignId == QString("ans"))
|
|
{
|
|
d->error = d->assignId + ": " + QObject::tr("Variable cannot be overwritten.");
|
|
return HNumber(0);
|
|
}
|
|
|
|
// variable can't have the same name as function
|
|
if (FunctionRepository::self()->function(d->assignId))
|
|
{
|
|
d->error = d->assignId + ": " + QObject::tr("Identifier matches an existing function name.");
|
|
return HNumber(0);
|
|
}
|
|
|
|
// magic: always set here to avoid being overwritten by user
|
|
set(QString("pi"), HMath::pi());
|
|
set(QString("phi"), HMath::phi());
|
|
|
|
for (int pc = 0; pc < d->codes.count(); pc++)
|
|
{
|
|
Opcode& opcode = d->codes[pc];
|
|
index = opcode.index;
|
|
switch (opcode.type)
|
|
{
|
|
// no operation
|
|
case Opcode::Nop:
|
|
break;
|
|
|
|
// load a constant, push to stack
|
|
case Opcode::Load:
|
|
val1 = d->constants[index];
|
|
stack.push(val1);
|
|
break;
|
|
|
|
// unary operation
|
|
case Opcode::Neg:
|
|
if (stack.count() < 1)
|
|
{
|
|
d->error = QObject::tr("Invalid expression.");
|
|
return HNumber(0);
|
|
}
|
|
val1 = stack.pop();
|
|
val1 = HMath::negate(val1);
|
|
stack.push(val1);
|
|
break;
|
|
|
|
// binary operation: take two values from stack, do the operation,
|
|
// push the result to stack
|
|
case Opcode::Add:
|
|
if (stack.count() < 2)
|
|
{
|
|
d->error = QObject::tr("Invalid expression.");
|
|
return HNumber(0);
|
|
}
|
|
val1 = stack.pop();
|
|
val2 = stack.pop();
|
|
val2 += val1;
|
|
stack.push(val2);
|
|
break;
|
|
|
|
case Opcode::Sub:
|
|
if (stack.count() < 2)
|
|
{
|
|
d->error = QObject::tr("Invalid expression.");
|
|
return HNumber(0);
|
|
}
|
|
val1 = stack.pop();
|
|
val2 = stack.pop();
|
|
val2 -= val1;
|
|
stack.push(val2);
|
|
break;
|
|
|
|
case Opcode::Mul:
|
|
if (stack.count() < 2)
|
|
{
|
|
d->error = QObject::tr("Invalid expression.");
|
|
return HNumber(0);
|
|
}
|
|
val1 = stack.pop();
|
|
val2 = stack.pop();
|
|
val2 *= val1;
|
|
stack.push(val2);
|
|
break;
|
|
|
|
case Opcode::Div:
|
|
if (stack.count() < 2)
|
|
{
|
|
d->error = QObject::tr("Invalid expression.");
|
|
return HNumber(0);
|
|
}
|
|
val1 = stack.pop();
|
|
val2 = stack.pop();
|
|
if (val1.isZero())
|
|
{
|
|
d->error = QObject::tr("Division by zero.");
|
|
return HNumber(0);
|
|
}
|
|
val2 /= val1;
|
|
stack.push(val2);
|
|
break;
|
|
|
|
case Opcode::Pow:
|
|
if (stack.count() < 2)
|
|
{
|
|
d->error = QObject::tr("Invalid expression.");
|
|
return HNumber(0);
|
|
}
|
|
val1 = stack.pop();
|
|
val2 = stack.pop();
|
|
val2 = HMath::raise(val2, val1);
|
|
stack.push(val2);
|
|
break;
|
|
|
|
case Opcode::Fact:
|
|
if (stack.count() < 1)
|
|
{
|
|
d->error = QObject::tr("Invalid expression.");
|
|
return HNumber(0);
|
|
}
|
|
val1 = stack.pop();
|
|
val1 = HMath::factorial(val1);
|
|
stack.push(val1);
|
|
break;
|
|
|
|
case Opcode::Modulo:
|
|
if (stack.count() < 2)
|
|
{
|
|
d->error = QObject::tr("Invalid expression.");
|
|
return HNumber(0);
|
|
}
|
|
val1 = stack.pop();
|
|
val2 = stack.pop();
|
|
if (val1.isZero())
|
|
{
|
|
d->error = QObject::tr("Division by zero.");
|
|
return HNumber(0);
|
|
}
|
|
val2 = val2 % val1;
|
|
stack.push(val2);
|
|
break;
|
|
|
|
case Opcode::IntDiv:
|
|
if (stack.count() < 2)
|
|
{
|
|
d->error = QObject::tr("Invalid expression.");
|
|
return HNumber(0);
|
|
}
|
|
val1 = stack.pop();
|
|
val2 = stack.pop();
|
|
if (val1.isZero())
|
|
{
|
|
d->error = QObject::tr("Division by zero.");
|
|
return HNumber(0);
|
|
}
|
|
val2 /= val1;
|
|
stack.push(HMath::integer(val2));
|
|
break;
|
|
|
|
// reference
|
|
case Opcode::Ref:
|
|
fname = d->identifiers[index];
|
|
if (has(fname))
|
|
{
|
|
// variable
|
|
stack.push(get(fname));
|
|
}
|
|
else
|
|
{
|
|
// function
|
|
function = FunctionRepository::self()->function(fname);
|
|
if (function)
|
|
refs.push(fname);
|
|
else
|
|
{
|
|
d->error = fname + ": " + QObject::tr("Unknown function or variable.");
|
|
return HNumber(0);
|
|
}
|
|
}
|
|
break;
|
|
|
|
// calling function
|
|
case Opcode::Function:
|
|
// must do this first to avoid crash when using vars like functions
|
|
if (refs.isEmpty())
|
|
break;
|
|
|
|
fname = refs.pop();
|
|
function = FunctionRepository::self()->function(fname);
|
|
if (!function)
|
|
{
|
|
d->error = fname + ": " + QObject::tr("Unknown function or variable.");
|
|
return HNumber(0);
|
|
}
|
|
|
|
if (stack.count() < index)
|
|
{
|
|
d->error = QObject::tr("Invalid expression.");
|
|
return HNumber(0);
|
|
}
|
|
|
|
args.clear();
|
|
for (; index; index--)
|
|
args.insert(args.begin(), stack.pop());
|
|
|
|
stack.push(function->exec(this, args));
|
|
if (!function->error().isEmpty())
|
|
{
|
|
d->error = function->error();
|
|
return HNumber(0);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// more than one value in stack ? unsuccesfull execution...
|
|
if (stack.count() != 1)
|
|
{
|
|
d->error = QObject::tr("Invalid expression.");
|
|
return HNumber(0);
|
|
}
|
|
|
|
HNumber result = stack.pop();
|
|
|
|
// handle variable assignment, e.g. "x=2*4"
|
|
if (!d->assignId.isEmpty())
|
|
set(d->assignId, result);
|
|
|
|
// "ans" is default variable to hold calculation result
|
|
set(QString("ans"), result);
|
|
|
|
return result;
|
|
}
|
|
|
|
void Evaluator::set(const QString& id, HNumber value)
|
|
{
|
|
if (!id.isEmpty())
|
|
{
|
|
d->variables[ id ].name = id;
|
|
d->variables[ id ].value = value;
|
|
}
|
|
}
|
|
|
|
HNumber Evaluator::get(const QString& id)
|
|
{
|
|
if (id.isEmpty()) return HNumber(0);
|
|
|
|
if (!d->variables.contains(id))
|
|
set(id, HNumber(0));
|
|
return d->variables[ id ].value;
|
|
}
|
|
|
|
bool Evaluator::has(const QString& id)
|
|
{
|
|
return id.isEmpty() ? false : d->variables.contains(id);
|
|
}
|
|
|
|
void Evaluator::remove(const QString& id)
|
|
{
|
|
d->variables.remove(id);
|
|
}
|
|
|
|
QVector<Variable> Evaluator::variables() const
|
|
{
|
|
QVector<Variable> result;
|
|
|
|
QMap<QString,Variable>::Iterator it;
|
|
for (it = d->variables.begin(); it != d->variables.end(); ++it)
|
|
{
|
|
Variable var;
|
|
var.name = it.value().name;
|
|
var.value = it.value().value;
|
|
result.append(var);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void Evaluator::clearVariables()
|
|
{
|
|
d->variables.clear();
|
|
set(QString("pi"), HMath::pi() );
|
|
set(QString("phi"), HMath::phi());
|
|
set(QString("ans"), HNumber(0) );
|
|
}
|
|
|
|
QString Evaluator::autoFix(const QString& expr, const QString& decimalPoint)
|
|
{
|
|
int par = 0;
|
|
QString result;
|
|
|
|
// strip off all funny characters
|
|
for (int c = 0; c < expr.length(); c++)
|
|
if (expr[c] >= QChar(32))
|
|
result.append(expr[c]);
|
|
|
|
// no extra whitespaces at the beginning and at the end
|
|
result = result.trimmed();
|
|
|
|
// strip trailing equal sign (=)
|
|
while (result.endsWith("="))
|
|
result = result.left(result.length()-1);
|
|
|
|
// automagically close all parenthesis
|
|
Tokens tokens = Evaluator::scan(result, decimalPoint);
|
|
if (tokens.count())
|
|
{
|
|
for (int i=0; i<tokens.count(); i++)
|
|
if (tokens[i].asOperator() == Token::LeftPar) par++;
|
|
else if (tokens[i].asOperator() == Token::RightPar) par--;
|
|
|
|
if (par < 0)
|
|
par = 0;
|
|
|
|
// if the scanner stops in the middle, do not bother to apply fix
|
|
const Token& lastToken = tokens[tokens.count()-1];
|
|
if (lastToken.pos()+lastToken.text().length() >= result.length())
|
|
while (par--)
|
|
result.append(')');
|
|
}
|
|
|
|
// special treatment for simple function
|
|
// e.g. "cos" is regarded as "cos(ans)"
|
|
if (!result.isEmpty())
|
|
{
|
|
Tokens tokens = Evaluator::scan(result, decimalPoint);
|
|
if (tokens.count() == 1)
|
|
{
|
|
if (tokens[0].isIdentifier())
|
|
{
|
|
Function* f = FunctionRepository::self()->function(tokens[0].text());
|
|
if (f) result.append("(ans)");
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Debugging aid
|
|
|
|
QString Evaluator::dump() const
|
|
{
|
|
QString result;
|
|
int c;
|
|
|
|
if (d->dirty)
|
|
{
|
|
Tokens tokens = scan(d->expression, d->decimalPoint);
|
|
compile(tokens);
|
|
}
|
|
|
|
result = QString("Expression: [%1]\n").arg(d->expression);
|
|
|
|
result.append(" Constants:\n");
|
|
for (c = 0; c < d->constants.count(); c++)
|
|
{
|
|
QString vtext;
|
|
HNumber val = d->constants[c];
|
|
char* ss = HMath::formatFixed(val);
|
|
result += QString(" #%1 = %2\n").arg(c).arg(ss);
|
|
free(ss);
|
|
}
|
|
|
|
result.append("\n");
|
|
result.append(" Identifiers:\n");
|
|
for (c = 0; c < d->identifiers.count(); c++)
|
|
{
|
|
QString vtext;
|
|
result += QString(" #%1 = %2\n").arg(c).arg(d->identifiers[c]);
|
|
}
|
|
|
|
result.append("\n");
|
|
result.append(" Code:\n");
|
|
for (int i = 0; i < d->codes.count(); i++)
|
|
{
|
|
QString ctext;
|
|
switch (d->codes[i].type)
|
|
{
|
|
case Opcode::Load : ctext = QString("Load #%1" ).arg(d->codes[i].index); break;
|
|
case Opcode::Ref : ctext = QString("Ref #%1" ).arg(d->codes[i].index); break;
|
|
case Opcode::Function: ctext = QString("Function (%1)").arg(d->codes[i].index); break;
|
|
case Opcode::Add : ctext = "Add" ; break;
|
|
case Opcode::Sub : ctext = "Sub" ; break;
|
|
case Opcode::Mul : ctext = "Mul" ; break;
|
|
case Opcode::Div : ctext = "Div" ; break;
|
|
case Opcode::Neg : ctext = "Neg" ; break;
|
|
case Opcode::Pow : ctext = "Pow" ; break;
|
|
case Opcode::Fact : ctext = "Fact" ; break;
|
|
default : ctext = "Unknown"; break;
|
|
}
|
|
result.append(" ").append(ctext).append("\n");
|
|
}
|
|
|
|
return result;
|
|
}
|