Skip to content

Commit

Permalink
complement test
Browse files Browse the repository at this point in the history
  • Loading branch information
y5nw committed Jul 12, 2024
1 parent 04d804e commit b5b0cef
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 72 deletions.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ msgid_plural "Plural form"
msgstr[0] "Singular result"
msgstr[1] "Plural result"

msgid "Not enough value"
msgid_plural "Not enough values"
msgstr[0] "Result"

msgid "Partial translation"
msgid_plural "Partial translations"
msgstr[0] "Partially translated"
msgstr[1] ""

msgctxt "context"
msgid "With context"
msgstr "Has context"
26 changes: 26 additions & 0 deletions games/devtest/mods/testtranslations/translation_mo.de.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
msgid ""
msgstr "Plural-Forms: nplurals=2; plural= n != 1;"

msgctxt "context"
msgid "With context"
msgstr "Has context"

msgctxt "context"
msgid "Singular form"
msgid_plural "Plural form"
msgstr[0] "Singular result"
msgstr[1] "Plural result"

# Replace plural form delimiter in the msgstr
msgid "Corrupt singular"
msgid_plural "Corrupt plural"
msgstr[0] "Corrupt singular result"
msgstr[1] "Corrupt plural result"

# Replace terminating NUL in the MO file
msgid "Corrupt entry"
msgstr "Corrupted result"

# Change the address of this entry to something invalid
msgid "Removed entry"
msgstr "Removed result"
125 changes: 72 additions & 53 deletions src/gettext_plural_form.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,28 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "gettext_plural_form.h"
#include "util/string.h"

// TODO: merge this with UnaryOperation using stsd::identity (C++20)?
static size_t minsize(const GettextPluralForm::Ptr &form) {
return form ? form->size() : 0;
}

static size_t minsize(const GettextPluralForm::Ptr &f, const GettextPluralForm::Ptr &g) {
if (sizeof(g) > 0)
return std::min(minsize(f), minsize(g));
return f ? f->size() : 0;
}

class Identity: public GettextPluralForm {
NumT operator()(const NumT n) const override {
return n;
}
public:
Identity(size_t nplurals): GettextPluralForm(nplurals) {};
NumT operator()(const NumT n) const override {
return n;
}
};

