valentina/src/libs/qmuparser/qmudef.cpp
2024-02-20 08:58:06 +02:00

677 lines
18 KiB
C++

/***************************************************************************************************
**
** Copyright (C) 2016 Roman Telezhynskyi <dismine(at)gmail.com>
**
** Permission is hereby granted, free of charge, to any person obtaining a copy of this
** software and associated documentation files (the "Software"), to deal in the Software
** without restriction, including without limitation the rights to use, copy, modify,
** merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all copies or
** substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
** NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
** DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
******************************************************************************************************/
#include "qmudef.h"
#include <QLocale>
#include <QSet>
enum State
{
Init = 0,
Sign = 1,
Thousand = 2,
Mantissa = 3,
Dot = 4,
Abscissa = 5,
ExpMark = 6,
ExpSign = 7,
Exponent = 8,
Done = 9
};
enum InputToken
{
InputSign = 1,
InputThousand = 2,
InputDigit = 3,
InputDot = 4,
InputExp = 5
};
static const QChar QmuEOF = QChar(static_cast<ushort>(0xffff)); // guaranteed not to be a character.
//---------------------------------------------------------------------------------------------------------------------
static auto GetChar(const QString &formula, int &index) -> QChar
{
if (index >= formula.size())
{
return QmuEOF;
}
return formula.at(index++);
}
//---------------------------------------------------------------------------------------------------------------------
static auto EatWhiteSpace(const QString &formula, int &index) -> QChar
{
QChar c;
do
{
c = GetChar(formula, index);
} while (c != QmuEOF && c.isSpace());
return c;
}
//---------------------------------------------------------------------------------------------------------------------
static auto CheckChar(QChar &c, const QLocale &locale, const QChar &decimal, const QChar &thousand) -> int
{
INIT_LOCALE_VARIABLES(locale);
Q_UNUSED(decimalPoint)
Q_UNUSED(groupSeparator)
if (c == positiveSign)
{
c = '+';
return InputToken::InputSign;
}
else if (c == negativeSign)
{
c = '-';
return InputToken::InputSign;
}
else if (c == sign0)
{
c = '0';
return InputToken::InputDigit;
}
else if (c == sign1)
{
c = '1';
return InputToken::InputDigit;
}
else if (c == sign2)
{
c = '2';
return InputToken::InputDigit;
}
else if (c == sign3)
{
c = '3';
return InputToken::InputDigit;
}
else if (c == sign4)
{
c = '4';
return InputToken::InputDigit;
}
else if (c == sign5)
{
c = '5';
return InputToken::InputDigit;
}
else if (c == sign6)
{
c = '6';
return InputToken::InputDigit;
}
else if (c == sign7)
{
c = '7';
return InputToken::InputDigit;
}
else if (c == sign8)
{
c = '8';
return InputToken::InputDigit;
}
else if (c == sign9)
{
c = '9';
return InputToken::InputDigit;
}
else if (c == decimal)
{
return InputToken::InputDot;
}
else if (c == thousand)
{
return InputToken::InputThousand;
}
else if (c == expLower)
{
c = 'e';
return InputToken::InputExp;
}
else if (c == expUpper)
{
c = 'E';
return InputToken::InputExp;
}
else
{
return 0;
}
return 0;
}
//---------------------------------------------------------------------------------------------------------------------
auto ReadVal(const QString &formula, qreal &val, const QLocale &locale, const QChar &decimal, const QChar &thousand)
-> qmusizetype
{
// Must not be equal
if (decimal == thousand || formula.isEmpty())
{
val = 0;
return -1;
}
INIT_LOCALE_VARIABLES(locale);
Q_UNUSED(decimalPoint)
Q_UNUSED(groupSeparator)
QSet<QChar> const reserved{positiveSign, negativeSign, sign0, sign1, sign2, sign3, sign4,
sign5, sign6, sign7, sign8, sign9, expUpper, expLower};
if (reserved.contains(decimal) || reserved.contains(thousand))
{
val = 0;
return -1;
}
// row - current state, column - new state
static uchar const table[9][6] = {
/* None InputSign InputThousand InputDigit InputDot InputExp */
{
0,
State::Sign,
0,
State::Mantissa,
State::Dot,
0,
}, // Init
{
0,
0,
0,
State::Mantissa,
State::Dot,
0,
}, // Sign
{
0,
0,
0,
State::Mantissa,
0,
0,
}, // Thousand
{
State::Done,
State::Done,
State::Thousand,
State::Mantissa,
State::Dot,
State::ExpMark,
}, // Mantissa
{
0,
0,
0,
State::Abscissa,
0,
0,
}, // Dot
{
State::Done,
State::Done,
0,
State::Abscissa,
0,
State::ExpMark,
}, // Abscissa
{
0,
State::ExpSign,
0,
State::Exponent,
0,
0,
}, // ExpMark
{
0,
0,
0,
State::Exponent,
0,
0,
}, // ExpSign
{State::Done, 0, 0, State::Exponent, 0, State::Done} // Exponent
};
int state = State::Init; // parse state
QString buf;
int index = 0; // start position
QChar c = EatWhiteSpace(formula, index);
while (true)
{
const int input = CheckChar(c, locale, decimal, thousand); // input token
state = table[state][input];
if (state == 0)
{
val = 0;
return -1;
}
else if (state == Done)
{
// Convert to C locale
QLocale cLocale(QLocale::C);
const QChar cDecimal = LocaleDecimalPoint(cLocale);
const QChar cThousand = LocaleGroupSeparator(cLocale);
if (locale != cLocale && (cDecimal != decimal || cThousand != thousand))
{
if (decimal == cThousand)
{ // Handle reverse to C locale case: thousand '.', decimal ','
const QChar tmpThousand = QLatin1Char('@');
buf.replace(thousand, tmpThousand);
buf.replace(decimal, cDecimal);
buf.replace(tmpThousand, cThousand);
}
else
{
buf.replace(thousand, cThousand);
buf.replace(decimal, cDecimal);
}
}
bool ok = false;
const double d = cLocale.toDouble(buf, &ok);
if (ok)
{
val = d;
return buf.size();
}
val = 0;
return -1;
}
buf.append(c);
c = GetChar(formula, index);
}
return -1;
}
//---------------------------------------------------------------------------------------------------------------------
auto NameRegExp(VariableRegex type) -> QString
{
static QString regex;
if (regex.isEmpty())
{
const QList<QLocale> allLocales =
QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);
QString positiveSigns;
QString negativeSigns;
QString decimalPoints;
QString groupSeparators;
for (const auto &locale : allLocales)
{
if (not positiveSigns.contains(LocalePositiveSign(locale)))
{
positiveSigns.append(LocalePositiveSign(locale));
}
if (not negativeSigns.contains(LocaleNegativeSign(locale)))
{
negativeSigns.append(LocaleNegativeSign(locale));
}
if (not decimalPoints.contains(LocaleDecimalPoint(locale)))
{
decimalPoints.append(LocaleDecimalPoint(locale));
}
if (not groupSeparators.contains(LocaleGroupSeparator(locale)))
{
groupSeparators.append(LocaleGroupSeparator(locale));
}
}
negativeSigns.replace('-', QLatin1String("\\-"));
groupSeparators.remove('\'');
// Same regexp in pattern.xsd shema file. Don't forget to synchronize.
// \p{Nd} - \p{Decimal_Digit_Number}
// \p{Zs} - \p{Space_Separator}
// Here we use permanent start of string and end of string anchors \A and \z to match whole pattern as one
// string. In some cases, a user may pass multiline or line that ends with a new line. To cover case with a new
// line at the end of string use /z anchor.
switch (type)
{
case VariableRegex::Variable:
regex = QString("\\A([^\\p{Nd}\\p{Zs}*\\/&|!<>^\\n\\()%1%2%3%4=?:;'\"]){1,1}"
"([^\\p{Zs}*\\/&|!<>^\\n\\()%1%2%3%4=?:;\"]){0,}\\z")
.arg(negativeSigns, positiveSigns, decimalPoints, groupSeparators);
break;
case VariableRegex::KnownMeasurement:
regex = QString("\\A([^@\\p{Nd}\\p{Zs}*\\/&|!<>^\\n\\()%1%2%3%4=?:;'\"]){1,1}"
"([^\\p{Zs}*\\/&|!<>^\\n\\()%1%2%3%4=?:;\"]){0,}\\z")
.arg(negativeSigns, positiveSigns, decimalPoints, groupSeparators);
break;
default:
break;
}
}
return regex;
}
//---------------------------------------------------------------------------------------------------------------------
auto FindFirstNotOf(const QString &string, const QString &chars, qmusizetype pos) -> qmusizetype
{
qmusizetype chPos = pos;
QString::const_iterator it = string.constBegin() + pos;
QString::const_iterator end = string.constEnd();
while (it != end)
{
if (not chars.contains(*it))
{
return chPos;
}
++it;
++chPos;
}
return -1;
}
//---------------------------------------------------------------------------------------------------------------------
auto SupportedLocale(const QLocale &locale) -> bool
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return locale.positiveSign().size() == 1 && locale.negativeSign().size() == 1 && locale.toString(0).size() == 1 &&
locale.toString(1).size() == 1 && locale.toString(2).size() == 1 && locale.toString(3).size() == 1 &&
locale.toString(4).size() == 1 && locale.toString(5).size() == 1 && locale.toString(6).size() == 1 &&
locale.toString(7).size() == 1 && locale.toString(8).size() == 1 && locale.toString(9).size() == 1 &&
locale.exponential().size() == 1 && locale.decimalPoint().size() == 1 && locale.groupSeparator().size() == 1;
#else
Q_UNUSED(locale)
return true;
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocalePositiveSign(const QLocale &locale) -> QChar
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const QString sign = locale.positiveSign();
if (sign.size() == 1)
{
return sign.front();
}
return QLocale::c().positiveSign().front();
#else
return locale.positiveSign();
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocaleNegativeSign(const QLocale &locale) -> QChar
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const QString sign = locale.negativeSign();
if (sign.size() == 1)
{
return sign.front();
}
return QLocale::c().negativeSign().front();
#else
return locale.negativeSign();
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocaleSign0(const QLocale &locale) -> QChar
{
const QString sign = locale.toString(0);
if (sign.size() == 1)
{
return sign.front();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return {'0'};
#else
return QChar('0');
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocaleSign1(const QLocale &locale) -> QChar
{
const QString sign = locale.toString(1);
if (sign.size() == 1)
{
return sign.front();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return {'1'};
#else
return QChar('1');
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocaleSign2(const QLocale &locale) -> QChar
{
const QString sign = locale.toString(2);
if (sign.size() == 1)
{
return sign.front();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return {'2'};
#else
return QChar('2');
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocaleSign3(const QLocale &locale) -> QChar
{
const QString sign = locale.toString(3);
if (sign.size() == 1)
{
return sign.front();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return {'3'};
#else
return QChar('3');
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocaleSign4(const QLocale &locale) -> QChar
{
const QString sign = locale.toString(4);
if (sign.size() == 1)
{
return sign.front();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return {'4'};
#else
return QChar('4');
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocaleSign5(const QLocale &locale) -> QChar
{
const QString sign = locale.toString(5);
if (sign.size() == 1)
{
return sign.front();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return {'5'};
#else
return QChar('5');
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocaleSign6(const QLocale &locale) -> QChar
{
const QString sign = locale.toString(6);
if (sign.size() == 1)
{
return sign.front();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return {'6'};
#else
return QChar('6');
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocaleSign7(const QLocale &locale) -> QChar
{
const QString sign = locale.toString(7);
if (sign.size() == 1)
{
return sign.front();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return {'7'};
#else
return QChar('7');
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocaleSign8(const QLocale &locale) -> QChar
{
const QString sign = locale.toString(8);
if (sign.size() == 1)
{
return sign.front();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return {'8'};
#else
return QChar('8');
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocaleSign9(const QLocale &locale) -> QChar
{
const QString sign = locale.toString(9);
if (sign.size() == 1)
{
return sign.front();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return {'9'};
#else
return QChar('9');
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocaleExpUpper(const QLocale &locale) -> QChar
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const QString sign = locale.exponential();
if (sign.size() == 1)
{
return sign.front().toUpper();
}
return QLocale::c().exponential().front().toUpper();
#else
return locale.exponential().toUpper();
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocaleExpLower(const QLocale &locale) -> QChar
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const QString sign = locale.exponential();
if (sign.size() == 1)
{
return sign.front().toLower();
}
return QLocale::c().exponential().front().toLower();
#else
return locale.exponential().toLower();
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocaleDecimalPoint(const QLocale &locale) -> QChar
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const QString sign = locale.decimalPoint();
if (sign.size() == 1)
{
return sign.front();
}
return QLocale::c().decimalPoint().front();
#else
return locale.decimalPoint();
#endif
}
//---------------------------------------------------------------------------------------------------------------------
auto LocaleGroupSeparator(const QLocale &locale) -> QChar
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const QString sign = locale.groupSeparator();
if (sign.size() == 1)
{
return sign.front();
}
return QLocale::c().groupSeparator().front();
#else
return locale.groupSeparator();
#endif
}