File indexing completed on 2024-05-05 17:45:01

0001 /*
0002     SPDX-FileCopyrightText: 2010 Matteo Agostinelli <agostinelli@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "qalculate_engine.h"
0008 
0009 #include <libqalculate/Calculator.h>
0010 #include <libqalculate/ExpressionItem.h>
0011 #include <libqalculate/Function.h>
0012 #include <libqalculate/Prefix.h>
0013 #include <libqalculate/Unit.h>
0014 #include <libqalculate/Variable.h>
0015 
0016 #include <QApplication>
0017 #include <QClipboard>
0018 #include <QDebug>
0019 #include <QFile>
0020 #include <QMutex>
0021 
0022 #include <KIO/Job>
0023 #include <KLocalizedString>
0024 #include <KProtocolManager>
0025 
0026 constexpr int evaluationTimeout = 10000;
0027 
0028 // Synchronization lock that ensures that
0029 // a) only one evaluation is running at a time
0030 // b) abortion and preemption of evaluation is synchronized
0031 class QalculateLock
0032 {
0033 public:
0034     QalculateLock()
0035     {
0036         QMutexLocker ctrlLocker(&s_ctrlLock);
0037         CALCULATOR->abort();
0038         s_evalLock.lock();
0039         CALCULATOR->startControl(evaluationTimeout);
0040     }
0041 
0042     ~QalculateLock()
0043     {
0044         CALCULATOR->stopControl();
0045         s_evalLock.unlock();
0046     }
0047 
0048 private:
0049     static QMutex s_ctrlLock;
0050     static QMutex s_evalLock;
0051 };
0052 
0053 QMutex QalculateLock::s_ctrlLock;
0054 QMutex QalculateLock::s_evalLock;
0055 
0056 QAtomicInt QalculateEngine::s_counter;
0057 
0058 QalculateEngine::QalculateEngine(QObject *parent)
0059     : QObject(parent)
0060 {
0061     s_counter.ref();
0062     if (!CALCULATOR) {
0063         new Calculator();
0064         CALCULATOR->terminateThreads();
0065         CALCULATOR->loadGlobalDefinitions();
0066         CALCULATOR->loadLocalDefinitions();
0067         CALCULATOR->loadGlobalCurrencies();
0068         CALCULATOR->loadExchangeRates();
0069     }
0070 }
0071 
0072 QalculateEngine::~QalculateEngine()
0073 {
0074     if (s_counter.deref()) {
0075         delete CALCULATOR;
0076         CALCULATOR = nullptr;
0077     }
0078 }
0079 
0080 void QalculateEngine::updateExchangeRates()
0081 {
0082     QUrl source = QUrl("http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml");
0083     QUrl dest = QUrl::fromLocalFile(QFile::decodeName(CALCULATOR->getExchangeRatesFileName().c_str()));
0084 
0085     KIO::Job *getJob = KIO::file_copy(source, dest, -1, KIO::Overwrite | KIO::HideProgressInfo);
0086     connect(getJob, &KJob::result, this, &QalculateEngine::updateResult);
0087 }
0088 
0089 void QalculateEngine::updateResult(KJob *job)
0090 {
0091     if (job->error()) {
0092         qDebug() << "The exchange rates could not be updated. The following error has been reported:" << job->errorString();
0093     } else {
0094         // the exchange rates have been successfully updated, now load them
0095         CALCULATOR->loadExchangeRates();
0096     }
0097 }
0098 
0099 #if QALCULATE_MAJOR_VERSION > 2 || QALCULATE_MINOR_VERSION > 6
0100 bool has_error()
0101 {
0102     while (CALCULATOR->message()) {
0103         if (CALCULATOR->message()->type() == MESSAGE_ERROR) {
0104             CALCULATOR->clearMessages();
0105             return true;
0106         }
0107         CALCULATOR->nextMessage();
0108     }
0109     return false;
0110 }
0111 
0112 bool check_valid_before(const std::string &expression, const EvaluationOptions &search_eo)
0113 {
0114     bool b_valid = false;
0115     if (!b_valid)
0116         b_valid = (expression.find_first_of(OPERATORS NUMBERS PARENTHESISS) != std::string::npos);
0117     if (!b_valid)
0118         b_valid = CALCULATOR->hasToExpression(expression, false, search_eo);
0119     if (!b_valid) {
0120         std::string str = expression;
0121         CALCULATOR->parseSigns(str);
0122         b_valid = (str.find_first_of(OPERATORS NUMBERS PARENTHESISS) != std::string::npos);
0123         if (!b_valid) {
0124             size_t i = str.find_first_of(SPACES);
0125             MathStructure m;
0126             if (!b_valid) {
0127                 CALCULATOR->parse(&m, str, search_eo.parse_options);
0128                 if (!has_error() && (m.isUnit() || m.isFunction() || (m.isVariable() && (i != std::string::npos || m.variable()->isKnown()))))
0129                     b_valid = true;
0130             }
0131         }
0132     }
0133     return b_valid;
0134 }
0135 #endif
0136 
0137 QString QalculateEngine::evaluate(const QString &expression, bool *isApproximate, int base, const QString &customBase)
0138 {
0139     if (expression.isEmpty()) {
0140         return QString();
0141     }
0142 
0143     QString input = expression;
0144     // Make sure to use toLocal8Bit, the expression can contain non-latin1 characters
0145     QByteArray ba = input.replace(QChar(0xA3), "GBP").replace(QChar(0xA5), "JPY").replace('$', "USD").replace(QChar(0x20AC), "EUR").toLocal8Bit();
0146     const char *ctext = ba.data();
0147 
0148     QalculateLock qalculateLock;
0149 
0150     EvaluationOptions eo;
0151 
0152     eo.auto_post_conversion = POST_CONVERSION_BEST;
0153     eo.keep_zero_units = false;
0154 
0155     eo.parse_options.angle_unit = ANGLE_UNIT_RADIANS;
0156     eo.structuring = STRUCTURING_SIMPLIFY;
0157 
0158     // suggested in https://github.com/Qalculate/libqalculate/issues/16
0159     // to avoid memory overflow for seemingly innocent calculations (Bug 277011)
0160     eo.approximation = APPROXIMATION_APPROXIMATE;
0161 
0162 #if QALCULATE_MAJOR_VERSION > 2 || QALCULATE_MINOR_VERSION > 6
0163     if (!check_valid_before(expression.toStdString(), eo)) {
0164         return QString(); // See https://github.com/Qalculate/libqalculate/issues/442
0165     }
0166 #endif
0167 
0168     CALCULATOR->setPrecision(16);
0169 #ifdef BASE_CUSTOM // v3.3.0 has setCustomOutputBase
0170     if (base == BASE_CUSTOM) {
0171         EvaluationOptions eo;
0172         eo.parse_options.base = 10;
0173         eo.approximation = APPROXIMATION_TRY_EXACT;
0174 
0175         MathStructure m = CALCULATOR->calculate(customBase.toStdString(), eo);
0176 
0177         if (m.isNumber() && (m.number().isPositive() || m.number().isInteger()) && (m.number().isGreaterThan(1) || m.number().isLessThan(-1))) {
0178             CALCULATOR->setCustomOutputBase(m.number());
0179         } else {
0180             base = BASE_DECIMAL;
0181         }
0182     }
0183 #endif
0184 
0185     MathStructure result = CALCULATOR->calculate(ctext, eo);
0186 
0187     PrintOptions po;
0188     po.base = base;
0189     po.number_fraction_format = FRACTION_DECIMAL;
0190     po.indicate_infinite_series = false;
0191     po.use_all_prefixes = false;
0192     po.use_denominator_prefix = true;
0193     po.negative_exponents = false;
0194     po.lower_case_e = true;
0195     po.base_display = BASE_DISPLAY_NORMAL;
0196 #if defined(QALCULATE_MAJOR_VERSION) && defined(QALCULATE_MINOR_VERSION)                                                                                       \
0197     && (QALCULATE_MAJOR_VERSION > 2 || (QALCULATE_MAJOR_VERSION == 2 && QALCULATE_MINOR_VERSION >= 2))
0198     po.interval_display = INTERVAL_DISPLAY_SIGNIFICANT_DIGITS;
0199 #endif
0200 
0201     result.format(po);
0202 
0203     m_lastResult = QString::fromStdString(result.print(po));
0204 
0205     if (isApproximate) {
0206         *isApproximate = result.isApproximate();
0207     }
0208 
0209     return m_lastResult;
0210 }
0211 
0212 void QalculateEngine::copyToClipboard(bool flag)
0213 {
0214     Q_UNUSED(flag);
0215 
0216     QApplication::clipboard()->setText(m_lastResult);
0217 }
0218 
0219 static const QMap<QString, int> s_commonBaseMappings = {
0220     {QStringLiteral("roman"), BASE_ROMAN_NUMERALS},
0221     {QStringLiteral("time"), BASE_TIME},
0222 
0223     {QStringLiteral("bin"), BASE_BINARY},
0224     {QStringLiteral("oct"), BASE_OCTAL},
0225     {QStringLiteral("dec"), BASE_DECIMAL},
0226     {QStringLiteral("duo"), 12},
0227     {QStringLiteral("hex"), BASE_HEXADECIMAL},
0228     {QStringLiteral("sexa"), BASE_SEXAGESIMAL},
0229 
0230 #ifdef BASE_CUSTOM // v3.3.0
0231     {QStringLiteral("pi"), BASE_PI},
0232     {QStringLiteral("e"), BASE_E},
0233     {QStringLiteral("sqrt2"), BASE_SQRT2},
0234 
0235     {QStringLiteral("golden"), BASE_GOLDEN_RATIO},
0236     {QStringLiteral("supergolden"), BASE_SUPER_GOLDEN_RATIO},
0237 
0238     {QStringLiteral("unicode"), BASE_UNICODE},
0239 #endif // v3.3.0
0240 
0241 #ifdef BASE_BIJECTIVE_26 // v3.5.0
0242     {QStringLiteral("bijective"), BASE_BIJECTIVE_26},
0243     {QStringLiteral("b26"), BASE_BIJECTIVE_26},
0244 #endif // v3.5.0
0245 
0246 #ifdef BASE_FP16 // v3.8.0
0247     {QStringLiteral("fp16"), BASE_FP16},
0248     {QStringLiteral("fp32"), BASE_FP32},
0249     {QStringLiteral("fp64"), BASE_FP64},
0250     {QStringLiteral("fp80"), BASE_FP80},
0251     {QStringLiteral("fp128"), BASE_FP128},
0252 #endif // v3.8.0
0253 
0254 #ifdef BASE_SEXAGESIMAL_2 // v3.18.0
0255     {QStringLiteral("sexa2"), BASE_SEXAGESIMAL_2},
0256     {QStringLiteral("sexa3"), BASE_SEXAGESIMAL_3},
0257 
0258     {QStringLiteral("latitude"), BASE_LATITUDE},
0259     {QStringLiteral("latitude2"), BASE_LATITUDE_2},
0260     {QStringLiteral("longitude"), BASE_LONGITUDE},
0261     {QStringLiteral("longitude2"), BASE_LONGITUDE_2},
0262 #endif
0263 
0264 #ifdef BASE_BINARY_DECIMAL // v4.2.0
0265     {QStringLiteral("bcd"), BASE_BINARY_DECIMAL},
0266 #endif
0267 };
0268 
0269 bool QalculateEngine::findPrefix(QString basePrefix, int *base, QString *customBase)
0270 {
0271     if (basePrefix.isEmpty()) {
0272         return true;
0273     }
0274 
0275     basePrefix = basePrefix.toLower();
0276     if (s_commonBaseMappings.contains(basePrefix)) {
0277         *base = s_commonBaseMappings[basePrefix];
0278         return true;
0279     }
0280 #ifdef BASE_CUSTOM // v3.3.0
0281     if (basePrefix.startsWith("base")) {
0282         *base = BASE_CUSTOM;
0283         *customBase = basePrefix.mid(4);
0284 
0285         return true;
0286     }
0287 #endif
0288     return false;
0289 }