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 }