diff --git a/providers/Calculator.cpp b/providers/Calculator.cpp index 78a795c..123ded1 100644 --- a/providers/Calculator.cpp +++ b/providers/Calculator.cpp @@ -1,88 +1,96 @@ /* * Copyright 2010-2012 Bart Kroon * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "Calculator.h" #include #include #include #include #include #include #include #include #include "calculator/evaluator.h" #include "calculator/numberformatter.h" #include "calculator/settings.h" #include Calculator::Calculator(QObject *parent) : Provider(parent) { Settings::instance()->resultPrecision = 5; } Calculator::~Calculator() {} QList Calculator::getResults(QString query) { + query = query.trimmed(); + QList list; Evaluator *ev = Evaluator::instance(); + const QString origQuery = query; query = ev->autoFix(query); if (query.isEmpty()) { return list; } ev->setExpression(query); const Quantity quantity = ev->evalNoAssign(); - if (!ev->error().isEmpty()) { + ProviderResult *app = new ProviderResult; + + if (!ev->error().isEmpty() && query == origQuery) { return list; } + if (ev->error().isEmpty()) { + app->name = NumberFormatter::format(quantity); + app->program = app->name; + } else { + app->name = query + ":\n" + ev->error(); + } - ProviderResult *app = new ProviderResult; app->icon = "accessories-calculator"; - app->name = NumberFormatter::format(quantity); - app->program = app->name; app->object = this; app->type = i18n("Calculation"); app->isCalculation = true; list.append(app); return list; } int Calculator::launch(const QString &exec) { QClipboard* clipboard = QApplication::clipboard(); clipboard->setText(exec); return 0; } // kate: indent-mode cstyle; space-indent on; indent-width 4; diff --git a/providers/calculator/evaluator.cpp b/providers/calculator/evaluator.cpp index 3cfdd3d..f674f9b 100644 --- a/providers/calculator/evaluator.cpp +++ b/providers/calculator/evaluator.cpp @@ -1,2187 +1,2187 @@ // This file is part of the SpeedCrunch project // Copyright (C) 2004 Ariya Hidayat // Copyright (C) 2005, 2006 Johan Thelin // Copyright (C) 2007-2016 @heldercorreia // Copyright (C) 2009 Wolf Lammen // Copyright (C) 2014 Tey // Copyright (C) 2015 Pol Welter // Copyright (C) 2015 Hadrien Theveneau // // 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 "settings.h" #include "rational.h" #include "units.h" #include #include #include #define ALLOW_IMPLICIT_MULT static constexpr int MAX_PRECEDENCE = INT_MAX; static constexpr int INVALID_PRECEDENCE = INT_MIN; #ifdef EVALUATOR_DEBUG #include #include #include QTextStream& operator<<(QTextStream& s, Quantity num) { s << DMath::format(num, Quantity::Format::Fixed()); return s; } #endif // EVALUATOR_DEBUG static Evaluator* s_evaluatorInstance = 0; static void s_deleteEvaluator() { delete s_evaluatorInstance; } bool isMinus(const QChar& ch) { return ch == QLatin1Char('-') || ch == QChar(0x2212); } bool isExponent(const QChar& ch, int base) { switch (base) { case 2: return ch == QLatin1Char('b') || ch == QLatin1Char('B'); case 8: return ch == QLatin1Char('o') || ch == QLatin1Char('O') || ch == QLatin1Char('C'); case 10: return ch == QLatin1Char('e') || ch == QLatin1Char('E'); case 16: return ch == QLatin1Char('h') || ch == QLatin1Char('H'); default: return false; } } const Quantity& Evaluator::checkOperatorResult(const Quantity& n) { switch (n.error()) { case Success: break; case NoOperand: if (!m_assignFunc) // The arguments are still NaN, so ignore this error. m_error = Evaluator::tr("cannot operate on a NaN"); break; case Underflow: m_error = Evaluator::tr("underflow - tiny result is out " "of SpeedCrunch's number range"); break; case Overflow: m_error = Evaluator::tr("overflow - huge result is out of " "SpeedCrunch's number range"); break; case ZeroDivide: m_error = Evaluator::tr("division by zero"); break; case OutOfLogicRange: m_error = Evaluator::tr("overflow - logic result exceeds " "maximum of 256 bits"); break; case OutOfIntegerRange: m_error = Evaluator::tr("overflow - integer result exceeds " "maximum limit for integers"); break; case TooExpensive: m_error = Evaluator::tr("too time consuming - " "computation was rejected"); break; case DimensionMismatch: // We cannot make any assumptions about the dimension of the arguments, // so ignore this error when assigning a function. if (!m_assignFunc) m_error = Evaluator::tr("dimension mismatch - quantities with " "different dimensions cannot be " "compared, added, etc."); break; case InvalidDimension: m_error = Evaluator::tr("invalid dimension - operation might " "require dimensionless arguments"); break; case EvalUnstable: m_error = Evaluator::tr("Computation aborted - encountered " "numerical instability"); break; default:; } return n; } QString Evaluator::stringFromFunctionError(Function* function) { if (!function->error()) return QString(); QString result = QString::fromLatin1("%1: "); switch (function->error()) { case Success: break; case InvalidParamCount: result += Evaluator::tr("wrong number of arguments"); break; case NoOperand: result += Evaluator::tr("does not take NaN as an argument"); break; case Overflow: result += Evaluator::tr("overflow - huge result is out of " "SpeedCrunch's number range"); break; case Underflow: result += Evaluator::tr("underflow - tiny result is out of " "SpeedCrunch's number range"); break; case OutOfLogicRange: result += Evaluator::tr("overflow - logic result exceeds " "maximum of 256 bits"); break; case OutOfIntegerRange: result += Evaluator::tr("result out of range"); break; case ZeroDivide: result += Evaluator::tr("division by zero"); break; case EvalUnstable: result += Evaluator::tr("Computation aborted - " "encountered numerical instability"); break; case OutOfDomain: result += Evaluator::tr("undefined for argument domain"); break; case TooExpensive: result += Evaluator::tr("computation too expensive"); break; case InvalidDimension: result += Evaluator::tr("invalid dimension - function " "might require dimensionless arguments"); break; case DimensionMismatch: result += Evaluator::tr("dimension mismatch - quantities with " "different dimensions cannot be compared, " "added, etc."); break; case IONoBase: case BadLiteral: case InvalidPrecision: case InvalidParam: result += Evaluator::tr("internal error, please report a bug"); break; default: result += Evaluator::tr("error"); break; }; return result.arg(function->identifier()); } class TokenStack : public QVector { public: TokenStack(); bool isEmpty() const; unsigned itemCount() const; Token pop(); void push(const Token& token); const Token& top(); const Token& top(unsigned index); bool hasError() const { return !m_error.isEmpty(); } QString error() const { return m_error; } void reduce(int count, int minPrecedence = INVALID_PRECEDENCE); void reduce(int count, Token&& top, int minPrecedence = INVALID_PRECEDENCE); void reduce(QList tokens, Token&& top, int minPrecedence = INVALID_PRECEDENCE); private: void ensureSpace(); int topIndex; QString m_error; }; const Token Token::null; static Token::Operator matchOperator(const QString& text) { Token::Operator result = Token::Invalid; if (text.length() == 1) { QChar p = text.at(0); switch(p.unicode()) { case '+': result = Token::Addition; break; case 0x2212: // − MINUS SIGN case '-': result = Token::Subtraction; break; case 0x00D7: // × MULTIPLICATION SIGN case 0x22C5: // ⋅ DOT OPERATOR case '*': result = Token::Multiplication; break; case 0x00F7: // ÷ DIVISION SIGN case '/': result = Token::Division; break; case '^': result = Token::Exponentiation; break; case ';': result = Token::ListSeparator; break; case '(': result = Token::AssociationStart; break; case ')': result = Token::AssociationEnd; break; case '!': result = Token::Factorial; break; case '=': result = Token::Assignment; break; case '\\': result = Token::IntegerDivision; break; case '&': result = Token::BitwiseLogicalAND; break; case '|': result = Token::BitwiseLogicalOR; break; default: result = Token::Invalid; } } else if (text.length() == 2) { if (text == "**") result = Token::Exponentiation; else if (text == "<<") result = Token::ArithmeticLeftShift; else if (text == ">>") result = Token::ArithmeticRightShift; else if (text == "->" || text == "in") result = Token::UnitConversion; } return result; } // Helper function: give operator precedence e.g. "+" is 300 while "*" is 500. static int opPrecedence(Token::Operator op) { int prec; switch (op) { case Token::Factorial: prec = 800; break; case Token::Exponentiation: prec = 700; break; case Token::Function: // Not really operator but needed for managing shift/reduce conflicts. prec = 600; break; case Token::Multiplication: case Token::Division: prec = 500; break; case Token::Modulo: case Token::IntegerDivision: prec = 600; break; case Token::Addition: case Token::Subtraction: prec = 300; break; case Token::ArithmeticLeftShift: case Token::ArithmeticRightShift: prec = 200; break; case Token::BitwiseLogicalAND: prec = 100; break; case Token::BitwiseLogicalOR: prec = 50; break; case Token::UnitConversion: prec = 0; break; case Token::AssociationEnd: case Token::ListSeparator: prec = -100; break; case Token::AssociationStart: prec = -200; break; default: prec = -200; break; } return prec; } Token::Token(Type type, const QString& text, int pos, int size) { m_type = type; m_text = text; m_pos = pos; m_size = size; m_minPrecedence = MAX_PRECEDENCE; } Token::Token(const Token& token) { m_type = token.m_type; m_text = token.m_text; m_pos = token.m_pos; m_size = token.m_size; m_minPrecedence = token.m_minPrecedence; } Token& Token::operator=(const Token& token) { m_type = token.m_type; m_text = token.m_text; m_pos = token.m_pos; m_size = token.m_size; m_minPrecedence = token.m_minPrecedence; return*this; } Quantity Token::asNumber() const { QString text = m_text; return isNumber() ? Quantity(CNumber((const char*)text.toLatin1())) : Quantity(0); } Token::Operator Token::asOperator() const { return isOperator() ? matchOperator(m_text) : Invalid; } QString Token::description() const { QString desc; switch (m_type) { case stxNumber: desc = "Number"; break; case stxIdentifier: desc = "Identifier"; break; case stxOpenPar: case stxClosePar: case stxSep: case stxOperator: desc = "Operator"; break; default: desc = "Unknown"; break; } while (desc.length() < 10) desc.prepend(' '); QString header; header.append(QString::number(m_pos) + "," + QString::number(m_pos + m_size - 1)); header.append("," + (m_minPrecedence == MAX_PRECEDENCE ? "MAX" : QString::number(m_minPrecedence))); header.append(" "); while (header.length() < 10) header.append(' '); desc.prepend(header); desc.append(" : ").append(m_text); return desc; } static bool tokenPositionCompare(const Token& a, const Token& b) { return (a.pos() < b.pos()); } TokenStack::TokenStack() : QVector() { topIndex = 0; m_error = ""; 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() { if (topIndex > 0) return Token(at(--topIndex)); m_error = "token stack is empty (BUG)"; return Token(); } const Token& TokenStack::top() { return top(0); } const Token& TokenStack::top(unsigned index) { return (topIndex > (int)index) ? at(topIndex - index - 1) : Token::null; } void TokenStack::ensureSpace() { int length = size(); while (topIndex >= length) { length += 10; resize(length); } } /** Remove \a count tokens from the top of the stack, add a stxAbstract token * to the top and adjust its text position and minimum precedence. * * \param minPrecedence minimum precedence to set the top token, or * \c INVALID_PRECEDENCE if this method should use the minimum value from * the removed tokens. */ void TokenStack::reduce(int count, int minPrecedence) { // assert(itemCount() > count); QList tokens; for (int i = 0 ; i < count ; ++i) tokens.append(pop()); reduce(tokens, Token(Token::stxAbstract), minPrecedence); } /** Remove \a count tokens from the top of the stack, push \a top to the top * and adjust its text position and minimum precedence. * * \param minPrecedence minimum precedence to set the top token, or * \c INVALID_PRECEDENCE if this method should use the minimum value * from the removed tokens. */ void TokenStack::reduce(int count, Token&& top, int minPrecedence) { // assert(itemCount() >= count); QList tokens; for (int i = 0 ; i < count ; ++i) tokens.append(pop()); reduce(tokens, std::forward(top), minPrecedence); } /** Push \a top to the top and adjust its text position and minimum precedence * using \a tokens. * * \param minPrecedence minimum precedence to set the top token, or * \c INVALID_PRECEDENCE if this method should use the minimum value from the * removed tokens. */ void TokenStack::reduce(QList tokens, Token&& top, int minPrecedence) { #ifdef EVALUATOR_DEBUG { const auto& _tokens = tokens; qDebug() << "reduce(" << _tokens.size() << ", " << top.description() << ", " << minPrecedence << ")"; for (const auto& t : _tokens) qDebug() << t.description(); } #endif // EVALUATOR_DEBUG qSort(tokens.begin(), tokens.end(), tokenPositionCompare); bool computeMinPrec = (minPrecedence == INVALID_PRECEDENCE); int min_prec = computeMinPrec ? MAX_PRECEDENCE : minPrecedence; int start = -1, end = -1; const auto& _tokens = tokens; for (auto& token : _tokens) { if (computeMinPrec) { Token::Operator op = token.asOperator(); if (op != Token::Invalid) { int prec = opPrecedence(op); if (prec < min_prec) min_prec = prec; } } if (token.pos() == -1 && token.size() == -1) continue; if (token.pos() == -1 || token.size() == -1) { #ifdef EVALUATOR_DEBUG qDebug() << "BUG: found token with either pos or size not set, " "but not both."; #endif // EVALUATOR_DEBUG continue; } if (start == -1) { start = token.pos(); } else { #ifdef EVALUATOR_DEBUG if (token.pos() != end) qDebug() << "BUG: tokens expressions are not successive."; #endif // EVALUATOR_DEBUG } end = token.pos() + token.size(); } if (start != -1) { top.setPos(start); top.setSize(end - start); } top.setMinPrecedence(min_prec); #ifdef EVALUATOR_DEBUG qDebug() << "=> " << top.description(); #endif // EVALUATOR_DEBUG push(top); } #ifdef EVALUATOR_DEBUG void Tokens::append(const Token& token) { qDebug() << QString("tokens.append: type=%1 text=%2") .arg(token.type()) .arg(token.text()); QVector::append(token); } #endif // EVALUATOR_DEBUG // Helper function: return true for valid identifier character. static bool isIdentifier(QChar ch) { return ch.unicode() == '_' || ch.unicode() == '$' || ch.isLetter(); } // Helper function: return true for valid radix characters. bool Evaluator::isRadixChar(const QChar& ch) { if (Settings::instance()->isRadixCharacterBoth()) return ch.unicode() == '.' || ch.unicode() == ','; // There are more than 2 radix characters, actually: // U+0027 ' apostrophe // U+002C , comma // U+002E . full stop // U+00B7 · middle dot // U+066B ٫ arabic decimal separator // U+2396 ⎖ decimal separator key symbol return ch.unicode() == Settings::instance()->radixCharacter(); } // Helper function: return true for valid thousand separator characters. bool Evaluator::isSeparatorChar(const QChar& ch) { // Match everything that is not alphanumeric or an operator or NUL. static const QRegExp s_separatorRE( "[^a-zA-Z0-9\\+\\-−\\*×⋅÷/\\^;\\(\\)%!=\\\\&\\|<>\\?#\\x0000]" ); if (isRadixChar(ch)) return false; return s_separatorRE.exactMatch(ch); } QString Evaluator::fixNumberRadix(const QString& number) { int dotCount = 0; int commaCount = 0; QChar lastRadixChar; // First pass: count the number of dot and comma characters. for (int i = 0 ; i < number.size() ; ++i) { QChar c = number[i]; if (isRadixChar(c)) { lastRadixChar = c; if (c == '.') ++dotCount; else if (c == ',') ++commaCount; else return QString(); // Should not happen. } } // Decide which radix characters to ignore based on their occurence count. bool ignoreDot = dotCount != 1; bool ignoreComma = commaCount != 1; if (!ignoreDot && !ignoreComma) { // If both radix characters are present once, // consider the last one as the radix point. ignoreDot = lastRadixChar != '.'; ignoreComma = lastRadixChar != ','; } QChar radixChar; // Null character by default. if (!ignoreDot) radixChar = '.'; else if (!ignoreComma) radixChar = ','; // Second pass: write the result. QString result = ""; for (int i = 0 ; i < number.size() ; ++i) { QChar c = number[i]; if (isRadixChar(c)) { if (c == radixChar) result.append('.'); } else result.append(c); } return result; } Evaluator* Evaluator::instance() { static Evaluator inst; return &inst; // if (!s_evaluatorInstance) { // s_evaluatorInstance = new Evaluator; // qAddPostRoutine(s_deleteEvaluator); // } // return s_evaluatorInstance; } Evaluator::Evaluator() { reset(); } void Evaluator::initializeBuiltInVariables() { m_variables[QLatin1String("e")] = DMath::e(); m_variables[QString::fromUtf8("ℯ")] = DMath::e(); m_variables[QLatin1String("pi")] = DMath::pi(); m_variables[QString::fromUtf8("π")] = DMath::pi(); m_variables[QLatin1String("j")] = DMath::i(); QList unitList(Units::getList()); for (Unit& u : unitList) { m_variables[u.name] = u.value; } initializeAngleUnits(); } void Evaluator::initializeAngleUnits() { if (Settings::instance()->angleUnit == 'r') { m_variables["radian"] = 1; m_variables["degree"] = HMath::pi() / HNumber(180); m_variables["gradian"] = HMath::pi() / HNumber(200); m_variables["gon"] = HMath::pi() / HNumber(200); } else if (Settings::instance()->angleUnit == 'g') { m_variables["radian"] = HNumber(200) / HMath::pi(); m_variables["degree"] = HNumber(200) / HNumber(180); m_variables["gradian"] = 1; m_variables["gon"] = 1; } else { // d m_variables["radian"] = HNumber(180) / HMath::pi(); m_variables["degree"] = 1; m_variables["gradian"] = HNumber(180) / HNumber(200); m_variables["gon"] = HNumber(180) / HNumber(200); } } void Evaluator::setExpression(const QString& expr) { m_expression = expr; m_dirty = true; m_valid = false; m_error = QString(); } QString Evaluator::expression() const { return m_expression; } // Returns the validity. Note: empty expression is always invalid. bool Evaluator::isValid() { if (m_dirty) { Tokens tokens = scan(m_expression); if (!tokens.valid()) compile(tokens); else m_valid = false; } return m_valid; } void Evaluator::reset() { m_expression = QString(); m_dirty = true; m_valid = false; m_error = QString(); m_constants.clear(); m_codes.clear(); m_assignId = QString(); m_assignFunc = false; m_assignArg.clear(); m_functionsInUse.clear(); initializeBuiltInVariables(); } QString Evaluator::error() const { return m_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(m_expression); } Tokens Evaluator::scan(const QString& expr) const { // Associate character codes with the highest number base // they might belong to. constexpr unsigned DIGIT_MAP_COUNT = 128; static unsigned char s_digitMap[DIGIT_MAP_COUNT] = { 0 }; if (s_digitMap[0] == 0) { // Initialize the digits map. std::fill_n(s_digitMap, DIGIT_MAP_COUNT, 255); for (int i = '0' ; i <= '9' ; ++i) s_digitMap[i] = i - '0' + 1; for (int i = 'a' ; i <= 'z' ; ++i) s_digitMap[i] = i - 'a' + 11; for (int i = 'A' ; i <= 'Z' ; ++i) s_digitMap[i] = i - 'A' + 11; } // Result. Tokens tokens; // Parsing state. enum { Init, Start, Finish, Bad, InNumberPrefix, InNumber, InExpIndicator, InExponentBase, InExponent, InIdentifier, InNumberEnd } state; // Initialize variables. state = Init; int i = 0; QString ex = expr; QString tokenText; int tokenStart = 0; // Includes leading spaces. Token::Type type; int numberBase = 10; int expBase = 0; int expStart = -1; // Index of the exponent part in the expression. QString expText; // Start of the exponent text matching /E[\+\-]*/ // Force a terminator. ex.append(QChar()); #ifdef EVALUATOR_DEBUG qDebug() << "Scanning" << ex; #endif // EVALUATOR_DEBUG // Main loop. while (state != Bad && state != Finish && i < ex.length()) { QChar ch = ex.at(i); #ifdef EVALUATOR_DEBUG qDebug() << QString("state=%1 ch=%2 i=%3 tokenText=%4") .arg(state).arg(ch).arg(i).arg(tokenText); #endif // EVALUATOR_DEBUG switch (state) { case Init: tokenStart = i; tokenText = ""; state = Start; // State variables reset expStart = -1; expText = ""; numberBase = 10; expBase = 0; // No break here on purpose (make sure Start is the next case) case Start: // Skip any whitespaces. if (ch.isSpace()) ++i; else if (ch == '?') // Comment. state = Finish; else if (ch.isDigit()) { // Check for number state = InNumberPrefix; } else if (ch == '#') { // Simple hexadecimal notation tokenText.append("0x"); numberBase = 16; state = InNumber; ++i; } else if (isRadixChar(ch)) { // Radix character? tokenText.append(ch); numberBase = 10; state = InNumber; ++i; } else if (isSeparatorChar(ch)) { // Leading separator, probably a number state = InNumberPrefix; } else if (ch.isNull()) // Terminator character. state = Finish; else if (isIdentifier(ch)) // Identifier or alphanumeric operator state = InIdentifier; else { // Look for operator match. int op; QString s; s = QString(ch).append(ex.at(i+1)); op = matchOperator(s); // Check for one-char operator. if (op == Token::Invalid) { s = QString(ch); op = matchOperator(s); } // Any matched operator? if (op != Token::Invalid) { switch(op) { case Token::AssociationStart: type = Token::stxOpenPar; break; case Token::AssociationEnd: type = Token::stxClosePar; break; case Token::ListSeparator: type = Token::stxSep; break; default: type = Token::stxOperator; } int len = s.length(); i += len; int tokenSize = i - tokenStart; tokens.append(Token(type, s.left(len), tokenStart, tokenSize)); state = Init; } else state = Bad; } break; // Manage both identifier and alphanumeric operators. case InIdentifier: // Consume as long as alpha, dollar sign, underscore, or digit. if (isIdentifier(ch) || ch.isDigit()) tokenText.append(ex.at(i++)); else { // We're done with identifier. int tokenSize = i - tokenStart; if (matchOperator(tokenText)) { tokens.append(Token(Token::stxOperator, tokenText, tokenStart, tokenSize)); } else { // Normal identifier. tokens.append(Token(Token::stxIdentifier, tokenText, tokenStart, tokenSize)); } state = Init; } break; // Find out the number base. case InNumberPrefix: if (ch.isDigit()) { // Only consume the first digit and the second digit // if the first was 0. tokenText.append(ex.at(i++)); if (tokenText != "0") { numberBase = 10; state = InNumber; } } else if (isExponent(ch, numberBase)) { if (tokenText.endsWith("0")) { // Maybe exponent (tokenText is "0" or "-0"). numberBase = 10; expText = ch.toUpper(); expStart = i; ++i; state = InExpIndicator; } else { // Only leading separators. state = Bad; } } else if (isRadixChar(ch)) { // Might be a radix point or a separator. // Collect it and decide later. tokenText.append(ch); numberBase = 10; state = InNumber; ++i; } else if (ch.toUpper() == 'X' && tokenText == "0") { // Hexadecimal number. numberBase = 16; tokenText.append('x'); ++i; state = InNumber; } else if (ch.toUpper() == 'B' && tokenText == "0") { // Binary number. numberBase = 2; tokenText.append('b'); ++i; state = InNumber; } else if (ch.toUpper() == 'O' && tokenText == "0") { // Octal number. numberBase = 8; tokenText.append('o'); ++i; state = InNumber; } else if (ch.toUpper() == 'D' && tokenText == "0") { // Decimal number (with prefix). numberBase = 10; tokenText.append('d'); ++i; state = InNumber; } else if (isSeparatorChar(ch)) { // Ignore thousand separators. ++i; } else if (tokenText.isEmpty() && (ch == '+' || isMinus(ch))) { // Allow expressions like "$-10" or "$+10". if (isMinus(ch)) tokenText.append('-'); ++i; } else { if (tokenText.endsWith("0")) { // Done with integer number (tokenText is "0" or "-0"). numberBase = 10; state = InNumberEnd; } else { // Only leading separators. state = Bad; } } break; // Parse the number digits. case InNumber: { ushort c = ch.unicode(); bool isDigit = c < DIGIT_MAP_COUNT && (s_digitMap[c] <= numberBase); if (isDigit) { // Consume as long as it's a digit tokenText.append(ex.at(i++).toUpper()); } else if (isExponent(ch, numberBase)) { // Maybe exponent expText = ch.toUpper(); expStart = i; ++i; tokenText = fixNumberRadix(tokenText); if (!tokenText.isNull()) { state = InExpIndicator; } else state = Bad; } else if (isRadixChar(ch)) { // Might be a radix point or a separator. // Collect it and decide later. tokenText.append(ch); ++i; } else if (isSeparatorChar(ch)) { // Ignore thousand separators. ++i; } else { // We're done with number. tokenText = fixNumberRadix(tokenText); if (!tokenText.isNull()) state = InNumberEnd; else state = Bad; } break; } // Validate exponent start. case InExpIndicator: { ushort c = ch.unicode(); bool isDigit = c < DIGIT_MAP_COUNT && (s_digitMap[c] <= numberBase); if (expBase == 0) { // Set the default exponent base (same as number) expBase = numberBase; } if (expText.length() == 1 && (ch == '+' || isMinus(ch))) { // Possible + or - right after E. expText.append(ch == QChar(0x2212) ? '-' : ch); ++i; } else if (isDigit) { if (ch == '0') { // Might be a base prefix expText.append(ch); ++i; state = InExponentBase; } else { // Parse the exponent absolute value. tokenText.append(expText); state = InExponent; } } else if (isSeparatorChar(ch)) { // Ignore thousand separators. ++i; } else { // Invalid thing here. Rollback: might be an identifier // used in implicit multiplication. i = expStart; state = InNumberEnd; } break; } // Detect exponent base. case InExponentBase: { int base = -1; switch (ch.toUpper().unicode()) { case 'B': base = 2; break; case 'O': base = 8; break; case 'D': base = 10; break; case 'X': base = 16; break; } if (base != -1) { // Specific exponent base found expBase = base; tokenText.append(expText); tokenText.append(ch.toLower()); ++i; } else { // No exponent base specified, use the default one tokenText.append(expText); } state = InExponent; break; } // Parse exponent. case InExponent: { ushort c = ch.unicode(); bool isDigit = c < DIGIT_MAP_COUNT && (s_digitMap[c] <= expBase); if (isDigit) { // Consume as long as it's a digit. tokenText.append(ex.at(i++)); } else if (isSeparatorChar(ch)) { // Ignore thousand separators. ++i; } else { // We're done with floating-point number. state = InNumberEnd; }; break; } case InNumberEnd: { int tokenSize = i - tokenStart; tokens.append(Token(Token::stxNumber, tokenText, tokenStart, tokenSize)); // Make sure a number cannot be followed by another number. if (ch.isDigit() || isRadixChar(ch) || ch == '#') state = Bad; else state = Init; break; } case Bad: tokens.setValid(false); break; default: break; }; } if (state == Bad) // Invalidating here too, because usually when we set state to Bad, // the case Bad won't be run. tokens.setValid(false); return tokens; } void Evaluator::compile(const Tokens& tokens) { #ifdef EVALUATOR_DEBUG QFile debugFile("eval.log"); debugFile.open(QIODevice::WriteOnly); QTextStream dbg(&debugFile); #endif // Initialize variables. m_dirty = false; m_valid = false; m_codes.clear(); m_constants.clear(); m_identifiers.clear(); m_error = QString(); // Sanity check. if (tokens.count() == 0) return; TokenStack syntaxStack; QStack argStack; unsigned argCount = 1; for (int i = 0; i <= tokens.count() && !syntaxStack.hasError(); ++i) { // Helper token: Invalid is end-of-expression. auto token = (i < tokens.count()) ? tokens.at(i) : Token(Token::stxOperator); auto tokenType = token.type(); if (tokenType >= Token::stxOperator) tokenType = Token::stxOperator; #ifdef EVALUATOR_DEBUG dbg << "\nToken: " << token.description() << "\n"; #endif // Unknown token is invalid. if (tokenType == Token::stxUnknown) break; // Try to apply all parsing rules. #ifdef EVALUATOR_DEBUG dbg << "\tChecking rules..." << "\n"; #endif // Repeat until no more rule applies. bool argHandled = false; while (!syntaxStack.hasError()) { bool ruleFound = false; // Rule for function last argument: id (arg) -> arg. if (!ruleFound && 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::AssociationEnd && arg.isOperand() && par1.asOperator() == Token::AssociationStart && id.isIdentifier()) { ruleFound = true; syntaxStack.reduce(4, MAX_PRECEDENCE); m_codes.append(Opcode(Opcode::Function, argCount)); #ifdef EVALUATOR_DEBUG dbg << "\tRule for function last argument " << argCount << " \n"; #endif argCount = argStack.empty() ? 0 : argStack.pop(); } } // Are we entering a function? If token is operator, // and stack already has: id (arg. if (!ruleFound && !argHandled && tokenType == Token::stxOperator && syntaxStack.itemCount() >= 3) { Token arg = syntaxStack.top(); Token par = syntaxStack.top(1); Token id = syntaxStack.top(2); if (arg.isOperand() && par.asOperator() == Token::AssociationStart && id.isIdentifier()) { ruleFound = true; argStack.push(argCount); #ifdef EVALUATOR_DEBUG dbg << "\tEntering new function, pushing argcount=" << argCount << " of parent function\n"; #endif argCount = 1; break; } } // Rule for postfix operators: Y POSTFIX -> Y. // Condition: Y is not an operator, POSTFIX is a postfix op. // Since we evaluate from left to right, // we need not check precedence at this point. if (!ruleFound && syntaxStack.itemCount() >= 2) { Token postfix = syntaxStack.top(); Token y = syntaxStack.top(1); if (postfix.isOperator() && y.isOperand()) { switch (postfix.asOperator()) { case Token::Factorial: ruleFound = true; syntaxStack.reduce(2); m_codes.append(Opcode(Opcode::Fact)); break; default:; } } #ifdef EVALUATOR_DEBUG if (ruleFound) { dbg << "\tRule for postfix operator " << postfix.text() << "\n"; } #endif } // Rule for parenthesis: (Y) -> Y. if (!ruleFound && syntaxStack.itemCount() >= 3) { Token right = syntaxStack.top(); Token y = syntaxStack.top(1); Token left = syntaxStack.top(2); if (y.isOperand() && right.asOperator() == Token::AssociationEnd && left.asOperator() == Token::AssociationStart) { ruleFound = true; syntaxStack.reduce(3, MAX_PRECEDENCE); #ifdef EVALUATOR_DEBUG dbg << "\tRule for (Y) -> Y" << "\n"; #endif } } // Rule for simplified syntax for function, // e.g. "sin pi" or "cos 1.2". Conditions: // *precedence of function reduction >= precedence of next token. // *or next token is not an operator. if (!ruleFound && syntaxStack.itemCount() >= 2) { Token arg = syntaxStack.top(); Token id = syntaxStack.top(1); if (arg.isOperand() && isFunction(id) && (!token.isOperator() || opPrecedence(Token::Function) >= opPrecedence(token.asOperator()))) { ruleFound = true; m_codes.append(Opcode(Opcode::Function, 1)); syntaxStack.reduce(2); #ifdef EVALUATOR_DEBUG dbg << "\tRule for simplified function syntax; function " << id.text() << "\n"; #endif } } // Rule for unary operator in simplified function syntax. // This handles cases like "sin -90". if (!ruleFound && syntaxStack.itemCount() >= 3) { Token x = syntaxStack.top(); Token op = syntaxStack.top(1); Token id = syntaxStack.top(2); if (x.isOperand() && isFunction(id) && (op.asOperator() == Token::Addition || op.asOperator() == Token::Subtraction)) { ruleFound = true; syntaxStack.reduce(2); if (op.asOperator() == Token::Subtraction) m_codes.append(Opcode(Opcode::Neg)); #ifdef EVALUATOR_DEBUG dbg << "\tRule for unary operator in simplified " "function syntax; function " << id.text() << "\n"; #endif } } // Rule for function arguments. If token is ; or ): // id (arg1 ; arg2 -> id (arg. // Must come before binary op rule, special case of the latter. if (!ruleFound && syntaxStack.itemCount() >= 5 && token.isOperator() && (token.asOperator() == Token::AssociationEnd || token.asOperator() == Token::ListSeparator)) { 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.isOperand() && sep.asOperator() == Token::ListSeparator && arg1.isOperand() && par.asOperator() == Token::AssociationStart && id.isIdentifier()) { ruleFound = true; argHandled = true; syntaxStack.reduce(3, MAX_PRECEDENCE); ++argCount; #ifdef EVALUATOR_DEBUG dbg << "\tRule for function argument " << argCount << " \n"; #endif } } // Rule for function call with parentheses, // but without argument, e.g. "2*PI()". if (!ruleFound && syntaxStack.itemCount() >= 3) { Token par2 = syntaxStack.top(); Token par1 = syntaxStack.top(1); Token id = syntaxStack.top(2); if (par2.asOperator() == Token::AssociationEnd && par1.asOperator() == Token::AssociationStart && id.isIdentifier()) { ruleFound = true; syntaxStack.reduce(3, MAX_PRECEDENCE); m_codes.append(Opcode(Opcode::Function, 0)); #ifdef EVALUATOR_DEBUG dbg << "\tRule for function call with parentheses, " "but without argument\n"; #endif } } // 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 it doesn't apply, e.g. "2^3^2" is evaluated as "2^(3^2)". // Exception: doesn't apply if B is a function name (to manage // shift/reduce conflict with simplified function syntax // (issue #600). if (!ruleFound && syntaxStack.itemCount() >= 3) { Token b = syntaxStack.top(); Token op = syntaxStack.top(1); Token a = syntaxStack.top(2); if (a.isOperand() && b.isOperand() && op.isOperator() && ( // Normal operator. (token.isOperator() && opPrecedence(op.asOperator()) >= opPrecedence(token.asOperator()) && !(b.isIdentifier() && token.asOperator() == Token::AssociationStart) && token.asOperator() != Token::Exponentiation) || ( // May represent implicit multiplication. token.isOperand() && opPrecedence(op.asOperator()) >= opPrecedence(Token::Multiplication))) && !(isFunction(b))) { ruleFound = true; switch (op.asOperator()) { // Simple binary operations. case Token::Addition: m_codes.append(Opcode::Add); break; case Token::Subtraction: m_codes.append(Opcode::Sub); break; case Token::Multiplication: m_codes.append(Opcode::Mul); break; case Token::Division: m_codes.append(Opcode::Div); break; case Token::Exponentiation: m_codes.append(Opcode::Pow); break; case Token::Modulo: m_codes.append(Opcode::Modulo); break; case Token::IntegerDivision: m_codes.append(Opcode::IntDiv); break; case Token::ArithmeticLeftShift: m_codes.append(Opcode::LSh); break; case Token::ArithmeticRightShift: m_codes.append(Opcode::RSh); break; case Token::BitwiseLogicalAND: m_codes.append(Opcode::BAnd); break; case Token::BitwiseLogicalOR: m_codes.append(Opcode::BOr); break; case Token::UnitConversion: { static const QRegExp unitNameNumberRE( "(^[0-9e\\+\\-\\.,]|[0-9e\\.,]$)", Qt::CaseInsensitive); QString unitName = m_expression.mid(b.pos(), b.size()).simplified(); // Make sure the whole unit name can be used // as a single operand in multiplications. if (b.minPrecedence() < opPrecedence(Token::Multiplication)) { unitName = "(" + unitName + ")"; } // Protect the unit name // if it starts or ends with a number. else if (unitNameNumberRE.indexIn(unitName) != -1) unitName = "(" + unitName + ")"; m_codes.append(Opcode(Opcode::Conv, unitName)); break; } default: break; }; syntaxStack.reduce(3); #ifdef EVALUATOR_DEBUG dbg << "\tRule for binary operator" << "\n"; #endif } } #ifdef ALLOW_IMPLICIT_MULT // Rule for implicit multiplication. // Action: Treat as A * B. // Exception: doesn't apply if B is a function name // (to manage shift/reduce conflict with simplified // function syntax (fixes issue #600). if (!ruleFound && syntaxStack.itemCount() >= 2) { Token b = syntaxStack.top(); Token a = syntaxStack.top(1); if (a.isOperand() && b.isOperand() && token.asOperator() != Token::AssociationStart && ( // Token is normal operator. (token.isOperator() && opPrecedence(Token::Multiplication) >= opPrecedence(token.asOperator())) || token.isOperand()) // Implicit multiplication. && !isFunction(b)) { ruleFound = true; syntaxStack.reduce(2, opPrecedence(Token::Multiplication)); m_codes.append(Opcode::Mul); #ifdef EVALUATOR_DEBUG dbg << "\tRule for implicit multiplication" << "\n"; #endif } } #endif // Rule for unary operator: (op1) (op2) X -> (op1) X. // Conditions: op2 is unary. // Current token has lower precedence than multiplication. if (!ruleFound && token.asOperator() != Token::AssociationStart && syntaxStack.itemCount() >= 3) { Token x = syntaxStack.top(); Token op2 = syntaxStack.top(1); Token op1 = syntaxStack.top(2); if (x.isOperand() && op1.isOperator() && (op2.asOperator() == Token::Addition || op2.asOperator() == Token::Subtraction) && (token.isOperand() || opPrecedence(token.asOperator()) <= opPrecedence(Token::Multiplication))) { ruleFound = true; if (op2.asOperator() == Token::Subtraction) m_codes.append(Opcode(Opcode::Neg)); syntaxStack.reduce(2); #ifdef EVALUATOR_DEBUG dbg << "\tRule for unary operator" << op2.text() << "\n"; #endif } } // Auxiliary rule for unary prefix operator: (op) X -> X. // Conditions: op is unary, op is first in syntax stack. // Action: create code for (op). Unary MINUS or PLUS are // treated with the precedence of multiplication. if (!ruleFound && token.asOperator() != Token::AssociationStart && syntaxStack.itemCount() == 2) { Token x = syntaxStack.top(); Token op = syntaxStack.top(1); if (x.isOperand() && (op.asOperator() == Token::Addition || op.asOperator() == Token::Subtraction) && ((token.isOperator() && opPrecedence(token.asOperator()) <= opPrecedence(Token::Multiplication)) || token.isOperand())) { ruleFound = true; if (op.asOperator() == Token::Subtraction) m_codes.append(Opcode(Opcode::Neg)); #ifdef EVALUATOR_DEBUG dbg << "\tRule for unary operator (auxiliary)" << "\n"; #endif syntaxStack.reduce(2); } } if (!ruleFound) break; } // Can't apply rules anymore, push the token. syntaxStack.push(token); // For identifier, generate code to load from reference. if (tokenType == Token::stxIdentifier) { m_identifiers.append(token.text()); m_codes.append(Opcode(Opcode::Ref, m_identifiers.count() - 1)); #ifdef EVALUATOR_DEBUG dbg << "\tPush " << token.text() << " to identifier pools" << "\n"; #endif } // For constants, generate code to load from a constant. if (tokenType == Token::stxNumber) { m_constants.append(token.asNumber()); m_codes.append(Opcode(Opcode::Load, m_constants.count() - 1)); #ifdef EVALUATOR_DEBUG dbg << "\tPush " << token.asNumber() << " to constant pools" << "\n"; #endif } } m_valid = false; if (syntaxStack.hasError()) m_error = syntaxStack.error(); // syntaxStack must left only one operand // and end-of-expression (i.e. Invalid). else if (syntaxStack.itemCount() == 2 && syntaxStack.top().isOperator() && syntaxStack.top().asOperator() == Token::Invalid && !syntaxStack.top(1).isOperator()) { m_valid = true; } #ifdef EVALUATOR_DEBUG dbg << "Dump: " << dump() << "\n"; debugFile.close(); #endif // Bad parsing? Clean-up everything. if (!m_valid) { m_constants.clear(); m_codes.clear(); m_identifiers.clear(); } } Quantity Evaluator::evalNoAssign() { Quantity result; if (m_dirty) { // Reset. m_assignId = QString(); m_assignFunc = false; m_assignArg.clear(); Tokens tokens = scan(m_expression); // Invalid expression? if (!tokens.valid()) { m_error = tr("invalid expression"); return Quantity(0); } // Variable assignment? if (tokens.count() > 2 && tokens.at(0).isIdentifier() && tokens.at(1).asOperator() == Token::Assignment) { m_assignId = tokens.at(0).text(); tokens.erase(tokens.begin()); tokens.erase(tokens.begin()); } else if (tokens.count() > 2 && tokens.at(0).isIdentifier() && tokens.at(1).asOperator() == Token::AssociationStart) { // Check for function assignment. // Syntax: // ident opLeftPar (ident (opSemiColon ident)*)? opRightPar opEqual int t; if (tokens.count() > 4 && tokens.at(2).asOperator() == Token::AssociationEnd) { // Functions with no argument. t = 3; if (tokens.at(3).asOperator() == Token::Assignment) m_assignFunc = true; } else { for (t = 2; t + 1 < tokens.count(); t += 2) { if (!tokens.at(t).isIdentifier()) break; m_assignArg.append(tokens.at(t).text()); if (tokens.at(t+1).asOperator() == Token::AssociationEnd) { t += 2; if (t < tokens.count() && tokens.at(t).asOperator() == Token::Assignment) { m_assignFunc = true; } break; } else if (tokens.at(t + 1) .asOperator() != Token::ListSeparator) break; } } if (m_assignFunc) { m_assignId = tokens.at(0).text(); for (; t >= 0; --t) tokens.erase(tokens.begin()); } else m_assignArg.clear(); } compile(tokens); if (!m_valid) { if (m_error.isEmpty()) m_error = tr("compile error"); return CNumber(0); } } result = exec(m_codes, m_constants, m_identifiers); return result; } Quantity Evaluator::exec(const QVector& opcodes, const QVector& constants, const QStringList& identifiers) { QStack stack; QHash refs; int index; Quantity val1, val2; QVector args; QString fname; Function* function; for (int pc = 0; pc < opcodes.count(); ++pc) { const Opcode& opcode = opcodes.at(pc); index = opcode.index; switch (opcode.type) { // No operation. case Opcode::Nop: break; // Load a constant, push to stack. case Opcode::Load: val1 = constants.at(index); stack.push(val1); break; // Unary operation. case Opcode::Neg: if (stack.count() < 1) { m_error = tr("invalid expression"); return CMath::nan(); } val1 = stack.pop(); val1 = checkOperatorResult(-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) { m_error = tr("invalid expression"); return CMath::nan(); } val1 = stack.pop(); val2 = stack.pop(); val2 = checkOperatorResult(val2 + val1); stack.push(val2); break; case Opcode::Sub: if (stack.count() < 2) { m_error = tr("invalid expression"); return CMath::nan(); } val1 = stack.pop(); val2 = stack.pop(); val2 = checkOperatorResult(val2 - val1); stack.push(val2); break; case Opcode::Mul: if (stack.count() < 2) { m_error = tr("invalid expression"); return CMath::nan(); } val1 = stack.pop(); val2 = stack.pop(); val2 = checkOperatorResult(val2 * val1); stack.push(val2); break; case Opcode::Div: if (stack.count() < 2) { m_error = tr("invalid expression"); return CMath::nan(); } val1 = stack.pop(); val2 = stack.pop(); val2 = checkOperatorResult(val2 / val1); stack.push(val2); break; case Opcode::Pow: if (stack.count() < 2) { m_error = tr("invalid expression"); return CMath::nan(); } val1 = stack.pop(); val2 = stack.pop(); val2 = checkOperatorResult(DMath::raise(val2, val1)); stack.push(val2); break; case Opcode::Fact: if (stack.count() < 1) { m_error = tr("invalid expression"); return CMath::nan(); } val1 = stack.pop(); val1 = checkOperatorResult(DMath::factorial(val1)); stack.push(val1); break; case Opcode::Modulo: if (stack.count() < 2) { m_error = tr("invalid expression"); return CMath::nan(); } val1 = stack.pop(); val2 = stack.pop(); val2 = checkOperatorResult(val2 % val1); stack.push(val2); break; case Opcode::IntDiv: if (stack.count() < 2) { m_error = tr("invalid expression"); return CMath::nan(); } val1 = stack.pop(); val2 = stack.pop(); val2 = checkOperatorResult(val2 / val1); stack.push(DMath::integer(val2)); break; case Opcode::LSh: if (stack.count() < 2) { m_error = tr("invalid expression"); return DMath::nan(); } val1 = stack.pop(); val2 = stack.pop(); val2 = val2 << val1; stack.push(val2); break; case Opcode::RSh: if (stack.count() < 2) { m_error = tr("invalid expression"); return DMath::nan(); } val1 = stack.pop(); val2 = stack.pop(); val2 = val2 >> val1; stack.push(val2); break; case Opcode::BAnd: if (stack.count() < 2) { m_error = tr("invalid expression"); return DMath::nan(); } val1 = stack.pop(); val2 = stack.pop(); val2 &= val1; stack.push(val2); break; case Opcode::BOr: if (stack.count() < 2) { m_error = tr("invalid expression"); return DMath::nan(); } val1 = stack.pop(); val2 = stack.pop(); val2 |= val1; stack.push(val2); break; case Opcode::Conv: if (stack.count() < 2) { m_error = tr("invalid expression"); return HMath::nan(); } val1 = stack.pop(); val2 = stack.pop(); if (val1.isZero()) { m_error = tr("unit must not be zero"); return HMath::nan(); } if (!val1.sameDimension(val2)) { m_error = tr("Conversion failed - dimension mismatch"); return HMath::nan(); } val2.setDisplayUnit(val1.numericValue(), opcode.text); stack.push(val2); break; // Reference. case Opcode::Ref: fname = identifiers.at(index); if (m_assignArg.contains(fname)) { // Argument. stack.push(CMath::nan()); } else if (m_variables.contains(fname)) { // Variable. stack.push(m_variables.value(fname)); } else { // Function. function = FunctionRepo::instance()->find(fname); if (function) { stack.push(CMath::nan()); refs.insert(stack.count(), fname); } else if (m_assignFunc) { // Allow arbitrary identifiers // when declaring user functions. stack.push(CMath::nan()); refs.insert(stack.count(), fname); } else { m_error = "" + fname + ": " + tr("unknown function or variable"); return CMath::nan(); } } break; // Calling function. case Opcode::Function: // Must do this first to avoid crash // when using vars like functions. if (refs.isEmpty()) break; fname = refs.take(stack.count() - index); function = FunctionRepo::instance()->find(fname); if (!function && !m_assignFunc) { m_error = "" + fname + ": " + tr("unknown function or variable"); return CMath::nan(); } if (stack.count() < index) { m_error = tr("invalid expression"); return CMath::nan(); } args.clear(); for(; index; --index) args.insert(args.begin(), stack.pop()); // Remove the NaN we put on the stack (needed to make the user // functions declaration work with arbitrary identifiers). stack.pop(); // Show function signature if user has given no argument (yet). if (function) { if (!args.count()) { m_error = QString::fromLatin1("%1(%2)").arg( fname, function->usage() ); return CMath::nan(); } } if (m_assignFunc) { // Allow arbitrary identifiers for declaring user functions. stack.push(CMath::nan()); } else { stack.push(function->exec(args)); if (function->error()) { m_error = stringFromFunctionError(function); return CMath::nan(); } } break; default: break; } } // More than one value in stack? Unsuccessful execution. if (stack.count() != 1) { m_error = tr("invalid expression"); return CMath::nan(); } return stack.pop(); } static void replaceSuperscriptPowersWithCaretEquivalent(QString& expr) { static const QRegularExpression s_superscriptPowersRE( "(\\x{207B})?[\\x{2070}¹²³\\x{2074}-\\x{2079}]+" ); static const QHash s_superscriptPowersHash { {L'\u207B', '-'}, {L'\u2070', '0'}, {L'\u00B9', '1'}, {L'\u00B2', '2'}, {L'\u00B3', '3'}, {L'\u2074', '4'}, {L'\u2075', '5'}, {L'\u2076', '6'}, {L'\u2077', '7'}, {L'\u2078', '8'}, {L'\u2079', '9'}, }; int offset = 0; while (true) { QRegularExpressionMatch match = s_superscriptPowersRE.match(expr, offset); if (!match.hasMatch()) break; QString power = match.captured(); for (int pos = power.size() - 1; pos >= 0; --pos) { QChar c = power.at(pos); power.replace(pos, 1, s_superscriptPowersHash.value(c, c)); } bool isNegative = match.capturedStart(1) != -1; if (isNegative) power = "^(" + power + ")"; else power = "^" + power; expr.replace(match.capturedStart(), match.capturedLength(), power); offset = match.capturedStart() + power.size(); } } QString Evaluator::autoFix(const QString& expr) { int par = 0; QString result; // Strip off all funny characters. for (int c = 0; c < expr.length(); ++c) if (expr.at(c) >= QChar(32)) result.append(expr.at(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); replaceSuperscriptPowersWithCaretEquivalent(result); // Automagically close all parenthesis. Tokens tokens = Evaluator::scan(result); if (tokens.count()) { for (int i = 0; i < tokens.count(); ++i) if (tokens.at(i).asOperator() == Token::AssociationStart) ++par; else if (tokens.at(i).asOperator() == Token::AssociationEnd) --par; if (par < 0) par = 0; // If the scanner stops in the middle, do not bother to apply fix. const Token& lastToken = tokens.at(tokens.count() - 1); if (lastToken.pos() + lastToken.size() >= 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); if (tokens.count() == 1 && tokens.at(0).isIdentifier() && FunctionRepo::instance()->find(tokens.at(0).text())) { - result.append("(ans)"); + result.append("(0)"); } } return result; } QString Evaluator::dump() { QString result; int c; if (m_dirty) { Tokens tokens = scan(m_expression); compile(tokens); } result = QString("Expression: [%1]\n").arg(m_expression); result.append(" Constants:\n"); for (c = 0; c < m_constants.count(); ++c) { auto val = m_constants.at(c); result += QString(" #%1 = %2\n").arg(c).arg( DMath::format(val, Quantity::Format::Fixed()) ); } result.append("\n"); result.append(" Identifiers:\n"); for (c = 0; c < m_identifiers.count(); ++c) { result += QString(" #%1 = %2\n").arg(c).arg(m_identifiers.at(c)); } result.append("\n"); result.append(" Code:\n"); for (int i = 0; i < m_codes.count(); ++i) { QString code; switch (m_codes.at(i).type) { case Opcode::Load: code = QString("Load #%1").arg(m_codes.at(i).index); break; case Opcode::Ref: code = QString("Ref #%1").arg(m_codes.at(i).index); break; case Opcode::Function: code = QString("Function (%1)").arg(m_codes.at(i).index); break; case Opcode::Add: code = "Add"; break; case Opcode::Sub: code = "Sub"; break; case Opcode::Mul: code = "Mul"; break; case Opcode::Div: code = "Div"; break; case Opcode::Neg: code = "Neg"; break; case Opcode::Pow: code = "Pow"; break; case Opcode::Fact: code = "Fact"; break; case Opcode::LSh: code = "LSh"; break; case Opcode::RSh: code = "RSh"; break; case Opcode::BAnd: code = "BAnd"; break; case Opcode::BOr: code = "BOr"; break; default: code = "Unknown"; break; } result.append(" ").append(code).append("\n"); } return result; }