+// Plural forms parser
+// ----------------------------------------------------------------------------
+
+/*
+ Simplified Grammar
+
+Expression:
+ LogicalOrExpression '?' Expression ':' Expression
+ LogicalOrExpression
+
+LogicalOrExpression:
+ LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c
+ LogicalAndExpression
+
+LogicalAndExpression:
+ EqualityExpression "&&" LogicalAndExpression // to (a && b) && c
+ EqualityExpression
+
+EqualityExpression:
+ RelationalExpression "==" RelationalExperession
+ RelationalExpression "!=" RelationalExperession
+ RelationalExpression
+
+RelationalExpression:
+ MultiplicativeExpression '>' MultiplicativeExpression
+ MultiplicativeExpression '<' MultiplicativeExpression
+ MultiplicativeExpression ">=" MultiplicativeExpression
+ MultiplicativeExpression "<=" MultiplicativeExpression
+ MultiplicativeExpression
+
+MultiplicativeExpression:
+ PmExpression '%' PmExpression
+ PmExpression
+
+PmExpression:
+ N
+ Number
+ '(' Expression ')'
+*/
+
+class wxPluralFormsToken
+{
+public:
+ enum Type
+ {
+ T_ERROR, T_EOF, T_NUMBER, T_N, T_PLURAL, T_NPLURALS, T_EQUAL, T_ASSIGN,
+ T_GREATER, T_GREATER_OR_EQUAL, T_LESS, T_LESS_OR_EQUAL,
+ T_REMINDER, T_NOT_EQUAL,
+ T_LOGICAL_AND, T_LOGICAL_OR, T_QUESTION, T_COLON, T_SEMICOLON,
+ T_LEFT_BRACKET, T_RIGHT_BRACKET
+ };
+ Type type() const { return m_type; }
+ void setType(Type type) { m_type = type; }
+ // for T_NUMBER only
+ typedef int Number;
+ Number number() const { return m_number; }
+ void setNumber(Number num) { m_number = num; }
+private:
+ Type m_type;
+ Number m_number;
+};
+
+
+class wxPluralFormsScanner
+{
+public:
+ wxPluralFormsScanner(const char* s);
+ const wxPluralFormsToken& token() const { return m_token; }
+ bool nextToken(); // returns false if error
+private:
+ const char* m_s;
+ wxPluralFormsToken m_token;
+};
+
+wxPluralFormsScanner::wxPluralFormsScanner(const char* s) : m_s(s)
+{
+ nextToken();
+}
+
+bool wxPluralFormsScanner::nextToken()
+{
+ wxPluralFormsToken::Type type = wxPluralFormsToken::T_ERROR;
+ while (isspace(*m_s))
+ {
+ ++m_s;
+ }
+ if (*m_s == 0)
+ {
+ type = wxPluralFormsToken::T_EOF;
+ }
+ else if (isdigit(*m_s))
+ {
+ wxPluralFormsToken::Number number = *m_s++ - '0';
+ while (isdigit(*m_s))
+ {
+ number = number * 10 + (*m_s++ - '0');
+ }
+ m_token.setNumber(number);
+ type = wxPluralFormsToken::T_NUMBER;
+ }
+ else if (isalpha(*m_s))
+ {
+ const char* begin = m_s++;
+ while (isalnum(*m_s))
+ {
+ ++m_s;
+ }
+ size_t size = m_s - begin;
+ if (size == 1 && memcmp(begin, "n", size) == 0)
+ {
+ type = wxPluralFormsToken::T_N;
+ }
+ else if (size == 6 && memcmp(begin, "plural", size) == 0)
+ {
+ type = wxPluralFormsToken::T_PLURAL;
+ }
+ else if (size == 8 && memcmp(begin, "nplurals", size) == 0)
+ {
+ type = wxPluralFormsToken::T_NPLURALS;
+ }
+ }
+ else if (*m_s == '=')
+ {
+ ++m_s;
+ if (*m_s == '=')
+ {
+ ++m_s;
+ type = wxPluralFormsToken::T_EQUAL;
+ }
+ else
+ {
+ type = wxPluralFormsToken::T_ASSIGN;
+ }
+ }
+ else if (*m_s == '>')
+ {
+ ++m_s;
+ if (*m_s == '=')
+ {
+ ++m_s;
+ type = wxPluralFormsToken::T_GREATER_OR_EQUAL;
+ }
+ else
+ {
+ type = wxPluralFormsToken::T_GREATER;
+ }
+ }
+ else if (*m_s == '<')
+ {
+ ++m_s;
+ if (*m_s == '=')
+ {
+ ++m_s;
+ type = wxPluralFormsToken::T_LESS_OR_EQUAL;
+ }
+ else
+ {
+ type = wxPluralFormsToken::T_LESS;
+ }
+ }
+ else if (*m_s == '%')
+ {
+ ++m_s;
+ type = wxPluralFormsToken::T_REMINDER;
+ }
+ else if (*m_s == '!' && m_s[1] == '=')
+ {
+ m_s += 2;
+ type = wxPluralFormsToken::T_NOT_EQUAL;
+ }
+ else if (*m_s == '&' && m_s[1] == '&')
+ {
+ m_s += 2;
+ type = wxPluralFormsToken::T_LOGICAL_AND;
+ }
+ else if (*m_s == '|' && m_s[1] == '|')
+ {
+ m_s += 2;
+ type = wxPluralFormsToken::T_LOGICAL_OR;
+ }
+ else if (*m_s == '?')
+ {
+ ++m_s;
+ type = wxPluralFormsToken::T_QUESTION;
+ }
+ else if (*m_s == ':')
+ {
+ ++m_s;
+ type = wxPluralFormsToken::T_COLON;
+ } else if (*m_s == ';') {
+ ++m_s;
+ type = wxPluralFormsToken::T_SEMICOLON;
+ }
+ else if (*m_s == '(')
+ {
+ ++m_s;
+ type = wxPluralFormsToken::T_LEFT_BRACKET;
+ }
+ else if (*m_s == ')')
+ {
+ ++m_s;
+ type = wxPluralFormsToken::T_RIGHT_BRACKET;
+ }
+ m_token.setType(type);
+ return type != wxPluralFormsToken::T_ERROR;
+}
+
+class wxPluralFormsNode;
+
+// NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not
+// fully defined yet:
+class wxPluralFormsNodePtr
+{
+public:
+ wxPluralFormsNodePtr(wxPluralFormsNode *p = NULL) : m_p(p) {}
+ ~wxPluralFormsNodePtr();
+ wxPluralFormsNode& operator*() const { return *m_p; }
+ wxPluralFormsNode* operator->() const { return m_p; }
+ wxPluralFormsNode* get() const { return m_p; }
+ wxPluralFormsNode* release();
+ void reset(wxPluralFormsNode *p);
+
+private:
+ wxPluralFormsNode *m_p;
+};
+
+class wxPluralFormsNode
+{
+public:
+ wxPluralFormsNode(const wxPluralFormsToken& token) : m_token(token) {}
+ const wxPluralFormsToken& token() const { return m_token; }
+ const wxPluralFormsNode* node(size_t i) const
+ { return m_nodes[i].get(); }
+ void setNode(size_t i, wxPluralFormsNode* n);
+ wxPluralFormsNode* releaseNode(size_t i);
+ wxPluralFormsToken::Number evaluate(wxPluralFormsToken::Number n) const;
+
+private:
+ wxPluralFormsToken m_token;
+ wxPluralFormsNodePtr m_nodes[3];
+};
+
+wxPluralFormsNodePtr::~wxPluralFormsNodePtr()
+{
+ delete m_p;
+}
+wxPluralFormsNode* wxPluralFormsNodePtr::release()
+{
+ wxPluralFormsNode *p = m_p;
+ m_p = NULL;
+ return p;
+}
+void wxPluralFormsNodePtr::reset(wxPluralFormsNode *p)
+{
+ if (p != m_p)
+ {
+ delete m_p;
+ m_p = p;
+ }
+}
+
+
+void wxPluralFormsNode::setNode(size_t i, wxPluralFormsNode* n)
+{
+ m_nodes[i].reset(n);
+}
+
+wxPluralFormsNode* wxPluralFormsNode::releaseNode(size_t i)
+{
+ return m_nodes[i].release();
+}
+
+wxPluralFormsToken::Number
+wxPluralFormsNode::evaluate(wxPluralFormsToken::Number n) const
+{
+ switch (token().type())
+ {
+ // leaf
+ case wxPluralFormsToken::T_NUMBER:
+ return token().number();
+ case wxPluralFormsToken::T_N:
+ return n;
+ // 2 args
+ case wxPluralFormsToken::T_EQUAL:
+ return node(0)->evaluate(n) == node(1)->evaluate(n);
+ case wxPluralFormsToken::T_NOT_EQUAL:
+ return node(0)->evaluate(n) != node(1)->evaluate(n);
+ case wxPluralFormsToken::T_GREATER:
+ return node(0)->evaluate(n) > node(1)->evaluate(n);
+ case wxPluralFormsToken::T_GREATER_OR_EQUAL:
+ return node(0)->evaluate(n) >= node(1)->evaluate(n);
+ case wxPluralFormsToken::T_LESS:
+ return node(0)->evaluate(n) < node(1)->evaluate(n);
+ case wxPluralFormsToken::T_LESS_OR_EQUAL:
+ return node(0)->evaluate(n) <= node(1)->evaluate(n);
+ case wxPluralFormsToken::T_REMINDER:
+ {
+ wxPluralFormsToken::Number number = node(1)->evaluate(n);
+ if (number != 0)
+ {
+ return node(0)->evaluate(n) % number;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ case wxPluralFormsToken::T_LOGICAL_AND:
+ return node(0)->evaluate(n) && node(1)->evaluate(n);
+ case wxPluralFormsToken::T_LOGICAL_OR:
+ return node(0)->evaluate(n) || node(1)->evaluate(n);
+ // 3 args
+ case wxPluralFormsToken::T_QUESTION:
+ return node(0)->evaluate(n)
+ ? node(1)->evaluate(n)
+ : node(2)->evaluate(n);
+ default:
+ return 0;
+ }
+}
+
+
+class wxPluralFormsCalculator
+{
+public:
+ wxPluralFormsCalculator() : m_nplurals(0), m_plural(0) {}
+
+ // input: number, returns msgstr index
+ int evaluate(int n) const;
+
+ // input: text after "Plural-Forms:" (e.g. "nplurals=2; plural=(n != 1);"),
+ // if s == 0, creates default handler
+ // returns 0 if error
+ static wxPluralFormsCalculator* make(const char* s = 0);
+
+ ~wxPluralFormsCalculator() {}
+
+ void init(wxPluralFormsToken::Number nplurals, wxPluralFormsNode* plural);
+ wxString getString() const;
+
+private:
+ wxPluralFormsToken::Number m_nplurals;
+ wxPluralFormsNodePtr m_plural;
+};
+
+wxDEFINE_SCOPED_PTR_TYPE(wxPluralFormsCalculator);
+
+void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals,
+ wxPluralFormsNode* plural)
+{
+ m_nplurals = nplurals;
+ m_plural.reset(plural);
+}
+
+int wxPluralFormsCalculator::evaluate(int n) const
+{
+ if (m_plural.get() == 0)
+ {
+ return 0;
+ }
+ wxPluralFormsToken::Number number = m_plural->evaluate(n);
+ if (number < 0 || number > m_nplurals)
+ {
+ return 0;
+ }
+ return number;
+}
+
+
+class wxPluralFormsParser
+{
+public:
+ wxPluralFormsParser(wxPluralFormsScanner& scanner) : m_scanner(scanner) {}
+ bool parse(wxPluralFormsCalculator& rCalculator);
+
+private:
+ wxPluralFormsNode* parsePlural();
+ // stops at T_SEMICOLON, returns 0 if error
+ wxPluralFormsScanner& m_scanner;
+ const wxPluralFormsToken& token() const;
+ bool nextToken();
+
+ wxPluralFormsNode* expression();
+ wxPluralFormsNode* logicalOrExpression();
+ wxPluralFormsNode* logicalAndExpression();
+ wxPluralFormsNode* equalityExpression();
+ wxPluralFormsNode* multiplicativeExpression();
+ wxPluralFormsNode* relationalExpression();
+ wxPluralFormsNode* pmExpression();
+};
+
+bool wxPluralFormsParser::parse(wxPluralFormsCalculator& rCalculator)
+{
+ if (token().type() != wxPluralFormsToken::T_NPLURALS)
+ return false;
+ if (!nextToken())
+ return false;
+ if (token().type() != wxPluralFormsToken::T_ASSIGN)
+ return false;
+ if (!nextToken())
+ return false;
+ if (token().type() != wxPluralFormsToken::T_NUMBER)
+ return false;
+ wxPluralFormsToken::Number nplurals = token().number();
+ if (!nextToken())
+ return false;
+ if (token().type() != wxPluralFormsToken::T_SEMICOLON)
+ return false;
+ if (!nextToken())
+ return false;
+ if (token().type() != wxPluralFormsToken::T_PLURAL)
+ return false;
+ if (!nextToken())
+ return false;
+ if (token().type() != wxPluralFormsToken::T_ASSIGN)
+ return false;
+ if (!nextToken())
+ return false;
+ wxPluralFormsNode* plural = parsePlural();
+ if (plural == 0)
+ return false;
+ if (token().type() != wxPluralFormsToken::T_SEMICOLON)
+ return false;
+ if (!nextToken())
+ return false;
+ if (token().type() != wxPluralFormsToken::T_EOF)
+ return false;
+ rCalculator.init(nplurals, plural);
+ return true;
+}
+
+wxPluralFormsNode* wxPluralFormsParser::parsePlural()
+{
+ wxPluralFormsNode* p = expression();
+ if (p == NULL)
+ {
+ return NULL;
+ }
+ wxPluralFormsNodePtr n(p);
+ if (token().type() != wxPluralFormsToken::T_SEMICOLON)
+ {
+ return NULL;
+ }
+ return n.release();
+}
+
+const wxPluralFormsToken& wxPluralFormsParser::token() const
+{
+ return m_scanner.token();
+}
+
+bool wxPluralFormsParser::nextToken()
+{
+ if (!m_scanner.nextToken())
+ return false;
+ return true;
+}
+
+wxPluralFormsNode* wxPluralFormsParser::expression()
+{
+ wxPluralFormsNode* p = logicalOrExpression();
+ if (p == NULL)
+ return NULL;
+ wxPluralFormsNodePtr n(p);
+ if (token().type() == wxPluralFormsToken::T_QUESTION)
+ {
+ wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
+ if (!nextToken())
+ {
+ return 0;
+ }
+ p = expression();
+ if (p == 0)
+ {
+ return 0;
+ }
+ qn->setNode(1, p);
+ if (token().type() != wxPluralFormsToken::T_COLON)
+ {
+ return 0;
+ }
+ if (!nextToken())
+ {
+ return 0;
+ }
+ p = expression();
+ if (p == 0)
+ {
+ return 0;
+ }
+ qn->setNode(2, p);
+ qn->setNode(0, n.release());
+ return qn.release();
+ }
+ return n.release();
+}
+
+wxPluralFormsNode*wxPluralFormsParser::logicalOrExpression()
+{
+ wxPluralFormsNode* p = logicalAndExpression();
+ if (p == NULL)
+ return NULL;
+ wxPluralFormsNodePtr ln(p);
+ if (token().type() == wxPluralFormsToken::T_LOGICAL_OR)
+ {
+ wxPluralFormsNodePtr un(new wxPluralFormsNode(token()));
+ if (!nextToken())
+ {
+ return 0;
+ }
+ p = logicalOrExpression();
+ if (p == 0)
+ {
+ return 0;
+ }
+ wxPluralFormsNodePtr rn(p); // right
+ if (rn->token().type() == wxPluralFormsToken::T_LOGICAL_OR)
+ {
+ // see logicalAndExpression comment
+ un->setNode(0, ln.release());
+ un->setNode(1, rn->releaseNode(0));
+ rn->setNode(0, un.release());
+ return rn.release();
+ }
+
+
+ un->setNode(0, ln.release());
+ un->setNode(1, rn.release());
+ return un.release();
+ }
+ return ln.release();
+}
+
+wxPluralFormsNode* wxPluralFormsParser::logicalAndExpression()
+{
+ wxPluralFormsNode* p = equalityExpression();
+ if (p == NULL)
+ return NULL;
+ wxPluralFormsNodePtr ln(p); // left
+ if (token().type() == wxPluralFormsToken::T_LOGICAL_AND)
+ {
+ wxPluralFormsNodePtr un(new wxPluralFormsNode(token())); // up
+ if (!nextToken())
+ {
+ return NULL;
+ }
+ p = logicalAndExpression();
+ if (p == 0)
+ {
+ return NULL;
+ }
+ wxPluralFormsNodePtr rn(p); // right
+ if (rn->token().type() == wxPluralFormsToken::T_LOGICAL_AND)
+ {
+// transform 1 && (2 && 3) -> (1 && 2) && 3
+// u r
+// l r -> u 3
+// 2 3 l 2
+ un->setNode(0, ln.release());
+ un->setNode(1, rn->releaseNode(0));
+ rn->setNode(0, un.release());
+ return rn.release();
+ }
+
+ un->setNode(0, ln.release());
+ un->setNode(1, rn.release());
+ return un.release();
+ }
+ return ln.release();
+}
+
+wxPluralFormsNode* wxPluralFormsParser::equalityExpression()
+{
+ wxPluralFormsNode* p = relationalExpression();
+ if (p == NULL)
+ return NULL;
+ wxPluralFormsNodePtr n(p);
+ if (token().type() == wxPluralFormsToken::T_EQUAL
+ || token().type() == wxPluralFormsToken::T_NOT_EQUAL)
+ {
+ wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
+ if (!nextToken())
+ {
+ return NULL;
+ }
+ p = relationalExpression();
+ if (p == NULL)
+ {
+ return NULL;
+ }
+ qn->setNode(1, p);
+ qn->setNode(0, n.release());
+ return qn.release();
+ }
+ return n.release();
+}
+
+wxPluralFormsNode* wxPluralFormsParser::relationalExpression()
+{
+ wxPluralFormsNode* p = multiplicativeExpression();
+ if (p == NULL)
+ return NULL;
+ wxPluralFormsNodePtr n(p);
+ if (token().type() == wxPluralFormsToken::T_GREATER
+ || token().type() == wxPluralFormsToken::T_LESS
+ || token().type() == wxPluralFormsToken::T_GREATER_OR_EQUAL
+ || token().type() == wxPluralFormsToken::T_LESS_OR_EQUAL)
+ {
+ wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
+ if (!nextToken())
+ {
+ return NULL;
+ }
+ p = multiplicativeExpression();
+ if (p == NULL)
+ {
+ return NULL;
+ }
+ qn->setNode(1, p);
+ qn->setNode(0, n.release());
+ return qn.release();
+ }
+ return n.release();
+}
+
+wxPluralFormsNode* wxPluralFormsParser::multiplicativeExpression()
+{
+ wxPluralFormsNode* p = pmExpression();
+ if (p == NULL)
+ return NULL;
+ wxPluralFormsNodePtr n(p);
+ if (token().type() == wxPluralFormsToken::T_REMINDER)
+ {
+ wxPluralFormsNodePtr qn(new wxPluralFormsNode(token()));
+ if (!nextToken())
+ {
+ return NULL;
+ }
+ p = pmExpression();
+ if (p == NULL)
+ {
+ return NULL;
+ }
+ qn->setNode(1, p);
+ qn->setNode(0, n.release());
+ return qn.release();
+ }
+ return n.release();
+}
+
+wxPluralFormsNode* wxPluralFormsParser::pmExpression()
+{
+ wxPluralFormsNodePtr n;
+ if (token().type() == wxPluralFormsToken::T_N
+ || token().type() == wxPluralFormsToken::T_NUMBER)
+ {
+ n.reset(new wxPluralFormsNode(token()));
+ if (!nextToken())
+ {
+ return NULL;
+ }
+ }
+ else if (token().type() == wxPluralFormsToken::T_LEFT_BRACKET) {
+ if (!nextToken())
+ {
+ return NULL;
+ }
+ wxPluralFormsNode* p = expression();
+ if (p == NULL)
+ {
+ return NULL;
+ }
+ n.reset(p);
+ if (token().type() != wxPluralFormsToken::T_RIGHT_BRACKET)
+ {
+ return NULL;
+ }
+ if (!nextToken())
+ {
+ return NULL;
+ }
+ }
+ else
+ {
+ return NULL;
+ }
+ return n.release();
+}
+
+wxPluralFormsCalculator* wxPluralFormsCalculator::make(const char* s)
+{
+ wxPluralFormsCalculatorPtr calculator(new wxPluralFormsCalculator);
+ if (s != NULL)
+ {
+ wxPluralFormsScanner scanner(s);
+ wxPluralFormsParser p(scanner);
+ if (!p.parse(*calculator))
+ {
+ return NULL;
+ }
+ }
+ return calculator.release();
+}
+
+
+
+
+// ----------------------------------------------------------------------------
+// wxMsgCatalogFile corresponds to one disk-file message catalog.
+//
+// This is a "low-level" class and is used only by wxMsgCatalog
+// ----------------------------------------------------------------------------
+
+WX_DECLARE_EXPORTED_STRING_HASH_MAP(wxString, wxMessagesHash);
+
+class wxMsgCatalogFile
+{
+public:
+ // ctor & dtor
+ wxMsgCatalogFile();
+ ~wxMsgCatalogFile();
+
+ // load the catalog from disk (szDirPrefix corresponds to language)
+ bool Load(const wxChar *szDirPrefix, const wxChar *szName,
+ wxPluralFormsCalculatorPtr& rPluralFormsCalculator);
+
+ // fills the hash with string-translation pairs
+ void FillHash(wxMessagesHash& hash, const wxString& msgIdCharset,
+ bool convertEncoding) const;
+
+private:
+ // this implementation is binary compatible with GNU gettext() version 0.10
+
+ // an entry in the string table
+ struct wxMsgTableEntry
+ {
+ size_t32 nLen; // length of the string
+ size_t32 ofsString; // pointer to the string
+ };
+
+ // header of a .mo file
+ struct wxMsgCatalogHeader
+ {
+ size_t32 magic, // offset +00: magic id
+ revision, // +04: revision
+ numStrings; // +08: number of strings in the file
+ size_t32 ofsOrigTable, // +0C: start of original string table
+ ofsTransTable; // +10: start of translated string table
+ size_t32 nHashSize, // +14: hash table size
+ ofsHashTable; // +18: offset of hash table start
+ };
+
+ // all data is stored here, NULL if no data loaded
+ size_t8 *m_pData;
+
+ // amount of memory pointed to by m_pData.
+ size_t32 m_nSize;
+
+ // data description
+ size_t32 m_numStrings; // number of strings in this domain
+ wxMsgTableEntry *m_pOrigTable, // pointer to original strings
+ *m_pTransTable; // translated
+
+ wxString m_charset;
+
+ // swap the 2 halves of 32 bit integer if needed
+ size_t32 Swap(size_t32 ui) const
+ {
+ return m_bSwapped ? (ui << 24) | ((ui & 0xff00) << 8) |
+ ((ui >> 8) & 0xff00) | (ui >> 24)
+ : ui;
+ }
+
+ const char *StringAtOfs(wxMsgTableEntry *pTable, size_t32 n) const
+ {
+ const wxMsgTableEntry * const ent = pTable + n;
+
+ // this check could fail for a corrupt message catalog
+ size_t32 ofsString = Swap(ent->ofsString);
+ if ( ofsString + Swap(ent->nLen) > m_nSize)
+ {
+ return NULL;
+ }
+
+ return (const char *)(m_pData + ofsString);
+ }
+
+ bool m_bSwapped; // wrong endianness?
+
+ DECLARE_NO_COPY_CLASS(wxMsgCatalogFile)
+};
+
+
+// ----------------------------------------------------------------------------
+// wxMsgCatalog corresponds to one loaded message catalog.