From 74431e6879f5f28b470d9d116c3d021d214da606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Neboj=C5=A1a=20=C4=86iri=C4=87?= Date: Mon, 20 Jul 2026 18:55:11 +0000 Subject: [PATCH 1/4] Add secondary longest-suffix exemplar guessing for Serbian --- .../inflection/dictionary/dictionary_sr.lst | 8 +++++ .../unicode/inflection/exemplar/suffix_sr.csv | 19 ++++++++++ ...SrGrammarSynthesizer_SrDisplayFunction.cpp | 35 +++++++++++++++++-- ...SrGrammarSynthesizer_SrDisplayFunction.hpp | 2 ++ .../inflection/dialog/inflection/sr.xml | 29 +++++++++++---- 5 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 inflection/resources/org/unicode/inflection/exemplar/suffix_sr.csv diff --git a/inflection/resources/org/unicode/inflection/dictionary/dictionary_sr.lst b/inflection/resources/org/unicode/inflection/dictionary/dictionary_sr.lst index eab9901b2..6ca49dfde 100644 --- a/inflection/resources/org/unicode/inflection/dictionary/dictionary_sr.lst +++ b/inflection/resources/org/unicode/inflection/dictionary/dictionary_sr.lst @@ -17,6 +17,14 @@ izuzetka: singular inanimate genitive masculine noun inflection=35 izuzetke: plural inanimate accusative masculine noun inflection=35 izuzetkom: singular inanimate instrumental masculine noun inflection=35 izuzetku: singular inanimate dative locative vocative masculine noun inflection=35 +изузетак: singular inanimate accusative nominative masculine noun inflection=35 +изузетка: singular inanimate genitive masculine noun inflection=35 +изузетку: singular inanimate dative locative vocative masculine noun inflection=35 +изузетком: singular inanimate instrumental masculine noun inflection=35 +изузеци: plural inanimate nominative vocative masculine noun inflection=35 +изузецима: plural inanimate dative instrumental locative masculine noun inflection=35 +изузетака: plural inanimate genitive masculine noun inflection=35 +изузетке: plural inanimate accusative masculine noun inflection=35 mačaka: plural genitive feminine noun inflection=42 mačka: singular nominative feminine noun inflection=42 mačkama: plural dative instrumental locative feminine noun inflection=42 diff --git a/inflection/resources/org/unicode/inflection/exemplar/suffix_sr.csv b/inflection/resources/org/unicode/inflection/exemplar/suffix_sr.csv new file mode 100644 index 000000000..83c7dbc2c --- /dev/null +++ b/inflection/resources/org/unicode/inflection/exemplar/suffix_sr.csv @@ -0,0 +1,19 @@ +ак,=,изузетак +њак,=,надвожњак +ац,=,дворац +ник,=,камен +ач,=,брод +ар,=,булевар +бус,=,аутобус +ан,=,Драган +ош,=,Милош +иша,=,Љубиша +а,=,Јелена +ица,=,Љубица +ија,=,Србија +ка,=,мачка +о,=,брдо +бло,=,дебло +е,=,море +сеље,=,насеље +ме,=,име diff --git a/inflection/src/inflection/grammar/synthesis/SrGrammarSynthesizer_SrDisplayFunction.cpp b/inflection/src/inflection/grammar/synthesis/SrGrammarSynthesizer_SrDisplayFunction.cpp index 11a271bd2..c4bc5896c 100644 --- a/inflection/src/inflection/grammar/synthesis/SrGrammarSynthesizer_SrDisplayFunction.cpp +++ b/inflection/src/inflection/grammar/synthesis/SrGrammarSynthesizer_SrDisplayFunction.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace inflection::grammar::synthesis { @@ -39,6 +40,7 @@ SrGrammarSynthesizer_SrDisplayFunction::SrGrammarSynthesizer_SrDisplayFunction(c {GrammemeConstants::NUMBER_SINGULAR, GrammemeConstants::NUMBER_PLURAL}, {GrammemeConstants::GENDER_MASCULINE, GrammemeConstants::GENDER_FEMININE, GrammemeConstants::GENDER_NEUTER} }, {}, true) + , suffixToExemplar(inflection::util::ResourceLocator::getRootForLocale(inflection::util::LocaleUtils::SERBIAN()) + u"/exemplar/suffix_" + inflection::util::LocaleUtils::SERBIAN().toString() + u".bist") { } @@ -159,8 +161,37 @@ ::inflection::dialog::DisplayValue *SrGrammarSynthesizer_SrDisplayFunction::getD if (dictionary.isKnownWord(displayString)) { displayString = inflectFromDictionary(constraints, displayString); } else if (enableInflectionGuess) { - // Let's use rule based inflection for nouns. Assume lemma is singular, nominative. - displayString = inflectWithRule(constraints, displayString); + // Primary guess: Rule based inflection for nouns. + ::std::u16string ruleResult = inflectWithRule(constraints, displayString); + if (ruleResult != displayString) { + displayString = ruleResult; + } else { + // Secondary guess: Longest suffix exemplar guessing. + auto countString = GrammarSynthesizerUtil::getFeatureValue(constraints, numberFeature); + auto caseString = GrammarSynthesizerUtil::getFeatureValue(constraints, caseFeature); + auto genderString = GrammarSynthesizerUtil::getFeatureValue(constraints, genderFeature); + std::vector<::std::u16string> constraintsVec; + if (!countString.empty()) constraintsVec.push_back(countString); + if (!caseString.empty() && caseString != GrammemeConstants::CASE_NOMINATIVE) constraintsVec.push_back(caseString); + if (!genderString.empty()) constraintsVec.push_back(genderString); + + for (int32_t suffixLen = std::min(7, static_cast(displayString.length() - 2)); suffixLen > 0; --suffixLen) { + std::u16string_view suffix = std::u16string_view(displayString).substr(displayString.length() - suffixLen); + auto exemplarResult = suffixToExemplar.findTarget(suffix); + if (exemplarResult.has_value()) { + const auto& exemplar = *exemplarResult; + int64_t exemplarGrammemes = 0; + dictionary.getCombinedBinaryType(&exemplarGrammemes, exemplar); + if (exemplarGrammemes != 0) { + auto guessedResult = dictionaryInflector.inflectExemplar(displayString, exemplar, exemplarGrammemes, constraintsVec, {}); + if (guessedResult.has_value()) { + displayString = *guessedResult; + break; + } + } + } + } + } } return new ::inflection::dialog::DisplayValue(displayString, constraints); } diff --git a/inflection/src/inflection/grammar/synthesis/SrGrammarSynthesizer_SrDisplayFunction.hpp b/inflection/src/inflection/grammar/synthesis/SrGrammarSynthesizer_SrDisplayFunction.hpp index c8ae8f9fa..2a4b4de46 100644 --- a/inflection/src/inflection/grammar/synthesis/SrGrammarSynthesizer_SrDisplayFunction.hpp +++ b/inflection/src/inflection/grammar/synthesis/SrGrammarSynthesizer_SrDisplayFunction.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -40,4 +41,5 @@ class inflection::grammar::synthesis::SrGrammarSynthesizer_SrDisplayFunction const ::inflection::dictionary::Inflector &inflector; const ::std::unique_ptr<::inflection::tokenizer::Tokenizer> tokenizer; ::inflection::dialog::DictionaryLookupInflector dictionaryInflector; + const ::inflection::grammar::BidirectionalStringMap suffixToExemplar; }; diff --git a/inflection/test/resources/inflection/dialog/inflection/sr.xml b/inflection/test/resources/inflection/dialog/inflection/sr.xml index ed1893364..60263b0ff 100644 --- a/inflection/test/resources/inflection/dialog/inflection/sr.xml +++ b/inflection/test/resources/inflection/dialog/inflection/sr.xml @@ -14,17 +14,34 @@ СрбијаСрбијо становникстановницима плаваплавe - - - - + + уранакуранка + уранакуранку + пашњакпашњака + клинацклинца + ученикученика + играчиграча + пекарпекара + микробусмикробуса + БојанБојана + РадошРадоша + РадишаРадише + кућицакућице + кућицакућици + географијагеографије + паткапатке + језеројезера + стаблостабла + моремора + саопштењесаопштења + презимепрезимену ИталијаИталијом авенијаавенијом авенијаавенијама - кадијакадија + кадијакадијо уметницауметнице - птицаптица + птицаптице СтанаСтано ЗораЗоро БожаБожо From 7d75b2d503ddc5c530e3019b213f77bb53bee089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Neboj=C5=A1a=20=C4=86iri=C4=87?= Date: Mon, 20 Jul 2026 18:57:13 +0000 Subject: [PATCH 2/4] Revert manual edits to dictionary_sr.lst --- .../org/unicode/inflection/dictionary/dictionary_sr.lst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/inflection/resources/org/unicode/inflection/dictionary/dictionary_sr.lst b/inflection/resources/org/unicode/inflection/dictionary/dictionary_sr.lst index 6ca49dfde..eab9901b2 100644 --- a/inflection/resources/org/unicode/inflection/dictionary/dictionary_sr.lst +++ b/inflection/resources/org/unicode/inflection/dictionary/dictionary_sr.lst @@ -17,14 +17,6 @@ izuzetka: singular inanimate genitive masculine noun inflection=35 izuzetke: plural inanimate accusative masculine noun inflection=35 izuzetkom: singular inanimate instrumental masculine noun inflection=35 izuzetku: singular inanimate dative locative vocative masculine noun inflection=35 -изузетак: singular inanimate accusative nominative masculine noun inflection=35 -изузетка: singular inanimate genitive masculine noun inflection=35 -изузетку: singular inanimate dative locative vocative masculine noun inflection=35 -изузетком: singular inanimate instrumental masculine noun inflection=35 -изузеци: plural inanimate nominative vocative masculine noun inflection=35 -изузецима: plural inanimate dative instrumental locative masculine noun inflection=35 -изузетака: plural inanimate genitive masculine noun inflection=35 -изузетке: plural inanimate accusative masculine noun inflection=35 mačaka: plural genitive feminine noun inflection=42 mačka: singular nominative feminine noun inflection=42 mačkama: plural dative instrumental locative feminine noun inflection=42 From 87ff7854bfd3d0efb59488e3f7e3352bba0762c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Neboj=C5=A1a=20=C4=86iri=C4=87?= Date: Mon, 20 Jul 2026 19:00:44 +0000 Subject: [PATCH 3/4] Replace magic numbers with MAX_SUFFIX_LEN and MIN_STEM_LEN constants --- .../synthesis/SrGrammarSynthesizer_SrDisplayFunction.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/inflection/src/inflection/grammar/synthesis/SrGrammarSynthesizer_SrDisplayFunction.cpp b/inflection/src/inflection/grammar/synthesis/SrGrammarSynthesizer_SrDisplayFunction.cpp index c4bc5896c..f376bf302 100644 --- a/inflection/src/inflection/grammar/synthesis/SrGrammarSynthesizer_SrDisplayFunction.cpp +++ b/inflection/src/inflection/grammar/synthesis/SrGrammarSynthesizer_SrDisplayFunction.cpp @@ -25,6 +25,9 @@ #include #include +static constexpr int32_t MAX_SUFFIX_LEN = 7; +static constexpr int32_t MIN_STEM_LEN = 2; + namespace inflection::grammar::synthesis { SrGrammarSynthesizer_SrDisplayFunction::SrGrammarSynthesizer_SrDisplayFunction(const ::inflection::dialog::SemanticFeatureModel& model) @@ -175,7 +178,7 @@ ::inflection::dialog::DisplayValue *SrGrammarSynthesizer_SrDisplayFunction::getD if (!caseString.empty() && caseString != GrammemeConstants::CASE_NOMINATIVE) constraintsVec.push_back(caseString); if (!genderString.empty()) constraintsVec.push_back(genderString); - for (int32_t suffixLen = std::min(7, static_cast(displayString.length() - 2)); suffixLen > 0; --suffixLen) { + for (int32_t suffixLen = std::min(MAX_SUFFIX_LEN, static_cast(displayString.length() - MIN_STEM_LEN)); suffixLen > 0; --suffixLen) { std::u16string_view suffix = std::u16string_view(displayString).substr(displayString.length() - suffixLen); auto exemplarResult = suffixToExemplar.findTarget(suffix); if (exemplarResult.has_value()) { From cc389e540d97e98b848b3e51f42b62a81a0f5bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Neboj=C5=A1a=20=C4=86iri=C4=87?= Date: Mon, 20 Jul 2026 19:22:44 +0000 Subject: [PATCH 4/4] Replace dictionary words with unlisted words in test cases --- inflection/test/resources/inflection/dialog/inflection/sr.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inflection/test/resources/inflection/dialog/inflection/sr.xml b/inflection/test/resources/inflection/dialog/inflection/sr.xml index 60263b0ff..a8cf7a287 100644 --- a/inflection/test/resources/inflection/dialog/inflection/sr.xml +++ b/inflection/test/resources/inflection/dialog/inflection/sr.xml @@ -30,9 +30,9 @@ кућицакућици географијагеографије паткапатке - језеројезера + огледалоогледала стаблостабла - моремора + уточиштеуточишта саопштењесаопштења презимепрезимену