//SPDX-License-Identifier: GPL-3.0
/*
* qalculate-helper.cpp
* Copyright (C) 2024 Marko Zajc
*
* 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, version 3.
*
* 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. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using std::string;
using std::stringstream;
using std::getline;
using std::vector;
using std::string_view;
using std::size_t;
using std::pair;
#if __cplusplus >= 201703L
#include
static bool ends_with(string_view str, string_view suffix) {
return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
}
#endif
const char TYPE_MESSAGE = 1;
const char TYPE_RESULT = 2;
const char LEVEL_INFO = 1;
const char LEVEL_WARNING = 2;
const char LEVEL_ERROR = 3;
const char LEVEL_UNKNOWN = 4;
const char SEPARATOR = 0;
const unsigned long MODE_PRECISION = 1 << 0;
const unsigned long MODE_EXACT = 1 << 1;
const unsigned long MODE_NOCOLOR = 1 << 2;
struct MathResult {
MathStructure input;
MathStructure output;
};
static void print_messages(unsigned long line_number, Calculator &calc) {
const CalculatorMessage *message;
while ((message = calc.message())) {
putchar(TYPE_MESSAGE);
switch (message->type()) {
case MESSAGE_INFORMATION:
putchar(LEVEL_INFO);
break;
case MESSAGE_WARNING:
putchar(LEVEL_WARNING);
break;
case MESSAGE_ERROR:
putchar(LEVEL_ERROR);
break;
default:
putchar(LEVEL_UNKNOWN);
break;
}
printf("line %lu: ", line_number);
fputs(message->c_message(), stdout);
putchar(SEPARATOR);
calc.nextMessage();
}
}
static MathStructure evaluate_single(Calculator &calc, const EvaluationOptions &eo, unsigned long line_number,
const string &expression, MathStructure *out_parsed = nullptr) {
MathStructure result;
if (!calc.calculate(&result, calc.unlocalizeExpression(expression), TIMEOUT_CALC, eo, out_parsed))
throw timeout_exception();
print_messages(line_number, calc);
return result;
}
static bool mode_set(unsigned long mode, unsigned long test) {
return mode & test;
}
static void set_precision(Calculator &calc, unsigned long mode, EvaluationOptions &eo, PrintOptions &po) {
int precision = PRECISION_DEFAULT;
if (mode_set(mode, MODE_EXACT)) {
eo.approximation = APPROXIMATION_EXACT;
po.number_fraction_format = FRACTION_DECIMAL_EXACT;
} else if (mode_set(mode, MODE_PRECISION)) {
precision = PRECISION_HIGH;
po.indicate_infinite_series = false;
}
calc.setPrecision(precision);
}
static MathResult evaluate_all(Calculator &calc, const vector &expressions, const EvaluationOptions &eo) {
for (size_t i = 0; i < expressions.size() - 1; ++i)
evaluate_single(calc, eo, i + 1, expressions[i]);
MathStructure parsed;
MathStructure evaluated = evaluate_single(calc, eo, expressions.size(), expressions.back(), &parsed);
return {parsed, evaluated};
}
static bool has_unknown(const MathStructure &ms) {
if (ms.size() == 0)
return ms.isUnknown();
else
return has_unknown(ms[0]);
}
static void replace_booleans(Calculator &calc, MathResult &result) {
bool inputBoolean = result.input.isLogicalAnd() || result.input.isLogicalNot() || result.input.isLogicalOr()
|| result.input.isLogicalXor() || result.input.isComparison();
bool outputBoolean = !has_unknown(result.output);
if (inputBoolean && outputBoolean && result.output.representsBoolean()) {
auto *replacement = result.output.isZero() ? calc.getActiveVariable("false") : calc.getActiveVariable("true");
if (replacement)
result.output.set(replacement);
}
}
static void print_result(Calculator &calc, MathResult result_struct, const PrintOptions &po, int mode) {
replace_booleans(calc, result_struct);
string result = calc.print(result_struct.output, TIMEOUT_PRINT, po, false, mode_set(mode, MODE_NOCOLOR) ? 0 : 1,
TAG_TYPE_TERMINAL);
if (ends_with(result, calc.timedOutString()))
throw timeout_exception();
if (!result_struct.output.isComparison()) // comparisons (eg. "x = 1") already have a comparison sign
result = (result_struct.output.isApproximate() ? "≈ " : "= ") + result;
if (!mode_set(mode, MODE_NOCOLOR) && ends_with(result, "\033[0m"))
result.erase(result.length() - 4);
putchar(TYPE_RESULT);
fputs(result.c_str(), stdout);
putchar(SEPARATOR);
}
static EvaluationOptions get_evaluationoptions() {
EvaluationOptions eo;
eo.approximation = APPROXIMATION_TRY_EXACT;
eo.parse_options.unknowns_enabled = false;
// eo.sync_units = false; // causes issues with monetary conversion, eg x usd > 1 eur. no idea why I
// enabled this in the first place
return eo;
}
static PrintOptions get_printoptions(int base) {
PrintOptions po;
po.base = base;
po.number_fraction_format = FRACTION_DECIMAL;
po.interval_display = INTERVAL_DISPLAY_PLUSMINUS;
po.use_unicode_signs = true;
po.time_zone = TIME_ZONE_UTC;
po.abbreviate_names = true;
po.spell_out_logical_operators = true;
po.allow_non_usable = true;
po.show_ending_zeroes = false;
return po;
}
static void load_calculator(Calculator &calc) {
do_defang_calculator(calc);
calc.loadExchangeRates();
calc.loadGlobalDefinitions();
#ifdef LIBQALCULATE_PRELOAD_DATASETS
DataSet *set = calc.getDataSet(1); // getDataSet() is 1-indexed for some reason
size_t i = 2;
while (set) {
set->loadObjects();
set = calc.getDataSet(i++);
}
#endif
}
static void evaluate(Calculator &calc, const vector &expressions, unsigned int mode, int base) {
PrintOptions po = get_printoptions(base);
EvaluationOptions eo = get_evaluationoptions();
set_precision(calc, mode, eo, po);
calc.setMessagePrintOptions(po);
do_seccomp();
auto result_struct = evaluate_all(calc, expressions.empty() ? vector {"0"} : expressions, eo);
print_result(calc, result_struct, po, mode);
}
static vector parseExpressions(stringstream input) {
vector result;
string expression;
while (std::getline(input, expression, '\n'))
result.push_back(expression);
return result;
}
int main(int argc, char **argv) {
do_setuid();
if (argc != 4)
return 1;
Calculator calc(true);
try {
load_calculator(calc);
evaluate(calc, parseExpressions(stringstream(argv[1])), std::strtoul(argv[2], nullptr, 10),
std::strtol(argv[3], nullptr, 10));
} catch (const qalculate_exception &e) {
return e.getCode();
}
}