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