class ConstValue: public GettextPluralForm {
public:
ConstValue(NumT val): value(val) {};
NumT operator()(const NumT n) const override{
ConstValue(size_t nplurals, NumT val): GettextPluralForm(nplurals), value(val) {};
NumT operator()(const NumT n) const override {
return value;
}
private:
Expand All @@ -40,9 +51,10 @@ class ConstValue: public GettextPluralForm {
template<template<typename> typename F>
class UnaryOperation: public GettextPluralForm {
public:
UnaryOperation(const Ptr &op): op(op) {}
UnaryOperation(const Ptr &op):
GettextPluralForm(minsize(op)), op(op) {}
NumT operator()(const NumT n) const override {
if (op)
if (operator bool())
return func((*op)(n));
return 0;
}
Expand All @@ -54,9 +66,11 @@ class UnaryOperation: public GettextPluralForm {
template<template<typename> typename F>
class BinaryOperation: public GettextPluralForm {
public:
BinaryOperation(const Ptr &lhs, const Ptr &rhs): lhs(lhs), rhs(rhs) {}
BinaryOperation(const Ptr &lhs, const Ptr &rhs):
GettextPluralForm(minsize(lhs, rhs)),
lhs(lhs), rhs(rhs) {}
NumT operator()(const NumT n) const override {
if (lhs && rhs)
if (operator bool())
return func((*lhs)(n), (*rhs)(n));
return 0;
}
Expand All @@ -67,9 +81,11 @@ class BinaryOperation: public GettextPluralForm {

class TernaryOperation: public GettextPluralForm {
public:
TernaryOperation(const Ptr &cond, const Ptr &val, const Ptr &alt): cond(cond), val(val), alt(alt) {}
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 (cond && val && alt)
if (operator bool())
return (*cond)(n) ? (*val)(n) : (*alt)(n);
return 0;
}
Expand All @@ -78,16 +94,16 @@ class TernaryOperation: public GettextPluralForm {
};

typedef std::pair<GettextPluralForm::Ptr, std::wstring_view> ParserResult;
typedef ParserResult (*Parser)(const std::wstring_view &);
typedef ParserResult (*Parser)(const size_t, const std::wstring_view &);

static ParserResult parse_expr(const std::wstring_view &str);
static ParserResult parse_expr(const size_t nplurals, const std::wstring_view &str);

template<Parser Parser, template<typename> typename Operator>
static ParserResult reduce_ltr(const ParserResult &res, const wchar_t* pattern)
static ParserResult reduce_ltr(const size_t nplurals, const ParserResult &res, const wchar_t* pattern)
{
if (!str_starts_with(res.second, pattern))
return ParserResult(nullptr, res.second);
auto next = Parser(res.second.substr(std::char_traits<wchar_t>::length(pattern)));
auto next = Parser(nplurals, res.second.substr(std::char_traits<wchar_t>::length(pattern)));
if (!next.first)
return next;
next.first = GettextPluralForm::Ptr(new BinaryOperation<Operator>(res.first, next.first));
Expand All @@ -96,28 +112,28 @@ static ParserResult reduce_ltr(const ParserResult &res, const wchar_t* pattern)
}

template<Parser Parser>
static ParserResult reduce_ltr(const ParserResult &res, const wchar_t**)
static ParserResult reduce_ltr(const size_t nplurals, const ParserResult &res, const wchar_t**)
{
return ParserResult(nullptr, res.second);
}

template<Parser Parser, template<typename> typename Operator, template<typename> typename... Operators>
static ParserResult reduce_ltr(const ParserResult &res, const wchar_t** patterns) {
auto next = reduce_ltr<Parser, Operator>(res, patterns[0]);
static ParserResult reduce_ltr(const size_t nplurals, const ParserResult &res, const wchar_t** patterns) {
auto next = reduce_ltr<Parser, Operator>(nplurals, res, patterns[0]);
if (next.first || next.second != res.second)
return next;
return reduce_ltr<Parser, Operators...>(res, patterns+1);
return reduce_ltr<Parser, Operators...>(nplurals, res, patterns+1);
}

template<Parser Parser, template<typename> typename Operator, template<typename> typename... Operators>
static ParserResult parse_ltr(const std::wstring_view &str, const wchar_t** patterns) {
auto pres = Parser(str);
static ParserResult parse_ltr(const size_t nplurals, const std::wstring_view &str, const wchar_t** patterns) {
auto &&pres = Parser(nplurals, str);
if (!pres.first)
return pres;
pres.second = trim(pres.second);
while (!pres.second.empty())
{
auto next = reduce_ltr<Parser, Operator, Operators...>(pres, patterns);
auto next = reduce_ltr<Parser, Operator, Operators...>(nplurals, pres, patterns);
if (!next.first)
return pres;
next.second = trim(next.second);
Expand All @@ -126,25 +142,25 @@ static ParserResult parse_ltr(const std::wstring_view &str, const wchar_t** patt
return pres;
}

static ParserResult parse_atomic(const std::wstring_view &str)
static ParserResult parse_atomic(const size_t nplurals, const std::wstring_view &str)
{
if (str.empty())
return ParserResult(nullptr, str);
if (str[0] == 'n')
return ParserResult(new Identity(), trim(str.substr(1)));
return ParserResult(new Identity(nplurals), trim(str.substr(1)));

wchar_t* endp;
auto val = wcstoul(str.data(), &endp, 10);
return ParserResult(new ConstValue(val), trim(str.substr(endp-str.data())));
return ParserResult(new ConstValue(nplurals, val), trim(str.substr(endp-str.data())));
}

static ParserResult parse_parenthesized(const std::wstring_view &str)
static ParserResult parse_parenthesized(const size_t nplurals, const std::wstring_view &str)
{
if (str.empty())
return ParserResult(nullptr, str);
if (str[0] != '(')
return parse_atomic(str);
auto result = parse_expr(str.substr(1));
return parse_atomic(nplurals, str);
auto result = parse_expr(nplurals, str.substr(1));
if (result.first) {
if (result.second.empty() || result.second[0] != ')')
result.first = nullptr;
Expand All @@ -154,75 +170,77 @@ static ParserResult parse_parenthesized(const std::wstring_view &str)
return result;
}

static ParserResult parse_negation(const std::wstring_view &str)
static ParserResult parse_negation(const size_t nplurals, const std::wstring_view &str)
{
if (str.empty())
return ParserResult(nullptr, str);
if (str[0] != '!')
return parse_parenthesized(str);
auto result = parse_negation(trim(str.substr(1)));
return parse_parenthesized(nplurals, str);
auto result = parse_negation(nplurals, trim(str.substr(1)));
if (result.first)
result.first = GettextPluralForm::Ptr(new UnaryOperation<std::logical_not>(result.first));
return result;
}

static ParserResult parse_multiplicative(const std::wstring_view &str)
static ParserResult parse_multiplicative(const size_t nplurals, const std::wstring_view &str)
{
static const wchar_t *patterns[] = { L"*", L"/", L"%" };
return parse_ltr<parse_negation, std::multiplies, std::divides, std::modulus>(str, patterns);
return parse_ltr<parse_negation, std::multiplies, std::divides, std::modulus>(nplurals, str, patterns);
}

static ParserResult parse_additive(const std::wstring_view &str)
static ParserResult parse_additive(const size_t nplurals, const std::wstring_view &str)
{
static const wchar_t *patterns[] = { L"+", L"-" };
return parse_ltr<parse_multiplicative, std::plus, std::minus>(str, patterns);
return parse_ltr<parse_multiplicative, std::plus, std::minus>(nplurals, str, patterns);
}

static ParserResult parse_comparison(const std::wstring_view &str)
static ParserResult parse_comparison(const size_t nplurals, const 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>(str, patterns);
return parse_ltr<parse_additive, std::less_equal, std::greater_equal, std::less, std::greater>(nplurals, str, patterns);
}

static ParserResult parse_equality(const std::wstring_view &str)
static ParserResult parse_equality(const size_t nplurals, const std::wstring_view &str)
{
static const wchar_t *patterns[] = { L"==", L"!=" };
return parse_ltr<parse_comparison, std::equal_to, std::not_equal_to>(str, patterns);
return parse_ltr<parse_comparison, std::equal_to, std::not_equal_to>(nplurals, str, patterns);
}

static ParserResult parse_conjunction(const std::wstring_view &str)
static ParserResult parse_conjunction(const size_t nplurals, const std::wstring_view &str)
{
static const wchar_t *and_pattern[] = { L"&&" };
return parse_ltr<parse_equality, std::logical_and>(str, and_pattern);
return parse_ltr<parse_equality, std::logical_and>(nplurals, str, and_pattern);
}

static ParserResult parse_disjunction(const std::wstring_view &str)
static ParserResult parse_disjunction(const size_t nplurals, const std::wstring_view &str)
{
static const wchar_t *or_pattern[] = { L"||" };
return parse_ltr<parse_conjunction, std::logical_or>(str, or_pattern);
return parse_ltr<parse_conjunction, std::logical_or>(nplurals, str, or_pattern);
}

static ParserResult parse_ternary(const std::wstring_view &str)
static ParserResult parse_ternary(const size_t nplurals, const std::wstring_view &str)
{
auto pres = parse_disjunction(str);
auto pres = parse_disjunction(nplurals, str);
if (pres.second.empty() || pres.second[0] != '?') // no ? :
return pres;
auto cond = pres.first;
pres = parse_ternary(trim(pres.second.substr(1)));
pres = parse_ternary(nplurals, trim(pres.second.substr(1)));
if (pres.second.empty() || pres.second[0] != ':')
return ParserResult(nullptr, pres.second);
auto val = pres.first;
pres = parse_ternary(trim(pres.second.substr(1)));
pres = parse_ternary(nplurals, trim(pres.second.substr(1)));
return ParserResult(new TernaryOperation(cond, val, pres.first), pres.second);
}

static ParserResult parse_expr(const std::wstring_view &str)
static ParserResult parse_expr(const size_t nplurals, const std::wstring_view &str)
{
return parse_ternary(trim(str));
return parse_ternary(nplurals, trim(str));
}

GettextPluralForm::Ptr GettextPluralForm::parse(const std::wstring_view &str) {
auto result = parse_expr(str);
GettextPluralForm::Ptr GettextPluralForm::parse(const size_t nplurals, const std::wstring_view &str) {
if (nplurals == 0)
return nullptr;
auto result = parse_expr(nplurals, str);
if (!result.second.empty())
return nullptr;
return result.first;
Expand All @@ -231,8 +249,9 @@ GettextPluralForm::Ptr GettextPluralForm::parse(const std::wstring_view &str) {
GettextPluralForm::Ptr GettextPluralForm::parseHeaderLine(const 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);
auto pos = str.find(L"plural=");
if (pos == str.npos)
return nullptr;
return parse(str.substr(pos+7, str.size()-pos-8));
return parse(nplurals, str.substr(pos+7, str.size()-pos-8));
}
9 changes: 7 additions & 2 deletions src/gettext_plural_form.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,15 @@ class GettextPluralForm {
public:
using NumT = unsigned long;
using Ptr = std::shared_ptr<GettextPluralForm>;
size_t size() const { return nplurals; };
virtual NumT operator()(const NumT) const = 0;
virtual operator bool() const { return true; }
virtual operator bool() const { return size() > 0; }
virtual ~GettextPluralForm() {};

static GettextPluralForm::Ptr parse(const std::wstring_view &str);
static GettextPluralForm::Ptr parse(const size_t nplurals, const std::wstring_view &str);
static GettextPluralForm::Ptr parseHeaderLine(const std::wstring_view &str);
protected:
GettextPluralForm(size_t nplurals): nplurals(nplurals) {};
private:
const size_t nplurals;
};
Loading

0 comments on commit b5b0cef

Please sign in to comment.