text: #include <iostream>
  #include <string>
  #include <vector>
  #include <pybind11/operators.h>
  #include <pybind11/pybind11.h>
  #include <pybind11/stl.h>
  #include <xmorphy/graphem/Token.h>
  #include <xmorphy/graphem/Tokenizer.h>
  #include <xmorphy/ml/TFDisambiguator.h>
  #include <xmorphy/ml/TFMorphemicSplitter.h>
  #include <xmorphy/ml/SingleWordDisambiguate.h>
  #include <xmorphy/morph/Processor.h>
  #include <xmorphy/morph/WordFormPrinter.h>
  #include <xmorphy/tag/GraphemTag.h>
  #include <xmorphy/tag/MorphTag.h>
  #include <xmorphy/tag/PhemTag.h>
  #include <xmorphy/tag/TokenTypeTag.h>
  #include <xmorphy/tag/UniMorphTag.h>
  #include <xmorphy/tag/UniSPTag.h>
  #include <xmorphy/tag/AnalyzerTag.h>
  
  namespace py = pybind11;
  using namespace std;
  
  X::UniMorphTag tagFromString(const std::string & s)
  {
      X::UniMorphTag result;
      from_string(s, result);
      return result;
  }
  
  struct MorphInfo
  {
  public:
      std::string normal_form;
      X::UniMorphTag tag = X::UniMorphTag::UNKN;
      X::UniSPTag sp = X::UniSPTag::X;
      double probability;
      X::AnalyzerTag analyzer;
  public:
  
      const std::string & getNormalFrom() const
      {
          return normal_form;
      }
  
      void setNormalForm(const std::string & normal_form_)
      {
          normal_form = normal_form_;
      }
  
      const X::UniMorphTag & getTag() const
      {
          return tag;
      }
  
      void setTag(X::UniMorphTag tag_)
      {
          tag = tag_;
      }
  
      const X::UniSPTag & getSP() const
      {
          return sp;
      }
  
      void setSP(X::UniSPTag sp_) {
          sp = sp_;
      }
  
      double getProbability() const
      {
          return probability;
      }
  
      void setProbability(double probability_)
      {
          probability = probability_;
      }
  
      const X::AnalyzerTag & getAnalyzerTag() const
      {
          return analyzer;
      }
  
      void setAnalyzerTag(X::AnalyzerTag analyzer_)
      {
          analyzer = analyzer_;
      }
  
      bool operator==(const MorphInfo & o) const
      {
          return std::tie(sp, tag, analyzer, probability, normal_form) ==
              std::tie(o.sp, o.tag, o.analyzer, o.probability, o.normal_form);
      }
  
      bool operator!=(const MorphInfo & o) const
      {
          return !(*this == o);
      }
  
      bool operator<(const MorphInfo & o) const
      {
          return std::tie(sp, tag, analyzer, probability, normal_form) <
                 std::tie(o.sp, o.tag, o.analyzer, o.probability, o.normal_form);
      }
  
      bool operator>(const MorphInfo& o) const
      {
          return std::tie(sp, tag, analyzer, probability, normal_form) >
                 std::tie(o.sp, o.tag, o.analyzer, o.probability, o.normal_form);
      }
  };
  
  struct WordForm
  {
  public:
      std::string word_form;
      std::vector<MorphInfo> infos;
      X::TokenTypeTag token_type = X::TokenTypeTag::UNKN;
      X::GraphemTag graphem_info = X::GraphemTag::UNKN;
      std::vector<X::PhemTag> phem_info;
  public:
  
      bool operator==(const WordForm & o) const
      {
          if (o.infos.size() != infos.size())
              return false;
          for (size_t i = 0; i < o.infos.size(); ++i)
          {
              if (o.infos[i] != infos[i])
                  return false;
          }
          return std::tie(word_form, token_type, graphem_info) == std::tie(o.word_form, o.token_type, o.graphem_info);
      }
  
      bool operator!=(const WordForm & o) const
      {
          return !(*this == o);
      }
  
      bool operator<(const WordForm& o) const
      {
          if (word_form >= o.word_form)
              return false;
          if (infos.size() >= o.infos.size())
              return false;
          if (!(token_type < o.token_type))
              return false;
          return graphem_info < o.graphem_info;
      }
  
      bool operator>(const WordForm& o) const
      {
          if (word_form > o.word_form)
              return true;
          if (infos.size() > o.infos.size())
              return true;
  
          for (size_t i = 0; i < infos.size(); ++i) {
              if (infos[i] < o.infos[i])
                  return true;
              else if (infos[i] > o.infos[i])
                  return false;
          }
  
          return token_type > o.token_type;
      }
  
      const std::string & getWordFrom() const
      {
          return word_form;
      }
  
      void setWordForm(const std::string & word_form_)
      {
          word_form = word_form_;
      }
  
      const std::vector<MorphInfo> & getInfos() const
      {
          return infos;
      }
  
      void setInfos(const std::vector<MorphInfo> & infos_)
      {
          infos = infos_;
      }
  
      const X::TokenTypeTag & getTokenType() const
      {
          return token_type;
      }
  
      void setTokenType(X::TokenTypeTag token_type_)
      {
          token_type = token_type_;
      }
  
      const X::GraphemTag& getGraphemTag() const
      {
          return graphem_info;
      }
  
      void setGraphemTag(X::GraphemTag graphem_info_)
      {
          graphem_info = graphem_info_;
      }
  
      const std::vector<X::PhemTag> getPhemInfo() const
      {
          return phem_info;
      }
  
      void setPhemInfo(const std::vector<X::PhemTag> & phem_info_)
      {
          phem_info = phem_info_;
      }
  
      std::string toString() const
      {
          if (token_type & X::TokenTypeTag::SEPR)
              return "";
  
          std::ostringstream os;
  
          for (const auto& mi : infos)
          {
              os << word_form << "\t";
              os << mi.normal_form << "\t";
              os << mi.sp << "\t";
              os << mi.tag << "\t";
              os << mi.analyzer;
              os << "\n";
          }
          std::string result = os.str();
          result.pop_back();
          return result;
      }
  };
  
  class MorphAnalyzer
  {
  private:
      std::optional<X::Tokenizer> tok;
  
      std::optional<X::Processor> analyzer;
      std::optional<X::SingleWordDisambiguate> disamb;
      std::optional<X::TFDisambiguator> context_disamb;
      std::optional<X::TFMorphemicSplitter> splitter;
  
  public:
      MorphAnalyzer()
      {
          tok.emplace();
          analyzer.emplace();
          disamb.emplace();
          context_disamb.emplace();
          splitter.emplace();
      }
  
      bool isWordContainsInDictionary(const std::string& str)
      {
          return analyzer->isWordContainsInDictionary(X::UniString(str));
      }
  
      std::vector<std::string> getNonDictionaryWords(const std::string & str)
      {
          std::vector<std::string> result;
          std::vector<X::TokenPtr> tokens = tok->analyze(X::UniString(str));
          auto filtered_tokens = analyzer->getNonDictionaryWords(tokens);
          for (const auto & token : filtered_tokens)
              result.push_back(token->getInner().getRawString());
          return result;
      }
  
      std::vector<WordForm> analyze(const std::string& str, bool disambiguate_single = false, bool disambiguate_context = false, bool morphemic_split = false)
      {
          std::vector<X::TokenPtr> tokens = tok->analyze(X::UniString(str));
          std::vector<X::WordFormPtr> forms = analyzer->analyze(tokens);
          //std::cerr << "STR:" << str << std::endl;
          if (disambiguate_single)
              disamb->disambiguate(forms);
  
          if (disambiguate_context)
              context_disamb->disambiguate(forms);
  
          std::vector<WordForm> result;
          for (auto wf_ptr : forms)
          {
              if (morphemic_split)
                  splitter->split(wf_ptr);
  
              std::vector<MorphInfo> infos;
              for (const auto & info : wf_ptr->getMorphInfo())
              {
                  MorphInfo new_info{
                      .normal_form = info.normalForm.toLowerCase().getRawString(),
                      .tag = info.tag,
                      .sp = info.sp,
                      .probability = info.probability,
                      .analyzer = info.at,
                  };
                  infos.push_back(new_info);
              }
              WordForm new_word_form{
                  .word_form = wf_ptr->getWordForm().getRawString(),
                  .infos = std::move(infos),
                  .token_type = wf_ptr->getTokenType(),
                  .graphem_info = wf_ptr->getGraphemTag(),
                  .phem_info = wf_ptr->getPhemInfo(),
              };
              result.emplace_back(new_word_form);
          }
          return result;
      }
  
      WordForm analyzeSingleWord(const std::string & str, bool disambiguate = false, bool morphemic_split = false)
      {
          X::TokenPtr token = tok->analyzeSingleWord(X::UniString(str));
          X::WordFormPtr form = analyzer->analyzeSingleToken(token);
  
          X::Sentence sentence;
          sentence.push_back(form);
          if (disambiguate)
              disamb->disambiguateSingleForm(form);
  
          if (morphemic_split)
              splitter->split(form);
  
          std::vector<MorphInfo> infos;
          for (const auto& info : form->getMorphInfo())
          {
              MorphInfo new_info{
                  .normal_form = info.normalForm.toLowerCase().getRawString(),
                  .tag = info.tag,
                  .sp = info.sp,
                  .probability = info.probability,
                  .analyzer = info.at,
              };
              infos.push_back(new_info);
          }
          WordForm new_word_form{
              .word_form = form->getWordForm().getRawString(),
              .infos = std::move(infos),
              .token_type = form->getTokenType(),
              .graphem_info = form->getGraphemTag(),
              .phem_info = form->getPhemInfo(),
          };
          return new_word_form;
      }
  
      std::string morphemicSplit(const std::string & word, X::UniSPTag & speech_part, X::UniMorphTag & tag)
      {
          X::UniString uword(word);
          auto upper_case_word = uword.toUpperCase();
          auto phem_info = splitter->split(upper_case_word, speech_part, tag);
          return {X::WordFormPrinter::writePhemInfo(uword.toLowerCase(), phem_info)};
      }
  
      std::vector<std::string> generateLexeme(const std::string & lemma, X::UniSPTag & speech_part, bool single_only, bool mult_only, bool only_dict, bool only_short)
      {
          auto parsed_forms = analyzer->generate(X::UniString(lemma));
          std::vector<std::string> result;
          std::unordered_set<X::UniString> filter;
          bool ins_found = false;
          bool acc_found = false;
          bool nom_found = false;
          for (const auto & form : parsed_forms)
          {
              if (form->sp != speech_part)
                  continue;
  
              if (only_dict && form->at == X::AnalyzerTag::SUFF)
                  return {};
  
              if (single_only && form->mt & X::UniMorphTag::Plur)
                  continue;
  
              if (mult_only && form->mt & X::UniMorphTag::Sing)
                  continue;
  
              if (form->sp == X::UniSPTag::NOUN)
              {
                  if (form->mt & X::UniMorphTag::Act || form->mt & X::UniMorphTag::Part)
                      continue;
  
                  if (form->wordform != X::UniString("ПОЛУ") && form->wordform.startsWith(X::UniString("ПОЛУ")) && !form->normalform.startsWith(X::UniString("ПОЛУ")))
                      continue;
  
                  if (form->wordform.startsWith(X::UniString("ВПОЛУ")) && !form->normalform.startsWith(X::UniString("ВПОЛУ")))
                      continue;
  
                  if (form->wordform.startsWith(X::UniString("МАДМУА")) && !form->normalform.startsWith(X::UniString("МАДМУА")))
                      continue;
  
                  if (form->wordform.startsWith(X::UniString("ПОД")) && !form->normalform.startsWith(X::UniString("ПОД")))
                      continue;
  
                  if (form->wordform.startsWith(X::UniString("ТЫЩ")) && !form->normalform.startsWith(X::UniString("ТЫЩ")))
                      continue;
  
                  if (form->wordform.startsWith(X::UniString("БОЖ")) && form->normalform.startsWith(X::UniString("БОГ")))
                      continue;
  
                  if (form->wordform == X::UniString("ОБР"))
                      continue;
  
                  if (form->normalform == X::UniString("СТВОЛИНА") && form->mt.getGender() == X::UniMorphTag::Masc)
                      continue;
  
                  if (form->normalform == X::UniString("ГОРБИНА") && form->mt.getGender() == X::UniMorphTag::Masc)
                      continue;
  
                  if (form->normalform == X::UniString("ОСТРОВИНА") && form->mt.getGender() == X::UniMorphTag::Masc)
                      continue;
  
                  if (form->normalform == X::UniString("ДОЖДИНА") && form->mt.getGender() == X::UniMorphTag::Masc)
                      continue;
  
                  if (form->normalform == X::UniString("ДОМИНА") && form->mt.getGender() == X::UniMorphTag::Masc)
                      continue;
  
                  if (form->normalform == X::UniString("ХОЛОДИНА") && form->mt.getGender() == X::UniMorphTag::Masc)
                      continue;
  
                  if (form->normalform == X::UniString("ГОЛОСИНА") && form->mt.getGender() == X::UniMorphTag::Masc)
                      continue;
  
                  if (form->normalform == X::UniString("КИРПИЧИНА") && form->mt.getGender() == X::UniMorphTag::Masc)
                      continue;
  
                  if (form->normalform == X::UniString("РОСТОК") && form->wordform != form->normalform && form->wordform.startsWith(X::UniString("РОСТОК")))
                      continue;
  
                  if (form->wordform == X::UniString("Ш"))
                      continue;
  
                  if (form->wordform == X::UniString("МАК") && form->mt.getCase() != X::UniMorphTag::Nom)
                      continue;
  
                  if (form->wordform == X::UniString("БАК") && form->mt.getCase() != X::UniMorphTag::Nom)
                      continue;
  
                  if (form->wordform == X::UniString("КОН") && form->mt.getCase() != X::UniMorphTag::Nom)
                      continue;
  
                  if (form->wordform == X::UniString("РИС") && form->mt.getCase() != X::UniMorphTag::Nom)
                      continue;
  
                  if (form->normalform == X::UniString("БАКТЕРОИД") && (form->wordform.endsWith(X::UniString("ОД")) || form->wordform.endsWith(X::UniString("ОДЫ"))))
                      continue;
  
                  if (form->wordform == X::UniString("БУКОВ") && form->normalform == X::UniString("БУКВА"))
                      continue;
  
                  if (form->wordform.startsWith(X::UniString("ХОЗЯВ")) && form->normalform == X::UniString("ХОЗЯИН"))
                      continue;
  
                  if (form->wordform.contains(X::UniString("БЭНД")) && !form->normalform.contains(X::UniString("БЭНД")))
                      continue;
  
                  if (form->wordform.startsWith(X::UniString("ШАБР")) && form->normalform == X::UniString("ШАБЕР"))
                      continue;
  
                  if (form->wordform == X::UniString("ОГНЬ"))
                      continue;
  
                  if (form->wordform == X::UniString("ВЕТР"))
                      continue;
  
                  // ДЕРЕВАМИ
                  if (form->normalform.endsWith(X::UniString("ДЕРЕВО")) && form->mt & X::UniMorphTag::Plur)
                  {
                      if (!form->wordform.contains(u'Ь'))
                          continue;
                  }
  
                  if (form->wordform == X::UniString("МАРИН") && form->mt & X::UniMorphTag::Sing)
                      continue;
  
                  if ((parsed_forms[0]->mt & X::UniMorphTag::Masc) && form->mt & X::UniMorphTag::Fem)
                      continue;
  
                  if ((parsed_forms[0]->mt & X::UniMorphTag::Fem) && form->mt & X::UniMorphTag::Masc)
                      continue;
  
                  if (form->mt & X::UniMorphTag::Acc && form->mt & X::UniMorphTag::Plur)
                  {
                      if (acc_found)
                          continue;
                      acc_found = true;
                  }
  
                  if (form->mt & X::UniMorphTag::Nom && form->mt & X::UniMorphTag::Plur)
                  {
                      if (nom_found)
                          continue;
                      nom_found = true;
                  }
  
                  if (form->mt & X::UniMorphTag::Ins && form->mt & X::UniMorphTag::Fem)
                      if (form->wordform.endsWith(X::UniString("ИЕЮ")))
                          continue;
  
                  if (form->mt & X::UniMorphTag::Ins)
                  {
                      if (ins_found && (
                              form->wordform.endsWith(X::UniString("ЕЮ")) || form->wordform.endsWith(X::UniString("ОЮ"))
                              || form->wordform.endsWith(X::UniString("ОМ")) || form->wordform.endsWith(X::UniString("ОЙ"))
                          ))
                          continue;
                      ins_found = true;
                  }
              }
  
              if (form->sp == X::UniSPTag::ADJ)
              {
                  if (form->mt & X::UniMorphTag::Short && !only_short)
                      continue;
  
                  if (!(form->mt & X::UniMorphTag::Short) && only_short)
                      continue;
  
                  if (form->wordform.startsWith(X::UniString("НАИ")) && !form->normalform.startsWith(X::UniString("НАИ")))
                      continue;
  
                  if (form->wordform.startsWith(X::UniString("ПО")) && !form->normalform.startsWith(X::UniString("ПО")))
                      continue;
              }
  
              if (form->wordform.find('-') != std::string::npos && form->normalform.find('-') == std::string::npos)
                  continue;
  
              if (form->wordform.find('-') == std::string::npos && form->normalform.find('-') != std::string::npos)
                  continue;
  
              if (form->sp == X::UniSPTag::VERB && form->mt & X::UniMorphTag::Part)
                  continue;
  
              if (form->mt & X::UniMorphTag::Conv)
                  continue;
  
              result.emplace_back(form->wordform.getRawString());
          }
          return result;
      }
  };
  
  
  PYBIND11_MODULE(pyxmorphy, m) {
      py::class_<X::UniSPTag>(m, "UniSPTag")
          .def_readonly_static("X", &X::UniSPTag::X)
          .def_readonly_static("ADJ", &X::UniSPTag::ADJ)
          .def_readonly_static("ADV", &X::UniSPTag::ADV)
          .def_readonly_static("INTJ", &X::UniSPTag::INTJ)
          .def_readonly_static("NOUN", &X::UniSPTag::NOUN)
          .def_readonly_static("PROPN", &X::UniSPTag::PROPN)
          .def_readonly_static("VERB", &X::UniSPTag::VERB)
          .def_readonly_static("ADP", &X::UniSPTag::ADP)
          .def_readonly_static("AUX", &X::UniSPTag::AUX)
          .def_readonly_static("CONJ", &X::UniSPTag::CONJ)
          .def_readonly_static("SCONJ", &X::UniSPTag::SCONJ)
          .def_readonly_static("DET", &X::UniSPTag::DET)
          .def_readonly_static("NUM", &X::UniSPTag::NUM)
          .def_readonly_static("PART", &X::UniSPTag::PART)
          .def_readonly_static("PRON", &X::UniSPTag::PRON)
          .def_readonly_static("PUNCT", &X::UniSPTag::PUNCT)
          .def_readonly_static("H", &X::UniSPTag::H)
          .def_readonly_static("R", &X::UniSPTag::R)
          .def_readonly_static("Q", &X::UniSPTag::Q)
          .def_readonly_static("SYM", &X::UniSPTag::SYM)
          .def("__str__", &X::UniSPTag::toString)
          .def("__eq__", &X::UniSPTag::operator==)
          .def("__ne__", &X::UniSPTag::operator!=)
          .def("__lt__", &X::UniSPTag::operator<)
          .def("__gt__", &X::UniSPTag::operator>)
          .def(py::init<const std::string &>());
  
      py::class_<X::UniMorphTag>(m, "UniMorphTag")
          .def_readonly_static("UNKN", &X::UniMorphTag::UNKN)
          .def_readonly_static("Masc", &X::UniMorphTag::Masc)
          .def_readonly_static("Fem", &X::UniMorphTag::Fem)
          .def_readonly_static("Neut", &X::UniMorphTag::Neut)
          .def_readonly_static("Anim", &X::UniMorphTag::Anim)
          .def_readonly_static("Inan", &X::UniMorphTag::Inan)
          .def_readonly_static("Sing", &X::UniMorphTag::Sing)
          .def_readonly_static("Plur", &X::UniMorphTag::Plur)
          .def_readonly_static("Ins", &X::UniMorphTag::Ins)
          .def_readonly_static("Acc", &X::UniMorphTag::Acc)
          .def_readonly_static("Nom", &X::UniMorphTag::Nom)
          .def_readonly_static("Dat", &X::UniMorphTag::Dat)
          .def_readonly_static("Gen", &X::UniMorphTag::Gen)
          .def_readonly_static("Loc", &X::UniMorphTag::Loc)
          .def_readonly_static("Voc", &X::UniMorphTag::Voc)
          .def_readonly_static("Cmp", &X::UniMorphTag::Cmp)
          .def_readonly_static("Sup", &X::UniMorphTag::Sup)
          .def_readonly_static("Pos", &X::UniMorphTag::Pos)
          .def_readonly_static("Fin", &X::UniMorphTag::Fin)
          .def_readonly_static("Inf", &X::UniMorphTag::Inf)
          .def_readonly_static("Conv", &X::UniMorphTag::Conv)
          .def_readonly_static("Part", &X::UniMorphTag::Part)
          .def_readonly_static("Imp", &X::UniMorphTag::Imp)
          .def_readonly_static("Ind", &X::UniMorphTag::Ind)
          .def_readonly_static("_1", &X::UniMorphTag::_1)
          .def_readonly_static("_2", &X::UniMorphTag::_2)
          .def_readonly_static("_3", &X::UniMorphTag::_3)
          .def_readonly_static("Fut", &X::UniMorphTag::Fut)
          .def_readonly_static("Past", &X::UniMorphTag::Past)
          .def_readonly_static("Pres", &X::UniMorphTag::Pres)
          .def_readonly_static("Notpast", &X::UniMorphTag::Notpast)
          .def_readonly_static("Short", &X::UniMorphTag::Short)
          .def_readonly_static("Act", &X::UniMorphTag::Act)
          .def_readonly_static("Pass", &X::UniMorphTag::Pass)
          .def_readonly_static("Mid", &X::UniMorphTag::Mid)
          .def_readonly_static("Digit", &X::UniMorphTag::Digit)
          .def("__eq__", &X::UniMorphTag::operator==)
          .def("__ne__", &X::UniMorphTag::operator!=)
          .def("__lt__", &X::UniMorphTag::operator<)
          .def("__gt__", &X::UniMorphTag::operator>)
          .def("__str__", &X::UniMorphTag::toString)
          .def_static("from_string", tagFromString)
          .def("get_case", py::overload_cast<>(&X::UniMorphTag::getCase, py::const_))
          .def("get_number", py::overload_cast<>(&X::UniMorphTag::getNumber, py::const_))
          .def("get_gender", py::overload_cast<>(&X::UniMorphTag::getGender, py::const_))
          .def("get_tense", py::overload_cast<>(&X::UniMorphTag::getTense, py::const_))
          .def("get_animacy", py::overload_cast<>(&X::UniMorphTag::getAnimacy, py::const_));
  
      py::class_<X::GraphemTag>(m, "GraphemTag")
          .def_readonly_static("UNKN", &X::GraphemTag::UNKN)
          .def_readonly_static("CYRILLIC", &X::GraphemTag::CYRILLIC)
          .def_readonly_static("LATIN", &X::GraphemTag::LATIN)
          .def_readonly_static("UPPER_CASE", &X::GraphemTag::UPPER_CASE)
          .def_readonly_static("LOWER_CASE", &X::GraphemTag::LOWER_CASE)
          .def_readonly_static("MIXED", &X::GraphemTag::MIXED)
          .def_readonly_static("CAP_START", &X::GraphemTag::CAP_START)
          .def_readonly_static("ABBR", &X::GraphemTag::ABBR)
          .def_readonly_static("NAM_ENT", &X::GraphemTag::NAM_ENT)
          .def_readonly_static("MULTI_WORD", &X::GraphemTag::MULTI_WORD)
          .def_readonly_static("SINGLE_WORD", &X::GraphemTag::SINGLE_WORD)
          .def_readonly_static("COMMA", &X::GraphemTag::COMMA)
          .def_readonly_static("DOT", &X::GraphemTag::DOT)
          .def_readonly_static("COLON", &X::GraphemTag::COLON)
          .def_readonly_static("SEMICOLON", &X::GraphemTag::SEMICOLON)
          .def_readonly_static("QUESTION_MARK", &X::GraphemTag::QUESTION_MARK)
          .def_readonly_static("EXCLAMATION_MARK", &X::GraphemTag::EXCLAMATION_MARK)
          .def_readonly_static("THREE_DOTS", &X::GraphemTag::THREE_DOTS)
          .def_readonly_static("QUOTE", &X::GraphemTag::QUOTE)
          .def_readonly_static("DASH", &X::GraphemTag::DASH)
          .def_readonly_static("PARENTHESIS_L", &X::GraphemTag::PARENTHESIS_L)
          .def_readonly_static("PARENTHESIS_R", &X::GraphemTag::PARENTHESIS_R)
          .def_readonly_static("UNCOMMON_PUNCT", &X::GraphemTag::UNCOMMON_PUNCT)
          .def_readonly_static("PUNCT_GROUP", &X::GraphemTag::PUNCT_GROUP)
          .def_readonly_static("LOWER_DASH", &X::GraphemTag::LOWER_DASH)
          .def_readonly_static("DECIMAL", &X::GraphemTag::DECIMAL)
          .def_readonly_static("BINARY", &X::GraphemTag::BINARY)
          .def_readonly_static("OCT", &X::GraphemTag::OCT)
          .def_readonly_static("HEX", &X::GraphemTag::HEX)
          .def_readonly_static("SPACE", &X::GraphemTag::SPACE)
          .def_readonly_static("TAB", &X::GraphemTag::TAB)
          .def_readonly_static("NEW_LINE", &X::GraphemTag::NEW_LINE)
          .def_readonly_static("CR", &X::GraphemTag::CR)
          .def_readonly_static("SINGLE_SEP", &X::GraphemTag::SINGLE_SEP)
          .def_readonly_static("MULTI_SEP", &X::GraphemTag::MULTI_SEP)
          .def("__eq__", &X::GraphemTag::operator==)
          .def("__ne__", &X::GraphemTag::operator!=)
          .def("__lt__", &X::GraphemTag::operator<)
          .def("__gt__", &X::GraphemTag::operator>)
          .def("__str__", &X::GraphemTag::toString);
  
      py::class_<X::AnalyzerTag>(m, "AnalyzerTag")
          .def_readonly_static("UNKN", &X::AnalyzerTag::UNKN)
          .def_readonly_static("DICT", &X::AnalyzerTag::DICT)
          .def_readonly_static("PREF", &X::AnalyzerTag::PREF)
          .def_readonly_static("SUFF", &X::AnalyzerTag::SUFF)
          .def_readonly_static("HYPH", &X::AnalyzerTag::HYPH)
          .def("__eq__", &X::AnalyzerTag::operator==)
          .def("__ne__", &X::AnalyzerTag::operator!=)
          .def("__lt__", &X::AnalyzerTag::operator<)
          .def("__gt__", &X::AnalyzerTag::operator>)
          .def("__str__", &X::AnalyzerTag::toString);
  
      py::class_<X::TokenTypeTag>(m, "TokenTypeTag")
          .def_readonly_static("UNKN", &X::TokenTypeTag::UNKN)
          .def_readonly_static("WORD", &X::TokenTypeTag::WORD)
          .def_readonly_static("PNCT", &X::TokenTypeTag::PNCT)
          .def_readonly_static("SEPR", &X::TokenTypeTag::SEPR)
          .def_readonly_static("NUMB", &X::TokenTypeTag::NUMB)
          .def_readonly_static("WRNM", &X::TokenTypeTag::WRNM)
          .def_readonly_static("HIER", &X::TokenTypeTag::HIER)
          .def("__eq__", &X::TokenTypeTag::operator==)
          .def("__ne__", &X::TokenTypeTag::operator!=)
          .def("__lt__", &X::TokenTypeTag::operator<)
          .def("__gt__", &X::TokenTypeTag::operator>)
          .def("__str__", &X::TokenTypeTag::toString);
  
      py::class_<X::PhemTag>(m, "PhemTag")
          .def_readonly_static("UNKN", &X::PhemTag::UNKN)
          .def_readonly_static("PREF", &X::PhemTag::PREF)
          .def_readonly_static("ROOT", &X::PhemTag::ROOT)
          .def_readonly_static("SUFF", &X::PhemTag::SUFF)
          .def_readonly_static("END", &X::PhemTag::END)
          .def_readonly_static("LINK", &X::PhemTag::LINK)
          .def_readonly_static("HYPH", &X::PhemTag::HYPH)
          .def_readonly_static("POSTFIX", &X::PhemTag::POSTFIX)
          .def_readonly_static("B_SUFF", &X::PhemTag::B_SUFF)
          .def_readonly_static("B_PREF", &X::PhemTag::B_PREF)
          .def_readonly_static("B_ROOT", &X::PhemTag::B_ROOT)
          .def("__eq__", &X::PhemTag::operator==)
          .def("__ne__", &X::PhemTag::operator!=)
          .def("__lt__", &X::PhemTag::operator<)
          .def("__gt__", &X::PhemTag::operator>)
          .def("__str__", &X::PhemTag::toString);
  
      py::class_<MorphInfo>(m, "MorphInfo")
          .def(py::init<>())
          .def_property("normal_form", &MorphInfo::getNormalFrom, &MorphInfo::setNormalForm)
          .def_property("sp", &MorphInfo::getSP, &MorphInfo::setSP)
          .def_property("tag", &MorphInfo::getTag, &MorphInfo::setTag)
          .def_property("probability", &MorphInfo::getProbability, &MorphInfo::setProbability)
          .def_property("analyzer_tag", &MorphInfo::getAnalyzerTag, &MorphInfo::setAnalyzerTag)
          .def("__eq__", &MorphInfo::operator==)
          .def("__ne__", &MorphInfo::operator!=)
          .def("__lt__", &MorphInfo::operator<)
          .def("__gt__", &MorphInfo::operator>);
  
      py::class_<WordForm>(m, "WordForm")
          .def(py::init<>())
          .def_property("word_form", &WordForm::getWordFrom, &WordForm::setWordForm)
          .def_property("infos", &WordForm::getInfos, &WordForm::setInfos)
          .def_property("token_type", &WordForm::getTokenType, &WordForm::setInfos)
          .def_property("graphem_info", &WordForm::getGraphemTag, &WordForm::setGraphemTag)
          .def_property("phem_info", &WordForm::getPhemInfo, &WordForm::setPhemInfo)
          .def("__eq__", &WordForm::operator==)
          .def("__ne__", &WordForm::operator!=)
          .def("__lt__", &WordForm::operator<)
          .def("__gt__", &WordForm::operator>)
          .def("__str__", &WordForm::toString);
  
      py::class_<MorphAnalyzer>(m, "MorphAnalyzer")
          .def(py::init<>())
          .def("analyze", &MorphAnalyzer::analyze)
          .def("analyze_single_word", &MorphAnalyzer::analyzeSingleWord)
          .def("is_dictionary_word", &MorphAnalyzer::isWordContainsInDictionary)
          .def("get_non_dictionary_words", &MorphAnalyzer::getNonDictionaryWords)
          .def("morphemic_split", &MorphAnalyzer::morphemicSplit)
          .def("generate_lexeme", &MorphAnalyzer::generateLexeme);
  }
  