// ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef PARSE_PLURAL_H_INCLUDED #define PARSE_PLURAL_H_INCLUDED #include #include #include #include namespace parse_plural { //expression interface struct Expression { virtual ~Expression() {} }; template struct Expr : public Expression { typedef T ValueType; virtual ValueType eval() const = 0; }; class ParsingError {}; class PluralForm { public: PluralForm(const std::string& stream); //throw ParsingError int getForm(std::int64_t n) const { n_ = std::abs(n) ; return static_cast(expr->eval()); } private: std::shared_ptr> expr; mutable std::int64_t n_; }; //validate plural form class InvalidPluralForm {}; class PluralFormInfo { public: PluralFormInfo(const std::string& definition, int pluralCount); //throw InvalidPluralForm int getCount() const { return static_cast(forms.size()); } bool isSingleNumberForm(int index) const { return 0 <= index && index < static_cast(forms.size()) ? forms[index].count == 1 : false; } int getFirstNumber (int index) const { return 0 <= index && index < static_cast(forms.size()) ? forms[index].firstNumber : -1; } private: struct FormInfo { FormInfo() : count(0), firstNumber(0) {} int count; int firstNumber; //which maps to the plural form index position }; std::vector forms; }; //--------------------------- implementation --------------------------- //http://www.gnu.org/software/hello/manual/gettext/Plural-forms.html //http://translate.sourceforge.net/wiki/l10n/pluralforms /* Grammar for Plural forms parser ------------------------------- expression: conditional-expression conditional-expression: logical-or-expression logical-or-expression ? expression : expression logical-or-expression: logical-and-expression logical-or-expression || logical-and-expression logical-and-expression: equality-expression logical-and-expression && equality-expression equality-expression: relational-expression relational-expression == relational-expression relational-expression != relational-expression relational-expression: multiplicative-expression multiplicative-expression > multiplicative-expression multiplicative-expression < multiplicative-expression multiplicative-expression >= multiplicative-expression multiplicative-expression <= multiplicative-expression multiplicative-expression: pm-expression multiplicative-expression % pm-expression pm-expression: variable-number-n-expression constant-number-expression ( expression ) .po format,e.g.: (n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2) */ namespace implementation { //specific binary expression based on STL function objects template struct BinaryExp : public Expr { typedef std::shared_ptr> ExpLhs; typedef std::shared_ptr> ExpRhs; BinaryExp(const ExpLhs& lhs, const ExpRhs& rhs, StlOp biop) : lhs_(lhs), rhs_(rhs), biop_(biop) { assert(lhs && rhs); } virtual typename StlOp::result_type eval() const { return biop_(lhs_->eval(), rhs_->eval()); } private: ExpLhs lhs_; ExpRhs rhs_; StlOp biop_; }; template inline std::shared_ptr> makeBiExp(const std::shared_ptr& lhs, const std::shared_ptr& rhs, StlOp biop) //throw ParsingError { auto exLeft = std::dynamic_pointer_cast>(lhs); auto exRight = std::dynamic_pointer_cast>(rhs); if (!exLeft || !exRight) throw ParsingError(); return std::make_shared>(exLeft, exRight, biop); } template struct ConditionalExp : public Expr { ConditionalExp(const std::shared_ptr>& ifExp, const std::shared_ptr>& thenExp, const std::shared_ptr>& elseExp) : ifExp_(ifExp), thenExp_(thenExp), elseExp_(elseExp) { assert(ifExp && thenExp && elseExp); } virtual typename Expr::ValueType eval() const { return ifExp_->eval() ? thenExp_->eval() : elseExp_->eval(); } private: std::shared_ptr> ifExp_; std::shared_ptr> thenExp_; std::shared_ptr> elseExp_; }; struct ConstNumberExp : public Expr { ConstNumberExp(std::int64_t n) : n_(n) {} virtual std::int64_t eval() const { return n_; } private: std::int64_t n_; }; struct VariableNumberNExp : public Expr { VariableNumberNExp(std::int64_t& n) : n_(n) {} virtual std::int64_t eval() const { return n_; } private: std::int64_t& n_; }; //------------------------------------------------------------------------------- struct Token { enum Type { TK_TERNARY_QUEST, TK_TERNARY_COLON, TK_OR, TK_AND, TK_EQUAL, TK_NOT_EQUAL, TK_LESS, TK_LESS_EQUAL, TK_GREATER, TK_GREATER_EQUAL, TK_MODULUS, TK_VARIABLE_N, TK_CONST_NUMBER, TK_BRACKET_LEFT, TK_BRACKET_RIGHT, TK_END }; Token(Type t) : type(t), number(0) {} Token(std::int64_t num) : type(TK_CONST_NUMBER), number(num) {} Type type; std::int64_t number; //if type == TK_CONST_NUMBER }; class Scanner { public: Scanner(const std::string& stream) : stream_(stream), pos(stream_.begin()) { tokens.push_back(std::make_pair("?" , Token::TK_TERNARY_QUEST)); tokens.push_back(std::make_pair(":" , Token::TK_TERNARY_COLON)); tokens.push_back(std::make_pair("||", Token::TK_OR )); tokens.push_back(std::make_pair("&&", Token::TK_AND )); tokens.push_back(std::make_pair("==", Token::TK_EQUAL )); tokens.push_back(std::make_pair("!=", Token::TK_NOT_EQUAL )); tokens.push_back(std::make_pair("<=", Token::TK_LESS_EQUAL )); tokens.push_back(std::make_pair("<" , Token::TK_LESS )); tokens.push_back(std::make_pair(">=", Token::TK_GREATER_EQUAL)); tokens.push_back(std::make_pair(">" , Token::TK_GREATER )); tokens.push_back(std::make_pair("%" , Token::TK_MODULUS )); tokens.push_back(std::make_pair("n" , Token::TK_VARIABLE_N )); tokens.push_back(std::make_pair("N" , Token::TK_VARIABLE_N )); tokens.push_back(std::make_pair("(" , Token::TK_BRACKET_LEFT )); tokens.push_back(std::make_pair(")" , Token::TK_BRACKET_RIGHT)); } Token nextToken() { //skip whitespace pos = std::find_if(pos, stream_.end(), [](char c) { return !zen::isWhiteSpace(c); }); if (pos == stream_.end()) return Token::TK_END; for (auto iter = tokens.begin(); iter != tokens.end(); ++iter) if (startsWith(iter->first)) { pos += iter->first.size(); return Token(iter->second); } auto digitEnd = std::find_if(pos, stream_.end(), [](char c) { return !zen::isDigit(c); }); if (digitEnd != pos) { auto number = zen::stringTo(std::string(pos, digitEnd)); pos = digitEnd; return number; } throw ParsingError(); //unknown token } private: bool startsWith(const std::string& prefix) const { if (stream_.end() - pos < static_cast(prefix.size())) return false; return std::equal(prefix.begin(), prefix.end(), pos); } typedef std::vector> TokenList; TokenList tokens; const std::string stream_; std::string::const_iterator pos; }; //------------------------------------------------------------------------------- class Parser { public: Parser(const std::string& stream, std::int64_t& n) : scn(stream), tk(scn.nextToken()), n_(n) {} std::shared_ptr> parse() //throw ParsingError; return value always bound! { auto e = std::dynamic_pointer_cast>(parseExpression()); //throw ParsingError if (!e) throw ParsingError(); expectToken(Token::TK_END); return e; } private: std::shared_ptr parseExpression() { return parseConditional(); }//throw ParsingError std::shared_ptr parseConditional() //throw ParsingError { std::shared_ptr e = parseLogicalOr(); if (token().type == Token::TK_TERNARY_QUEST) { nextToken(); auto ifExp = std::dynamic_pointer_cast>(e); auto thenExp = std::dynamic_pointer_cast>(parseExpression()); //associativity: <- expectToken(Token::TK_TERNARY_COLON); nextToken(); auto elseExp = std::dynamic_pointer_cast>(parseExpression()); // if (!ifExp || !thenExp || !elseExp) throw ParsingError(); return std::make_shared>(ifExp, thenExp, elseExp); } return e; } std::shared_ptr parseLogicalOr() { std::shared_ptr e = parseLogicalAnd(); while (token().type == Token::TK_OR) //associativity: -> { nextToken(); std::shared_ptr rhs = parseLogicalAnd(); e = makeBiExp(e, rhs, std::logical_or()); //throw ParsingError } return e; } std::shared_ptr parseLogicalAnd() { std::shared_ptr e = parseEquality(); while (token().type == Token::TK_AND) //associativity: -> { nextToken(); std::shared_ptr rhs = parseEquality(); e = makeBiExp(e, rhs, std::logical_and()); //throw ParsingError } return e; } std::shared_ptr parseEquality() { std::shared_ptr e = parseRelational(); Token::Type t = token().type; if (t == Token::TK_EQUAL || //associativity: n/a t == Token::TK_NOT_EQUAL) { nextToken(); std::shared_ptr rhs = parseRelational(); if (t == Token::TK_EQUAL) return makeBiExp(e, rhs, std::equal_to ()); //throw ParsingError if (t == Token::TK_NOT_EQUAL) return makeBiExp(e, rhs, std::not_equal_to()); // } return e; } std::shared_ptr parseRelational() { std::shared_ptr e = parseMultiplicative(); Token::Type t = token().type; if (t == Token::TK_LESS || //associativity: n/a t == Token::TK_LESS_EQUAL || t == Token::TK_GREATER || t == Token::TK_GREATER_EQUAL) { nextToken(); std::shared_ptr rhs = parseMultiplicative(); if (t == Token::TK_LESS) return makeBiExp(e, rhs, std::less ()); // if (t == Token::TK_LESS_EQUAL) return makeBiExp(e, rhs, std::less_equal ()); //throw ParsingError if (t == Token::TK_GREATER) return makeBiExp(e, rhs, std::greater ()); // if (t == Token::TK_GREATER_EQUAL) return makeBiExp(e, rhs, std::greater_equal()); // } return e; } std::shared_ptr parseMultiplicative() { std::shared_ptr e = parsePrimary(); while (token().type == Token::TK_MODULUS) //associativity: -> { nextToken(); std::shared_ptr rhs = parsePrimary(); //"compile-time" check: n % 0 if (auto literal = std::dynamic_pointer_cast(rhs)) if (literal->eval() == 0) throw ParsingError(); e = makeBiExp(e, rhs, std::modulus()); //throw ParsingError } return e; } std::shared_ptr parsePrimary() { if (token().type == Token::TK_VARIABLE_N) { nextToken(); return std::make_shared(n_); } else if (token().type == Token::TK_CONST_NUMBER) { const std::int64_t number = token().number; nextToken(); return std::make_shared(number); } else if (token().type == Token::TK_BRACKET_LEFT) { nextToken(); std::shared_ptr e = parseExpression(); expectToken(Token::TK_BRACKET_RIGHT); nextToken(); return e; } else throw ParsingError(); } void nextToken() { tk = scn.nextToken(); } const Token& token() const { return tk; } void expectToken(Token::Type t) //throw ParsingError { if (token().type != t) throw ParsingError(); } Scanner scn; Token tk; std::int64_t& n_; }; } inline PluralFormInfo::PluralFormInfo(const std::string& definition, int pluralCount) //throw InvalidPluralForm { if (pluralCount < 1) throw InvalidPluralForm(); forms.resize(pluralCount); try { parse_plural::PluralForm pf(definition); //throw parse_plural::ParsingError //PERF_START //perf: 80ns per iteration max (for arabic) //=> 1000 iterations should be fast enough and still detect all "single number forms" for (int j = 0; j < 1000; ++j) { int form = pf.getForm(j); if (0 <= form && form < static_cast(forms.size())) { if (forms[form].count == 0) forms[form].firstNumber = j; ++forms[form].count; } else throw InvalidPluralForm(); } } catch (const parse_plural::ParsingError&) { throw InvalidPluralForm(); } //ensure each form is used at least once: if (!std::all_of(forms.begin(), forms.end(), [](const FormInfo& fi) { return fi.count >= 1; })) throw InvalidPluralForm(); } inline PluralForm::PluralForm(const std::string& stream) : expr(implementation::Parser(stream, n_).parse()) {} //throw ParsingError } #endif // PARSE_PLURAL_H_INCLUDED