mirror of
https://github.com/luanti-org/luanti.git
synced 2025-10-05 19:31:04 +00:00
Minor refactor to the Plural-Forms
parser (#16489)
This commit is contained in:
parent
053ca6287a
commit
cc6b56b034
3 changed files with 158 additions and 174 deletions
|
@ -1,136 +1,77 @@
|
|||
// Minetest
|
||||
// Luanti
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
/* This file implements a recursive descent parser for gettext plural forms.
|
||||
* Left recursion (for left-associative operators) is implemented by parse_ltr, which iteratively attempts to reduce
|
||||
* expressions from operations of the same precedence. This should not be confused with reduce_ltr, which recurses
|
||||
* through a list of operators with the same precedence (not the input string!) to search for a match.
|
||||
* Note that this only implements a subset of C expressions. See:
|
||||
* https://git.savannah.gnu.org/gitweb/?p=gettext.git;a=blob;f=gettext-runtime/intl/plural.y
|
||||
*/
|
||||
|
||||
#include "gettext_plural_form.h"
|
||||
#include "util/string.h"
|
||||
#include <type_traits>
|
||||
|
||||
static size_t minsize(const GettextPluralForm::Ptr &form)
|
||||
static GettextPluralForm::NumT identity(GettextPluralForm::NumT n)
|
||||
{
|
||||
return form ? form->size() : 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
static size_t minsize(const GettextPluralForm::Ptr &f, const GettextPluralForm::Ptr &g)
|
||||
static GettextPluralForm::NumT ternary_op(GettextPluralForm::NumT n, const GettextPluralForm::Function &cond,
|
||||
const GettextPluralForm::Function &val, const GettextPluralForm::Function &alt)
|
||||
{
|
||||
if (sizeof(g) > 0)
|
||||
return std::min(minsize(f), minsize(g));
|
||||
return f ? f->size() : 0;
|
||||
return cond(n) ? val(n) : alt(n);
|
||||
}
|
||||
|
||||
class Identity: public GettextPluralForm
|
||||
template<template<typename> typename Func, class... Args>
|
||||
static GettextPluralForm::Function wrap_op(Args&&... args)
|
||||
{
|
||||
public:
|
||||
Identity(size_t nplurals): GettextPluralForm(nplurals) {};
|
||||
NumT operator()(const NumT n) const override
|
||||
{
|
||||
return n;
|
||||
}
|
||||
};
|
||||
return std::bind(Func<GettextPluralForm::NumT>(), std::bind(std::move(args), std::placeholders::_1)...);
|
||||
}
|
||||
|
||||
class ConstValue: public GettextPluralForm
|
||||
{
|
||||
public:
|
||||
ConstValue(size_t nplurals, NumT val): GettextPluralForm(nplurals), value(val) {};
|
||||
NumT operator()(const NumT n) const override
|
||||
{
|
||||
return value;
|
||||
}
|
||||
private:
|
||||
NumT value;
|
||||
};
|
||||
typedef std::pair<GettextPluralForm::Function, std::wstring_view> ParserResult;
|
||||
typedef ParserResult (*Parser)(std::wstring_view);
|
||||
|
||||
template<template<typename> typename F>
|
||||
class UnaryOperation: public GettextPluralForm
|
||||
{
|
||||
public:
|
||||
UnaryOperation(const Ptr &op):
|
||||
GettextPluralForm(minsize(op)), op(op) {}
|
||||
NumT operator()(const NumT n) const override
|
||||
{
|
||||
if (operator bool())
|
||||
return func((*op)(n));
|
||||
return 0;
|
||||
}
|
||||
private:
|
||||
Ptr op;
|
||||
static constexpr F<NumT> func = {};
|
||||
};
|
||||
|
||||
template<template<typename> typename F>
|
||||
class BinaryOperation: public GettextPluralForm
|
||||
{
|
||||
public:
|
||||
BinaryOperation(const Ptr &lhs, const Ptr &rhs):
|
||||
GettextPluralForm(minsize(lhs, rhs)),
|
||||
lhs(lhs), rhs(rhs) {}
|
||||
NumT operator()(const NumT n) const override
|
||||
{
|
||||
if (operator bool())
|
||||
return func((*lhs)(n), (*rhs)(n));
|
||||
return 0;
|
||||
}
|
||||
private:
|
||||
Ptr lhs, rhs;
|
||||
static constexpr F<NumT> func = {};
|
||||
};
|
||||
|
||||
class TernaryOperation: public GettextPluralForm
|
||||
{
|
||||
public:
|
||||
TernaryOperation(const Ptr &cond, const Ptr &val, const Ptr &alt):
|
||||
GettextPluralForm(std::min(minsize(cond), minsize(val, alt))),
|
||||
cond(cond), val(val), alt(alt) {}
|
||||
NumT operator()(const NumT n) const override
|
||||
{
|
||||
if (operator bool())
|
||||
return (*cond)(n) ? (*val)(n) : (*alt)(n);
|
||||
return 0;
|
||||
}
|
||||
private:
|
||||
Ptr cond, val, alt;
|
||||
};
|
||||
|
||||
typedef std::pair<GettextPluralForm::Ptr, std::wstring_view> ParserResult;
|
||||
typedef ParserResult (*Parser)(const size_t, std::wstring_view);
|
||||
|
||||
static ParserResult parse_expr(const size_t nplurals, std::wstring_view str);
|
||||
static ParserResult parse_expr(std::wstring_view str);
|
||||
|
||||
template<Parser Parser, template<typename> typename Operator>
|
||||
static ParserResult reduce_ltr(const size_t nplurals, const ParserResult &res, const wchar_t* pattern)
|
||||
static ParserResult reduce_ltr_single(const ParserResult &res, const std::wstring &pattern)
|
||||
{
|
||||
if (!str_starts_with(res.second, pattern))
|
||||
return ParserResult(nullptr, res.second);
|
||||
auto next = Parser(nplurals, trim(res.second.substr(std::char_traits<wchar_t>::length(pattern))));
|
||||
auto next = Parser(trim(res.second.substr(pattern.size())));
|
||||
if (!next.first)
|
||||
return next;
|
||||
next.first = GettextPluralForm::Ptr(new BinaryOperation<Operator>(res.first, next.first));
|
||||
next.first = wrap_op<Operator>(res.first, next.first);
|
||||
next.second = trim(next.second);
|
||||
return next;
|
||||
}
|
||||
|
||||
template<Parser Parser>
|
||||
static ParserResult reduce_ltr(const size_t nplurals, const ParserResult &res, const wchar_t**)
|
||||
static ParserResult reduce_ltr(const ParserResult &res)
|
||||
{
|
||||
return ParserResult(nullptr, res.second);
|
||||
}
|
||||
|
||||
template<Parser Parser, template<typename> typename Operator, template<typename> typename... Operators>
|
||||
static ParserResult reduce_ltr(const size_t nplurals, const ParserResult &res, const wchar_t** patterns)
|
||||
static ParserResult reduce_ltr(const ParserResult &res, const std::wstring &pattern, const typename std::conditional<1,std::wstring,Operators<GettextPluralForm::NumT>>::type&... patterns)
|
||||
{
|
||||
auto next = reduce_ltr<Parser, Operator>(nplurals, res, patterns[0]);
|
||||
auto next = reduce_ltr_single<Parser, Operator>(res, pattern);
|
||||
if (next.first || next.second != res.second)
|
||||
return next;
|
||||
return reduce_ltr<Parser, Operators...>(nplurals, res, patterns+1);
|
||||
return reduce_ltr<Parser, Operators...>(res, patterns...);
|
||||
}
|
||||
|
||||
template<Parser Parser, template<typename> typename Operator, template<typename> typename... Operators>
|
||||
static ParserResult parse_ltr(const size_t nplurals, std::wstring_view str, const wchar_t** patterns)
|
||||
template<Parser Parser, template<typename> typename... Operators>
|
||||
static ParserResult parse_ltr(std::wstring_view str, const typename std::conditional<1,std::wstring,Operators<GettextPluralForm::NumT>>::type&... patterns)
|
||||
{
|
||||
auto &&pres = Parser(nplurals, str);
|
||||
auto &&pres = Parser(str);
|
||||
if (!pres.first)
|
||||
return pres;
|
||||
pres.second = trim(pres.second);
|
||||
while (!pres.second.empty()) {
|
||||
auto next = reduce_ltr<Parser, Operator, Operators...>(nplurals, pres, patterns);
|
||||
auto next = reduce_ltr<Parser, Operators...>(pres, patterns...);
|
||||
if (!next.first)
|
||||
return pres;
|
||||
next.second = trim(next.second);
|
||||
|
@ -139,25 +80,26 @@ static ParserResult parse_ltr(const size_t nplurals, std::wstring_view str, cons
|
|||
return pres;
|
||||
}
|
||||
|
||||
static ParserResult parse_atomic(const size_t nplurals, std::wstring_view str)
|
||||
static ParserResult parse_atomic(std::wstring_view str)
|
||||
{
|
||||
if (str.empty())
|
||||
return ParserResult(nullptr, str);
|
||||
if (str[0] == 'n')
|
||||
return ParserResult(new Identity(nplurals), trim(str.substr(1)));
|
||||
return ParserResult(identity, trim(str.substr(1)));
|
||||
|
||||
wchar_t* endp;
|
||||
auto val = wcstoul(str.data(), &endp, 10);
|
||||
return ParserResult(new ConstValue(nplurals, val), trim(str.substr(endp-str.data())));
|
||||
return ParserResult([val](GettextPluralForm::NumT _) -> GettextPluralForm::NumT { return val; },
|
||||
trim(str.substr(endp-str.data())));
|
||||
}
|
||||
|
||||
static ParserResult parse_parenthesized(const size_t nplurals, std::wstring_view str)
|
||||
static ParserResult parse_parenthesized(std::wstring_view str)
|
||||
{
|
||||
if (str.empty())
|
||||
return ParserResult(nullptr, str);
|
||||
if (str[0] != '(')
|
||||
return parse_atomic(nplurals, str);
|
||||
auto result = parse_expr(nplurals, str.substr(1));
|
||||
return parse_atomic(str);
|
||||
auto result = parse_expr(str.substr(1));
|
||||
if (result.first) {
|
||||
if (result.second.empty() || result.second[0] != ')')
|
||||
result.first = nullptr;
|
||||
|
@ -167,90 +109,101 @@ static ParserResult parse_parenthesized(const size_t nplurals, std::wstring_view
|
|||
return result;
|
||||
}
|
||||
|
||||
static ParserResult parse_negation(const size_t nplurals, std::wstring_view str)
|
||||
static ParserResult parse_negation(std::wstring_view str)
|
||||
{
|
||||
if (str.empty())
|
||||
return ParserResult(nullptr, str);
|
||||
if (str[0] != '!')
|
||||
return parse_parenthesized(nplurals, str);
|
||||
auto result = parse_negation(nplurals, trim(str.substr(1)));
|
||||
return parse_parenthesized(str);
|
||||
auto result = parse_negation(trim(str.substr(1)));
|
||||
if (result.first)
|
||||
result.first = GettextPluralForm::Ptr(new UnaryOperation<std::logical_not>(result.first));
|
||||
result.first = wrap_op<std::logical_not>(result.first);
|
||||
return result;
|
||||
}
|
||||
|
||||
static ParserResult parse_multiplicative(const size_t nplurals, std::wstring_view str)
|
||||
template<typename T> struct safe_divides {
|
||||
T operator()(T lhs, T rhs) const
|
||||
{
|
||||
return rhs == 0 ? 0 : (lhs / rhs);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> struct safe_modulus {
|
||||
T operator()(T lhs, T rhs) const
|
||||
{
|
||||
return rhs == 0 ? 0 : (lhs % rhs);
|
||||
}
|
||||
};
|
||||
|
||||
static ParserResult parse_multiplicative(std::wstring_view str)
|
||||
{
|
||||
static const wchar_t *patterns[] = { L"*", L"/", L"%" };
|
||||
return parse_ltr<parse_negation, std::multiplies, std::divides, std::modulus>(nplurals, str, patterns);
|
||||
return parse_ltr<parse_negation, std::multiplies, safe_divides, safe_modulus>(str, L"*", L"/", L"%");
|
||||
}
|
||||
|
||||
static ParserResult parse_additive(const size_t nplurals, std::wstring_view str)
|
||||
static ParserResult parse_additive(std::wstring_view str)
|
||||
{
|
||||
static const wchar_t *patterns[] = { L"+", L"-" };
|
||||
return parse_ltr<parse_multiplicative, std::plus, std::minus>(nplurals, str, patterns);
|
||||
return parse_ltr<parse_multiplicative, std::plus, std::minus>(str, L"+", L"-");
|
||||
}
|
||||
|
||||
static ParserResult parse_comparison(const size_t nplurals, std::wstring_view str)
|
||||
static ParserResult parse_comparison(std::wstring_view str)
|
||||
{
|
||||
static const wchar_t *patterns[] = { L"<=", L">=", L"<", L">" };
|
||||
return parse_ltr<parse_additive, std::less_equal, std::greater_equal, std::less, std::greater>(nplurals, str, patterns);
|
||||
return parse_ltr<parse_additive, std::less_equal, std::greater_equal, std::less, std::greater>(str, L"<=", L">=", L"<", L">");
|
||||
}
|
||||
|
||||
static ParserResult parse_equality(const size_t nplurals, std::wstring_view str)
|
||||
static ParserResult parse_equality(std::wstring_view str)
|
||||
{
|
||||
static const wchar_t *patterns[] = { L"==", L"!=" };
|
||||
return parse_ltr<parse_comparison, std::equal_to, std::not_equal_to>(nplurals, str, patterns);
|
||||
return parse_ltr<parse_comparison, std::equal_to, std::not_equal_to>(str, L"==", L"!=");
|
||||
}
|
||||
|
||||
static ParserResult parse_conjunction(const size_t nplurals, std::wstring_view str)
|
||||
static ParserResult parse_conjunction(std::wstring_view str)
|
||||
{
|
||||
static const wchar_t *and_pattern[] = { L"&&" };
|
||||
return parse_ltr<parse_equality, std::logical_and>(nplurals, str, and_pattern);
|
||||
return parse_ltr<parse_equality, std::logical_and>(str, L"&&");
|
||||
}
|
||||
|
||||
static ParserResult parse_disjunction(const size_t nplurals, std::wstring_view str)
|
||||
static ParserResult parse_disjunction(std::wstring_view str)
|
||||
{
|
||||
static const wchar_t *or_pattern[] = { L"||" };
|
||||
return parse_ltr<parse_conjunction, std::logical_or>(nplurals, str, or_pattern);
|
||||
return parse_ltr<parse_conjunction, std::logical_or>(str, L"||");
|
||||
}
|
||||
|
||||
static ParserResult parse_ternary(const size_t nplurals, std::wstring_view str)
|
||||
static ParserResult parse_ternary(std::wstring_view str)
|
||||
{
|
||||
auto pres = parse_disjunction(nplurals, str);
|
||||
auto pres = parse_disjunction(str);
|
||||
if (pres.second.empty() || pres.second[0] != '?') // no ? :
|
||||
return pres;
|
||||
auto cond = pres.first;
|
||||
pres = parse_ternary(nplurals, trim(pres.second.substr(1)));
|
||||
pres = parse_ternary(trim(pres.second.substr(1)));
|
||||
if (pres.second.empty() || pres.second[0] != ':')
|
||||
return ParserResult(nullptr, pres.second);
|
||||
auto val = pres.first;
|
||||
pres = parse_ternary(nplurals, trim(pres.second.substr(1)));
|
||||
return ParserResult(new TernaryOperation(cond, val, pres.first), pres.second);
|
||||
pres = parse_ternary(trim(pres.second.substr(1)));
|
||||
return ParserResult(std::bind(ternary_op, std::placeholders::_1,
|
||||
std::move(cond), std::move(val), std::move(pres.first)), pres.second);
|
||||
}
|
||||
|
||||
static ParserResult parse_expr(const size_t nplurals, std::wstring_view str)
|
||||
static ParserResult parse_expr(std::wstring_view str)
|
||||
{
|
||||
return parse_ternary(nplurals, trim(str));
|
||||
return parse_ternary(trim(str));
|
||||
}
|
||||
|
||||
GettextPluralForm::Ptr GettextPluralForm::parse(const size_t nplurals, std::wstring_view str)
|
||||
static GettextPluralForm::Function parse(std::wstring_view str)
|
||||
{
|
||||
if (nplurals == 0)
|
||||
return nullptr;
|
||||
auto result = parse_expr(nplurals, str);
|
||||
auto result = parse_expr(str);
|
||||
if (!result.second.empty())
|
||||
return nullptr;
|
||||
return result.first;
|
||||
}
|
||||
|
||||
GettextPluralForm::Ptr GettextPluralForm::parseHeaderLine(std::wstring_view str)
|
||||
GettextPluralForm::GettextPluralForm(std::wstring_view str)
|
||||
{
|
||||
if (!str_starts_with(str, L"Plural-Forms: nplurals=") || !str_ends_with(str, L";"))
|
||||
return nullptr;
|
||||
auto nplurals = wcstoul(str.data()+23, nullptr, 10);
|
||||
return;
|
||||
auto size = wcstoul(str.data()+23, nullptr, 10);
|
||||
auto pos = str.find(L"plural=");
|
||||
if (pos == str.npos)
|
||||
return nullptr;
|
||||
return parse(nplurals, str.substr(pos+7, str.size()-pos-8));
|
||||
return;
|
||||
auto result = parse(str.substr(pos+7, str.size()-pos-8));
|
||||
if (size > 0 && result) {
|
||||
nplurals = size;
|
||||
func = result;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue