Vikisözlük trwiktionary https://tr.wiktionary.org/wiki/Vikis%C3%B6zl%C3%BCk:Anasayfa MediaWiki 1.47.0-wmf.7 case-sensitive Ortam Özel Tartışma Kullanıcı Kullanıcı mesaj Vikisözlük Vikisözlük tartışma Dosya Dosya tartışma MediaWiki MediaWiki tartışma Şablon Şablon tartışma Yardım Yardım tartışma Kategori Kategori tartışma Portal Portal tartışma YeniKurum YeniKurum tartışma Ek Ek tartışma Alıntılar Alıntılar tartışma TimedText TimedText talk Modül Modül tartışma Event Event talk misafir 0 53753 5669941 5257723 2026-06-23T10:22:02Z Huntersenpai2 68080 /* Çeviriler */ Yazım hatası düzeltildi 5669941 wikitext text/x-wiki {{bakınız|misafirhane}} ==Türkçe== [mi·sa:·fir] ===Söyleniş=== * {{IPA|dil=tr|/mi.saː.ˈfiɾ/}} ===Köken=== {{k|dil=tr|ota}} {{z|ota|مسافر}}, [[Arapça]] {{z|ar|مِسَافِر}} ===Ad=== {{tr-ad}} {{Vikipedi}} # {{t|dil=tr|sosyoloji}} [[bir]] [[yer|yere]] [[veya]] [[biri|birinin]] [[ev|evine]] [[kısa]] [[bir]] [[süre]] [[kalmak]] [[için]] [[gelmek|gelen]] [[kişi]], [[konuk]], [[mihman]] ====Çekimleme==== {{tr-ad-tablo}} ====Çeviriler==== {{Üst|tip=çeviriler}} *Almanca: {{ç|de|Besucher|m}}, {{ç|de|Besucherin|f}} *Arapça: {{ç|ar|مُسَافِر|m}} *Ersya dili: {{ç|myv|инже}} *Fince: {{ç|fi|vieras}} *Friuli dili: {{ç|fur|forest}} *Hausaca: {{ç|ha|baƙo}} *İngilizce: {{ç|en|guest}} *Lehçe: {{ç|pl|gość}} *Macarca: {{ç|hu|vendég}} *Malgaşça: {{ç|mg|vahiny}} *Mari dili: {{ç|chm|уна}} *Mokşa dili: {{ç|mdf|конак}} *Osmanlı Türkçesi: {{ç|ota|مسافر}} *Pencapça: {{ç|pa|ਅਤਿਥਿ}} *Rumence: {{ç|ro|musafir}} *Telugu dili: {{ç|te|అతిథి}} *Yunanca: {{ç|el|μουσαφίρης}} {{Alt}} ===Kaynakça=== * {{Kaynak-TDK}} ====Atasözleri==== * [[Ahmak misafir ev sahibini ağırlar]] * [[Köylü, misafir kabul etmeyiz demez, konacak konak yoktur der]] * [[Misafir balığa benzer, bekledi mi kokar]] * [[Misafir ev sahibinin kuzusudur]] * [[Misafir kısmeti ile gelir]] * [[Misafir misafiri istemez, ev sahibi ikisini de]] * [[Misafir on kısmetle gelir; birini yer, dokuzunu bırakır]] * [[Misafir umduğunu değil bulduğunu yer]] qm4sxddz4v84fjb4qkawa2l81fw9row batırık 0 127583 5669903 4354579 2026-06-22T20:19:02Z ~2026-36427-17 68146 /* Ad */ Yazım hatası düzeltildi, açıklama detaylandirildi 5669903 wikitext text/x-wiki ==Türkçe== ===Ad=== {{tr-ad}} # {{terim|dil=tr|halk ağzı}} [[Kısır]]ın su ve taze sebzeler ile karıştırılmasıyla yapılan yöresel bir gıda 23hvum881xg8n4sf2yhrfd2juicyxnf შიში 0 148291 5669906 4541794 2026-06-22T21:35:32Z Turgut46 26665 /* Gürcüce */ 5669906 wikitext text/x-wiki == Gürcüce == ===Ad=== {{başlık başı|ka|ad}} # [[korku]] n4j16tttogoi3djms6ofvj7luh9rnpz грейпфрут 0 178379 5669945 4537736 2026-06-23T11:20:59Z MustafaCavlak 59368 /* Rusça */ 5669945 wikitext text/x-wiki ==Rusça== ===Ad=== {{başlık başı|ru|ad|baş=грейпфру́т|c=e}} # [[greyfurt]] ====Çekimleme==== {{ru-ad-cansız-s-E-a|грейпфру́т}} 2j75lkdepndkgf7wgm3n84jfpfo26gm Vikisözlük:Değişiklik sayılarına göre Vikisözlükçüler listesi 4 466356 5669904 5669610 2026-06-22T21:05:16Z YBot 37147 Güncelleme 5669904 wikitext text/x-wiki {{/begin}} <center> {| class="wikitable" ! # ! Kullanıcı ! Değişiklik sayısı ! Kullanıcı grupları |- | 1 | [[Kullanıcı:HastaLaVi2|HastaLaVi2]] | align="center" | 1.264.519 | arayüz yöneticisi, hizmetli |- | 2 | [[Kullanıcı:Victor Trevor|Victor Trevor]] | align="center" | 144.832 | |- | 3 | [[Kullanıcı:MustafaCavlak|MustafaCavlak]] | align="center" | 102.273 | hizmetli, geçici hesap IP görüntüleyici |- | 4 | [[Kullanıcı:Viki|Viki]] | align="center" | 45.152 | |- | 5 | [[Kullanıcı:By erdo can|By erdo can]] | align="center" | 42.866 | |- | 6 | [[Kullanıcı:Satirdan kahraman|Satirdan kahraman]] | align="center" | 37.378 | |- | 7 | [[Kullanıcı:Sae1962|Sae1962]] | align="center" | 34.149 | |- | 8 | [[Kullanıcı:Saltinbas|Saltinbas]] | align="center" | 26.434 | |- | 9 | [[Kullanıcı:MüjdeA|MüjdeA]] | align="center" | 25.502 | |- | 10 | [[Kullanıcı:Sabri76|Sabri76]] | align="center" | 20.349 | |- | 11 | [[Kullanıcı:Katakay|Katakay]] | align="center" | 19.415 | |- | 12 | [[Kullanıcı:Turgut46|Turgut46]] | align="center" | 17.527 | hizmetli |- | 13 | [[Kullanıcı:Vuzorg|Vuzorg]] | align="center" | 15.932 | |- | 14 | [[Kullanıcı:Ender|Ender]] | align="center" | 14.945 | |- | 15 | [[Kullanıcı:Kök|Kök]] | align="center" | 8.437 | |- | 16 | [[Kullanıcı:Curious|Curious]] | align="center" | 7.438 | |- | 17 | [[Kullanıcı:Bobcats|Bobcats]] | align="center" | 6.526 | |- | 18 | [[Kullanıcı:Nosferatü|Nosferatü]] | align="center" | 6.408 | |- | 19 | [[Kullanıcı:Uncitoyen|Uncitoyen]] | align="center" | 5.685 | hizmetli |- | 20 | [[Kullanıcı:Vikiyılmaz|Vikiyılmaz]] | align="center" | 5.369 | |- | 21 | [[Kullanıcı:Sinek~trwiktionary|Sinek~trwiktionary]] | align="center" | 5.221 | |- | 22 | [[Kullanıcı:YZCTEK|YZCTEK]] | align="center" | 4.880 | |- | 23 | [[Kullanıcı:Vorhon|Vorhon]] | align="center" | 4.699 | |- | 24 | [[Kullanıcı:LA2|LA2]] | align="center" | 4.433 | |- | 25 | [[Kullanıcı:Apisite|Apisite]] | align="center" | 4.428 | |- | 26 | [[Kullanıcı:B5.101.222.187|B5.101.222.187]] | align="center" | 4.259 | |- | 27 | [[Kullanıcı:Kavurt|Kavurt]] | align="center" | 4.088 | |- | 28 | [[Kullanıcı:Vitruvian|Vitruvian]] | align="center" | 4.054 | |- | 29 | [[Kullanıcı:123snake45|123snake45]] | align="center" | 3.971 | |- | 30 | [[Kullanıcı:Alikiroglu61|Alikiroglu61]] | align="center" | 3.773 | |- | 31 | [[Kullanıcı:3210|3210]] | align="center" | 3.716 | |- | 32 | [[Kullanıcı:Goreno|Goreno]] | align="center" | 3.474 | |- | 33 | [[Kullanıcı:Runningfridgesrule|Runningfridgesrule]] | align="center" | 3.119 | |- | 34 | [[Kullanıcı:Reality006|Reality006]] | align="center" | 3.099 | |- | 35 | [[Kullanıcı:HâlitM|HâlitM]] | align="center" | 3.052 | |- | 36 | [[Kullanıcı:Ahmed922229|Ahmed922229]] | align="center" | 3.009 | |- | 37 | [[Kullanıcı:Barishan|Barishan]] | align="center" | 2.971 | |- | 38 | [[Kullanıcı:Ekrem691|Ekrem691]] | align="center" | 2.398 | |- | 39 | [[Kullanıcı:Esege|Esege]] | align="center" | 2.321 | |- | 40 | [[Kullanıcı:Ugur Basak|Ugur Basak]] | align="center" | 2.267 | |- | 41 | [[Kullanıcı:Gamlıbaykuş|Gamlıbaykuş]] | align="center" | 2.098 | |- | 42 | [[Kullanıcı:AML ZİYA AKYASAN|AML ZİYA AKYASAN]] | align="center" | 1.989 | |- | 43 | [[Kullanıcı:Entry62|Entry62]] | align="center" | 1.959 | |- | 44 | [[Kullanıcı:Magurale|Magurale]] | align="center" | 1.854 | |- | 45 | [[Kullanıcı:Sihl fatma|Sihl fatma]] | align="center" | 1.835 | |- | 46 | [[Kullanıcı:Don Alessandro|Don Alessandro]] | align="center" | 1.815 | |- | 47 | [[Kullanıcı:Burudet88|Burudet88]] | align="center" | 1.791 | |- | 48 | [[Kullanıcı:Şêr|Şêr]] | align="center" | 1.668 | |- | 49 | [[Kullanıcı:Abuk78|Abuk78]] | align="center" | 1.534 | |- | 50 | [[Kullanıcı:Göktürker|Göktürker]] | align="center" | 1.508 | |- | 51 | [[Kullanıcı:Flanoz|Flanoz]] | align="center" | 1.463 | |- | 52 | [[Kullanıcı:Sa'y|Sa'y]] | align="center" | 1.345 | |- | 53 | [[Kullanıcı:Mzekiu|Mzekiu]] | align="center" | 1.344 | |- | 54 | [[Kullanıcı:Samleml enes bilgin|Samleml enes bilgin]] | align="center" | 1.315 | |- | 55 | [[Kullanıcı:İbrahim alican|İbrahim alican]] | align="center" | 1.292 | |- | 56 | [[Kullanıcı:SİHL Gökhan|SİHL Gökhan]] | align="center" | 1.278 | |- | 57 | [[Kullanıcı:Nazendegül|Nazendegül]] | align="center" | 1.260 | |- | 58 | [[Kullanıcı:ליאון12|ליאון12]] | align="center" | 1.178 | |- | 59 | [[Kullanıcı:Semlaml110|Semlaml110]] | align="center" | 1.174 | |- | 60 | [[Kullanıcı:Ahmetaslan|Ahmetaslan]] | align="center" | 1.141 | |- | 61 | [[Kullanıcı:Kibele|Kibele]] | align="center" | 1.136 | |- | 62 | [[Kullanıcı:Wwww8888|Wwww8888]] | align="center" | 1.125 | |- | 63 | [[Kullanıcı:Kmlblg 332|Kmlblg 332]] | align="center" | 1.124 | |- | 64 | [[Kullanıcı:Lagrium|Lagrium]] | align="center" | 1.094 | |- | 65 | [[Kullanıcı:SİHL ALİ MUSA|SİHL ALİ MUSA]] | align="center" | 1.045 | |- | 66 | [[Kullanıcı:Ömer faruk çakmak|Ömer faruk çakmak]] | align="center" | 996 | |- | 67 | [[Kullanıcı:Aybeg|Aybeg]] | align="center" | 968 | |- | 68 | [[Kullanıcı:ВМНС|ВМНС]] | align="center" | 962 | |- | 69 | [[Kullanıcı:Almanbet Janışev|Almanbet Janışev]] | align="center" | 954 | |- | 70 | [[Kullanıcı:Seksen iki yüz kırk beş|Seksen iki yüz kırk beş]] | align="center" | 931 | |- | 71 | [[Kullanıcı:Maxa|Maxa]] | align="center" | 912 | |- | 72 | [[Kullanıcı:Wiamboo|Wiamboo]] | align="center" | 880 | |- | 73 | [[Kullanıcı:SİHL Menzulekumaş|SİHL Menzulekumaş]] | align="center" | 846 | |- | 74 | [[Kullanıcı:Melkorettin|Melkorettin]] | align="center" | 812 | |- | 75 | [[Kullanıcı:Yasemin|Yasemin]] | align="center" | 802 | |- | 76 | [[Kullanıcı:Peirep|Peirep]] | align="center" | 791 | |- | 77 | [[Kullanıcı:Jeuwre|Jeuwre]] | align="center" | 786 | |- | 78 | [[Kullanıcı:S.ihl.selmaaksoy|S.ihl.selmaaksoy]] | align="center" | 781 | |- | 79 | [[Kullanıcı:Gibranist|Gibranist]] | align="center" | 773 | |- | 80 | [[Kullanıcı:Kmlblgfatmanur|Kmlblgfatmanur]] | align="center" | 771 | |- | 81 | [[Kullanıcı:Asım Hoca|Asım Hoca]] | align="center" | 765 | |- | 82 | [[Kullanıcı:Malafaya|Malafaya]] | align="center" | 765 | |- | 83 | [[Kullanıcı:Hagvoda|Hagvoda]] | align="center" | 760 | |- | 84 | [[Kullanıcı:YosefHan7|YosefHan7]] | align="center" | 756 | |- | 85 | [[Kullanıcı:Mustafahoca|Mustafahoca]] | align="center" | 745 | |- | 86 | [[Kullanıcı:ZazaLejyoner|ZazaLejyoner]] | align="center" | 740 | |- | 87 | [[Kullanıcı:Sürmene aml ve eml yusufcakir|Sürmene aml ve eml yusufcakir]] | align="center" | 721 | |- | 88 | [[Kullanıcı:Mehmet-k61|Mehmet-k61]] | align="center" | 670 | |- | 89 | [[Kullanıcı:Sinus46|Sinus46]] | align="center" | 670 | |- | 90 | [[Kullanıcı:Kml serhan|Kml serhan]] | align="center" | 668 | |- | 91 | [[Kullanıcı:TrDAsh|TrDAsh]] | align="center" | 639 | |- | 92 | [[Kullanıcı:Itidal|Itidal]] | align="center" | 635 | |- | 93 | [[Kullanıcı:Ömerbysl|Ömerbysl]] | align="center" | 635 | |- | 94 | [[Kullanıcı:Chelik99|Chelik99]] | align="center" | 587 | |- | 95 | [[Kullanıcı:Mereyü|Mereyü]] | align="center" | 556 | |- | 96 | [[Kullanıcı:Octahedron80|Octahedron80]] | align="center" | 547 | |- | 97 | [[Kullanıcı:ALİ İHSAN KARAMAN|ALİ İHSAN KARAMAN]] | align="center" | 533 | |- | 98 | [[Kullanıcı:Gullerdiyari|Gullerdiyari]] | align="center" | 520 | |- | 99 | [[Kullanıcı:Yeniler|Yeniler]] | align="center" | 515 | |- | 100 | [[Kullanıcı:Andac20|Andac20]] | align="center" | 509 | |- | 101 | [[Kullanıcı:Dilyâresi|Dilyâresi]] | align="center" | 485 | |- | 102 | [[Kullanıcı:Nerepla Keskin|Nerepla Keskin]] | align="center" | 483 | |- | 103 | [[Kullanıcı:Berkucar0|Berkucar0]] | align="center" | 473 | |- | 104 | [[Kullanıcı:İsmetaydin|İsmetaydin]] | align="center" | 465 | |- | 105 | [[Kullanıcı:Sadmleml enes|Sadmleml enes]] | align="center" | 464 | |- | 106 | [[Kullanıcı:Condmatstrel|Condmatstrel]] | align="center" | 461 | |- | 107 | [[Kullanıcı:Wutsje|Wutsje]] | align="center" | 440 | |- | 108 | [[Kullanıcı:Lo Ximiendo|Lo Ximiendo]] | align="center" | 435 | |- | 109 | [[Kullanıcı:Kml znnt|Kml znnt]] | align="center" | 430 | |- | 110 | [[Kullanıcı:CommonsDelinker|CommonsDelinker]] | align="center" | 423 | |- | 111 | [[Kullanıcı:Kendim|Kendim]] | align="center" | 418 | |- | 112 | [[Kullanıcı:Pathoschild|Pathoschild]] | align="center" | 413 | |- | 113 | [[Kullanıcı:Kemal.KURBAN|Kemal.KURBAN]] | align="center" | 413 | |- | 114 | [[Kullanıcı:Merbeg|Merbeg]] | align="center" | 410 | |- | 115 | [[Kullanıcı:Hamzahoca|Hamzahoca]] | align="center" | 405 | |- | 116 | [[Kullanıcı:AHMET AYSOY|AHMET AYSOY]] | align="center" | 393 | |- | 117 | [[Kullanıcı:Sihl ayhan|Sihl ayhan]] | align="center" | 385 | |- | 118 | [[Kullanıcı:A.d.m.l.ve.e.m.lmuhammet alican|A.d.m.l.ve.e.m.lmuhammet alican]] | align="center" | 383 | |- | 119 | [[Kullanıcı:Kvazimodo|Kvazimodo]] | align="center" | 381 | |- | 120 | [[Kullanıcı:Muhammet yardım|Muhammet yardım]] | align="center" | 378 | |- | 121 | [[Kullanıcı:Egemensen~trwiktionary|Egemensen~trwiktionary]] | align="center" | 376 | |- | 122 | [[Kullanıcı:Johnny281993|Johnny281993]] | align="center" | 376 | |- | 123 | [[Kullanıcı:Türkçe|Türkçe]] | align="center" | 368 | |- | 124 | [[Kullanıcı:Vito Genovese|Vito Genovese]] | align="center" | 365 | |- | 125 | [[Kullanıcı:Ata Kemal|Ata Kemal]] | align="center" | 365 | |- | 126 | [[Kullanıcı:ToprakM|ToprakM]] | align="center" | 365 | |- | 127 | [[Kullanıcı:Yunus Emre Biçer|Yunus Emre Biçer]] | align="center" | 364 | |- | 128 | [[Kullanıcı:Bm|Bm]] | align="center" | 347 | |- | 129 | [[Kullanıcı:Ampouble|Ampouble]] | align="center" | 343 | |- | 130 | [[Kullanıcı:SLÖzlem|SLÖzlem]] | align="center" | 343 | |- | 131 | [[Kullanıcı:S.orsev.ayşe kibaroğlu|S.orsev.ayşe kibaroğlu]] | align="center" | 341 | |- | 132 | [[Kullanıcı:MelekArı|MelekArı]] | align="center" | 336 | |- | 133 | [[Kullanıcı:Tegel|Tegel]] | align="center" | 313 | |- | 134 | [[Kullanıcı:The cat|The cat]] | align="center" | 311 | |- | 135 | [[Kullanıcı:SAL 378|SAL 378]] | align="center" | 305 | |- | 136 | [[Kullanıcı:Saml livess 61|Saml livess 61]] | align="center" | 297 | |- | 137 | [[Kullanıcı:Guljan|Guljan]] | align="center" | 296 | |- | 138 | [[Kullanıcı:Kmlblg272|Kmlblg272]] | align="center" | 292 | |- | 139 | [[Kullanıcı:SLDerya|SLDerya]] | align="center" | 292 | |- | 140 | [[Kullanıcı:Srhat|Srhat]] | align="center" | 285 | |- | 141 | [[Kullanıcı:2004onuralp|2004onuralp]] | align="center" | 285 | |- | 142 | [[Kullanıcı:Swam pl|Swam pl]] | align="center" | 284 | |- | 143 | [[Kullanıcı:Kml ibrahim|Kml ibrahim]] | align="center" | 275 | |- | 144 | [[Kullanıcı:MSchoentgen|MSchoentgen]] | align="center" | 274 | |- | 145 | [[Kullanıcı:Underfell Flowey|Underfell Flowey]] | align="center" | 270 | |- | 146 | [[Kullanıcı:Dbl2010|Dbl2010]] | align="center" | 264 | |- | 147 | [[Kullanıcı:Funda|Funda]] | align="center" | 260 | |- | 148 | [[Kullanıcı:Kebab in my butt|Kebab in my butt]] | align="center" | 260 | |- | 149 | [[Kullanıcı:Biyolojiyabikurdi|Biyolojiyabikurdi]] | align="center" | 258 | |- | 150 | [[Kullanıcı:Arhancelo|Arhancelo]] | align="center" | 256 | |- | 151 | [[Kullanıcı:SLnuray|SLnuray]] | align="center" | 251 | |- | 152 | [[Kullanıcı:Gilthuanas|Gilthuanas]] | align="center" | 250 | |- | 153 | [[Kullanıcı:Yorınçga573|Yorınçga573]] | align="center" | 249 | |- | 154 | [[Kullanıcı:SL.resul akyıldız|SL.resul akyıldız]] | align="center" | 247 | |- | 155 | [[Kullanıcı:SLözgül|SLözgül]] | align="center" | 246 | |- | 156 | [[Kullanıcı:Azxpetro|Azxpetro]] | align="center" | 244 | |- | 157 | [[Kullanıcı:Determinator|Determinator]] | align="center" | 243 | |- | 158 | [[Kullanıcı:Серк.123|Серк.123]] | align="center" | 243 | |- | 159 | [[Kullanıcı:Ceaeven|Ceaeven]] | align="center" | 240 | |- | 160 | [[Kullanıcı:Sihl gülten deveci|Sihl gülten deveci]] | align="center" | 237 | |- | 161 | [[Kullanıcı:Axbyc|Axbyc]] | align="center" | 236 | |- | 162 | [[Kullanıcı:Superyetkin|Superyetkin]] | align="center" | 235 | arayüz yöneticisi |- | 163 | [[Kullanıcı:Wikihez|Wikihez]] | align="center" | 228 | |- | 164 | [[Kullanıcı:SLFatma|SLFatma]] | align="center" | 227 | |- | 165 | [[Kullanıcı:Kmlblg240|Kmlblg240]] | align="center" | 224 | |- | 166 | [[Kullanıcı:Erdemaslancan|Erdemaslancan]] | align="center" | 224 | |- | 167 | [[Kullanıcı:Stml 172|Stml 172]] | align="center" | 220 | |- | 168 | [[Kullanıcı:SLUğurcan|SLUğurcan]] | align="center" | 219 | |- | 169 | [[Kullanıcı:Deknuydt|Deknuydt]] | align="center" | 217 | |- | 170 | [[Kullanıcı:Kwamikagami|Kwamikagami]] | align="center" | 212 | |- | 171 | [[Kullanıcı:Axis09|Axis09]] | align="center" | 210 | |- | 172 | [[Kullanıcı:OSMAN ÇAVUŞLU|OSMAN ÇAVUŞLU]] | align="center" | 209 | |- | 173 | [[Kullanıcı:Public class|Public class]] | align="center" | 208 | |- | 174 | [[Kullanıcı:Aivasedo|Aivasedo]] | align="center" | 205 | |- | 175 | [[Kullanıcı:Sal 390|Sal 390]] | align="center" | 203 | |- | 176 | [[Kullanıcı:Onuralpateş2004|Onuralpateş2004]] | align="center" | 202 | |- | 177 | [[Kullanıcı:Aslan yakup|Aslan yakup]] | align="center" | 197 | |- | 178 | [[Kullanıcı:SLebru|SLebru]] | align="center" | 197 | |- | 179 | [[Kullanıcı:Sal-zeynep|Sal-zeynep]] | align="center" | 196 | |- | 180 | [[Kullanıcı:SLRahman|SLRahman]] | align="center" | 194 | |- | 181 | [[Kullanıcı:Chapultepec|Chapultepec]] | align="center" | 193 | |- | 182 | [[Kullanıcı:Trong Dang|Trong Dang]] | align="center" | 192 | |- | 183 | [[Kullanıcı:Syum90|Syum90]] | align="center" | 185 | |- | 184 | [[Kullanıcı:NURULLAH GÜLDAL|NURULLAH GÜLDAL]] | align="center" | 183 | |- | 185 | [[Kullanıcı:Vikiçizer|Vikiçizer]] | align="center" | 182 | |- | 186 | [[Kullanıcı:MehmedKM|MehmedKM]] | align="center" | 182 | |- | 187 | [[Kullanıcı:Bikarhêner|Bikarhêner]] | align="center" | 180 | |- | 188 | [[Kullanıcı:Şaşılık|Şaşılık]] | align="center" | 179 | |- | 189 | [[Kullanıcı:Felecita|Felecita]] | align="center" | 176 | |- | 190 | [[Kullanıcı:Ddenkel~trwiktionary|Ddenkel~trwiktionary]] | align="center" | 175 | |- | 191 | [[Kullanıcı:Sezinumaykucuk|Sezinumaykucuk]] | align="center" | 175 | |- | 192 | [[Kullanıcı:Sallapattias|Sallapattias]] | align="center" | 171 | |- | 193 | [[Kullanıcı:SLburhantuncer|SLburhantuncer]] | align="center" | 168 | |- | 194 | [[Kullanıcı:Bahattin|Bahattin]] | align="center" | 168 | |- | 195 | [[Kullanıcı:Kml suleakguc|Kml suleakguc]] | align="center" | 167 | |- | 196 | [[Kullanıcı:Sihl ASİYE|Sihl ASİYE]] | align="center" | 167 | |- | 197 | [[Kullanıcı:Mehmetkasımay|Mehmetkasımay]] | align="center" | 165 | |- | 198 | [[Kullanıcı:SLfatma254|SLfatma254]] | align="center" | 163 | |- | 199 | [[Kullanıcı:Yko 235|Yko 235]] | align="center" | 162 | |- | 200 | [[Kullanıcı:Stml 158|Stml 158]] | align="center" | 159 | |- | 201 | [[Kullanıcı:Stml 121|Stml 121]] | align="center" | 156 | |- | 202 | [[Kullanıcı:SLEngin|SLEngin]] | align="center" | 154 | |- | 203 | [[Kullanıcı:Serdar62|Serdar62]] | align="center" | 154 | |- | 204 | [[Kullanıcı:Terryclient|Terryclient]] | align="center" | 151 | |- | 205 | [[Kullanıcı:Czeski Wratek|Czeski Wratek]] | align="center" | 151 | |- | 206 | [[Kullanıcı:Calq|Calq]] | align="center" | 148 | |- | 207 | [[Kullanıcı:Sihl Ramazan Erdöl|Sihl Ramazan Erdöl]] | align="center" | 146 | |- | 208 | [[Kullanıcı:Drabdullayev17|Drabdullayev17]] | align="center" | 140 | |- | 209 | [[Kullanıcı:Lionel Cristiano|Lionel Cristiano]] | align="center" | 139 | |- | 210 | [[Kullanıcı:Hasan Sami|Hasan Sami]] | align="center" | 138 | |- | 211 | [[Kullanıcı:Abuk SABUK|Abuk SABUK]] | align="center" | 138 | |- | 212 | [[Kullanıcı:Dilci|Dilci]] | align="center" | 137 | |- | 213 | [[Kullanıcı:Matiia|Matiia]] | align="center" | 137 | |- | 214 | [[Kullanıcı:SİHL İSA ELER|SİHL İSA ELER]] | align="center" | 135 | |- | 215 | [[Kullanıcı:Ata Kemal Ayaser|Ata Kemal Ayaser]] | align="center" | 135 | |- | 216 | [[Kullanıcı:Emperyan|Emperyan]] | align="center" | 135 | |- | 217 | [[Kullanıcı:Lou|Lou]] | align="center" | 133 | |- | 218 | [[Kullanıcı:Rolby|Rolby]] | align="center" | 133 | |- | 219 | [[Kullanıcı:Peter Berbe|Peter Berbe]] | align="center" | 133 | |- | 220 | [[Kullanıcı:Eminovič|Eminovič]] | align="center" | 132 | |- | 221 | [[Kullanıcı:Hdayetozsoy|Hdayetozsoy]] | align="center" | 131 | |- | 222 | [[Kullanıcı:Fankibiber|Fankibiber]] | align="center" | 129 | |- | 223 | [[Kullanıcı:GerardM|GerardM]] | align="center" | 125 | |- | 224 | [[Kullanıcı:Ka dir 641|Ka dir 641]] | align="center" | 125 | |- | 225 | [[Kullanıcı:Mesutbolukbas|Mesutbolukbas]] | align="center" | 124 | |- | 226 | [[Kullanıcı:SLemine607 10D|SLemine607 10D]] | align="center" | 122 | |- | 227 | [[Kullanıcı:Tarihçi Selo|Tarihçi Selo]] | align="center" | 120 | |- | 228 | [[Kullanıcı:Lambiam|Lambiam]] | align="center" | 120 | |- | 229 | [[Kullanıcı:Dodecaplex|Dodecaplex]] | align="center" | 120 | |- | 230 | [[Kullanıcı:Vay~trwiktionary|Vay~trwiktionary]] | align="center" | 119 | |- | 231 | [[Kullanıcı:Yavuz Atasever|Yavuz Atasever]] | align="center" | 118 | |- | 232 | [[Kullanıcı:Metal Militia|Metal Militia]] | align="center" | 117 | |- | 233 | [[Kullanıcı:Aarp65|Aarp65]] | align="center" | 117 | |- | 234 | [[Kullanıcı:Eyüp Türküm|Eyüp Türküm]] | align="center" | 117 | |- | 235 | [[Kullanıcı:Kalem.metin|Kalem.metin]] | align="center" | 117 | |- | 236 | [[Kullanıcı:Mario The PS2 Guy|Mario The PS2 Guy]] | align="center" | 117 | |- | 237 | [[Kullanıcı:Meyegon|Meyegon]] | align="center" | 115 | |- | 238 | [[Kullanıcı:Cûndûllah el-Kurdî|Cûndûllah el-Kurdî]] | align="center" | 115 | |- | 239 | [[Kullanıcı:Minorax|Minorax]] | align="center" | 115 | |- | 240 | [[Kullanıcı:Baris6161TURK|Baris6161TURK]] | align="center" | 113 | |- | 241 | [[Kullanıcı:Kml 207aysegül|Kml 207aysegül]] | align="center" | 112 | |- | 242 | [[Kullanıcı:SLseda|SLseda]] | align="center" | 111 | |- | 243 | [[Kullanıcı:Kmoksy|Kmoksy]] | align="center" | 111 | |- | 244 | [[Kullanıcı:LesChloroformistes|LesChloroformistes]] | align="center" | 110 | |- | 245 | [[Kullanıcı:Ladsgroup|Ladsgroup]] | align="center" | 109 | |- | 246 | [[Kullanıcı:Müntesib|Müntesib]] | align="center" | 109 | |- | 247 | [[Kullanıcı:Kmlrbabalik|Kmlrbabalik]] | align="center" | 108 | |- | 248 | [[Kullanıcı:Kmlblg86|Kmlblg86]] | align="center" | 108 | |- | 249 | [[Kullanıcı:S htk 117|S htk 117]] | align="center" | 105 | |- | 250 | [[Kullanıcı:SLGamze|SLGamze]] | align="center" | 105 | |- | 251 | [[Kullanıcı:Doğru Tercüman|Doğru Tercüman]] | align="center" | 105 | |- | 252 | [[Kullanıcı:Küçükdere iöo gülay|Küçükdere iöo gülay]] | align="center" | 104 | |- | 253 | [[Kullanıcı:Gülşah|Gülşah]] | align="center" | 102 | |- | 254 | [[Kullanıcı:Balamax|Balamax]] | align="center" | 102 | |- | 255 | [[Kullanıcı:Ormanbotanigi|Ormanbotanigi]] | align="center" | 101 | |- | 256 | [[Kullanıcı:Stml 176|Stml 176]] | align="center" | 101 | |- | 257 | [[Kullanıcı:Stml 165|Stml 165]] | align="center" | 101 | |- | 258 | [[Kullanıcı:Kmlblg399|Kmlblg399]] | align="center" | 100 | |- | 259 | [[Kullanıcı:Nerval|Nerval]] | align="center" | 99 | |- | 260 | [[Kullanıcı:Stml 72|Stml 72]] | align="center" | 98 | |- | 261 | [[Kullanıcı:Stryn|Stryn]] | align="center" | 97 | |- | 262 | [[Kullanıcı:Küçükdere iöo sinan|Küçükdere iöo sinan]] | align="center" | 96 | |- | 263 | [[Kullanıcı:Dünya vatandaşı|Dünya vatandaşı]] | align="center" | 95 | |- | 264 | [[Kullanıcı:Muhammet yazıcı|Muhammet yazıcı]] | align="center" | 95 | |- | 265 | [[Kullanıcı:SİHL 729|SİHL 729]] | align="center" | 95 | |- | 266 | [[Kullanıcı:Xorasan|Xorasan]] | align="center" | 95 | |- | 267 | [[Kullanıcı:Fiilçeker|Fiilçeker]] | align="center" | 94 | |- | 268 | [[Kullanıcı:RadiX|RadiX]] | align="center" | 94 | |- | 269 | [[Kullanıcı:Muallim Fatih|Muallim Fatih]] | align="center" | 94 | |- | 270 | [[Kullanıcı:Marrovi|Marrovi]] | align="center" | 94 | |- | 271 | [[Kullanıcı:Spacebirdy|Spacebirdy]] | align="center" | 93 | |- | 272 | [[Kullanıcı:MhmtÖ|MhmtÖ]] | align="center" | 93 | |- | 273 | [[Kullanıcı:Berr.in|Berr.in]] | align="center" | 92 | |- | 274 | [[Kullanıcı:SL-busenur.523|SL-busenur.523]] | align="center" | 91 | |- | 275 | [[Kullanıcı:SLİpek|SLİpek]] | align="center" | 90 | |- | 276 | [[Kullanıcı:Kmlblgözlem|Kmlblgözlem]] | align="center" | 89 | |- | 277 | [[Kullanıcı:Cavlak-FS|Cavlak-FS]] | align="center" | 88 | |- | 278 | [[Kullanıcı:Azminwan|Azminwan]] | align="center" | 87 | |- | 279 | [[Kullanıcı:Moonpulsar|Moonpulsar]] | align="center" | 87 | |- | 280 | [[Kullanıcı:Stml 171|Stml 171]] | align="center" | 86 | |- | 281 | [[Kullanıcı:Turkmen|Turkmen]] | align="center" | 85 | |- | 282 | [[Kullanıcı:Xyz...|Xyz...]] | align="center" | 84 | |- | 283 | [[Kullanıcı:Stml 117|Stml 117]] | align="center" | 83 | |- | 284 | [[Kullanıcı:Juliancolton|Juliancolton]] | align="center" | 81 | |- | 285 | [[Kullanıcı:Ludwig20|Ludwig20]] | align="center" | 81 | |- | 286 | [[Kullanıcı:2004ateş|2004ateş]] | align="center" | 81 | |- | 287 | [[Kullanıcı:Süleymanbedir|Süleymanbedir]] | align="center" | 80 | |- | 288 | [[Kullanıcı:Kıdemli|Kıdemli]] | align="center" | 80 | |- | 289 | [[Kullanıcı:Karabat Kuşu|Karabat Kuşu]] | align="center" | 79 | |- | 290 | [[Kullanıcı:SAML VE EML CEMİL BAYRAM|SAML VE EML CEMİL BAYRAM]] | align="center" | 79 | |- | 291 | [[Kullanıcı:Semlaml111|Semlaml111]] | align="center" | 79 | |- | 292 | [[Kullanıcı:SEML Turan ÇAKIR|SEML Turan ÇAKIR]] | align="center" | 78 | |- | 293 | [[Kullanıcı:Küçükdere iöo celilakyürek|Küçükdere iöo celilakyürek]] | align="center" | 78 | |- | 294 | [[Kullanıcı:SLemine613|SLemine613]] | align="center" | 78 | |- | 295 | [[Kullanıcı:Dokuz sekiz|Dokuz sekiz]] | align="center" | 78 | |- | 296 | [[Kullanıcı:Sayonzei|Sayonzei]] | align="center" | 78 | |- | 297 | [[Kullanıcı:İlhanbiskin|İlhanbiskin]] | align="center" | 77 | |- | 298 | [[Kullanıcı:İsmailhoca|İsmailhoca]] | align="center" | 77 | |- | 299 | [[Kullanıcı:Kmlblg80|Kmlblg80]] | align="center" | 77 | |- | 300 | [[Kullanıcı:Masseman|Masseman]] | align="center" | 76 | |- | 301 | [[Kullanıcı:Ridvanu|Ridvanu]] | align="center" | 75 | |- | 302 | [[Kullanıcı:Ege yalcin~trwiktionary|Ege yalcin~trwiktionary]] | align="center" | 75 | |- | 303 | [[Kullanıcı:İbrhm.kuru|İbrhm.kuru]] | align="center" | 73 | |- | 304 | [[Kullanıcı:Mustafa Eşitgen|Mustafa Eşitgen]] | align="center" | 73 | |- | 305 | [[Kullanıcı:Fagus|Fagus]] | align="center" | 73 | |- | 306 | [[Kullanıcı:SL Pelin|SL Pelin]] | align="center" | 72 | |- | 307 | [[Kullanıcı:Seninbey~trwiktionary|Seninbey~trwiktionary]] | align="center" | 72 | |- | 308 | [[Kullanıcı:EmrahÖ|EmrahÖ]] | align="center" | 72 | |- | 309 | [[Kullanıcı:BitikciKebbenek|BitikciKebbenek]] | align="center" | 72 | |- | 310 | [[Kullanıcı:Emree4|Emree4]] | align="center" | 71 | |- | 311 | [[Kullanıcı:Kontrolcu|Kontrolcu]] | align="center" | 71 | |- | 312 | [[Kullanıcı:Nebra|Nebra]] | align="center" | 71 | |- | 313 | [[Kullanıcı:Pembe karadeniz|Pembe karadeniz]] | align="center" | 71 | |- | 314 | [[Kullanıcı:Stml 147|Stml 147]] | align="center" | 70 | |- | 315 | [[Kullanıcı:SEMLNazımKURŞUN|SEMLNazımKURŞUN]] | align="center" | 70 | |- | 316 | [[Kullanıcı:Giresunlu1993|Giresunlu1993]] | align="center" | 70 | |- | 317 | [[Kullanıcı:Plenumchamber~trwiktionary|Plenumchamber~trwiktionary]] | align="center" | 69 | |- | 318 | [[Kullanıcı:~2026-19042-98|~2026-19042-98]] | align="center" | 69 | |- | 319 | [[Kullanıcı:Rock on She|Rock on She]] | align="center" | 67 | |- | 320 | [[Kullanıcı:SLSerkan|SLSerkan]] | align="center" | 65 | |- | 321 | [[Kullanıcı:Karduelis|Karduelis]] | align="center" | 65 | |- | 322 | [[Kullanıcı:Zontollektuel|Zontollektuel]] | align="center" | 65 | |- | 323 | [[Kullanıcı:Evrenkoruyan|Evrenkoruyan]] | align="center" | 64 | |- | 324 | [[Kullanıcı:Adanalınacak|Adanalınacak]] | align="center" | 64 | |- | 325 | [[Kullanıcı:Hapşu|Hapşu]] | align="center" | 63 | |- | 326 | [[Kullanıcı:Dixtosa|Dixtosa]] | align="center" | 63 | |- | 327 | [[Kullanıcı:El.wikt.user|El.wikt.user]] | align="center" | 62 | |- | 328 | [[Kullanıcı:SL özge üstün|SL özge üstün]] | align="center" | 62 | |- | 329 | [[Kullanıcı:Sal hasanilhan|Sal hasanilhan]] | align="center" | 61 | |- | 330 | [[Kullanıcı:Prenses|Prenses]] | align="center" | 60 | |- | 331 | [[Kullanıcı:Tofeiku|Tofeiku]] | align="center" | 60 | |- | 332 | [[Kullanıcı:Sihl Murat|Sihl Murat]] | align="center" | 59 | |- | 333 | [[Kullanıcı:Renamed user 1e23409a06e0b7922c2dfc98dde51974|Renamed user 1e23409a06e0b7922c2dfc98dde51974]] | align="center" | 59 | |- | 334 | [[Kullanıcı:Arnavutsezgin|Arnavutsezgin]] | align="center" | 58 | |- | 335 | [[Kullanıcı:Erkan Yilmaz|Erkan Yilmaz]] | align="center" | 58 | |- | 336 | [[Kullanıcı:Galibibülent|Galibibülent]] | align="center" | 57 | |- | 337 | [[Kullanıcı:S.orsev.dilek soydan|S.orsev.dilek soydan]] | align="center" | 57 | |- | 338 | [[Kullanıcı:Stml 183|Stml 183]] | align="center" | 57 | |- | 339 | [[Kullanıcı:Buzulkuşu|Buzulkuşu]] | align="center" | 57 | |- | 340 | [[Kullanıcı:Spanier|Spanier]] | align="center" | 57 | |- | 341 | [[Kullanıcı:LejyonerZaza|LejyonerZaza]] | align="center" | 57 | |- | 342 | [[Kullanıcı:SAL 329|SAL 329]] | align="center" | 56 | |- | 343 | [[Kullanıcı:S htk 428|S htk 428]] | align="center" | 56 | |- | 344 | [[Kullanıcı:Stml 161|Stml 161]] | align="center" | 56 | |- | 345 | [[Kullanıcı:HASAN ÖZGÜREN EML|HASAN ÖZGÜREN EML]] | align="center" | 56 | |- | 346 | [[Kullanıcı:SLesra|SLesra]] | align="center" | 56 | |- | 347 | [[Kullanıcı:SLhalit540|SLhalit540]] | align="center" | 56 | |- | 348 | [[Kullanıcı:Stultiwikia|Stultiwikia]] | align="center" | 56 | |- | 349 | [[Kullanıcı:LisafBia|LisafBia]] | align="center" | 56 | |- | 350 | [[Kullanıcı:Quinlan83|Quinlan83]] | align="center" | 56 | |- | 351 | [[Kullanıcı:Şegirt|Şegirt]] | align="center" | 56 | |- | 352 | [[Kullanıcı:TAKASUGI Shinji|TAKASUGI Shinji]] | align="center" | 55 | |- | 353 | [[Kullanıcı:Sarvaturi~trwiktionary|Sarvaturi~trwiktionary]] | align="center" | 55 | |- | 354 | [[Kullanıcı:Ks.Tahd|Ks.Tahd]] | align="center" | 55 | |- | 355 | [[Kullanıcı:Darkhorn~trwiktionary|Darkhorn~trwiktionary]] | align="center" | 54 | |- | 356 | [[Kullanıcı:Anlztrk|Anlztrk]] | align="center" | 54 | |- | 357 | [[Kullanıcı:Samleml özgür aydın|Samleml özgür aydın]] | align="center" | 53 | |- | 358 | [[Kullanıcı:Elvire|Elvire]] | align="center" | 53 | |- | 359 | [[Kullanıcı:İmmortalance|İmmortalance]] | align="center" | 53 | |- | 360 | [[Kullanıcı:Tehonk|Tehonk]] | align="center" | 53 | |- | 361 | [[Kullanıcı:NotBot|NotBot]] | align="center" | 52 | |- | 362 | [[Kullanıcı:SHTK.ARZU|SHTK.ARZU]] | align="center" | 52 | |- | 363 | [[Kullanıcı:SLOĞUZHAN|SLOĞUZHAN]] | align="center" | 52 | |- | 364 | [[Kullanıcı:WikimediaNotifier|WikimediaNotifier]] | align="center" | 52 | |- | 365 | [[Kullanıcı:Gufosowa|Gufosowa]] | align="center" | 52 | |- | 366 | [[Kullanıcı:たまほめ|たまほめ]] | align="center" | 51 | |- | 367 | [[Kullanıcı:Tembelejderha|Tembelejderha]] | align="center" | 50 | |- | 368 | [[Kullanıcı:Kml Nurcan|Kml Nurcan]] | align="center" | 50 | |- | 369 | [[Kullanıcı:Elton|Elton]] | align="center" | 50 | |- | 370 | [[Kullanıcı:Ozgurhatic|Ozgurhatic]] | align="center" | 50 | |- | 371 | [[Kullanıcı:Tıbbiye|Tıbbiye]] | align="center" | 50 | |- | 372 | [[Kullanıcı:Sepkenki|Sepkenki]] | align="center" | 50 | |- | 373 | [[Kullanıcı:Świętokrzyskie3|Świętokrzyskie3]] | align="center" | 50 | |- | 374 | [[Kullanıcı:Stml 188|Stml 188]] | align="center" | 48 | |- | 375 | [[Kullanıcı:Maderibeyza|Maderibeyza]] | align="center" | 48 | |- | 376 | [[Kullanıcı:Müftülükarif|Müftülükarif]] | align="center" | 48 | |- | 377 | [[Kullanıcı:Spl908455|Spl908455]] | align="center" | 48 | |- | 378 | [[Kullanıcı:Gökçe Yörük|Gökçe Yörük]] | align="center" | 48 | |- | 379 | [[Kullanıcı:Greywolf28|Greywolf28]] | align="center" | 48 | |- | 380 | [[Kullanıcı:Sal ruveyda|Sal ruveyda]] | align="center" | 47 | |- | 381 | [[Kullanıcı:S htk 145|S htk 145]] | align="center" | 47 | |- | 382 | [[Kullanıcı:Cmrncntn|Cmrncntn]] | align="center" | 47 | |- | 383 | [[Kullanıcı:Anon0004|Anon0004]] | align="center" | 47 | |- | 384 | [[Kullanıcı:Doruk Salancı~trwiktionary|Doruk Salancı~trwiktionary]] | align="center" | 46 | |- | 385 | [[Kullanıcı:Seml abdullah ege|Seml abdullah ege]] | align="center" | 46 | |- | 386 | [[Kullanıcı:SACAK ALİFATİH|SACAK ALİFATİH]] | align="center" | 46 | |- | 387 | [[Kullanıcı:Adml polat çelik 156|Adml polat çelik 156]] | align="center" | 46 | |- | 388 | [[Kullanıcı:Luckas Blade|Luckas Blade]] | align="center" | 46 | |- | 389 | [[Kullanıcı:BurstPower|BurstPower]] | align="center" | 46 | |- | 390 | [[Kullanıcı:Lord Leatherface~trwiktionary|Lord Leatherface~trwiktionary]] | align="center" | 45 | |- | 391 | [[Kullanıcı:Mambet|Mambet]] | align="center" | 45 | |- | 392 | [[Kullanıcı:Öz Türkçe|Öz Türkçe]] | align="center" | 45 | |- | 393 | [[Kullanıcı:JAn Dudík|JAn Dudík]] | align="center" | 45 | |- | 394 | [[Kullanıcı:Billinghurst|Billinghurst]] | align="center" | 45 | |- | 395 | [[Kullanıcı:Praxidicae|Praxidicae]] | align="center" | 45 | |- | 396 | [[Kullanıcı:MathXplore|MathXplore]] | align="center" | 45 | |- | 397 | [[Kullanıcı:Etimoloji|Etimoloji]] | align="center" | 45 | |- | 398 | [[Kullanıcı:Zbutie3.14|Zbutie3.14]] | align="center" | 45 | |- | 399 | [[Kullanıcı:Brightt11|Brightt11]] | align="center" | 45 | |- | 400 | [[Kullanıcı:Sunshine23|Sunshine23]] | align="center" | 44 | |- | 401 | [[Kullanıcı:BD2412|BD2412]] | align="center" | 44 | |- | 402 | [[Kullanıcı:Wisar12|Wisar12]] | align="center" | 44 | |- | 403 | [[Kullanıcı:Turkittihadcemiyeti|Turkittihadcemiyeti]] | align="center" | 44 | |- | 404 | [[Kullanıcı:Hoo man|Hoo man]] | align="center" | 43 | |- | 405 | [[Kullanıcı:Ghybu|Ghybu]] | align="center" | 43 | |- | 406 | [[Kullanıcı:Zolgoyo|Zolgoyo]] | align="center" | 43 | |- | 407 | [[Kullanıcı:YBK4|YBK4]] | align="center" | 43 | |- | 408 | [[Kullanıcı:Aktas5561|Aktas5561]] | align="center" | 42 | |- | 409 | [[Kullanıcı:Wiki13|Wiki13]] | align="center" | 42 | |- | 410 | [[Kullanıcı:Alexander Mikhalenko|Alexander Mikhalenko]] | align="center" | 42 | |- | 411 | [[Kullanıcı:Orsev.i.o.ayşe kibaroğlu|Orsev.i.o.ayşe kibaroğlu]] | align="center" | 41 | |- | 412 | [[Kullanıcı:Stml 168|Stml 168]] | align="center" | 41 | |- | 413 | [[Kullanıcı:Sihl selim|Sihl selim]] | align="center" | 41 | |- | 414 | [[Kullanıcı:V. Doğan Günay~trwiktionary|V. Doğan Günay~trwiktionary]] | align="center" | 41 | |- | 415 | [[Kullanıcı:Filanca|Filanca]] | align="center" | 41 | |- | 416 | [[Kullanıcı:Nurettincerek|Nurettincerek]] | align="center" | 41 | |- | 417 | [[Kullanıcı:Umitduranist|Umitduranist]] | align="center" | 41 | |- | 418 | [[Kullanıcı:Wentayan|Wentayan]] | align="center" | 41 | |- | 419 | [[Kullanıcı:Abant12|Abant12]] | align="center" | 41 | |- | 420 | [[Kullanıcı:Ahmetcatalkaya|Ahmetcatalkaya]] | align="center" | 40 | |- | 421 | [[Kullanıcı:Ahulusi|Ahulusi]] | align="center" | 40 | |- | 422 | [[Kullanıcı:Savh|Savh]] | align="center" | 40 | |- | 423 | [[Kullanıcı:FocalPoint|FocalPoint]] | align="center" | 40 | |- | 424 | [[Kullanıcı:Bulgu|Bulgu]] | align="center" | 40 | |- | 425 | [[Kullanıcı:Dijan|Dijan]] | align="center" | 39 | |- | 426 | [[Kullanıcı:Saçak fatmamandirali|Saçak fatmamandirali]] | align="center" | 39 | |- | 427 | [[Kullanıcı:Yabancı|Yabancı]] | align="center" | 39 | |- | 428 | [[Kullanıcı:Cekli829|Cekli829]] | align="center" | 39 | |- | 429 | [[Kullanıcı:Vanished user Xorisdtbdfgonugyfs|Vanished user Xorisdtbdfgonugyfs]] | align="center" | 39 | |- | 430 | [[Kullanıcı:Mathonius|Mathonius]] | align="center" | 39 | |- | 431 | [[Kullanıcı:Vincent Vega|Vincent Vega]] | align="center" | 39 | |- | 432 | [[Kullanıcı:Samuele2002|Samuele2002]] | align="center" | 39 | |- | 433 | [[Kullanıcı:CanerDemirci281993|CanerDemirci281993]] | align="center" | 39 | |- | 434 | [[Kullanıcı:Eusbarbosa|Eusbarbosa]] | align="center" | 38 | |- | 435 | [[Kullanıcı:Açak osman|Açak osman]] | align="center" | 38 | |- | 436 | [[Kullanıcı:Araklı|Araklı]] | align="center" | 38 | |- | 437 | [[Kullanıcı:Oylm mat|Oylm mat]] | align="center" | 37 | |- | 438 | [[Kullanıcı:Yalhi|Yalhi]] | align="center" | 37 | |- | 439 | [[Kullanıcı:Admlmuhammet alican|Admlmuhammet alican]] | align="center" | 37 | |- | 440 | [[Kullanıcı:SLHakan|SLHakan]] | align="center" | 37 | |- | 441 | [[Kullanıcı:JackPotte|JackPotte]] | align="center" | 37 | |- | 442 | [[Kullanıcı:Onkaimeon|Onkaimeon]] | align="center" | 37 | |- | 443 | [[Kullanıcı:Vanished user 127237|Vanished user 127237]] | align="center" | 37 | |- | 444 | [[Kullanıcı:Aabdullayev851|Aabdullayev851]] | align="center" | 37 | |- | 445 | [[Kullanıcı:Wooze|Wooze]] | align="center" | 37 | |- | 446 | [[Kullanıcı:Intolerable situation|Intolerable situation]] | align="center" | 37 | |- | 447 | [[Kullanıcı:Tapio Toola|Tapio Toola]] | align="center" | 37 | |- | 448 | [[Kullanıcı:Ykio emelaydın|Ykio emelaydın]] | align="center" | 36 | |- | 449 | [[Kullanıcı:S htk 612|S htk 612]] | align="center" | 36 | |- | 450 | [[Kullanıcı:Flyax|Flyax]] | align="center" | 36 | |- | 451 | [[Kullanıcı:Manco Capac|Manco Capac]] | align="center" | 36 | |- | 452 | [[Kullanıcı:Joseph|Joseph]] | align="center" | 36 | |- | 453 | [[Kullanıcı:Zuzumaykut|Zuzumaykut]] | align="center" | 36 | |- | 454 | [[Kullanıcı:Goktr001|Goktr001]] | align="center" | 36 | |- | 455 | [[Kullanıcı:Rxy|Rxy]] | align="center" | 36 | |- | 456 | [[Kullanıcı:MPF|MPF]] | align="center" | 36 | |- | 457 | [[Kullanıcı:Alperen|Alperen]] | align="center" | 35 | |- | 458 | [[Kullanıcı:S htk 186|S htk 186]] | align="center" | 35 | |- | 459 | [[Kullanıcı:Yonca573|Yonca573]] | align="center" | 35 | |- | 460 | [[Kullanıcı:Pinar|Pinar]] | align="center" | 34 | |- | 461 | [[Kullanıcı:Mardetanha|Mardetanha]] | align="center" | 34 | |- | 462 | [[Kullanıcı:Nanahuatl|Nanahuatl]] | align="center" | 34 | |- | 463 | [[Kullanıcı:Yoldasso|Yoldasso]] | align="center" | 34 | |- | 464 | [[Kullanıcı:Java2002|Java2002]] | align="center" | 34 | |- | 465 | [[Kullanıcı:Hasley|Hasley]] | align="center" | 34 | |- | 466 | [[Kullanıcı:Recep Arslanbaş|Recep Arslanbaş]] | align="center" | 34 | |- | 467 | [[Kullanıcı:TugbekOlek|TugbekOlek]] | align="center" | 33 | |- | 468 | [[Kullanıcı:Vese~trwiktionary|Vese~trwiktionary]] | align="center" | 33 | |- | 469 | [[Kullanıcı:Ozyurekli|Ozyurekli]] | align="center" | 33 | |- | 470 | [[Kullanıcı:EsenBoga|EsenBoga]] | align="center" | 33 | |- | 471 | [[Kullanıcı:Golgelerim|Golgelerim]] | align="center" | 33 | |- | 472 | [[Kullanıcı:Sanya3|Sanya3]] | align="center" | 33 | |- | 473 | [[Kullanıcı:Kırmızı renk|Kırmızı renk]] | align="center" | 33 | |- | 474 | [[Kullanıcı:Stml 153|Stml 153]] | align="center" | 32 | |- | 475 | [[Kullanıcı:SLsoner|SLsoner]] | align="center" | 32 | |- | 476 | [[Kullanıcı:KoreanQuoter|KoreanQuoter]] | align="center" | 32 | |- | 477 | [[Kullanıcı:Sungurer|Sungurer]] | align="center" | 32 | |- | 478 | [[Kullanıcı:HestNewDes|HestNewDes]] | align="center" | 32 | |- | 479 | [[Kullanıcı:Sml-hanife1|Sml-hanife1]] | align="center" | 31 | |- | 480 | [[Kullanıcı:SAL 417|SAL 417]] | align="center" | 31 | |- | 481 | [[Kullanıcı:MercanX|MercanX]] | align="center" | 31 | |- | 482 | [[Kullanıcı:Kokoloc|Kokoloc]] | align="center" | 31 | |- | 483 | [[Kullanıcı:Köksarı|Köksarı]] | align="center" | 30 | |- | 484 | [[Kullanıcı:Akkiz|Akkiz]] | align="center" | 30 | |- | 485 | [[Kullanıcı:Mclovinx|Mclovinx]] | align="center" | 30 | |- | 486 | [[Kullanıcı:Talha1481|Talha1481]] | align="center" | 30 | |- | 487 | [[Kullanıcı:Kumanof|Kumanof]] | align="center" | 30 | |- | 488 | [[Kullanıcı:Tenperver|Tenperver]] | align="center" | 30 | |- | 489 | [[Kullanıcı:~2026-22795-35|~2026-22795-35]] | align="center" | 30 | |- | 490 | [[Kullanıcı:Sı 643|Sı 643]] | align="center" | 29 | |- | 491 | [[Kullanıcı:SAMLEML MURAT ÇEBİ|SAMLEML MURAT ÇEBİ]] | align="center" | 29 | |- | 492 | [[Kullanıcı:Kmlblg318|Kmlblg318]] | align="center" | 29 | |- | 493 | [[Kullanıcı:Merube 89|Merube 89]] | align="center" | 29 | |- | 494 | [[Kullanıcı:Hgav|Hgav]] | align="center" | 29 | |- | 495 | [[Kullanıcı:Alsace38|Alsace38]] | align="center" | 29 | |- | 496 | [[Kullanıcı:Dilşo Kardî|Dilşo Kardî]] | align="center" | 29 | |- | 497 | [[Kullanıcı:Ulukan|Ulukan]] | align="center" | 29 | |- | 498 | [[Kullanıcı:Kaan2121|Kaan2121]] | align="center" | 29 | |- | 499 | [[Kullanıcı:Selahattin ilhan|Selahattin ilhan]] | align="center" | 29 | |- | 500 | [[Kullanıcı:Zartatue|Zartatue]] | align="center" | 29 | |} </center> kqvc5kc410469oopjleymsynwqmr4hi бездетность 0 539966 5669928 4024567 2026-06-23T08:07:32Z MustafaCavlak 59368 /* Rusça */ 5669928 wikitext text/x-wiki ==Rusça== ===Köken=== {{ek|dil=ru|безде́тный|ость}} ===Ad=== {{başlık başı|ru|ad|baş=безде́тность|c=d}} # [[çocuksuzluk]] ====Çekimleme==== {{ru-ad-3da|безде́тность}} 7eygytyjoyhntcgztjweblm0bl8nyst Մուրադյան 0 641566 5669820 4591614 2026-06-22T17:15:36Z MustafaCavlak 59368 /* Ermenice */ 5669820 wikitext text/x-wiki ==Ermenice== {{vikipedi|dil=hy}} ===Köken=== {{ek|dil=hy|Մուրադ|յան}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|soyadı}} sj1c1wmcjuyvo0k0i602q2b5xsifkwx magnaient 0 1131745 5669905 5131504 2026-06-22T21:33:08Z Turgut46 26665 /* Fransızca */ Test. 5669905 wikitext text/x-wiki == Fransızca == ===Eylem=== {{başlık başı|fr|çekimli eylem}} # {{fr-er-çekimi}} okfo03kga04w7hbz6qppldygp4ax7ta ατσαλάκωτος 0 1302656 5669783 5355268 2026-06-22T13:34:31Z MustafaCavlak 59368 /* Ön ad */ 5669783 wikitext text/x-wiki ==Yunanca== ===Ön ad=== {{el-ön ad}} # buruşmamış, buruşturulmamış ====Çekimleme==== {{el-çekim-öa|çekim=ος-η-ο|kök=ατσαλάκωτ}} 45b6ciby9poay0czhhbg59b3lz8yonc ατσούγκριστος 0 1302665 5669782 5355277 2026-06-22T13:32:35Z MustafaCavlak 59368 /* Ön ad */ 5669782 wikitext text/x-wiki ==Yunanca== ===Ön ad=== {{el-ön ad}} # tokuşturulmamış ====Çekimleme==== {{el-çekim-öa|çekim=ος-η-ο|kök=ατσούγκριστ}} 4oux8f2nqg16w57nvyxyu9so97ii6hy διάδοχος 0 1303292 5669771 5355905 2026-06-22T13:15:38Z MustafaCavlak 59368 /* Ön ad */ 5669771 wikitext text/x-wiki ==Yunanca== ===Ön ad=== {{el-ön ad}} # [[ardıl]] ====Çekimleme==== {{el-çekim-öa|çekim=ος-η-ο|kök=διάδοχ}} a9jbh4kj1klqf3vji0x9wbq3kqprbk4 ελευθερόβουλος 0 1303675 5669785 5356289 2026-06-22T13:38:22Z MustafaCavlak 59368 /* Ön ad */ 5669785 wikitext text/x-wiki ==Yunanca== ===Ön ad=== {{el-ön ad}} # [[özgür]] [[iradeli]] ====Çekimleme==== {{el-çekim-öa|çekim=ος-η-ο|kök=ελευθερόβουλ}} s44uwjk68v2vq58v3e6e077fchkz241 κατάφυτος 0 1304612 5669775 5357226 2026-06-22T13:23:39Z MustafaCavlak 59368 /* Ön ad */ 5669775 wikitext text/x-wiki ==Yunanca== ===Ön ad=== {{el-ön ad}} # [[yeşillik]]le dolu, [[bitki]]yle dolu (yer); [[ağaçlık]] ====Çekimleme==== {{el-çekim-öa|çekim=ος-η-ο|kök=κατάφυτ}} mmalxkdlcngc5xgx477ovfv14mricgt ολόλαμπρος 0 1305534 5669767 5358148 2026-06-22T13:13:27Z MustafaCavlak 59368 /* Ön ad */ 5669767 wikitext text/x-wiki ==Yunanca== ===Ön ad=== {{el-ön ad}} # [[apaydın]] ====Çekimleme==== {{el-çekim-öa|çekim=ος-η-ο|kök=ολόλαμπρ}} rcu3hjuy8wnuis2gdniln6ukc1p6mh0 ολοφάνερος 0 1305556 5669769 5358170 2026-06-22T13:14:17Z MustafaCavlak 59368 /* Ön ad */ 5669769 wikitext text/x-wiki ==Yunanca== ===Ön ad=== {{el-ön ad}} # [[apaçık]] ====Çekimleme==== {{el-çekim-öa|çekim=ος-η-ο|kök=ολοφάνερ}} 4cbtmj1yvckmxrbhvnti4642zrolfky ολόφωτος 0 1305557 5669768 5358171 2026-06-22T13:13:49Z MustafaCavlak 59368 5669768 wikitext text/x-wiki ==Yunanca== ===Ön ad=== {{el-ön ad}} # [[apaydın]] ====Çekimleme==== {{el-çekim-öa|çekim=ος-η-ο|kök=ολόφωτ}} 23ggq0zhc5w79c3a50cyabmrb49lbzl garantiulo 0 1589089 5669758 2026-06-22T13:00:50Z MustafaCavlak 59368 Yeni sayfa : ==Esperanto dili== ===Ad=== {{eo-ad}} # [[rehine]] ===Ek okumalar=== * {{R:eo:PIV 2020}} 5669758 wikitext text/x-wiki ==Esperanto dili== ===Ad=== {{eo-ad}} # [[rehine]] ===Ek okumalar=== * {{R:eo:PIV 2020}} j343reg9vfy7varukdew1rmie1fzj4w όμηρος 0 1589090 5669759 2026-06-22T13:03:50Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|e|c2=d|όμηροι}} # [[rehine]] 5669759 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|e|c2=d|όμηροι}} # [[rehine]] 4n67px70d7fvy6evnpdql2026dttgrv τήξη 0 1589091 5669760 2026-06-22T13:05:11Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|d}} # [[erime]] 5669760 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|d}} # [[erime]] aft0iihsjuab6d63rnuxwcorzjjtqr7 τετράδα 0 1589092 5669761 2026-06-22T13:07:38Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|d}} # [[dörtlü]] 5669761 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|d}} # [[dörtlü]] 2acbds2z6dogyuynz9my5e601vhggzx τζελατίνα 0 1589093 5669762 2026-06-22T13:09:18Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|d}} # {{farklı|dil=el|ζελατίνα}} 5669762 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|d}} # {{farklı|dil=el|ζελατίνα}} 5ny8f207z1eiq8hxju4spzz8zk19h6h ζελατίνα 0 1589094 5669763 2026-06-22T13:09:36Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|d}} # [[jelatin]] 5669763 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|d}} # [[jelatin]] 8504r5n5a0vtm49p9k04fkkurjblg8h αντίσωμα 0 1589095 5669764 2026-06-22T13:11:21Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|n}} # [[antikor]] 5669764 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|n}} # [[antikor]] j214rc7mowzno7byaqm16rjkb6kuvd1 ερευνητής 0 1589096 5669765 2026-06-22T13:12:10Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|e}} # [[araştırmacı]] 5669765 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|e}} # [[araştırmacı]] eo2ar2rwqheimwo70zokom01eqkisrn ερευνήτρια 0 1589097 5669766 2026-06-22T13:12:57Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|d}} # {{dişil karşılığı|el|ερευνητής}} 5669766 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|d}} # {{dişil karşılığı|el|ερευνητής}} 43h2h15elfbn9ra5kmi3gbzy8yb1fc2 βλακεία 0 1589098 5669770 2026-06-22T13:14:42Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|d}} # [[aptallık]] 5669770 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|d}} # [[aptallık]] ez9m8d0pe609uula0rpjy1yh3yyz4y9 κυψέλη 0 1589099 5669772 2026-06-22T13:16:26Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|d}} # [[arı kovanı]] 5669772 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|d}} # [[arı kovanı]] 98uoe3vfkb55h68m4admhw7dvhb14pn αλισίβα 0 1589100 5669773 2026-06-22T13:17:59Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|d}} # [[Arap sabunu]] 5669773 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|d}} # [[Arap sabunu]] bic77naab5r18i3i6ao4yj8ponfz4p8 5669774 5669773 2026-06-22T13:20:30Z MustafaCavlak 59368 /* Ad */ 5669774 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|d}} # [[kül suyu]] re7hscpukzbxgbyqqlss4l9mryswyul ψιθύρισμα 0 1589101 5669776 2026-06-22T13:25:02Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|n}} # [[fısıldama]], [[fısıltı]] 5669776 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|n}} # [[fısıldama]], [[fısıltı]] 8wkhx2ii7aser5kb5h3ftzwrxsnsoip φωτόμετρο 0 1589102 5669777 2026-06-22T13:26:04Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|n}} # [[ışıkölçer]] 5669777 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|n}} # [[ışıkölçer]] 7zhh03u8rebgy7qkzmwdcyb3gx4612c χάπι 0 1589103 5669778 2026-06-22T13:26:37Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|n}} # [[hap]] 5669778 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|n}} # [[hap]] gioyqvu3a9tsa76tni0yhaerexjba3d θύσανος 0 1589104 5669779 2026-06-22T13:27:21Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|e}} # [[püskül]] 5669779 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|e}} # [[püskül]] 7mz5dn1hfofk2e1tlowv3nhaq51jl7d παραβλέπω 0 1589105 5669780 2026-06-22T13:28:28Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Eylem=== {{el-eylem}} # [[görmezlikten gelmek]] 5669780 wikitext text/x-wiki ==Yunanca== ===Eylem=== {{el-eylem}} # [[görmezlikten gelmek]] fnm07i1ysv82niy0utlxkhuj4wveejo τσουγκρίζω 0 1589106 5669781 2026-06-22T13:31:53Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Eylem=== {{el-eylem}} # [[tokuşturmak]] 5669781 wikitext text/x-wiki ==Yunanca== ===Eylem=== {{el-eylem}} # [[tokuşturmak]] 9oc3b56m68zarnf6511h8yapmjixy5o τσαλακωμένος 0 1589107 5669784 2026-06-22T13:35:58Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ortaç=== {{el-ortaç}} # {{el-ortaç çekimi|τσαλακώνω}} 5669784 wikitext text/x-wiki ==Yunanca== ===Ortaç=== {{el-ortaç}} # {{el-ortaç çekimi|τσαλακώνω}} bsaeluu8h95uu0av97ngn0i2v8zatkl ιωδισμός 0 1589108 5669786 2026-06-22T13:41:42Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|e}} # [[iyot]] [[zehirlenme]]si 5669786 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|e}} # [[iyot]] [[zehirlenme]]si 8drjo3a388ydzm84g5x5ruxe3o0jvm4 ιώδιο 0 1589109 5669787 2026-06-22T13:43:26Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|n}} # [[iyot]] 5669787 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|n}} # [[iyot]] ea6m214ep2bomdoojuolzkddzi9gtcd ιωδιούχος 0 1589110 5669788 2026-06-22T13:44:53Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ön ad=== {{el-ön ad}} # [[iyotlu]] 5669788 wikitext text/x-wiki ==Yunanca== ===Ön ad=== {{el-ön ad}} # [[iyotlu]] e3j1slq3ppmqljgszmuzcdp6fsx1jz8 ατομικός αριθμός 0 1589111 5669789 2026-06-22T13:46:43Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|e}} # [[atom numarası]] 5669789 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|e}} # [[atom numarası]] scw2ka5tmyhl61hzxykw5hrxes7dobu περπάτημα 0 1589112 5669790 2026-06-22T13:57:25Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|n}} # [[yürüme]], [[yürüyüş]] 5669790 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|n}} # [[yürüme]], [[yürüyüş]] 28hzcql7vdebh0dfz27r4cu2x5pm7n4 βάδισμα 0 1589113 5669791 2026-06-22T13:58:46Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|n}} # [[yürüyüş]] 5669791 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|n}} # [[yürüyüş]] 3ya1pk99lkyvdmnwrkpmch5eh0znah4 σπουδή 0 1589114 5669792 2026-06-22T14:00:05Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|d}} # [[çalışma]] 5669792 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|d}} # [[çalışma]] 65wocg34xln9027vhosmufz4f3iypul σπουδαστήριο 0 1589115 5669793 2026-06-22T14:00:53Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|n}} # [[çalışma odası]] 5669793 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|n}} # [[çalışma odası]] nitalu3htv8w4m5hejevlda5bkuewcw 5669794 5669793 2026-06-22T14:01:20Z MustafaCavlak 59368 /* Ad */ 5669794 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|n}} # [[çalışma odası]], [[etüt odası]] khsp846ogxrwe1jbq6i816q5u04avf9 εγκλείω 0 1589116 5669795 2026-06-22T14:02:54Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Eylem=== {{el-eylem}} # [[kapatmak]], [[hapsetmek]] 5669795 wikitext text/x-wiki ==Yunanca== ===Eylem=== {{el-eylem}} # [[kapatmak]], [[hapsetmek]] geea60pouc0t9fwpjfl70pclvm9k3ga περιορίζω 0 1589117 5669796 2026-06-22T14:04:06Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Eylem=== {{el-eylem}} # [[kısıtlamak]], [[sınırlamak]] 5669796 wikitext text/x-wiki ==Yunanca== ===Eylem=== {{el-eylem}} # [[kısıtlamak]], [[sınırlamak]] osjlibn9l5b7qyjsrq7l1g5p9v4kmkl έκθεση 0 1589118 5669797 2026-06-22T14:07:17Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Ad=== {{el-ad|d}} # [[sergi]], [[sergileme]], [[fuar]] # [[rapor]] 5669797 wikitext text/x-wiki ==Yunanca== ===Ad=== {{el-ad|d}} # [[sergi]], [[sergileme]], [[fuar]] # [[rapor]] mts8qpsd0sm5d7o0lmx5cmzg4333a1r ποικίλλω 0 1589119 5669798 2026-06-22T14:12:27Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Eylem=== {{el-eylem}} # {{t|dil=el|geçişli}} [[süslemek]], [[bezemek]] # {{t|dil=el|geçişli}} [[çeşitlendirmek]] # {{t|dil=el|geçişsiz}} [[çeşitlilik]] [[göstermek]], [[çeşitlenmek]] #: {{ux|el|Οι χαρακτήρες των ανθρώπων '''ποικίλλουν'''. Δεν είμαστε όλοι ίδιοι.|İnsanların karakterleri çeşitlilik gösterir. Hepimiz aynı değiliz.}} 5669798 wikitext text/x-wiki ==Yunanca== ===Eylem=== {{el-eylem}} # {{t|dil=el|geçişli}} [[süslemek]], [[bezemek]] # {{t|dil=el|geçişli}} [[çeşitlendirmek]] # {{t|dil=el|geçişsiz}} [[çeşitlilik]] [[göstermek]], [[çeşitlenmek]] #: {{ux|el|Οι χαρακτήρες των ανθρώπων '''ποικίλλουν'''. Δεν είμαστε όλοι ίδιοι.|İnsanların karakterleri çeşitlilik gösterir. Hepimiz aynı değiliz.}} b6nx14bgdhromoucw5y2erfsco8hbix konstipasyonizm 0 1589120 5669799 2026-06-22T14:16:59Z ~2026-36094-38 68142 Yeni sayfa : ==Türkçe== ===Ad=== {{tr-ad}} # bir süreci kasten geciktirme, engelleme veya kötüye kullanma uygulaması ([[Latince]] constipatio - engelleme, tıkama) ====Köken==== # {{k|dil=tr|fr}} {{ç|fr|constipationnisme}} ({{ek|dil=tr|konstipasyon|izm}}) 5669799 wikitext text/x-wiki ==Türkçe== ===Ad=== {{tr-ad}} # bir süreci kasten geciktirme, engelleme veya kötüye kullanma uygulaması ([[Latince]] constipatio - engelleme, tıkama) ====Köken==== # {{k|dil=tr|fr}} {{ç|fr|constipationnisme}} ({{ek|dil=tr|konstipasyon|izm}}) gxwqxxeieh3ykd1rmbejnpp6d58ut9q konflagrasyonizm 0 1589121 5669800 2026-06-22T14:24:01Z ~2026-36094-38 68142 Yeni sayfa : ==Türkçe== ===Ad=== {{tr-ad}} # kötü amaçlarla herhangi bir malı, binayı veya araziyi ateşe verme, kundakçılık ([[Latince]] conflagratio - büyük yangın) ====Köken==== # {{k|dil=tr|fr}} {{ç|fr|conflagrationnisme}} ({{ek|dil=tr|konflagrasyon|izm}}) 5669800 wikitext text/x-wiki ==Türkçe== ===Ad=== {{tr-ad}} # kötü amaçlarla herhangi bir malı, binayı veya araziyi ateşe verme, kundakçılık ([[Latince]] conflagratio - büyük yangın) ====Köken==== # {{k|dil=tr|fr}} {{ç|fr|conflagrationnisme}} ({{ek|dil=tr|konflagrasyon|izm}}) 47xdq2ikl3op00ifo9fcgdnexnxopmb konsorsiyonizm 0 1589122 5669801 2026-06-22T14:35:06Z ~2026-36094-38 68142 Yeni sayfa : ==Türkçe== ===Ad=== {{tr-ad}} # düşmanla işbirliği ([[Latince]] consortio - gizli anlaşma) ====Köken==== # {{k|dil=tr|fr}} {{ç|fr|consortionnisme}} ({{ek|dil=tr|konsorsiyon|izm}}) 5669801 wikitext text/x-wiki ==Türkçe== ===Ad=== {{tr-ad}} # düşmanla işbirliği ([[Latince]] consortio - gizli anlaşma) ====Köken==== # {{k|dil=tr|fr}} {{ç|fr|consortionnisme}} ({{ek|dil=tr|konsorsiyon|izm}}) eaqr7lnf2g3gzzvwa9twwy6uwcyrvyc υποβάλλω 0 1589123 5669802 2026-06-22T15:32:35Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Eylem=== {{el-eylem}} # [[sunmak]], [[arz etmek]] # [[maruz bırakmak]] # [[aklına sokmak]], [[zorlamak]], [[empoze etmek]] 5669802 wikitext text/x-wiki ==Yunanca== ===Eylem=== {{el-eylem}} # [[sunmak]], [[arz etmek]] # [[maruz bırakmak]] # [[aklına sokmak]], [[zorlamak]], [[empoze etmek]] 422yjpdddbd43tqzeh0ovlsgjzm8mv0 υποβάλει 0 1589124 5669803 2026-06-22T15:33:15Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Eylem=== {{başlık başı|el|çekimli eylem}} # {{çekim|dil=el|υποβάλλω||act|bitimsiz|;|3t|bağımlı}} 5669803 wikitext text/x-wiki ==Yunanca== ===Eylem=== {{başlık başı|el|çekimli eylem}} # {{çekim|dil=el|υποβάλλω||act|bitimsiz|;|3t|bağımlı}} 0gop4591t4b3dxkmf0sgdghp69i9o6o υποδουλώσει 0 1589125 5669804 2026-06-22T15:34:12Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Eylem=== {{başlık başı|el|çekimli eylem}} # {{çekim|dil=el|υποδουλώνω||act|bitimsiz|;|3t|bağımlı}} 5669804 wikitext text/x-wiki ==Yunanca== ===Eylem=== {{başlık başı|el|çekimli eylem}} # {{çekim|dil=el|υποδουλώνω||act|bitimsiz|;|3t|bağımlı}} 31495fij0y2l6blyf82ry21pv1k5tp0 νυστάξει 0 1589126 5669805 2026-06-22T15:35:36Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Eylem=== {{başlık başı|el|çekimli eylem}} # {{çekim|dil=el|νυστάζω||act|bitimsiz|;|3t|bağımlı}} 5669805 wikitext text/x-wiki ==Yunanca== ===Eylem=== {{başlık başı|el|çekimli eylem}} # {{çekim|dil=el|νυστάζω||act|bitimsiz|;|3t|bağımlı}} 2trvwkgf4ojfkdp8kp78ldwb1dcz4lb προχωρήσει 0 1589127 5669806 2026-06-22T16:02:18Z MustafaCavlak 59368 Yeni sayfa : ==Yunanca== ===Eylem=== {{başlık başı|el|çekimli eylem}} # {{çekim|dil=el|[[προχωράω]] / [[προχωρώ]]||act|bitimsiz|;|3t|bağımlı}} 5669806 wikitext text/x-wiki ==Yunanca== ===Eylem=== {{başlık başı|el|çekimli eylem}} # {{çekim|dil=el|[[προχωράω]] / [[προχωρώ]]||act|bitimsiz|;|3t|bağımlı}} 5utnfuzp6n4lkva5h705jlntl4f1q9w կուղբ 0 1589128 5669807 2026-06-22T16:22:47Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[kunduz]] 5669807 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[kunduz]] dnibffrqsiyohdiwvkddpgkm1p4asfn դրամ 0 1589129 5669808 2026-06-22T16:27:45Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # {{t|dil=hy|para}} [[dram]] 5669808 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # {{t|dil=hy|para}} [[dram]] eq70bbkkbmo70qdl8h84o7a4hxtng5a Kategori:Ermenicede para 14 1589130 5669809 2026-06-22T16:29:16Z MustafaCavlak 59368 Yeni sayfa : {{kategori konu|dil=hy|konu=para}} 5669809 wikitext text/x-wiki {{kategori konu|dil=hy|konu=para}} 8296hffw05c9czqvejqs9h5f1iuplq3 դրամա 0 1589131 5669810 2026-06-22T16:30:33Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[drama]] 5669810 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[drama]] 4hjnqvgtchzesogsiqhwgbleh7acc5y երևանյան 0 1589132 5669811 2026-06-22T16:42:12Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ön ad=== {{başlık başı|hy|ön ad}} # [[Erivan]] ile ilgili 5669811 wikitext text/x-wiki ==Ermenice== ===Ön ad=== {{başlık başı|hy|ön ad}} # [[Erivan]] ile ilgili mxcqxk8vvgeebpxknil2sbf4st5zxt4 ահ 0 1589133 5669812 2026-06-22T16:44:18Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[korku]], [[dehşet]] 5669812 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[korku]], [[dehşet]] d9r42jfla2zeltju2w7bp7mkwa5xom7 սարսափ 0 1589134 5669813 2026-06-22T16:45:43Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[korku]], [[dehşet]] 5669813 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[korku]], [[dehşet]] d9r42jfla2zeltju2w7bp7mkwa5xom7 երկյուղ 0 1589135 5669814 2026-06-22T16:49:03Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[korku]], [[dehşet]] 5669814 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[korku]], [[dehşet]] d9r42jfla2zeltju2w7bp7mkwa5xom7 երկյուղել 0 1589136 5669815 2026-06-22T16:52:08Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Eylem=== {{hy-eylem}} # [[korkmak]], [[ürkmek]] 5669815 wikitext text/x-wiki ==Ermenice== ===Eylem=== {{hy-eylem}} # [[korkmak]], [[ürkmek]] qrdsxwxakf1az3pwld7cxeboqoop4ru Վիքիբառարան 0 1589137 5669816 2026-06-22T16:56:29Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Özel ad=== {{hy-özel ad}} # [[Vikisözlük]] 5669816 wikitext text/x-wiki ==Ermenice== ===Özel ad=== {{hy-özel ad}} # [[Vikisözlük]] nyndv61u1ub4es5p61n14na45tsa9x0 բառարանագիր 0 1589138 5669817 2026-06-22T16:59:01Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[sözlükçü]], [[leksikograf]] 5669817 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[sözlükçü]], [[leksikograf]] 6yj575akljsh0nlks1ummfju2zwcqt3 բառարանագրություն 0 1589139 5669818 2026-06-22T17:00:08Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[leksikografi]], [[sözlükçülük]] 5669818 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[leksikografi]], [[sözlükçülük]] s69yy12xiwmdpx6la681d6fhxt38qx5 ճպուռ 0 1589140 5669819 2026-06-22T17:12:24Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # {{t|dil=hy|böcekler}} [[yusufçuk]] 5669819 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # {{t|dil=hy|böcekler}} [[yusufçuk]] 8k7zsfe98j73wkrgi99h9h2lliz1un9 Խաչատուրյան 0 1589141 5669821 2026-06-22T17:18:27Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|soyadı}} 5669821 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|soyadı}} 3ed14gmtlu670v5z9gfb2bdfh552qbc Ղալումյան 0 1589142 5669822 2026-06-22T17:21:41Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|soyadı}} 5669822 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|soyadı}} 3ed14gmtlu670v5z9gfb2bdfh552qbc Դավիդյան 0 1589143 5669823 2026-06-22T17:28:36Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|soyadı}} 5669823 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|soyadı}} 3ed14gmtlu670v5z9gfb2bdfh552qbc Դաշյան 0 1589144 5669824 2026-06-22T17:29:47Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|soyadı}} 5669824 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|soyadı}} 3ed14gmtlu670v5z9gfb2bdfh552qbc Շաղոյան 0 1589145 5669825 2026-06-22T17:30:42Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|soyadı}} 5669825 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|soyadı}} 3ed14gmtlu670v5z9gfb2bdfh552qbc Նիկոլ 0 1589146 5669826 2026-06-22T17:38:47Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} 5669826 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} hur9nd2gh9hk6v3aewh25v3svdcfwod Գառնիկի 0 1589147 5669827 2026-06-22T17:39:57Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} 5669827 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} hur9nd2gh9hk6v3aewh25v3svdcfwod Արմեն 0 1589148 5669828 2026-06-22T17:40:29Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} 5669828 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} hur9nd2gh9hk6v3aewh25v3svdcfwod Ռոբերտ 0 1589149 5669829 2026-06-22T17:41:19Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} 5669829 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} hur9nd2gh9hk6v3aewh25v3svdcfwod Սերժ 0 1589150 5669830 2026-06-22T17:41:57Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} 5669830 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} hur9nd2gh9hk6v3aewh25v3svdcfwod Ազատի 0 1589151 5669831 2026-06-22T17:42:20Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} 5669831 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} hur9nd2gh9hk6v3aewh25v3svdcfwod Վովայի 0 1589152 5669832 2026-06-22T17:42:59Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} 5669832 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} hur9nd2gh9hk6v3aewh25v3svdcfwod Վլադիմիր 0 1589153 5669833 2026-06-22T17:43:55Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} 5669833 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} hur9nd2gh9hk6v3aewh25v3svdcfwod Աբազյան 0 1589154 5669834 2026-06-22T17:51:20Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} 5669834 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} hur9nd2gh9hk6v3aewh25v3svdcfwod Աբդիու 0 1589155 5669835 2026-06-22T17:57:04Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} 5669835 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} hur9nd2gh9hk6v3aewh25v3svdcfwod Աբդլմսեհ 0 1589156 5669836 2026-06-22T17:57:29Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} 5669836 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} hur9nd2gh9hk6v3aewh25v3svdcfwod Ահարոն 0 1589157 5669837 2026-06-22T17:58:18Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} 5669837 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} hur9nd2gh9hk6v3aewh25v3svdcfwod Անձրևես 0 1589158 5669838 2026-06-22T17:59:08Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} 5669838 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} hur9nd2gh9hk6v3aewh25v3svdcfwod Ասլիպարոն 0 1589159 5669839 2026-06-22T17:59:56Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} 5669839 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenisteniyor}} ===Özel ad=== {{hy-özel ad}} # {{özel ad|dil=hy|erkek adı}} hur9nd2gh9hk6v3aewh25v3svdcfwod խանութ 0 1589160 5669840 2026-06-22T18:22:27Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[mağaza]], [[dükkân]] 5669840 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[mağaza]], [[dükkân]] s7z65cnx8icrbktnqvuqzf7jp4oz7kq 5669841 5669840 2026-06-22T18:24:01Z MustafaCavlak 59368 /* Ad */ 5669841 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[mağaza]], [[dükkân]] ====Çekimleme==== {{hy-ad-ի-ներ}} 8h06wyw3if04ftsxr8qkz0mqelzvrnf խանութներ 0 1589161 5669842 2026-06-22T18:25:10Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||nom|ç}} 5669842 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||nom|ç}} jxqwawaybh9gm7xv3av43td0l88bqzk խանութը 0 1589162 5669843 2026-06-22T18:25:57Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||def|nom|t}} 5669843 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||def|nom|t}} lsl927emptcicxj3lhmdjj8sw0w623c խանութն 0 1589163 5669844 2026-06-22T18:26:12Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||def|nom|t}} 5669844 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||def|nom|t}} lsl927emptcicxj3lhmdjj8sw0w623c խանութները 0 1589164 5669845 2026-06-22T18:26:41Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||def|nom|ç}} 5669845 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||def|nom|ç}} 1axhjk0jnlwe4mbvqitjmhup9n6zik1 խանութներն 0 1589165 5669846 2026-06-22T18:26:57Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||def|nom|ç}} 5669846 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||def|nom|ç}} 1axhjk0jnlwe4mbvqitjmhup9n6zik1 խանութի 0 1589166 5669847 2026-06-22T18:27:12Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||dat|t}} 5669847 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||dat|t}} thjjsxjxvdqeagi6sde83ux3axo30we խանութների 0 1589167 5669848 2026-06-22T18:27:28Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||dat|ç}} 5669848 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||dat|ç}} 4nbhp8vddhy5ck0ghdzghldqwgvgaqi խանութին 0 1589168 5669849 2026-06-22T18:27:41Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||def|dat|t}} 5669849 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||def|dat|t}} j8mp7qltwzn747ue6hf9nqcgm27sea1 խանութներին 0 1589169 5669850 2026-06-22T18:27:55Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||def|dat|ç}} 5669850 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||def|dat|ç}} rj8yfcbbbski54l9i7mm07uco36ey9k խանութից 0 1589170 5669851 2026-06-22T18:28:09Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||abl|t}} 5669851 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||abl|t}} g9wz2nhkpr8x5eu411we16w1vy7i6ho խանութներից 0 1589171 5669852 2026-06-22T18:28:35Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||abl|ç}} 5669852 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||abl|ç}} 6d5628r6e3gsa5m5ngxe61tlebncic3 խանութով 0 1589172 5669853 2026-06-22T18:28:50Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||araç|t}} 5669853 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||araç|t}} 9boahh7mdyihbvvsua5j4l8tw81qlo1 խանութներով 0 1589173 5669854 2026-06-22T18:29:07Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||araç|ç}} 5669854 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||araç|ç}} g3isj9di0j5yesizp1g1kemsogowzo8 խանութում 0 1589174 5669855 2026-06-22T18:29:21Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||loc|t}} 5669855 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||loc|t}} m3rrkyfgex0mgnaaj84us9wxus0h1xf խանութներում 0 1589175 5669856 2026-06-22T18:29:38Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||loc|ç}} 5669856 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||loc|ç}} 5wr24l4va9piyf3r5ygt6w06fmszkt1 խանութս 0 1589176 5669857 2026-06-22T18:29:57Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|nom|t}} 5669857 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|nom|t}} 0l8ooccra86gmqrw7mmq62ftqddxjfo խանութներս 0 1589177 5669858 2026-06-22T18:30:09Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|nom|ç}} 5669858 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|nom|ç}} fujhb6fnehguk2twpnyw1tu5u73pglf խանութիս 0 1589178 5669859 2026-06-22T18:30:22Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|dat|t}} 5669859 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|dat|t}} sx0gp10ixxm8stpvofaep24hw55zovx խանութներիս 0 1589179 5669860 2026-06-22T18:30:38Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|dat|ç}} 5669860 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|dat|ç}} kyz2mhbnmtm7kzmnypaj1jan7epata2 խանութիցս 0 1589180 5669861 2026-06-22T18:30:52Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|abl|t}} 5669861 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|abl|t}} 0urimbosg8qbfkhu39cn9o9emg8i4gq խանութներիցս 0 1589181 5669862 2026-06-22T18:31:05Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|abl|ç}} 5669862 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|abl|ç}} mj17v69atodmlvmz4rgxkr4mlsxpuxy խանութովս 0 1589182 5669863 2026-06-22T18:31:22Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|araç|t}} 5669863 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|araç|t}} gpwagm6l0ub3f0hdo8unpryjmlglce0 խանութներովս 0 1589183 5669864 2026-06-22T18:31:36Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|araç|ç}} 5669864 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|araç|ç}} 65exkbn81jr15fw2ljbyf8a7g8xvolw խանութումս 0 1589184 5669865 2026-06-22T18:31:49Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|loc|t}} 5669865 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|loc|t}} hc7e6pj096btu5n30n3wo0cxms7ztpf խանութներումս 0 1589185 5669866 2026-06-22T18:32:11Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|loc|ç}} 5669866 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||1t|loc|ç}} 2ry87eusl9dpbr9ag2lvnml5rvlamie խանութդ 0 1589186 5669867 2026-06-22T18:32:30Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|nom|t}} 5669867 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|nom|t}} 767lk1qpjmhqp5sm9rx4s2vc5v3681h խանութներդ 0 1589187 5669868 2026-06-22T18:32:42Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|nom|ç}} 5669868 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|nom|ç}} altws2gbfmsfvx1bbfu9iw8hkgz8z66 խանութիդ 0 1589188 5669869 2026-06-22T18:32:57Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|dat|t}} 5669869 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|dat|t}} 5di352l4pravbzssbmkfyrljqrxv9je խանութներիդ 0 1589189 5669870 2026-06-22T18:33:10Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|dat|ç}} 5669870 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|dat|ç}} 65hgzg70kscixspmi53oip21i3blhfd խանութիցդ 0 1589190 5669871 2026-06-22T18:33:25Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|abl|t}} 5669871 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|abl|t}} c9dny6f0d6spgfct1z8adgrtrltjl5o խանութներիցդ 0 1589191 5669872 2026-06-22T18:33:40Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|abl|ç}} 5669872 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|abl|ç}} i9eaaeckgs7zz5a36dzfknx20peelvz խանութովդ 0 1589192 5669873 2026-06-22T18:33:53Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|araç|t}} 5669873 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|araç|t}} qacphxoc38uwaqfdhyc0ymuz965jjyi խանութներովդ 0 1589193 5669874 2026-06-22T18:34:09Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|araç|ç}} 5669874 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|araç|ç}} oszetni4y2g6z672dx05w3wfsf0eaay խանութումդ 0 1589194 5669875 2026-06-22T18:34:24Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|loc|t}} 5669875 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|loc|t}} 97tlgjoo664j4r826d2amr8ydkb0kvc խանութներումդ 0 1589195 5669876 2026-06-22T18:34:40Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|loc|ç}} 5669876 wikitext text/x-wiki ==Ermenice== ===Ad=== {{başlık başı|hy|çekimli ad}} # {{çekim|dil=hy|խանութ||2t|loc|ç}} oyezee2wucauuwg0t4f6vy4s7nvht5m մանրաթել 0 1589196 5669877 2026-06-22T18:40:35Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[lif]] 5669877 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[lif]] kdvpptd14ft5ngmicld2152xqpisrqw փոքր 0 1589197 5669878 2026-06-22T18:43:13Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ön ad=== {{hy-ön ad}} # [[küçük]] 5669878 wikitext text/x-wiki ==Ermenice== ===Ön ad=== {{hy-ön ad}} # [[küçük]] f8rppenu2t322m5a1egdsvy07m0uumt պորտալար 0 1589198 5669879 2026-06-22T18:56:18Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[göbek bağı]], [[kordon]] 5669879 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[göbek bağı]], [[kordon]] 30w1d6nc4e5ucnhgouf5xntyr0g7scv հարսնապար 0 1589199 5669880 2026-06-22T18:58:37Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[gelin]]in kendi düğününde yaptığı [[dans]]; gelin dansı 5669880 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[gelin]]in kendi düğününde yaptığı [[dans]]; gelin dansı kyiy188ki2gm7wsl69vmez55cdoss0a պարասրահ 0 1589200 5669881 2026-06-22T19:01:17Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[dans salonu]] 5669881 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[dans salonu]] 5d32j8dpw731xal83ia4abj1tcobv4z սրահ 0 1589201 5669882 2026-06-22T19:02:18Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[salon]] 5669882 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[salon]] 69uie1u73cx3zpwr12h3u7nuztqkmmt գահ 0 1589202 5669883 2026-06-22T19:03:21Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[taht]] 5669883 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[taht]] ey67y6oyxr8letywa0ff6zggxc6br7y ընթերցել 0 1589203 5669884 2026-06-22T19:05:46Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Eylem=== {{hy-eylem}} # {{t|dil=hy|resmî dil}} [[okumak]] #: {{eş anlamlılar|hy|կարդալ}} 5669884 wikitext text/x-wiki ==Ermenice== ===Eylem=== {{hy-eylem}} # {{t|dil=hy|resmî dil}} [[okumak]] #: {{eş anlamlılar|hy|կարդալ}} dl4mudvgwx9jyo9u2zm1xwjtocdmogt Kategori:Ermenice resmî dil 14 1589204 5669885 2026-06-22T19:06:17Z MustafaCavlak 59368 Yeni sayfa : {{kategori söz|dil=hy|söz=resmî dil}} 5669885 wikitext text/x-wiki {{kategori söz|dil=hy|söz=resmî dil}} 8ugv68xbaxwzcl6pufjpv1x0fsut83r վերընթերցել 0 1589205 5669886 2026-06-22T19:07:40Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Eylem=== {{hy-eylem}} # [[tekrar]] [[okumak]], [[yeniden]] okumak 5669886 wikitext text/x-wiki ==Ermenice== ===Eylem=== {{hy-eylem}} # [[tekrar]] [[okumak]], [[yeniden]] okumak ffaft0v3uh8u9fd6b9ynsf30s5algrp միջազգային 0 1589206 5669887 2026-06-22T19:09:31Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ön ad=== {{hy-ön ad}} # [[uluslararası]] 5669887 wikitext text/x-wiki ==Ermenice== ===Ön ad=== {{hy-ön ad}} # [[uluslararası]] 6jqo8rdmarlls4gs6nj0rxvc1xtpg5v գալակտիկական 0 1589207 5669888 2026-06-22T19:11:47Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ön ad=== {{başlık başı|hy|ön ad}} # [[galaksi]] ile ilgili 5669888 wikitext text/x-wiki ==Ermenice== ===Ön ad=== {{başlık başı|hy|ön ad}} # [[galaksi]] ile ilgili dok432ihcn7gufwynujy4kg4hzve35t գալակտիկա 0 1589208 5669889 2026-06-22T19:12:17Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[galaksi]] 5669889 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[galaksi]] 6nqpor2zwyl7ledimvb7sgd52oe45d9 արևելագետ 0 1589209 5669890 2026-06-22T19:15:08Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[oryantalist]] 5669890 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[oryantalist]] nntrwk1p76oru97odlo8f9pqun454mg արևելք 0 1589210 5669891 2026-06-22T19:15:43Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[doğu]] 5669891 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[doğu]] 6vnlyv45glkvviva2au956y7727d7co հյուսիս-արևելք 0 1589211 5669892 2026-06-22T19:16:21Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[kuzeydoğu]] 5669892 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[kuzeydoğu]] q0ljx2x3d9kc99dc3c5mrqigy4egsg0 հյուսիս 0 1589212 5669893 2026-06-22T19:17:02Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[kuzey]] 5669893 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[kuzey]] rs01gxoqfy1ami6e0e8fsmmttu0gozt հյուսիս-արևմուտք 0 1589213 5669894 2026-06-22T19:17:34Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[kuzeybatı]] 5669894 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[kuzeybatı]] q3sg2m2474bjnfxkpnei23vqohyt7x5 արևմուտք 0 1589214 5669895 2026-06-22T19:18:05Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[batı]] 5669895 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[batı]] iq8ue4g8r7payau99odj2jcwotb10q1 հարավ-արևմուտք 0 1589215 5669896 2026-06-22T19:18:31Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[güneybatı]] 5669896 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[güneybatı]] ei4w03vxsh0905ahsosuxpwvfpbbze9 հարավ 0 1589216 5669897 2026-06-22T19:18:58Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[güney]] 5669897 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[güney]] n8v59lzr7lnywudhosnqwq0irhjkeo9 հարավ-արևելք 0 1589217 5669898 2026-06-22T19:19:25Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[güneydoğu]] 5669898 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[güneydoğu]] m92wxl0wo7z051mwo71k1j71r74balo Կորեա 0 1589218 5669899 2026-06-22T19:20:21Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Özel ad=== {{hy-özel ad}} # [[Kore]] 5669899 wikitext text/x-wiki ==Ermenice== ===Özel ad=== {{hy-özel ad}} # [[Kore]] 2198efggmjib8khibme0g0fcaxykmlb Հյուսիսային Կորեա 0 1589219 5669900 2026-06-22T19:23:01Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== {{vikipedi|dil=hy}} ===Özel ad=== {{hy-özel ad|baş=[[հյուսիսային|Հյուսիսային]] [[Կորեա]]}} # {{t|dil=hy|ülkeler}} [[Kuzey Kore]] ====Çekimleme==== {{hy-ad-ի-ներ|çoğulsuz=evet}} 5669900 wikitext text/x-wiki ==Ermenice== {{vikipedi|dil=hy}} ===Özel ad=== {{hy-özel ad|baş=[[հյուսիսային|Հյուսիսային]] [[Կորեա]]}} # {{t|dil=hy|ülkeler}} [[Kuzey Kore]] ====Çekimleme==== {{hy-ad-ի-ներ|çoğulsuz=evet}} 8b1chi7jbx2y93706xsx92xhsc88wwr հյուսիսային 0 1589220 5669901 2026-06-22T19:24:50Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{kökenli|hy|xcl|հիւսիսային}}. ===Söyleniş=== * {{ses|dil=hy|LL-Q8785 (hye)-Veravi95-հյուսիսային.wav|(Batı Ermenicesi}} ===Ön ad=== {{hy-ön ad|GYB=հիւսիսային}} # [[kuzey]] ====Çekimleme==== {{hy-ad-ի-ներ|n=on}} 5669901 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenli|hy|xcl|հիւսիսային}}. ===Söyleniş=== * {{ses|dil=hy|LL-Q8785 (hye)-Veravi95-հյուսիսային.wav|(Batı Ermenicesi}} ===Ön ad=== {{hy-ön ad|GYB=հիւսիսային}} # [[kuzey]] ====Çekimleme==== {{hy-ad-ի-ներ|n=on}} 9ozlbe0dnt2ppo64xtrgb43z08r5e8t 5669902 5669901 2026-06-22T19:26:32Z MustafaCavlak 59368 /* Söyleniş */ 5669902 wikitext text/x-wiki ==Ermenice== ===Köken=== {{kökenli|hy|xcl|հիւսիսային}}. ===Söyleniş=== * {{hy-d}} {{IPA|dil=hy|[hjusisɑjín]}} * {{hy-b}} {{IPA|dil=hy|[hʏsisɑjín]}} * {{ses|dil=hy|LL-Q8785 (hye)-Veravi95-հյուսիսային.wav|(Batı Ermenicesi)}} ===Ön ad=== {{hy-ön ad|GYB=հիւսիսային}} # [[kuzey]] ====Çekimleme==== {{hy-ad-ի-ներ|n=on}} gw6xzssi02g6jhsg20uj0ikyi2yz7ex српскохрватски 0 1589221 5669907 2026-06-23T06:38:28Z MustafaCavlak 59368 Yeni sayfa : ==Sırp-Hırvatça== ===Ön ad=== {{başlık başı|sh|ön ad|baş=српскохр̀ва̄тскӣ|Latin harfleriyle|srpskohr̀vātskī}} # [[Sırp-Hırvatça]] ===Ad=== {{başlık başı|sh|ad|baş=српскохр̀ва̄тскӣ|c=e|Latin harfleriyle|srpskohr̀vātskī}} # {{t|dil=sh|diller}} [[Sırp-Hırvatça]] 5669907 wikitext text/x-wiki ==Sırp-Hırvatça== ===Ön ad=== {{başlık başı|sh|ön ad|baş=српскохр̀ва̄тскӣ|Latin harfleriyle|srpskohr̀vātskī}} # [[Sırp-Hırvatça]] ===Ad=== {{başlık başı|sh|ad|baş=српскохр̀ва̄тскӣ|c=e|Latin harfleriyle|srpskohr̀vātskī}} # {{t|dil=sh|diller}} [[Sırp-Hırvatça]] rbdp62ppzff0j4vjca3ng111fi26lu8 ուսանող 0 1589222 5669908 2026-06-23T06:39:33Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[üniversite]] [[öğrenci]]si 5669908 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[üniversite]] [[öğrenci]]si 1je0iw0om1g5i1uu9gnbo7flbnk63qh сковорода 0 1589223 5669909 2026-06-23T06:42:56Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== {{vikipedi|dil=ru}} ===Ad=== {{başlık başı|ru|ad|c=d|baş=сковорода́}} # [[tava]] 5669909 wikitext text/x-wiki ==Rusça== {{vikipedi|dil=ru}} ===Ad=== {{başlık başı|ru|ad|c=d|baş=сковорода́}} # [[tava]] odvqi7t2jbgtav0hjtpmrgx7imdb3xd այստեղ 0 1589224 5669910 2026-06-23T06:55:57Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Adıl=== {{başlık başı|hy|adıl}} # [[burada]], [[bura]]ya, [[burası]] 5669910 wikitext text/x-wiki ==Ermenice== ===Adıl=== {{başlık başı|hy|adıl}} # [[burada]], [[bura]]ya, [[burası]] 3ioc6xqkv09qzv8kf11v2ahfrdy7rzj думаю 0 1589225 5669911 2026-06-23T07:00:11Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́маю}} # {{çekim|dil=ru|ду́мать||1t|pres|ind|tamamlanmamış}} 5669911 wikitext text/x-wiki ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́маю}} # {{çekim|dil=ru|ду́мать||1t|pres|ind|tamamlanmamış}} fvgqj3bz78zid38gdmzjt8boh5ojyw8 думающий 0 1589226 5669912 2026-06-23T07:01:53Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Ortaç=== {{başlık başı|ru|ortaç|baş=ду́мающий}} # {{çekim|dil=ru|ду́мать||pres|act|tamamlanmamış|part}} 5669912 wikitext text/x-wiki ==Rusça== ===Ortaç=== {{başlık başı|ru|ortaç|baş=ду́мающий}} # {{çekim|dil=ru|ду́мать||pres|act|tamamlanmamış|part}} kizkkf8bojly7f4gofhu2kbeinb3zyr думавший 0 1589227 5669913 2026-06-23T07:03:06Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Ortaç=== {{başlık başı|ru|ortaç|baş=ду́мавший}} # {{çekim|dil=ru|ду́мать||past|act|tamamlanmamış|part}} 5669913 wikitext text/x-wiki ==Rusça== ===Ortaç=== {{başlık başı|ru|ortaç|baş=ду́мавший}} # {{çekim|dil=ru|ду́мать||past|act|tamamlanmamış|part}} bi1j6lfmh8ce7jbioj9jkkth563nyk1 думаешь 0 1589228 5669914 2026-06-23T07:04:33Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́маешь}} # {{çekim|dil=ru|ду́мать||2t|pres|ind|tamamlanmamış}} 5669914 wikitext text/x-wiki ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́маешь}} # {{çekim|dil=ru|ду́мать||2t|pres|ind|tamamlanmamış}} 002u48krl867jg4fclnpuqfg4u0ndip думает 0 1589229 5669915 2026-06-23T07:05:36Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́мает}} # {{çekim|dil=ru|ду́мать||3t|pres|ind|tamamlanmamış}} 5669915 wikitext text/x-wiki ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́мает}} # {{çekim|dil=ru|ду́мать||3t|pres|ind|tamamlanmamış}} 60pvtv1hb9pfu8yuyawavnyp2pvfffx думаем 0 1589230 5669916 2026-06-23T07:06:46Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́маем}} # {{çekim|dil=ru|ду́мать||1ç|pres|ind|tamamlanmamış}} 5669916 wikitext text/x-wiki ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́маем}} # {{çekim|dil=ru|ду́мать||1ç|pres|ind|tamamlanmamış}} 1flcughilmnlon9fznjtga29pb7i54x думаете 0 1589231 5669917 2026-06-23T07:07:37Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́маете}} # {{çekim|dil=ru|ду́мать||2ç|pres|ind|tamamlanmamış}} 5669917 wikitext text/x-wiki ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́маете}} # {{çekim|dil=ru|ду́мать||2ç|pres|ind|tamamlanmamış}} 16li9baxqj58p0saa1y8lzkr6cmn249 думают 0 1589232 5669918 2026-06-23T07:08:34Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́мают}} # {{çekim|dil=ru|ду́мать||3ç|pres|ind|tamamlanmamış}} 5669918 wikitext text/x-wiki ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́мают}} # {{çekim|dil=ru|ду́мать||3ç|pres|ind|tamamlanmamış}} 46ean69r38p5yy40ki0ow2f915ztfi4 думай 0 1589233 5669919 2026-06-23T07:09:43Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́май}} # {{çekim|dil=ru|ду́мать||2t|imp|tamamlanmamış}} 5669919 wikitext text/x-wiki ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́май}} # {{çekim|dil=ru|ду́мать||2t|imp|tamamlanmamış}} gq610ywpe200uhofomdb66a7hpg616c думайте 0 1589234 5669920 2026-06-23T07:10:37Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́майте}} # {{çekim|dil=ru|ду́мать||2ç|imp|tamamlanmamış}} 5669920 wikitext text/x-wiki ==Rusça== ===Eylem=== {{başlık başı|ru|çekimli eylem|baş=ду́майте}} # {{çekim|dil=ru|ду́мать||2ç|imp|tamamlanmamış}} qtsd23ed0ztogewlm9dhnj3zv2fbahr Modül:ru-ad 828 1589235 5669921 2026-06-23T07:17:49Z MustafaCavlak 59368 imported from en for testing 5669921 Scribunto text/plain --[=[ This module contains functions for creating inflection tables for Russian nouns. Author: Benwing, rewritten from early version by Wikitiki89 Form of arguments: One of the following: 1. LEMMA|DECL|PLSTEM (all arguments optional) 2. ACCENT|LEMMA|DECL|PLSTEM (all arguments optional) 3. multiple sets of arguments separated by the literal word "or" Arguments: ACCENT: Accent pattern (a b c d e f b' d' f' f''). Multiple values can be specified, separated by commas. If omitted, defaults to a or b depending on the position of stress on the lemma or explicitly-specified declension. LEMMA: Lemma form (i.e. nom sg or nom pl), with appropriately-placed stress; or the stem, if an explicit declension is specified (in this case, the declension usually looks like an ending, and the stem is the portion of the lemma minus the ending). In the first argument set (i.e. first set of arguments separated by "or"), defaults to page name; in later sets, defaults to lemma of previous set. A plural form can be given, and causes argument n= to default to n=p (plural only). Normally, an accent is required if multisyllabic, and unaccented monosyllables with automatically be stressed; prefix with * to override both behaviors. DECL: Declension field. Normally omitted to autodetect based on the lemma form; see below. PLSTEM: special plural stem (defaults to stem of lemma) Additional named arguments: a: animacy (a/an/anim = animate, i/in/inan = inanimate, b/bi/both/ai = both (listing animate first in the headword), ia = both (listing inanimate first in the headword), otherwise inanimate) n: number restriction (p = plural only, s = singular only, b = both; defaults to both unless the lemma is plural, in which case it defaults to plural only) CASE_NUM or acc_NUM_ANIM or par/loc/voc: override (or multiple values separated by commas) for case/number combination; forms auto-linked; can have raw links in it, can have an ending "note" (*, +, 1, 2, 3, etc.) pltail: Specify something (usually a * or similar) to attach to the end of the last plural form when there's more than one. Used in conjunction with notes= to indicate that alternative plural forms are obsolete, poetic, etc. pltailall: Similar pltail= but attaches to all plural forms. Typically used in conjunction with notes= to make a comment about the plural as a whole (e.g. it's mostly hypothetical, rare and awkward, etc.). sgtail, sgtailall: Same as pltail=, pltailall= but for the singular. obltail, obltailall: Same as pltail=, pltailall= but for oblique cases (not the nominative or accusative). CASE_NUM_tail: Attach the argument to the end of the last form (whether there's one or more than one) for the particular case/number combination. Note that this doesn't work quite like pltail= or sgtail= in that it doesn't skip adding the argument when there's only one form. CASE_NUM_tailall: Attach the argument to the end of all forms specified for the particular case/number combination. Similar to pltailall= or sgtailall=. suffix: Add a suffix such as ся to all forms. prefix: Add a prefix to all forms. plhyp, plhypall, CASE_NUM_hyp, etc.: Same as pltail, pltailall, CASE_NUM_tal, etc. but specify that the marked forms are mostly hypothetical or rare/awkard. Generally you will want plhypall=y to mark the plural as hypothetical. Per word named arguments: All of the above named arguments have per-word variants, e.g. a1, a2, ...; n1, n2, ...; CASE_NUM1, CASE_NUM2, ...; pltail1, pltail2, ...; etc. These apply to the individual words of a form. Case abbreviations: nom: nominative gen: genitive dat: dative acc: accusative ins: instrumental pre: prepositional par: partitive loc: locative voc: vocative Number abbreviations: sg: singular pl: plural Animacy abbreviations: an: animate in: inanimate Declension field: One of the following for regular nouns: (blank) GENDER -VARIANT GENDER-VARIANT $ DECLTYPE DECLTYPE/DECLTYPE (also, can append various special-case markers to any of the above) Or one of the following for adjectival nouns: + +ь $ +short, +mixed or +proper +DECLTYPE GENDER if present is m, f, n or 3f; for regular nouns, required if the lemma ends in -ь or is plural, ignored otherwise. 3f is the same as f but in the case of a plural lemma in -и, detects a third-declension feminine with singular in -ь rather than a first-declension feminine with singular in -а or -я. VARIANT is one way of requesting variant declensions (see also special case (1) for variant nom pls, and special case (2) for variant gen pls). The currently allowed values are -ья (will select a mixed declension that has some normal declension in its singular and the -ья plural declension); -ин (for animate masculine nouns in -ин with plural in -е -- note that this is autodetected in the majority of cases where the ending in -янин or -анин); -ишко (used for inanimate neuter-form diminutive masculine nouns in -ишко [also сараю́шко] with nom pl -и and colloquial feminine-ending alternants in some singular cases); -ище (similar to -ишко but used for *animate* augmentative masculine neuter-form nouns in -ище). Variants -ишко and -ище must be given with with special case (1). $ is indeclinable words. It is principally useful in multiword expressions where some of the words are indeclinable. DECLTYPE is an explicit declension type. Normally you shouldn't use this, and should instead let the declension type be autodetected based on the ending, supplying the appropriate hint if needed (gender for regular nouns, +ь for adjectives). If provided, the declension type is usually the same as the ending, and if present, the lemma field should be just the stem, without the ending. Possibilities for regular nouns are (blank) or # for hard-consonant declension, а, я, о, е or ё, е́, й, ья, ье or ьё, ь-m, ь-f, ин, ёнок or онок or енок, ёночек or оночек or еночек, мя, -а or #-а, ь-я, й-я, о-и or о-ы, -ья or #-ья, $ (indeclinable). Old-style (pre-reform) declensions use ъ instead of (blank), ъ-а instead of -а, ъ-ья instead of -ья, and инъ, ёнокъ/онокъ/енокъ, ёночекъ/оночекъ/еночекъ instead of the same without terminating ъ. The declensions can also be written with an accent on them; this chooses the same declension (except for е vs. е́), but causes ACCENT to default to pattern b instead of a. For adjectival nouns, you should normally supply just + and let the ending determine the declension; supply +ь in the case of a possessive adjectival noun in -ий, which have an extra -ь- in most endings compared with normal adjectival nouns in -ий, but which can't be distinguished based on the nominative singular. You can also supply +short, +mixed or +proper, which constrains the declension appropriately but still autodetects the gender-specific and stress-specific variant. If you do supply a specific declension type, as with regular nouns you need to omit the ending from the lemma field and supply just the stem. Possibilities are +ый, +ое, +ая, +ій, +ее, +яя, +ой, +о́е, +а́я, +ьій, +ье, +ья, +-short or +#-short (masc), +о-short, +о-stressed-short or +о́-short, +а-short, +а-stressed-short or +а́-short, and similar for -mixed and -proper (except there aren't any stressed mixed declensions). DECLTYPE/DECLTYPE is used for nouns with one declension in the singular and a different one in the plural, for cases that PLVARIANT and special case (1) below don't cover. Special-case markers: (1) for Zaliznyak-style alternative nominative plural ending: -а or -я for masculine, -и or -ы for neuter (2) for Zaliznyak-style alternative genitive plural ending: -ъ/none for masculine, -ей for feminine, -ов(ъ) for neuter, -ей for plural variant -ья * for reducibles (nom sg or gen pl has an extra vowel before the final consonant as compared with the stem found in other cases) ;ё for Zaliznyak-style alternation between last е in stem and ё TODO: 1. Multi-word issues: -- FIXME: Make sure internal_notes handled correctly; we may run into issues with multiple internal notes from different words, if we're not careful to use different footnote symbols for each type of footnote (which we don't do currently). [NOT DONE, MAY NOT DO] -- Handling default lemma: With multiple words, we should probably split the page name on spaces and default each word in turn [NOT DONE, MAY NOT DO] 2a. FIXME: For -ишко diminutives and -ище augmentatives, should add an appropriate category of some sort (currently marked by colloqfem= in category). 2b. FIXME: Adding a note to dat_sg also adds it to loc_sg when it exists; seems wrong. See луг. 2c. FIXME: When you have both d' and f in feminines and you use sgtail=*, you get two *'s. See User:Benwing2/test-ru-noun-debug. 3. ADJECTIVE FIXMES: 3a. FIXME: Change calls to ru-adj11 to use the new proper name support in ru-adjective. 3b. FIXME: Test that omitting a manual form in ru-adjective leaves the form as a big dash. 3c. FIXME: какой-либо and какой-то display genitives with translit -go instead of -vo. To fix this properly requires implementing real manual translit for adjectives. 3d. FIXME: Implement real manual translit for adjectives. 5. [FIXME: Consider adding an indicator in the header line when the ё/e alternation occurs. This is a bit tricky to calculate: If special case ;ё is given, but also if ё occurs in the stem and the accent pattern is as follows -- for sg-only, b' d' f' f'', also b d f if the noun is masc or 3rd-decl fem (i.e. nom-sg ending is non-syllabic); for pl-only, e f f' f'', also b b' c if the gen pl is non-syllabic; for sg/pl, any but a or b, also b if either nom sg or gen pl is non-syllabic. But it gets more complicated due to overrides. An alternative is to check all forms to see if ё is present in some but not all; but this is tricky also because e.g. reducibles frequently have ё/null alternation, which doesn't count, and some endings have е or ё in them, which also doesn't count. If we were to do it this way, we'd have to (a) count the number of е's in the form(s) with ё and verify that there's at least one form without ё and with one more е than in the form(s) with ё (and it gets trickier if different forms with ё have different numbers of е in them, although that is probably rare); and (b) ignore the appropriate endings (the best way to do this would probably be to look at the actual suffixes that were generated in args.suffixes and chop off any matching ending in the actual form(s), but also chop off final -е/ё, as well as -ев(ъ)/-ёв(ъ)/-ей/-ёй in the gen pl, which is frequently overridden, unless perhaps the stem ends in the same way). It'd probably not possible to do this in a 100% foolproof way but can be "good enough" for nearly all circumstances.] [MIGHT BE TOO MUCH WORK] 6. HEADWORD FIXMES: 6a. FIXME: In ru-headword, create a category for words whose gender doesn't match the form. (This is easy to do for ru-noun+ but harder for ru-noun. We would need to do limited autodetection of the ending: for singulars, -а/я should be feminine, -е/о/ё should be neuter, -ь should be masculine or feminine, anything else should be masculine; for plurals, -и/ы should be masculine or feminine, -а/я should be neuter except that -ія can be feminine or neuter due to old-style adjectival pluralia tantum nouns, anything else can be any gender.) 6b. FIXME: Recognize indeclinable nouns and indicate as indeclinable. Probably should work by checking the case forms to see if they're the same. 9. FIXME: Change stress-pattern detection and overriding to happen inside of looping over the two parts of a slash decl. Requires that the loop over the two parts happen outside of the loop over stress patterns. Requires that the category code get split into two parts, one to handle combined singular/plural categories that goes outside the two loops, and one to handle everything else that goes inside the two loops. 10. FIXME: override_matches_suffix() had a free variable reference to ARGS in it, which should have triggered an error whenever there was a nom_sg or nom_pl override but didn't. Is there an error causing this never to be called? Check. 11a. FIXME: In a multiword lemma, using loc2=+ causes only the second word to get linked instead of the whole expression. Same for par2=+, voc2=+. 11b. FIXME: Using loc=+ with a multiword lemma should do the right thing, same as if locN=+ is specified for each individual word. Instead it generates the locative as a whole from the dative, which fails e.g. if some of the words are adjectival. Same for par=+, voc=+. 13. Multi-word issues: -- Setting n=pl when auto-detecting a plural lemma. How does that interact with multi-word stuff? (DONE) -- compute_heading() -- what to do with multiple words? I assume we should display info on the first noun (non-indeclinable, non-adjectival), and on the first adjectival word otherwise, and finally on an indeclinable word (DONE) -- args.genders -- it should presumably come from the same word as is used in compute_heading(); but we should allow the overall gender to be overridden, at least in ru-noun+ (DONE) -- Bug in args.suffix: Gets added to every word in attach_with() and then again at the end, after pltail and such. Needs to be added to the last word only, before pltail. Need also suffixN for individual words. (DONE, NEEDS TESTING) -- Should have ..N versions of pltail and variants. (DONE, NEEDS TESTING) -- Need to handle overrides of acc_sg, acc_pl (DONE) -- Overrides of nom_sg/nom_pl should also override acc_sg/acc_pl if it was originally empty and the animacy is inanimate; similarly for gen_sg/gen_pl and animates; this needs to work both for per-word and overall overrides. (DONE) -- do_generate_forms(_multi) need to run part of make_table(), enough to combine all per_word_info into single lists of forms and store back into args[case]. (DONE, NEEDS TESTING) -- In generate_forms, should probably check if a=="i" and only return acc_sg_in as acc_sg=; or if a=="a" and only return acc_sg_an as acc_sg=; in old/new comparison code, do something similar, also when a=="b" check if acc_sg_in==acc_sg_an and make it acc_sg; when a=="b" and the _in and _an variants are different, might need to ignore them or check that acc_sg_in==nom_sg and acc_sg_an==gen_sg; similarly for _pl (DONE, NEEDS TESTING) -- Need to test with multiple words! [DONE] -- Current handling of <adj> won't work properly with multiple words; will need to translate word-by-word in that case (should be solved by manual-translit branch) [DONE] 14. In multiple-words branch, fix ru-decl-noun-multi so it recognizes things like *, (1), (2) and ; without the need for a separator. Consider using semicolon as a separator, since we already use it to separate ё from a previous declension. Maybe use $ or ~ for an indeclinable word; don't use semicolon. [IMPLEMENTED. NEED TO TEST.] 16. [Consider having ru-noun+ treat par= as a second genitive in the headword, as is done with край] [WON'T DO] 17. [FIXME: Consider removing slash patterns and instead handling them by allowing additional declension flags 'sg' and 'pl'. This simplifies the various special cases caused by slash declensions. It would also be possible to remove the special plural stem, which would get rid of more special cases. On the other hand, it makes it more complicated to support plural variant -ья with all singular types, and the category code that displays things like "Russian nouns with singular -X and plural -Y" also gets more complicated, and there's something convenient and intuitive about plural stems, and slash declensions are also convenient and at least somewhat intuitive. One possibility is to externally allow slash declensions and special plural stems and rewrite them internally to separate stems with 'sg' and 'pl' declension flags; but there are still the two coding issues mentioned above.] 18. [FIXME: Consider redoing slash patterns so they operate at the outer level, i.e. things like special cases apply separately in the singular and plural part of the slash pattern.] 19. In ru-noun, don't recognize -а with m as plural unless (1) or n=pl is also given, because there are masculine words with the feminine ending. Check using the test code whether this changes anything. Also check if there are other similar cases (neuter with -и isn't parallel because -и is always plural). [IMPLEMENTED. NEED TO TEST.] 19a. Internal notes weren't propagated properly from adjectives. [IMPLEMENTED. NEED TO TEST.] 19b. Add support for -ишко and -ище variants (p. 74 of Z), which conversationally and/or colloquially have feminine endings in certain cases. [IMPLEMENTED. NEED TO TEST. MAKE SURE THE INTERNAL NOTES APPEAR.] 19d. For masculine animate neuter-form nouns, the accusative singular ends in -а (-я soft) instead of -о. [IMPLEMENTED. NEED TO TEST. NOTE: Currently this variant only can be selected using new-style arguments where the gender can be given. Perhaps we should consider allowing gender to be specified with old-style explicit declensions.] 21. Put back gender hints for pl adjectival nouns; used by ru-noun+. [IMPLEMENTED. NEED TO TEST.] 23. Mixed and proper-noun adjectives have built-in notes. We need to handle those notes with an "internal_notes" section similar to what is used in the adjective module. [IMPLEMENTED. NEED TO TEST.] 24. Adjective detection code here needs to work the same as for the adjective module, in particular in the handling of short, stressed-short, mixed, proper, stressed-proper. [IMPLEMENTED. NEED TO TEST.] 25. Consider simplifying plural-variant code to only allow -ья as a plural variant [and maybe even change that to be something like (1')]. [IMPLEMENTED REDUCTION OF PLURAL VARIANTS TO -ья; PLURAL-VARIANT CODE STILL COMPLEX, THOUGH. NEED TO TEST.] 26. Automatically superscript *, numbers and similar things at the beginning of a note. Also do this in adjective module. [IMPLEMENTED. NEED TO TEST.] 28. Make the check for multiple stress patterns (categorizing/tracking) smarter, to keep a list of them and check at the end, so we handle multiple stress patterns specified through different arg sets. [IMPLEMENTED; NEED TO TEST.] 29. More sophisticated handling of user-requested plural variant vs. special case (1) vs. plural-detected variant. [IMPLEMENTED. NEED TO TEST FURTHER.] 30. Solution to ambiguous plural involving gender spec "3f". [IMPLEMENTED; NEED TO TEST. Use запчасти, новости.] 33. With pluralia tantum adjectival nouns, we don't know the gender. By default we assume masculine (or feminine for old-style -ія nouns) and currently this goes into the category, but shouldn't. [IMPLEMENTED.] 39. [Eventually: Even with decl type explicitly given, the full stem with ending should be included.] [MAY NEVER IMPLEMENT] 40. [Get error "Unable to dereduce" with strange noun ва́йя, what should happen?] [WILL NOT FIX; USE AN OVERRIDE] 41. In творог, module generates partitive творогу́ when it should copy the dative творогу́,тво́рогу. (DONE) 42. [[груз 200]] doesn't work. Interprets 200 as a footnote symbol. 43. When converting е -> ё not after cons and with translit, we should convert e -> o to avoid double j. (DONE) 44. FIXME: In ро́вня/ровня́, similarly with неровня, marks genitive plural ровня́ as irregular even though it isn't. (NOT OUR ERROR; THE DECLENSIONS OF THESE NOUNS MARKED THE ENDING-STRESSED VARIANTS WITH (2).) ]=]-- local m_utilities = require("Module:utilities") local m_table = require("Module:table") local m_links = require("Module:links") local com = require("Module:ru-common") local nom = require("Module:ru-nominal") local m_ru_adj = require("Module:ru-adjective") local m_str_utils = require("Module:string utilities") local scriptutils = require("Module:script utilities") local m_table_tools = require("Module:table tools") local m_debug = require("Module:debug") local export = {} local lang = require("Module:languages").getByCode("ru") local Latn = require("Module:scripts").getByCode("Latn") local format = m_str_utils.format local rfind = m_str_utils.find local rsubn = m_str_utils.gsub local rmatch = m_str_utils.match local rsplit = m_str_utils.split local u = m_str_utils.char local ulower = m_str_utils.lower local usub = m_str_utils.sub local ulen = m_str_utils.len local unpack = unpack or table.unpack -- Lua 5.2 compatibility -- If enabled, compare this module with new version of module to make -- sure all declensions are the same. Eventually consider removing this; -- but useful as new code is created. local test_new_ru_noun_module = false local AC = u(0x0301) -- acute = ́ local GR = u(0x0300) -- grave = ̀ local CFLEX = u(0x0302) -- circumflex = ̂ local DIA = u(0x0308) -- diaeresis = ̈ local PSEUDOCONS = u(0xFFF2) -- pseudoconsonant placeholder, matching ru-common local IRREGMARKER = "△" local HYPMARKER = "⟐" local paucal_marker = "*" local paucal_internal_note = "* Used with the numbers 1.5, 2, 3, 4 and higher numbers after 20 ending in 2, 3, and 4." -- text class to check lowercase arg against to see if Latin text embedded in it local latin_text_class = "[a-zščžěáéíóúýàèìòùỳâêîôûŷạẹịọụỵȧėȯẏ]" -- Forward functions local generate_forms_1 local determine_decl local handle_forms_and_overrides local handle_overall_forms_and_overrides local concat_word_forms local make_table local detect_adj_type local detect_stress_pattern local override_stress_pattern local determine_stress_variant local determine_stem_variant local is_reducible local is_dereducible local add_bare_suffix local attach_stressed local do_stress_pattern local canonicalize_override -- version of rsubn() that discards all but the first return value local function rsub(term, foo, bar) local retval = rsubn(term, foo, bar) return retval end -- version of rsubn() that returns a 2nd argument boolean indicating whether -- a substitution was made. local function rsubb(term, foo, bar) local retval, nsubs = rsubn(term, foo, bar) return retval, nsubs > 0 end -- version of rfind() that lowercases its string first, for case-insensitive matching local function rlfind(term, foo) return rfind(ulower(term), foo) end local function track(page) m_debug.track("ru-noun/" .. page) return true end -- version of m_table.insertIfNot() that makes sure 'false' doesn't get inserted by mistake. local function insert_if_not(foo, bar) assert(bar ~= false) m_table.insertIfNot(foo, bar) end -- Fancy version of ine() (if-not-empty). Converts empty string to nil, -- but also strips leading/trailing space and then single or double quotes, -- to allow for embedded spaces. local function ine(arg) if not arg then return nil end arg = rsub(arg, "^%s*(.-)%s*$", "%1") if arg == "" then return nil end local inside_quotes = rmatch(arg, '^"(.*)"$') if inside_quotes then return inside_quotes end inside_quotes = rmatch(arg, "^'(.*)'$") if inside_quotes then return inside_quotes end return arg end -- FIXME: Move to utils -- Iterate over a chain of parameters, FIRST then PREF2, PREF3, ..., -- inserting into LIST (newly created if omitted). Return LIST. local function get_arg_chain(args, first, pref, list) if not list then list = {} end local val = args[first] local i = 2 while val do table.insert(list, val) val = args[pref .. i] i = i + 1 end return list end -- synthesize a frame so that exported functions meant to be called from -- templates can be called from the debug console. local function debug_frame(parargs, args) return {args = args, getParent = function() return {args = parargs} end} end local function rutr_pairs_equal(term1, term2) local ru1, tr1 = term1[1], term1[2] local ru2, tr2 = term2[1], term2[2] local ru1entry, ru1notes = m_table_tools.separate_notes(m_links.remove_links(ru1)) local ru2entry, ru2notes = m_table_tools.separate_notes(m_links.remove_links(ru2)) if ru1entry ~= ru2entry then return false end local tr1entry, tr1notes local tr2entry, tr2notes if tr1 then tr1entry, tr1notes = m_table_tools.separate_notes(tr1) end if tr2 then tr2entry, tr2notes = m_table_tools.separate_notes(tr2) end if tr1entry == tr2entry then return true elseif type(tr1entry) == type(tr2entry) then return false else tr1entry = tr1entry or com.translit_no_links(ru1entry) tr2entry = tr2entry or com.translit_no_links(ru2entry) return tr1entry == tr2entry end end local function contains_rutr_pair(list, pair) for _, item in ipairs(list) do if rutr_pairs_equal(item, pair) then return true end end return false end -- Clone parent's args while also assigning nil to empty strings. local function clone_args(frame) local args = {} for pname, param in pairs(frame:getParent().args) do args[pname] = ine(param) end return args end -- Old-style declensions. local declensions_old = {} -- New-style declensions; computed automatically from the old-style ones, -- for the most part. local declensions = {} -- Internal notes for old-style declensions. local internal_notes_table_old = {} -- Same for new-style declensions. local internal_notes_table = {} -- Category and type information corresponding to declensions: These may -- contain the following fields: 'singular', 'plural', 'decl', 'hard', 'g', -- 'suffix', 'gensg', 'irregpl', 'alt_nom_pl', 'cant_reduce', 'ignore_reduce', -- 'stem_suffix'. -- -- 'singular' is used to construct a category of the form -- "Russian nouns SINGULAR". If omitted, a category is constructed of the -- form "Russian nouns ending in -ENDING", where ENDING is the actual -- nom sg ending shorn of its acute accents; or "Russian nouns ending -- in suffix -ENDING", if 'suffix' is true. The value of SINGULAR can be -- one of the following: a single string, a list of strings, or a function, -- which is passed one argument (the value of ENDING that would be used to -- auto-initialize the category), and should return a single string or list -- of strings. Such a category is only constructed if 'gensg' is true. -- -- 'plural' is analogous but used to construct a category of the form -- "Russian nouns with PLURAL", and if omitted, a category is constructed -- of the form "Russian nouns with plural -ENDING", based on the actual -- nom pl ending shorn of its acute accents. Currently no plural category -- is actually constructed. -- -- In addition, a category may normally constructed from the combination of -- 'singular' and 'plural', appropriately defaulted; e.g. if both are present, -- the combined category will be "Russian nouns SINGULAR with PLURAL" and -- if both are missing, the combined category will be -- "Russian nouns ending in -SGENDING with plural -PLENDING" (or -- "Russian nouns ending in suffix -SGENDING with plural -PLENDING" if -- 'suffix' is true). Note that if either singular or plural or both -- specifies a list, looping will occur over all combinations. Such a -- category is constructed only if 'irregpl' or 'alt_nom_pl' or 'suffix' -- is true or if the declension class is a slash class. -- -- 'decl' is "1st", "2nd", "3rd" or "indeclinable"; 'hard' is "hard", "soft" -- or "none"; 'g' is "m", "f", "n" or "none"; these are all traditional -- declension categories. -- -- If 'suffix' is true, the declension type includes a long suffix -- added to the string that itself undergoes reducibility and such, and so -- reducibility cannot occur in the stem minus the suffix. Categories will -- be created for the suffix. -- -- 'alt_nom_pl' indicates that the declension has an alternative nominative -- plural (corresponding to Zaliznyak's special case 1; compare special case 2 -- for alternative genitive plural). 'irregpl' indicates that the entire -- plural is irregular. -- -- In addition to the above categories, additional more specific categories -- are constructed based on the final letter of the stem, e.g. -- "Russian velar-stem 1st-declension hard nouns". See calls to -- com.get_stem_trailing_letter_type(). 'stem_suffix', if present, is added to -- the end of the stem when get_stem_trailing_letter_type() is called. -- This is the only place that 'stem_suffix' is used. This is for use with -- the '-ья' and '-ье' declension types, so that the trailing letter is -- 'ь' and not whatever precedes it. -- -- 'enable_categories' is a special hack for testing, which disables all -- category insertion if false. Delete this as soon as we've verified the -- working of the category code and created all the necessary categories. local enable_categories = true -- Category/type info corresponding to old-style declensions; see above. local declensions_old_cat = {} -- Category/type info corresponding to new-style declensions. Computed -- automatically from the old-style ones, for the most part. Same format -- as the old-style ones. local declensions_cat = {} -- Table listing aliases of old-style declension classes. local declensions_old_aliases = {} -- Table listing aliases of new-style declension classes; computed -- automatically from the old-style ones. local declensions_aliases = {} local stress_patterns = {} -- Set of patterns with ending-stressed genitive plural. local ending_stressed_gen_pl_patterns = {} -- Set of patterns with ending-stressed prepositional singular. local ending_stressed_pre_sg_patterns = {} -- Set of patterns with ending-stressed dative singular. local ending_stressed_dat_sg_patterns = {} -- Set of patterns with all singular forms ending-stressed. local ending_stressed_sg_patterns = {} -- Set of patterns with all plural forms ending-stressed. local ending_stressed_pl_patterns = {} local declinable_cases_except_accusative = { "nom_sg", "gen_sg", "dat_sg", "ins_sg", "pre_sg", "nom_pl", "gen_pl", "dat_pl", "ins_pl", "pre_pl", } local accusative_cases_unsplit_animacy = { "acc_sg", "acc_pl", } local accusative_cases_split_animacy = { "acc_sg_an", "acc_sg_in", "acc_pl_an", "acc_pl_in", } local lemma_linked_cases = { "nom_sg_linked", "nom_pl_linked", } local overridable_only_cases = { "par", "loc", "voc", "par_pl", "loc_pl", "voc_pl", "count", "pauc", } local overridable_only_cases_set = m_table.listToSet(overridable_only_cases) -- List of all cases that are declined normally. local decl_cases = m_table.append(declinable_cases_except_accusative, accusative_cases_unsplit_animacy) -- List of all cases that can be overridden (includes all cases except the "linked" lemma case variants). Also -- currently the same as the cases returned by export.generate_forms(). local overridable_cases = m_table.append(decl_cases, accusative_cases_split_animacy, overridable_only_cases) -- List of all cases that can be displayed (includes all cases except plain accusatives). local displayable_cases = m_table.append(declinable_cases_except_accusative, lemma_linked_cases, accusative_cases_split_animacy, overridable_only_cases) -- List of all cases, including those that are declined normally (nom/gen/dat/acc/ins/pre sg and pl), plus -- animate/inanimate accusative variants (computed automatically as appropriate from the previous cases), plus -- additional overridable cases (loc/par/voc and plural), plus the "linked" lemma case variants used in ru-noun+ -- headwords (nom_sg_linked, nom_pl_linked, whose values come from nom_sg and nom_pl but may have additional embedded -- links if they were given in the lemma). local all_cases = m_table.append(overridable_cases, lemma_linked_cases) local function english_case_description(case) if case == "par" or case == "loc" or case == "voc" then -- For historical reasons, the singular of these cases doens't include "_sg" in their code. case = case .. "_sg" end local engcase = rsub(case, "^([a-z]*)", { nom="nominative", gen="genitive", dat="dative", acc="accusative", ins="instrumental", pre="prepositional", par="partitive", loc="locative", voc="vocative", count="count form", pauc="paucal form", }) engcase = rsub(engcase, "(_[a-z]*)", { _sg=" singular", _pl=" plural", _an="", _in="", --_an=" animate", _in=" inanimate" }) return engcase end -------------------------------------------------------------------------- -- Tracking and categorization -- -------------------------------------------------------------------------- -- FIXME! Move below the main code -- FIXME!! Consider deleting most of this tracking code once we've enabled -- all the categories. Note that some of the tracking categories aren't -- completely redundant; e.g. we have tracking pages that combine decl and -- stress classes, such as "а/a" or "о-и/d'", which are more or less -- equivalent to stem/gender/stress categories, but we also have the same -- prefixed by "reducible-stem/" for reducible stems. local function tracking_code(stress, orig_decl, decl, args, n, islast) assert(orig_decl) assert(decl) local hint_types = com.get_stem_trailing_letter_type(args.stem) if orig_decl == decl then orig_decl = nil end if args.notes then track("notes") end local function all_pl_irreg() track("irreg") for _, case in ipairs(overridable_cases) do if rfind(case, "_pl") then track("irreg/" .. case) end end end local function track_prefix(prefix) local function dotrack(suf) track(prefix .. suf) end dotrack(stress) dotrack(decl) dotrack(decl .. "/" .. stress) if orig_decl then dotrack(orig_decl) dotrack(orig_decl .. "/" .. stress) end for _, hint_type in ipairs(hint_types) do dotrack(hint_type) dotrack(decl .. "/" .. hint_type) if orig_decl then dotrack(orig_decl .. "/" .. hint_type) end end end track_prefix("") if args.reducible then track("reducible-stem") track_prefix("reducible-stem/") end if rlfind(args.stem, "и́?н$") and (decl == "" or decl == "#") then track("irregular-in") end if args.pltail then track("pltail") end if args.sgtail then track("sgtail") end if args.pltailall then track("pltailall") end if args.sgtailall then track("sgtailall") end if args.pl ~= args.stem then track("irreg-pl-stem") track("irreg") end if args.alt_gen_pl then track("alt-gen-pl") track("irreg") track("irreg/gen_pl") end if args.want_sc1 then track("want-sc1") track("irreg") track("irreg/nom_pl") end if rfind(decl, "-и$") or rfind(decl, "-а$") or rfind(decl, "-я$") then track("irreg") track("irreg/nom_pl") end if rfind(decl, "-ья$") then track("variant-ья") all_pl_irreg() end if rfind(decl, "%(ишк%)$") then track("variant-ишко") end if rfind(decl, "%(ищ%)$") then track("variant-ище") end if args.jo_special then track("jo-special") end if args.manual then track("manual") end if args.explicit_gender then track("explicit-gender") track("explicit-gender/" .. args.explicit_gender) end for _, case in ipairs(overridable_cases) do if args[case .. n] and not args.manual then track("override") track("override/" .. case .. n) track("irreg") track("irreg/" .. case .. n) -- questionable use: track_prefix("irreg/" .. case .. "/") -- questionable use: track_prefix("irreg/" .. case .. n .. "/") end if islast and args[case] and not args.manual then track("override") track("override/" .. case) track("irreg") track("irreg/" .. case) -- questionable use: track_prefix("irreg/" .. case .. "/") end if args[case .. "_tail"] then track("casenum-tail") track("casenum-tail/" .. case) end if args[case .. "_tailall"] then track("casenum-tailall") track("casenum-tailall/" .. case) end end end local gender_to_full = {m="masculine", f="feminine", n="neuter"} local gender_to_short = {m="masc", f="fem", n="neut"} -- Insert the category CAT (a string) into list CATEGORIES. String will -- have "Russian " prepended and ~ substituted for the plural part of speech. local function insert_category(categories, cat, pos, atbeg) if enable_categories then local fullcat = "Russian " .. rsub(cat, "~", pos .. "s") if atbeg then table.insert(categories, 1, fullcat) else table.insert(categories, fullcat) end end end -- Insert categories into ARGS.CATEGORIES corresponding to the specified -- stress and declension classes and to the form of the stem (e.g. velar, -- sibilant, etc.). Also initialize values used to compute the declension -- heading that describes similar information. N is the number of the -- word being processed; ISLAST is true if this is the last word. local function categorize_and_init_heading(stress, decl, args, n, islast) local function cat_to_list(cat) if not cat then return {} elseif type(cat) == "string" then return {cat} else assert(type(cat) == "table") return cat end end -- Insert category CAT into the list of categories in ARGS. -- CAT may be nil, a single string or a list of strings. We call -- insert_category() on each string. The strings will have "Russian " -- prepended and "~" replaced with the plural part of speech. local function insert_cat(cat) for _, c in ipairs(cat_to_list(cat)) do insert_category(args.categories, c, args.pos) end end -- "Resolve" the category spec CATSPEC into the sort of category spec -- accepted by insert_cat(), i.e. nil, a single string or a list of -- strings. CATSPEC may be any of these or a function, which takes one -- argument (SUFFIX) and returns another CATSPEC. local function resolve_cat(catspec, suffix) if type(catspec) == "function" then return resolve_cat(catspec(suffix), suffix) else return catspec end end -- Check whether an override for nom_sg or nom_pl still contains the -- normal suffix (which should already have accents removed) in at least -- one of its entries. If no override then of course we return true. local function override_matches_suffix(args, case, n, suffix) assert(suffix == com.remove_accents(suffix)) -- NOTE: It might not be completely correct to pass in args.forms -- here when n == ""; this is different behavior from -- handle_overall_forms_and_overrides(). But args.forms is only used -- to retrieve the dat_sg for handling loc/par, and we're never -- called with loc or par as the value of CASE, so it doesn't matter. -- We add an assert to make sure of this. assert(case ~= "loc" and case ~= "par") local override = canonicalize_override(args, case, args.forms, n) if not override then return true end for _, x in ipairs(override) do local ru, tr = x[1], x[2] local entry, notes = m_table_tools.separate_notes(ru) entry = com.remove_accents(m_links.remove_links(entry)) if rlfind(entry, suffix .. "$") then return true end end return false end if args.manual then return end local h = args.heading_info assert(decl) local decl_cats = args.old and declensions_old_cat or declensions_cat local sgdecl, pldecl local is_slash_decl = rfind(decl, "/") if is_slash_decl then local indiv_decls = rsplit(decl, "/") sgdecl, pldecl = indiv_decls[1], indiv_decls[2] else sgdecl, pldecl = decl, decl end local sgdc = decl_cats[sgdecl] local pldc = decl_cats[pldecl] assert(sgdc) assert(pldc) local sghint_types = com.get_stem_trailing_letter_type( args.stem .. (sgdc.stem_suffix or "")) -- insert English version of Zaliznyak stem type if sgdc.decl == "indeclinable" then insert_cat("indeclinable ~") insert_if_not(h.stemetc, "indecl") else local stem_type = sgdc.decl == "3rd" and "3rd-declension" or m_table.contains(sghint_types, "velar") and "velar-stem" or m_table.contains(sghint_types, "sibilant") and "sibilant-stem" or m_table.contains(sghint_types, "c") and "ц-stem" or m_table.contains(sghint_types, "i") and "i-stem" or m_table.contains(sghint_types, "vowel") and "vowel-stem" or m_table.contains(sghint_types, "soft-cons") and "vowel-stem" or m_table.contains(sghint_types, "palatal") and "vowel-stem" or sgdc.hard == "soft" and "soft-stem" or "hard-stem" local short_stem_type = stem_type == "3rd-declension" and "3rd-decl" or stem_type if sgdc.adj then -- Don't include gender for pluralia tantum because it's mostly -- indeterminate (certainly when specified using a plural lemma, -- which will be usually; technically it's partly or completely -- determinate in certain old-style adjectives that distinguish -- masculine from feminine/neuter, but this is too rare a case -- to worry about) local gendertext = args.thisn == "p" and "plural-only" or gender_to_full[sgdc.g] insert_if_not(h.adjectival, "yes") if args.thisn ~= "p" then insert_if_not(h.gender, gender_to_short[sgdc.g]) end if sgdc.possadj then insert_cat(sgdc.decl .. " possessive " .. gendertext .. " accent-" .. (stress:gsub("''", "ʺ"):gsub("'", "ʹ")) .. " adjectival ~") insert_if_not(h.stemetc, sgdc.decl .. " poss") insert_if_not(h.stress, stress) elseif stem_type == "soft-stem" or stem_type == "vowel-stem" then insert_cat(stem_type .. " " .. gendertext .. " adjectival ~") insert_if_not(h.stemetc, short_stem_type) else insert_cat(stem_type .. " " .. gendertext .. " accent-" .. (stress:gsub("''", "ʺ"):gsub("'", "ʹ")) .. " adjectival ~") insert_if_not(h.stemetc, short_stem_type) insert_if_not(h.stress, stress) end else -- NOTE: There are 8 Zaliznyak-style stem types and 3 genders, but -- we don't create a category for masculine-form 3rd-declension -- nouns (there is such a noun, путь, but it mostly behaves -- like a feminine noun), so there are 23. insert_cat(stem_type .. " " .. gender_to_full[sgdc.g] .. "-form ~") -- NOTE: Here we are creating categories for the combination of -- stem, gender and accent. There are 10 accent patterns and 23 -- combinations of stem and gender, which potentially makes for -- 10*23 = 230 such categories, which is a lot. Not all such -- categories should actually exist; there were maybe 75 former -- declension templates, each of which was essentially categorized -- by the same three variables, but some of which dealt with -- ancillary issues like irregular plurals; this amounts to 67 -- actual stem/gender/accent categories, although there are more -- of them in Zaliznyak (FIXME, how many? See generate_cats.py). insert_cat(stem_type .. " " .. gender_to_full[sgdc.g] .. "-form accent-" .. (stress:gsub("''", "ʺ"):gsub("'", "ʹ")) .. " ~") insert_if_not(h.adjectival, "no") insert_if_not(h.gender, gender_to_short[sgdc.g]) insert_if_not(h.stemetc, short_stem_type) insert_if_not(h.stress, stress) end insert_cat("~ with accent pattern " .. (stress:gsub("''", "ʺ"):gsub("'", "ʹ"))) end local sgsuffix = args.suffixes.nom_sg if sgsuffix then assert(#sgsuffix == 1) -- If this ever fails, then implement a loop sgsuffix = com.remove_accents(sgsuffix[1]) -- If we are plural only or if nom_sg is overridden and has -- an unusual suffix, then don't create category for sg suffix if args.thisn == "p" or not override_matches_suffix(args, "nom_sg", n, sgsuffix) or islast and not override_matches_suffix(args, "nom_sg", "", sgsuffix) then sgsuffix = nil end end local plsuffix = args.suffixes.nom_pl if plsuffix then assert(#plsuffix == 1) -- If this ever fails, then implement a loop plsuffix = com.remove_accents(plsuffix[1]) -- If we are a singulare tantum or if nom_pl is overridden and has -- an unusual suffix, then don't create category for pl suffix if args.thisn == "s" or not override_matches_suffix(args, "nom_pl", n, plsuffix) or islast and not override_matches_suffix(args, "nom_pl", "", plsuffix) then plsuffix = nil end end sgsuffix = sgsuffix and rsub(sgsuffix, "ъ$", "") plsuffix = plsuffix and rsub(plsuffix, "ъ$", "") local sgcat = sgsuffix and (resolve_cat(sgdc.singular, sgsuffix) or "ending in " .. (sgsuffix == "" and "a consonant" or (sgdc.suffix and "suffix " or "") .. "-" .. sgsuffix)) local plcat = plsuffix and (resolve_cat(pldc.plural, plsuffix) or "plural -" .. plsuffix) if sgcat and sgdc.gensg then for _, cat in ipairs(cat_to_list(sgcat)) do insert_cat("~ " .. cat) end end if sgcat and plcat and (sgdc.suffix or sgdc.alt_nom_pl or sgdc.irregpl or is_slash_decl and plsuffix == "-ья") then for _, scat in ipairs(cat_to_list(sgcat)) do for _, pcat in ipairs(cat_to_list(plcat)) do insert_cat("~ " .. scat .. " with " .. pcat) end end end if args.pl ~= args.stem then insert_cat("~ with irregular plural stem") end if args.reducible and not sgdc.ignore_reduce then insert_cat("~ with reducible stem") if args.soft_n then insert_cat("~ with soft final н in reduced stem") end insert_if_not(h.reducible, "yes") else insert_if_not(h.reducible, "no") end if args.alt_gen_pl then insert_cat("~ with alternative genitive plural") end if sgdc.adj then insert_cat("adjectival ~") end end local function compute_heading(args) local headings = {} local h = args.heading_info table.insert(headings, args.a == "a" and "anim" or args.a == "i" and "inan" or "bian") table.insert(headings, args.nonumber and "uncountable" or args.n == "s" and "sg-only" or args.n == "p" and "pl-only" or nil) if #h.gender > 0 then table.insert(headings, table.concat(h.gender, "/") .. "-form") end if #h.stemetc > 0 then table.insert(headings, table.concat(h.stemetc, "/")) end if #h.stress > 0 then local stresses = {} for _, stress in ipairs(h.stress) do table.insert(stresses, (stress:gsub("''", "ʺ"):gsub("'", "ʹ"))) end table.insert(headings, "accent-" .. table.concat(stresses, "/")) end local function handle_bool(boolvals, text, into) into = into or headings if m_table.contains(boolvals, "yes") and m_table.contains(boolvals, "no") then table.insert(into, "[" .. text .. "]") elseif m_table.contains(boolvals, "yes") then table.insert(into, text) end end handle_bool(h.adjectival, "adj") handle_bool(h.reducible, "reduc") return headings end local function compute_overall_heading_categories_and_genders(args) local hinfo = args.per_word_heading_info local index = 0 -- First try for non-adjectival, non-indeclinable for i=1,#hinfo do if not m_table.contains(hinfo[i].stemetc, "indecl") and not m_table.contains(hinfo[i].adjectival, "yes") then index = i break end end if index == 0 then -- Then just non-indeclinable for i=1,#hinfo do if not m_table.contains(hinfo[i].stemetc, "indecl") then index = i break end end end -- Finally, do anything if index == 0 then index = 1 end -- Compute final heading local headings = args.per_word_headings[index] local categories = args.per_word_categories[index] if args.any_irreg then table.insert(headings, "irreg") insert_category(categories, "irregular ~", args.pos) end for _, case in ipairs(overridable_cases) do local is_pl = rfind(case, "_pl") if args.n == "s" and is_pl or args.n == "p" and not is_pl then -- Don't create singular categories when plural-only or vice-versa elseif overridable_only_cases_set[case] then if args.any_overridden[case] then insert_category(categories, "~ with " .. english_case_description(case), args.pos) end elseif args.any_irreg_case[case] then insert_category(categories, "~ with irregular " .. english_case_description(case), args.pos) end end local heading = args.manual and "" or "(<span style=\"font-size: smaller;\">[[Appendix:Russian nouns#Declension tables|" .. table.concat(headings, " ") .. "]]</span>)" args.heading = heading args.categories = categories args.genders = args.per_word_genders[index] end -------------------------------------------------------------------------- -- Main code -- -------------------------------------------------------------------------- -- Used by do_generate_forms(). local function arg1_is_stress(arg1) if not arg1 then return false end for _, arg in ipairs(rsplit(arg1, ",")) do if not rfind(arg, "^[a-f]'?'?$") then return false end end return true end -- Used by do_generate_forms(), handling a word joiner argument -- of the form 'join:JOINER'. local function extract_word_joiner(spec) word_joiner = rmatch(spec, "^join:(.*)$") assert(word_joiner) return com.split_russian_tr(word_joiner, "dopair") end local function determine_headword_gender(args, sgdc, gender) -- If gender unspecified, use normal gender of declension, except when -- adjectival nouns that are pluralia tantum, where the gender is -- mostly indeterminate (FIXME, not completely with old-style declensions -- but we don't handle that currently). if not gender then if sgdc.adj and args.thisn == "p" then gender = nil else gender = sgdc.g end end -- Determine headword genders gender = gender and gender ~= "none" and gender .. "-" or "" local plsuffix = args.n == "p" and "-p" or "" local hgens if args.a == "a" then hgens = {gender .. "an" .. plsuffix} elseif args.a == "i" then hgens = {gender .. "in" .. plsuffix} elseif args.a == "ai" then hgens = {gender .. "an" .. plsuffix, gender .. "in" .. plsuffix} else hgens = {gender .. "in" .. plsuffix, gender .. "an" .. plsuffix} end -- Insert into list of genders for _, hgen in ipairs(hgens) do insert_if_not(args.genders, hgen) end end function export.do_generate_forms(args, old) old = old or args.old args.old = old args.pos = args.pos or "noun" -- This is a list with each element corresponding to a word and -- consisting of a two-element list, ARG_SETS and JOINER, where ARG_SETS -- is a list of ARG_SET objects, one per alternative stem, and JOINER -- is a string indicating how to join the word to the next one. local per_word_info = {} -- Gather arguments into a list of ARG_SET objects, containing (potentially) -- elements 1, 2, 3, 4, corresponding to accent pattern, stem, declension -- type, pl stem and coming from consecutive numbered parameters. Sets of -- declension parameters are separated by the word "or". local arg_sets = {} -- Find maximum-numbered arg, allowing for holes local max_arg = 0 for k, v in pairs(args) do if type(k) == "number" and k > max_arg then max_arg = k end end -- Now gather the arguments. local offset = 0 local arg_set = {} for i=1,(max_arg + 1) do local end_arg_set = false local end_word = false -- FIXME, is this correct? local word_joiner if i == max_arg + 1 then end_arg_set = true end_word = true word_joiner = {""} elseif args[i] == "_" then end_arg_set = true end_word = true word_joiner = {" "} elseif args[i] == "-" then end_arg_set = true end_word = true word_joiner = {"-"} elseif args[i] and rfind(args[i], "^join:") then end_arg_set = true end_word = true word_joiner = extract_word_joiner(args[i]) elseif args[i] == "or" then end_arg_set = true end if end_arg_set then table.insert(arg_sets, arg_set) arg_set = {} offset = i if end_word then table.insert(per_word_info, {arg_sets, word_joiner}) arg_sets = {} end else -- If the first argument isn't stress, that means all arguments -- have been shifted to the left one. We want to shift them -- back to the right one, so we change the offset so that we -- get the same effect of skipping a slot in the arg set. if i - offset == 1 and not arg1_is_stress(args[i]) then offset = offset - 1 end if i - offset > 4 then error("Too many arguments for argument set: arg " .. i .. " = " .. (args[i] or "(blank)")) end arg_set[i - offset] = args[i] end end return generate_forms_1(args, per_word_info) end function export.do_generate_forms_multi(args, old) old = old or args.old args.old = old args.pos = args.pos or "noun" -- This is a list with each element corresponding to a word and -- consisting of a two-element list, ARG_SET and JOINER, where ARG_SET -- is a list of ARG_SET objects, one per alternative stem, and JOINER -- is a string indicating how to join the word to the next one. local per_word_info = {} -- Find maximum-numbered arg, allowing for holes (FIXME: Is this needed -- here? Will there be holes?) local max_arg = 0 for k, v in pairs(args) do if type(k) == "number" and k > max_arg then max_arg = k end end -- Gather arguments into a list of ARG_SET objects, containing -- (potentially) elements 1, 2, 3, corresponding to accent pattern, -- lemma+declension spec, pl stem, exactly as with do_generate_forms() -- and {{ru-noun-table}} except that the values come from a single argument -- of the form ACCENTPATTERN:LEMMADECL:PL where all but LEMMADECL may (and -- probably will be) omitted and LEMMADECL may be of the following forms: -- LEMMA (for a noun with empty decl spec), -- LEMMADECL (for a noun with non-empty decl spec beginning with a -- *, left paren or semicolon), -- LEMMA^DECL (for a noun with non-empty decl spec), -- LEMMA$ (for an indeclinable word) -- LEMMA+ (for an adjective with auto-detected decl class) -- LEMMA+DECL (for an adjective with explicit decl class) -- Sets of parameters for the same word are separated by the word "or". local arg_sets = {} local continue_arg_sets = true for i=1,(max_arg + 1) do local end_word = false local word_joiner local process_arg = false if i == max_arg + 1 then end_word = true word_joiner = {""} elseif args[i] == "-" then end_word = true word_joiner = {"-"} continue_arg_sets = true elseif rfind(args[i], "^join:") then end_word = true word_joiner = extract_word_joiner(args[i]) continue_arg_sets = true elseif args[i] == "or" then continue_arg_sets = true else if continue_arg_sets then continue_arg_sets = false else end_word = true word_joiner = {" "} end process_arg = true end if end_word then table.insert(per_word_info, {arg_sets, word_joiner}) arg_sets = {} end if process_arg then local vals = rsplit(args[i], ":") if #vals > 3 then error("Can't specify more than 3 colon-separated params of param set: " .. args[i]) end local arg_set = {} if arg1_is_stress(vals[1]) then arg_set[1] = vals[1] arg_set[2] = vals[2] arg_set[4] = vals[3] else arg_set[2] = vals[1] arg_set[4] = vals[2] end -- recognize indeclinable local indecl_stem = rmatch(arg_set[2], "^(.-)%$$") if indecl_stem then arg_set[2] = indecl_stem arg_set[3] = "$" else -- recognize adjective local adj_stem, adj_type = rmatch(arg_set[2], "^(.*)(%+.*)$") if adj_stem then arg_set[2] = adj_stem arg_set[3] = adj_type else -- recognize noun with ^ local noun_stem, noun_type = rmatch(arg_set[2], "^(.*)%^(.*)$") if noun_stem then arg_set[2] = noun_stem arg_set[3] = noun_type else -- recognize noun without ^ but with decl spec noun_stem, noun_type = rmatch(arg_set[2], "^(..-)([;*(].*)$") if noun_stem then arg_set[2] = noun_stem arg_set[3] = noun_type else -- noun without ^ or decl spec; nothing to do end end end end table.insert(arg_sets, arg_set) end end return generate_forms_1(args, per_word_info) end -- Implementation of do_generate_forms() and do_generate_forms_multi(), -- which have equivalent functionality but different calling sequence. -- Implementation of do_generate_forms() and do_generate_forms_multi(), -- which have equivalent functionality but different calling sequence, -- as well as show_z() for template {{ru-decl-noun-z}}, which has a -- subset of the functionality of the other two. generate_forms_1 = function(args, per_word_info) local orig_args if test_new_ru_noun_module then orig_args = mw.clone(args) end local pagename = args.pagename or mw.loadData("Module:headword/data").pagename local old = args.old local function verify_animacy_value(val) if not val then return nil end if val == "a" or val == "an" or val == "anim" then return "a" elseif val == "i" or val == "in" or val == "inan" then return "i" elseif val == "b" or val == "bi" or val == "both" or val == "ai" then return "ai" elseif val == "ia" then return "ia" end error("Animacy value " .. val .. " should be empty or a/an/anim (animate), i/in/inan (inanimate), b/bi/both/ai (bianimate, listing animate first), or ia (bianimate, listing inanimate first)") return nil end local function verify_number_value(val, allow_none) if not val then return nil end local short = usub(val, 1, 1) if short == "s" or short == "p" or short == "b" or (allow_none and short == "n" or false) then return short end if allow_none then error("Number value " .. val .. " should be empty or start with 's' (singular), 'p' (plural), 'b' (both) or 'n' (none)") else error("Number value " .. val .. " should be empty or start with 's' (singular), 'p' (plural), or 'b' (both)") end return nil end -- Verify and canonicalize animacy, number, prefix, suffix assert(#per_word_info >= 1) for i=1,#per_word_info do args["a" .. i] = verify_animacy_value(args["a" .. i]) args["n" .. i] = verify_number_value(args["n" .. i]) args["prefix" .. i] = com.split_russian_tr(args["prefix" .. i] or "", "dopair") args["suffix" .. i] = com.split_russian_tr(args["suffix" .. i] or "", "dopair") end args.a = verify_animacy_value(args.a) or "i" -- args.ndef, if set, is the default value for args.n; if unset, it defaults -- to "both". It is set to "singular" in ru-proper noun+. We store the value -- of args.n in args.orign before defaulting to args.ndef because we may -- change it to plural-only later on if it was unspecified (this happens if -- an individual word's lemma is plural), and to determine whether it was -- unspecified, we need the original value before defaulting. args.n = verify_number_value(args.n, "allow none") -- treat n=none like n=sg but set a flag so "singular" isn't displayed if args.n == "n" then args.n = "s" args.nonumber = true end args.ndef = verify_number_value(args.ndef) args.orign = args.n args.n = args.n or args.ndef args.prefix = com.split_russian_tr(args.prefix or "", "dopair") args.suffix = com.split_russian_tr(args.suffix or "", "dopair") -- Attach overall prefix to first per-word prefix, similarly for suffix args.prefix1 = com.concat_paired_russian_tr(args.prefix, args.prefix1) args["suffix" .. #per_word_info] = com.concat_paired_russian_tr( args["suffix" .. #per_word_info], args.suffix) -- Initialize non-word-specific arguments. -- -- The following is a list of WORD_INFO items, one per word, each of -- which is a two element list of WORD_FORMS (a table listing the forms for -- each case) and JOINER (a string, indicating how to join the word with -- the next one). args.per_word_info = {} -- List of HEADING_INFO items, one per word, containing the raw material -- used to generate the header. Initialized from 'args.heading_info', -- which is initialized in categorize_and_init_heading(). args.per_word_heading_info = {} -- List of CATEGORIES items, one per word, containing the categories -- used to initialize the page categories. Initialized from -- 'args.categories', which is initialized in categorize_and_init_heading(). args.per_word_categories = {} -- List of HEADINGS items, one per word, containing the actual words that -- go into the header if we were to use that word to construct the header. -- Comes from compute_heading(). We use this to generate the actual header -- string, which goes into 'args.heading' at the end (done in -- compute_overall_heading_categories_and_genders()). args.per_word_headings = {} -- List of GENDERS items, one per word, containing the headword genders -- for each word (where "headword gender" is in the format used in -- headwords and actually includes animacy and number as well, e.g. -- 'm-in' or 'f-an-p'). We end up selecting one such item and putting -- it into 'args.genders' at the end -- (in compute_overall_heading_categories_and_genders()). args.per_word_genders = {} args.any_overridden = {} args.any_non_nil = {} args.any_irreg = false args.any_irreg_case = {} local function insert_cat(cat) insert_category(args.categories, cat, args.pos) end args.internal_notes = {} local decl_sufs = old and declensions_old or declensions local decl_cats = old and declensions_old_cat or declensions_cat local intable = old and internal_notes_table_old or internal_notes_table -- Default lemma defaults to previous lemma, first one to page name. -- FIXME: With multiple words, we should probably split the page name -- on spaces and default each word in turn local default_lemma local all_stresses_seen -- Made into a function to avoid having to indent a lot of code. -- Process a single arg set of a single word. This inserts the forms -- for the word into args.forms and sets categories and tracking pages. local function do_arg_set(arg_set, n, islast) local stress_arg = arg_set[1] local decl = arg_set[3] or "" local pl, pltr if arg_set[4] then pl, pltr = com.split_russian_tr(arg_set[4]) end -- Extract special markers from declension class. if decl == "manual" then decl = "$" args.manual = true if #per_word_info > 1 or #per_word_info[1][1] > 1 then error("Can't specify multiple words or argument sets when manual") end if pl then error("Can't specify optional stem parameters when manual") end end decl, args.jo_special = rsubb(decl, "([^/%a])ё$", "%1") if not args.jo_special then decl, args.jo_special = rsubb(decl, "([^/%a])ё([^/%a])", "%1%2") end decl, args.want_sc1 = rsubb(decl, "%(1%)", "") decl, args.alt_gen_pl = rsubb(decl, "%(2%)", "") decl, args.reducible = rsubb(decl, "%*", "") decl, args.soft_n = rsubb(decl, "%(нь%)", "") decl = rsub(decl, ";", "") -- Get the lemma. local lemma = args.manual and "-" or arg_set[2] or default_lemma if not lemma then error("Lemma in first argument set must be specified") end default_lemma = lemma local lemmatr lemma, lemmatr = com.split_russian_tr(lemma) -- If we're conjugating a suffix, insert a pseudoconsonant at the beginning -- of all forms, so they get conjugated as if ending in a consonant. -- We remove the pseudoconsonant later. local is_suffix = lemma ~= "-" and rfind(lemma, "^%-") args.any_suffix = args.any_suffix or is_suffix local asif_prefix = args["asif_prefix" .. n] or args.asif_prefix or is_suffix and PSEUDOCONS if asif_prefix then lemma = rsub(lemma, "^%-", "-" .. asif_prefix) if lemmatr then lemmatr = rsub(lemmatr, "^%-", "-" .. com.translit(asif_prefix)) end end args.thisa = args["a" .. n] or args.a args.thisn = args["n" .. n] or args.n -- Check for explicit allow-unaccented indication. local allow_unaccented lemma, allow_unaccented = rsubb(lemma, "^%*", "") args.allow_unaccented = args.allow_unaccented or allow_unaccented if args.allow_unaccented then track("allow-unaccented") end args.orig_lemma = lemma lemma = m_links.remove_links(lemma) args.lemma_no_links = lemma args.lemmatr = lemmatr if args.lemma then -- Explicit lemma given. args.explicit_lemma, args.explicit_lemmatr = com.split_russian_tr(args.lemma) end -- Treat suffixes without an accent, and suffixes with an accent on the -- initial hyphen, as if they were preceded with a *, which overrides -- all the logic that normally (a) normalizes the accent, and (b) -- complains about multisyllabic words without an accent. Don't do this -- if lemma is just -, which is used specially in manual declension -- tables (e.g. сто, три). if lemma ~= "-" and (rfind(lemma, "^%-" .. AC) or (com.is_unstressed(lemma) and rfind(lemma, "^%-"))) then args.allow_unaccented = true end -- Convert lemma and decl arg into stem and canonicalized decl. -- This will autodetect the declension from the lemma if an explicit -- decl isn't given. local stem, tr, gender, was_accented, was_plural, was_autodetected if rfind(decl, "^%+") then stem, tr, decl, gender, was_accented, was_plural, was_autodetected = detect_adj_type(lemma, lemmatr, decl, old) else stem, tr, decl, gender, was_accented, was_plural, was_autodetected = determine_decl(lemma, lemmatr, decl, args) end if was_plural then args.n = args.orign or "p" args.thisn = args["n" .. n] or args.n elseif decl ~= "$" then args.thisn = args.thisn or "b" end args.explicit_gender = gender -- If allow-unaccented not given, maybe check for missing accents. if not args.allow_unaccented and not stress_arg and was_autodetected and com.needs_accents(lemma) then -- If user gave the full word and expects us to determine the -- declension and stress, the word should have an accent on the -- stem or ending. We have a separate check farther below for -- an accent on a multisyllabic stem, after stripping off any -- ending; but this way we get an error if the user e.g. writes -- "гора" without an accent rather than assuming it's stem -- stressed, as would otherwise happen. error("Lemma must have an accent in it: " .. lemma) end -- If stress not given, auto-determine; else validate/canonicalize -- stress arg, override in certain cases and convert to list. if not stress_arg then stress_arg = {detect_stress_pattern(stem, decl, decl_cats, args.reducible, was_plural, was_accented)} else stress_arg = rsplit(stress_arg, ",") for i=1,#stress_arg do local stress = stress_arg[i] stress = override_stress_pattern(decl, stress) if not stress_patterns[stress] then error("Unrecognized accent pattern " .. stress) end stress_arg[i] = stress end end -- parse slash decl to list local sub_decls if rfind(decl, "/") then track("mixed-decl") insert_cat("~ with mixed declension") local indiv_decls = rsplit(decl, "/") -- Should have been caught in canonicalize_decl() assert(#indiv_decls == 2) sub_decls = {{indiv_decls[1], "sg"}, {indiv_decls[2], "pl"}} else sub_decls = {{decl}} end -- Get singular declension and corresponding category. local sgdecl = sub_decls[1][1] local sgdc = decl_cats[sgdecl] assert(sgdc) -- Compute headword gender(s). We base it off the singular declension -- if we have a slash declension -- it's the best we can do. determine_headword_gender(args, sgdc, gender) local original_stem, original_tr = stem, tr local original_pl, original_pltr = pl, pltr -- Loop over accent patterns in case more than one given. for _, stress in ipairs(stress_arg) do args.suffixes = {} stem, tr = original_stem, original_tr local bare, baretr local stem_for_bare, tr_for_bare pl, pltr = original_pl, original_pltr insert_if_not(all_stresses_seen, stress) local stem_was_unstressed = com.is_unstressed(stem) -- If special case ;ё was given and stem is unstressed, -- add ё to the stem now; but don't let this interfere with -- restressing, to handle cases like железа́ with gen pl желёз -- but nom pl же́лезы. if stem_was_unstressed and args.jo_special then -- Beware, Cyrillic еЕ in first rsub, Latin eE in second local new_stem = rsub(stem, "([еЕ])([^еЕ]*)$", function(e, rest) return (e == "Е" and "Ё" or "ё") .. rest end ) if stem == new_stem then error("No е in stem to replace with ё") end stem = new_stem if tr then local subbed -- e after j -> o, e not after j -> jo; don't just convert e -> jo -- and then map jjo -> jo because we want to preserve double j tr, subbed = rsubb(tr, "([jJ])([eE])([^eE]*)$", function(j, e, rest) return j .. (e == "E" and "O" or "o") .. AC .. rest end ) if not subbed then tr = rsub(tr, "([eE])([^eE]*)$", function(e, rest) return (e == "E" and "Jo" or "jo") .. AC .. rest end ) end tr = com.j_correction(tr) end -- This is used to handle железа́ with gen pl желёз and nom pl -- же́лезы. We have two stressed stems, one for the gen pl and -- one for the remaining pl cases, and the variable 'stem' can -- handle only one, so we put the second (gen pl) stem in -- stem_for_bare, which goes into the value of 'bare' (used -- only for gen pl here). stem_for_bare, tr_for_bare = stem, tr end -- Maybe add stress to the stem, depending on whether the -- stem was unstressed and the stress pattern. Stem pattern f -- and variants call for initial stress (голова́ -> го́ловы); -- stem pattern d and variants call for stem-final stress -- (сапожо́к -> сапо́жки). Stem patterns b and b' apparently -- call for stem-final stress as well but it's unlikely to -- make much of a difference (pattern b' only occurs in 3rd-decl -- feminines, which should already have stress in the stem, -- and the only place pattern b gets stem stress is in bare -- forms, i.e. nom sg and/or gen pl depending on the decl type, -- and nom sg stress should already be in the lemma while -- gen pl stress is handled by a different ending-stressing -- mechanism in attach_unstressed(); however, the user is -- free to leave a masc or 3rd-decl fem lemma completely -- unstressed with pattern b, and then the stem-final stress -- *will* make a difference). local function restress_stem(stem, tr, stress, stem_unstressed) -- If the user has indicated they purposely are leaving the -- word unstressed by putting a * at the beginning of the main -- stem, leave it unstressed. This might indicate lack of -- knowledge of the stress or a truly unaccented word -- (e.g. an unaccented suffix). if args.allow_unaccented then return stem, tr end if tr and com.is_unstressed(stem) ~= com.is_unstressed(tr) then error("Stem " .. stem .. " and translit " .. tr .. " must have same accent pattern") end -- it's safe to accent monosyllabic stems if com.is_monosyllabic(stem) then stem, tr = com.make_ending_stressed(stem, tr) -- For those patterns that are ending-stressed in the singular -- nominative (and hence are likely to be expressed without an -- accent on the stem) it's safe to put a particular accent on -- the stem depending on the stress type. Otherwise, give an -- error if no accent. elseif stem_unstressed then if rfind(stress, "^f") then stem, tr = com.make_beginning_stressed(stem, tr) elseif (rfind(stress, "^[bd]") or args.thisn == "p" and ending_stressed_pl_patterns[stress]) then stem, tr = com.make_ending_stressed(stem, tr) elseif com.needs_accents(stem) then error("Stem " .. stem .. " requires an accent") end end return stem, tr end stem, tr = restress_stem(stem, tr, stress, stem_was_unstressed) -- Leave pl unaccented if user wants this; see restress_stem(). if pl and not args.allow_unaccented then if pltr and com.is_unstressed(pl) ~= com.is_unstressed(pltr) then error("Plural stem " .. pl .. " and translit " .. pltr .. " must have same accent pattern") end if com.is_monosyllabic(pl) then pl, pltr = com.make_ending_stressed(pl, pltr) end -- I think this is safe. if com.needs_accents(pl) then if ending_stressed_pl_patterns[stress] then pl, pltr = com.make_ending_stressed(pl, pltr) elseif not args.allow_unaccented then error("Plural stem " .. pl .. " requires an accent") end end end local resolved_bare, resolved_baretr -- Handle (de)reducibles -- FIXME! We are dereducing based on the singular declension. -- In a slash declension things can get weird and we don't -- handle that. We are also computing the bare value from the -- singular stem, and again things can get weird with a plural -- stem. Note that we don't compute a bare value unless we have -- to (either (de)reducible or stress pattern f/f'/f'' combined -- with ё special case); the remaining times we generate the bare -- value directly from the plural stem. if args.reducible and not sgdc.ignore_reduce then -- Zaliznyak treats all nouns in -ье and -ья as being -- reducible. We handle this automatically and don't require -- the user to specify this, but ignore it if so for -- compatibility. if is_reducible(sgdc) then -- If we derived the stem from a nom pl form, then -- it's already reduced, and we need to dereduce it to -- get a bare form; otherwise the stem comes from the -- nom sg and we need to reduce it to get the real stem. if was_plural then resolved_bare, resolved_baretr = export.dereduce_nom_sg_stem(stem, tr, sgdc, stress, old, "error") else resolved_bare, resolved_baretr = stem, tr stem, tr = export.reduce_nom_sg_stem(stem, tr, sgdecl, args.soft_n, "error") -- Stem will be unstressed if stress was on elided -- vowel; restress stem the way we did above. (This is -- needed in at least one word, сапожо́к 3*d(2), with -- plural stem probably сапо́жк- and gen pl probably -- сапо́жек.) stem, tr = restress_stem(stem, tr, stress, com.is_unstressed(stem)) if stress ~= "a" and stress ~= "b" and args.alt_gen_pl and not pl then -- Nouns like рожо́к, глазо́к of type 3*d(2) have -- gen pl's ро́жек, гла́зок; to handle this, -- dereduce the reduced stem and store in a -- special place. args.gen_pl_bare, args.gen_pl_baretr = export.dereduce_nom_sg_stem(stem, tr, sgdc, stress, old, "error") end end elseif is_dereducible(sgdc) then resolved_bare, resolved_baretr = export.dereduce_nom_sg_stem(stem, tr, sgdc, stress, old, "error") else error("Declension class " .. sgdecl .. " not (de)reducible") end elseif stem_for_bare and stem ~= stem_for_bare then resolved_bare, resolved_baretr = add_bare_suffix(stem_for_bare, tr_for_bare, old, sgdc, false) end -- Leave unaccented if user wants this; see restress_stem(). -- FIXME, we no longer allow the user to specify the bare value -- so it's unclear if this is needed any more. if resolved_bare and not args.allow_unaccented then if resolved_baretr and com.is_unstressed(resolved_bare) ~= com.is_unstressed(resolved_baretr) then error("Resolved bare stem " .. resolved_bare .. " and translit " .. resolved_baretr .. " must have same accent pattern") end if com.is_monosyllabic(resolved_bare) then resolved_bare, resolved_baretr = com.make_ending_stressed(resolved_bare, resolved_baretr) else if com.needs_accents(resolved_bare) then error("Resolved bare stem " .. resolved_bare .. " requires an accent") end end end args.stem, args.stemtr = stem, tr args.bare, args.baretr = resolved_bare, resolved_baretr args.ustem, args.ustemtr = com.make_unstressed_once(stem, tr) if pl then args.pl, args.pltr = pl, pltr else args.pl, args.pltr = stem, tr end args.upl, args.upltr = com.make_unstressed_once(args.pl, args.pltr) -- Special hack for любо́вь and other reducible 3rd-fem nouns, -- which have the full stem in the ins sg args.ins_sg_stem = sgdecl == "ь-f" and args.reducible and resolved_bare args.ins_sg_tr = sgdecl == "ь-f" and args.reducible and resolved_baretr -- Loop over declension classes (we may have two of them, one for -- singular and one for plural, in the case of a mixed declension -- class of the form SGDECL/PLDECL). for _,decl_spec in ipairs(sub_decls) do local orig_decl = decl_spec[1] local number = decl_spec[2] local real_decl = determine_stress_variant(orig_decl, stress) real_decl = determine_stem_variant(real_decl, number == "pl" and args.pl or args.stem) -- sanity checking; errors should have been caught in -- canonicalize_decl() assert(decl_cats[real_decl], "real_decl " .. real_decl .. " nonexistent") assert(decl_sufs[real_decl], "real_decl " .. real_decl .. " nonexistent") tracking_code(stress, orig_decl, real_decl, args, n, islast) do_stress_pattern(stress, args, real_decl, number, n, islast) -- handle internal notes local internal_note = intable[real_decl] if internal_note then insert_if_not(args.internal_notes, internal_note) end end categorize_and_init_heading(stress, decl, args, n, islast) end end local n = 0 for _, word_info in ipairs(per_word_info) do n = n + 1 local islast = n == #per_word_info local arg_sets, joiner = word_info[1], word_info[2] args.forms = {} args.heading_info = {animacy={}, number={}, gender={}, stress={}, stemetc={}, adjectival={}, reducible={}} args.categories = {} args.genders = {} args.this_any_non_nil = {} args.any_suffix = false if #arg_sets > 1 then track("multiple-arg-sets") insert_cat("~ with multiple argument sets") track("multiple-declensions") insert_cat("~ with multiple declensions") end default_lemma = pagename all_stresses_seen = {} -- Loop over all arg sets. for _, arg_set in ipairs(arg_sets) do do_arg_set(arg_set, n, islast) end if #all_stresses_seen > 1 then track("multiple-accent-patterns") insert_cat("~ with multiple accent patterns") track("multiple-declensions") insert_cat("~ with multiple declensions") end table.insert(args.per_word_heading_info, args.heading_info) table.insert(args.per_word_categories, args.categories) local headings = compute_heading(args) table.insert(args.per_word_headings, headings) table.insert(args.per_word_genders, args.genders) handle_forms_and_overrides(args, n, islast) if args.any_suffix then -- If we're conjugating a suffix, remove the pseudoconsonant or asif_prefix -- that we previously inserted at the beginning. local asif_prefix = args["asif_prefix" .. n] or args.asif_prefix or PSEUDOCONS local asif_prefix_tr = com.translit(asif_prefix) for _, case in ipairs(all_cases) do if args.forms[case] then local newforms = {} for _, form in ipairs(args.forms[case]) do local formru = form[1] local formtr = form[2] formru = rsub(formru, "^%-" .. asif_prefix, "-") if formtr then formtr = rsub(formtr, "^%-" .. asif_prefix_tr, "-") end if formru == "-" then -- if no ending, insert "(no suffix)". table.insert(newforms, {"(no suffix)"}) else table.insert(newforms, {formru, formtr}) end end args.forms[case] = newforms end end end table.insert(args.per_word_info, {args.forms, joiner}) end handle_overall_forms_and_overrides(args) compute_overall_heading_categories_and_genders(args) for _, case in ipairs(all_cases) do if args[case] then for _, form in ipairs(args[case]) do local ru, tr = form[1], form[2] local ruentry, runotes = m_table_tools.separate_notes(ru) ruentry = m_links.remove_links(ruentry) if rfind(ulower(ruentry), latin_text_class) then --error("Found Latin text " .. ruentry .. " in case " .. case) track("latin-text") track("latin-text/" .. case) end end end end -- Test code to compare existing module to new one. if test_new_ru_noun_module then local m_new_ru_noun = require("Module:User:Benwing2/ru-noun") local newargs = m_new_ru_noun.do_generate_forms(orig_args, old) local difdecl = false for _, case in ipairs(all_cases) do local arg = args[case] local newarg = newargs[case] local is_pl = rfind(case, "_pl") if args.thisn == "s" and is_pl or args.thisn == "p" and not is_pl then -- Don't need to check cases that won't be displayed. elseif not m_table.deepEquals(arg, newarg) then local monosyl_accent_diff = false -- Differences only in monosyllabic accents. Enable if we -- change the algorithm for these. --if arg and newarg and #arg == 1 and #newarg == 1 then -- local ru1, tr1 = arg[1][1], arg[1][2] -- local ru2, tr2 = newarg[1][1], newarg[1][2] -- if com.is_monosyllabic(ru1) and com.is_monosyllabic(ru2) then -- ru1, tr1 = com.remove_accents(ru1, tr1) -- ru2, tr2 = com.remove_accents(ru2, tr2) -- if ru1 == ru2 and tr1 == tr2 then -- monosyl_accent_diff = true -- end -- end --end if monosyl_accent_diff then track("monosyl-accent-diff") difdecl = true else -- Uncomment this to display the particular case and -- differing forms. --error(case .. " " .. (arg and com.concat_forms(arg) or "nil") .. " || " .. (newarg and com.concat_forms(newarg) or "nil")) track("different-decl") difdecl = true end break end end if not difdecl then track("same-decl") end end return args end -- Implementation of main entry point local function do_show(frame, old) local args = clone_args(frame) local args = export.do_generate_forms(args, old) return make_table(args) .. m_utilities.format_categories(args.categories, lang) end -- The main entry point for modern declension tables. function export.show(frame) return do_show(frame, false) end -- The main entry point for old declension tables. function export.show_old(frame) return do_show(frame, true) end -- Implementation of new entry point, esp. for multiple words local function do_show_multi(frame) local args = clone_args(frame) local args = export.do_generate_forms_multi(args) return make_table(args) .. m_utilities.format_categories(args.categories, lang) end -- The new entry point, esp. for multiple words (but works fine for -- single words). function export.show_multi(frame) return do_show_multi(frame) end local function get_form(forms, preserve_links, raw) local canon_forms = {} for _, form in ipairs(forms) do if raw then local ru, tr = form[1], form[2] ru = rsub(ru, "|", "<!>") if tr then tr = rsub(tr, "|", "<!>") end insert_if_not(canon_forms, {ru, tr}) else local ru, tr = form[1], form[2] local ruentry, runotes = m_table_tools.separate_notes(ru) -- Skip hypothetical forms (but include in the raw versions) if not rfind(runotes, HYPMARKER) then local trentry, trnotes if tr then trentry, trnotes = m_table_tools.separate_notes(tr) end if not preserve_links then ruentry = m_links.remove_links(ruentry) end ruentry = rsub(ruentry, "|", "<!>") if trentry then trentry = rsub(trentry, "|", "<!>") end insert_if_not(canon_forms, {ruentry, trentry}) end end end return com.concat_forms(canon_forms) end local function case_will_be_displayed(args, case) local ispl = rfind(case, "_pl") local caseok = true if args.n == "p" then caseok = ispl elseif args.n == "s" then caseok = not ispl end for _, override_case in ipairs(overridable_only_cases) do if case == override_case and not args.any_overridden[override_case] then caseok = false break end end if args.a == "a" or args.a == "i" then if rfind(case, "_[ai]n") then caseok = false end else -- bianimate -- don't include inanimate/animate variants if combined variant exists -- (typically because inanimate/animate variants are the same); -- FIXME: This could conceivably be different from how the display -- code works, which just checks that the inanimate/animate variants -- are the same when deciding whether to display them, in particular -- if there is an override. Here we are following the algorithm of -- handle_overall_forms_and_overrides(). if (case == "acc_sg_in" or case == "acc_sg_an") and args.acc_sg or (case == "acc_pl_in" or case == "acc_pl_an") and args.acc_pl then caseok = false end end if not args[case] then caseok = false end return caseok end local function concat_case_args(args, do_all, raw) local ins_text = {} for _, case in ipairs(do_all and all_cases or overridable_cases) do if case_will_be_displayed(args, case) then local forms = get_form(args[case], rfind(case, "_linked"), raw) if forms ~= "" then table.insert(ins_text, case .. (raw and "_raw" or "") .. "=" .. forms) end end end return table.concat(ins_text, "|") end -- The entry point for 'ru-noun-forms' to generate all noun forms. -- This returns a single string, with | separating arguments and named -- arguments of the form NAME=VALUE. function export.generate_forms(frame) local args = clone_args(frame) args = export.do_generate_forms(args, false) return concat_case_args(args) end -- The entry point to generate multiple sets of noun forms. This is a hack -- to speed up calling from a bot, where we often want to compare old and new -- argument results to make sure they're the same. Each set of arguments is -- jammed together into a single argument with individual values separated by -- <!>; named arguments are of the form NAME<->VALUE. The return value for -- each set of arguments is as in export.generate_forms(), and the return -- values are concatenated with <!> separating them. NOTE: This will fail if -- the exact sequences <!> or <-> happen to occur in values (which is unlikely, -- esp. as we don't even use the characters <, ! or > for anything) and aren't -- HTML-escaped. function export.generate_multi_forms(frame) local retvals = {} for _, argset in ipairs(frame.args) do local args = {} local i = 0 local argvals = rsplit(argset, "<!>") for _, argval in ipairs(argvals) do local split_arg = rsplit(argval, "<%->") if #split_arg == 1 then i = i + 1 args[i] = ine(split_arg[1]) else assert(#split_arg == 2) args[split_arg[1]] = ine(split_arg[2]) end end args = export.do_generate_forms(args, false) table.insert(retvals, concat_case_args(args)) end return table.concat(retvals, "<!>") end -- The entry point for 'ru-noun-form' to generate a particular noun form. function export.generate_form(frame) local args = clone_args(frame) if not args.form then error("Must specify desired form using form=") end local form = args.form if not m_table.contains(all_cases, form) then error("Unrecognized form " .. form) end local args = export.do_generate_forms(args, false) if not args[form] then return "" else return get_form(args[form]) end end -- The entry point for generating arguments of various sorts, including -- the case forms, gender, number and animacy. function export.generate_args(frame) local args = clone_args(frame) args = export.do_generate_forms(args, false) local retargs = {} table.insert(retargs, concat_case_args(args, "doall")) table.insert(retargs, concat_case_args(args, "doall", "raw")) table.insert(retargs, "g=" .. table.concat(args.genders, ",")) -- The following is correct even with ndef because if ndef is -- set we will set it in args.n. table.insert(retargs, "n=" .. (args.n or "b")) table.insert(retargs, "a=" .. (args.a or "i")) return table.concat(retargs, "|") end -- The entry point for compatibility with {{ru-decl-noun-z}}. function export.show_z(frame) local args = clone_args(frame) local stem = args[1] local stress = args[2] local specific = args[4] or "" -- Parse gender/animacy spec local gender, anim = rmatch(args[3], "^([mfn])-([a-z]+)") if not gender then error("Unrecognized gender/anim spec " .. args[3]) end if anim ~= "an" and anim ~= "in" then anim = "both" end args.a = anim -- Handle specific specific = rsub(specific, "ё", ";ё") -- Compute decl; special case for семьянин (perhaps not necessary) local decl = com.make_unstressed_once(stem) == "семьянин" and "#" .. specific or gender .. specific -- Handle overrides args.pre_sg = args.prp_sg args.pre_pl = args.prp_pl args.notes = args.note if args.par then args.par = "+" end if args.loc then if args.loc == "в" then args.loc = "в +" elseif args.loc == "на" then args.loc = "на +" else args.loc = "в +,на +" end end local arg_set = {} table.insert(arg_set, stress) table.insert(arg_set, stem) table.insert(arg_set, decl) local per_word_info = {{{arg_set}, ""}} return generate_forms_1(args, per_word_info) end -------------------------------------------------------------------------- -- Autodetection and lemma munging -- -------------------------------------------------------------------------- -- Attempt to detect the type of the lemma based on its ending, separating -- off the stem and the ending. GENDER must be present with -ь and plural -- stems, and is otherwise ignored. Return up to three values: The stem -- (lemma minus ending), the singular lemma ending, and if the lemma was -- plural, the plural lemma ending. If the lemma was singular, the singular -- lemma ending will contain any user-given accents; likewise, if the -- lemma was plural, the plural ending will contain such accents. -- VARIANT comes from the declension spec and controls certain declension -- variants. local function detect_lemma_type(lemma, tr, gender, args, variant) local base, ending = rmatch(lemma, "^(.*)([еЕ]" .. AC .. ")$") -- accented if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*[" .. com.sib_c .. "])([еЕ])$") -- unaccented if base then if variant == "-ище" and not rfind(lemma, "[щЩ][еЕ]$") then error("With declension variant -ище, lemma should end in -ще: " .. lemma) end return base, com.strip_tr_ending(tr, ending), variant == "-ище" and "(ищ)е-и" or "о" end if variant == "-ишко" then base, ending = rmatch(lemma, "^(.*[шШ][кК])([оО])$") -- unaccented if not base then error("With declension variant -ишко, lemma should end in -шко: " .. lemma) end return base, com.strip_tr_ending(tr, ending), "(ишк)о-и" end if variant == "-ин" then base, ending = rmatch(lemma, "^(.*)([иИ][" .. AC .. GR .. "]?[нН][ъЪ]?)$") -- maybe accented if not base then error("With declension variant -ин, lemma should end in -ин(ъ): " .. lemma) end return base, com.strip_tr_ending(tr, ending), ulower(ending) end -- Now autodetect -ин; only animate and in -анин/-янин base, ending = rmatch(lemma, "^(.*[аАяЯ][" .. AC .. GR .. "]?[нН])([иИ][" .. AC .. GR .. "]?[нН][ъЪ]?)$") -- Need to check the animacy to avoid nouns like маиганин, цианин, -- меланин, соланин, etc. if base and args.thisa == "a" then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*)([ёЁ]" .. AC .. "?[нН][оО][кК][ъЪ]?)$") if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*[" .. com.sib_c .. "])([оО]" .. AC .. "[нН][оО][кК][ъЪ]?)$") if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*)([ёЁ]" .. AC .. "?[нН][оО][чЧ][еЕ][кК][ъЪ]?)$") if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*[" .. com.sib_c .. "])([оО]" .. AC .. "[нН][оО][чЧ][еЕ][кК][ъЪ]?)$") if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*)([мМ][яЯ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end --recognize plural endings if gender == "n" then base, ending = rmatch(lemma, "^(.*)([ьЬ][яЯ][" .. AC .. GR .. "]?)$") if base then -- Don't do this; о/-ья is too rare -- error("Ambiguous plural lemma " .. lemma .. " in -ья, singular could be -о or -ье/-ьё; specify the singular") return base, com.strip_tr_ending(tr, ending), "ье", ending end base, ending = rmatch(lemma, "^(.*)([аяАЯ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), rfind(ending, "[аА]") and "о" or "е", ending end base, ending = rmatch(lemma, "^(.*)([ыиЫИ][" .. AC .. GR .. "]?)$") if base then if rfind(ending, "[ыЫ]") or rfind(base, "[" .. com.sib .. com.velar .. "]$") then return base, com.strip_tr_ending(tr, ending), "о-и", ending else -- FIXME, should we return a slash declension? error("No neuter declension е-и available; use a slash declension") end end end if gender == "f" then base, ending = rmatch(lemma, "^(.*)([ьЬ][иИ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), "ья", ending end end -- Recognize masculines with irregular plurals, but only if the user -- either explicitly specified that this noun is plural (n=p) or -- specifically requested the irregular plural. This is necessary -- because some masculine nouns have feminine endings, which look -- like irregular plurals. if gender == "m" then if args.thisn == "p" or variant == "-ья" then base, ending = rmatch(lemma, "^(.*)([ьЬ][яЯ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), (args.old and "ъ-ья" or "-ья"), ending end end if args.thisn == "p" or args.want_sc1 then base, ending = rmatch(lemma, "^(.*)([аА][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), (args.old and "ъ-а" or "-а"), ending end base, ending = rmatch(lemma, "^(.*)([яЯ][" .. AC .. GR .. "]?)$") if base then if rfind(base, "[" .. com.vowel .. "][" .. AC .. GR .. "]?$") then return base, com.strip_tr_ending(tr, ending), "й-я", ending else return base, com.strip_tr_ending(tr, ending), "ь-я", ending end end end end if gender == "m" or gender == "f" then base, ending = rmatch(lemma, "^(.*[" .. com.sib .. com.velar .. "])([иИ][" .. AC .. GR .. "]?)$") if not base then base, ending = rmatch(lemma, "^(.*)([ыЫ][" .. AC .. GR .. "]?)$") end if base then return base, com.strip_tr_ending(tr, ending), gender == "m" and (args.old and "ъ" or "") or "а", ending end base, ending = rmatch(lemma, "^(.*[" .. com.vowel .. "й][" .. AC .. GR .. "]?)([иИ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), gender == "m" and "й" or "я", ending end base, ending = rmatch(lemma, "^(.*)([иИ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), gender == "m" and "ь-m" or "я", ending end end if gender == "3f" then base, ending = rmatch(lemma, "^(.*)([иИ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), "ь-f", ending end end -- end of recognize-plurals code base, ending = rmatch(lemma, "^(.*)([ьЬ][яеёЯЕЁ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*)([йаяеоёъЙАЯЕОЁЪ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*)([ьЬ])$") if base then if gender == "m" or gender == "f" then return base, com.strip_tr_ending(tr, ending), "ь-" .. gender elseif gender == "3f" then return base, com.strip_tr_ending(tr, ending), "ь-f" else error("Need to specify gender m or f with lemma in -ь: ".. lemma) end end if rfind(lemma, "[ыиЫИ][" .. AC .. GR .. "]?$") then error("If this is a plural lemma, gender must be specified: " .. lemma) elseif rfind(lemma, "[иИіІуУыЫѣѢэЭюЮѵѴ]" .. DIA .. "?[" .. AC .. GR .. "]?$") then error("Don't know how to decline lemma ending in this type of vowel: " .. lemma) end return lemma, tr, "" end local plural_variant_detection_map = { [""] = {["-ья"]="-ья"}, ["ъ"] = {["-ья"]="ъ-ья"}, } local special_case_1_to_plural_variant = { [""] = "-а", ["ъ"] = "ъ-а", ["й"] = "й-я", ["ь-m"] = "ь-я", ["о"] = "о-и", -- these last two are here to avoid getting errors in that checks for -- compatibility with special case 1; the basic variants of these decls -- don't actually exist ["(ишк)о"] = "(ишк)о-и", ["(ищ)е"] = "(ищ)е-и", } local function map_decl(decl, fun) if rfind(decl, "/") then local split_decl = rsplit(decl, "/") if #split_decl ~= 2 then error("Mixed declensional class " .. decl .. "needs exactly two classes, singular and plural") end return fun(split_decl[1]) .. "/" .. fun(split_decl[2]) else return fun(decl) end end -- Canonicalize decl class into non-accented and alias-resolved form; -- but note that some canonical decl class names with an accent in them -- (e.g. е́, not the same as е, whose accented version is ё; and various -- adjective declensions). local function canonicalize_decl(decl, old) local function do_canon(decl) -- remove accents, but not from е́ (for adj decls, accent matters -- as well but we handle that by mapping the accent to a stress pattern -- and then to the accented version in determine_stress_variant()) if decl ~= "е́" then decl = com.remove_accents(decl) end local decl_aliases = old and declensions_old_aliases or declensions_aliases local decl_cats = old and declensions_old_cat or declensions_cat if decl_aliases[decl] then -- If we find an alias, map it. decl = decl_aliases[decl] elseif not decl_cats[decl] then error("Unrecognized declension class " .. decl) end return decl end return map_decl(decl, do_canon) end -- Attempt to determine the actual declension (including plural variants) -- based on a combination of the declension the user specified, what can be -- detected from the lemma, and special case (1), if given in the declension. -- DECL is the value the user passed for the declension field, after -- extraneous annotations (special cases (1) and (2), * for reducible, -- ё for ё/ё alternation and a ; that may precede ё) have been stripped off. -- What's left is one of the following: -- -- 1. Blank, meaning to autodetect the declension from the lemma -- 2. A hyphen followed by a declension variant (-ья, -ин, -ишко, -ище; see -- long comment at top of file) -- 3. A gender (m, f, n, 3f) -- 4. A gender plus declension variant (e.g. f-ья) -- 5. An actual declension, possibly including a plural variant (e.g. о-и) or -- a slash declension (e.g. я/-ья, used for the noun дядя). -- -- Return seven args: stem (lemma minus ending), translit, canonicalized -- declension, explicitly specified gender if any (m, f, n or nil), whether -- the specified declension or detected ending was accented, whether the -- detected ending was pl, and whether the declension was autodetected -- (corresponds to cases where a full word with ending attached is required -- in the lemma field). "Canonicalized" means after autodetection, with -- accents removed, with any aliases mapped to their canonical versions -- and with any requested declension variants applied. The result is either a -- declension that will have a categorization entry (in declensions_cat[] or -- declensions_old_cat[]) or a slash declension where each part similarly has -- a categorization entry. -- -- Note that gender is never required when an explicit declension is given, -- and in connection with stem autodetection is required only when the lemma -- either ends in -ь or is plural. determine_decl = function(lemma, tr, decl, args) -- Assume we're passed a value for DECL of types 1-4 above, and -- fetch gender and requested declension variant. local stem local want_ya_plural, orig_pl_ending, variant local was_autodetected local gender = rmatch(decl, "^(3?[mfn]?)$") if not gender then gender, variant = rmatch(decl, "^(3?[mfn]?)(%-[^%-]+)$") -- But be careful with explicit declensions like -а that look like -- variants without gender (FIXME, eventually we should maybe do -- something about the potential ambiguity). if gender == "" and not m_table.contains({"-ья", "-ин", "-ишко", "-ище"}, variant) then gender, variant = nil, nil end end -- If DECL is of type 1-4, handle declension variants and detect -- the actual declension from the lemma. if gender then -- Check for declension variants if variant then if variant == "-ья" then want_ya_plural = "-ья" else -- Sanity-check remaining declension variants, which need -- specific values of animacy, gender and special-case (1) local sc1_needed local animate_needed if variant == "-ишко" then animate_needed = false sc1_needed = true elseif variant == "-ище" then animate_needed = true sc1_needed = true elseif variant == "-ин" then animate_needed = true sc1_needed = false else -- WARNING: If adding another variant, you need to also -- add to the list farther above. error("Unrecognized declension variant " .. variant .. ", should be -ья, -ин, -ишко or -ище") end if sc1_needed and not args.want_sc1 then error("Declension variant " .. variant .. " must be used with special case (1)") elseif sc1_needed == false and args.want_sc1 then error("Declension variant " .. variant .. " must not be used with special case (1)") end if animate_needed and args.thisa ~= "a" then error("Declension variant " .. variant .. " must be specified as animate") elseif animate_needed == false and args.thisa == "a" then error("Declension variant " .. variant .. " must not be specified as animate") end if gender ~= "" and gender ~= "m" then error("Declension variant " .. variant .. " should be used with the masculine gender") end end end stem, tr, decl, orig_pl_ending = detect_lemma_type(lemma, tr, gender, args, variant) was_autodetected = true else stem, tr = lemma, tr end -- Now canonicalize gender if gender == "3f" then gender = "f" elseif gender == "" then gender = nil end -- The ending should be treated as accented if either the original singular -- or plural ending was accented, or if the stem is non-syllabic. local was_accented = com.is_stressed(decl) or orig_pl_ending and com.is_stressed(orig_pl_ending) or com.is_nonsyllabic(stem) local was_plural = not not orig_pl_ending decl = canonicalize_decl(decl, args.old) -- The rest of this code concerns plural variants. It's somewhat -- complicated because there are potentially four sources of plural -- variants (not to mention plural variants constructed using slash -- notation): -- -- 1. A user-requested plural variant in declension types 2 or 4 above -- (currently only -ья) -- 2. An explicit plural variant encoded in an explicit declension of -- type 5 above -- 3. An autodetected plural variant (which will happen in some cases -- when autodetection is performed on a nominative plural) -- 4. A plural variant derived using special case (1). -- -- Up to three actual plural variants might exist (e.g. if the user -- specifies a DECL value or 'm-ья(1)' and a STEM ending in -а, -- although not all three can ever be compatible because -ья and (1) -- are never compatible). We can't have all four because if there's -- an explicit plural variant, there won't be a user-requested or -- autodetected plural variant. -- -- The goal below is to do two things: Check that all available plural -- variants are the same, and generate the actual declension. -- If we have a type-2 or type-3 variant, we already have the actual -- declension; else we need to use a table to map the basic declension -- to the one with the plural variant encoded in it. -- -- NOTE: The code below was written with a more general plural-variant -- system. It probably can be simplified a lot now. -- 1: Handle explicit decl with slash variant if rfind(decl, "/") then if want_ya_plural then -- Don't think this can happen error("Plural variant " .. want_ya_plural .. " not compatible with slash declension " .. decl) end if args.want_sc1 then error("Special case (1) not compatible with slash declension" .. decl) end return stem, tr, decl, gender, was_accented, was_plural, was_autodetected end -- 2: Retrieve explicitly specified or autodetected decl and pl. variant local basic_decl, detected_or_explicit_plural = rmatch(decl, "^(.*)(%-[^mf]+)$") if basic_decl == "ь" then basic_decl = "ь-m" end basic_decl = basic_decl or decl -- 3: Any user-requested plural variant must agree with explicit or -- autodetected variant. if want_ya_plural and detected_or_explicit_plural and want_ya_plural ~= detected_or_explicit_plural then error("Plural variant " .. want_ya_plural .. " requested but plural variant " .. detected_or_explicit_plural .. " detected from plural stem") end -- 4: Handle special case (1). Derive the full declension, make sure its -- plural variant matches any other available plural variants, and -- return the declension. if args.want_sc1 then local sc1_decl = special_case_1_to_plural_variant[basic_decl] or error("Special case (1) not compatible with declension " .. basic_decl) local sc1_plural = rsub(sc1_decl, "^.*%-", "-") local other_plural = want_ya_plural or detected_or_explicit_plural if other_plural and sc1_plural ~= other_plural then error("Plural variant " .. other_plural .. " specified or detected, but special case (1) calls for plural variant " .. sc1_plural) end return stem, tr, sc1_decl, gender, was_accented, was_plural, was_autodetected end -- 5: Handle user-requested plural variant without explicit or detected -- one. (If an explicit or detected one exists, we've already checked -- that it agrees with the user-requested one, and so we already have -- our full declension.) if want_ya_plural and not detected_or_explicit_plural then local variant_decl if plural_variant_detection_map[decl] then variant_decl = plural_variant_detection_map[decl][want_ya_plural] end if variant_decl then return stem, tr, variant_decl, gender, was_accented, was_plural, was_autodetected else return stem, tr, decl .. (args.old and "/ъ-ья" or "/-ья"), gender, was_accented, was_plural, was_autodetected end end -- 6: Just return the full declension, which will include any available -- plural variant in it. return stem, tr, decl, gender, was_accented, was_plural, was_autodetected end -- Convert soft adjectival declensions into hard ones following certain -- stem-final consonants. FIXME: We call this in two places, once -- to handle auto-detection and once to handle explicit declensions; but -- in the former case we end up calling it twice. local function determine_adj_stem_variant(decl, stem) local iend = rmatch(decl, "^%+[іи]([йея]?)$") -- Convert ій/ий to ый after velar or sibilant. This is important for -- velars; doesn't really matter one way or the other for sibilants as -- the sibilant rules will convert both sets of endings to the same -- thing (whereas there will be a difference with о vs. е for velars). if iend and rfind(stem, "[" .. com.velar .. com.sib .. "]$") then decl = "+ы" .. iend -- The following is necessary for -ц, unclear if makes sense for -- sibilants. (Would be necessary -- I think -- if we were -- inferring short adjective forms, but we're not.) elseif decl == "+ее" and rfind(stem, "[" .. com.sib_c .. "]$") then decl = "+ое" end return decl end -- Attempt to determine the actual adjective declension based on a -- combination of the declension the user specified and what can be detected -- from the stem. DECL is the value the user passed for the declension field, -- after extraneous annotations have been removed (although none are probably -- relevant here). What's left is one of the following, which always begins -- with +: -- -- 1. +, meaning to autodetect the declension from the stem -- 2. +ь, same as + but selects +ьий instead of +ий if lemma ends in -ий -- 3. +short, +mixed or +proper, with the declension partly specified but -- the particular gender/number-specific short/mixed variant to be -- autodetected -- 4. A gender (+m, +f or +n), used only for detecting the singular of -- plural-form lemmas (this is primarily used in conjunction with template -- ru-noun+, to explicitly specify the gender; for the actual declension, -- it doesn't much matter what singular gender we pick since we're a -- plural only) -- 5. A gender plus short/mixed/proper/ь (e.g. +f-mixed), again with the gender -- used only for detecting the singular of plural-form short/mixed lemmas -- 6. An actual declension, possibly including a slash declension -- (WARNING: Unclear if slash declensions will work, especially those -- that are adjective/noun combinations) -- -- Returns the same seven args as for determine_decl(). The returned -- declension will always begin with +. detect_adj_type = function(lemma, tr, decl, old) local was_autodetected local base, ending local basedecl, g = rmatch(decl, "^(%+)([mfn])$") if not basedecl then g, basedecl = rmatch(decl, "^%+([mfn])%-([a-zь]+)$") if basedecl then basedecl = "+" .. basedecl end end decl = basedecl or decl if decl == "+" or decl == "+ь" then local loc = rfind(lemma, "[аеиіоыя][" .. AC .. GR .. "]?[йея]$") or rfind(lemma, "ь[йея]$") if loc then base, ending = usub(lemma, 1, loc - 1), usub(lemma, loc) end if ending == "ий" and decl == "+ь" then decl = "+ьий" elseif ending == "ій" and decl == "+ь" then decl = "+ьій" elseif ending then decl = "+" .. ending else local loc, shortmixed = rfind(lemma, "[аоы][" .. AC .. GR .. "]?$") or rfind(lemma, "ъ?$") if loc then base, ending = usub(lemma, 1, loc - 1), usub(lemma, loc) shortmixed = rfind(base, "^[" .. com.uppercase .. "].*[иы]" .. AC .. "н$") and "stressed-proper" or -- accented rfind(base, "^[" .. com.uppercase .. "].*[иы]н$") and "proper" or --not accented rlfind(base, "[ёео][" .. AC .. GR .. "]?в$") and "short" or rlfind(base, "[ыи]" .. AC .. "н$") and "stressed-short" or -- accented rlfind(base, "[ыи]н$") and "mixed" --not accented end if not shortmixed then error("Cannot determine stem type of adjective: " .. lemma) end decl = "+" .. ending .. "-" .. shortmixed end was_autodetected = true elseif m_table.contains({"+short", "+mixed", "+proper"}, decl) then base, ending = rmatch(lemma, "^(.-)([оаыъ]?[" .. AC .. GR .. "]?)$") assert(base) local shortmixed = usub(decl, 2) if rlfind(base, "[ыи]" .. AC .. "н$") then -- accented if shortmixed == "short" then shortmixed = "stressed-short" elseif shortmixed == "proper" then shortmixed = "stressed-proper" end end decl = "+" .. ending .. "-" .. shortmixed was_autodetected = true else base = lemma end if ending and ending ~= "" then tr = com.strip_tr_ending(tr, ending) end -- Remove any accents from the declension, but not their presence. -- We will convert was_accented into stress pattern b, and convert that -- back to an accented version in determine_stress_variant(). This way -- we end up with the stressed version whether the user placed an accent -- in the ending or decl or specified stress pattern b. -- FIXME, might not work in the presence of slash declensions local was_accented = com.is_stressed(decl) decl = com.remove_accents(decl) decl = map_decl(decl, function(decl) return determine_adj_stem_variant(decl, base) end) local singdecl if decl == "+ые" then singdecl = (g == "m" or not g) and (was_accented and "+ой" or "+ый") or not old and g == "f" and "+ая" or not old and g == "n" and "+ое" elseif decl == "+ыя" and old then singdecl = (g == "f" or not g) and "+ая" or g == "n" and "+ое" elseif decl == "+ие" and not old then singdecl = (g == "m" or not g) and "+ий" or g == "f" and "+яя" or g == "n" and "+ее" elseif decl == "+іе" and old and (g == "m" or not g) then singdecl = "+ій" elseif decl == "+ія" and old then singdecl = (g == "f" or not g) and "+яя" or g == "n" and "+ее" elseif decl == "+ьи" then singdecl = (g == "m" or not g) and (old and "+ьій" or "+ьий") or g == "f" and "+ья" or g == "n" and "+ье" elseif rfind(decl, "^%+ы%-") then -- decl +ы-mixed or similar local beg = (g == "m" or not g) and (old and "ъ" or "") or g == "f" and "а" or g == "n" and "о" singdecl = beg and "+" .. beg .. usub(decl, 3) end if singdecl then was_plural = true decl = singdecl end return base, tr, canonicalize_decl(decl, old), g, was_accented, was_plural, was_autodetected end -- If stress pattern omitted, detect it based on whether ending is stressed -- or the decl class or stem accent calls for inherent stress, defaulting to -- pattern a. This is run after alias resolution and accent removal of DECL; -- WAS_ACCENTED indicates whether the ending was originally stressed. -- FIXME: This is run before splitting slash patterns but should be run after. detect_stress_pattern = function(stem, decl, decl_cats, reducible, was_plural, was_accented) -- ёнок and ёночек always bear stress if rfind(decl, "ёнокъ?") or rfind(decl, "ёночекъ?") then return "b" -- stressed suffix и́н; missing in plural and true endings don't bear stress -- (except for exceptional господи́н) elseif rfind(decl, "инъ?") and was_accented then return "d" -- Adjectival -ой always bears the stress elseif rfind(decl, "%+ой") then return "b" -- Adjectival stressed-short, stressed-proper bears the stress elseif rfind(decl, "^%+.*%-stressed") then return "b" -- Pattern b if ending was accented by user elseif was_accented then return "b" -- Nonsyllabic stem means pattern b elseif com.is_nonsyllabic(stem) then return "b" -- Accent on reducible vowel in masc nom sg (not plural) means pattern b. -- Think about whether we want to enable this. -- elseif reducible and not was_plural then -- -- FIXME hack. Eliminate plural part of slash declension. -- decl = rsub(decl, "/.*", "") -- if decl_cats[decl] and decl_cats[decl].g == "m" then -- if com.is_ending_stressed(stem) or com.is_monosyllabic(stem) then -- return "b" -- end -- end end return "a" end -- In certain special cases, depending on the declension, we override the -- user-specified stress pattern and convert it to something else. -- NOTE: This function is run after alias resolution and accent removal. -- FIXME: It's also run before splitting slash patterns but should be run after. override_stress_pattern = function(decl, stress) -- ёнок and ёночек always bear stress; if user specified a, -- convert to b. Don't do this with slash patterns (see FIXME above). if stress == "a" and (rfind(decl, "^ёнокъ?$") or rfind(decl, "^ёночекъ?$")) then return "b" end return stress end -- Canonicalize an adjectival declension to either the stressed or unstressed -- variant depending on the stress. Ultimately this is what ensures that -- the user's stress mark on an adjectival ending is respected. determine_stress_variant = function(decl, stress) if stress == "b" then if decl == "+ая" then return "+а́я" elseif decl == "+ое" then return "+о́е" else -- Convert +...-short to +...-stressed-short and same for -proper local b, e = rmatch(decl, "^%+(.*)%-(short)$") if not b then b, e = rmatch(decl, "^%+(.*)%-(proper)$") end if b and not rfind(b, "%-stressed") then return "+" .. b .. "-stressed-" .. e end end end return decl end -- Canonicalize a declension based on the final stem consonant, in -- particular converting soft declensions to hard ones after velars and/or -- sibilants. FIXME: We also do this canonicalization earlier on during -- auto-detection (determine_adj_stem_variant() is called by -- detect_adj_type(), and code in detect_lemma_type() does the equivalent -- of the first clause below). Doing it here ensures that explicitly -- specified declensions get handled as well, but it would be nice to not -- do the same thing twice in the auto-detection case. determine_stem_variant = function(decl, stem) if decl == "е" and rfind(stem, "[" .. com.sib_c .. "]$") then return "о" end return determine_adj_stem_variant(decl, stem) end is_reducible = function(decl_cat) if decl_cat.suffix or decl_cat.cant_reduce or decl_cat.adj then return false elseif decl_cat.decl == "3rd" and decl_cat.g == "f" or decl_cat.g == "m" then return true else return false end end -- Reduce nom sg to stem by eliminating the "epenthetic" vowel. Applies to -- masculine 2nd-declension hard and soft, and 3rd-declension feminine in -- -ь. STEM and DECL are after determine_decl(), before converting -- outward-facing declensions to inward ones. function export.reduce_nom_sg_stem(stem, tr, decl, soft_n, can_err) local full_stem = stem .. (decl == "й" and decl or "") local full_tr = tr and tr .. (decl == "й" and "j" or "") local ret, rettr = com.reduce_stem(full_stem, full_tr, soft_n) if not ret and can_err then error("Unable to reduce stem " .. stem) end return ret, rettr end is_dereducible = function(decl_cat) if decl_cat.suffix or decl_cat.cant_reduce or decl_cat.adj then return false elseif decl_cat.decl == "1st" or decl_cat.decl == "2nd" and decl_cat.g == "n" then return true else return false end end -- Add a possible suffix to the bare stem, according to the declension and -- value of OLD. This may be -ь, -ъ, -й or nothing. We need to do this here -- because we don't actually attach such a suffix in attach_unstressed() due -- to situations where we don't want the suffix added, e.g. dereducible nouns -- in -ня. add_bare_suffix = function(bare, baretr, old, sgdc, dereduced) if old and sgdc.hard == "hard" then -- Final -ъ isn't transliterated return bare .. "ъ", baretr elseif sgdc.hard == "soft" or sgdc.hard == "palatal" then -- This next clause corresponds to a special case in Vitalik's module. -- It says that nouns in -ня (accent class a) have gen pl without -- trailing -ь. It appears to apply to most nouns in -ня (possibly -- all in -льня), but ку́хня (gen pl ку́хонь) and дерéвня (gen pl -- дереве́нь) is an exception. (Vitalik's module has an extra -- condition here 'stress == "a"' that would exclude дере́вня but I -- don't think this condition is in Zaliznyak, as he indicates -- дере́вня as having an exceptional genitive plural.) if dereduced and rfind(bare, "[нН]$") and sgdc.decl == "1st" then -- FIXME: What happens in this case old-style? I assume that -- -ъ is added, but this is a guess. -- Final -ъ isn't transliterated return bare .. (old and "ъ" or ""), baretr elseif rfind(bare, "[" .. com.vowel .. "][" .. AC .. GR .. "]?$") then return bare .. "й", baretr and (baretr .. "j") else return bare .. "ь", baretr and (baretr .. "ʹ") end else return bare, baretr end end -- Dereduce stem to the form found in the gen pl (and maybe nom sg) by -- inserting an epenthetic vowel. Applies to 1st declension and 2nd -- declension neuter, and to 2nd declension masculine when the stem was -- specified as a plural form (in which case we're deriving the nom sg, -- and also the gen pl in the alt-gen-pl scenario). STEM and DECL are -- after determine_decl(), before converting outward-facing declensions -- to inward ones. STRESS is the stess pattern. function export.dereduce_nom_sg_stem(stem, tr, sgdc, stress, old, can_err) local epenthetic_stress = ending_stressed_gen_pl_patterns[stress] local ret, rettr = com.dereduce_stem(stem, tr, epenthetic_stress) if not ret then if can_err then error("Unable to dereduce stem " .. stem) else return nil, nil end end return add_bare_suffix(ret, rettr, old, sgdc, true) end -------------------------------------------------------------------------- -- Second-declension masculine -- -------------------------------------------------------------------------- ----------------- Masculine hard ------------------- -- Hard-masculine declension, ending in a hard consonant -- (ending in -ъ, old-style). declensions_old["ъ"] = { ["nom_sg"] = "ъ", ["gen_sg"] = "а́", ["dat_sg"] = "у́", ["acc_sg"] = nil, ["ins_sg"] = "о́мъ", ["pre_sg"] = "ѣ́", ["nom_pl"] = "ы́", ["gen_pl"] = function(stem, stress) return nom.sibilant_suffixes[ulower(usub(stem, -1))] and "е́й" or "о́въ" end, ["alt_gen_pl"] = "ъ", ["dat_pl"] = "а́мъ", ["acc_pl"] = nil, ["ins_pl"] = "а́ми", ["pre_pl"] = "а́хъ", } declensions_old_cat["ъ"] = { decl="2nd", hard="hard", g="m" } -- Normal mapping of old ъ would be "" (blank), but we set up "#" as an alias -- so we have a way of referring to it without defaulting if need be and -- distinct from auto-detection (e.g. in the second stem of a word, or to -- override autodetection of -ёнок or -ин -- the latter is necessary in the -- case of семьянин). declensions_aliases["#"] = "" ----------------- Masculine hard, irregular plural ------------------- -- Hard-masculine declension, ending in a hard consonant -- (ending in -ъ, old-style), with irreg nom pl -а. declensions_old["ъ-а"] = mw.clone(declensions_old["ъ"]) declensions_old["ъ-а"]["nom_pl"] = "а́" declensions_old_cat["ъ-а"] = { decl="2nd", hard="hard", g="m", alt_nom_pl=true } declensions_aliases["#-a"] = "-a" -- Hard-masculine declension, ending in a hard consonant -- (ending in -ъ, old-style), with irreg soft pl -ья. -- Differs from the normal declension throughout the plural. declensions_old["ъ-ья"] = { ["nom_sg"] = "ъ", ["gen_sg"] = "а́", ["dat_sg"] = "у́", ["acc_sg"] = nil, ["ins_sg"] = "о́мъ", ["pre_sg"] = "ѣ́", ["nom_pl"] = "ья́", ["gen_pl"] = "ьёвъ", ["alt_gen_pl"] = "е́й", ["dat_pl"] = "ья́мъ", ["acc_pl"] = nil, ["ins_pl"] = "ья́ми", ["pre_pl"] = "ья́хъ", } declensions_old_cat["ъ-ья"] = { decl="2nd", hard="hard", g="m", irregpl=true } declensions_aliases["#-ья"] = "-ья" ----------------- Masculine hard, suffixed, irregular plural ------------------- declensions_old["инъ"] = { ["nom_sg"] = "и́нъ", ["gen_sg"] = "и́на", ["dat_sg"] = "и́ну", ["acc_sg"] = nil, ["ins_sg"] = "и́номъ", ["pre_sg"] = "и́нѣ", ["nom_pl"] = "е́", ["gen_pl"] = "ъ", ["dat_pl"] = "а́мъ", ["acc_pl"] = nil, ["ins_pl"] = "а́ми", ["pre_pl"] = "а́хъ", } declensions_old_cat["инъ"] = { decl="2nd", hard="hard", g="m", suffix=true } declensions_old["ёнокъ"] = { ["nom_sg"] = "ёнокъ", ["gen_sg"] = "ёнка", ["dat_sg"] = "ёнку", ["acc_sg"] = nil, ["ins_sg"] = "ёнкомъ", ["pre_sg"] = "ёнкѣ", ["nom_pl"] = "я́та", ["gen_pl"] = "я́тъ", ["dat_pl"] = "я́тамъ", ["acc_pl"] = nil, ["ins_pl"] = "я́тами", ["pre_pl"] = "я́тахъ", } declensions_old_cat["ёнокъ"] = { decl="2nd", hard="hard", g="m", suffix=true } declensions_old_aliases["онокъ"] = "ёнокъ" declensions_old_aliases["енокъ"] = "ёнокъ" declensions_old["ёночекъ"] = { ["nom_sg"] = "ёночекъ", ["gen_sg"] = "ёночка", ["dat_sg"] = "ёночку", ["acc_sg"] = nil, ["ins_sg"] = "ёночкомъ", ["pre_sg"] = "ёночкѣ", ["nom_pl"] = "я́тки", ["gen_pl"] = "я́токъ", ["dat_pl"] = "я́ткамъ", ["acc_pl"] = nil, ["ins_pl"] = "я́тками", ["pre_pl"] = "я́ткахъ", } declensions_old_cat["ёночекъ"] = { decl="2nd", hard="hard", g="m", suffix=true } declensions_old_aliases["оночекъ"] = "ёночекъ" declensions_old_aliases["еночекъ"] = "ёночекъ" ----------------- Masculine soft ------------------- -- Normal soft-masculine declension in -ь declensions_old["ь-m"] = { ["nom_sg"] = "ь", ["gen_sg"] = "я́", ["dat_sg"] = "ю́", ["acc_sg"] = nil, ["ins_sg"] = "ёмъ", ["pre_sg"] = "ѣ́", ["nom_pl"] = "и́", ["gen_pl"] = "е́й", ["alt_gen_pl"] = "ь", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["ь-m"] = { decl="2nd", hard="soft", g="m" } -- Soft-masculine declension in -ь with irreg nom pl -я declensions_old["ь-я"] = mw.clone(declensions_old["ь-m"]) declensions_old["ь-я"]["nom_pl"] = "я́" declensions_old_cat["ь-я"] = { decl="2nd", hard="soft", g="m", alt_nom_pl=true } ----------------- Masculine palatal ------------------- -- Masculine declension in palatal -й declensions_old["й"] = { ["nom_sg"] = "й", ["gen_sg"] = "я́", ["dat_sg"] = "ю́", ["acc_sg"] = nil, ["ins_sg"] = "ёмъ", ["pre_sg"] = function(stem, stress) return rlfind(stem, "[іи][" .. AC .. GR .. "]?$") and not ending_stressed_pre_sg_patterns[stress] and "и" or "ѣ́" end, ["nom_pl"] = "и́", ["gen_pl"] = "ёвъ", ["alt_gen_pl"] = "й", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["й"] = { decl="2nd", hard="palatal", g="m" } declensions_old["й-я"] = mw.clone(declensions_old["й"]) declensions_old["й-я"]["nom_pl"] = "я́" declensions_old_cat["й-я"] = { decl="2nd", hard="palatal", g="m", alt_nom_pl=true } -------------------------------------------------------------------------- -- First-declension feminine -- -------------------------------------------------------------------------- ----------------- Feminine hard ------------------- -- Hard-feminine declension in -а declensions_old["а"] = { ["nom_sg"] = "а́", ["gen_sg"] = "ы́", ["dat_sg"] = "ѣ́", ["acc_sg"] = "у́", ["ins_sg"] = {"о́й<insa>", "о́ю<insb>"}, -- see concat_word_forms_1() ["pre_sg"] = "ѣ́", ["nom_pl"] = "ы́", ["gen_pl"] = function(stem, stress) return nom.sibilant_suffixes[ulower(usub(stem, -1))] and ending_stressed_gen_pl_patterns[stress] and "е́й" or "ъ" end, ["alt_gen_pl"] = "е́й", ["dat_pl"] = "а́мъ", ["acc_pl"] = nil, ["ins_pl"] = "а́ми", ["pre_pl"] = "а́хъ", } declensions_old_cat["а"] = { decl="1st", hard="hard", g="f" } ----------------- Feminine soft ------------------- -- Soft-feminine declension in -я declensions_old["я"] = { ["nom_sg"] = "я́", ["gen_sg"] = "и́", ["dat_sg"] = function(stem, stress) return rlfind(stem, "[іи][" .. AC .. GR .. "]?$") and not ending_stressed_dat_sg_patterns[stress] and "и" or "ѣ́" end, ["acc_sg"] = "ю́", ["ins_sg"] = {"ёй<insa>", "ёю<insb>"}, -- see concat_word_forms_1() ["pre_sg"] = function(stem, stress) return rlfind(stem, "[іи][" .. AC .. GR .. "]?$") and not ending_stressed_pre_sg_patterns[stress] and "и" or "ѣ́" end, ["nom_pl"] = "и́", ["gen_pl"] = function(stem, stress) return ending_stressed_gen_pl_patterns[stress] and not rlfind(stem, "[" .. com.vowel .. "]́?$") and "е́й" or "й" end, ["alt_gen_pl"] = "е́й", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["я"] = { decl="1st", hard="soft", g="f" } -- Soft-feminine declension in -ья. -- Almost like ь + -я endings except for genitive plural. declensions_old["ья"] = { ["nom_sg"] = "ья́", ["gen_sg"] = "ьи́", ["dat_sg"] = "ьѣ́", ["acc_sg"] = "ью́", ["ins_sg"] = {"ьёй<insa>", "ьёю<insb>"}, -- see concat_word_forms_1() ["pre_sg"] = "ьѣ́", ["nom_pl"] = "ьи́", ["gen_pl"] = function(stem, stress) -- circumflex accent is a signal that forces stress, particularly -- in accent pattern d/d'. return (ending_stressed_gen_pl_patterns[stress] or stress == "d" or stress == "d'") and "е̂й" or "ий" end, ["dat_pl"] = "ья́мъ", ["acc_pl"] = nil, ["ins_pl"] = "ья́ми", ["pre_pl"] = "ья́хъ", } declensions_old_cat["ья"] = { decl="1st", hard="soft", g="f", stem_suffix="ь", gensg=true, ignore_reduce=true -- already has dereduced gen pl } -------------------------------------------------------------------------- -- Second-declension neuter -- -------------------------------------------------------------------------- ----------------- Neuter hard ------------------- -- Normal hard-neuter declension in -о declensions_old["о"] = { ["nom_sg"] = "о́", ["gen_sg"] = "а́", ["dat_sg"] = "у́", ["acc_sg"] = function(stem, stress, args) return not (args.explicit_gender == "m" and args.thisa == "a") and "о́" or nil end, ["ins_sg"] = "о́мъ", ["pre_sg"] = "ѣ́", ["nom_pl"] = "а́", ["gen_pl"] = function(stem, stress) return nom.sibilant_suffixes[ulower(usub(stem, -1))] and ending_stressed_gen_pl_patterns[stress] and "е́й" or "ъ" end, ["alt_gen_pl"] = "о́въ", ["dat_pl"] = "а́мъ", ["acc_pl"] = nil, ["ins_pl"] = "а́ми", ["pre_pl"] = "а́хъ", } declensions_old_cat["о"] = { decl="2nd", hard="hard", g="n" } -- Hard-neuter declension in -о with irreg nom pl -и declensions_old["о-и"] = mw.clone(declensions_old["о"]) declensions_old["о-и"]["nom_pl"] = "ы́" declensions_old_cat["о-и"] = { decl="2nd", hard="hard", g="n", alt_nom_pl=true } declensions_old_aliases["о-ы"] = "о-и" -- Masculine-gender neuter-form declension in -(ишк)о with irreg nom pl -и, -- with colloquial feminine endings in some of the singular cases -- (§5 p. 74 of Zaliznyak) declensions_old["(ишк)о-и"] = mw.clone(declensions_old["о-и"]) declensions_old["(ишк)о-и"]["gen_sg"] = {"а́", "ы́1"} declensions_old["(ишк)о-и"]["dat_sg"] = {"у́", "ѣ́1"} declensions_old["(ишк)о-и"]["ins_sg"] = {"о́мъ", "о́й1"} declensions_old_cat["(ишк)о-и"] = { decl="2nd", hard="hard", g="n", colloqfem=true, alt_nom_pl=true } internal_notes_table_old["(ишк)о-и"] = "<sup>1</sup> Colloquial." -- Masculine-gender animate neuter-form declension in -(ищ)е with irreg -- nom pl -и, with colloquial feminine endings in some of the singular cases -- (§4 p. 74 of Zaliznyak) declensions_old["(ищ)е-и"] = mw.clone(declensions_old["о-и"]) declensions_old["(ищ)е-и"]["acc_sg"] = {"а́", "у́1"} declensions_old["(ищ)е-и"]["gen_sg"] = {"а́", "ы́2"} declensions_old["(ищ)е-и"]["dat_sg"] = {"у́", "ѣ́2"} declensions_old["(ищ)е-и"]["ins_sg"] = {"о́мъ", "о́й2"} declensions_old_cat["(ищ)е-и"] = { decl="2nd", hard="hard", g="n", colloqfem=true, alt_nom_pl=true } internal_notes_table_old["(ищ)е-и"] = "<sup>1</sup> Colloquial.<br /><sup>2</sup> Less common, more colloquial." ----------------- Neuter soft ------------------- -- Soft-neuter declension in -е (stressed -ё) declensions_old["е"] = { ["nom_sg"] = "ё", ["gen_sg"] = "я́", ["dat_sg"] = "ю́", ["acc_sg"] = function(stem, stress, args) return not (args.explicit_gender == "m" and args.thisa == "a") and "ё" or nil end, ["ins_sg"] = "ёмъ", ["pre_sg"] = function(stem, stress) return rlfind(stem, "[іи][" .. AC .. GR .. "]?$") and not ending_stressed_pre_sg_patterns[stress] and "и" or "ѣ́" end, ["nom_pl"] = "я́", ["gen_pl"] = function(stem, stress) return ending_stressed_gen_pl_patterns[stress] and not rlfind(stem, "[" .. com.vowel .. "][" .. AC .. GR .. "]?$") and "е́й" or "й" end, ["alt_gen_pl"] = "ёвъ", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["е"] = { singular = function(suffix) if suffix == "ё" then return "ending in -ё" else return {} end end, decl="2nd", hard="soft", g="n", gensg=true } -- User-facing declension type "ё" = "е" declensions_old_aliases["ё"] = "е" -- Rare soft-neuter declension in stressed -е́ (e.g. муде́, бытие́) declensions_old["е́"] = { ["nom_sg"] = "е́", ["gen_sg"] = "я́", ["dat_sg"] = "ю́", ["acc_sg"] = function(stem, stress, args) return not (args.explicit_gender == "m" and args.thisa == "a") and "е́" or nil end, ["ins_sg"] = "е́мъ", ["pre_sg"] = function(stem, stress) -- FIXME!!! Are we sure about this condition? This is what was -- found in the old template, but the related -е declension has -- -ие prep sg ending -(и)и only when *not* stressed. return rlfind(stem, "[іи][" .. AC .. GR .. "]?$") and "и́" or "ѣ́" end, ["nom_pl"] = "я́", ["gen_pl"] = function(stem, stress) return rlfind(stem, "[" .. com.vowel .. "][" .. AC .. GR .. "]?$") and "й" or "е́й" end, ["alt_gen_pl"] = "ёвъ", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["е́"] = { singular = "ending in stressed -е", decl="2nd", hard="soft", g="n", gensg=true } -- Soft-neuter declension in unstressed -ье (stressed -ьё). declensions_old["ье"] = { ["nom_sg"] = "ьё", ["gen_sg"] = "ья́", ["dat_sg"] = "ью́", ["acc_sg"] = function(stem, stress, args) return not (args.explicit_gender == "m" and args.thisa == "a") and "ьё" or nil end, ["ins_sg"] = "ьёмъ", ["pre_sg"] = "ьѣ́", ["nom_pl"] = "ья́", ["gen_pl"] = function(stem, stress) return ending_stressed_gen_pl_patterns[stress] and "е́й" or "ий" end, ["alt_gen_pl"] = "ьёвъ", ["dat_pl"] = "ья́мъ", ["acc_pl"] = nil, ["ins_pl"] = "ья́ми", ["pre_pl"] = "ья́хъ", } declensions_old_cat["ье"] = { decl="2nd", hard="soft", g="n", stem_suffix="ь", gensg=true, ignore_reduce=true -- already has dereduced gen pl } declensions_old_aliases["ьё"] = "ье" -------------------------------------------------------------------------- -- Third declension -- -------------------------------------------------------------------------- declensions_old["ь-f"] = { ["nom_sg"] = "ь", ["gen_sg"] = "и́", ["dat_sg"] = "и́", ["acc_sg"] = "ь", ["ins_sg"] = "ью́", ["pre_sg"] = "и́", ["nom_pl"] = "и́", ["gen_pl"] = "е́й", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["ь-f"] = { decl="3rd", hard="soft", g="f" } declensions_old["мя"] = { ["nom_sg"] = "мя", ["gen_sg"] = "мени", ["dat_sg"] = "мени", ["acc_sg"] = nil, ["ins_sg"] = "менемъ", ["pre_sg"] = "мени", ["nom_pl"] = "мена́", ["gen_pl"] = "мёнъ", ["dat_pl"] = "мена́мъ", ["acc_pl"] = nil, ["ins_pl"] = "мена́ми", ["pre_pl"] = "мена́хъ", } declensions_old_cat["мя"] = { decl="3rd", hard="soft", g="n", cant_reduce=true } -------------------------------------------------------------------------- -- Indeclinable -- -------------------------------------------------------------------------- -- Indeclinable declension; no endings. declensions_old["$"] = { ["nom_sg"] = "", ["gen_sg"] = "", ["dat_sg"] = "", ["acc_sg"] = nil, ["ins_sg"] = "", ["pre_sg"] = "", ["nom_pl"] = "", ["gen_pl"] = "", ["dat_pl"] = "", ["acc_pl"] = nil, ["ins_pl"] = "", ["pre_pl"] = "", } declensions_old_cat["$"] = { decl="indeclinable", hard="none", g="none" } -------------------------------------------------------------------------- -- Adjectival -- -------------------------------------------------------------------------- -- This needs to be up here because it is called just below. local function old_to_new(v) v = rsub(v, "ъ$", "") v = rsub(v, "^ъ", "") v = rsub(v, "(%A)ъ", "%1") v = rsub(v, "ъ(%A)", "%1") v = rsub(v, "і", "и") v = rsub(v, "ѣ", "е") return v end -- Meaning of entry is: -- 1. The declension name in module ru-adjective -- 2. The masculine declension name in this module -- 3. The neuter declension name in this module -- 4. The feminine declension name in this module -- 5. The value of hard= for the declensions_cat entry -- 6. The value of decl= for the declensions_cat entry -- 7. The value of possadj= for the declensions_cat entry (true if possessive -- or similar type of adjective) local adj_decl_map = { {"ый", "ый", "ое", "ая", "hard", "long", false}, {"ій", "ій", "ее", "яя", "soft", "long", false}, {"ой", "ой", "о́е", "а́я", "hard", "long", false}, {"ьій", "ьій", "ье", "ья", "palatal", "long", true}, {"short", "ъ-short", "о-short", "а-short", "hard", "short", true}, {"mixed", "ъ-mixed", "о-mixed", "а-mixed", "hard", "mixed", true}, {"proper", "ъ-proper", "о-proper", "а-proper", "hard", "proper", true}, {"stressed-short", "ъ-stressed-short", "о-stressed-short", "а-stressed-short", "hard", "short", true}, {"stressed-proper", "ъ-stressed-proper", "о-stressed-proper", "а-stressed-proper", "hard", "proper", true}, } local function get_adjectival_decl(adjtype, gender, old) local decl, intnotes = m_ru_adj.get_nominal_decl(adjtype, gender, old) -- hack fem ins_sg to insert <insa>, <insb>; see concat_word_forms_1() if gender == "f" and type(decl["ins_sg"]) == "table" and #decl["ins_sg"] == 2 then decl["ins_sg"][1] = decl["ins_sg"][1] .. "<insa>" decl["ins_sg"][2] = decl["ins_sg"][2] .. "<insb>" end return decl, intnotes end for _, declspec in ipairs(adj_decl_map) do local oadjdecl = declspec[1] local nadjdecl = old_to_new(oadjdecl) local odecl_by_gender = {m="+" .. declspec[2], n="+" .. declspec[3], f="+" .. declspec[4]} local hard = declspec[5] local decltype = declspec[6] local possadj = declspec[7] for _, g in ipairs({"m", "n", "f"}) do local odecl = odecl_by_gender[g] local ndecl = old_to_new(odecl) declensions_old[odecl], internal_notes_table_old[odecl] = get_adjectival_decl(oadjdecl, g, true) declensions[ndecl], internal_notes_table[ndecl] = get_adjectival_decl(nadjdecl, g, false) declensions_old_cat[odecl] = { decl=decltype, hard=hard, g=g, adj=true, possadj=possadj } declensions_cat[ndecl] = { decl=decltype, hard=hard, g=g, adj=true, possadj=possadj } end end -- Set up some aliases. declensions_old_aliases["+о́-short"] = "+о-stressed-short" declensions_old_aliases["+а́-short"] = "+а-stressed-short" declensions_old_aliases["+о́-proper"] = "+о-stressed-proper" declensions_old_aliases["+а́-proper"] = "+а-stressed-proper" declensions_aliases["+#-short"] = "+-short" declensions_aliases["+#-mixed"] = "+-mixed" declensions_aliases["+#-proper"] = "+-proper" declensions_aliases["+#-stressed-short"] = "+-stressed-short" declensions_aliases["+#-stressed-proper"] = "+-stressed-proper" -------------------------------------------------------------------------- -- Populate new from old -- -------------------------------------------------------------------------- -- Function to convert an entry in an old declensions table to new. local function old_decl_entry_to_new(v) if not v then return nil elseif type(v) == "table" then local new_entry = {} for _, i in ipairs(v) do table.insert(new_entry, old_decl_entry_to_new(i)) end return new_entry elseif type(v) == "function" then return function(stem, suffix, args) return old_decl_entry_to_new(v(stem, suffix, args)) end else return old_to_new(v) end end -- Function to convert an old declensions table to new. local function old_decl_to_new(odecl) local ndecl = {} for k, v in pairs(odecl) do ndecl[k] = old_decl_entry_to_new(v) end return ndecl end -- Function to convert an entry in an old declensions_cat table to new. local function old_decl_cat_entry_to_new(odecl_cat_entry) if not odecl_cat_entry then return nil elseif type(odecl_cat_entry) == "function" then return function(suffix) return old_decl_cat_entry_to_new(odecl_cat_entry(suffix)) end elseif type(odecl_cat_entry) == "table" then local ndecl_cat_entry = {} for k, v in pairs(odecl_cat_entry) do ndecl_cat_entry[k] = old_decl_cat_entry_to_new(v) end return ndecl_cat_entry elseif type(odecl_cat_entry) == "boolean" then return odecl_cat_entry else assert(type(odecl_cat_entry) == "string") return old_to_new(odecl_cat_entry) end end -- Function to convert an old declensions_cat table to new. local function old_decl_cat_to_new(odeclcat) local ndeclcat = {} for k, v in pairs(odeclcat) do ndeclcat[k] = old_decl_cat_entry_to_new(v) end return ndeclcat end -- populate declensions[] from declensions_old[] for odecltype, odecl in pairs(declensions_old) do local ndecltype = old_to_new(odecltype) if not declensions[ndecltype] then declensions[ndecltype] = old_decl_to_new(odecl) end end -- populate declensions_cat[] from declensions_old_cat[] for odecltype, odeclcat in pairs(declensions_old_cat) do local ndecltype = old_to_new(odecltype) if not declensions_cat[ndecltype] then declensions_cat[ndecltype] = old_decl_cat_to_new(odeclcat) end end -- populate declensions_aliases[] from declensions_old_aliases[] for ofrom, oto in pairs(declensions_old_aliases) do local from = old_to_new(ofrom) if not declensions_aliases[from] then declensions_aliases[from] = old_to_new(oto) end end -- populate internal_notes_table[] from internal_notes_table_old[] for odecl, note in pairs(internal_notes_table_old) do local ndecl = old_to_new(odecl) if not internal_notes_table[ndecl] then -- FIXME, should we be calling old_to_new() here? internal_notes_table[ndecl] = note end end -------------------------------------------------------------------------- -- Inflection functions -- -------------------------------------------------------------------------- -- Attach the stressed stem (or plural stem, or barestem) out of ARGS -- to the unstressed suffix SUF, modifying the suffix as necessary for the -- last letter of the stem (e.g. if it is velar, sibilant or ц). CASE is -- the case form being created and is used to select the plural stem if -- needed. Returns two values, the combined form and the modified suffix. local function attach_unstressed(args, case, suf, was_stressed) if suf == nil then return nil, nil elseif rfind(suf, CFLEX) then -- if suf has circumflex accent, it forces stressed return attach_stressed(args, case, suf) end local stem, tr if rfind(case, "_pl") then stem, tr = args.pl, args.pltr end if not stem and case == "ins_sg" then stem, tr = args.ins_sg_stem, args.ins_sg_tr end if not stem then stem, tr = args.stem, args.stemtr end if nom.nonsyllabic_suffixes[suf] then -- If gen_pl, use special args.gen_pl_bare if given, else regular -- args.bare if there isn't a plural stem. If nom_sg, always use -- regular args.bare. local barearg, bareargtr if case == "gen_pl" then barearg, bareargtr = args.gen_pl_bare, args.gen_pl_baretr if not barearg and args.pl == args.stem then barearg, bareargtr = args.bare, args.baretr end else barearg, bareargtr = args.bare, args.baretr end local barestem = barearg or stem local barestem, baretr if barearg then barestem, baretr = barearg, bareargtr else barestem, baretr = stem, tr end if was_stressed and case == "gen_pl" then if not barearg then local gen_pl_stem, gen_pl_tr = com.make_ending_stressed(stem, tr) barestem, baretr = gen_pl_stem, gen_pl_tr end end if rlfind(barestem, "[йьъ]$") then suf = "" else if suf == "ъ" then -- OK elseif suf == "й" or suf == "ь" then if barearg and case == "gen_pl" then -- explicit bare or reducible, don't add -ь suf = "" elseif rfind(barestem, "[" .. com.vowel .. "][" .. AC .. GR .. "]?$") then -- not reducible, do add -ь and correct to -й if necessary suf = "й" else suf = "ь" end end end return com.concat_russian_tr(barestem, baretr, suf, nil, "dopair"), suf end suf = com.make_unstressed(suf) local rules = nom.unstressed_rules[ulower(usub(stem, -1))] return nom.combine_stem_and_suffix(stem, tr, suf, rules, args.old) end -- Analogous to attach_unstressed() but for the unstressed stem and a -- stressed suffix. attach_stressed = function(args, case, suf) if suf == nil then return nil, nil end -- circumflex forces stress even when the accent pattern calls for no stress suf = rsub(suf, "̂", AC) if com.is_unstressed(suf) then return attach_unstressed(args, case, suf, "was stressed") end local stem, tr if rfind(case, "_pl") then stem, tr = args.upl, args.upltr end if not stem then stem, tr = args.ustem, args.ustemtr end local rules = nom.stressed_rules[ulower(usub(stem, -1))] return nom.combine_stem_and_suffix(stem, tr, suf, rules, args.old) end -- Attach the appropriate stressed or unstressed stem (or plural stem as -- determined by CASE, or barestem) out of ARGS to the suffix SUF, which may -- be a list of alternative suffixes (e.g. in the inst sg of feminine nouns). -- Calls FUN (either attach_stressed() or attach_unstressed()) to do the work -- for an individual suffix. Returns two values, a list of combined forms -- and a list of the real suffixes used (which may be modified from the -- passed-in suffixes, e.g. by removing stress marks or modifying vowels in -- various ways after a stem-final velar, sibilant or ц). Each combined form -- is a two-element list {stem, tr} (or a one-element list if tr is nil). -- IRREG is true if this is an irregular form. We are handling the Nth word; -- ISLAST is true if this is the last one. local function attach_with(args, case, suf, fun, irreg, n, islast) if type(suf) == "table" then local all_combineds = {} local all_realsufs = {} for _, x in ipairs(suf) do local combineds, realsufs = attach_with(args, case, x, fun, irreg, n, islast) for _, combined in ipairs(combineds) do table.insert(all_combineds, combined) end for _, realsuf in ipairs(realsufs) do table.insert(all_realsufs, realsuf) end end return all_combineds, all_realsufs else local combined, realsuf = fun(args, case, suf) local irregsuf = irreg and {IRREGMARKER} or {""} return {combined and com.concat_paired_russian_tr( com.concat_paired_russian_tr(args["prefix" .. n], combined), com.concat_paired_russian_tr(args["suffix" .. n], irregsuf)) or nil}, {realsuf and realsuf .. args["suffix" .. n][1] or nil} end end -- Generate the form(s) and suffix(es) for CASE according to the declension -- table DECL, using the attachment function FUN (one of attach_stressed() -- or attach_unstressed()). IS_SLASH is true if this is a slash declension -- (different declensions for singular and plural). We are handling the Nth -- word; ISLAST is true if this is the last one. local function gen_form(args, decl, case, stress, fun, is_slash, n, islast) local irreg = false if not args.suffixes[case] then args.suffixes[case] = {} end local decl_sufs = args.old and declensions_old or declensions decl_sufs = decl_sufs[decl] local suf = decl_sufs[case] local decl_cats = args.old and declensions_old_cat or declensions_cat local ispl = rfind(case, "_pl") if ispl and (decl_cats[decl].irregpl or args.pl and args.pl ~= args.stem or is_slash) then irreg = true end if case == "nom_pl" and decl_cats[decl].alt_nom_pl then irreg = true end if type(suf) == "function" then suf = suf(ispl and args.pl or args.stem, stress, args) end if case == "gen_pl" and args.alt_gen_pl then suf = decl_sufs.alt_gen_pl irreg = true if not suf then error("No alternative genitive plural available for this declension class") end end local combineds, realsufs = attach_with(args, case, suf, fun, irreg, n, islast) for _, realsuf in ipairs(realsufs) do args.any_non_nil[case] = true args.this_any_non_nil[case] = true insert_if_not(args.suffixes[case], realsuf) end return combineds end local attachers = { ["+"] = attach_stressed, ["-"] = attach_unstressed, } do_stress_pattern = function(stress, args, decl, number, n, islast) local f = {} for _, case in ipairs(decl_cases) do if not number or (number == "sg" and rfind(case, "_sg")) or (number == "pl" and rfind(case, "_pl")) then f[case] = gen_form(args, decl, case, stress, attachers[stress_patterns[stress][case]], not not number, n, islast) -- Turn empty form lists into nil to facilitate computation of -- animate/inanimate accusatives below if f[case] and #f[case] == 0 then f[case] = nil end -- Compute linked versions of potential lemma cases, for use -- in the ru-noun+ headword. We substitute the original lemma -- (before removing links) for forms that are the same as the -- lemma, if the original lemma has links. if f[case] and (case == "nom_sg" or case == "nom_pl") then local linked_forms = {} for _, form in ipairs(f[case]) do -- Return true if FORM is "close enough" to LEMMA that we can substitute the -- linked form of the lemma. Currently this means exactly the same except that -- we ignore acute and grave accent differences in monosyllables, and ignore -- notes that may have been appended (e.g. the triangle marking irregularity). local entry, notes = m_table_tools.separate_notes(form[1]) local lemma = args.lemma_no_links local close_enough_to_lemma = entry == lemma or (com.is_monosyllabic(entry) and com.is_monosyllabic(lemma) and com.remove_accents(entry) == com.remove_accents(lemma)) if close_enough_to_lemma and rfind(args.orig_lemma, "%[%[") then table.insert(linked_forms, {args.orig_lemma .. notes, args.lemmatr and args.lemmatr .. notes}) else table.insert(linked_forms, form) end end f[case .. "_linked"] = linked_forms end end end -- Set acc an/in variants now as appropriate. We used to do this in -- handle_forms_and_overrides(), which simplified the handling of -- nom/gen/acc overrides but caused problems for words like мазло and -- трепло that had a mixture of nil and non-nil accusative forms. local an = args.thisa if not number or number == "sg" then f.acc_sg_an = f.acc_sg_an or f.acc_sg or an == "i" and f.nom_sg or f.gen_sg f.acc_sg_in = f.acc_sg_in or f.acc_sg or an == "a" and f.gen_sg or f.nom_sg end if not number or number == "pl" then f.acc_pl_an = f.acc_pl_an or f.acc_pl or an == "i" and f.nom_pl or f.gen_pl f.acc_pl_in = f.acc_pl_in or f.acc_pl or an == "a" and f.gen_pl or f.nom_pl end for case, forms in pairs(f) do if not args.forms[case] then args.forms[case] = {} end for _, form in ipairs(forms) do insert_if_not(args.forms[case], form) end end end stress_patterns["a"] = { nom_sg="-", gen_sg="-", dat_sg="-", acc_sg="-", ins_sg="-", pre_sg="-", nom_pl="-", gen_pl="-", dat_pl="-", acc_pl="-", ins_pl="-", pre_pl="-", } stress_patterns["b"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="+", ins_sg="+", pre_sg="+", nom_pl="+", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["b'"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="+", ins_sg="-", pre_sg="+", nom_pl="+", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["c"] = { nom_sg="-", gen_sg="-", dat_sg="-", acc_sg="-", ins_sg="-", pre_sg="-", nom_pl="+", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["d"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="+", ins_sg="+", pre_sg="+", nom_pl="-", gen_pl="-", dat_pl="-", acc_pl="-", ins_pl="-", pre_pl="-", } stress_patterns["d'"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="-", ins_sg="+", pre_sg="+", nom_pl="-", gen_pl="-", dat_pl="-", acc_pl="-", ins_pl="-", pre_pl="-", } stress_patterns["e"] = { nom_sg="-", gen_sg="-", dat_sg="-", acc_sg="-", ins_sg="-", pre_sg="-", nom_pl="-", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["f"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="+", ins_sg="+", pre_sg="+", nom_pl="-", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["f'"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="-", ins_sg="+", pre_sg="+", nom_pl="-", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["f''"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="+", ins_sg="-", pre_sg="+", nom_pl="-", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } ending_stressed_gen_pl_patterns = m_table.listToSet({"b", "b'", "c", "e", "f", "f'", "f''"}) ending_stressed_pre_sg_patterns = m_table.listToSet({"b", "b'", "d", "d'", "f", "f'", "f''"}) ending_stressed_dat_sg_patterns = ending_stressed_pre_sg_patterns ending_stressed_sg_patterns = ending_stressed_pre_sg_patterns ending_stressed_pl_patterns = m_table.listToSet({"b", "b'", "c"}) local numbers = { ["s"] = "singular", ["p"] = "plural", } local old_title_temp = [=[Pre-reform declension of <b lang="ru" class="Cyrl">{lemma}</b>]=] local title_temp = [=[Declension of <b lang="ru" class="Cyrl">{lemma}</b>]=] local extra_case_template, extra_case_template_with_plural local internal_notes_template local notes_template local templates = {} -- Convert a raw override into a canonicalized list of individual overrides. -- If input is nil, so is output. Certain junk (e.g. <br/>) is removed, -- and ~ and ~~ are substituted appropriately; ARGS and CASE are required for -- this purpose. FORMS is the list of existing case forms and is currently -- used only to retrieve the dat_sg for handling + in loc/par (this means -- that any overrides of the dat_sg have to be handled before handling -- overrides of loc/par). N is the suffix used to retrieve the override -- -- either a number for word-specific overrides, or an empty string for -- overall overrides. -- -- It will still be necessary to call m_table_tools.separate_notes() to separate -- off any trailing "notes" (asterisks, superscript numbers, etc.), and -- m_links.remove_links() to remove any links to get the raw override form. canonicalize_override = function(args, case, forms, n) local val = args[case .. n] if not val then return nil end -- clean <br /> that's in many multi-form entries and messes up linking val = rsub(val, "<br%s*/>", "") -- substitute ~ and ~~ and split by commas local stem, manualtr if rfind(case, "_pl") then stem, manualtr = args.pl, args.pltr end if not stem then stem, manualtr = args.stem, args.stemtr end local ustem, umanualtr = com.make_unstressed_once(stem, manualtr) local stemtr, ustemtr = manualtr, umanualtr local vals = rsplit(val, "%s*,%s*") local retvals = {} for _, val in ipairs(vals) do local valru, valtr = com.split_russian_tr(val) valru = rsub(valru, "~~", ustem) valru = rsub(valru, "~", com.is_stressed(val) and ustem or stem) if rfind(valru, "^%*") then valru = rsub(valru, "^%*", "") .. HYPMARKER end if valtr then stemtr = stemtr or com.translit_no_links(stem) ustemtr = ustemtr or com.translit_no_links(ustem) valtr = rsub(valtr, "~~", ustemtr) valtr = rsub(valtr, "~", com.is_stressed(val) and ustemtr or stemtr) elseif val:find("~") and manualtr then valtr = com.translit_no_links(val) valtr = rsub(valtr, "~~", umanualtr) valtr = rsub(valtr, "~", com.is_stressed(val) and umanualtr or manualtr) end if valtr then if valtr:sub(1, 1) == "*" then valtr = valtr:sub(2) .. HYPMARKER end valtr = com.j_correction(valtr) end table.insert(retvals, {valru, valtr}) end vals = retvals -- handle + in loc/par meaning "the expected form"; NOTE: This requires -- that dat_sg has already been processed! if case == "loc" or case == "par" then local new_vals = {} for _, rutr in ipairs(vals) do -- don't just handle + by itself in case the arg has в or на -- or whatever attached to it if rfind(rutr[1], "^%+") or rfind(rutr[1], "[%s%[|]%+") then for _, dat in ipairs(forms["dat_sg"]) do local ru, tr = rutr[1], rutr[2] local datru, dattr = dat[1], dat[2] local valru, valtr -- separate off any footnote symbols (which may have been -- introduced by a *tail or *tailall argument, which we need -- to process before this stage so overrides don't get -- automatically marked with footnote symbols); if par, -- try to preserve them, but with loc this can cause -- problems in case there are multiple dative forms -- (stress variants) and the last one is marked with a -- footnote symbol (occurs in грудь) local datru_entry, datru_notes = m_table_tools.separate_notes(datru) local dattr_entry, dattr_notes if dattr then dattr_entry, dattr_notes = m_table_tools.separate_notes(dattr) end if case == "par" then valru, valtr = datru_entry, dattr_entry else valru, valtr = com.make_ending_stressed(datru_entry, dattr_entry) datru_notes = "" dattr_notes = "" end -- wrap the word in brackets so it's linked; but not if it -- appears to already be linked ru = rsub(ru, "^%+", "[[" .. valru .. "]]") ru = rsub(ru, "([%[|])%+", "%1" .. valru) ru = rsub(ru, "(%s)%+", "%1[[" .. valru .. "]]") ru = ru .. datru_notes -- do the translit; but it shouldn't have brackets in it if tr or valtr then tr = tr or com.translit_no_links(rutr[1]) valtr = valtr or com.translit_no_links(valru) tr = rsub(tr, "^%+", valtr) tr = rsub(tr, "(%s)%+", "%1" .. valtr) tr = tr .. dattr_notes end table.insert(new_vals, {ru, tr}) end else table.insert(new_vals, rutr) end end vals = new_vals end -- auto-accent/check for necessary accents local newvals = {} for _, v in ipairs(vals) do local ru, tr = v[1], v[2] if not args.allow_unaccented then if tr and com.is_unstressed(ru) ~= com.is_unstressed(tr) then error("Override " .. ru .. " and translit " .. tr .. " must have same accent pattern") end -- it's safe to accent monosyllabic stems if com.is_monosyllabic(ru) then ru, tr = com.make_ending_stressed(ru, tr) elseif com.needs_accents(ru) then error("Override " .. ru .. " for case " .. case .. n .. " requires an accent") end end table.insert(newvals, {ru, tr}) end vals = newvals return vals end local function process_overrides(args, f, n) local function process_override(case) if args[case .. n] then local overrides = canonicalize_override(args, case, f, n) if not f[case] then f[case] = {} end local new_overrides = {} for _, form in ipairs(overrides) do -- Don't consider overrides of loc/par/voc irregular since -- they're only specified through overrides; FIXME: Theoretically -- we could consider loc/par irregular if they don't follow the -- expected forms; but we'd have to figure out how to eliminate -- the preposition that may be specified if not overridable_only_cases_set[case] and not args.manual and not contains_rutr_pair(f[case], form) then local formru, formnotes = m_table_tools.separate_notes(form[1]) if formru ~= "-" then -- don't mark an override of - as irregular, even if -- it has an attached footnote symbol form = com.concat_paired_russian_tr(form, {IRREGMARKER}) end end if case == "pauc" then -- Internal note indicating that the form is for numbers 2, 3 and 4. form = com.concat_paired_russian_tr(form, {paucal_marker}) end table.insert(new_overrides, form) end f[case] = new_overrides args.any_overridden[case] = true end end -- do dative singular first because it will be used by loc/par process_override("dat_sg") -- now do the rest for _, case in ipairs(overridable_cases) do if case ~= "dat_sg" then process_override(case) end end -- if the nominative is overridden, use it to set the linked version unless -- that is also specifically overridden if args["nom_sg" .. n] and not args["nom_sg_linked" .. n] then f.nom_sg_linked = f.nom_sg end if args["nom_pl" .. n] and not args["nom_pl_linked" .. n] then f.nom_pl_linked = f.nom_pl end -- convert empty lists to nil to facilitate computation of accusative -- case variants below. for _, case in ipairs(all_cases) do if f[case] then if type(f[case]) ~= "table" then error("Logic error, args[case] should be nil or table") end if #f[case] == 0 then f[case] = nil end end end end local function process_tail_args(args, f, n) local function append_note_all(case, value) value = com.split_russian_tr(value, "dopair") local function append1(case) if f[case] then for i=1,#f[case] do f[case][i] = com.concat_paired_russian_tr(f[case][i], value) end end end append1(case) if case == "acc_sg" then append1("acc_sg_in") append1("acc_sg_an") elseif case == "acc_pl" then append1("acc_pl_in") append1("acc_pl_an") end end local function append_note_last(case, value, gt_one) value = com.split_russian_tr(value, "dopair") local function append1(case) if f[case] then local lastarg = #f[case] if lastarg > (gt_one and 1 or 0) then f[case][lastarg] = com.concat_paired_russian_tr(f[case][lastarg], value) end end end append1(case) if case == "acc_sg" then append1("acc_sg_in") append1("acc_sg_an") elseif case == "acc_pl" then append1("acc_pl_in") append1("acc_pl_an") end end local function handle_tail_hyp(suf) local arg, val for _, case in ipairs(all_cases) do arg = args[case .. "_" .. suf .. n] if arg then append_note_last(case, suf == "hyp" and HYPMARKER or arg) end arg = args[case .. "_" .. suf .. "all" .. n] if arg then append_note_all(case, suf == "hyp" and HYPMARKER or arg) end if not rfind(case, "_pl") then arg = args["sg" .. suf .. "all" .. n] if arg then append_note_all(case, suf == "hyp" and HYPMARKER or arg) end arg = args["sg" .. suf .. n] if arg then append_note_last(case, suf == "hyp" and HYPMARKER or arg, ">1") end else arg = args["pl" .. suf .. "all" .. n] if arg then append_note_all(case, suf == "hyp" and HYPMARKER or arg) end arg = args["pl" .. suf .. n] if arg then append_note_last(case, suf == "hyp" and HYPMARKER or arg, ">1") end end if not rfind(case, "nom_") and not rfind(case, "acc_") then arg = args["obl" .. suf .. "all" .. n] if arg then append_note_all(case, suf == "hyp" and HYPMARKER or arg) end arg = args["obl" .. suf .. n] if arg then append_note_last(case, suf == "hyp" and HYPMARKER or arg, ">1") end end end end handle_tail_hyp("tail") handle_tail_hyp("hyp") end handle_forms_and_overrides = function(args, n, islast) local f = args.forms process_tail_args(args, f, n) process_overrides(args, f, n) local an = args.thisa -- Maybe set the value of the animate/inanimate accusative variants based -- on nom/acc/gen overrides. Don't do this if there was a specific override -- of this form. Otherwise, always propagate an accusative singular -- override, and propagate the nom/gen sg if there wasn't a specific -- accusative suffix anywhere (occurs in the fem sg and sometimes the neut -- sg). This logic duplicates logic in handle_overall_forms_and_overrides(); -- see long comment there. We need to duplicate the whole logic here to -- handle words like мать-одиночка, which has an override of acc_sg1. if not args["acc_sg_an" .. n] then if args["acc_sg" .. n] then f.acc_sg_an = f.acc_sg elseif not args.this_any_non_nil.acc_sg then f.acc_sg_an = f.acc_sg or an == "i" and f.nom_sg or f.gen_sg or f.acc_sg_an end end if not args["acc_sg_in" .. n] then if args["acc_sg" .. n] then f.acc_sg_in = f.acc_sg elseif not args.this_any_non_nil.acc_sg then f.acc_sg_in = f.acc_sg or an == "a" and f.gen_sg or f.nom_sg or f.acc_sg_in end end if not args["acc_pl_an" .. n] then if args["acc_pl" .. n] then f.acc_pl_an = f.acc_pl elseif not args.this_any_non_nil.acc_pl then f.acc_pl_an = f.acc_pl or an== "i" and f.nom_pl or f.gen_pl or f.acc_pl_an end end if not args["acc_pl_in" .. n] then if args["acc_pl" .. n] then f.acc_pl_in = f.acc_pl elseif not args.this_any_non_nil.acc_pl then f.acc_pl_in = f.acc_pl or an == "a" and f.gen_pl or f.nom_pl or f.acc_pl_in end end f.loc = f.loc or f.pre_sg f.par = f.par or f.gen_sg f.voc = f.voc or f.nom_sg -- Set these in case we have plural only, in which case the -- singular will also get set to these same values in case we are -- a plural-only word in a singular-only expression. f.loc_pl = f.loc_pl or f.pre_pl f.par_pl = f.par_pl or f.gen_pl f.voc_pl = f.voc_pl or f.nom_pl f.count = f.count or f.gen_pl f.pauc = f.pauc or f.gen_sg local nu = args.thisn -- If we have a singular-only, set the plural forms to the singular forms, -- and vice-versa. This is important so that things work in multi-word -- expressions that combine different number restrictions (e.g. -- singular-only with singular/plural or singular-only with plural-only, -- compare "St. Vincent and the Grenadines" [Сент-Винсент и Гренадины]). if nu == "s" then f.nom_pl_linked = f.nom_sg_linked f.nom_pl = f.nom_sg f.gen_pl = f.gen_sg f.dat_pl = f.dat_sg f.acc_pl = f.acc_sg f.acc_pl_an = f.acc_sg_an f.acc_pl_in = f.acc_sg_in f.ins_pl = f.ins_sg f.pre_pl = f.pre_sg f.nom_pl = f.nom_sg f.loc_pl = f.loc f.par_pl = f.par f.voc_pl = f.voc elseif nu == "p" then f.nom_sg_linked = f.nom_pl_linked f.nom_sg = f.nom_pl f.gen_sg = f.gen_pl f.dat_sg = f.dat_pl f.acc_sg = f.acc_pl f.acc_sg_an = f.acc_pl_an f.acc_sg_in = f.acc_pl_in f.ins_sg = f.ins_pl f.pre_sg = f.pre_pl f.nom_sg = f.nom_pl f.loc = f.loc_pl f.par = f.par_pl f.voc = f.voc_pl end end handle_overall_forms_and_overrides = function(args) local overall_forms = {} for _, case in ipairs(displayable_cases) do overall_forms[case] = concat_word_forms(args.per_word_info, case) end local acc_sg_overridden = args.acc_sg local acc_pl_overridden = args.acc_pl process_tail_args(args, overall_forms, "") process_overrides(args, overall_forms, "") if case_will_be_displayed(args, "pauc") then insert_if_not(args.internal_notes, paucal_internal_note) end -- if IRREGMARKER is anywhere in text, remove all instances and put -- at the end before any notes. local function clean_irreg_marker(case, text) if rfind(text, IRREGMARKER) then text = rsub(text, IRREGMARKER, "") local entry, notes = m_table_tools.separate_notes(text) if case_will_be_displayed(args, case) then insert_if_not(args.internal_notes, IRREGMARKER .. " Irregular.") args.any_irreg = true args.any_irreg_case[case] = true end return entry .. IRREGMARKER .. notes else return text end end -- set final args[case] and clean up IRREGMARKER. for _, case in ipairs(all_cases) do args[case] = overall_forms[case] if args[case] then local cleaned_forms = {} for _, form in ipairs(args[case]) do local ru, tr = form[1], form[2] ru = clean_irreg_marker(case, ru) if tr then tr = clean_irreg_marker(case, tr) end table.insert(cleaned_forms, {ru, tr}) end args[case] = cleaned_forms end end -- Maybe set the value of the animate/inanimate accusative variants based -- on nom/acc/gen overrides. Don't do this if there was a specific override -- of this form. Otherwise, always propagate an accusative singular -- override, and propagate the nom/gen sg if there wasn't a specific -- accusative suffix anywhere (occurs in the fem sg and sometimes the neut -- sg). We need to do this somewhat complicated procedure to get overrides -- to work correctly, e.g. acc_sg overrides of feminine and neuter nouns -- (such as мать and полслова) and gen_sg/gen_pl overrides of masculine -- animate nouns. We also ran into an issue with words like мазло that are -- neuter-form but can be both masculine animate (in which case the -- acc sg inherits from the genitive) and neuter animate (in which case the -- acc sg has the fixed ending -о, same as nominative, despite the animacy). -- Remember also that the animate/inanimate accusative variants are the ones -- displayed, not the plain acc_sg/acc_pl ones. if not args.any_overridden.acc_sg_an then if acc_sg_overridden then args.acc_sg_an = args.acc_sg elseif not args.any_non_nil.acc_sg then args.acc_sg_an = args.acc_sg or args.a == "i" and args.nom_sg or args.gen_sg or args.acc_sg_an end end if not args.any_overridden.acc_sg_in then if acc_sg_overridden then args.acc_sg_in = args.acc_sg elseif not args.any_non_nil.acc_sg then args.acc_sg_in = args.acc_sg or args.a == "a" and args.gen_sg or args.nom_sg or args.acc_sg_in end end if not args.any_overridden.acc_pl_an then if acc_pl_overridden then args.acc_pl_an = args.acc_pl elseif not args.any_non_nil.acc_pl then args.acc_pl_an = args.acc_pl or args.a == "i" and args.nom_pl or args.gen_pl or args.acc_pl_an end end if not args.any_overridden.acc_pl_in then if acc_pl_overridden then args.acc_pl_in = args.acc_pl elseif not args.any_non_nil.acc_pl then args.acc_pl_in = args.acc_pl or args.a == "a" and args.gen_pl or args.nom_pl or args.acc_pl_in end end -- Try to set the values of acc_sg and acc_pl. The only time we can't is -- when the noun is bianimate and the anim/inan values are different. -- This is used primarily for generate_forms() and generate_multi_forms(), -- since we don't actually display these forms. if args.a == "a" then args.acc_sg = args.acc_sg or args.acc_sg_an args.acc_pl = args.acc_pl or args.acc_pl_an elseif args.a == "i" then args.acc_sg = args.acc_sg or args.acc_sg_in args.acc_pl = args.acc_pl or args.acc_pl_in else -- bianimate args.acc_sg = args.acc_sg or m_table.deepEquals(args.acc_sg_in, args.acc_sg_an) and args.acc_sg_in or nil args.acc_pl = args.acc_pl or m_table.deepEquals(args.acc_pl_in, args.acc_pl_an) and args.acc_pl_in or nil end end -- Subfunction of concat_word_forms(), used to implement recursively -- generating all combinations of elements from WORD_FORMS (a list, one -- element per word, of a list of the forms for a word, each of which is a -- two-element list of {RUSSIAN, TR}) and TRAILING_FORMS (a list of forms, the -- accumulated suffixes for trailing words so far in the recursion process, -- again where each form is a two-element list {RUSSIAN, TR}). Each time we -- recur we take the last FORMS item off of WORD_FORMS and to each form in -- FORMS we add all elements in TRAILING_FORMS, passing the newly generated -- list of items down the next recursion level with the shorter WORD_FORMS. -- We end up returning a list of concatenated forms, where each list item -- is a two-element list {RUSSIAN, TR}. local function concat_word_forms_1(word_forms, trailing_forms) if #word_forms == 0 then local retforms = {} for _, form in ipairs(trailing_forms) do local ru, tr = form[1], form[2] -- Remove <insa> and <insb> markers; they've served their purpose. ru = rsub(ru, "<ins[ab]>", "") tr = tr and rsub(tr, "<ins[ab]>", "") table.insert(retforms, {ru, tr}) end return retforms else local last_form_info = table.remove(word_forms) local last_forms, joiner = last_form_info[1], last_form_info[2] local new_trailing_forms = {} for _, form in ipairs(last_forms) do for _, trailing_form in ipairs(trailing_forms) do -- If form to prepend is empty, don't add the joiner; this -- is principally used in overall overrides, where we stuff -- the entire override into the last word local full_form = form[1] == "" and trailing_form or com.concat_paired_russian_tr(form, com.concat_paired_russian_tr(joiner, trailing_form), "movenotes") if rfind(full_form[1], "<insa>") and rfind(full_form[1], "<insb>") then -- REJECT! So we don't get mixtures of the two feminine -- instrumental singular endings. else table.insert(new_trailing_forms, full_form) end end end return concat_word_forms_1(word_forms, new_trailing_forms) end end -- Generate a list of overall forms by concatenating the per-word forms. -- PER_WORD_INFO comes from args.per_word_info and is a list of -- WORD_INFO items, one per word, each of which a two element list of -- WORD_FORMS (a table listing the forms for each case) and JOINER (a string). -- We loop over all possible combinations of elements from each word's list -- of forms for the given case; this requires recursion. concat_word_forms = function(per_word_info, case) local word_forms = {} -- Gather the appropriate word forms. We have to recreate this anew -- because it will be destructively modified by concat_word_forms_1(). for _, word_info in ipairs(per_word_info) do table.insert(word_forms, {word_info[1][case], word_info[2]}) end -- We need to start the recursion with the second parameter containing -- one blank element rather than no elements, otherwise no elements -- will be propagated to the next recursion level. return concat_word_forms_1(word_forms, {{""}}) end local accel_forms = { nom_sg = "nom|s", nom_sg_linked = "nom|s", nom_pl = "nom|p", nom_pl_linked = "nom|p", gen_sg = "gen|s", gen_pl = "gen|p", dat_sg = "dat|s", dat_pl = "dat|p", acc_sg_an = "an|acc|s", acc_pl_an = "an|acc|p", acc_sg_in = "in|acc|s", acc_pl_in = "in|acc|p", ins_sg = "ins|s", ins_pl = "ins|p", pre_sg = "pre|s", pre_pl = "pre|p", loc = "loc|s", loc_pl = "loc|p", voc = "voc|s", voc_pl = "voc|p", par = "par|s", par_pl = "par|p", count = "count form", pauc = "pau", } -- Make the table make_table = function(args) local data = {} data.after_title = " " .. args.heading data.number = args.nonumber and "" or numbers[args.n] local lemma_forms = args[args.n == "p" and "nom_pl" or "nom_sg"] data.lemma = nom.show_form(args.explicit_lemma and {{args.explicit_lemma, args.explicit_lemmatr}} or args[args.n == "p" and "nom_pl_linked" or "nom_sg_linked"], "lemma", nil, nil) data.title = args.title or format(args.old and old_title_temp or title_temp, data) local sg_an_in_equal = m_table.deepEquals(args.acc_sg_an, args.acc_sg_in) local pl_an_in_equal = m_table.deepEquals(args.acc_pl_an, args.acc_pl_in) for _, case in ipairs(displayable_cases) do local accel_form = accel_forms[case] if not accel_form then error("Something wrong, can't find accelerator form for " .. case) end if (sg_an_in_equal and (case == "acc_sg_an" or case == "acc_sg_in") or pl_an_in_equal and (case == "acc_pl_an" or case == "acc_pl_in")) then accel_form = rsub(accel_form, "^[ai]n|", "") end if args.n == "p" then accel_form = rsub(accel_form, "|p$", "") end data[case] = nom.show_form(args[case], false, accel_form, lemma_forms, "remove monosyllabic accents only lemma") end local temp = nil if args.n == "s" then data.nom_x = data.nom_sg data.gen_x = data.gen_sg data.dat_x = data.dat_sg data.acc_x_an = data.acc_sg_an data.acc_x_in = data.acc_sg_in data.ins_x = data.ins_sg data.pre_x = data.pre_sg if sg_an_in_equal then temp = "one_number" else temp = "one_number_split_animacy" end elseif args.n == "p" then data.nom_x = data.nom_pl data.gen_x = data.gen_pl data.dat_x = data.dat_pl data.acc_x_an = data.acc_pl_an data.acc_x_in = data.acc_pl_in data.ins_x = data.ins_pl data.pre_x = data.pre_pl data.par = data.par_pl data.loc = data.loc_pl data.voc = data.voc_pl if pl_an_in_equal then temp = "one_number" else temp = "one_number_split_animacy" end else if pl_an_in_equal then temp = "both_numbers" elseif sg_an_in_equal then temp = "both_numbers_split_animacy_plural_only" else temp = "both_numbers_split_animacy" end end for _, extra_case in ipairs({ {"par", "partitive"}, {"loc", "locative"}, {"voc", "vocative"}, }) do local case, engcase, template = unpack(extra_case) local a = args.a local colspan = (a == "b" or a == "bi" or a == "both" or a == "ai" or a == "ia") and 2 or 1 if args.n ~= "s" and args.n ~= "p" and args.any_overridden[case .. "_pl"] then if not args.any_overridden[case] then data[case] = "" end template = extra_case_template_with_plural elseif args.n ~= "p" and args.any_overridden[case] or args.n == "p" and args.any_overridden[case .. "_pl"] then template = extra_case_template end if template then template = format(template(colspan), {case=case, engcase=engcase}) data[case .. "_clause"] = format(template, data) else data[case .. "_clause"] = "" end end for _, extra_case in ipairs({ {"count", "count form"}, {"pauc", "paucal"}, }) do local case, engcase, template = unpack(extra_case) local a = args.a local colspan = (a == "b" or a == "bi" or a == "both" or a == "ai" or a == "ia") and 2 or 1 if args.n ~= "p" and args.any_overridden[case] then template = extra_case_template end if template then template = format(template(colspan), {case=case, engcase=engcase}) data[case .. "_clause"] = format(template, data) else data[case .. "_clause"] = "" end end local notes = get_arg_chain(args, "notes", "notes") local all_notes = {} for _, note in ipairs(args.internal_notes) do -- Superscript footnote marker at beginning of note, similarly to what's -- done at end of forms. local symbol, entry = m_table_tools.get_initial_notes(note) table.insert(all_notes, symbol .. entry) end for _, note in ipairs(notes) do -- Here too. local symbol, entry = m_table_tools.get_initial_notes(note) table.insert(all_notes, symbol .. entry) end data.notes = table.concat(all_notes, "<br />") data.notes_clause = data.notes ~= "" and format(notes_template, data) or "" return format(templates[temp], data) end function extra_case_template(colspan) colspan = colspan or 1 return ([===[ ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=%s | {engcase} | {\op}{case}{\cl} | |-]===]):format(colspan) end function extra_case_template_with_plural(colspan) colspan = colspan or 1 return ([===[ ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=%s | {engcase} | {\op}{case}{\cl} | {\op}{case}_pl{\cl} |-]===]):format(colspan) end notes_template = [===[ <div style="width:100%;text-align:left;background:var(--wikt-palette-lightblue, #d9ebff);"> <div style="display:inline-block;text-align:left;padding-left:1em;padding-right:1em"> {notes} </div></div> ]===] local function template_prelude(min_width) min_width = min_width or "70" return rsub([===[ <div> <div class="NavFrame" data-toggle-category="declension" style="max-width:MINWIDTHem"> <div class="NavHead" style="background:var(--wikt-palette-lighterblue, #ebf4ff);">{title}<span style="font-weight:normal;">{after_title}</span>&nbsp;</div> <div class="NavContent"> {\op}| style="table-layout:fixed; text-align:center; max-width:MINWIDTHem; width:100%;" class="inflection inflection-table" |- class="rowgroup" ]===], "MINWIDTH", min_width) end local function template_postlude() return [===[|-{par_clause}{loc_clause}{voc_clause}{count_clause}{pauc_clause} |{\cl}{notes_clause}</div></div></div>]===] end templates["both_numbers"] = template_prelude("45") .. [===[ ! style="width:7em;background:var(--wikt-palette-lightblue, #d9ebff);" | ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | singular ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | plural |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | nominative | data-accel-col=1 | {nom_sg} | data-accel-col=2 | {nom_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | genitive | data-accel-col=1 | {gen_sg} | data-accel-col=2 | {gen_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | dative | data-accel-col=1 | {dat_sg} | data-accel-col=2 | {dat_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | accusative | data-accel-col=1 | {acc_sg_an} | data-accel-col=2 | {acc_pl_an} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | instrumental | data-accel-col=1 | {ins_sg} | data-accel-col=2 | {ins_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | prepositional | data-accel-col=1 | {pre_sg} | data-accel-col=2 | {pre_pl} ]===] .. template_postlude() templates["both_numbers_split_animacy"] = template_prelude("50") .. [===[ ! style="width:15em;background:var(--wikt-palette-lightblue, #d9ebff);" colspan=2 | ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | singular ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | plural |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | nominative | data-accel-col=1 | {nom_sg} | data-accel-col=2 | {nom_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | genitive | data-accel-col=1 | {gen_sg} | data-accel-col=2 | {gen_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | dative | data-accel-col=1 | {dat_sg} | data-accel-col=2 | {dat_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" rowspan=2 | accusative ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | animate | data-accel-col=1 | {acc_sg_an} | data-accel-col=2 | {acc_pl_an} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | inanimate | data-accel-col=1 | {acc_sg_in} | data-accel-col=2 | {acc_pl_in} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | instrumental | data-accel-col=1 | {ins_sg} | data-accel-col=2 | {ins_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | prepositional | data-accel-col=1 | {pre_sg} | data-accel-col=2 | {pre_pl} ]===] .. template_postlude() templates["both_numbers_split_animacy_plural_only"] = template_prelude("50") .. [===[ ! style="width:15em;background:var(--wikt-palette-lightblue, #d9ebff);" colspan=2 | ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | singular ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | plural |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | nominative | data-accel-col=1 | {nom_sg} | data-accel-col=2 | {nom_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | genitive | data-accel-col=1 | {gen_sg} | data-accel-col=2 | {gen_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | dative | data-accel-col=1 | {dat_sg} | data-accel-col=2 | {dat_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" rowspan=2 | accusative ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | animate | data-accel-col=1 rowspan=2 | {acc_sg_an} | data-accel-col=2 | {acc_pl_an} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | inanimate | data-accel-col=2 | {acc_pl_in} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | instrumental | data-accel-col=1 | {ins_sg} | data-accel-col=2 | {ins_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | prepositional | data-accel-col=1 | {pre_sg} | data-accel-col=2 | {pre_pl} ]===] .. template_postlude() templates["one_number"] = template_prelude("30") .. [===[ ! style="width:7em;background:var(--wikt-palette-lightblue, #d9ebff);" | ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | {number} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | nominative | {nom_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | genitive | {gen_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | dative | {dat_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | accusative | {acc_x_an} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | instrumental | {ins_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | prepositional | {pre_x} ]===] .. template_postlude() templates["one_number_split_animacy"] = template_prelude("35") .. [===[ ! style="width:15em;background:var(--wikt-palette-lightblue, #d9ebff);" colspan=2 | ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | {number} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | nominative | {nom_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | genitive | {gen_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | dative | {dat_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" rowspan=2 | accusative ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | animate | {acc_x_an} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | inanimate | {acc_x_in} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | instrumental | {ins_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | prepositional | {pre_x} ]===] .. template_postlude() return export 4xe9f3vlbxgqj57x0p9pa3akucutgdj 5669926 5669921 2026-06-23T07:33:24Z MustafaCavlak 59368 5669926 Scribunto text/plain --[=[ This module contains functions for creating inflection tables for Russian nouns. Author: Benwing, rewritten from early version by Wikitiki89 Form of arguments: One of the following: 1. LEMMA|DECL|PLSTEM (all arguments optional) 2. ACCENT|LEMMA|DECL|PLSTEM (all arguments optional) 3. multiple sets of arguments separated by the literal word "or" Arguments: ACCENT: Accent pattern (a b c d e f b' d' f' f''). Multiple values can be specified, separated by commas. If omitted, defaults to a or b depending on the position of stress on the lemma or explicitly-specified declension. LEMMA: Lemma form (i.e. nom sg or nom pl), with appropriately-placed stress; or the stem, if an explicit declension is specified (in this case, the declension usually looks like an ending, and the stem is the portion of the lemma minus the ending). In the first argument set (i.e. first set of arguments separated by "or"), defaults to page name; in later sets, defaults to lemma of previous set. A plural form can be given, and causes argument n= to default to n=p (plural only). Normally, an accent is required if multisyllabic, and unaccented monosyllables with automatically be stressed; prefix with * to override both behaviors. DECL: Declension field. Normally omitted to autodetect based on the lemma form; see below. PLSTEM: special plural stem (defaults to stem of lemma) Additional named arguments: a: animacy (a/an/anim = animate, i/in/inan = inanimate, b/bi/both/ai = both (listing animate first in the headword), ia = both (listing inanimate first in the headword), otherwise inanimate) n: number restriction (p = plural only, s = singular only, b = both; defaults to both unless the lemma is plural, in which case it defaults to plural only) CASE_NUM or acc_NUM_ANIM or par/loc/voc: override (or multiple values separated by commas) for case/number combination; forms auto-linked; can have raw links in it, can have an ending "note" (*, +, 1, 2, 3, etc.) pltail: Specify something (usually a * or similar) to attach to the end of the last plural form when there's more than one. Used in conjunction with notes= to indicate that alternative plural forms are obsolete, poetic, etc. pltailall: Similar pltail= but attaches to all plural forms. Typically used in conjunction with notes= to make a comment about the plural as a whole (e.g. it's mostly hypothetical, rare and awkward, etc.). sgtail, sgtailall: Same as pltail=, pltailall= but for the singular. obltail, obltailall: Same as pltail=, pltailall= but for oblique cases (not the nominative or accusative). CASE_NUM_tail: Attach the argument to the end of the last form (whether there's one or more than one) for the particular case/number combination. Note that this doesn't work quite like pltail= or sgtail= in that it doesn't skip adding the argument when there's only one form. CASE_NUM_tailall: Attach the argument to the end of all forms specified for the particular case/number combination. Similar to pltailall= or sgtailall=. suffix: Add a suffix such as ся to all forms. prefix: Add a prefix to all forms. plhyp, plhypall, CASE_NUM_hyp, etc.: Same as pltail, pltailall, CASE_NUM_tal, etc. but specify that the marked forms are mostly hypothetical or rare/awkard. Generally you will want plhypall=y to mark the plural as hypothetical. Per word named arguments: All of the above named arguments have per-word variants, e.g. a1, a2, ...; n1, n2, ...; CASE_NUM1, CASE_NUM2, ...; pltail1, pltail2, ...; etc. These apply to the individual words of a form. Case abbreviations: nom: nominative gen: genitive dat: dative acc: accusative ins: instrumental pre: prepositional par: partitive loc: locative voc: vocative Number abbreviations: sg: singular pl: plural Animacy abbreviations: an: animate in: inanimate Declension field: One of the following for regular nouns: (blank) GENDER -VARIANT GENDER-VARIANT $ DECLTYPE DECLTYPE/DECLTYPE (also, can append various special-case markers to any of the above) Or one of the following for adjectival nouns: + +ь $ +short, +mixed or +proper +DECLTYPE GENDER if present is m, f, n or 3f; for regular nouns, required if the lemma ends in -ь or is plural, ignored otherwise. 3f is the same as f but in the case of a plural lemma in -и, detects a third-declension feminine with singular in -ь rather than a first-declension feminine with singular in -а or -я. VARIANT is one way of requesting variant declensions (see also special case (1) for variant nom pls, and special case (2) for variant gen pls). The currently allowed values are -ья (will select a mixed declension that has some normal declension in its singular and the -ья plural declension); -ин (for animate masculine nouns in -ин with plural in -е -- note that this is autodetected in the majority of cases where the ending in -янин or -анин); -ишко (used for inanimate neuter-form diminutive masculine nouns in -ишко [also сараю́шко] with nom pl -и and colloquial feminine-ending alternants in some singular cases); -ище (similar to -ишко but used for *animate* augmentative masculine neuter-form nouns in -ище). Variants -ишко and -ище must be given with with special case (1). $ is indeclinable words. It is principally useful in multiword expressions where some of the words are indeclinable. DECLTYPE is an explicit declension type. Normally you shouldn't use this, and should instead let the declension type be autodetected based on the ending, supplying the appropriate hint if needed (gender for regular nouns, +ь for adjectives). If provided, the declension type is usually the same as the ending, and if present, the lemma field should be just the stem, without the ending. Possibilities for regular nouns are (blank) or # for hard-consonant declension, а, я, о, е or ё, е́, й, ья, ье or ьё, ь-m, ь-f, ин, ёнок or онок or енок, ёночек or оночек or еночек, мя, -а or #-а, ь-я, й-я, о-и or о-ы, -ья or #-ья, $ (indeclinable). Old-style (pre-reform) declensions use ъ instead of (blank), ъ-а instead of -а, ъ-ья instead of -ья, and инъ, ёнокъ/онокъ/енокъ, ёночекъ/оночекъ/еночекъ instead of the same without terminating ъ. The declensions can also be written with an accent on them; this chooses the same declension (except for е vs. е́), but causes ACCENT to default to pattern b instead of a. For adjectival nouns, you should normally supply just + and let the ending determine the declension; supply +ь in the case of a possessive adjectival noun in -ий, which have an extra -ь- in most endings compared with normal adjectival nouns in -ий, but which can't be distinguished based on the nominative singular. You can also supply +short, +mixed or +proper, which constrains the declension appropriately but still autodetects the gender-specific and stress-specific variant. If you do supply a specific declension type, as with regular nouns you need to omit the ending from the lemma field and supply just the stem. Possibilities are +ый, +ое, +ая, +ій, +ее, +яя, +ой, +о́е, +а́я, +ьій, +ье, +ья, +-short or +#-short (masc), +о-short, +о-stressed-short or +о́-short, +а-short, +а-stressed-short or +а́-short, and similar for -mixed and -proper (except there aren't any stressed mixed declensions). DECLTYPE/DECLTYPE is used for nouns with one declension in the singular and a different one in the plural, for cases that PLVARIANT and special case (1) below don't cover. Special-case markers: (1) for Zaliznyak-style alternative nominative plural ending: -а or -я for masculine, -и or -ы for neuter (2) for Zaliznyak-style alternative genitive plural ending: -ъ/none for masculine, -ей for feminine, -ов(ъ) for neuter, -ей for plural variant -ья * for reducibles (nom sg or gen pl has an extra vowel before the final consonant as compared with the stem found in other cases) ;ё for Zaliznyak-style alternation between last е in stem and ё TODO: 1. Multi-word issues: -- FIXME: Make sure internal_notes handled correctly; we may run into issues with multiple internal notes from different words, if we're not careful to use different footnote symbols for each type of footnote (which we don't do currently). [NOT DONE, MAY NOT DO] -- Handling default lemma: With multiple words, we should probably split the page name on spaces and default each word in turn [NOT DONE, MAY NOT DO] 2a. FIXME: For -ишко diminutives and -ище augmentatives, should add an appropriate category of some sort (currently marked by colloqfem= in category). 2b. FIXME: Adding a note to dat_sg also adds it to loc_sg when it exists; seems wrong. See луг. 2c. FIXME: When you have both d' and f in feminines and you use sgtail=*, you get two *'s. See User:Benwing2/test-ru-noun-debug. 3. ADJECTIVE FIXMES: 3a. FIXME: Change calls to ru-adj11 to use the new proper name support in ru-adjective. 3b. FIXME: Test that omitting a manual form in ru-adjective leaves the form as a big dash. 3c. FIXME: какой-либо and какой-то display genitives with translit -go instead of -vo. To fix this properly requires implementing real manual translit for adjectives. 3d. FIXME: Implement real manual translit for adjectives. 5. [FIXME: Consider adding an indicator in the header line when the ё/e alternation occurs. This is a bit tricky to calculate: If special case ;ё is given, but also if ё occurs in the stem and the accent pattern is as follows -- for sg-only, b' d' f' f'', also b d f if the noun is masc or 3rd-decl fem (i.e. nom-sg ending is non-syllabic); for pl-only, e f f' f'', also b b' c if the gen pl is non-syllabic; for sg/pl, any but a or b, also b if either nom sg or gen pl is non-syllabic. But it gets more complicated due to overrides. An alternative is to check all forms to see if ё is present in some but not all; but this is tricky also because e.g. reducibles frequently have ё/null alternation, which doesn't count, and some endings have е or ё in them, which also doesn't count. If we were to do it this way, we'd have to (a) count the number of е's in the form(s) with ё and verify that there's at least one form without ё and with one more е than in the form(s) with ё (and it gets trickier if different forms with ё have different numbers of е in them, although that is probably rare); and (b) ignore the appropriate endings (the best way to do this would probably be to look at the actual suffixes that were generated in args.suffixes and chop off any matching ending in the actual form(s), but also chop off final -е/ё, as well as -ев(ъ)/-ёв(ъ)/-ей/-ёй in the gen pl, which is frequently overridden, unless perhaps the stem ends in the same way). It'd probably not possible to do this in a 100% foolproof way but can be "good enough" for nearly all circumstances.] [MIGHT BE TOO MUCH WORK] 6. HEADWORD FIXMES: 6a. FIXME: In ru-headword, create a category for words whose gender doesn't match the form. (This is easy to do for ru-noun+ but harder for ru-noun. We would need to do limited autodetection of the ending: for singulars, -а/я should be feminine, -е/о/ё should be neuter, -ь should be masculine or feminine, anything else should be masculine; for plurals, -и/ы should be masculine or feminine, -а/я should be neuter except that -ія can be feminine or neuter due to old-style adjectival pluralia tantum nouns, anything else can be any gender.) 6b. FIXME: Recognize indeclinable nouns and indicate as indeclinable. Probably should work by checking the case forms to see if they're the same. 9. FIXME: Change stress-pattern detection and overriding to happen inside of looping over the two parts of a slash decl. Requires that the loop over the two parts happen outside of the loop over stress patterns. Requires that the category code get split into two parts, one to handle combined singular/plural categories that goes outside the two loops, and one to handle everything else that goes inside the two loops. 10. FIXME: override_matches_suffix() had a free variable reference to ARGS in it, which should have triggered an error whenever there was a nom_sg or nom_pl override but didn't. Is there an error causing this never to be called? Check. 11a. FIXME: In a multiword lemma, using loc2=+ causes only the second word to get linked instead of the whole expression. Same for par2=+, voc2=+. 11b. FIXME: Using loc=+ with a multiword lemma should do the right thing, same as if locN=+ is specified for each individual word. Instead it generates the locative as a whole from the dative, which fails e.g. if some of the words are adjectival. Same for par=+, voc=+. 13. Multi-word issues: -- Setting n=pl when auto-detecting a plural lemma. How does that interact with multi-word stuff? (DONE) -- compute_heading() -- what to do with multiple words? I assume we should display info on the first noun (non-indeclinable, non-adjectival), and on the first adjectival word otherwise, and finally on an indeclinable word (DONE) -- args.genders -- it should presumably come from the same word as is used in compute_heading(); but we should allow the overall gender to be overridden, at least in ru-noun+ (DONE) -- Bug in args.suffix: Gets added to every word in attach_with() and then again at the end, after pltail and such. Needs to be added to the last word only, before pltail. Need also suffixN for individual words. (DONE, NEEDS TESTING) -- Should have ..N versions of pltail and variants. (DONE, NEEDS TESTING) -- Need to handle overrides of acc_sg, acc_pl (DONE) -- Overrides of nom_sg/nom_pl should also override acc_sg/acc_pl if it was originally empty and the animacy is inanimate; similarly for gen_sg/gen_pl and animates; this needs to work both for per-word and overall overrides. (DONE) -- do_generate_forms(_multi) need to run part of make_table(), enough to combine all per_word_info into single lists of forms and store back into args[case]. (DONE, NEEDS TESTING) -- In generate_forms, should probably check if a=="i" and only return acc_sg_in as acc_sg=; or if a=="a" and only return acc_sg_an as acc_sg=; in old/new comparison code, do something similar, also when a=="b" check if acc_sg_in==acc_sg_an and make it acc_sg; when a=="b" and the _in and _an variants are different, might need to ignore them or check that acc_sg_in==nom_sg and acc_sg_an==gen_sg; similarly for _pl (DONE, NEEDS TESTING) -- Need to test with multiple words! [DONE] -- Current handling of <adj> won't work properly with multiple words; will need to translate word-by-word in that case (should be solved by manual-translit branch) [DONE] 14. In multiple-words branch, fix ru-decl-noun-multi so it recognizes things like *, (1), (2) and ; without the need for a separator. Consider using semicolon as a separator, since we already use it to separate ё from a previous declension. Maybe use $ or ~ for an indeclinable word; don't use semicolon. [IMPLEMENTED. NEED TO TEST.] 16. [Consider having ru-noun+ treat par= as a second genitive in the headword, as is done with край] [WON'T DO] 17. [FIXME: Consider removing slash patterns and instead handling them by allowing additional declension flags 'sg' and 'pl'. This simplifies the various special cases caused by slash declensions. It would also be possible to remove the special plural stem, which would get rid of more special cases. On the other hand, it makes it more complicated to support plural variant -ья with all singular types, and the category code that displays things like "Russian nouns with singular -X and plural -Y" also gets more complicated, and there's something convenient and intuitive about plural stems, and slash declensions are also convenient and at least somewhat intuitive. One possibility is to externally allow slash declensions and special plural stems and rewrite them internally to separate stems with 'sg' and 'pl' declension flags; but there are still the two coding issues mentioned above.] 18. [FIXME: Consider redoing slash patterns so they operate at the outer level, i.e. things like special cases apply separately in the singular and plural part of the slash pattern.] 19. In ru-noun, don't recognize -а with m as plural unless (1) or n=pl is also given, because there are masculine words with the feminine ending. Check using the test code whether this changes anything. Also check if there are other similar cases (neuter with -и isn't parallel because -и is always plural). [IMPLEMENTED. NEED TO TEST.] 19a. Internal notes weren't propagated properly from adjectives. [IMPLEMENTED. NEED TO TEST.] 19b. Add support for -ишко and -ище variants (p. 74 of Z), which conversationally and/or colloquially have feminine endings in certain cases. [IMPLEMENTED. NEED TO TEST. MAKE SURE THE INTERNAL NOTES APPEAR.] 19d. For masculine animate neuter-form nouns, the accusative singular ends in -а (-я soft) instead of -о. [IMPLEMENTED. NEED TO TEST. NOTE: Currently this variant only can be selected using new-style arguments where the gender can be given. Perhaps we should consider allowing gender to be specified with old-style explicit declensions.] 21. Put back gender hints for pl adjectival nouns; used by ru-noun+. [IMPLEMENTED. NEED TO TEST.] 23. Mixed and proper-noun adjectives have built-in notes. We need to handle those notes with an "internal_notes" section similar to what is used in the adjective module. [IMPLEMENTED. NEED TO TEST.] 24. Adjective detection code here needs to work the same as for the adjective module, in particular in the handling of short, stressed-short, mixed, proper, stressed-proper. [IMPLEMENTED. NEED TO TEST.] 25. Consider simplifying plural-variant code to only allow -ья as a plural variant [and maybe even change that to be something like (1')]. [IMPLEMENTED REDUCTION OF PLURAL VARIANTS TO -ья; PLURAL-VARIANT CODE STILL COMPLEX, THOUGH. NEED TO TEST.] 26. Automatically superscript *, numbers and similar things at the beginning of a note. Also do this in adjective module. [IMPLEMENTED. NEED TO TEST.] 28. Make the check for multiple stress patterns (categorizing/tracking) smarter, to keep a list of them and check at the end, so we handle multiple stress patterns specified through different arg sets. [IMPLEMENTED; NEED TO TEST.] 29. More sophisticated handling of user-requested plural variant vs. special case (1) vs. plural-detected variant. [IMPLEMENTED. NEED TO TEST FURTHER.] 30. Solution to ambiguous plural involving gender spec "3f". [IMPLEMENTED; NEED TO TEST. Use запчасти, новости.] 33. With pluralia tantum adjectival nouns, we don't know the gender. By default we assume masculine (or feminine for old-style -ія nouns) and currently this goes into the category, but shouldn't. [IMPLEMENTED.] 39. [Eventually: Even with decl type explicitly given, the full stem with ending should be included.] [MAY NEVER IMPLEMENT] 40. [Get error "Unable to dereduce" with strange noun ва́йя, what should happen?] [WILL NOT FIX; USE AN OVERRIDE] 41. In творог, module generates partitive творогу́ when it should copy the dative творогу́,тво́рогу. (DONE) 42. [[груз 200]] doesn't work. Interprets 200 as a footnote symbol. 43. When converting е -> ё not after cons and with translit, we should convert e -> o to avoid double j. (DONE) 44. FIXME: In ро́вня/ровня́, similarly with неровня, marks genitive plural ровня́ as irregular even though it isn't. (NOT OUR ERROR; THE DECLENSIONS OF THESE NOUNS MARKED THE ENDING-STRESSED VARIANTS WITH (2).) ]=]-- local m_utilities = require("Modül:araçlar") local m_table = require("Modül:table") local m_links = require("Modül:bağlantılar") local com = require("Modül:ru-genel") local nom = require("Modül:ru-nominal") local m_ru_adj = require("Modül:ru-ön ad") local m_str_utils = require("Modül:string araçları") local scriptutils = require("Modül:alfabe araçları") local m_table_tools = require("Modül:table tools") local m_debug = require("Modül:debug") local export = {} local lang = require("Modül:diller").getirKodaGore("ru") local Latn = require("Modül:alfabeler").getirKodaGore("Latn") local format = m_str_utils.format local rfind = m_str_utils.find local rsubn = m_str_utils.gsub local rmatch = m_str_utils.match local rsplit = m_str_utils.split local u = m_str_utils.char local ulower = m_str_utils.lower local usub = m_str_utils.sub local ulen = m_str_utils.len local unpack = unpack or table.unpack -- Lua 5.2 compatibility -- If enabled, compare this module with new version of module to make -- sure all declensions are the same. Eventually consider removing this; -- but useful as new code is created. local test_new_ru_noun_module = false local AC = u(0x0301) -- acute = ́ local GR = u(0x0300) -- grave = ̀ local CFLEX = u(0x0302) -- circumflex = ̂ local DIA = u(0x0308) -- diaeresis = ̈ local PSEUDOCONS = u(0xFFF2) -- pseudoconsonant placeholder, matching ru-common local IRREGMARKER = "△" local HYPMARKER = "⟐" local paucal_marker = "*" local paucal_internal_note = "* Used with the numbers 1.5, 2, 3, 4 and higher numbers after 20 ending in 2, 3, and 4." -- text class to check lowercase arg against to see if Latin text embedded in it local latin_text_class = "[a-zščžěáéíóúýàèìòùỳâêîôûŷạẹịọụỵȧėȯẏ]" -- Forward functions local generate_forms_1 local determine_decl local handle_forms_and_overrides local handle_overall_forms_and_overrides local concat_word_forms local make_table local detect_adj_type local detect_stress_pattern local override_stress_pattern local determine_stress_variant local determine_stem_variant local is_reducible local is_dereducible local add_bare_suffix local attach_stressed local do_stress_pattern local canonicalize_override -- version of rsubn() that discards all but the first return value local function rsub(term, foo, bar) local retval = rsubn(term, foo, bar) return retval end -- version of rsubn() that returns a 2nd argument boolean indicating whether -- a substitution was made. local function rsubb(term, foo, bar) local retval, nsubs = rsubn(term, foo, bar) return retval, nsubs > 0 end -- version of rfind() that lowercases its string first, for case-insensitive matching local function rlfind(term, foo) return rfind(ulower(term), foo) end local function track(page) m_debug.track("ru-noun/" .. page) return true end -- version of m_table.insertIfNot() that makes sure 'false' doesn't get inserted by mistake. local function insert_if_not(foo, bar) assert(bar ~= false) m_table.insertIfNot(foo, bar) end -- Fancy version of ine() (if-not-empty). Converts empty string to nil, -- but also strips leading/trailing space and then single or double quotes, -- to allow for embedded spaces. local function ine(arg) if not arg then return nil end arg = rsub(arg, "^%s*(.-)%s*$", "%1") if arg == "" then return nil end local inside_quotes = rmatch(arg, '^"(.*)"$') if inside_quotes then return inside_quotes end inside_quotes = rmatch(arg, "^'(.*)'$") if inside_quotes then return inside_quotes end return arg end -- FIXME: Move to utils -- Iterate over a chain of parameters, FIRST then PREF2, PREF3, ..., -- inserting into LIST (newly created if omitted). Return LIST. local function get_arg_chain(args, first, pref, list) if not list then list = {} end local val = args[first] local i = 2 while val do table.insert(list, val) val = args[pref .. i] i = i + 1 end return list end -- synthesize a frame so that exported functions meant to be called from -- templates can be called from the debug console. local function debug_frame(parargs, args) return {args = args, getParent = function() return {args = parargs} end} end local function rutr_pairs_equal(term1, term2) local ru1, tr1 = term1[1], term1[2] local ru2, tr2 = term2[1], term2[2] local ru1entry, ru1notes = m_table_tools.separate_notes(m_links.remove_links(ru1)) local ru2entry, ru2notes = m_table_tools.separate_notes(m_links.remove_links(ru2)) if ru1entry ~= ru2entry then return false end local tr1entry, tr1notes local tr2entry, tr2notes if tr1 then tr1entry, tr1notes = m_table_tools.separate_notes(tr1) end if tr2 then tr2entry, tr2notes = m_table_tools.separate_notes(tr2) end if tr1entry == tr2entry then return true elseif type(tr1entry) == type(tr2entry) then return false else tr1entry = tr1entry or com.translit_no_links(ru1entry) tr2entry = tr2entry or com.translit_no_links(ru2entry) return tr1entry == tr2entry end end local function contains_rutr_pair(list, pair) for _, item in ipairs(list) do if rutr_pairs_equal(item, pair) then return true end end return false end -- Clone parent's args while also assigning nil to empty strings. local function clone_args(frame) local args = {} for pname, param in pairs(frame:getParent().args) do args[pname] = ine(param) end return args end -- Old-style declensions. local declensions_old = {} -- New-style declensions; computed automatically from the old-style ones, -- for the most part. local declensions = {} -- Internal notes for old-style declensions. local internal_notes_table_old = {} -- Same for new-style declensions. local internal_notes_table = {} -- Category and type information corresponding to declensions: These may -- contain the following fields: 'singular', 'plural', 'decl', 'hard', 'g', -- 'suffix', 'gensg', 'irregpl', 'alt_nom_pl', 'cant_reduce', 'ignore_reduce', -- 'stem_suffix'. -- -- 'singular' is used to construct a category of the form -- "Russian nouns SINGULAR". If omitted, a category is constructed of the -- form "Russian nouns ending in -ENDING", where ENDING is the actual -- nom sg ending shorn of its acute accents; or "Russian nouns ending -- in suffix -ENDING", if 'suffix' is true. The value of SINGULAR can be -- one of the following: a single string, a list of strings, or a function, -- which is passed one argument (the value of ENDING that would be used to -- auto-initialize the category), and should return a single string or list -- of strings. Such a category is only constructed if 'gensg' is true. -- -- 'plural' is analogous but used to construct a category of the form -- "Russian nouns with PLURAL", and if omitted, a category is constructed -- of the form "Russian nouns with plural -ENDING", based on the actual -- nom pl ending shorn of its acute accents. Currently no plural category -- is actually constructed. -- -- In addition, a category may normally constructed from the combination of -- 'singular' and 'plural', appropriately defaulted; e.g. if both are present, -- the combined category will be "Russian nouns SINGULAR with PLURAL" and -- if both are missing, the combined category will be -- "Russian nouns ending in -SGENDING with plural -PLENDING" (or -- "Russian nouns ending in suffix -SGENDING with plural -PLENDING" if -- 'suffix' is true). Note that if either singular or plural or both -- specifies a list, looping will occur over all combinations. Such a -- category is constructed only if 'irregpl' or 'alt_nom_pl' or 'suffix' -- is true or if the declension class is a slash class. -- -- 'decl' is "1st", "2nd", "3rd" or "indeclinable"; 'hard' is "hard", "soft" -- or "none"; 'g' is "m", "f", "n" or "none"; these are all traditional -- declension categories. -- -- If 'suffix' is true, the declension type includes a long suffix -- added to the string that itself undergoes reducibility and such, and so -- reducibility cannot occur in the stem minus the suffix. Categories will -- be created for the suffix. -- -- 'alt_nom_pl' indicates that the declension has an alternative nominative -- plural (corresponding to Zaliznyak's special case 1; compare special case 2 -- for alternative genitive plural). 'irregpl' indicates that the entire -- plural is irregular. -- -- In addition to the above categories, additional more specific categories -- are constructed based on the final letter of the stem, e.g. -- "Russian velar-stem 1st-declension hard nouns". See calls to -- com.get_stem_trailing_letter_type(). 'stem_suffix', if present, is added to -- the end of the stem when get_stem_trailing_letter_type() is called. -- This is the only place that 'stem_suffix' is used. This is for use with -- the '-ья' and '-ье' declension types, so that the trailing letter is -- 'ь' and not whatever precedes it. -- -- 'enable_categories' is a special hack for testing, which disables all -- category insertion if false. Delete this as soon as we've verified the -- working of the category code and created all the necessary categories. local enable_categories = true -- Category/type info corresponding to old-style declensions; see above. local declensions_old_cat = {} -- Category/type info corresponding to new-style declensions. Computed -- automatically from the old-style ones, for the most part. Same format -- as the old-style ones. local declensions_cat = {} -- Table listing aliases of old-style declension classes. local declensions_old_aliases = {} -- Table listing aliases of new-style declension classes; computed -- automatically from the old-style ones. local declensions_aliases = {} local stress_patterns = {} -- Set of patterns with ending-stressed genitive plural. local ending_stressed_gen_pl_patterns = {} -- Set of patterns with ending-stressed prepositional singular. local ending_stressed_pre_sg_patterns = {} -- Set of patterns with ending-stressed dative singular. local ending_stressed_dat_sg_patterns = {} -- Set of patterns with all singular forms ending-stressed. local ending_stressed_sg_patterns = {} -- Set of patterns with all plural forms ending-stressed. local ending_stressed_pl_patterns = {} local declinable_cases_except_accusative = { "nom_sg", "gen_sg", "dat_sg", "ins_sg", "pre_sg", "nom_pl", "gen_pl", "dat_pl", "ins_pl", "pre_pl", } local accusative_cases_unsplit_animacy = { "acc_sg", "acc_pl", } local accusative_cases_split_animacy = { "acc_sg_an", "acc_sg_in", "acc_pl_an", "acc_pl_in", } local lemma_linked_cases = { "nom_sg_linked", "nom_pl_linked", } local overridable_only_cases = { "par", "loc", "voc", "par_pl", "loc_pl", "voc_pl", "count", "pauc", } local overridable_only_cases_set = m_table.listToSet(overridable_only_cases) -- List of all cases that are declined normally. local decl_cases = m_table.append(declinable_cases_except_accusative, accusative_cases_unsplit_animacy) -- List of all cases that can be overridden (includes all cases except the "linked" lemma case variants). Also -- currently the same as the cases returned by export.generate_forms(). local overridable_cases = m_table.append(decl_cases, accusative_cases_split_animacy, overridable_only_cases) -- List of all cases that can be displayed (includes all cases except plain accusatives). local displayable_cases = m_table.append(declinable_cases_except_accusative, lemma_linked_cases, accusative_cases_split_animacy, overridable_only_cases) -- List of all cases, including those that are declined normally (nom/gen/dat/acc/ins/pre sg and pl), plus -- animate/inanimate accusative variants (computed automatically as appropriate from the previous cases), plus -- additional overridable cases (loc/par/voc and plural), plus the "linked" lemma case variants used in ru-noun+ -- headwords (nom_sg_linked, nom_pl_linked, whose values come from nom_sg and nom_pl but may have additional embedded -- links if they were given in the lemma). local all_cases = m_table.append(overridable_cases, lemma_linked_cases) local function english_case_description(case) if case == "par" or case == "loc" or case == "voc" then -- For historical reasons, the singular of these cases doens't include "_sg" in their code. case = case .. "_sg" end local engcase = rsub(case, "^([a-z]*)", { nom="nominative", gen="genitive", dat="dative", acc="accusative", ins="instrumental", pre="prepositional", par="partitive", loc="locative", voc="vocative", count="count form", pauc="paucal form", }) engcase = rsub(engcase, "(_[a-z]*)", { _sg=" singular", _pl=" plural", _an="", _in="", --_an=" animate", _in=" inanimate" }) return engcase end -------------------------------------------------------------------------- -- Tracking and categorization -- -------------------------------------------------------------------------- -- FIXME! Move below the main code -- FIXME!! Consider deleting most of this tracking code once we've enabled -- all the categories. Note that some of the tracking categories aren't -- completely redundant; e.g. we have tracking pages that combine decl and -- stress classes, such as "а/a" or "о-и/d'", which are more or less -- equivalent to stem/gender/stress categories, but we also have the same -- prefixed by "reducible-stem/" for reducible stems. local function tracking_code(stress, orig_decl, decl, args, n, islast) assert(orig_decl) assert(decl) local hint_types = com.get_stem_trailing_letter_type(args.stem) if orig_decl == decl then orig_decl = nil end if args.notes then track("notes") end local function all_pl_irreg() track("irreg") for _, case in ipairs(overridable_cases) do if rfind(case, "_pl") then track("irreg/" .. case) end end end local function track_prefix(prefix) local function dotrack(suf) track(prefix .. suf) end dotrack(stress) dotrack(decl) dotrack(decl .. "/" .. stress) if orig_decl then dotrack(orig_decl) dotrack(orig_decl .. "/" .. stress) end for _, hint_type in ipairs(hint_types) do dotrack(hint_type) dotrack(decl .. "/" .. hint_type) if orig_decl then dotrack(orig_decl .. "/" .. hint_type) end end end track_prefix("") if args.reducible then track("reducible-stem") track_prefix("reducible-stem/") end if rlfind(args.stem, "и́?н$") and (decl == "" or decl == "#") then track("irregular-in") end if args.pltail then track("pltail") end if args.sgtail then track("sgtail") end if args.pltailall then track("pltailall") end if args.sgtailall then track("sgtailall") end if args.pl ~= args.stem then track("irreg-pl-stem") track("irreg") end if args.alt_gen_pl then track("alt-gen-pl") track("irreg") track("irreg/gen_pl") end if args.want_sc1 then track("want-sc1") track("irreg") track("irreg/nom_pl") end if rfind(decl, "-и$") or rfind(decl, "-а$") or rfind(decl, "-я$") then track("irreg") track("irreg/nom_pl") end if rfind(decl, "-ья$") then track("variant-ья") all_pl_irreg() end if rfind(decl, "%(ишк%)$") then track("variant-ишко") end if rfind(decl, "%(ищ%)$") then track("variant-ище") end if args.jo_special then track("jo-special") end if args.manual then track("manual") end if args.explicit_gender then track("explicit-gender") track("explicit-gender/" .. args.explicit_gender) end for _, case in ipairs(overridable_cases) do if args[case .. n] and not args.manual then track("override") track("override/" .. case .. n) track("irreg") track("irreg/" .. case .. n) -- questionable use: track_prefix("irreg/" .. case .. "/") -- questionable use: track_prefix("irreg/" .. case .. n .. "/") end if islast and args[case] and not args.manual then track("override") track("override/" .. case) track("irreg") track("irreg/" .. case) -- questionable use: track_prefix("irreg/" .. case .. "/") end if args[case .. "_tail"] then track("casenum-tail") track("casenum-tail/" .. case) end if args[case .. "_tailall"] then track("casenum-tailall") track("casenum-tailall/" .. case) end end end local gender_to_full = {m="masculine", f="feminine", n="neuter"} local gender_to_short = {m="masc", f="fem", n="neut"} -- Insert the category CAT (a string) into list CATEGORIES. String will -- have "Russian " prepended and ~ substituted for the plural part of speech. local function insert_category(categories, cat, pos, atbeg) if enable_categories then local fullcat = "Russian " .. rsub(cat, "~", pos .. "s") if atbeg then table.insert(categories, 1, fullcat) else table.insert(categories, fullcat) end end end -- Insert categories into ARGS.CATEGORIES corresponding to the specified -- stress and declension classes and to the form of the stem (e.g. velar, -- sibilant, etc.). Also initialize values used to compute the declension -- heading that describes similar information. N is the number of the -- word being processed; ISLAST is true if this is the last word. local function categorize_and_init_heading(stress, decl, args, n, islast) local function cat_to_list(cat) if not cat then return {} elseif type(cat) == "string" then return {cat} else assert(type(cat) == "table") return cat end end -- Insert category CAT into the list of categories in ARGS. -- CAT may be nil, a single string or a list of strings. We call -- insert_category() on each string. The strings will have "Russian " -- prepended and "~" replaced with the plural part of speech. local function insert_cat(cat) for _, c in ipairs(cat_to_list(cat)) do insert_category(args.categories, c, args.pos) end end -- "Resolve" the category spec CATSPEC into the sort of category spec -- accepted by insert_cat(), i.e. nil, a single string or a list of -- strings. CATSPEC may be any of these or a function, which takes one -- argument (SUFFIX) and returns another CATSPEC. local function resolve_cat(catspec, suffix) if type(catspec) == "function" then return resolve_cat(catspec(suffix), suffix) else return catspec end end -- Check whether an override for nom_sg or nom_pl still contains the -- normal suffix (which should already have accents removed) in at least -- one of its entries. If no override then of course we return true. local function override_matches_suffix(args, case, n, suffix) assert(suffix == com.remove_accents(suffix)) -- NOTE: It might not be completely correct to pass in args.forms -- here when n == ""; this is different behavior from -- handle_overall_forms_and_overrides(). But args.forms is only used -- to retrieve the dat_sg for handling loc/par, and we're never -- called with loc or par as the value of CASE, so it doesn't matter. -- We add an assert to make sure of this. assert(case ~= "loc" and case ~= "par") local override = canonicalize_override(args, case, args.forms, n) if not override then return true end for _, x in ipairs(override) do local ru, tr = x[1], x[2] local entry, notes = m_table_tools.separate_notes(ru) entry = com.remove_accents(m_links.remove_links(entry)) if rlfind(entry, suffix .. "$") then return true end end return false end if args.manual then return end local h = args.heading_info assert(decl) local decl_cats = args.old and declensions_old_cat or declensions_cat local sgdecl, pldecl local is_slash_decl = rfind(decl, "/") if is_slash_decl then local indiv_decls = rsplit(decl, "/") sgdecl, pldecl = indiv_decls[1], indiv_decls[2] else sgdecl, pldecl = decl, decl end local sgdc = decl_cats[sgdecl] local pldc = decl_cats[pldecl] assert(sgdc) assert(pldc) local sghint_types = com.get_stem_trailing_letter_type( args.stem .. (sgdc.stem_suffix or "")) -- insert English version of Zaliznyak stem type if sgdc.decl == "indeclinable" then insert_cat("indeclinable ~") insert_if_not(h.stemetc, "indecl") else local stem_type = sgdc.decl == "3rd" and "3rd-declension" or m_table.contains(sghint_types, "velar") and "velar-stem" or m_table.contains(sghint_types, "sibilant") and "sibilant-stem" or m_table.contains(sghint_types, "c") and "ц-stem" or m_table.contains(sghint_types, "i") and "i-stem" or m_table.contains(sghint_types, "vowel") and "vowel-stem" or m_table.contains(sghint_types, "soft-cons") and "vowel-stem" or m_table.contains(sghint_types, "palatal") and "vowel-stem" or sgdc.hard == "soft" and "soft-stem" or "hard-stem" local short_stem_type = stem_type == "3rd-declension" and "3rd-decl" or stem_type if sgdc.adj then -- Don't include gender for pluralia tantum because it's mostly -- indeterminate (certainly when specified using a plural lemma, -- which will be usually; technically it's partly or completely -- determinate in certain old-style adjectives that distinguish -- masculine from feminine/neuter, but this is too rare a case -- to worry about) local gendertext = args.thisn == "p" and "plural-only" or gender_to_full[sgdc.g] insert_if_not(h.adjectival, "yes") if args.thisn ~= "p" then insert_if_not(h.gender, gender_to_short[sgdc.g]) end if sgdc.possadj then insert_cat(sgdc.decl .. " possessive " .. gendertext .. " accent-" .. (stress:gsub("''", "ʺ"):gsub("'", "ʹ")) .. " adjectival ~") insert_if_not(h.stemetc, sgdc.decl .. " poss") insert_if_not(h.stress, stress) elseif stem_type == "soft-stem" or stem_type == "vowel-stem" then insert_cat(stem_type .. " " .. gendertext .. " adjectival ~") insert_if_not(h.stemetc, short_stem_type) else insert_cat(stem_type .. " " .. gendertext .. " accent-" .. (stress:gsub("''", "ʺ"):gsub("'", "ʹ")) .. " adjectival ~") insert_if_not(h.stemetc, short_stem_type) insert_if_not(h.stress, stress) end else -- NOTE: There are 8 Zaliznyak-style stem types and 3 genders, but -- we don't create a category for masculine-form 3rd-declension -- nouns (there is such a noun, путь, but it mostly behaves -- like a feminine noun), so there are 23. insert_cat(stem_type .. " " .. gender_to_full[sgdc.g] .. "-form ~") -- NOTE: Here we are creating categories for the combination of -- stem, gender and accent. There are 10 accent patterns and 23 -- combinations of stem and gender, which potentially makes for -- 10*23 = 230 such categories, which is a lot. Not all such -- categories should actually exist; there were maybe 75 former -- declension templates, each of which was essentially categorized -- by the same three variables, but some of which dealt with -- ancillary issues like irregular plurals; this amounts to 67 -- actual stem/gender/accent categories, although there are more -- of them in Zaliznyak (FIXME, how many? See generate_cats.py). insert_cat(stem_type .. " " .. gender_to_full[sgdc.g] .. "-form accent-" .. (stress:gsub("''", "ʺ"):gsub("'", "ʹ")) .. " ~") insert_if_not(h.adjectival, "no") insert_if_not(h.gender, gender_to_short[sgdc.g]) insert_if_not(h.stemetc, short_stem_type) insert_if_not(h.stress, stress) end insert_cat("~ with accent pattern " .. (stress:gsub("''", "ʺ"):gsub("'", "ʹ"))) end local sgsuffix = args.suffixes.nom_sg if sgsuffix then assert(#sgsuffix == 1) -- If this ever fails, then implement a loop sgsuffix = com.remove_accents(sgsuffix[1]) -- If we are plural only or if nom_sg is overridden and has -- an unusual suffix, then don't create category for sg suffix if args.thisn == "p" or not override_matches_suffix(args, "nom_sg", n, sgsuffix) or islast and not override_matches_suffix(args, "nom_sg", "", sgsuffix) then sgsuffix = nil end end local plsuffix = args.suffixes.nom_pl if plsuffix then assert(#plsuffix == 1) -- If this ever fails, then implement a loop plsuffix = com.remove_accents(plsuffix[1]) -- If we are a singulare tantum or if nom_pl is overridden and has -- an unusual suffix, then don't create category for pl suffix if args.thisn == "s" or not override_matches_suffix(args, "nom_pl", n, plsuffix) or islast and not override_matches_suffix(args, "nom_pl", "", plsuffix) then plsuffix = nil end end sgsuffix = sgsuffix and rsub(sgsuffix, "ъ$", "") plsuffix = plsuffix and rsub(plsuffix, "ъ$", "") local sgcat = sgsuffix and (resolve_cat(sgdc.singular, sgsuffix) or "ending in " .. (sgsuffix == "" and "a consonant" or (sgdc.suffix and "suffix " or "") .. "-" .. sgsuffix)) local plcat = plsuffix and (resolve_cat(pldc.plural, plsuffix) or "plural -" .. plsuffix) if sgcat and sgdc.gensg then for _, cat in ipairs(cat_to_list(sgcat)) do insert_cat("~ " .. cat) end end if sgcat and plcat and (sgdc.suffix or sgdc.alt_nom_pl or sgdc.irregpl or is_slash_decl and plsuffix == "-ья") then for _, scat in ipairs(cat_to_list(sgcat)) do for _, pcat in ipairs(cat_to_list(plcat)) do insert_cat("~ " .. scat .. " with " .. pcat) end end end if args.pl ~= args.stem then insert_cat("~ with irregular plural stem") end if args.reducible and not sgdc.ignore_reduce then insert_cat("~ with reducible stem") if args.soft_n then insert_cat("~ with soft final н in reduced stem") end insert_if_not(h.reducible, "yes") else insert_if_not(h.reducible, "no") end if args.alt_gen_pl then insert_cat("~ with alternative genitive plural") end if sgdc.adj then insert_cat("adjectival ~") end end local function compute_heading(args) local headings = {} local h = args.heading_info table.insert(headings, args.a == "a" and "anim" or args.a == "i" and "inan" or "bian") table.insert(headings, args.nonumber and "uncountable" or args.n == "s" and "sg-only" or args.n == "p" and "pl-only" or nil) if #h.gender > 0 then table.insert(headings, table.concat(h.gender, "/") .. "-form") end if #h.stemetc > 0 then table.insert(headings, table.concat(h.stemetc, "/")) end if #h.stress > 0 then local stresses = {} for _, stress in ipairs(h.stress) do table.insert(stresses, (stress:gsub("''", "ʺ"):gsub("'", "ʹ"))) end table.insert(headings, "accent-" .. table.concat(stresses, "/")) end local function handle_bool(boolvals, text, into) into = into or headings if m_table.contains(boolvals, "yes") and m_table.contains(boolvals, "no") then table.insert(into, "[" .. text .. "]") elseif m_table.contains(boolvals, "yes") then table.insert(into, text) end end handle_bool(h.adjectival, "adj") handle_bool(h.reducible, "reduc") return headings end local function compute_overall_heading_categories_and_genders(args) local hinfo = args.per_word_heading_info local index = 0 -- First try for non-adjectival, non-indeclinable for i=1,#hinfo do if not m_table.contains(hinfo[i].stemetc, "indecl") and not m_table.contains(hinfo[i].adjectival, "yes") then index = i break end end if index == 0 then -- Then just non-indeclinable for i=1,#hinfo do if not m_table.contains(hinfo[i].stemetc, "indecl") then index = i break end end end -- Finally, do anything if index == 0 then index = 1 end -- Compute final heading local headings = args.per_word_headings[index] local categories = args.per_word_categories[index] if args.any_irreg then table.insert(headings, "irreg") insert_category(categories, "irregular ~", args.pos) end for _, case in ipairs(overridable_cases) do local is_pl = rfind(case, "_pl") if args.n == "s" and is_pl or args.n == "p" and not is_pl then -- Don't create singular categories when plural-only or vice-versa elseif overridable_only_cases_set[case] then if args.any_overridden[case] then insert_category(categories, "~ with " .. english_case_description(case), args.pos) end elseif args.any_irreg_case[case] then insert_category(categories, "~ with irregular " .. english_case_description(case), args.pos) end end local heading = args.manual and "" or "(<span style=\"font-size: smaller;\">[[Appendix:Russian nouns#Declension tables|" .. table.concat(headings, " ") .. "]]</span>)" args.heading = heading args.categories = categories args.genders = args.per_word_genders[index] end -------------------------------------------------------------------------- -- Main code -- -------------------------------------------------------------------------- -- Used by do_generate_forms(). local function arg1_is_stress(arg1) if not arg1 then return false end for _, arg in ipairs(rsplit(arg1, ",")) do if not rfind(arg, "^[a-f]'?'?$") then return false end end return true end -- Used by do_generate_forms(), handling a word joiner argument -- of the form 'join:JOINER'. local function extract_word_joiner(spec) word_joiner = rmatch(spec, "^join:(.*)$") assert(word_joiner) return com.split_russian_tr(word_joiner, "dopair") end local function determine_headword_gender(args, sgdc, gender) -- If gender unspecified, use normal gender of declension, except when -- adjectival nouns that are pluralia tantum, where the gender is -- mostly indeterminate (FIXME, not completely with old-style declensions -- but we don't handle that currently). if not gender then if sgdc.adj and args.thisn == "p" then gender = nil else gender = sgdc.g end end -- Determine headword genders gender = gender and gender ~= "none" and gender .. "-" or "" local plsuffix = args.n == "p" and "-p" or "" local hgens if args.a == "a" then hgens = {gender .. "an" .. plsuffix} elseif args.a == "i" then hgens = {gender .. "in" .. plsuffix} elseif args.a == "ai" then hgens = {gender .. "an" .. plsuffix, gender .. "in" .. plsuffix} else hgens = {gender .. "in" .. plsuffix, gender .. "an" .. plsuffix} end -- Insert into list of genders for _, hgen in ipairs(hgens) do insert_if_not(args.genders, hgen) end end function export.do_generate_forms(args, old) old = old or args.old args.old = old args.pos = args.pos or "noun" -- This is a list with each element corresponding to a word and -- consisting of a two-element list, ARG_SETS and JOINER, where ARG_SETS -- is a list of ARG_SET objects, one per alternative stem, and JOINER -- is a string indicating how to join the word to the next one. local per_word_info = {} -- Gather arguments into a list of ARG_SET objects, containing (potentially) -- elements 1, 2, 3, 4, corresponding to accent pattern, stem, declension -- type, pl stem and coming from consecutive numbered parameters. Sets of -- declension parameters are separated by the word "or". local arg_sets = {} -- Find maximum-numbered arg, allowing for holes local max_arg = 0 for k, v in pairs(args) do if type(k) == "number" and k > max_arg then max_arg = k end end -- Now gather the arguments. local offset = 0 local arg_set = {} for i=1,(max_arg + 1) do local end_arg_set = false local end_word = false -- FIXME, is this correct? local word_joiner if i == max_arg + 1 then end_arg_set = true end_word = true word_joiner = {""} elseif args[i] == "_" then end_arg_set = true end_word = true word_joiner = {" "} elseif args[i] == "-" then end_arg_set = true end_word = true word_joiner = {"-"} elseif args[i] and rfind(args[i], "^join:") then end_arg_set = true end_word = true word_joiner = extract_word_joiner(args[i]) elseif args[i] == "or" then end_arg_set = true end if end_arg_set then table.insert(arg_sets, arg_set) arg_set = {} offset = i if end_word then table.insert(per_word_info, {arg_sets, word_joiner}) arg_sets = {} end else -- If the first argument isn't stress, that means all arguments -- have been shifted to the left one. We want to shift them -- back to the right one, so we change the offset so that we -- get the same effect of skipping a slot in the arg set. if i - offset == 1 and not arg1_is_stress(args[i]) then offset = offset - 1 end if i - offset > 4 then error("Too many arguments for argument set: arg " .. i .. " = " .. (args[i] or "(blank)")) end arg_set[i - offset] = args[i] end end return generate_forms_1(args, per_word_info) end function export.do_generate_forms_multi(args, old) old = old or args.old args.old = old args.pos = args.pos or "noun" -- This is a list with each element corresponding to a word and -- consisting of a two-element list, ARG_SET and JOINER, where ARG_SET -- is a list of ARG_SET objects, one per alternative stem, and JOINER -- is a string indicating how to join the word to the next one. local per_word_info = {} -- Find maximum-numbered arg, allowing for holes (FIXME: Is this needed -- here? Will there be holes?) local max_arg = 0 for k, v in pairs(args) do if type(k) == "number" and k > max_arg then max_arg = k end end -- Gather arguments into a list of ARG_SET objects, containing -- (potentially) elements 1, 2, 3, corresponding to accent pattern, -- lemma+declension spec, pl stem, exactly as with do_generate_forms() -- and {{ru-noun-table}} except that the values come from a single argument -- of the form ACCENTPATTERN:LEMMADECL:PL where all but LEMMADECL may (and -- probably will be) omitted and LEMMADECL may be of the following forms: -- LEMMA (for a noun with empty decl spec), -- LEMMADECL (for a noun with non-empty decl spec beginning with a -- *, left paren or semicolon), -- LEMMA^DECL (for a noun with non-empty decl spec), -- LEMMA$ (for an indeclinable word) -- LEMMA+ (for an adjective with auto-detected decl class) -- LEMMA+DECL (for an adjective with explicit decl class) -- Sets of parameters for the same word are separated by the word "or". local arg_sets = {} local continue_arg_sets = true for i=1,(max_arg + 1) do local end_word = false local word_joiner local process_arg = false if i == max_arg + 1 then end_word = true word_joiner = {""} elseif args[i] == "-" then end_word = true word_joiner = {"-"} continue_arg_sets = true elseif rfind(args[i], "^join:") then end_word = true word_joiner = extract_word_joiner(args[i]) continue_arg_sets = true elseif args[i] == "or" then continue_arg_sets = true else if continue_arg_sets then continue_arg_sets = false else end_word = true word_joiner = {" "} end process_arg = true end if end_word then table.insert(per_word_info, {arg_sets, word_joiner}) arg_sets = {} end if process_arg then local vals = rsplit(args[i], ":") if #vals > 3 then error("Can't specify more than 3 colon-separated params of param set: " .. args[i]) end local arg_set = {} if arg1_is_stress(vals[1]) then arg_set[1] = vals[1] arg_set[2] = vals[2] arg_set[4] = vals[3] else arg_set[2] = vals[1] arg_set[4] = vals[2] end -- recognize indeclinable local indecl_stem = rmatch(arg_set[2], "^(.-)%$$") if indecl_stem then arg_set[2] = indecl_stem arg_set[3] = "$" else -- recognize adjective local adj_stem, adj_type = rmatch(arg_set[2], "^(.*)(%+.*)$") if adj_stem then arg_set[2] = adj_stem arg_set[3] = adj_type else -- recognize noun with ^ local noun_stem, noun_type = rmatch(arg_set[2], "^(.*)%^(.*)$") if noun_stem then arg_set[2] = noun_stem arg_set[3] = noun_type else -- recognize noun without ^ but with decl spec noun_stem, noun_type = rmatch(arg_set[2], "^(..-)([;*(].*)$") if noun_stem then arg_set[2] = noun_stem arg_set[3] = noun_type else -- noun without ^ or decl spec; nothing to do end end end end table.insert(arg_sets, arg_set) end end return generate_forms_1(args, per_word_info) end -- Implementation of do_generate_forms() and do_generate_forms_multi(), -- which have equivalent functionality but different calling sequence. -- Implementation of do_generate_forms() and do_generate_forms_multi(), -- which have equivalent functionality but different calling sequence, -- as well as show_z() for template {{ru-decl-noun-z}}, which has a -- subset of the functionality of the other two. generate_forms_1 = function(args, per_word_info) local orig_args if test_new_ru_noun_module then orig_args = mw.clone(args) end local pagename = args.pagename or mw.loadData("Modül:başlık başı/veri").pagename local old = args.old local function verify_animacy_value(val) if not val then return nil end if val == "a" or val == "an" or val == "anim" then return "a" elseif val == "i" or val == "in" or val == "inan" then return "i" elseif val == "b" or val == "bi" or val == "both" or val == "ai" then return "ai" elseif val == "ia" then return "ia" end error("Animacy value " .. val .. " should be empty or a/an/anim (animate), i/in/inan (inanimate), b/bi/both/ai (bianimate, listing animate first), or ia (bianimate, listing inanimate first)") return nil end local function verify_number_value(val, allow_none) if not val then return nil end local short = usub(val, 1, 1) if short == "s" or short == "p" or short == "b" or (allow_none and short == "n" or false) then return short end if allow_none then error("Number value " .. val .. " should be empty or start with 's' (singular), 'p' (plural), 'b' (both) or 'n' (none)") else error("Number value " .. val .. " should be empty or start with 's' (singular), 'p' (plural), or 'b' (both)") end return nil end -- Verify and canonicalize animacy, number, prefix, suffix assert(#per_word_info >= 1) for i=1,#per_word_info do args["a" .. i] = verify_animacy_value(args["a" .. i]) args["n" .. i] = verify_number_value(args["n" .. i]) args["prefix" .. i] = com.split_russian_tr(args["prefix" .. i] or "", "dopair") args["suffix" .. i] = com.split_russian_tr(args["suffix" .. i] or "", "dopair") end args.a = verify_animacy_value(args.a) or "i" -- args.ndef, if set, is the default value for args.n; if unset, it defaults -- to "both". It is set to "singular" in ru-proper noun+. We store the value -- of args.n in args.orign before defaulting to args.ndef because we may -- change it to plural-only later on if it was unspecified (this happens if -- an individual word's lemma is plural), and to determine whether it was -- unspecified, we need the original value before defaulting. args.n = verify_number_value(args.n, "allow none") -- treat n=none like n=sg but set a flag so "singular" isn't displayed if args.n == "n" then args.n = "s" args.nonumber = true end args.ndef = verify_number_value(args.ndef) args.orign = args.n args.n = args.n or args.ndef args.prefix = com.split_russian_tr(args.prefix or "", "dopair") args.suffix = com.split_russian_tr(args.suffix or "", "dopair") -- Attach overall prefix to first per-word prefix, similarly for suffix args.prefix1 = com.concat_paired_russian_tr(args.prefix, args.prefix1) args["suffix" .. #per_word_info] = com.concat_paired_russian_tr( args["suffix" .. #per_word_info], args.suffix) -- Initialize non-word-specific arguments. -- -- The following is a list of WORD_INFO items, one per word, each of -- which is a two element list of WORD_FORMS (a table listing the forms for -- each case) and JOINER (a string, indicating how to join the word with -- the next one). args.per_word_info = {} -- List of HEADING_INFO items, one per word, containing the raw material -- used to generate the header. Initialized from 'args.heading_info', -- which is initialized in categorize_and_init_heading(). args.per_word_heading_info = {} -- List of CATEGORIES items, one per word, containing the categories -- used to initialize the page categories. Initialized from -- 'args.categories', which is initialized in categorize_and_init_heading(). args.per_word_categories = {} -- List of HEADINGS items, one per word, containing the actual words that -- go into the header if we were to use that word to construct the header. -- Comes from compute_heading(). We use this to generate the actual header -- string, which goes into 'args.heading' at the end (done in -- compute_overall_heading_categories_and_genders()). args.per_word_headings = {} -- List of GENDERS items, one per word, containing the headword genders -- for each word (where "headword gender" is in the format used in -- headwords and actually includes animacy and number as well, e.g. -- 'm-in' or 'f-an-p'). We end up selecting one such item and putting -- it into 'args.genders' at the end -- (in compute_overall_heading_categories_and_genders()). args.per_word_genders = {} args.any_overridden = {} args.any_non_nil = {} args.any_irreg = false args.any_irreg_case = {} local function insert_cat(cat) insert_category(args.categories, cat, args.pos) end args.internal_notes = {} local decl_sufs = old and declensions_old or declensions local decl_cats = old and declensions_old_cat or declensions_cat local intable = old and internal_notes_table_old or internal_notes_table -- Default lemma defaults to previous lemma, first one to page name. -- FIXME: With multiple words, we should probably split the page name -- on spaces and default each word in turn local default_lemma local all_stresses_seen -- Made into a function to avoid having to indent a lot of code. -- Process a single arg set of a single word. This inserts the forms -- for the word into args.forms and sets categories and tracking pages. local function do_arg_set(arg_set, n, islast) local stress_arg = arg_set[1] local decl = arg_set[3] or "" local pl, pltr if arg_set[4] then pl, pltr = com.split_russian_tr(arg_set[4]) end -- Extract special markers from declension class. if decl == "manual" then decl = "$" args.manual = true if #per_word_info > 1 or #per_word_info[1][1] > 1 then error("Can't specify multiple words or argument sets when manual") end if pl then error("Can't specify optional stem parameters when manual") end end decl, args.jo_special = rsubb(decl, "([^/%a])ё$", "%1") if not args.jo_special then decl, args.jo_special = rsubb(decl, "([^/%a])ё([^/%a])", "%1%2") end decl, args.want_sc1 = rsubb(decl, "%(1%)", "") decl, args.alt_gen_pl = rsubb(decl, "%(2%)", "") decl, args.reducible = rsubb(decl, "%*", "") decl, args.soft_n = rsubb(decl, "%(нь%)", "") decl = rsub(decl, ";", "") -- Get the lemma. local lemma = args.manual and "-" or arg_set[2] or default_lemma if not lemma then error("Lemma in first argument set must be specified") end default_lemma = lemma local lemmatr lemma, lemmatr = com.split_russian_tr(lemma) -- If we're conjugating a suffix, insert a pseudoconsonant at the beginning -- of all forms, so they get conjugated as if ending in a consonant. -- We remove the pseudoconsonant later. local is_suffix = lemma ~= "-" and rfind(lemma, "^%-") args.any_suffix = args.any_suffix or is_suffix local asif_prefix = args["asif_prefix" .. n] or args.asif_prefix or is_suffix and PSEUDOCONS if asif_prefix then lemma = rsub(lemma, "^%-", "-" .. asif_prefix) if lemmatr then lemmatr = rsub(lemmatr, "^%-", "-" .. com.translit(asif_prefix)) end end args.thisa = args["a" .. n] or args.a args.thisn = args["n" .. n] or args.n -- Check for explicit allow-unaccented indication. local allow_unaccented lemma, allow_unaccented = rsubb(lemma, "^%*", "") args.allow_unaccented = args.allow_unaccented or allow_unaccented if args.allow_unaccented then track("allow-unaccented") end args.orig_lemma = lemma lemma = m_links.remove_links(lemma) args.lemma_no_links = lemma args.lemmatr = lemmatr if args.lemma then -- Explicit lemma given. args.explicit_lemma, args.explicit_lemmatr = com.split_russian_tr(args.lemma) end -- Treat suffixes without an accent, and suffixes with an accent on the -- initial hyphen, as if they were preceded with a *, which overrides -- all the logic that normally (a) normalizes the accent, and (b) -- complains about multisyllabic words without an accent. Don't do this -- if lemma is just -, which is used specially in manual declension -- tables (e.g. сто, три). if lemma ~= "-" and (rfind(lemma, "^%-" .. AC) or (com.is_unstressed(lemma) and rfind(lemma, "^%-"))) then args.allow_unaccented = true end -- Convert lemma and decl arg into stem and canonicalized decl. -- This will autodetect the declension from the lemma if an explicit -- decl isn't given. local stem, tr, gender, was_accented, was_plural, was_autodetected if rfind(decl, "^%+") then stem, tr, decl, gender, was_accented, was_plural, was_autodetected = detect_adj_type(lemma, lemmatr, decl, old) else stem, tr, decl, gender, was_accented, was_plural, was_autodetected = determine_decl(lemma, lemmatr, decl, args) end if was_plural then args.n = args.orign or "p" args.thisn = args["n" .. n] or args.n elseif decl ~= "$" then args.thisn = args.thisn or "b" end args.explicit_gender = gender -- If allow-unaccented not given, maybe check for missing accents. if not args.allow_unaccented and not stress_arg and was_autodetected and com.needs_accents(lemma) then -- If user gave the full word and expects us to determine the -- declension and stress, the word should have an accent on the -- stem or ending. We have a separate check farther below for -- an accent on a multisyllabic stem, after stripping off any -- ending; but this way we get an error if the user e.g. writes -- "гора" without an accent rather than assuming it's stem -- stressed, as would otherwise happen. error("Lemma must have an accent in it: " .. lemma) end -- If stress not given, auto-determine; else validate/canonicalize -- stress arg, override in certain cases and convert to list. if not stress_arg then stress_arg = {detect_stress_pattern(stem, decl, decl_cats, args.reducible, was_plural, was_accented)} else stress_arg = rsplit(stress_arg, ",") for i=1,#stress_arg do local stress = stress_arg[i] stress = override_stress_pattern(decl, stress) if not stress_patterns[stress] then error("Unrecognized accent pattern " .. stress) end stress_arg[i] = stress end end -- parse slash decl to list local sub_decls if rfind(decl, "/") then track("mixed-decl") insert_cat("~ with mixed declension") local indiv_decls = rsplit(decl, "/") -- Should have been caught in canonicalize_decl() assert(#indiv_decls == 2) sub_decls = {{indiv_decls[1], "sg"}, {indiv_decls[2], "pl"}} else sub_decls = {{decl}} end -- Get singular declension and corresponding category. local sgdecl = sub_decls[1][1] local sgdc = decl_cats[sgdecl] assert(sgdc) -- Compute headword gender(s). We base it off the singular declension -- if we have a slash declension -- it's the best we can do. determine_headword_gender(args, sgdc, gender) local original_stem, original_tr = stem, tr local original_pl, original_pltr = pl, pltr -- Loop over accent patterns in case more than one given. for _, stress in ipairs(stress_arg) do args.suffixes = {} stem, tr = original_stem, original_tr local bare, baretr local stem_for_bare, tr_for_bare pl, pltr = original_pl, original_pltr insert_if_not(all_stresses_seen, stress) local stem_was_unstressed = com.is_unstressed(stem) -- If special case ;ё was given and stem is unstressed, -- add ё to the stem now; but don't let this interfere with -- restressing, to handle cases like железа́ with gen pl желёз -- but nom pl же́лезы. if stem_was_unstressed and args.jo_special then -- Beware, Cyrillic еЕ in first rsub, Latin eE in second local new_stem = rsub(stem, "([еЕ])([^еЕ]*)$", function(e, rest) return (e == "Е" and "Ё" or "ё") .. rest end ) if stem == new_stem then error("No е in stem to replace with ё") end stem = new_stem if tr then local subbed -- e after j -> o, e not after j -> jo; don't just convert e -> jo -- and then map jjo -> jo because we want to preserve double j tr, subbed = rsubb(tr, "([jJ])([eE])([^eE]*)$", function(j, e, rest) return j .. (e == "E" and "O" or "o") .. AC .. rest end ) if not subbed then tr = rsub(tr, "([eE])([^eE]*)$", function(e, rest) return (e == "E" and "Jo" or "jo") .. AC .. rest end ) end tr = com.j_correction(tr) end -- This is used to handle железа́ with gen pl желёз and nom pl -- же́лезы. We have two stressed stems, one for the gen pl and -- one for the remaining pl cases, and the variable 'stem' can -- handle only one, so we put the second (gen pl) stem in -- stem_for_bare, which goes into the value of 'bare' (used -- only for gen pl here). stem_for_bare, tr_for_bare = stem, tr end -- Maybe add stress to the stem, depending on whether the -- stem was unstressed and the stress pattern. Stem pattern f -- and variants call for initial stress (голова́ -> го́ловы); -- stem pattern d and variants call for stem-final stress -- (сапожо́к -> сапо́жки). Stem patterns b and b' apparently -- call for stem-final stress as well but it's unlikely to -- make much of a difference (pattern b' only occurs in 3rd-decl -- feminines, which should already have stress in the stem, -- and the only place pattern b gets stem stress is in bare -- forms, i.e. nom sg and/or gen pl depending on the decl type, -- and nom sg stress should already be in the lemma while -- gen pl stress is handled by a different ending-stressing -- mechanism in attach_unstressed(); however, the user is -- free to leave a masc or 3rd-decl fem lemma completely -- unstressed with pattern b, and then the stem-final stress -- *will* make a difference). local function restress_stem(stem, tr, stress, stem_unstressed) -- If the user has indicated they purposely are leaving the -- word unstressed by putting a * at the beginning of the main -- stem, leave it unstressed. This might indicate lack of -- knowledge of the stress or a truly unaccented word -- (e.g. an unaccented suffix). if args.allow_unaccented then return stem, tr end if tr and com.is_unstressed(stem) ~= com.is_unstressed(tr) then error("Stem " .. stem .. " and translit " .. tr .. " must have same accent pattern") end -- it's safe to accent monosyllabic stems if com.is_monosyllabic(stem) then stem, tr = com.make_ending_stressed(stem, tr) -- For those patterns that are ending-stressed in the singular -- nominative (and hence are likely to be expressed without an -- accent on the stem) it's safe to put a particular accent on -- the stem depending on the stress type. Otherwise, give an -- error if no accent. elseif stem_unstressed then if rfind(stress, "^f") then stem, tr = com.make_beginning_stressed(stem, tr) elseif (rfind(stress, "^[bd]") or args.thisn == "p" and ending_stressed_pl_patterns[stress]) then stem, tr = com.make_ending_stressed(stem, tr) elseif com.needs_accents(stem) then error("Stem " .. stem .. " requires an accent") end end return stem, tr end stem, tr = restress_stem(stem, tr, stress, stem_was_unstressed) -- Leave pl unaccented if user wants this; see restress_stem(). if pl and not args.allow_unaccented then if pltr and com.is_unstressed(pl) ~= com.is_unstressed(pltr) then error("Plural stem " .. pl .. " and translit " .. pltr .. " must have same accent pattern") end if com.is_monosyllabic(pl) then pl, pltr = com.make_ending_stressed(pl, pltr) end -- I think this is safe. if com.needs_accents(pl) then if ending_stressed_pl_patterns[stress] then pl, pltr = com.make_ending_stressed(pl, pltr) elseif not args.allow_unaccented then error("Plural stem " .. pl .. " requires an accent") end end end local resolved_bare, resolved_baretr -- Handle (de)reducibles -- FIXME! We are dereducing based on the singular declension. -- In a slash declension things can get weird and we don't -- handle that. We are also computing the bare value from the -- singular stem, and again things can get weird with a plural -- stem. Note that we don't compute a bare value unless we have -- to (either (de)reducible or stress pattern f/f'/f'' combined -- with ё special case); the remaining times we generate the bare -- value directly from the plural stem. if args.reducible and not sgdc.ignore_reduce then -- Zaliznyak treats all nouns in -ье and -ья as being -- reducible. We handle this automatically and don't require -- the user to specify this, but ignore it if so for -- compatibility. if is_reducible(sgdc) then -- If we derived the stem from a nom pl form, then -- it's already reduced, and we need to dereduce it to -- get a bare form; otherwise the stem comes from the -- nom sg and we need to reduce it to get the real stem. if was_plural then resolved_bare, resolved_baretr = export.dereduce_nom_sg_stem(stem, tr, sgdc, stress, old, "error") else resolved_bare, resolved_baretr = stem, tr stem, tr = export.reduce_nom_sg_stem(stem, tr, sgdecl, args.soft_n, "error") -- Stem will be unstressed if stress was on elided -- vowel; restress stem the way we did above. (This is -- needed in at least one word, сапожо́к 3*d(2), with -- plural stem probably сапо́жк- and gen pl probably -- сапо́жек.) stem, tr = restress_stem(stem, tr, stress, com.is_unstressed(stem)) if stress ~= "a" and stress ~= "b" and args.alt_gen_pl and not pl then -- Nouns like рожо́к, глазо́к of type 3*d(2) have -- gen pl's ро́жек, гла́зок; to handle this, -- dereduce the reduced stem and store in a -- special place. args.gen_pl_bare, args.gen_pl_baretr = export.dereduce_nom_sg_stem(stem, tr, sgdc, stress, old, "error") end end elseif is_dereducible(sgdc) then resolved_bare, resolved_baretr = export.dereduce_nom_sg_stem(stem, tr, sgdc, stress, old, "error") else error("Declension class " .. sgdecl .. " not (de)reducible") end elseif stem_for_bare and stem ~= stem_for_bare then resolved_bare, resolved_baretr = add_bare_suffix(stem_for_bare, tr_for_bare, old, sgdc, false) end -- Leave unaccented if user wants this; see restress_stem(). -- FIXME, we no longer allow the user to specify the bare value -- so it's unclear if this is needed any more. if resolved_bare and not args.allow_unaccented then if resolved_baretr and com.is_unstressed(resolved_bare) ~= com.is_unstressed(resolved_baretr) then error("Resolved bare stem " .. resolved_bare .. " and translit " .. resolved_baretr .. " must have same accent pattern") end if com.is_monosyllabic(resolved_bare) then resolved_bare, resolved_baretr = com.make_ending_stressed(resolved_bare, resolved_baretr) else if com.needs_accents(resolved_bare) then error("Resolved bare stem " .. resolved_bare .. " requires an accent") end end end args.stem, args.stemtr = stem, tr args.bare, args.baretr = resolved_bare, resolved_baretr args.ustem, args.ustemtr = com.make_unstressed_once(stem, tr) if pl then args.pl, args.pltr = pl, pltr else args.pl, args.pltr = stem, tr end args.upl, args.upltr = com.make_unstressed_once(args.pl, args.pltr) -- Special hack for любо́вь and other reducible 3rd-fem nouns, -- which have the full stem in the ins sg args.ins_sg_stem = sgdecl == "ь-f" and args.reducible and resolved_bare args.ins_sg_tr = sgdecl == "ь-f" and args.reducible and resolved_baretr -- Loop over declension classes (we may have two of them, one for -- singular and one for plural, in the case of a mixed declension -- class of the form SGDECL/PLDECL). for _,decl_spec in ipairs(sub_decls) do local orig_decl = decl_spec[1] local number = decl_spec[2] local real_decl = determine_stress_variant(orig_decl, stress) real_decl = determine_stem_variant(real_decl, number == "pl" and args.pl or args.stem) -- sanity checking; errors should have been caught in -- canonicalize_decl() assert(decl_cats[real_decl], "real_decl " .. real_decl .. " nonexistent") assert(decl_sufs[real_decl], "real_decl " .. real_decl .. " nonexistent") tracking_code(stress, orig_decl, real_decl, args, n, islast) do_stress_pattern(stress, args, real_decl, number, n, islast) -- handle internal notes local internal_note = intable[real_decl] if internal_note then insert_if_not(args.internal_notes, internal_note) end end categorize_and_init_heading(stress, decl, args, n, islast) end end local n = 0 for _, word_info in ipairs(per_word_info) do n = n + 1 local islast = n == #per_word_info local arg_sets, joiner = word_info[1], word_info[2] args.forms = {} args.heading_info = {animacy={}, number={}, gender={}, stress={}, stemetc={}, adjectival={}, reducible={}} args.categories = {} args.genders = {} args.this_any_non_nil = {} args.any_suffix = false if #arg_sets > 1 then track("multiple-arg-sets") insert_cat("~ with multiple argument sets") track("multiple-declensions") insert_cat("~ with multiple declensions") end default_lemma = pagename all_stresses_seen = {} -- Loop over all arg sets. for _, arg_set in ipairs(arg_sets) do do_arg_set(arg_set, n, islast) end if #all_stresses_seen > 1 then track("multiple-accent-patterns") insert_cat("~ with multiple accent patterns") track("multiple-declensions") insert_cat("~ with multiple declensions") end table.insert(args.per_word_heading_info, args.heading_info) table.insert(args.per_word_categories, args.categories) local headings = compute_heading(args) table.insert(args.per_word_headings, headings) table.insert(args.per_word_genders, args.genders) handle_forms_and_overrides(args, n, islast) if args.any_suffix then -- If we're conjugating a suffix, remove the pseudoconsonant or asif_prefix -- that we previously inserted at the beginning. local asif_prefix = args["asif_prefix" .. n] or args.asif_prefix or PSEUDOCONS local asif_prefix_tr = com.translit(asif_prefix) for _, case in ipairs(all_cases) do if args.forms[case] then local newforms = {} for _, form in ipairs(args.forms[case]) do local formru = form[1] local formtr = form[2] formru = rsub(formru, "^%-" .. asif_prefix, "-") if formtr then formtr = rsub(formtr, "^%-" .. asif_prefix_tr, "-") end if formru == "-" then -- if no ending, insert "(no suffix)". table.insert(newforms, {"(no suffix)"}) else table.insert(newforms, {formru, formtr}) end end args.forms[case] = newforms end end end table.insert(args.per_word_info, {args.forms, joiner}) end handle_overall_forms_and_overrides(args) compute_overall_heading_categories_and_genders(args) for _, case in ipairs(all_cases) do if args[case] then for _, form in ipairs(args[case]) do local ru, tr = form[1], form[2] local ruentry, runotes = m_table_tools.separate_notes(ru) ruentry = m_links.remove_links(ruentry) if rfind(ulower(ruentry), latin_text_class) then --error("Found Latin text " .. ruentry .. " in case " .. case) track("latin-text") track("latin-text/" .. case) end end end end -- Test code to compare existing module to new one. if test_new_ru_noun_module then local m_new_ru_noun = require("Modül:User:Benwing2/ru-noun") local newargs = m_new_ru_noun.do_generate_forms(orig_args, old) local difdecl = false for _, case in ipairs(all_cases) do local arg = args[case] local newarg = newargs[case] local is_pl = rfind(case, "_pl") if args.thisn == "s" and is_pl or args.thisn == "p" and not is_pl then -- Don't need to check cases that won't be displayed. elseif not m_table.deepEquals(arg, newarg) then local monosyl_accent_diff = false -- Differences only in monosyllabic accents. Enable if we -- change the algorithm for these. --if arg and newarg and #arg == 1 and #newarg == 1 then -- local ru1, tr1 = arg[1][1], arg[1][2] -- local ru2, tr2 = newarg[1][1], newarg[1][2] -- if com.is_monosyllabic(ru1) and com.is_monosyllabic(ru2) then -- ru1, tr1 = com.remove_accents(ru1, tr1) -- ru2, tr2 = com.remove_accents(ru2, tr2) -- if ru1 == ru2 and tr1 == tr2 then -- monosyl_accent_diff = true -- end -- end --end if monosyl_accent_diff then track("monosyl-accent-diff") difdecl = true else -- Uncomment this to display the particular case and -- differing forms. --error(case .. " " .. (arg and com.concat_forms(arg) or "nil") .. " || " .. (newarg and com.concat_forms(newarg) or "nil")) track("different-decl") difdecl = true end break end end if not difdecl then track("same-decl") end end return args end -- Implementation of main entry point local function do_show(frame, old) local args = clone_args(frame) local args = export.do_generate_forms(args, old) return make_table(args) .. m_utilities.format_categories(args.categories, lang) end -- The main entry point for modern declension tables. function export.show(frame) return do_show(frame, false) end -- The main entry point for old declension tables. function export.show_old(frame) return do_show(frame, true) end -- Implementation of new entry point, esp. for multiple words local function do_show_multi(frame) local args = clone_args(frame) local args = export.do_generate_forms_multi(args) return make_table(args) .. m_utilities.format_categories(args.categories, lang) end -- The new entry point, esp. for multiple words (but works fine for -- single words). function export.show_multi(frame) return do_show_multi(frame) end local function get_form(forms, preserve_links, raw) local canon_forms = {} for _, form in ipairs(forms) do if raw then local ru, tr = form[1], form[2] ru = rsub(ru, "|", "<!>") if tr then tr = rsub(tr, "|", "<!>") end insert_if_not(canon_forms, {ru, tr}) else local ru, tr = form[1], form[2] local ruentry, runotes = m_table_tools.separate_notes(ru) -- Skip hypothetical forms (but include in the raw versions) if not rfind(runotes, HYPMARKER) then local trentry, trnotes if tr then trentry, trnotes = m_table_tools.separate_notes(tr) end if not preserve_links then ruentry = m_links.remove_links(ruentry) end ruentry = rsub(ruentry, "|", "<!>") if trentry then trentry = rsub(trentry, "|", "<!>") end insert_if_not(canon_forms, {ruentry, trentry}) end end end return com.concat_forms(canon_forms) end local function case_will_be_displayed(args, case) local ispl = rfind(case, "_pl") local caseok = true if args.n == "p" then caseok = ispl elseif args.n == "s" then caseok = not ispl end for _, override_case in ipairs(overridable_only_cases) do if case == override_case and not args.any_overridden[override_case] then caseok = false break end end if args.a == "a" or args.a == "i" then if rfind(case, "_[ai]n") then caseok = false end else -- bianimate -- don't include inanimate/animate variants if combined variant exists -- (typically because inanimate/animate variants are the same); -- FIXME: This could conceivably be different from how the display -- code works, which just checks that the inanimate/animate variants -- are the same when deciding whether to display them, in particular -- if there is an override. Here we are following the algorithm of -- handle_overall_forms_and_overrides(). if (case == "acc_sg_in" or case == "acc_sg_an") and args.acc_sg or (case == "acc_pl_in" or case == "acc_pl_an") and args.acc_pl then caseok = false end end if not args[case] then caseok = false end return caseok end local function concat_case_args(args, do_all, raw) local ins_text = {} for _, case in ipairs(do_all and all_cases or overridable_cases) do if case_will_be_displayed(args, case) then local forms = get_form(args[case], rfind(case, "_linked"), raw) if forms ~= "" then table.insert(ins_text, case .. (raw and "_raw" or "") .. "=" .. forms) end end end return table.concat(ins_text, "|") end -- The entry point for 'ru-noun-forms' to generate all noun forms. -- This returns a single string, with | separating arguments and named -- arguments of the form NAME=VALUE. function export.generate_forms(frame) local args = clone_args(frame) args = export.do_generate_forms(args, false) return concat_case_args(args) end -- The entry point to generate multiple sets of noun forms. This is a hack -- to speed up calling from a bot, where we often want to compare old and new -- argument results to make sure they're the same. Each set of arguments is -- jammed together into a single argument with individual values separated by -- <!>; named arguments are of the form NAME<->VALUE. The return value for -- each set of arguments is as in export.generate_forms(), and the return -- values are concatenated with <!> separating them. NOTE: This will fail if -- the exact sequences <!> or <-> happen to occur in values (which is unlikely, -- esp. as we don't even use the characters <, ! or > for anything) and aren't -- HTML-escaped. function export.generate_multi_forms(frame) local retvals = {} for _, argset in ipairs(frame.args) do local args = {} local i = 0 local argvals = rsplit(argset, "<!>") for _, argval in ipairs(argvals) do local split_arg = rsplit(argval, "<%->") if #split_arg == 1 then i = i + 1 args[i] = ine(split_arg[1]) else assert(#split_arg == 2) args[split_arg[1]] = ine(split_arg[2]) end end args = export.do_generate_forms(args, false) table.insert(retvals, concat_case_args(args)) end return table.concat(retvals, "<!>") end -- The entry point for 'ru-noun-form' to generate a particular noun form. function export.generate_form(frame) local args = clone_args(frame) if not args.form then error("Must specify desired form using form=") end local form = args.form if not m_table.contains(all_cases, form) then error("Unrecognized form " .. form) end local args = export.do_generate_forms(args, false) if not args[form] then return "" else return get_form(args[form]) end end -- The entry point for generating arguments of various sorts, including -- the case forms, gender, number and animacy. function export.generate_args(frame) local args = clone_args(frame) args = export.do_generate_forms(args, false) local retargs = {} table.insert(retargs, concat_case_args(args, "doall")) table.insert(retargs, concat_case_args(args, "doall", "raw")) table.insert(retargs, "g=" .. table.concat(args.genders, ",")) -- The following is correct even with ndef because if ndef is -- set we will set it in args.n. table.insert(retargs, "n=" .. (args.n or "b")) table.insert(retargs, "a=" .. (args.a or "i")) return table.concat(retargs, "|") end -- The entry point for compatibility with {{ru-decl-noun-z}}. function export.show_z(frame) local args = clone_args(frame) local stem = args[1] local stress = args[2] local specific = args[4] or "" -- Parse gender/animacy spec local gender, anim = rmatch(args[3], "^([mfn])-([a-z]+)") if not gender then error("Unrecognized gender/anim spec " .. args[3]) end if anim ~= "an" and anim ~= "in" then anim = "both" end args.a = anim -- Handle specific specific = rsub(specific, "ё", ";ё") -- Compute decl; special case for семьянин (perhaps not necessary) local decl = com.make_unstressed_once(stem) == "семьянин" and "#" .. specific or gender .. specific -- Handle overrides args.pre_sg = args.prp_sg args.pre_pl = args.prp_pl args.notes = args.note if args.par then args.par = "+" end if args.loc then if args.loc == "в" then args.loc = "в +" elseif args.loc == "на" then args.loc = "на +" else args.loc = "в +,на +" end end local arg_set = {} table.insert(arg_set, stress) table.insert(arg_set, stem) table.insert(arg_set, decl) local per_word_info = {{{arg_set}, ""}} return generate_forms_1(args, per_word_info) end -------------------------------------------------------------------------- -- Autodetection and lemma munging -- -------------------------------------------------------------------------- -- Attempt to detect the type of the lemma based on its ending, separating -- off the stem and the ending. GENDER must be present with -ь and plural -- stems, and is otherwise ignored. Return up to three values: The stem -- (lemma minus ending), the singular lemma ending, and if the lemma was -- plural, the plural lemma ending. If the lemma was singular, the singular -- lemma ending will contain any user-given accents; likewise, if the -- lemma was plural, the plural ending will contain such accents. -- VARIANT comes from the declension spec and controls certain declension -- variants. local function detect_lemma_type(lemma, tr, gender, args, variant) local base, ending = rmatch(lemma, "^(.*)([еЕ]" .. AC .. ")$") -- accented if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*[" .. com.sib_c .. "])([еЕ])$") -- unaccented if base then if variant == "-ище" and not rfind(lemma, "[щЩ][еЕ]$") then error("With declension variant -ище, lemma should end in -ще: " .. lemma) end return base, com.strip_tr_ending(tr, ending), variant == "-ище" and "(ищ)е-и" or "о" end if variant == "-ишко" then base, ending = rmatch(lemma, "^(.*[шШ][кК])([оО])$") -- unaccented if not base then error("With declension variant -ишко, lemma should end in -шко: " .. lemma) end return base, com.strip_tr_ending(tr, ending), "(ишк)о-и" end if variant == "-ин" then base, ending = rmatch(lemma, "^(.*)([иИ][" .. AC .. GR .. "]?[нН][ъЪ]?)$") -- maybe accented if not base then error("With declension variant -ин, lemma should end in -ин(ъ): " .. lemma) end return base, com.strip_tr_ending(tr, ending), ulower(ending) end -- Now autodetect -ин; only animate and in -анин/-янин base, ending = rmatch(lemma, "^(.*[аАяЯ][" .. AC .. GR .. "]?[нН])([иИ][" .. AC .. GR .. "]?[нН][ъЪ]?)$") -- Need to check the animacy to avoid nouns like маиганин, цианин, -- меланин, соланин, etc. if base and args.thisa == "a" then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*)([ёЁ]" .. AC .. "?[нН][оО][кК][ъЪ]?)$") if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*[" .. com.sib_c .. "])([оО]" .. AC .. "[нН][оО][кК][ъЪ]?)$") if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*)([ёЁ]" .. AC .. "?[нН][оО][чЧ][еЕ][кК][ъЪ]?)$") if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*[" .. com.sib_c .. "])([оО]" .. AC .. "[нН][оО][чЧ][еЕ][кК][ъЪ]?)$") if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*)([мМ][яЯ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end --recognize plural endings if gender == "n" then base, ending = rmatch(lemma, "^(.*)([ьЬ][яЯ][" .. AC .. GR .. "]?)$") if base then -- Don't do this; о/-ья is too rare -- error("Ambiguous plural lemma " .. lemma .. " in -ья, singular could be -о or -ье/-ьё; specify the singular") return base, com.strip_tr_ending(tr, ending), "ье", ending end base, ending = rmatch(lemma, "^(.*)([аяАЯ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), rfind(ending, "[аА]") and "о" or "е", ending end base, ending = rmatch(lemma, "^(.*)([ыиЫИ][" .. AC .. GR .. "]?)$") if base then if rfind(ending, "[ыЫ]") or rfind(base, "[" .. com.sib .. com.velar .. "]$") then return base, com.strip_tr_ending(tr, ending), "о-и", ending else -- FIXME, should we return a slash declension? error("No neuter declension е-и available; use a slash declension") end end end if gender == "f" then base, ending = rmatch(lemma, "^(.*)([ьЬ][иИ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), "ья", ending end end -- Recognize masculines with irregular plurals, but only if the user -- either explicitly specified that this noun is plural (n=p) or -- specifically requested the irregular plural. This is necessary -- because some masculine nouns have feminine endings, which look -- like irregular plurals. if gender == "m" then if args.thisn == "p" or variant == "-ья" then base, ending = rmatch(lemma, "^(.*)([ьЬ][яЯ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), (args.old and "ъ-ья" or "-ья"), ending end end if args.thisn == "p" or args.want_sc1 then base, ending = rmatch(lemma, "^(.*)([аА][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), (args.old and "ъ-а" or "-а"), ending end base, ending = rmatch(lemma, "^(.*)([яЯ][" .. AC .. GR .. "]?)$") if base then if rfind(base, "[" .. com.vowel .. "][" .. AC .. GR .. "]?$") then return base, com.strip_tr_ending(tr, ending), "й-я", ending else return base, com.strip_tr_ending(tr, ending), "ь-я", ending end end end end if gender == "m" or gender == "f" then base, ending = rmatch(lemma, "^(.*[" .. com.sib .. com.velar .. "])([иИ][" .. AC .. GR .. "]?)$") if not base then base, ending = rmatch(lemma, "^(.*)([ыЫ][" .. AC .. GR .. "]?)$") end if base then return base, com.strip_tr_ending(tr, ending), gender == "m" and (args.old and "ъ" or "") or "а", ending end base, ending = rmatch(lemma, "^(.*[" .. com.vowel .. "й][" .. AC .. GR .. "]?)([иИ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), gender == "m" and "й" or "я", ending end base, ending = rmatch(lemma, "^(.*)([иИ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), gender == "m" and "ь-m" or "я", ending end end if gender == "3f" then base, ending = rmatch(lemma, "^(.*)([иИ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), "ь-f", ending end end -- end of recognize-plurals code base, ending = rmatch(lemma, "^(.*)([ьЬ][яеёЯЕЁ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*)([йаяеоёъЙАЯЕОЁЪ][" .. AC .. GR .. "]?)$") if base then return base, com.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*)([ьЬ])$") if base then if gender == "m" or gender == "f" then return base, com.strip_tr_ending(tr, ending), "ь-" .. gender elseif gender == "3f" then return base, com.strip_tr_ending(tr, ending), "ь-f" else error("Need to specify gender m or f with lemma in -ь: ".. lemma) end end if rfind(lemma, "[ыиЫИ][" .. AC .. GR .. "]?$") then error("If this is a plural lemma, gender must be specified: " .. lemma) elseif rfind(lemma, "[иИіІуУыЫѣѢэЭюЮѵѴ]" .. DIA .. "?[" .. AC .. GR .. "]?$") then error("Don't know how to decline lemma ending in this type of vowel: " .. lemma) end return lemma, tr, "" end local plural_variant_detection_map = { [""] = {["-ья"]="-ья"}, ["ъ"] = {["-ья"]="ъ-ья"}, } local special_case_1_to_plural_variant = { [""] = "-а", ["ъ"] = "ъ-а", ["й"] = "й-я", ["ь-m"] = "ь-я", ["о"] = "о-и", -- these last two are here to avoid getting errors in that checks for -- compatibility with special case 1; the basic variants of these decls -- don't actually exist ["(ишк)о"] = "(ишк)о-и", ["(ищ)е"] = "(ищ)е-и", } local function map_decl(decl, fun) if rfind(decl, "/") then local split_decl = rsplit(decl, "/") if #split_decl ~= 2 then error("Mixed declensional class " .. decl .. "needs exactly two classes, singular and plural") end return fun(split_decl[1]) .. "/" .. fun(split_decl[2]) else return fun(decl) end end -- Canonicalize decl class into non-accented and alias-resolved form; -- but note that some canonical decl class names with an accent in them -- (e.g. е́, not the same as е, whose accented version is ё; and various -- adjective declensions). local function canonicalize_decl(decl, old) local function do_canon(decl) -- remove accents, but not from е́ (for adj decls, accent matters -- as well but we handle that by mapping the accent to a stress pattern -- and then to the accented version in determine_stress_variant()) if decl ~= "е́" then decl = com.remove_accents(decl) end local decl_aliases = old and declensions_old_aliases or declensions_aliases local decl_cats = old and declensions_old_cat or declensions_cat if decl_aliases[decl] then -- If we find an alias, map it. decl = decl_aliases[decl] elseif not decl_cats[decl] then error("Unrecognized declension class " .. decl) end return decl end return map_decl(decl, do_canon) end -- Attempt to determine the actual declension (including plural variants) -- based on a combination of the declension the user specified, what can be -- detected from the lemma, and special case (1), if given in the declension. -- DECL is the value the user passed for the declension field, after -- extraneous annotations (special cases (1) and (2), * for reducible, -- ё for ё/ё alternation and a ; that may precede ё) have been stripped off. -- What's left is one of the following: -- -- 1. Blank, meaning to autodetect the declension from the lemma -- 2. A hyphen followed by a declension variant (-ья, -ин, -ишко, -ище; see -- long comment at top of file) -- 3. A gender (m, f, n, 3f) -- 4. A gender plus declension variant (e.g. f-ья) -- 5. An actual declension, possibly including a plural variant (e.g. о-и) or -- a slash declension (e.g. я/-ья, used for the noun дядя). -- -- Return seven args: stem (lemma minus ending), translit, canonicalized -- declension, explicitly specified gender if any (m, f, n or nil), whether -- the specified declension or detected ending was accented, whether the -- detected ending was pl, and whether the declension was autodetected -- (corresponds to cases where a full word with ending attached is required -- in the lemma field). "Canonicalized" means after autodetection, with -- accents removed, with any aliases mapped to their canonical versions -- and with any requested declension variants applied. The result is either a -- declension that will have a categorization entry (in declensions_cat[] or -- declensions_old_cat[]) or a slash declension where each part similarly has -- a categorization entry. -- -- Note that gender is never required when an explicit declension is given, -- and in connection with stem autodetection is required only when the lemma -- either ends in -ь or is plural. determine_decl = function(lemma, tr, decl, args) -- Assume we're passed a value for DECL of types 1-4 above, and -- fetch gender and requested declension variant. local stem local want_ya_plural, orig_pl_ending, variant local was_autodetected local gender = rmatch(decl, "^(3?[mfn]?)$") if not gender then gender, variant = rmatch(decl, "^(3?[mfn]?)(%-[^%-]+)$") -- But be careful with explicit declensions like -а that look like -- variants without gender (FIXME, eventually we should maybe do -- something about the potential ambiguity). if gender == "" and not m_table.contains({"-ья", "-ин", "-ишко", "-ище"}, variant) then gender, variant = nil, nil end end -- If DECL is of type 1-4, handle declension variants and detect -- the actual declension from the lemma. if gender then -- Check for declension variants if variant then if variant == "-ья" then want_ya_plural = "-ья" else -- Sanity-check remaining declension variants, which need -- specific values of animacy, gender and special-case (1) local sc1_needed local animate_needed if variant == "-ишко" then animate_needed = false sc1_needed = true elseif variant == "-ище" then animate_needed = true sc1_needed = true elseif variant == "-ин" then animate_needed = true sc1_needed = false else -- WARNING: If adding another variant, you need to also -- add to the list farther above. error("Unrecognized declension variant " .. variant .. ", should be -ья, -ин, -ишко or -ище") end if sc1_needed and not args.want_sc1 then error("Declension variant " .. variant .. " must be used with special case (1)") elseif sc1_needed == false and args.want_sc1 then error("Declension variant " .. variant .. " must not be used with special case (1)") end if animate_needed and args.thisa ~= "a" then error("Declension variant " .. variant .. " must be specified as animate") elseif animate_needed == false and args.thisa == "a" then error("Declension variant " .. variant .. " must not be specified as animate") end if gender ~= "" and gender ~= "m" then error("Declension variant " .. variant .. " should be used with the masculine gender") end end end stem, tr, decl, orig_pl_ending = detect_lemma_type(lemma, tr, gender, args, variant) was_autodetected = true else stem, tr = lemma, tr end -- Now canonicalize gender if gender == "3f" then gender = "f" elseif gender == "" then gender = nil end -- The ending should be treated as accented if either the original singular -- or plural ending was accented, or if the stem is non-syllabic. local was_accented = com.is_stressed(decl) or orig_pl_ending and com.is_stressed(orig_pl_ending) or com.is_nonsyllabic(stem) local was_plural = not not orig_pl_ending decl = canonicalize_decl(decl, args.old) -- The rest of this code concerns plural variants. It's somewhat -- complicated because there are potentially four sources of plural -- variants (not to mention plural variants constructed using slash -- notation): -- -- 1. A user-requested plural variant in declension types 2 or 4 above -- (currently only -ья) -- 2. An explicit plural variant encoded in an explicit declension of -- type 5 above -- 3. An autodetected plural variant (which will happen in some cases -- when autodetection is performed on a nominative plural) -- 4. A plural variant derived using special case (1). -- -- Up to three actual plural variants might exist (e.g. if the user -- specifies a DECL value or 'm-ья(1)' and a STEM ending in -а, -- although not all three can ever be compatible because -ья and (1) -- are never compatible). We can't have all four because if there's -- an explicit plural variant, there won't be a user-requested or -- autodetected plural variant. -- -- The goal below is to do two things: Check that all available plural -- variants are the same, and generate the actual declension. -- If we have a type-2 or type-3 variant, we already have the actual -- declension; else we need to use a table to map the basic declension -- to the one with the plural variant encoded in it. -- -- NOTE: The code below was written with a more general plural-variant -- system. It probably can be simplified a lot now. -- 1: Handle explicit decl with slash variant if rfind(decl, "/") then if want_ya_plural then -- Don't think this can happen error("Plural variant " .. want_ya_plural .. " not compatible with slash declension " .. decl) end if args.want_sc1 then error("Special case (1) not compatible with slash declension" .. decl) end return stem, tr, decl, gender, was_accented, was_plural, was_autodetected end -- 2: Retrieve explicitly specified or autodetected decl and pl. variant local basic_decl, detected_or_explicit_plural = rmatch(decl, "^(.*)(%-[^mf]+)$") if basic_decl == "ь" then basic_decl = "ь-m" end basic_decl = basic_decl or decl -- 3: Any user-requested plural variant must agree with explicit or -- autodetected variant. if want_ya_plural and detected_or_explicit_plural and want_ya_plural ~= detected_or_explicit_plural then error("Plural variant " .. want_ya_plural .. " requested but plural variant " .. detected_or_explicit_plural .. " detected from plural stem") end -- 4: Handle special case (1). Derive the full declension, make sure its -- plural variant matches any other available plural variants, and -- return the declension. if args.want_sc1 then local sc1_decl = special_case_1_to_plural_variant[basic_decl] or error("Special case (1) not compatible with declension " .. basic_decl) local sc1_plural = rsub(sc1_decl, "^.*%-", "-") local other_plural = want_ya_plural or detected_or_explicit_plural if other_plural and sc1_plural ~= other_plural then error("Plural variant " .. other_plural .. " specified or detected, but special case (1) calls for plural variant " .. sc1_plural) end return stem, tr, sc1_decl, gender, was_accented, was_plural, was_autodetected end -- 5: Handle user-requested plural variant without explicit or detected -- one. (If an explicit or detected one exists, we've already checked -- that it agrees with the user-requested one, and so we already have -- our full declension.) if want_ya_plural and not detected_or_explicit_plural then local variant_decl if plural_variant_detection_map[decl] then variant_decl = plural_variant_detection_map[decl][want_ya_plural] end if variant_decl then return stem, tr, variant_decl, gender, was_accented, was_plural, was_autodetected else return stem, tr, decl .. (args.old and "/ъ-ья" or "/-ья"), gender, was_accented, was_plural, was_autodetected end end -- 6: Just return the full declension, which will include any available -- plural variant in it. return stem, tr, decl, gender, was_accented, was_plural, was_autodetected end -- Convert soft adjectival declensions into hard ones following certain -- stem-final consonants. FIXME: We call this in two places, once -- to handle auto-detection and once to handle explicit declensions; but -- in the former case we end up calling it twice. local function determine_adj_stem_variant(decl, stem) local iend = rmatch(decl, "^%+[іи]([йея]?)$") -- Convert ій/ий to ый after velar or sibilant. This is important for -- velars; doesn't really matter one way or the other for sibilants as -- the sibilant rules will convert both sets of endings to the same -- thing (whereas there will be a difference with о vs. е for velars). if iend and rfind(stem, "[" .. com.velar .. com.sib .. "]$") then decl = "+ы" .. iend -- The following is necessary for -ц, unclear if makes sense for -- sibilants. (Would be necessary -- I think -- if we were -- inferring short adjective forms, but we're not.) elseif decl == "+ее" and rfind(stem, "[" .. com.sib_c .. "]$") then decl = "+ое" end return decl end -- Attempt to determine the actual adjective declension based on a -- combination of the declension the user specified and what can be detected -- from the stem. DECL is the value the user passed for the declension field, -- after extraneous annotations have been removed (although none are probably -- relevant here). What's left is one of the following, which always begins -- with +: -- -- 1. +, meaning to autodetect the declension from the stem -- 2. +ь, same as + but selects +ьий instead of +ий if lemma ends in -ий -- 3. +short, +mixed or +proper, with the declension partly specified but -- the particular gender/number-specific short/mixed variant to be -- autodetected -- 4. A gender (+m, +f or +n), used only for detecting the singular of -- plural-form lemmas (this is primarily used in conjunction with template -- ru-noun+, to explicitly specify the gender; for the actual declension, -- it doesn't much matter what singular gender we pick since we're a -- plural only) -- 5. A gender plus short/mixed/proper/ь (e.g. +f-mixed), again with the gender -- used only for detecting the singular of plural-form short/mixed lemmas -- 6. An actual declension, possibly including a slash declension -- (WARNING: Unclear if slash declensions will work, especially those -- that are adjective/noun combinations) -- -- Returns the same seven args as for determine_decl(). The returned -- declension will always begin with +. detect_adj_type = function(lemma, tr, decl, old) local was_autodetected local base, ending local basedecl, g = rmatch(decl, "^(%+)([mfn])$") if not basedecl then g, basedecl = rmatch(decl, "^%+([mfn])%-([a-zь]+)$") if basedecl then basedecl = "+" .. basedecl end end decl = basedecl or decl if decl == "+" or decl == "+ь" then local loc = rfind(lemma, "[аеиіоыя][" .. AC .. GR .. "]?[йея]$") or rfind(lemma, "ь[йея]$") if loc then base, ending = usub(lemma, 1, loc - 1), usub(lemma, loc) end if ending == "ий" and decl == "+ь" then decl = "+ьий" elseif ending == "ій" and decl == "+ь" then decl = "+ьій" elseif ending then decl = "+" .. ending else local loc, shortmixed = rfind(lemma, "[аоы][" .. AC .. GR .. "]?$") or rfind(lemma, "ъ?$") if loc then base, ending = usub(lemma, 1, loc - 1), usub(lemma, loc) shortmixed = rfind(base, "^[" .. com.uppercase .. "].*[иы]" .. AC .. "н$") and "stressed-proper" or -- accented rfind(base, "^[" .. com.uppercase .. "].*[иы]н$") and "proper" or --not accented rlfind(base, "[ёео][" .. AC .. GR .. "]?в$") and "short" or rlfind(base, "[ыи]" .. AC .. "н$") and "stressed-short" or -- accented rlfind(base, "[ыи]н$") and "mixed" --not accented end if not shortmixed then error("Cannot determine stem type of adjective: " .. lemma) end decl = "+" .. ending .. "-" .. shortmixed end was_autodetected = true elseif m_table.contains({"+short", "+mixed", "+proper"}, decl) then base, ending = rmatch(lemma, "^(.-)([оаыъ]?[" .. AC .. GR .. "]?)$") assert(base) local shortmixed = usub(decl, 2) if rlfind(base, "[ыи]" .. AC .. "н$") then -- accented if shortmixed == "short" then shortmixed = "stressed-short" elseif shortmixed == "proper" then shortmixed = "stressed-proper" end end decl = "+" .. ending .. "-" .. shortmixed was_autodetected = true else base = lemma end if ending and ending ~= "" then tr = com.strip_tr_ending(tr, ending) end -- Remove any accents from the declension, but not their presence. -- We will convert was_accented into stress pattern b, and convert that -- back to an accented version in determine_stress_variant(). This way -- we end up with the stressed version whether the user placed an accent -- in the ending or decl or specified stress pattern b. -- FIXME, might not work in the presence of slash declensions local was_accented = com.is_stressed(decl) decl = com.remove_accents(decl) decl = map_decl(decl, function(decl) return determine_adj_stem_variant(decl, base) end) local singdecl if decl == "+ые" then singdecl = (g == "m" or not g) and (was_accented and "+ой" or "+ый") or not old and g == "f" and "+ая" or not old and g == "n" and "+ое" elseif decl == "+ыя" and old then singdecl = (g == "f" or not g) and "+ая" or g == "n" and "+ое" elseif decl == "+ие" and not old then singdecl = (g == "m" or not g) and "+ий" or g == "f" and "+яя" or g == "n" and "+ее" elseif decl == "+іе" and old and (g == "m" or not g) then singdecl = "+ій" elseif decl == "+ія" and old then singdecl = (g == "f" or not g) and "+яя" or g == "n" and "+ее" elseif decl == "+ьи" then singdecl = (g == "m" or not g) and (old and "+ьій" or "+ьий") or g == "f" and "+ья" or g == "n" and "+ье" elseif rfind(decl, "^%+ы%-") then -- decl +ы-mixed or similar local beg = (g == "m" or not g) and (old and "ъ" or "") or g == "f" and "а" or g == "n" and "о" singdecl = beg and "+" .. beg .. usub(decl, 3) end if singdecl then was_plural = true decl = singdecl end return base, tr, canonicalize_decl(decl, old), g, was_accented, was_plural, was_autodetected end -- If stress pattern omitted, detect it based on whether ending is stressed -- or the decl class or stem accent calls for inherent stress, defaulting to -- pattern a. This is run after alias resolution and accent removal of DECL; -- WAS_ACCENTED indicates whether the ending was originally stressed. -- FIXME: This is run before splitting slash patterns but should be run after. detect_stress_pattern = function(stem, decl, decl_cats, reducible, was_plural, was_accented) -- ёнок and ёночек always bear stress if rfind(decl, "ёнокъ?") or rfind(decl, "ёночекъ?") then return "b" -- stressed suffix и́н; missing in plural and true endings don't bear stress -- (except for exceptional господи́н) elseif rfind(decl, "инъ?") and was_accented then return "d" -- Adjectival -ой always bears the stress elseif rfind(decl, "%+ой") then return "b" -- Adjectival stressed-short, stressed-proper bears the stress elseif rfind(decl, "^%+.*%-stressed") then return "b" -- Pattern b if ending was accented by user elseif was_accented then return "b" -- Nonsyllabic stem means pattern b elseif com.is_nonsyllabic(stem) then return "b" -- Accent on reducible vowel in masc nom sg (not plural) means pattern b. -- Think about whether we want to enable this. -- elseif reducible and not was_plural then -- -- FIXME hack. Eliminate plural part of slash declension. -- decl = rsub(decl, "/.*", "") -- if decl_cats[decl] and decl_cats[decl].g == "m" then -- if com.is_ending_stressed(stem) or com.is_monosyllabic(stem) then -- return "b" -- end -- end end return "a" end -- In certain special cases, depending on the declension, we override the -- user-specified stress pattern and convert it to something else. -- NOTE: This function is run after alias resolution and accent removal. -- FIXME: It's also run before splitting slash patterns but should be run after. override_stress_pattern = function(decl, stress) -- ёнок and ёночек always bear stress; if user specified a, -- convert to b. Don't do this with slash patterns (see FIXME above). if stress == "a" and (rfind(decl, "^ёнокъ?$") or rfind(decl, "^ёночекъ?$")) then return "b" end return stress end -- Canonicalize an adjectival declension to either the stressed or unstressed -- variant depending on the stress. Ultimately this is what ensures that -- the user's stress mark on an adjectival ending is respected. determine_stress_variant = function(decl, stress) if stress == "b" then if decl == "+ая" then return "+а́я" elseif decl == "+ое" then return "+о́е" else -- Convert +...-short to +...-stressed-short and same for -proper local b, e = rmatch(decl, "^%+(.*)%-(short)$") if not b then b, e = rmatch(decl, "^%+(.*)%-(proper)$") end if b and not rfind(b, "%-stressed") then return "+" .. b .. "-stressed-" .. e end end end return decl end -- Canonicalize a declension based on the final stem consonant, in -- particular converting soft declensions to hard ones after velars and/or -- sibilants. FIXME: We also do this canonicalization earlier on during -- auto-detection (determine_adj_stem_variant() is called by -- detect_adj_type(), and code in detect_lemma_type() does the equivalent -- of the first clause below). Doing it here ensures that explicitly -- specified declensions get handled as well, but it would be nice to not -- do the same thing twice in the auto-detection case. determine_stem_variant = function(decl, stem) if decl == "е" and rfind(stem, "[" .. com.sib_c .. "]$") then return "о" end return determine_adj_stem_variant(decl, stem) end is_reducible = function(decl_cat) if decl_cat.suffix or decl_cat.cant_reduce or decl_cat.adj then return false elseif decl_cat.decl == "3rd" and decl_cat.g == "f" or decl_cat.g == "m" then return true else return false end end -- Reduce nom sg to stem by eliminating the "epenthetic" vowel. Applies to -- masculine 2nd-declension hard and soft, and 3rd-declension feminine in -- -ь. STEM and DECL are after determine_decl(), before converting -- outward-facing declensions to inward ones. function export.reduce_nom_sg_stem(stem, tr, decl, soft_n, can_err) local full_stem = stem .. (decl == "й" and decl or "") local full_tr = tr and tr .. (decl == "й" and "j" or "") local ret, rettr = com.reduce_stem(full_stem, full_tr, soft_n) if not ret and can_err then error("Unable to reduce stem " .. stem) end return ret, rettr end is_dereducible = function(decl_cat) if decl_cat.suffix or decl_cat.cant_reduce or decl_cat.adj then return false elseif decl_cat.decl == "1st" or decl_cat.decl == "2nd" and decl_cat.g == "n" then return true else return false end end -- Add a possible suffix to the bare stem, according to the declension and -- value of OLD. This may be -ь, -ъ, -й or nothing. We need to do this here -- because we don't actually attach such a suffix in attach_unstressed() due -- to situations where we don't want the suffix added, e.g. dereducible nouns -- in -ня. add_bare_suffix = function(bare, baretr, old, sgdc, dereduced) if old and sgdc.hard == "hard" then -- Final -ъ isn't transliterated return bare .. "ъ", baretr elseif sgdc.hard == "soft" or sgdc.hard == "palatal" then -- This next clause corresponds to a special case in Vitalik's module. -- It says that nouns in -ня (accent class a) have gen pl without -- trailing -ь. It appears to apply to most nouns in -ня (possibly -- all in -льня), but ку́хня (gen pl ку́хонь) and дерéвня (gen pl -- дереве́нь) is an exception. (Vitalik's module has an extra -- condition here 'stress == "a"' that would exclude дере́вня but I -- don't think this condition is in Zaliznyak, as he indicates -- дере́вня as having an exceptional genitive plural.) if dereduced and rfind(bare, "[нН]$") and sgdc.decl == "1st" then -- FIXME: What happens in this case old-style? I assume that -- -ъ is added, but this is a guess. -- Final -ъ isn't transliterated return bare .. (old and "ъ" or ""), baretr elseif rfind(bare, "[" .. com.vowel .. "][" .. AC .. GR .. "]?$") then return bare .. "й", baretr and (baretr .. "j") else return bare .. "ь", baretr and (baretr .. "ʹ") end else return bare, baretr end end -- Dereduce stem to the form found in the gen pl (and maybe nom sg) by -- inserting an epenthetic vowel. Applies to 1st declension and 2nd -- declension neuter, and to 2nd declension masculine when the stem was -- specified as a plural form (in which case we're deriving the nom sg, -- and also the gen pl in the alt-gen-pl scenario). STEM and DECL are -- after determine_decl(), before converting outward-facing declensions -- to inward ones. STRESS is the stess pattern. function export.dereduce_nom_sg_stem(stem, tr, sgdc, stress, old, can_err) local epenthetic_stress = ending_stressed_gen_pl_patterns[stress] local ret, rettr = com.dereduce_stem(stem, tr, epenthetic_stress) if not ret then if can_err then error("Unable to dereduce stem " .. stem) else return nil, nil end end return add_bare_suffix(ret, rettr, old, sgdc, true) end -------------------------------------------------------------------------- -- Second-declension masculine -- -------------------------------------------------------------------------- ----------------- Masculine hard ------------------- -- Hard-masculine declension, ending in a hard consonant -- (ending in -ъ, old-style). declensions_old["ъ"] = { ["nom_sg"] = "ъ", ["gen_sg"] = "а́", ["dat_sg"] = "у́", ["acc_sg"] = nil, ["ins_sg"] = "о́мъ", ["pre_sg"] = "ѣ́", ["nom_pl"] = "ы́", ["gen_pl"] = function(stem, stress) return nom.sibilant_suffixes[ulower(usub(stem, -1))] and "е́й" or "о́въ" end, ["alt_gen_pl"] = "ъ", ["dat_pl"] = "а́мъ", ["acc_pl"] = nil, ["ins_pl"] = "а́ми", ["pre_pl"] = "а́хъ", } declensions_old_cat["ъ"] = { decl="2nd", hard="hard", g="m" } -- Normal mapping of old ъ would be "" (blank), but we set up "#" as an alias -- so we have a way of referring to it without defaulting if need be and -- distinct from auto-detection (e.g. in the second stem of a word, or to -- override autodetection of -ёнок or -ин -- the latter is necessary in the -- case of семьянин). declensions_aliases["#"] = "" ----------------- Masculine hard, irregular plural ------------------- -- Hard-masculine declension, ending in a hard consonant -- (ending in -ъ, old-style), with irreg nom pl -а. declensions_old["ъ-а"] = mw.clone(declensions_old["ъ"]) declensions_old["ъ-а"]["nom_pl"] = "а́" declensions_old_cat["ъ-а"] = { decl="2nd", hard="hard", g="m", alt_nom_pl=true } declensions_aliases["#-a"] = "-a" -- Hard-masculine declension, ending in a hard consonant -- (ending in -ъ, old-style), with irreg soft pl -ья. -- Differs from the normal declension throughout the plural. declensions_old["ъ-ья"] = { ["nom_sg"] = "ъ", ["gen_sg"] = "а́", ["dat_sg"] = "у́", ["acc_sg"] = nil, ["ins_sg"] = "о́мъ", ["pre_sg"] = "ѣ́", ["nom_pl"] = "ья́", ["gen_pl"] = "ьёвъ", ["alt_gen_pl"] = "е́й", ["dat_pl"] = "ья́мъ", ["acc_pl"] = nil, ["ins_pl"] = "ья́ми", ["pre_pl"] = "ья́хъ", } declensions_old_cat["ъ-ья"] = { decl="2nd", hard="hard", g="m", irregpl=true } declensions_aliases["#-ья"] = "-ья" ----------------- Masculine hard, suffixed, irregular plural ------------------- declensions_old["инъ"] = { ["nom_sg"] = "и́нъ", ["gen_sg"] = "и́на", ["dat_sg"] = "и́ну", ["acc_sg"] = nil, ["ins_sg"] = "и́номъ", ["pre_sg"] = "и́нѣ", ["nom_pl"] = "е́", ["gen_pl"] = "ъ", ["dat_pl"] = "а́мъ", ["acc_pl"] = nil, ["ins_pl"] = "а́ми", ["pre_pl"] = "а́хъ", } declensions_old_cat["инъ"] = { decl="2nd", hard="hard", g="m", suffix=true } declensions_old["ёнокъ"] = { ["nom_sg"] = "ёнокъ", ["gen_sg"] = "ёнка", ["dat_sg"] = "ёнку", ["acc_sg"] = nil, ["ins_sg"] = "ёнкомъ", ["pre_sg"] = "ёнкѣ", ["nom_pl"] = "я́та", ["gen_pl"] = "я́тъ", ["dat_pl"] = "я́тамъ", ["acc_pl"] = nil, ["ins_pl"] = "я́тами", ["pre_pl"] = "я́тахъ", } declensions_old_cat["ёнокъ"] = { decl="2nd", hard="hard", g="m", suffix=true } declensions_old_aliases["онокъ"] = "ёнокъ" declensions_old_aliases["енокъ"] = "ёнокъ" declensions_old["ёночекъ"] = { ["nom_sg"] = "ёночекъ", ["gen_sg"] = "ёночка", ["dat_sg"] = "ёночку", ["acc_sg"] = nil, ["ins_sg"] = "ёночкомъ", ["pre_sg"] = "ёночкѣ", ["nom_pl"] = "я́тки", ["gen_pl"] = "я́токъ", ["dat_pl"] = "я́ткамъ", ["acc_pl"] = nil, ["ins_pl"] = "я́тками", ["pre_pl"] = "я́ткахъ", } declensions_old_cat["ёночекъ"] = { decl="2nd", hard="hard", g="m", suffix=true } declensions_old_aliases["оночекъ"] = "ёночекъ" declensions_old_aliases["еночекъ"] = "ёночекъ" ----------------- Masculine soft ------------------- -- Normal soft-masculine declension in -ь declensions_old["ь-m"] = { ["nom_sg"] = "ь", ["gen_sg"] = "я́", ["dat_sg"] = "ю́", ["acc_sg"] = nil, ["ins_sg"] = "ёмъ", ["pre_sg"] = "ѣ́", ["nom_pl"] = "и́", ["gen_pl"] = "е́й", ["alt_gen_pl"] = "ь", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["ь-m"] = { decl="2nd", hard="soft", g="m" } -- Soft-masculine declension in -ь with irreg nom pl -я declensions_old["ь-я"] = mw.clone(declensions_old["ь-m"]) declensions_old["ь-я"]["nom_pl"] = "я́" declensions_old_cat["ь-я"] = { decl="2nd", hard="soft", g="m", alt_nom_pl=true } ----------------- Masculine palatal ------------------- -- Masculine declension in palatal -й declensions_old["й"] = { ["nom_sg"] = "й", ["gen_sg"] = "я́", ["dat_sg"] = "ю́", ["acc_sg"] = nil, ["ins_sg"] = "ёмъ", ["pre_sg"] = function(stem, stress) return rlfind(stem, "[іи][" .. AC .. GR .. "]?$") and not ending_stressed_pre_sg_patterns[stress] and "и" or "ѣ́" end, ["nom_pl"] = "и́", ["gen_pl"] = "ёвъ", ["alt_gen_pl"] = "й", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["й"] = { decl="2nd", hard="palatal", g="m" } declensions_old["й-я"] = mw.clone(declensions_old["й"]) declensions_old["й-я"]["nom_pl"] = "я́" declensions_old_cat["й-я"] = { decl="2nd", hard="palatal", g="m", alt_nom_pl=true } -------------------------------------------------------------------------- -- First-declension feminine -- -------------------------------------------------------------------------- ----------------- Feminine hard ------------------- -- Hard-feminine declension in -а declensions_old["а"] = { ["nom_sg"] = "а́", ["gen_sg"] = "ы́", ["dat_sg"] = "ѣ́", ["acc_sg"] = "у́", ["ins_sg"] = {"о́й<insa>", "о́ю<insb>"}, -- see concat_word_forms_1() ["pre_sg"] = "ѣ́", ["nom_pl"] = "ы́", ["gen_pl"] = function(stem, stress) return nom.sibilant_suffixes[ulower(usub(stem, -1))] and ending_stressed_gen_pl_patterns[stress] and "е́й" or "ъ" end, ["alt_gen_pl"] = "е́й", ["dat_pl"] = "а́мъ", ["acc_pl"] = nil, ["ins_pl"] = "а́ми", ["pre_pl"] = "а́хъ", } declensions_old_cat["а"] = { decl="1st", hard="hard", g="f" } ----------------- Feminine soft ------------------- -- Soft-feminine declension in -я declensions_old["я"] = { ["nom_sg"] = "я́", ["gen_sg"] = "и́", ["dat_sg"] = function(stem, stress) return rlfind(stem, "[іи][" .. AC .. GR .. "]?$") and not ending_stressed_dat_sg_patterns[stress] and "и" or "ѣ́" end, ["acc_sg"] = "ю́", ["ins_sg"] = {"ёй<insa>", "ёю<insb>"}, -- see concat_word_forms_1() ["pre_sg"] = function(stem, stress) return rlfind(stem, "[іи][" .. AC .. GR .. "]?$") and not ending_stressed_pre_sg_patterns[stress] and "и" or "ѣ́" end, ["nom_pl"] = "и́", ["gen_pl"] = function(stem, stress) return ending_stressed_gen_pl_patterns[stress] and not rlfind(stem, "[" .. com.vowel .. "]́?$") and "е́й" or "й" end, ["alt_gen_pl"] = "е́й", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["я"] = { decl="1st", hard="soft", g="f" } -- Soft-feminine declension in -ья. -- Almost like ь + -я endings except for genitive plural. declensions_old["ья"] = { ["nom_sg"] = "ья́", ["gen_sg"] = "ьи́", ["dat_sg"] = "ьѣ́", ["acc_sg"] = "ью́", ["ins_sg"] = {"ьёй<insa>", "ьёю<insb>"}, -- see concat_word_forms_1() ["pre_sg"] = "ьѣ́", ["nom_pl"] = "ьи́", ["gen_pl"] = function(stem, stress) -- circumflex accent is a signal that forces stress, particularly -- in accent pattern d/d'. return (ending_stressed_gen_pl_patterns[stress] or stress == "d" or stress == "d'") and "е̂й" or "ий" end, ["dat_pl"] = "ья́мъ", ["acc_pl"] = nil, ["ins_pl"] = "ья́ми", ["pre_pl"] = "ья́хъ", } declensions_old_cat["ья"] = { decl="1st", hard="soft", g="f", stem_suffix="ь", gensg=true, ignore_reduce=true -- already has dereduced gen pl } -------------------------------------------------------------------------- -- Second-declension neuter -- -------------------------------------------------------------------------- ----------------- Neuter hard ------------------- -- Normal hard-neuter declension in -о declensions_old["о"] = { ["nom_sg"] = "о́", ["gen_sg"] = "а́", ["dat_sg"] = "у́", ["acc_sg"] = function(stem, stress, args) return not (args.explicit_gender == "m" and args.thisa == "a") and "о́" or nil end, ["ins_sg"] = "о́мъ", ["pre_sg"] = "ѣ́", ["nom_pl"] = "а́", ["gen_pl"] = function(stem, stress) return nom.sibilant_suffixes[ulower(usub(stem, -1))] and ending_stressed_gen_pl_patterns[stress] and "е́й" or "ъ" end, ["alt_gen_pl"] = "о́въ", ["dat_pl"] = "а́мъ", ["acc_pl"] = nil, ["ins_pl"] = "а́ми", ["pre_pl"] = "а́хъ", } declensions_old_cat["о"] = { decl="2nd", hard="hard", g="n" } -- Hard-neuter declension in -о with irreg nom pl -и declensions_old["о-и"] = mw.clone(declensions_old["о"]) declensions_old["о-и"]["nom_pl"] = "ы́" declensions_old_cat["о-и"] = { decl="2nd", hard="hard", g="n", alt_nom_pl=true } declensions_old_aliases["о-ы"] = "о-и" -- Masculine-gender neuter-form declension in -(ишк)о with irreg nom pl -и, -- with colloquial feminine endings in some of the singular cases -- (§5 p. 74 of Zaliznyak) declensions_old["(ишк)о-и"] = mw.clone(declensions_old["о-и"]) declensions_old["(ишк)о-и"]["gen_sg"] = {"а́", "ы́1"} declensions_old["(ишк)о-и"]["dat_sg"] = {"у́", "ѣ́1"} declensions_old["(ишк)о-и"]["ins_sg"] = {"о́мъ", "о́й1"} declensions_old_cat["(ишк)о-и"] = { decl="2nd", hard="hard", g="n", colloqfem=true, alt_nom_pl=true } internal_notes_table_old["(ишк)о-и"] = "<sup>1</sup> Colloquial." -- Masculine-gender animate neuter-form declension in -(ищ)е with irreg -- nom pl -и, with colloquial feminine endings in some of the singular cases -- (§4 p. 74 of Zaliznyak) declensions_old["(ищ)е-и"] = mw.clone(declensions_old["о-и"]) declensions_old["(ищ)е-и"]["acc_sg"] = {"а́", "у́1"} declensions_old["(ищ)е-и"]["gen_sg"] = {"а́", "ы́2"} declensions_old["(ищ)е-и"]["dat_sg"] = {"у́", "ѣ́2"} declensions_old["(ищ)е-и"]["ins_sg"] = {"о́мъ", "о́й2"} declensions_old_cat["(ищ)е-и"] = { decl="2nd", hard="hard", g="n", colloqfem=true, alt_nom_pl=true } internal_notes_table_old["(ищ)е-и"] = "<sup>1</sup> Colloquial.<br /><sup>2</sup> Less common, more colloquial." ----------------- Neuter soft ------------------- -- Soft-neuter declension in -е (stressed -ё) declensions_old["е"] = { ["nom_sg"] = "ё", ["gen_sg"] = "я́", ["dat_sg"] = "ю́", ["acc_sg"] = function(stem, stress, args) return not (args.explicit_gender == "m" and args.thisa == "a") and "ё" or nil end, ["ins_sg"] = "ёмъ", ["pre_sg"] = function(stem, stress) return rlfind(stem, "[іи][" .. AC .. GR .. "]?$") and not ending_stressed_pre_sg_patterns[stress] and "и" or "ѣ́" end, ["nom_pl"] = "я́", ["gen_pl"] = function(stem, stress) return ending_stressed_gen_pl_patterns[stress] and not rlfind(stem, "[" .. com.vowel .. "][" .. AC .. GR .. "]?$") and "е́й" or "й" end, ["alt_gen_pl"] = "ёвъ", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["е"] = { singular = function(suffix) if suffix == "ё" then return "ending in -ё" else return {} end end, decl="2nd", hard="soft", g="n", gensg=true } -- User-facing declension type "ё" = "е" declensions_old_aliases["ё"] = "е" -- Rare soft-neuter declension in stressed -е́ (e.g. муде́, бытие́) declensions_old["е́"] = { ["nom_sg"] = "е́", ["gen_sg"] = "я́", ["dat_sg"] = "ю́", ["acc_sg"] = function(stem, stress, args) return not (args.explicit_gender == "m" and args.thisa == "a") and "е́" or nil end, ["ins_sg"] = "е́мъ", ["pre_sg"] = function(stem, stress) -- FIXME!!! Are we sure about this condition? This is what was -- found in the old template, but the related -е declension has -- -ие prep sg ending -(и)и only when *not* stressed. return rlfind(stem, "[іи][" .. AC .. GR .. "]?$") and "и́" or "ѣ́" end, ["nom_pl"] = "я́", ["gen_pl"] = function(stem, stress) return rlfind(stem, "[" .. com.vowel .. "][" .. AC .. GR .. "]?$") and "й" or "е́й" end, ["alt_gen_pl"] = "ёвъ", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["е́"] = { singular = "ending in stressed -е", decl="2nd", hard="soft", g="n", gensg=true } -- Soft-neuter declension in unstressed -ье (stressed -ьё). declensions_old["ье"] = { ["nom_sg"] = "ьё", ["gen_sg"] = "ья́", ["dat_sg"] = "ью́", ["acc_sg"] = function(stem, stress, args) return not (args.explicit_gender == "m" and args.thisa == "a") and "ьё" or nil end, ["ins_sg"] = "ьёмъ", ["pre_sg"] = "ьѣ́", ["nom_pl"] = "ья́", ["gen_pl"] = function(stem, stress) return ending_stressed_gen_pl_patterns[stress] and "е́й" or "ий" end, ["alt_gen_pl"] = "ьёвъ", ["dat_pl"] = "ья́мъ", ["acc_pl"] = nil, ["ins_pl"] = "ья́ми", ["pre_pl"] = "ья́хъ", } declensions_old_cat["ье"] = { decl="2nd", hard="soft", g="n", stem_suffix="ь", gensg=true, ignore_reduce=true -- already has dereduced gen pl } declensions_old_aliases["ьё"] = "ье" -------------------------------------------------------------------------- -- Third declension -- -------------------------------------------------------------------------- declensions_old["ь-f"] = { ["nom_sg"] = "ь", ["gen_sg"] = "и́", ["dat_sg"] = "и́", ["acc_sg"] = "ь", ["ins_sg"] = "ью́", ["pre_sg"] = "и́", ["nom_pl"] = "и́", ["gen_pl"] = "е́й", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["ь-f"] = { decl="3rd", hard="soft", g="f" } declensions_old["мя"] = { ["nom_sg"] = "мя", ["gen_sg"] = "мени", ["dat_sg"] = "мени", ["acc_sg"] = nil, ["ins_sg"] = "менемъ", ["pre_sg"] = "мени", ["nom_pl"] = "мена́", ["gen_pl"] = "мёнъ", ["dat_pl"] = "мена́мъ", ["acc_pl"] = nil, ["ins_pl"] = "мена́ми", ["pre_pl"] = "мена́хъ", } declensions_old_cat["мя"] = { decl="3rd", hard="soft", g="n", cant_reduce=true } -------------------------------------------------------------------------- -- Indeclinable -- -------------------------------------------------------------------------- -- Indeclinable declension; no endings. declensions_old["$"] = { ["nom_sg"] = "", ["gen_sg"] = "", ["dat_sg"] = "", ["acc_sg"] = nil, ["ins_sg"] = "", ["pre_sg"] = "", ["nom_pl"] = "", ["gen_pl"] = "", ["dat_pl"] = "", ["acc_pl"] = nil, ["ins_pl"] = "", ["pre_pl"] = "", } declensions_old_cat["$"] = { decl="indeclinable", hard="none", g="none" } -------------------------------------------------------------------------- -- Adjectival -- -------------------------------------------------------------------------- -- This needs to be up here because it is called just below. local function old_to_new(v) v = rsub(v, "ъ$", "") v = rsub(v, "^ъ", "") v = rsub(v, "(%A)ъ", "%1") v = rsub(v, "ъ(%A)", "%1") v = rsub(v, "і", "и") v = rsub(v, "ѣ", "е") return v end -- Meaning of entry is: -- 1. The declension name in module ru-adjective -- 2. The masculine declension name in this module -- 3. The neuter declension name in this module -- 4. The feminine declension name in this module -- 5. The value of hard= for the declensions_cat entry -- 6. The value of decl= for the declensions_cat entry -- 7. The value of possadj= for the declensions_cat entry (true if possessive -- or similar type of adjective) local adj_decl_map = { {"ый", "ый", "ое", "ая", "hard", "long", false}, {"ій", "ій", "ее", "яя", "soft", "long", false}, {"ой", "ой", "о́е", "а́я", "hard", "long", false}, {"ьій", "ьій", "ье", "ья", "palatal", "long", true}, {"short", "ъ-short", "о-short", "а-short", "hard", "short", true}, {"mixed", "ъ-mixed", "о-mixed", "а-mixed", "hard", "mixed", true}, {"proper", "ъ-proper", "о-proper", "а-proper", "hard", "proper", true}, {"stressed-short", "ъ-stressed-short", "о-stressed-short", "а-stressed-short", "hard", "short", true}, {"stressed-proper", "ъ-stressed-proper", "о-stressed-proper", "а-stressed-proper", "hard", "proper", true}, } local function get_adjectival_decl(adjtype, gender, old) local decl, intnotes = m_ru_adj.get_nominal_decl(adjtype, gender, old) -- hack fem ins_sg to insert <insa>, <insb>; see concat_word_forms_1() if gender == "f" and type(decl["ins_sg"]) == "table" and #decl["ins_sg"] == 2 then decl["ins_sg"][1] = decl["ins_sg"][1] .. "<insa>" decl["ins_sg"][2] = decl["ins_sg"][2] .. "<insb>" end return decl, intnotes end for _, declspec in ipairs(adj_decl_map) do local oadjdecl = declspec[1] local nadjdecl = old_to_new(oadjdecl) local odecl_by_gender = {m="+" .. declspec[2], n="+" .. declspec[3], f="+" .. declspec[4]} local hard = declspec[5] local decltype = declspec[6] local possadj = declspec[7] for _, g in ipairs({"m", "n", "f"}) do local odecl = odecl_by_gender[g] local ndecl = old_to_new(odecl) declensions_old[odecl], internal_notes_table_old[odecl] = get_adjectival_decl(oadjdecl, g, true) declensions[ndecl], internal_notes_table[ndecl] = get_adjectival_decl(nadjdecl, g, false) declensions_old_cat[odecl] = { decl=decltype, hard=hard, g=g, adj=true, possadj=possadj } declensions_cat[ndecl] = { decl=decltype, hard=hard, g=g, adj=true, possadj=possadj } end end -- Set up some aliases. declensions_old_aliases["+о́-short"] = "+о-stressed-short" declensions_old_aliases["+а́-short"] = "+а-stressed-short" declensions_old_aliases["+о́-proper"] = "+о-stressed-proper" declensions_old_aliases["+а́-proper"] = "+а-stressed-proper" declensions_aliases["+#-short"] = "+-short" declensions_aliases["+#-mixed"] = "+-mixed" declensions_aliases["+#-proper"] = "+-proper" declensions_aliases["+#-stressed-short"] = "+-stressed-short" declensions_aliases["+#-stressed-proper"] = "+-stressed-proper" -------------------------------------------------------------------------- -- Populate new from old -- -------------------------------------------------------------------------- -- Function to convert an entry in an old declensions table to new. local function old_decl_entry_to_new(v) if not v then return nil elseif type(v) == "table" then local new_entry = {} for _, i in ipairs(v) do table.insert(new_entry, old_decl_entry_to_new(i)) end return new_entry elseif type(v) == "function" then return function(stem, suffix, args) return old_decl_entry_to_new(v(stem, suffix, args)) end else return old_to_new(v) end end -- Function to convert an old declensions table to new. local function old_decl_to_new(odecl) local ndecl = {} for k, v in pairs(odecl) do ndecl[k] = old_decl_entry_to_new(v) end return ndecl end -- Function to convert an entry in an old declensions_cat table to new. local function old_decl_cat_entry_to_new(odecl_cat_entry) if not odecl_cat_entry then return nil elseif type(odecl_cat_entry) == "function" then return function(suffix) return old_decl_cat_entry_to_new(odecl_cat_entry(suffix)) end elseif type(odecl_cat_entry) == "table" then local ndecl_cat_entry = {} for k, v in pairs(odecl_cat_entry) do ndecl_cat_entry[k] = old_decl_cat_entry_to_new(v) end return ndecl_cat_entry elseif type(odecl_cat_entry) == "boolean" then return odecl_cat_entry else assert(type(odecl_cat_entry) == "string") return old_to_new(odecl_cat_entry) end end -- Function to convert an old declensions_cat table to new. local function old_decl_cat_to_new(odeclcat) local ndeclcat = {} for k, v in pairs(odeclcat) do ndeclcat[k] = old_decl_cat_entry_to_new(v) end return ndeclcat end -- populate declensions[] from declensions_old[] for odecltype, odecl in pairs(declensions_old) do local ndecltype = old_to_new(odecltype) if not declensions[ndecltype] then declensions[ndecltype] = old_decl_to_new(odecl) end end -- populate declensions_cat[] from declensions_old_cat[] for odecltype, odeclcat in pairs(declensions_old_cat) do local ndecltype = old_to_new(odecltype) if not declensions_cat[ndecltype] then declensions_cat[ndecltype] = old_decl_cat_to_new(odeclcat) end end -- populate declensions_aliases[] from declensions_old_aliases[] for ofrom, oto in pairs(declensions_old_aliases) do local from = old_to_new(ofrom) if not declensions_aliases[from] then declensions_aliases[from] = old_to_new(oto) end end -- populate internal_notes_table[] from internal_notes_table_old[] for odecl, note in pairs(internal_notes_table_old) do local ndecl = old_to_new(odecl) if not internal_notes_table[ndecl] then -- FIXME, should we be calling old_to_new() here? internal_notes_table[ndecl] = note end end -------------------------------------------------------------------------- -- Inflection functions -- -------------------------------------------------------------------------- -- Attach the stressed stem (or plural stem, or barestem) out of ARGS -- to the unstressed suffix SUF, modifying the suffix as necessary for the -- last letter of the stem (e.g. if it is velar, sibilant or ц). CASE is -- the case form being created and is used to select the plural stem if -- needed. Returns two values, the combined form and the modified suffix. local function attach_unstressed(args, case, suf, was_stressed) if suf == nil then return nil, nil elseif rfind(suf, CFLEX) then -- if suf has circumflex accent, it forces stressed return attach_stressed(args, case, suf) end local stem, tr if rfind(case, "_pl") then stem, tr = args.pl, args.pltr end if not stem and case == "ins_sg" then stem, tr = args.ins_sg_stem, args.ins_sg_tr end if not stem then stem, tr = args.stem, args.stemtr end if nom.nonsyllabic_suffixes[suf] then -- If gen_pl, use special args.gen_pl_bare if given, else regular -- args.bare if there isn't a plural stem. If nom_sg, always use -- regular args.bare. local barearg, bareargtr if case == "gen_pl" then barearg, bareargtr = args.gen_pl_bare, args.gen_pl_baretr if not barearg and args.pl == args.stem then barearg, bareargtr = args.bare, args.baretr end else barearg, bareargtr = args.bare, args.baretr end local barestem = barearg or stem local barestem, baretr if barearg then barestem, baretr = barearg, bareargtr else barestem, baretr = stem, tr end if was_stressed and case == "gen_pl" then if not barearg then local gen_pl_stem, gen_pl_tr = com.make_ending_stressed(stem, tr) barestem, baretr = gen_pl_stem, gen_pl_tr end end if rlfind(barestem, "[йьъ]$") then suf = "" else if suf == "ъ" then -- OK elseif suf == "й" or suf == "ь" then if barearg and case == "gen_pl" then -- explicit bare or reducible, don't add -ь suf = "" elseif rfind(barestem, "[" .. com.vowel .. "][" .. AC .. GR .. "]?$") then -- not reducible, do add -ь and correct to -й if necessary suf = "й" else suf = "ь" end end end return com.concat_russian_tr(barestem, baretr, suf, nil, "dopair"), suf end suf = com.make_unstressed(suf) local rules = nom.unstressed_rules[ulower(usub(stem, -1))] return nom.combine_stem_and_suffix(stem, tr, suf, rules, args.old) end -- Analogous to attach_unstressed() but for the unstressed stem and a -- stressed suffix. attach_stressed = function(args, case, suf) if suf == nil then return nil, nil end -- circumflex forces stress even when the accent pattern calls for no stress suf = rsub(suf, "̂", AC) if com.is_unstressed(suf) then return attach_unstressed(args, case, suf, "was stressed") end local stem, tr if rfind(case, "_pl") then stem, tr = args.upl, args.upltr end if not stem then stem, tr = args.ustem, args.ustemtr end local rules = nom.stressed_rules[ulower(usub(stem, -1))] return nom.combine_stem_and_suffix(stem, tr, suf, rules, args.old) end -- Attach the appropriate stressed or unstressed stem (or plural stem as -- determined by CASE, or barestem) out of ARGS to the suffix SUF, which may -- be a list of alternative suffixes (e.g. in the inst sg of feminine nouns). -- Calls FUN (either attach_stressed() or attach_unstressed()) to do the work -- for an individual suffix. Returns two values, a list of combined forms -- and a list of the real suffixes used (which may be modified from the -- passed-in suffixes, e.g. by removing stress marks or modifying vowels in -- various ways after a stem-final velar, sibilant or ц). Each combined form -- is a two-element list {stem, tr} (or a one-element list if tr is nil). -- IRREG is true if this is an irregular form. We are handling the Nth word; -- ISLAST is true if this is the last one. local function attach_with(args, case, suf, fun, irreg, n, islast) if type(suf) == "table" then local all_combineds = {} local all_realsufs = {} for _, x in ipairs(suf) do local combineds, realsufs = attach_with(args, case, x, fun, irreg, n, islast) for _, combined in ipairs(combineds) do table.insert(all_combineds, combined) end for _, realsuf in ipairs(realsufs) do table.insert(all_realsufs, realsuf) end end return all_combineds, all_realsufs else local combined, realsuf = fun(args, case, suf) local irregsuf = irreg and {IRREGMARKER} or {""} return {combined and com.concat_paired_russian_tr( com.concat_paired_russian_tr(args["prefix" .. n], combined), com.concat_paired_russian_tr(args["suffix" .. n], irregsuf)) or nil}, {realsuf and realsuf .. args["suffix" .. n][1] or nil} end end -- Generate the form(s) and suffix(es) for CASE according to the declension -- table DECL, using the attachment function FUN (one of attach_stressed() -- or attach_unstressed()). IS_SLASH is true if this is a slash declension -- (different declensions for singular and plural). We are handling the Nth -- word; ISLAST is true if this is the last one. local function gen_form(args, decl, case, stress, fun, is_slash, n, islast) local irreg = false if not args.suffixes[case] then args.suffixes[case] = {} end local decl_sufs = args.old and declensions_old or declensions decl_sufs = decl_sufs[decl] local suf = decl_sufs[case] local decl_cats = args.old and declensions_old_cat or declensions_cat local ispl = rfind(case, "_pl") if ispl and (decl_cats[decl].irregpl or args.pl and args.pl ~= args.stem or is_slash) then irreg = true end if case == "nom_pl" and decl_cats[decl].alt_nom_pl then irreg = true end if type(suf) == "function" then suf = suf(ispl and args.pl or args.stem, stress, args) end if case == "gen_pl" and args.alt_gen_pl then suf = decl_sufs.alt_gen_pl irreg = true if not suf then error("No alternative genitive plural available for this declension class") end end local combineds, realsufs = attach_with(args, case, suf, fun, irreg, n, islast) for _, realsuf in ipairs(realsufs) do args.any_non_nil[case] = true args.this_any_non_nil[case] = true insert_if_not(args.suffixes[case], realsuf) end return combineds end local attachers = { ["+"] = attach_stressed, ["-"] = attach_unstressed, } do_stress_pattern = function(stress, args, decl, number, n, islast) local f = {} for _, case in ipairs(decl_cases) do if not number or (number == "sg" and rfind(case, "_sg")) or (number == "pl" and rfind(case, "_pl")) then f[case] = gen_form(args, decl, case, stress, attachers[stress_patterns[stress][case]], not not number, n, islast) -- Turn empty form lists into nil to facilitate computation of -- animate/inanimate accusatives below if f[case] and #f[case] == 0 then f[case] = nil end -- Compute linked versions of potential lemma cases, for use -- in the ru-noun+ headword. We substitute the original lemma -- (before removing links) for forms that are the same as the -- lemma, if the original lemma has links. if f[case] and (case == "nom_sg" or case == "nom_pl") then local linked_forms = {} for _, form in ipairs(f[case]) do -- Return true if FORM is "close enough" to LEMMA that we can substitute the -- linked form of the lemma. Currently this means exactly the same except that -- we ignore acute and grave accent differences in monosyllables, and ignore -- notes that may have been appended (e.g. the triangle marking irregularity). local entry, notes = m_table_tools.separate_notes(form[1]) local lemma = args.lemma_no_links local close_enough_to_lemma = entry == lemma or (com.is_monosyllabic(entry) and com.is_monosyllabic(lemma) and com.remove_accents(entry) == com.remove_accents(lemma)) if close_enough_to_lemma and rfind(args.orig_lemma, "%[%[") then table.insert(linked_forms, {args.orig_lemma .. notes, args.lemmatr and args.lemmatr .. notes}) else table.insert(linked_forms, form) end end f[case .. "_linked"] = linked_forms end end end -- Set acc an/in variants now as appropriate. We used to do this in -- handle_forms_and_overrides(), which simplified the handling of -- nom/gen/acc overrides but caused problems for words like мазло and -- трепло that had a mixture of nil and non-nil accusative forms. local an = args.thisa if not number or number == "sg" then f.acc_sg_an = f.acc_sg_an or f.acc_sg or an == "i" and f.nom_sg or f.gen_sg f.acc_sg_in = f.acc_sg_in or f.acc_sg or an == "a" and f.gen_sg or f.nom_sg end if not number or number == "pl" then f.acc_pl_an = f.acc_pl_an or f.acc_pl or an == "i" and f.nom_pl or f.gen_pl f.acc_pl_in = f.acc_pl_in or f.acc_pl or an == "a" and f.gen_pl or f.nom_pl end for case, forms in pairs(f) do if not args.forms[case] then args.forms[case] = {} end for _, form in ipairs(forms) do insert_if_not(args.forms[case], form) end end end stress_patterns["a"] = { nom_sg="-", gen_sg="-", dat_sg="-", acc_sg="-", ins_sg="-", pre_sg="-", nom_pl="-", gen_pl="-", dat_pl="-", acc_pl="-", ins_pl="-", pre_pl="-", } stress_patterns["b"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="+", ins_sg="+", pre_sg="+", nom_pl="+", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["b'"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="+", ins_sg="-", pre_sg="+", nom_pl="+", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["c"] = { nom_sg="-", gen_sg="-", dat_sg="-", acc_sg="-", ins_sg="-", pre_sg="-", nom_pl="+", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["d"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="+", ins_sg="+", pre_sg="+", nom_pl="-", gen_pl="-", dat_pl="-", acc_pl="-", ins_pl="-", pre_pl="-", } stress_patterns["d'"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="-", ins_sg="+", pre_sg="+", nom_pl="-", gen_pl="-", dat_pl="-", acc_pl="-", ins_pl="-", pre_pl="-", } stress_patterns["e"] = { nom_sg="-", gen_sg="-", dat_sg="-", acc_sg="-", ins_sg="-", pre_sg="-", nom_pl="-", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["f"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="+", ins_sg="+", pre_sg="+", nom_pl="-", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["f'"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="-", ins_sg="+", pre_sg="+", nom_pl="-", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["f''"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="+", ins_sg="-", pre_sg="+", nom_pl="-", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } ending_stressed_gen_pl_patterns = m_table.listToSet({"b", "b'", "c", "e", "f", "f'", "f''"}) ending_stressed_pre_sg_patterns = m_table.listToSet({"b", "b'", "d", "d'", "f", "f'", "f''"}) ending_stressed_dat_sg_patterns = ending_stressed_pre_sg_patterns ending_stressed_sg_patterns = ending_stressed_pre_sg_patterns ending_stressed_pl_patterns = m_table.listToSet({"b", "b'", "c"}) local numbers = { ["s"] = "singular", ["p"] = "plural", } local old_title_temp = [=[Pre-reform declension of <b lang="ru" class="Cyrl">{lemma}</b>]=] local title_temp = [=[Declension of <b lang="ru" class="Cyrl">{lemma}</b>]=] local extra_case_template, extra_case_template_with_plural local internal_notes_template local notes_template local templates = {} -- Convert a raw override into a canonicalized list of individual overrides. -- If input is nil, so is output. Certain junk (e.g. <br/>) is removed, -- and ~ and ~~ are substituted appropriately; ARGS and CASE are required for -- this purpose. FORMS is the list of existing case forms and is currently -- used only to retrieve the dat_sg for handling + in loc/par (this means -- that any overrides of the dat_sg have to be handled before handling -- overrides of loc/par). N is the suffix used to retrieve the override -- -- either a number for word-specific overrides, or an empty string for -- overall overrides. -- -- It will still be necessary to call m_table_tools.separate_notes() to separate -- off any trailing "notes" (asterisks, superscript numbers, etc.), and -- m_links.remove_links() to remove any links to get the raw override form. canonicalize_override = function(args, case, forms, n) local val = args[case .. n] if not val then return nil end -- clean <br /> that's in many multi-form entries and messes up linking val = rsub(val, "<br%s*/>", "") -- substitute ~ and ~~ and split by commas local stem, manualtr if rfind(case, "_pl") then stem, manualtr = args.pl, args.pltr end if not stem then stem, manualtr = args.stem, args.stemtr end local ustem, umanualtr = com.make_unstressed_once(stem, manualtr) local stemtr, ustemtr = manualtr, umanualtr local vals = rsplit(val, "%s*,%s*") local retvals = {} for _, val in ipairs(vals) do local valru, valtr = com.split_russian_tr(val) valru = rsub(valru, "~~", ustem) valru = rsub(valru, "~", com.is_stressed(val) and ustem or stem) if rfind(valru, "^%*") then valru = rsub(valru, "^%*", "") .. HYPMARKER end if valtr then stemtr = stemtr or com.translit_no_links(stem) ustemtr = ustemtr or com.translit_no_links(ustem) valtr = rsub(valtr, "~~", ustemtr) valtr = rsub(valtr, "~", com.is_stressed(val) and ustemtr or stemtr) elseif val:find("~") and manualtr then valtr = com.translit_no_links(val) valtr = rsub(valtr, "~~", umanualtr) valtr = rsub(valtr, "~", com.is_stressed(val) and umanualtr or manualtr) end if valtr then if valtr:sub(1, 1) == "*" then valtr = valtr:sub(2) .. HYPMARKER end valtr = com.j_correction(valtr) end table.insert(retvals, {valru, valtr}) end vals = retvals -- handle + in loc/par meaning "the expected form"; NOTE: This requires -- that dat_sg has already been processed! if case == "loc" or case == "par" then local new_vals = {} for _, rutr in ipairs(vals) do -- don't just handle + by itself in case the arg has в or на -- or whatever attached to it if rfind(rutr[1], "^%+") or rfind(rutr[1], "[%s%[|]%+") then for _, dat in ipairs(forms["dat_sg"]) do local ru, tr = rutr[1], rutr[2] local datru, dattr = dat[1], dat[2] local valru, valtr -- separate off any footnote symbols (which may have been -- introduced by a *tail or *tailall argument, which we need -- to process before this stage so overrides don't get -- automatically marked with footnote symbols); if par, -- try to preserve them, but with loc this can cause -- problems in case there are multiple dative forms -- (stress variants) and the last one is marked with a -- footnote symbol (occurs in грудь) local datru_entry, datru_notes = m_table_tools.separate_notes(datru) local dattr_entry, dattr_notes if dattr then dattr_entry, dattr_notes = m_table_tools.separate_notes(dattr) end if case == "par" then valru, valtr = datru_entry, dattr_entry else valru, valtr = com.make_ending_stressed(datru_entry, dattr_entry) datru_notes = "" dattr_notes = "" end -- wrap the word in brackets so it's linked; but not if it -- appears to already be linked ru = rsub(ru, "^%+", "[[" .. valru .. "]]") ru = rsub(ru, "([%[|])%+", "%1" .. valru) ru = rsub(ru, "(%s)%+", "%1[[" .. valru .. "]]") ru = ru .. datru_notes -- do the translit; but it shouldn't have brackets in it if tr or valtr then tr = tr or com.translit_no_links(rutr[1]) valtr = valtr or com.translit_no_links(valru) tr = rsub(tr, "^%+", valtr) tr = rsub(tr, "(%s)%+", "%1" .. valtr) tr = tr .. dattr_notes end table.insert(new_vals, {ru, tr}) end else table.insert(new_vals, rutr) end end vals = new_vals end -- auto-accent/check for necessary accents local newvals = {} for _, v in ipairs(vals) do local ru, tr = v[1], v[2] if not args.allow_unaccented then if tr and com.is_unstressed(ru) ~= com.is_unstressed(tr) then error("Override " .. ru .. " and translit " .. tr .. " must have same accent pattern") end -- it's safe to accent monosyllabic stems if com.is_monosyllabic(ru) then ru, tr = com.make_ending_stressed(ru, tr) elseif com.needs_accents(ru) then error("Override " .. ru .. " for case " .. case .. n .. " requires an accent") end end table.insert(newvals, {ru, tr}) end vals = newvals return vals end local function process_overrides(args, f, n) local function process_override(case) if args[case .. n] then local overrides = canonicalize_override(args, case, f, n) if not f[case] then f[case] = {} end local new_overrides = {} for _, form in ipairs(overrides) do -- Don't consider overrides of loc/par/voc irregular since -- they're only specified through overrides; FIXME: Theoretically -- we could consider loc/par irregular if they don't follow the -- expected forms; but we'd have to figure out how to eliminate -- the preposition that may be specified if not overridable_only_cases_set[case] and not args.manual and not contains_rutr_pair(f[case], form) then local formru, formnotes = m_table_tools.separate_notes(form[1]) if formru ~= "-" then -- don't mark an override of - as irregular, even if -- it has an attached footnote symbol form = com.concat_paired_russian_tr(form, {IRREGMARKER}) end end if case == "pauc" then -- Internal note indicating that the form is for numbers 2, 3 and 4. form = com.concat_paired_russian_tr(form, {paucal_marker}) end table.insert(new_overrides, form) end f[case] = new_overrides args.any_overridden[case] = true end end -- do dative singular first because it will be used by loc/par process_override("dat_sg") -- now do the rest for _, case in ipairs(overridable_cases) do if case ~= "dat_sg" then process_override(case) end end -- if the nominative is overridden, use it to set the linked version unless -- that is also specifically overridden if args["nom_sg" .. n] and not args["nom_sg_linked" .. n] then f.nom_sg_linked = f.nom_sg end if args["nom_pl" .. n] and not args["nom_pl_linked" .. n] then f.nom_pl_linked = f.nom_pl end -- convert empty lists to nil to facilitate computation of accusative -- case variants below. for _, case in ipairs(all_cases) do if f[case] then if type(f[case]) ~= "table" then error("Logic error, args[case] should be nil or table") end if #f[case] == 0 then f[case] = nil end end end end local function process_tail_args(args, f, n) local function append_note_all(case, value) value = com.split_russian_tr(value, "dopair") local function append1(case) if f[case] then for i=1,#f[case] do f[case][i] = com.concat_paired_russian_tr(f[case][i], value) end end end append1(case) if case == "acc_sg" then append1("acc_sg_in") append1("acc_sg_an") elseif case == "acc_pl" then append1("acc_pl_in") append1("acc_pl_an") end end local function append_note_last(case, value, gt_one) value = com.split_russian_tr(value, "dopair") local function append1(case) if f[case] then local lastarg = #f[case] if lastarg > (gt_one and 1 or 0) then f[case][lastarg] = com.concat_paired_russian_tr(f[case][lastarg], value) end end end append1(case) if case == "acc_sg" then append1("acc_sg_in") append1("acc_sg_an") elseif case == "acc_pl" then append1("acc_pl_in") append1("acc_pl_an") end end local function handle_tail_hyp(suf) local arg, val for _, case in ipairs(all_cases) do arg = args[case .. "_" .. suf .. n] if arg then append_note_last(case, suf == "hyp" and HYPMARKER or arg) end arg = args[case .. "_" .. suf .. "all" .. n] if arg then append_note_all(case, suf == "hyp" and HYPMARKER or arg) end if not rfind(case, "_pl") then arg = args["sg" .. suf .. "all" .. n] if arg then append_note_all(case, suf == "hyp" and HYPMARKER or arg) end arg = args["sg" .. suf .. n] if arg then append_note_last(case, suf == "hyp" and HYPMARKER or arg, ">1") end else arg = args["pl" .. suf .. "all" .. n] if arg then append_note_all(case, suf == "hyp" and HYPMARKER or arg) end arg = args["pl" .. suf .. n] if arg then append_note_last(case, suf == "hyp" and HYPMARKER or arg, ">1") end end if not rfind(case, "nom_") and not rfind(case, "acc_") then arg = args["obl" .. suf .. "all" .. n] if arg then append_note_all(case, suf == "hyp" and HYPMARKER or arg) end arg = args["obl" .. suf .. n] if arg then append_note_last(case, suf == "hyp" and HYPMARKER or arg, ">1") end end end end handle_tail_hyp("tail") handle_tail_hyp("hyp") end handle_forms_and_overrides = function(args, n, islast) local f = args.forms process_tail_args(args, f, n) process_overrides(args, f, n) local an = args.thisa -- Maybe set the value of the animate/inanimate accusative variants based -- on nom/acc/gen overrides. Don't do this if there was a specific override -- of this form. Otherwise, always propagate an accusative singular -- override, and propagate the nom/gen sg if there wasn't a specific -- accusative suffix anywhere (occurs in the fem sg and sometimes the neut -- sg). This logic duplicates logic in handle_overall_forms_and_overrides(); -- see long comment there. We need to duplicate the whole logic here to -- handle words like мать-одиночка, which has an override of acc_sg1. if not args["acc_sg_an" .. n] then if args["acc_sg" .. n] then f.acc_sg_an = f.acc_sg elseif not args.this_any_non_nil.acc_sg then f.acc_sg_an = f.acc_sg or an == "i" and f.nom_sg or f.gen_sg or f.acc_sg_an end end if not args["acc_sg_in" .. n] then if args["acc_sg" .. n] then f.acc_sg_in = f.acc_sg elseif not args.this_any_non_nil.acc_sg then f.acc_sg_in = f.acc_sg or an == "a" and f.gen_sg or f.nom_sg or f.acc_sg_in end end if not args["acc_pl_an" .. n] then if args["acc_pl" .. n] then f.acc_pl_an = f.acc_pl elseif not args.this_any_non_nil.acc_pl then f.acc_pl_an = f.acc_pl or an== "i" and f.nom_pl or f.gen_pl or f.acc_pl_an end end if not args["acc_pl_in" .. n] then if args["acc_pl" .. n] then f.acc_pl_in = f.acc_pl elseif not args.this_any_non_nil.acc_pl then f.acc_pl_in = f.acc_pl or an == "a" and f.gen_pl or f.nom_pl or f.acc_pl_in end end f.loc = f.loc or f.pre_sg f.par = f.par or f.gen_sg f.voc = f.voc or f.nom_sg -- Set these in case we have plural only, in which case the -- singular will also get set to these same values in case we are -- a plural-only word in a singular-only expression. f.loc_pl = f.loc_pl or f.pre_pl f.par_pl = f.par_pl or f.gen_pl f.voc_pl = f.voc_pl or f.nom_pl f.count = f.count or f.gen_pl f.pauc = f.pauc or f.gen_sg local nu = args.thisn -- If we have a singular-only, set the plural forms to the singular forms, -- and vice-versa. This is important so that things work in multi-word -- expressions that combine different number restrictions (e.g. -- singular-only with singular/plural or singular-only with plural-only, -- compare "St. Vincent and the Grenadines" [Сент-Винсент и Гренадины]). if nu == "s" then f.nom_pl_linked = f.nom_sg_linked f.nom_pl = f.nom_sg f.gen_pl = f.gen_sg f.dat_pl = f.dat_sg f.acc_pl = f.acc_sg f.acc_pl_an = f.acc_sg_an f.acc_pl_in = f.acc_sg_in f.ins_pl = f.ins_sg f.pre_pl = f.pre_sg f.nom_pl = f.nom_sg f.loc_pl = f.loc f.par_pl = f.par f.voc_pl = f.voc elseif nu == "p" then f.nom_sg_linked = f.nom_pl_linked f.nom_sg = f.nom_pl f.gen_sg = f.gen_pl f.dat_sg = f.dat_pl f.acc_sg = f.acc_pl f.acc_sg_an = f.acc_pl_an f.acc_sg_in = f.acc_pl_in f.ins_sg = f.ins_pl f.pre_sg = f.pre_pl f.nom_sg = f.nom_pl f.loc = f.loc_pl f.par = f.par_pl f.voc = f.voc_pl end end handle_overall_forms_and_overrides = function(args) local overall_forms = {} for _, case in ipairs(displayable_cases) do overall_forms[case] = concat_word_forms(args.per_word_info, case) end local acc_sg_overridden = args.acc_sg local acc_pl_overridden = args.acc_pl process_tail_args(args, overall_forms, "") process_overrides(args, overall_forms, "") if case_will_be_displayed(args, "pauc") then insert_if_not(args.internal_notes, paucal_internal_note) end -- if IRREGMARKER is anywhere in text, remove all instances and put -- at the end before any notes. local function clean_irreg_marker(case, text) if rfind(text, IRREGMARKER) then text = rsub(text, IRREGMARKER, "") local entry, notes = m_table_tools.separate_notes(text) if case_will_be_displayed(args, case) then insert_if_not(args.internal_notes, IRREGMARKER .. " Irregular.") args.any_irreg = true args.any_irreg_case[case] = true end return entry .. IRREGMARKER .. notes else return text end end -- set final args[case] and clean up IRREGMARKER. for _, case in ipairs(all_cases) do args[case] = overall_forms[case] if args[case] then local cleaned_forms = {} for _, form in ipairs(args[case]) do local ru, tr = form[1], form[2] ru = clean_irreg_marker(case, ru) if tr then tr = clean_irreg_marker(case, tr) end table.insert(cleaned_forms, {ru, tr}) end args[case] = cleaned_forms end end -- Maybe set the value of the animate/inanimate accusative variants based -- on nom/acc/gen overrides. Don't do this if there was a specific override -- of this form. Otherwise, always propagate an accusative singular -- override, and propagate the nom/gen sg if there wasn't a specific -- accusative suffix anywhere (occurs in the fem sg and sometimes the neut -- sg). We need to do this somewhat complicated procedure to get overrides -- to work correctly, e.g. acc_sg overrides of feminine and neuter nouns -- (such as мать and полслова) and gen_sg/gen_pl overrides of masculine -- animate nouns. We also ran into an issue with words like мазло that are -- neuter-form but can be both masculine animate (in which case the -- acc sg inherits from the genitive) and neuter animate (in which case the -- acc sg has the fixed ending -о, same as nominative, despite the animacy). -- Remember also that the animate/inanimate accusative variants are the ones -- displayed, not the plain acc_sg/acc_pl ones. if not args.any_overridden.acc_sg_an then if acc_sg_overridden then args.acc_sg_an = args.acc_sg elseif not args.any_non_nil.acc_sg then args.acc_sg_an = args.acc_sg or args.a == "i" and args.nom_sg or args.gen_sg or args.acc_sg_an end end if not args.any_overridden.acc_sg_in then if acc_sg_overridden then args.acc_sg_in = args.acc_sg elseif not args.any_non_nil.acc_sg then args.acc_sg_in = args.acc_sg or args.a == "a" and args.gen_sg or args.nom_sg or args.acc_sg_in end end if not args.any_overridden.acc_pl_an then if acc_pl_overridden then args.acc_pl_an = args.acc_pl elseif not args.any_non_nil.acc_pl then args.acc_pl_an = args.acc_pl or args.a == "i" and args.nom_pl or args.gen_pl or args.acc_pl_an end end if not args.any_overridden.acc_pl_in then if acc_pl_overridden then args.acc_pl_in = args.acc_pl elseif not args.any_non_nil.acc_pl then args.acc_pl_in = args.acc_pl or args.a == "a" and args.gen_pl or args.nom_pl or args.acc_pl_in end end -- Try to set the values of acc_sg and acc_pl. The only time we can't is -- when the noun is bianimate and the anim/inan values are different. -- This is used primarily for generate_forms() and generate_multi_forms(), -- since we don't actually display these forms. if args.a == "a" then args.acc_sg = args.acc_sg or args.acc_sg_an args.acc_pl = args.acc_pl or args.acc_pl_an elseif args.a == "i" then args.acc_sg = args.acc_sg or args.acc_sg_in args.acc_pl = args.acc_pl or args.acc_pl_in else -- bianimate args.acc_sg = args.acc_sg or m_table.deepEquals(args.acc_sg_in, args.acc_sg_an) and args.acc_sg_in or nil args.acc_pl = args.acc_pl or m_table.deepEquals(args.acc_pl_in, args.acc_pl_an) and args.acc_pl_in or nil end end -- Subfunction of concat_word_forms(), used to implement recursively -- generating all combinations of elements from WORD_FORMS (a list, one -- element per word, of a list of the forms for a word, each of which is a -- two-element list of {RUSSIAN, TR}) and TRAILING_FORMS (a list of forms, the -- accumulated suffixes for trailing words so far in the recursion process, -- again where each form is a two-element list {RUSSIAN, TR}). Each time we -- recur we take the last FORMS item off of WORD_FORMS and to each form in -- FORMS we add all elements in TRAILING_FORMS, passing the newly generated -- list of items down the next recursion level with the shorter WORD_FORMS. -- We end up returning a list of concatenated forms, where each list item -- is a two-element list {RUSSIAN, TR}. local function concat_word_forms_1(word_forms, trailing_forms) if #word_forms == 0 then local retforms = {} for _, form in ipairs(trailing_forms) do local ru, tr = form[1], form[2] -- Remove <insa> and <insb> markers; they've served their purpose. ru = rsub(ru, "<ins[ab]>", "") tr = tr and rsub(tr, "<ins[ab]>", "") table.insert(retforms, {ru, tr}) end return retforms else local last_form_info = table.remove(word_forms) local last_forms, joiner = last_form_info[1], last_form_info[2] local new_trailing_forms = {} for _, form in ipairs(last_forms) do for _, trailing_form in ipairs(trailing_forms) do -- If form to prepend is empty, don't add the joiner; this -- is principally used in overall overrides, where we stuff -- the entire override into the last word local full_form = form[1] == "" and trailing_form or com.concat_paired_russian_tr(form, com.concat_paired_russian_tr(joiner, trailing_form), "movenotes") if rfind(full_form[1], "<insa>") and rfind(full_form[1], "<insb>") then -- REJECT! So we don't get mixtures of the two feminine -- instrumental singular endings. else table.insert(new_trailing_forms, full_form) end end end return concat_word_forms_1(word_forms, new_trailing_forms) end end -- Generate a list of overall forms by concatenating the per-word forms. -- PER_WORD_INFO comes from args.per_word_info and is a list of -- WORD_INFO items, one per word, each of which a two element list of -- WORD_FORMS (a table listing the forms for each case) and JOINER (a string). -- We loop over all possible combinations of elements from each word's list -- of forms for the given case; this requires recursion. concat_word_forms = function(per_word_info, case) local word_forms = {} -- Gather the appropriate word forms. We have to recreate this anew -- because it will be destructively modified by concat_word_forms_1(). for _, word_info in ipairs(per_word_info) do table.insert(word_forms, {word_info[1][case], word_info[2]}) end -- We need to start the recursion with the second parameter containing -- one blank element rather than no elements, otherwise no elements -- will be propagated to the next recursion level. return concat_word_forms_1(word_forms, {{""}}) end local accel_forms = { nom_sg = "nom|s", nom_sg_linked = "nom|s", nom_pl = "nom|p", nom_pl_linked = "nom|p", gen_sg = "gen|s", gen_pl = "gen|p", dat_sg = "dat|s", dat_pl = "dat|p", acc_sg_an = "an|acc|s", acc_pl_an = "an|acc|p", acc_sg_in = "in|acc|s", acc_pl_in = "in|acc|p", ins_sg = "ins|s", ins_pl = "ins|p", pre_sg = "pre|s", pre_pl = "pre|p", loc = "loc|s", loc_pl = "loc|p", voc = "voc|s", voc_pl = "voc|p", par = "par|s", par_pl = "par|p", count = "count form", pauc = "pau", } -- Make the table make_table = function(args) local data = {} data.after_title = " " .. args.heading data.number = args.nonumber and "" or numbers[args.n] local lemma_forms = args[args.n == "p" and "nom_pl" or "nom_sg"] data.lemma = nom.show_form(args.explicit_lemma and {{args.explicit_lemma, args.explicit_lemmatr}} or args[args.n == "p" and "nom_pl_linked" or "nom_sg_linked"], "lemma", nil, nil) data.title = args.title or format(args.old and old_title_temp or title_temp, data) local sg_an_in_equal = m_table.deepEquals(args.acc_sg_an, args.acc_sg_in) local pl_an_in_equal = m_table.deepEquals(args.acc_pl_an, args.acc_pl_in) for _, case in ipairs(displayable_cases) do local accel_form = accel_forms[case] if not accel_form then error("Something wrong, can't find accelerator form for " .. case) end if (sg_an_in_equal and (case == "acc_sg_an" or case == "acc_sg_in") or pl_an_in_equal and (case == "acc_pl_an" or case == "acc_pl_in")) then accel_form = rsub(accel_form, "^[ai]n|", "") end if args.n == "p" then accel_form = rsub(accel_form, "|p$", "") end data[case] = nom.show_form(args[case], false, accel_form, lemma_forms, "remove monosyllabic accents only lemma") end local temp = nil if args.n == "s" then data.nom_x = data.nom_sg data.gen_x = data.gen_sg data.dat_x = data.dat_sg data.acc_x_an = data.acc_sg_an data.acc_x_in = data.acc_sg_in data.ins_x = data.ins_sg data.pre_x = data.pre_sg if sg_an_in_equal then temp = "one_number" else temp = "one_number_split_animacy" end elseif args.n == "p" then data.nom_x = data.nom_pl data.gen_x = data.gen_pl data.dat_x = data.dat_pl data.acc_x_an = data.acc_pl_an data.acc_x_in = data.acc_pl_in data.ins_x = data.ins_pl data.pre_x = data.pre_pl data.par = data.par_pl data.loc = data.loc_pl data.voc = data.voc_pl if pl_an_in_equal then temp = "one_number" else temp = "one_number_split_animacy" end else if pl_an_in_equal then temp = "both_numbers" elseif sg_an_in_equal then temp = "both_numbers_split_animacy_plural_only" else temp = "both_numbers_split_animacy" end end for _, extra_case in ipairs({ {"par", "partitive"}, {"loc", "locative"}, {"voc", "vocative"}, }) do local case, engcase, template = unpack(extra_case) local a = args.a local colspan = (a == "b" or a == "bi" or a == "both" or a == "ai" or a == "ia") and 2 or 1 if args.n ~= "s" and args.n ~= "p" and args.any_overridden[case .. "_pl"] then if not args.any_overridden[case] then data[case] = "" end template = extra_case_template_with_plural elseif args.n ~= "p" and args.any_overridden[case] or args.n == "p" and args.any_overridden[case .. "_pl"] then template = extra_case_template end if template then template = format(template(colspan), {case=case, engcase=engcase}) data[case .. "_clause"] = format(template, data) else data[case .. "_clause"] = "" end end for _, extra_case in ipairs({ {"count", "count form"}, {"pauc", "paucal"}, }) do local case, engcase, template = unpack(extra_case) local a = args.a local colspan = (a == "b" or a == "bi" or a == "both" or a == "ai" or a == "ia") and 2 or 1 if args.n ~= "p" and args.any_overridden[case] then template = extra_case_template end if template then template = format(template(colspan), {case=case, engcase=engcase}) data[case .. "_clause"] = format(template, data) else data[case .. "_clause"] = "" end end local notes = get_arg_chain(args, "notes", "notes") local all_notes = {} for _, note in ipairs(args.internal_notes) do -- Superscript footnote marker at beginning of note, similarly to what's -- done at end of forms. local symbol, entry = m_table_tools.get_initial_notes(note) table.insert(all_notes, symbol .. entry) end for _, note in ipairs(notes) do -- Here too. local symbol, entry = m_table_tools.get_initial_notes(note) table.insert(all_notes, symbol .. entry) end data.notes = table.concat(all_notes, "<br />") data.notes_clause = data.notes ~= "" and format(notes_template, data) or "" return format(templates[temp], data) end function extra_case_template(colspan) colspan = colspan or 1 return ([===[ ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=%s | {engcase} | {\op}{case}{\cl} | |-]===]):format(colspan) end function extra_case_template_with_plural(colspan) colspan = colspan or 1 return ([===[ ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=%s | {engcase} | {\op}{case}{\cl} | {\op}{case}_pl{\cl} |-]===]):format(colspan) end notes_template = [===[ <div style="width:100%;text-align:left;background:var(--wikt-palette-lightblue, #d9ebff);"> <div style="display:inline-block;text-align:left;padding-left:1em;padding-right:1em"> {notes} </div></div> ]===] local function template_prelude(min_width) min_width = min_width or "70" return rsub([===[ <div> <div class="NavFrame" data-toggle-category="declension" style="max-width:MINWIDTHem"> <div class="NavHead" style="background:var(--wikt-palette-lighterblue, #ebf4ff);">{title}<span style="font-weight:normal;">{after_title}</span>&nbsp;</div> <div class="NavContent"> {\op}| style="table-layout:fixed; text-align:center; max-width:MINWIDTHem; width:100%;" class="inflection inflection-table" |- class="rowgroup" ]===], "MINWIDTH", min_width) end local function template_postlude() return [===[|-{par_clause}{loc_clause}{voc_clause}{count_clause}{pauc_clause} |{\cl}{notes_clause}</div></div></div>]===] end templates["both_numbers"] = template_prelude("45") .. [===[ ! style="width:7em;background:var(--wikt-palette-lightblue, #d9ebff);" | ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | singular ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | plural |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | nominative | data-accel-col=1 | {nom_sg} | data-accel-col=2 | {nom_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | genitive | data-accel-col=1 | {gen_sg} | data-accel-col=2 | {gen_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | dative | data-accel-col=1 | {dat_sg} | data-accel-col=2 | {dat_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | accusative | data-accel-col=1 | {acc_sg_an} | data-accel-col=2 | {acc_pl_an} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | instrumental | data-accel-col=1 | {ins_sg} | data-accel-col=2 | {ins_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | prepositional | data-accel-col=1 | {pre_sg} | data-accel-col=2 | {pre_pl} ]===] .. template_postlude() templates["both_numbers_split_animacy"] = template_prelude("50") .. [===[ ! style="width:15em;background:var(--wikt-palette-lightblue, #d9ebff);" colspan=2 | ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | singular ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | plural |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | nominative | data-accel-col=1 | {nom_sg} | data-accel-col=2 | {nom_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | genitive | data-accel-col=1 | {gen_sg} | data-accel-col=2 | {gen_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | dative | data-accel-col=1 | {dat_sg} | data-accel-col=2 | {dat_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" rowspan=2 | accusative ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | animate | data-accel-col=1 | {acc_sg_an} | data-accel-col=2 | {acc_pl_an} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | inanimate | data-accel-col=1 | {acc_sg_in} | data-accel-col=2 | {acc_pl_in} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | instrumental | data-accel-col=1 | {ins_sg} | data-accel-col=2 | {ins_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | prepositional | data-accel-col=1 | {pre_sg} | data-accel-col=2 | {pre_pl} ]===] .. template_postlude() templates["both_numbers_split_animacy_plural_only"] = template_prelude("50") .. [===[ ! style="width:15em;background:var(--wikt-palette-lightblue, #d9ebff);" colspan=2 | ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | singular ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | plural |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | nominative | data-accel-col=1 | {nom_sg} | data-accel-col=2 | {nom_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | genitive | data-accel-col=1 | {gen_sg} | data-accel-col=2 | {gen_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | dative | data-accel-col=1 | {dat_sg} | data-accel-col=2 | {dat_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" rowspan=2 | accusative ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | animate | data-accel-col=1 rowspan=2 | {acc_sg_an} | data-accel-col=2 | {acc_pl_an} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | inanimate | data-accel-col=2 | {acc_pl_in} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | instrumental | data-accel-col=1 | {ins_sg} | data-accel-col=2 | {ins_pl} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | prepositional | data-accel-col=1 | {pre_sg} | data-accel-col=2 | {pre_pl} ]===] .. template_postlude() templates["one_number"] = template_prelude("30") .. [===[ ! style="width:7em;background:var(--wikt-palette-lightblue, #d9ebff);" | ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | {number} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | nominative | {nom_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | genitive | {gen_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | dative | {dat_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | accusative | {acc_x_an} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | instrumental | {ins_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | prepositional | {pre_x} ]===] .. template_postlude() templates["one_number_split_animacy"] = template_prelude("35") .. [===[ ! style="width:15em;background:var(--wikt-palette-lightblue, #d9ebff);" colspan=2 | ! style="background:var(--wikt-palette-lightblue, #d9ebff);" | {number} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | nominative | {nom_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | genitive | {gen_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | dative | {dat_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" rowspan=2 | accusative ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | animate | {acc_x_an} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" | inanimate | {acc_x_in} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | instrumental | {ins_x} |- ! style="background:var(--wikt-palette-lighterblue, #ebf4ff);" colspan=2 | prepositional | {pre_x} ]===] .. template_postlude() return export l4f28v6akyf5m5p3slq8zohc8q0rf6j Modül:ru-genel 828 1589236 5669922 2026-06-23T07:23:41Z MustafaCavlak 59368 imported from en 5669922 Scribunto text/plain local export = {} --[==[ intro: Author: Benwing; some very early work by Rua and Atitarev This module holds some commonly used functions for the Russian language. It's generally for use from other modules, not `#invoke`, although some functions can be invoked from a template (`export.iotation()`, `export.reduce_stem()`, `export.dereduce_stem()`, actually added to support calling from a bot script rather than from a user template; and `export.make_unstressed()`, called from {{tl|R:ru:Vasmer}}). There may be issues when invoking such functions from templates when transliteration is present, due to the need for the transliteration to be decomposed, as mentioned below (all strings from Wiktionary pages are normally in composed form). NOTE NOTE NOTE: All functions assume that transliteration (but not Russian) has had its acute and grave accents decomposed using `export.decompose()`. This is the first thing that should be done to all user-specified transliteration and any transliteration we compute that we expect to work with. ]==] local m_str_utils = require("Module:string utilities") local m_table_tools = require("Module:table tools") -- Prevents an infinite require loop since ru-translit requires a different function in this module. local m_ru_translit = require("Module:require when needed")("Module:ru-translit") local m_headword_utilities = require("Module:require when needed")("Module:headword utilities") local m_table = require("Module:require when needed")("Module:table") local gsplit = m_str_utils.gsplit local split = m_str_utils.split local toNFD = mw.ustring.toNFD local u = m_str_utils.char local ufind = mw.ustring.find local ugsub = mw.ustring.gsub local ulen = m_str_utils.len local ulower = m_str_utils.lower local umatch = mw.ustring.match local unpack = unpack or table.unpack -- Lua 5.2 compatibility local usub = m_str_utils.sub local insert = table.insert local concat = table.concat local AC = u(0x0301) -- acute = ́ local GR = u(0x0300) -- grave = ̀ local CFLEX = u(0x0302) -- circumflex = ̂ local BREVE = u(0x0306) -- breve ̆ local DIA = u(0x0308) -- diaeresis = ̈ local CARON = u(0x030C) -- caron ̌ local OGONEK = u(0x0328) -- ogonek ̨ local PSEUDOVOWEL = u(0xFFF1) -- pseudovowel placeholder local PSEUDOCONS = u(0xFFF2) -- pseudoconsonant placeholder -- any stress export.stress = AC .. GR -- regex for any optional stress(s) export.opt_stress = "[" .. export.stress .. "]*" -- any accent export.accent = export.stress .. DIA .. BREVE .. CARON .. OGONEK -- regex for any optional accent(s) export.opt_accent = "[" .. export.accent .. "]*" -- any composed Cyrillic vowel with grave accent export.composed_grave_vowel = "ѐЀѝЍ" -- any Cyrillic vowel except ёЁ export.vowel_no_jo = "аАеЕиИӥӤіІїЇоОуУыЫѣѢэЭюЮяЯѵѴ" .. PSEUDOVOWEL .. export.composed_grave_vowel -- any Cyrillic vowel, including ёЁ export.vowel = export.vowel_no_jo .. "ёЁ" -- any vowel in transliteration export.tr_vowel = "aAeEěĚɛƐiIoOǒǑǫǪuUyY" .. PSEUDOVOWEL -- any consonant in transliteration, omitting "j" and the soft/hard sign export.tr_cons_no_approx = "bBcCčČdDfFgGkKlLmMnNpPrRsSšŠtTvVxXzZžŽ" .. PSEUDOCONS -- any consonant in transliteration, including soft/hard sign export.tr_cons = export.tr_cons_no_approx .. "jJʹʺ" -- regex for any consonant in transliteration, including soft/hard sign, -- optionally followed by any accent export.tr_cons_acc_re = "[" .. export.tr_cons .. "]" .. export.opt_accent -- any Cyrillic consonant except sibilants and ц export.cons_except_sib_c = "бБвВгГдДзЗйЙкКлЛмМнНпПрРсСтТфФхХъЪьЬѳѲ" .. PSEUDOCONS -- Cyrillic sibilant consonants export.sib = "жЖчЧшШщЩ" -- Cyrillic sibilant consonants and ц export.sib_c = export.sib .. "цЦ" -- any Cyrillic consonant export.cons = export.cons_except_sib_c .. export.sib_c -- Cyrillic velar consonants export.velar = "гГкКхХ" -- uppercase Cyrillic export.uppercase = "АБВГДЕЀЁЖЗИЍӤІЇЙКЛМНОПРСТУФХЦЧШЩЪЫЬѢЭЮЯѲѴ" local function ine(x) return x ~= "" and x or nil end --[==[ Apply Proto-Slavic iotation. This is the change that is affected by a Slavic -j- after a consonant. `stem` is the Cyrillic stem and `tr` its optional manual translit. If `shch` is given, т iotates to щ instead of ч. Normally return two values, the iotated Cyrillic stem and corresponding transliteration (which will be {nil} if passed in as {nil}). If invoked from a template or bot, however, the return value will be a single string; if manual transliteration is present, the return value will be a string of the form `CYRILLIC//TRANSLIT` with two slahes separating the Cyrillic from the translit. ]==] function export.iotation(stem, tr, shch) local combine_tr = false -- so this can be called from a template (or usually, a bot) if type(stem) == "table" then stem, tr, shch = ine(stem.args[1]), ine(stem.args[2]), ine(stem.args[3]) combine_tr = true end stem = ugsub(stem, "[сх]$", "ш") stem = ugsub(stem, "с[кт]$", "щ") stem = ugsub(stem, "[кц]$", "ч") -- normally "т" is iotated as "ч" but there are many verbs that are iotated with "щ" if shch == "щ" then stem = stem:gsub("т$", "щ") else stem = stem:gsub("т$", "ч") end stem = ugsub(stem, "[гдз]$", "ж") stem = ugsub(stem, "[бвмпфѳ]$", "%0л") -- ѵ is equivalent to в after certain vowels stem = ugsub(stem, "[аАеЕѐЀиИѝЍӥӤіІїЇѣѢэЭяЯ]" .. export.opt_stress .. "ѵ$", "%0л") if tr then tr = tr:gsub("[sx]$", "š") :gsub("s[kt]$", "šč") :gsub("[kc]$", "č") -- normally "т" is iotated as "ч" but there are many verbs that are iotated with "щ" if shch == "щ" then tr = tr:gsub("t$", "šč") else tr = tr:gsub("t$", "č") end tr = tr:gsub("[dgz]$", "ž") :gsub("[bfmpv]$", "%0l") end if combine_tr then return export.combine_russian_tr(stem, tr) end return stem, tr end do -- A word needs accents if it is unstressed and contains more than one -- vowel, unless it's a prefix or suffix local function word_needs_accents(word) return not (word:sub(1, 1) == "-" or word:sub(-1) == "-") and export.is_unstressed(word) and not export.is_monosyllabic(word) end -- Does a set of Cyrillic words in connected text need accents? We need to -- split by word and check each one. function export.needs_accents(text) for word in gsplit(text, "%s+") do if word_needs_accents(word) then return true end end return false end end --[==[ True if either: # A vowel is marked with explicit primary stress. # The word has a single jo in it (which doesn't have secondary stress). This is because a word with multiple jos requires explicit stress to be marked, since we can't infer where primary stress lies. `Jo` can be `ё`, `ѣ̈`, or `я̈` (e.g. `сѣ̈дла`, plural of `сѣдло́`). ]==] function export.is_stressed(word) if word:find(AC) then return true end word = toNFD(word) return ( select(2, ugsub(word, "[еѣяЕѢЯ]" .. DIA, "")) == 1 and not umatch(word, "[еѣяЕѢЯ]" .. DIA .. GR) ) end --[==[ True if a Cyrillic word requires explicit stress. ]==] function export.is_unstressed(word) return not export.is_stressed(word) end --[==[ True if Cyrillic word is stressed on the last syllable. ]==] function export.is_ending_stressed(word) return ufind(word, "[ёЁ][^" .. export.vowel .. "]*$") or ufind(word, "[" .. export.vowel .. "][́̈][^" .. export.vowel .. "]*$") end do local function word_is_multi_stressed(word) -- Allow ё along with another stress, e.g. [[трёхэта́жный]], [[гёрлфре́нд]] or [[Са́мсё]]. return ufind(word, "[" .. export.vowel .. "][́̈].*[" .. export.vowel .. "][́̈]") end --[==[ True if a Cyrillic term has two or more stresses (acute or diaeresis) in a given word. ]==] function export.is_multi_stressed(text) for word in gsplit(text, "[%s-]+") do if word_is_multi_stressed(word) then return true end end return false end end --[==[ True if Cyrillic word is stressed on the first syllable. ]==] function export.is_beginning_stressed(word) return ufind(word, "^[^" .. export.vowel .. "]*[ёЁ]") or ufind(word, "^[^" .. export.vowel .. "]*[" .. export.vowel .. "]́") end --[==[ True if Cyrillic word has no vowel. Don't treat suffixes as nonsyllabic even if they have no vowel, as they are generally added onto words with vowels. ]==] function export.is_nonsyllabic(word) return not ufind(word, "^%-") and not ufind(word, "[" .. export.vowel .. "]") end --[==[ True if Cyrillic word has no more than one vowel; includes non-syllabic stems such as `льд-`. ]==] function export.is_monosyllabic(word) return not ufind(word, "[" .. export.vowel .. "].*[" .. export.vowel .. "]") end local recomposer = { -- Cyrillic letters ["е" .. DIA] = "ё", ["Е" .. DIA] = "Ё", ["и" .. DIA] = "ӥ", ["И" .. DIA] = "Ӥ", ["и" .. BREVE] = "й", ["И" .. BREVE] = "Й", ["і" .. DIA] = "ї", ["І" .. DIA] = "Ї", -- Latin letters ["c" .. CARON] = "č", ["C" .. CARON] = "Č", ["e" .. CARON] = "ě", ["E" .. CARON] = "Ě", ["o" .. CARON] = "ǒ", ["O" .. CARON] = "Ǒ", ["o" .. OGONEK] = "ǫ", ["O" .. OGONEK] = "Ǫ", ["s" .. CARON] = "š", ["S" .. CARON] = "Š", ["z" .. CARON] = "ž", ["Z" .. CARON] = "Ž", -- used in ru-pron: ["ж" .. BREVE] = "ӂ", -- used in ru-pron ["Ж" .. BREVE] = "Ӂ", ["j" .. CFLEX] = "ĵ", ["J" .. CFLEX] = "Ĵ", ["j" .. CARON] = "ǰ", -- no composed uppercase equivalent of J-caron ["ʒ" .. CARON] = "ǯ", ["Ʒ" .. CARON] = "Ǯ", } --[==[ Decompose acute, grave, etc. on letters (esp. Latin) into individivual character + combining accent. But recompose Cyrillic and Latin characters that we want to treat as units and get caught in the crossfire. We mostly want acute and grave decomposed; perhaps should just explicitly decompose those and no others. ]==] function export.decompose(text) return (ugsub(toNFD(text), ".[" .. BREVE .. DIA .. CARON .. OGONEK .. "]", recomposer)) end function export.assert_decomposed(text) assert(not ufind(text, "[áéíóúýàèìòùỳäëïöüÿÁÉÍÓÚÝÀÈÌÒÙỲÄËÏÖÜŸ]")) end --[==[ Transliterate text and then apply acute/grave decomposition. ]==] function export.translit(text, no_include_monosyllabic_jo_accent) local jo_accent = not no_include_monosyllabic_jo_accent and "mono" or nil return export.decompose(m_ru_translit.tr(text, nil, nil, jo_accent)) end --[==[ Recompose acutes and graves into preceding vowels. Probably not necessary. ]==] function export.recompose(text) return mw.ustring.toNFC(text) end local grave_decomposer = { ["ѐ"] = "е" .. GR, ["Ѐ"] = "Е" .. GR, ["ѝ"] = "и" .. GR, ["Ѝ"] = "И" .. GR, } --[==[ Decompose precomposed Cyrillic chars w/grave accent; not necessary for acute accent as there aren't precomposed Cyrillic chars w/acute accent, and undesirable for precomposed `ё` and `Ё`. ]==] function export.decompose_grave(word) return (ugsub(word, "[ѐЀѝЍ]", grave_decomposer)) end local grave_deaccenter = { [GR] = "", -- grave accent ["ѐ"] = "е", -- composed Cyrillic chars w/grave accent ["Ѐ"] = "Е", ["ѝ"] = "и", ["Ѝ"] = "И", } local deaccenter = mw.clone(grave_deaccenter) deaccenter[AC] = "" -- acute accent --[==[ Remove acute and grave accents; don't affect composed diaeresis in `ёЁ` or uncomposed diaeresis in `-ѣ̈-` (as in plural `сѣ̈дла` of `сѣдло́`). NOTE: Translit must already be decomposed! See comment at top. ]==] function export.remove_accents(word, tr) local ru_removed = ugsub(word, "[́̀ѐЀѝЍ]", deaccenter) if not tr then return ru_removed, nil end return ru_removed, (ugsub(tr, "[" .. AC .. GR .. "]", deaccenter)) end --[==[ Remove grave accents; don't affect acute or composed diaeresis in `ёЁ` or uncomposed diaeresis in `-ѣ̈-` (as in plural `сѣ̈дла` of `сѣдло́`). NOTE: Translit must already be decomposed! See comment at top. ]==] function export.remove_grave_accents(word, tr) local ru_removed = ugsub(word, "[̀ѐЀѝЍ]", grave_deaccenter) if not tr then return ru_removed, nil end return ru_removed, (ugsub(tr, GR, "")) end --[==[ Remove acute and grave accents in monosyllabic words; don't affect diaeresis (composed or uncomposed) because it indicates a change in vowel quality, which still applies to monosyllabic words. Don't change suffixes, where a "monosyllabic" stress is still significant (e.g. `-ча́т` short masculine of `-ча́тый`, vs. `-́чат` short masculine of `-́чатый`). NOTE: Translit must already be decomposed! See comment at top. ]==] function export.remove_monosyllabic_accents(word, tr) if export.is_monosyllabic(word) and not ufind(word, "^%-") then return export.remove_accents(word, tr) else return word, tr end end local destresser = mw.clone(deaccenter) destresser["ё"] = "е" destresser["Ё"] = "Е" destresser[DIA] = "" -- diaeresis -- Subfunction of split_syllables(). On input we get sections of text consisting of CONSONANT - VOWEL - CONSONANT - -- VOWEL ... - CONSONANT, where CONSONANT consists of zero or more consonants and VOWEL consists of exactly one vowel -- plus any following accent(s); we combine these into syllables as required by split_syllables(). local function combine_captures(captures) if captures[1] and not captures[2] then return captures end local combined = {} for i = 1, (#captures - 1), 2 do insert(combined, captures[i] .. captures[i+1]) end combined[#combined] = combined[#combined] .. captures[#captures] return combined end --[==[ Split Russian text and transliteration into syllables. Syllables end with vowel + accent(s), except for the last syllable, which includes any trailing consonants. NOTE: Translit must already be decomposed! See comment at top. ]==] function export.split_syllables(ru, tr) -- Split into alternating consonant/vowel sequences, as described in -- combine_captures(). local rusyllables = combine_captures(split(ru, "([" .. export.vowel .. "]" .. export.opt_accent .. ")")) local trsyllables if tr then export.assert_decomposed(tr) trsyllables = combine_captures(split(tr, "([" .. export.tr_vowel .. "]" .. export.opt_accent .. ")")) if #rusyllables ~= #trsyllables then error("Russian " .. ru .. " doesn't have same number of syllables as translit " .. tr) end end --error(concat(rusyllables, "/") .. "(" .. #rusyllables .. (trsyllables and (") || " .. concat(trsyllables, "/") .. "(" .. #trsyllables .. ")") or "")) return rusyllables, trsyllables end --[==[ Split Russian word and transliteration into hyphen-separated components. Rejoining with `concat(..., "-")` will recover the original word. If the original word ends in a hyphen, that hyphen gets included with the preceding component(this is the only case when an individual component has a hyphen in it). ]==] function export.split_hyphens(ru, tr) local rucomponents = split(ru, "%-") if rucomponents[#rucomponents] == "" and #rucomponents > 1 then rucomponents[#rucomponents - 1] = rucomponents[#rucomponents - 1] .. "-" table.remove(rucomponents) end local trcomponents if tr then trcomponents = split(tr, "%-") if trcomponents[#trcomponents] == "" and #trcomponents > 1 then trcomponents[#trcomponents - 1] = trcomponents[#trcomponents - 1] .. "-" table.remove(trcomponents) end if #rucomponents ~= #trcomponents then error("Russian " .. ru .. " doesn't have same number of hyphenated components as translit " .. tr) end end return rucomponents, trcomponents end --[==[ Apply `j` correction, converting `je` to `e` after consonants, `jo` to `o` after a sibilant, `ju` to `u` after hard sibilant. NOTE: Translit must already be decomposed! See comment at top. ]==] function export.j_correction(tr) tr = ugsub(tr, "([" .. export.tr_cons_no_approx .. "]" .. export.opt_accent ..")[Jj]([EeĚě])", "%1%2") tr = ugsub(tr, "([žščŽŠČ])[Jj]([Oo])", "%1%2") return (ugsub(tr, "([žšŽŠ])[Jj]([Uu])", "%1%2")) end local function make_unstressed_ru(ru) -- The following regexp has grave+acute+diaeresis after the bracket return (ugsub(ru, "[̀́̈ёЁѐЀѝЍ]", destresser)) end --[==[ Remove all stress marks (acute, grave, diaeresis). NOTE: Translit must already be decomposed! See comment at top. ]==] function export.make_unstressed(ru, tr) -- so this can be called from a template (specifically {{R:ru:Vasmer}}) if type(ru) == "table" then ru, tr = ine(ru.args[1]), ine(ru.args[2]) end if not tr then return make_unstressed_ru(ru), nil end -- In the presence of TR, we need to do things the hard way: Splitting -- into syllables and only converting Latin o to e opposite a ё. local rusyl, trsyl = export.split_syllables(ru, tr) for i=1,#rusyl do if ufind(rusyl[i], "[ёЁ]") then trsyl[i] = ugsub(trsyl[i], "[Oo]", {["O"] = "E", ["o"] = "e"}) end rusyl[i] = make_unstressed_ru(rusyl[i]) -- the following should still work as it will affect accents only trsyl[i] = make_unstressed_ru(trsyl[i]) end -- Also need to apply j correction as otherwise we'll have je after cons, etc. return concat(rusyl, ""), export.j_correction(concat(trsyl, "")) end local function remove_jo_ru(word) return (ugsub(word, "[̈ёЁ]", destresser)) end local function make_unstressed_once_ru(word) -- leave graves alone return (ugsub(word, "([́̈ёЁ])([^́̈ёЁ]*)$", function(x, rest) return destresser[x] .. rest; end, 1)) end --[==[ Remove diaeresis stress marks only. NOTE: Translit must already be decomposed! See comment at top. ]==] function export.remove_jo(ru, tr) if not tr then return remove_jo_ru(ru), nil end -- In the presence of TR, we need to do things the hard way: Splitting -- into syllables and only converting Latin o to e opposite a ё. local rusyl, trsyl = export.split_syllables(ru, tr) for i=1,#rusyl do if ufind(rusyl[i], "[ёЁ]") then trsyl[i] = ugsub(trsyl[i], "[Oo]", {["O"] = "E", ["o"] = "e"}) end rusyl[i] = remove_jo_ru(rusyl[i]) -- the following should still work as it will affect accents only trsyl[i] = make_unstressed_once_ru(trsyl[i]) end -- Also need to apply j correction as otherwise we'll have je after cons, etc. return concat(rusyl, ""), export.j_correction(concat(trsyl, "")) end local function map_last_hyphenated_component(fn, ru, tr) if ufind(ru, "%-") then -- If there is a hyphen, do it the hard way by splitting into -- individual components and doing the last one. Otherwise we just do -- the whole string. local rucomponents, trcomponents = export.split_hyphens(ru, tr) local lastru, lasttr = fn(rucomponents[#rucomponents], trcomponents and trcomponents[#trcomponents] or nil) rucomponents[#rucomponents] = lastru ru = concat(rucomponents, "-") if trcomponents then trcomponents[#trcomponents] = lasttr tr = concat(trcomponents, "-") end return ru, tr end return fn(ru, tr) end -- Make last stressed syllable (acute or diaeresis) unstressed; leave unstressed; leave graves alone; if `NOCONCAT`, -- return individual syllables. NOTE: Translit must already be decomposed! See comment at top. local function make_unstressed_once_after_hyphen_split(ru, tr, noconcat) if not tr then return make_unstressed_once_ru(ru), nil end -- In the presence of TR, we need to do things the hard way, as with -- make_unstressed(). local rusyl, trsyl = export.split_syllables(ru, tr) for i=#rusyl,1,-1 do local stressed = export.is_stressed(rusyl[i]) if stressed then if ufind(rusyl[i], "[ёЁ]") then trsyl[i] = ugsub(trsyl[i], "[Oo]", {["O"] = "E", ["o"] = "e"}) end rusyl[i] = make_unstressed_once_ru(rusyl[i]) -- the following should still work as it will affect accents only trsyl[i] = make_unstressed_once_ru(trsyl[i]) break end end if noconcat then return rusyl, trsyl end -- Also need to apply j correction as otherwise we'll have je after cons return concat(rusyl, ""), export.j_correction(concat(trsyl, "")) end --[==[ Make last stressed syllable (acute or diaeresis) to the right of any hyphen unstressed (unless the hyphen is word-final); leave graves alone. We don't destress a syllable to the left of a hyphen unless the hyphen is word-final (i.e. a prefix). Otherwise e.g. the accents in the first part of words like ко́е-како́й and а́льфа-лу́ч won't remain. NOTE: Translit must already be decomposed! See comment at top. ]==] function export.make_unstressed_once(ru, tr) return map_last_hyphenated_component(make_unstressed_once_after_hyphen_split, ru, tr) end local function make_unstressed_once_at_beginning_ru(word) -- leave graves alone return (ugsub(word, "^([^́̈ёЁ]*)([́̈ёЁ])", function(rest, x) return rest .. destresser[x]; end, 1)) end --[==[ Make first stressed syllable (acute or diaeresis) unstressed; leave graves alone; if `NOCONCAT`, return individual syllables. NOTE: Translit must already be decomposed! See comment at top. ]==] function export.make_unstressed_once_at_beginning(ru, tr, noconcat) if not tr then return make_unstressed_once_at_beginning_ru(ru), nil end -- In the presence of TR, we need to do things the hard way, as with -- make_unstressed(). local rusyl, trsyl = export.split_syllables(ru, tr) for i=1,#rusyl do local stressed = export.is_stressed(rusyl[i]) if stressed then if ufind(rusyl[i], "[ёЁ]") then trsyl[i] = ugsub(trsyl[i], "[Oo]", {["O"] = "E", ["o"] = "e"}) end rusyl[i] = make_unstressed_once_at_beginning_ru(rusyl[i]) -- the following should still work as it will affect accents only trsyl[i] = make_unstressed_once_at_beginning_ru(trsyl[i]) break end end if noconcat then return rusyl, trsyl end -- Also need to apply j correction as otherwise we'll have je after cons return concat(rusyl, ""), export.j_correction(concat(trsyl, "")) end --[==[ Subfunction of `make_ending_stressed()`, `make_beginning_stressed()`, which add an acute accent to a syllable that may already have a grave accent; in such a case, remove the grave. NOTE: Translit must already be decomposed! See comment at top. ]==] function export.correct_grave_acute_clash(word, tr) word = ugsub(word, "([̀ѐЀѝЍ])́", function(x) return grave_deaccenter[x] .. AC; end) word = ugsub(word, AC .. GR, AC) if not tr then return word, nil end tr = ugsub(tr, GR .. AC, AC) tr = ugsub(tr, AC .. GR, AC) return word, tr end local function make_ending_stressed_ru(word) -- If already ending stressed, just return word so we don't mess up ё if export.is_ending_stressed(word) then return word end -- Destress the last stressed syllable word = make_unstressed_once_ru(word) -- Add an acute to the last syllable word = ugsub(word, "([" .. export.vowel_no_jo .. "])([^" .. export.vowel .. "]*)$", "%1́%2") -- If that caused an acute and grave next to each other, remove the grave return export.correct_grave_acute_clash(word) end -- Remove the last primary stress from the word and put it on the final syllable. Leave grave accents alone except in -- the last syllable. If final syllable already has primary stress, do nothing. NOTE: Translit must already be -- decomposed! See comment at top. local function make_ending_stressed_after_hyphen_split(ru, tr) if not tr then return make_ending_stressed_ru(ru), nil end -- If already ending stressed, just return ru/tr so we don't mess up ё if export.is_ending_stressed(ru) then return ru, tr end -- Destress the last stressed syllable; pass in "noconcat" so we get -- the individual syllables back local rusyl, trsyl = make_unstressed_once_after_hyphen_split(ru, tr, "noconcat") -- Add an acute to the last syllable of both Russian and translit rusyl[#rusyl] = ugsub(rusyl[#rusyl], "([" .. export.vowel_no_jo .. "])", "%1" .. AC) trsyl[#trsyl] = ugsub(trsyl[#trsyl], "([" .. export.tr_vowel .. "])", "%1" .. AC) -- If that caused an acute and grave next to each other, remove the grave rusyl[#rusyl], trsyl[#trsyl] = export.correct_grave_acute_clash(rusyl[#rusyl], trsyl[#trsyl]) -- j correction didn't get applied in make_unstressed_once because -- we short-circuited it and made it return lists of syllables return concat(rusyl, ""), export.j_correction(concat(trsyl, "")) end --[==[ Remove the last primary stress from the portion of the word to the right of any hyphen (unless the hyphen is word-final) and put it on the final syllable. Leave grave accents alone except in the last syllable. If final syllable already has primary stress, do nothing. (See `make_unstressed_once()` for why we don't affect stresses to the left of a hyphen.) NOTE: Translit must already be decomposed! See comment at top. ]==] function export.make_ending_stressed(ru, tr) return map_last_hyphenated_component(make_ending_stressed_after_hyphen_split, ru, tr) end local function make_beginning_stressed_ru(word) -- If already beginning stressed, just return word so we don't mess up ё if export.is_beginning_stressed(word) then return word end -- Destress the first stressed syllable word = make_unstressed_once_at_beginning_ru(word) -- Add an acute to the first syllable word = ugsub(word, "^([^" .. export.vowel .. "]*)([" .. export.vowel_no_jo .. "])", "%1%2́") -- If that caused an acute and grave next to each other, remove the grave return export.correct_grave_acute_clash(word) end --[==[ Remove the first primary stress from the word and put it on the initial syllable. Leave grave accents alone except in the first syllable. If initial syllable already has primary stress, do nothing. NOTE: Translit must already be decomposed! See comment at top. ]==] function export.make_beginning_stressed(ru, tr) if not tr then return make_beginning_stressed_ru(ru), nil end -- If already beginning stressed, just return ru/tr so we don't mess up ё if export.is_beginning_stressed(ru) then return ru, tr end -- Destress the first stressed syllable; pass in "noconcat" so we get -- the individual syllables back local rusyl, trsyl = export.make_unstressed_once_at_beginning(ru, tr, "noconcat") -- Add an acute to the first syllable of both Russian and translit rusyl[1] = ugsub(rusyl[1], "([" .. export.vowel_no_jo .. "])", "%1" .. AC) trsyl[1] = ugsub(trsyl[1], "([" .. export.tr_vowel .. "])", "%1" .. AC) -- If that caused an acute and grave next to each other, remove the grave rusyl[1], trsyl[1] = export.correct_grave_acute_clash(rusyl[1], trsyl[1]) -- j correction didn't get applied in make_unstressed_once_at_beginning -- because we short-circuited it and made it return lists of syllables return concat(rusyl, ""), export.j_correction(concat(trsyl, "")) end -- used for tracking and categorization local hard_cons = {"hard-cons", "cons"} local hard_vowel = {"vowel", "hard-vowel"} local i_vowel = {"i", "vowel", "soft-vowel"} local sibilant_cons = {"sibilant", "cons"} local soft_vowel = {"vowel", "soft-vowel"} local velar_cons = {"velar", "cons"} local trailing_letter_type = { ["ш"] = sibilant_cons, ["щ"] = sibilant_cons, ["ч"] = sibilant_cons, ["ж"] = sibilant_cons, ["ц"] = {"c", "cons"}, ["к"] = velar_cons, ["г"] = velar_cons, ["х"] = velar_cons, ["ь"] = {"soft-cons", "cons"}, ["ъ"] = hard_cons, ["й"] = {"palatal", "cons"}, ["а"] = hard_vowel, ["я"] = soft_vowel, ["э"] = hard_vowel, ["е"] = soft_vowel, ["ѐ"] = soft_vowel, ["ѣ"] = soft_vowel, ["и"] = i_vowel, ["ѝ"] = i_vowel, ["ӥ"] = i_vowel, ["і"] = i_vowel, ["ї"] = i_vowel, ["ѵ"] = i_vowel, ["ы"] = hard_vowel, ["о"] = hard_vowel, ["ё"] = soft_vowel, ["у"] = hard_vowel, ["ю"] = soft_vowel, } function export.get_stem_trailing_letter_type(stem) return trailing_letter_type[ulower(usub(export.remove_accents(stem), -1))] or hard_cons end --[==[ Reduce stem by eliminating the "epenthetic" vowel. Applies to nominative singular masculine 2nd-declension hard and soft, and 3rd-declension feminine in -ь (e.g. [[любовь]]). `STEM` should be the result after calling `detect_stem_type()`, but with final -й if present. Normally returns two arguments (`STEM` and `TR`), but can be called from a template or bot using `#invoke` and will return one argument (`STEM`, or `STEM//TR` if `TR` is present). Returns {nil} if unable to reduce. NOTE: Translit must already be decomposed! See comment at top. ]==] function export.reduce_stem(stem, tr, soft_n) local pre, letter, post local pretr, lettertr, posttr local combine_tr = false -- test cases with translit: -- =p.reduce_stem("фе́ез", "fɛ́jez") -> фе́йз, fɛ́jz -- =p.reduce_stem("фе́йез", "fɛ́jez") -> фе́йз, fɛ́jz -- =p.reduce_stem("фе́без", "fɛ́bez") -> фе́бз, fɛ́bz -- =p.reduce_stem("фе́лез", "fɛ́lez") -> фе́льз, fɛ́lʹz -- =p.reduce_stem("феёз", p.decompose("fɛjóz")) -> фейз, fɛjz -- =p.reduce_stem("фейёз", p.decompose("fɛjjóz")) -> фейз, fɛjz -- =p.reduce_stem("фебёз", p.decompose("fɛbjóz")) -> фебз, fɛbz -- =p.reduce_stem("фелёз", p.decompose("fɛljóz")) -> фельз, fɛlʹz -- =p.reduce_stem("фе́бей", "fɛ́bej") -> фе́бь, fɛ́bʹ -- =p.reduce_stem("фебёй", p.decompose("fɛbjój")) -> фебь, fɛbʹ -- =p.reduce_stem("фе́ей", "fɛ́jej") -> фе́йй, fɛ́jj -- =p.reduce_stem("феёй", p.decompose("fɛjój")) -> фейй, fɛjj -- so this can be called from a template (or usually, a bot) if type(stem) == "table" then stem, tr = ine(stem.args[1]), ine(stem.args[2]) combine_tr = true end pre, letter, post = umatch(stem, "^(.*)([оОеЕёЁ])́?([" .. export.cons .. "]+)$") if not pre then return nil, nil end if tr then -- FIXME, may not be necessary to write the posttr portion as a -- consonant + zero or more consonant/accent combinations -- when will -- we ever get an accent after a consonant? That would indicate a -- failure of the decompose mechanism. pretr, lettertr, posttr = umatch(tr, "^(.*)([oOeE])́?([" .. export.tr_cons .. "][" .. export.tr_cons .. export.accent .. "]*)$") if not pretr then return nil, nil -- should not happen unless tr is really messed up end -- Remove any trailing j's on the translit stem which don't have a -- corresponding й on the Cyrillic stem. This avoids excess j's with -- cases like индонези́ец//indonɛzíjec, but retains multiple j's where -- necessary. pretr = ugsub(pretr, "[jJ]+$", function(m) return usub(m, 1, ulen(umatch(pre, "[йЙ]*$"))) end) end if letter == "О" or letter == "о" then -- FIXME, what about when the accent is on the removed letter? if post == "й" or post == "Й" then -- FIXME, is this correct? return nil, nil end letter = "" else local is_upper = ufind(post, "[" .. export.uppercase .. "]") if ufind(pre, "[" .. export.vowel .. "]́?$") then letter = is_upper and "Й" or "й" elseif post == "й" or post == "Й" then letter = is_upper and "Ь" or "ь" post = "" if posttr then posttr = "" end elseif ufind(post, "[" .. export.velar .. "]$") and ufind(pre, "[" .. export.cons_except_sib_c .. "]$") or ufind(post, "[^йЙ" .. export.velar .. "]$") and ufind(pre, "[лЛ]$") or soft_n and ufind(pre, "[нН]$") then letter = is_upper and "Ь" or "ь" else letter = "" end end stem = pre .. letter .. post if tr then tr = pretr .. export.translit(letter) .. posttr end if combine_tr then return export.combine_russian_tr(stem, tr) else return stem, tr end end --[==[ Generate the dereduced stem given `STEM` and `EPENTHETIC_STRESS` (which indicates whether the epenthetic vowel should be stressed); this is without any terminating non-syllabic ending, which is added if needed by the calling function. Normally returns two arguments (`STEM` and `TR`), but can be called from a template using `#invoke` (or by bot) and will return one argument (`STEM`, or `STEM//TR` if `TR` is present). Returns {nil} if unable to dereduce. NOTE: Translit must already be decomposed! See comment at top. ]==] function export.dereduce_stem(stem, tr, epenthetic_stress) local combine_tr = false -- so this can be called from a template (or usually, a bot) if type(stem) == "table" then stem, tr, epenthetic_stress = ine(stem.args[1]), ine(stem.args[2]), ine(stem.args[3]) combine_tr = true end if epenthetic_stress then stem, tr = export.make_unstressed_once(stem, tr) end local pre, letter, post local pretr, lettertr, posttr -- FIXME!!! Deal with this special case --if not (z.stem_type == "soft" and _.equals(z.stress_type, {"b", "f"}) -- we should ignore asterix for 2*b and 2*f (so to process it just like 2b or 2f) -- or _.contains(z.specific, "(2)") and _.equals(z.stem_type, {"velar", "letter-ц", "vowel"})) -- and also the same for (2)-specific and 3,5,6 stem-types --then -- I think this corresponds to our -ья and -ье types, which we -- handle separately --if z.stem_type == "vowel" then -- 1). -- if _.equals(z.stress_type, {"b", "c", "e", "f", "f'", "b'" }) then -- gen_pl ending stressed -- TODO: special vars for that -- z.stems["gen_pl"] = _.replace(z.stems["gen_pl"], "ь$", "е́") -- else -- z.stems["gen_pl"] = _.replace(z.stems["gen_pl"], "ь$", "и") -- end --end pre, letter, post = umatch(stem, "^(.*)([" .. export.cons .. "])([" .. export.cons .. "])$") if tr then pretr, lettertr, posttr = umatch(tr, "^(.*)(" .. export.tr_cons_acc_re .. ")(" .. export.tr_cons_acc_re .. ")$") if pre and not pretr then return nil, nil -- should not happen unless tr is really messed up end end if pre then local is_upper = ufind(post, "[" .. export.uppercase .. "]") local epvowel if ufind(letter, "[ьйЬЙ]") then letter = "" lettertr = "" if ufind(post, "[цЦ]$") or not epenthetic_stress then epvowel = is_upper and "Е" or "е" else epvowel = is_upper and "Ё" or "ё" end elseif ufind(letter, "[" .. export.cons_except_sib_c .. "]") and ufind(post, "[" .. export.velar .. "]") or ufind(letter, "[" .. export.velar .. "]") then epvowel = is_upper and "О" or "о" elseif post == "ц" or post == "Ц" then epvowel = is_upper and "Е" or "е" elseif epenthetic_stress then if ufind(letter, "[" .. export.sib .. "]") then epvowel = is_upper and "О́" or "о́" else epvowel = is_upper and "Ё" or "ё" end else epvowel = is_upper and "Е" or "е" end assert(epvowel) stem = pre .. letter .. epvowel .. post if tr then tr = pretr .. lettertr .. export.translit(epvowel) .. posttr tr = export.j_correction(tr) end if epenthetic_stress then stem, tr = export.make_ending_stressed(stem, tr) end if combine_tr then return export.combine_russian_tr(stem, tr) else return stem, tr end end return nil, nil end --[==[ Parse an entry that potentially has final footnote symbols and initial `*` for a hypothetical entry into initial symbols, text and final symbols. ]==] function export.split_symbols(entry, do_subscript) local prefentry, finalnotes = m_table_tools.separate_notes(entry) local initnotes, text = umatch(prefentry, "(%*?)(.*)$") return initnotes, text, finalnotes end -------------------------------------------------------------------------- -- Used for manual translit -- -------------------------------------------------------------------------- --[==[ Transliterate text after removing any links. The translit is decomposed in the process. ]==] function export.translit_no_links(text) return export.translit(require("Module:links").remove_links(text)) end --[==[ Split a combined Cyrillic/translit string (either in the form `CYRILLIC//TRANSLIT` or just `CYRILLIC`) into its components, decompose the translit, and either return a two-element list of `CYRILLIC` and `TRANSLIT` (if `dopair` is given), or two separate return values (if `dopair` is not specified). ]==] function export.split_russian_tr(term, dopair) local ru, tr if not term:find("//") then ru = term else local splitvals = split(term, "//") if #splitvals ~= 2 then error("Must have at most one // in a Russian//translit expr: '" .. term .. "'") end ru, tr = splitvals[1], export.decompose(splitvals[2]) end if dopair then return {ru, tr} else return ru, tr end end --[==[ Combine Cyrillic and translit into a single string separated by `//`. If translit is {nil}, the return value will be the same as the passed-in Cyrillic. `ru` can be a string (which any manual translit in `tr`) or a two-element table of `{CYRILLIC, TRANSLIT}`. ]==] function export.combine_russian_tr(ru, tr) if type(ru) == "table" then ru, tr = unpack(ru) end if tr then return ru .. "//" .. tr else return ru end end local function concat_maybe_moving_notes(x, y, movenotes) if movenotes then local xentry, xnotes = m_table_tools.separate_notes(x) local yentry, ynotes = m_table_tools.separate_notes(y) return xentry .. yentry .. xnotes .. ynotes else return x .. y end end --[==[ Concatenate two Cyrillic strings `RU1` and `RU2` that may have corresponding manual transliteration `TR1` and `TR2` (which should be {nil} if there is no manual translit, and otherwise should be decomposed). If `DOPAIR`, return a two-item list of the combined Cyrillic and manual translit (which will be {nil} if both `TR1` and `TR2` are {nil}); else, return two values, the combined Cyrillic and manual translit. If `MOVENOTES`, extract any footnote symbols at the end of `RU1` and move them to the end of the concatenated string, before any footnote symbols for `RU2`; same thing goes for `TR1` and `TR2`. ]==] function export.concat_russian_tr(ru1, tr1, ru2, tr2, dopair, movenotes) local ru, tr if not tr1 and not tr2 then ru = concat_maybe_moving_notes(ru1, ru2, movenotes) else if not tr1 then tr1 = export.translit_no_links(ru1) end if not tr2 then tr2 = export.translit_no_links(ru2) end ru, tr = concat_maybe_moving_notes(ru1, ru2, movenotes), export.j_correction(concat_maybe_moving_notes(tr1, tr2, movenotes)) end if dopair then return {ru, tr} else return ru, tr end end --[==[ Concatenate two Cyrillic/translit combinations (where each combination is a two-element list of `{CYRILLIC, TRANSLIT}` where `TRANSLIT` may be nil) by individually concatenating the Cyrillic and translit portions, and return a concatenated combination as a two-element list. If the manual translit portions of both terms on entry are {nil}, the result will also have {nil} manual translit. If `MOVENOTES`, extract any footnote symbols at the end of `TERM1` and move them after the concatenated string and before any footnote symbols at the end of `TERM2`. ]==] function export.concat_paired_russian_tr(term1, term2, movenotes) assert(type(term1) == "table") assert(type(term2) == "table") local ru1, tr1 = term1[1], term1[2] local ru2, tr2 = term2[1], term2[2] return export.concat_russian_tr(ru1, tr1, ru2, tr2, "dopair", movenotes) end function export.concat_forms(forms) local joined_rutr = {} for _, form in ipairs(forms) do insert(joined_rutr, export.combine_russian_tr(form)) end return concat(joined_rutr, ",") end --[==[ Given a list of forms, where each form is a two-element list of `{CYRILLIC, TRANSLIT}`, strip footnote symbols from the end of the Cyrillic and translit. ]==] function export.strip_notes_from_forms(forms) local newforms = {} for _, form in ipairs(forms) do local ru, tr = form[1], form[2] ru, _ = m_table_tools.separate_notes(ru) if tr then tr, _ = m_table_tools.separate_notes(tr) end insert(newforms, {ru, tr}) end return newforms end --[==[ Given a list of forms, where each form is a two-element list of `{CYRILLIC, TRANSLIT}`, unzip into parallel lists of Cyrillic and translit. The latter list may have gaps in it. ]==] function export.unzip_forms(forms) local rulist = {} local trlist = {} for i, form in ipairs(forms) do local ru, tr = form[1], form[2] rulist[i] = ru trlist[i] = tr end return rulist, trlist end --[==[ Given parallel lists of Cyrillic and translit (where the latter list may have gaps in it), return a list of forms, where each form is a two-element list of `{CYRILLIC, TRANSLIT}`. ]==] function export.zip_forms(rulist, trlist) local forms = {} for i, ru in ipairs(rulist) do insert(forms, {ru, trlist[i]}) end return forms end local function any_forms_have_translit(forms) for _, form in ipairs(forms) do if form[2] then return true end end return false end --[==[ Given a list of forms, where each form is a two-element list of `{CYRILLIC, TRANSLIT}`, combine forms with identical Cyrillic, concatenating the translit with a comma+space in between. ]==] function export.combine_translit_of_duplicate_forms(forms) if not forms[1] then return forms end -- Optimization to avoid creating a new list in the majority case when no translit exists. if not any_forms_have_translit(forms) then return forms end local newforms = {} insert(newforms, {forms[1][1], forms[1][2]}) for i = 2, #forms do local found_duplicate = false for j = 1, #newforms do -- If the Russian of the next form is the same as that of the last one, combine their translits and modify -- newforms[] in-place. Otherwise add the next form to newforms[]. Make sure to clone the form rather than -- just appending it directly since we may modify it in-place; we don't want to side-effect `forms` as -- passed in. if forms[i][1] == newforms[j][1] then local tr1 = newforms[j][2] local tr2 = forms[i][2] if not tr1 and not tr2 then -- this shouldn't normally happen else tr1 = tr1 or export.translit_no_links(newforms[j][1]) tr2 = tr2 or export.translit_no_links(forms[i][1]) if tr1 == tr2 then -- this shouldn't normally happen else newforms[j][2] = tr1 .. ", " .. tr2 end end found_duplicate = true break end end if not found_duplicate then insert(newforms, {forms[i][1], forms[i][2]}) end end return newforms end function export.any_termobjs_have_translit(termobjs) for _, termobj in ipairs(termobjs) do if termobj.tr then return true end end return false end --[==[ Given a list of term objects, where each object is a table of `{term = CYRILLIC, tr = TRANSLIT, ...}`, combine forms with identical Cyrillic and identical ancillary properties, concatenating the translit with a comma+space in between. Also recompose all transliteration. ]==] function export.combine_translit_of_duplicate_termobjs_and_recompose(termobjs) if not termobjs[1] then return termobjs end -- Optimization to avoid creating a new list in the majority case when no translit exists. if not export.any_termobjs_have_translit(termobjs) then return termobjs end local newtermobjs = {} insert(newtermobjs, m_table.shallowCopy(termobjs[1])) for i = 2, #termobjs do local found_duplicate = false for j = 1, #newtermobjs do -- If the Russian of the next termobj is the same as that of the last one, combine their translits and -- modify newtermobjs[] in-place. Otherwise add the next termobj to newtermobjs[]. Make sure to clone the -- termobj rather than just appending it directly since we may modify it in-place; we don't want to -- side-effect `termobjs` as passed in. if termobjs[i].term == newtermobjs[j].term and m_headword_utilities.termobj_ancillary_properties_equal(termobjs[i], newtermobjs[j]) then local tr1 = newtermobjs[j].tr local tr2 = termobjs[i].tr if not tr1 and not tr2 then -- this shouldn't normally happen else tr1 = tr1 or export.translit_no_links(newtermobjs[j].term) tr2 = tr2 or export.translit_no_links(termobjs[i].term) if tr1 == tr2 then -- this shouldn't normally happen else newtermobjs[j].tr = tr1 .. ", " .. tr2 end end found_duplicate = true break end end if not found_duplicate then insert(newtermobjs, m_table.shallowCopy(termobjs[i])) end end for _, newtermobj in ipairs(newtermobjs) do if newtermobj.tr then newtermobj.tr = export.recompose(newtermobj.tr) end end return newtermobjs end --[==[ Given a list of forms, where each form is a two-element list of `{CYRILLIC, TRANSLIT}`, split cases where two different transliterations have been packed into a single translit field by creating two adjacent term/translit pairs. This is the opposite operation of `combine_translit_of_duplicate_forms()`. ]==] function export.split_translit_of_duplicate_forms(forms) if not forms[1] then return forms end -- Optimization to avoid creating a new list in the majority case when no translit exists. if not export.any_forms_have_translit(forms) then return forms end local newforms = {} for _, form in ipairs(forms) do local ru, tr = unpack(form) if not tr or not tr:find(",") or ru:find(",") then insert(newforms, form) else local split_trs = split(tr, ",%s*") local default_tr = export.translit_no_links(ru) for _, split_tr in ipairs(split_trs) do if split_tr == default_tr then split_tr = nil end insert(newforms, {ru, split_tr}) end end end return newforms end --[==[ Given a list of forms, where each form is a two-element list of `{CYRILLIC, TRANSLIT}`, split cases where two different transliterations have been packed into a single translit field by creating two adjacent term/translit pairs. This is the opposite operation of `combine_translit_of_duplicate_forms()`. ]==] function export.split_translit_of_duplicate_termobjs_and_decompose(termobjs) if not termobjs[1] then return termobjs end -- Optimization to avoid creating a new list in the majority case when no translit exists. if not export.any_termobjs_have_translit(termobjs) then return termobjs end local newobjs = {} for _, obj in ipairs(termobjs) do local tr = obj.tr if tr then tr = export.decompose(tr) end if not tr or not tr:find(",") or obj.term:find(",[^%s]") then if not tr or tr == obj.tr then insert(newobjs, obj) else obj = m_table.shallowCopy(obj) obj.tr = tr insert(newobjs, obj) end else local term = obj.term local embedded_comma_in_term = not not term:find(",") -- If there's an embedded comma in the term, we want to split on commas not followed by a space; otherwise, -- we split on commas whether or not followed by a space. To implement "comma not followed by space", we -- can use a "frontier pattern"; the one below matches if the character after the comma is in the specified -- set (which is anything but a comma or space) and the comma itself is not in the specified set (in other -- words, it is a comma or space, which matches because it's a comma). This doesn't work correctly if there -- are two commas in a row, but that should not happen as there should not be an empty translit. local split_trs = split(tr, embedded_comma_in_term and ",%f[^,%s]" or ",%s*") local default_tr = export.translit_no_links(term) for _, split_tr in ipairs(split_trs) do if split_tr == default_tr then split_tr = nil end local newobj = m_table.shallowCopy(obj) newobj.tr = split_tr insert(newobjs, newobj) end end end return newobjs end function export.strip_ending(ru, tr, ending) local strippedru = ugsub(ru, ending .. "$", "") if strippedru == ru then error("Argument " .. ru .. " doesn't end with expected ending " .. ending) end ru = strippedru tr = export.strip_tr_ending(tr, ending) return ru, tr end function export.strip_tr_ending(tr, ending) if not tr then return nil end local endingtr = ugsub(export.translit_no_links(ending), "^([Jj])", "%1?") local strippedtr = ugsub(tr, endingtr .. "$", "") if strippedtr == tr then error("Translit " .. tr .. " doesn't end with expected ending " .. endingtr) end return strippedtr end return export jifqt1url7tz4h3rtigfh31hoqhxgrp Modül:ru-nominal 828 1589237 5669923 2026-06-23T07:25:54Z MustafaCavlak 59368 imported from en 5669923 Scribunto text/plain --[[ This module holds functions shared between ru-noun and ru-adjective. ]] local export = {} local lang = require("Module:languages").getByCode("ru") local m_links = require("Module:links") local m_table = require("Module:table") local com = require("Module:ru-common") local m_ru_translit = require("Module:ru-translit") local m_table_tools = require("Module:table tools") local u = mw.ustring.char local rfind = mw.ustring.find local rsubn = mw.ustring.gsub local unpack = unpack or table.unpack -- Lua 5.2 compatibility local usub = mw.ustring.sub local HYPMARKER = "⟐" -- version of rsubn() that discards all but the first return value local function rsub(term, foo, bar) local retval = rsubn(term, foo, bar) return retval end -- Insert an entry into an existing list if not already present, comparing the entry to items in the existing list -- using a key function. If entry already found, combine it into the existing entry using combine_func, a function of -- two arguments (the existing and new entries), which should return the combined entry. Return false if entry already -- found, true if new entry inserted. If combine_func not specified, the existing entry is left alone. If combine_func -- is specified, the return value will be written over the existing value (i.e. the existing list will be modified -- in-place). -- -- FIXME: General enough to consider moving to [[Module:table]]. local function insert_if_not_by_key(list, new_entry, keyfunc, combine_func) local new_entry_key = keyfunc(new_entry) for i, item in ipairs(list) do local item_key = keyfunc(item) if m_table.deepEquals(item_key, new_entry_key) then if combine_func then list[i] = combine_func(item, new_entry) end return false end end table.insert(list, new_entry) return true end -------------------------------------------------------------------------- -- Used for manual translit -- -------------------------------------------------------------------------- function export.combine_stem_and_suffix(stem, tr, suf, rules, old) local first = usub(suf, 1, 1) if rules then local conv = rules[first] if conv then local ending = usub(suf, 2) -- The following regexp is not quite the same as com.vowels. For one thing -- it includes й, which is important. It leaves out ы, which may or may not -- be important. if old and conv == "и" and rfind(ending, "^́?[аеёиійоуэюяѣ]") then conv = "і" end suf = conv .. ending end end -- If <adj> is present in the suffix, it means we need to translate it -- specially; do that now. local is_adj = rfind(suf, "<adj>") suf = rsub(suf, "<adj>", "") local suftr = is_adj and m_ru_translit.tr_adj(suf, "mono") return com.concat_russian_tr(stem, tr, suf, suftr, "dopair"), suf end -------------------------------------------------------------------------- -- Formatting forms for display -- -------------------------------------------------------------------------- -- Generate a string to substitute into a particular form in a Wiki-markup table. `forms` is the list of forms, -- generated by concat_word_forms(). `is_lemma` is true if we're formatting the entry for use in displaying the lemma -- in the declension table title. In this case, we don't include the translit, and remove monosyllabic accents from the -- Cyrillic (but not in multiword expressions). `accel_form` is the form code to speicfy in the accelerator, e.g. -- 'nom|m|s', or nil for no accelerator. `lemma_forms` is the list of {RU, TR} lemma forms for use in the accelerator, -- or nil if `accel_form` is nil. `remove_monosyllabic_accents_lemma_only` indicates that monosyllabic accents should -- be removed only in the lemma; otherwise we remove them from all forms. (FIXME: Rethink why we have this flag; we -- should be consistent.) function export.show_form(forms, is_lemma, accel_form, lemma_forms, remove_monosyllabic_accents_lemma_only) local russianvals = {} local latinvals = {} local lemmavals = {} -- First fetch the lemma forms and translit. If there are adjacent forms that have identical Russian including -- stress but different translit (e.g. азербайджа́нец with translits 'azerbajdžánec' and 'azɛrbajdžánec'), we -- combine the translits, comma-separating them. (This is necessary because there is currently only one tr= -- field per term.) We don't do this when processing the forms below; instead we handle this in a different -- and more general fashion (see below). local lemmaru, lemmatr if accel_form and lemma_forms and lemma_forms[1] ~= "-" then lemma_forms = com.combine_translit_of_duplicate_forms(com.strip_notes_from_forms(lemma_forms)) for i, form in ipairs(lemma_forms) do local ru, tr = unpack(lemma_forms[i]) ru, tr = com.remove_monosyllabic_accents(ru, tr) lemma_forms[i] = {ru, tr} end lemmaru, lemmatr = com.unzip_forms(lemma_forms) end -- Accumulate separately the Russian and transliteration into RUSSIANVALS and LATINVALS, then concatenate each down -- below. We need a fair amount of logic here: -- (1) to separate out footnote symbols; -- (2) to separate out the hypothetical marker (a footnote symbol but causes display of the Russian and translit -- in a special font); -- (3) to maybe remove monosyllabic accents; -- (4) to deduplicate repeated forms. -- We used to generate the display (HTML) as we went, but this prevented proper deduplication because the -- accelerator classes included in the HTML were different for otherwise identical forms. (Specifically, if two -- forms are the same in the Russian but different in the translit, the Russian will have different display forms -- because the translit is included in the accelerator classes.) So what we do is accumulate, separately for the -- Russian and translit, objects containing the entry (Russian or translit), the separated footnote symbols, -- whether the entry is hypothetical, and (for Russian only) the corresponding translit(s), for accelerator -- generation. As we accumulate, we duduplicate based only on comparing the entries of two objects. If we need to -- deduplicate two objects, we also need to combine their footnotes and transliteration (in the latter case, by -- comma-separating; we do this because there is only one tr= field for each term). for _, form in ipairs(forms) do local ru, tr = form[1], form[2] local ruentry, runotes = m_table_tools.separate_notes(ru) local trentry, trnotes if tr then trentry, trnotes = m_table_tools.separate_notes(tr) trnotes = rsub(trnotes, HYPMARKER, "") end if (is_lemma or not remove_monosyllabic_accents_lemma_only) then ruentry, trentry = com.remove_monosyllabic_accents(ruentry, trentry) end local ishyp = rfind(runotes, HYPMARKER) if ishyp then runotes = rsub(runotes, HYPMARKER, "") end local ruobj = {entry = ruentry, tr = {trentry or true}, ishyp = ishyp, notes = runotes} if not trentry then trentry = com.translit_no_links(ruentry) end if not trnotes then trnotes = com.translit_no_links(runotes) end local trobj = {entry = trentry, ishyp = ishyp, notes = trnotes} local function keyfunc(obj) return obj.entry end local function combine_func_ru(obj1, obj2) for _, tr in ipairs(obj2.tr) do m_table.insertIfNot(obj1.tr, tr) end obj1.notes = obj1.notes .. obj2.notes obj1.ishyp = obj1.ishyp or obj2.ishyp return obj1 end local function combine_func_tr(obj1, obj2) obj1.notes = obj1.notes .. obj2.notes obj1.ishyp = obj1.ishyp or obj2.ishyp return obj1 end if is_lemma then -- m_table.insertIfNot(lemmavals, ruspan .. " (" .. trspan .. ")") insert_if_not_by_key(lemmavals, ruobj, keyfunc, combine_func_ru) else insert_if_not_by_key(russianvals, ruobj, keyfunc, combine_func_ru) insert_if_not_by_key(latinvals, trobj, keyfunc, combine_func_tr) end end -- Now finally format each object and concatenate them together. local function concatenate_ru(objs) local is_missing = false for i, obj in ipairs(objs) do local accel = nil if lemmaru then local translit = nil if #obj.tr == 1 and obj.tr[1] == true then -- no translit else for j, tr in ipairs(obj.tr) do if tr == true then obj.tr[j] = com.translit_no_links(obj.entry) end end translit = table.concat(obj.tr, ", ") end accel = {form = accel_form, translit = translit, lemma = lemmaru, lemma_translit = lemmatr} end if obj.entry == "-" and #forms == 1 then objs[i] = "&mdash;" is_missing = true end if obj.ishyp then -- no accelerator for hypothetical forms objs[i] = m_links.full_link({lang = lang, term = nil, alt = obj.entry, tr = "-"}, "hypothetical") else objs[i] = m_links.full_link({lang = lang, term = obj.entry, tr = "-", accel = accel}) end objs[i] = objs[i] .. m_table_tools.superscript_notes(obj.notes) end return table.concat(objs, ", "), is_missing end local function concatenate_tr(objs) local scriptutils = require("Module:script utilities") for i, obj in ipairs(objs) do local trspan = m_links.remove_links(obj.entry) .. m_table_tools.superscript_notes(obj.notes) if obj.ishyp then -- FIXME, in the old [[Module:ru-noun]] code, notes were omitted from hypothetical entries. Correct? objs[i] = scriptutils.tag_text(trspan, lang, require("Module:scripts").getByCode("Latn"), "hypothetical") else objs[i] = scriptutils.tag_translit(trspan, lang, "default", " style=\"color: var(--wikt-palette-grey-8,#888);\"") end end return table.concat(objs, ", ") end if is_lemma then local russian_span, is_missing = concatenate_ru(lemmavals) return russian_span else local russian_span, is_missing = concatenate_ru(russianvals) if is_missing then return russian_span end local latin_span = concatenate_tr(latinvals) return russian_span .. "<br />" .. latin_span end end -------------------------------------------------------------------------- -- Sibilant/Velar/ц rules -- -------------------------------------------------------------------------- local stressed_sibilant_rules = { ["я"] = "а", ["ы"] = "и", ["ё"] = "о́", ["ю"] = "у", } local stressed_c_rules = { ["я"] = "а", ["ё"] = "о́", ["ю"] = "у", } local unstressed_sibilant_rules = { ["я"] = "а", ["ы"] = "и", ["о"] = "е", ["ю"] = "у", } local unstressed_c_rules = { ["я"] = "а", ["о"] = "е", ["ю"] = "у", } local velar_rules = { ["ы"] = "и", } export.stressed_rules = { ["ш"] = stressed_sibilant_rules, ["щ"] = stressed_sibilant_rules, ["ч"] = stressed_sibilant_rules, ["ж"] = stressed_sibilant_rules, ["ц"] = stressed_c_rules, ["к"] = velar_rules, ["г"] = velar_rules, ["х"] = velar_rules, } export.unstressed_rules = { ["ш"] = unstressed_sibilant_rules, ["щ"] = unstressed_sibilant_rules, ["ч"] = unstressed_sibilant_rules, ["ж"] = unstressed_sibilant_rules, ["ц"] = unstressed_c_rules, ["к"] = velar_rules, ["г"] = velar_rules, ["х"] = velar_rules, } export.nonsyllabic_suffixes = m_table.listToSet({"", "ъ", "ь", "й"}) export.sibilant_suffixes = m_table.listToSet({"ш", "щ", "ч", "ж"}) return export 1uawletg0fhb4bjt39sk8u6mg2vk01k Modül:ru-ön ad 828 1589238 5669924 2026-06-23T07:27:06Z MustafaCavlak 59368 imported from en 5669924 Scribunto text/plain --[=[ This module contains functions for creating inflection tables for Russian adjectives. Author: Benwing, rewritten from early version by Wikitiki89 Arguments: 1: lemma; nom sg, or just the stem if an explicit declension type is given in arg 2 2: declension type (usually omitted to autodetect based on the lemma), along with any short accent type and optional irregular short stem; see below. CASE_NUMGEN: Override a given form; see abbreviations below suffix: any suffix to attach unchanged to the end of each form notes: Notes to add to the end of the table title: Override the title shorttail: Footnote (e.g. *, 1, 2, etc.) to add to short forms if there's more than one; automatically superscripted shorttailall: Same as shorttail= but applies to all short forms even if there's only one CASE_NUMGEN_tail: Like shorttailall but only for a specific form nofull: Short forms only Case abbreviations: nom: nominative gen: genitive dat: dative acc: accusative ins: instrumental pre: prepositional short: short form Number/gender abbreviations: m: masculine singular n: neuter singular f: feminine singular p: plural mp: masculine plural (old-style and special numeral tables only) fp: masculine plural (special numeral tables only) Animacy abbreviations: an: animate in: inanimate Declension-type argument (arg 2): Form is DECLSPEC or DECLSPEC,DECLSPEC,... where DECLSPEC is one of the following: DECLTYPE:SHORTACCENT:SHORTSTEM DECLTYPE:SHORTACCENT DECLTYPE SHORTACCENT:SHORTSTEM SHORTACCENT (blank) DECLTYPE should normally be omitted, and the declension autodetected from the ending; or it should be ь, to indicate that an adjective in -ий is of the possessive variety with an extra -ь- in most of the endings. Alternatively, it can be an explicit declension type, in which case the lemma field needs to be replaced with the bare stem; the following are the possibilities: ый ий ой ьий short mixed manual (new-style) ый ій ьій short stressed-short mixed proper stressed-proper ъ-short ъ-stressed-short ъ-mixed ъ-proper ъ-stressed-proper manual (old-style, where ъ-* is a synonym for *, for any *) SHORTACCENT is one of a a' b b' c c' c'' to auto-generate the short forms with the specified accent pattern (following Zaliznyak); if omitted, no short forms will be auto-generated. SHORTACCENT can contain a * in it for "reducible" adjectives, where the short masculine singular has an epenthetic vowel in the final syllable. It can also contain (1) or (2) to indicate special cases. Both are used in conjunction with adjectives in -нный/-нний. Special case (1) causes the short masculine singular to end in -н instead of -нн; special case (2) causes all short forms to end this way. SHORTSTEM, if present, is used as the short stem to base the short forms off of, instead of the normal adjective long stem (possibly with a final-syllable accent added in the case of declension type -о́й). TODO: 1. Figure out what the symbol X-inside-square (⊠) means, which seems to go with all adjectives in -о́й with multi-syllabic stems. It may mean that the masculine singular short form is missing. If this indeed a regular thing, we need to implement it (and if it's regular but means something else, we need to implement that, too). Also figure out what the other signs on pages 68-76 etc. mean: -, X, ~, П₂, Р₂, diamond (♢), triangle (△; might simply mean a misc. irregularity; explained on p. 61). 2. Mark irregular overridden forms with △ (esp. appropriate for short forms). 3. Should non-reducible adjectives in -нный and -нний default to special case (1)? 4. In the case of a non-dereducible short masc sing of stress type b, we don't currently move the stress to the last syllable. Should we? 5. FIXME: In decline(), we used to default acc_n to nom_n. Now we do that in handle_forms_and_overrides(). Verify that this is more correct. 6. FIXME: Allow multiple heads, both to handle cases where two manual translits exist (or rather, one automatic and one manual), and cases where two stresses are possible, e.g. мину́вший or ми́нувший. 7. FIXME: Add code to generate the regular comparative. The rules are as follows: If the stem doesn't end in к г х, add -ee with no stress change if the short form is type a, else add -е́е (including type a'). If the stem ends in к г х, turn the last consonant into ч ж ш, add -е, and place the stress on the syllable preceding the ending. (E.g. дорого́й -> доро́же) ]=]-- local m_utilities = require("Module:utilities") local m_links = require("Module:links") local m_table = require("Module:table") local com = require("Module:ru-common") local nom = require("Module:ru-nominal") local strutils = require("Module:string utilities") local m_table_tools = require("Module:table tools") local m_debug = require("Module:debug") local export = {} local lang = require("Module:languages").getByCode("ru") ----------------------------- Utility functions ------------------------ local rfind = mw.ustring.find local rsubn = mw.ustring.gsub local rmatch = mw.ustring.match local rsplit = mw.text.split local ulower = mw.ustring.lower local usub = mw.ustring.sub -- version of rsubn() that discards all but the first return value local function rsub(term, foo, bar) local retval = rsubn(term, foo, bar) return retval end -- version of rsubn() that returns a 2nd argument boolean indicating whether -- a substitution was made. local function rsubb(term, foo, bar) local retval, nsubs = rsubn(term, foo, bar) return retval, nsubs > 0 end -- Insert a single form (consisting of {RUSSIAN, TR}) or a list of such -- forms into an existing list of such forms, adding NOTESYM (a string or nil) -- to the new forms if not nil. Return whether an insertion was performed. local function insert_forms_into_existing_forms(existing, newforms, notesym) if type(newforms) ~= "table" then newforms = {newforms} end local inserted = false for _, item in ipairs(newforms) do if not m_table.contains(existing, item) then if notesym then item = com.concat_paired_russian_tr(item, {notesym}) end table.insert(existing, item) inserted = true end end return inserted end local function track(page) m_debug.track("ru-adjective/" .. page) return true end -- Fancy version of ine() (if-not-empty). Converts empty string to nil, -- but also strips leading/trailing space and then single or double quotes, -- to allow for embedded spaces. local function ine(arg) if not arg then return nil end arg = rsub(arg, "^%s*(.-)%s*$", "%1") if arg == "" then return nil end local inside_quotes = rmatch(arg, '^"(.*)"$') if inside_quotes then return inside_quotes end inside_quotes = rmatch(arg, "^'(.*)'$") if inside_quotes then return inside_quotes end return arg end -------------------- Global declension/case/etc. variables ------------------- -- 'enable_categories' is a special hack for testing, which disables all -- category insertion if false. Delete this as soon as we've verified the -- working of the category code and created all the necessary categories. local enable_categories = true local declensions = {} local declensions_old = {} local internal_notes_table = {} local internal_notes_table_old = {} local internal_notes_genders = {} local internal_notes_genders_old = {} local short_declensions = {} local short_declensions_old = {} -- Formerly used for the ой-rare type, which has been removed; but keep -- the code around in case we need it later. local short_internal_notes_table = {} local short_internal_notes_table_old = {} local short_stress_patterns = {} local long_cases = { "nom_m", "nom_n", "nom_f", "nom_p", "gen_m", "gen_f", "gen_p", "dat_m", "dat_f", "dat_p", "acc_m_an", "acc_m_in", "acc_p_an", "acc_p_in", "acc_f", "acc_n", "ins_m", "ins_f", "ins_p", "pre_m", "pre_f", "pre_p", -- extra old long cases "nom_mp", "acc_mp_an", "acc_mp_in", -- extra dva cases "nom_fp", "acc_fp_an", "acc_fp_in", -- extra oba cases "gen_mp", "gen_fp", "dat_mp", "dat_fp", "ins_mp", "ins_fp", "pre_mp", "pre_fp", -- extra cases for compounds of два "acc_mp", "acc_fp", } -- Short cases and corresponding numbered arguments local short_cases = { "short_m", "short_n", "short_f", "short_p" } -- Create master list of all possible cases (actually case/number/gender pairs) local all_cases = mw.clone(long_cases) for _, case in ipairs(short_cases) do m_table.insertIfNot(all_cases, case) end -- If enabled, compare this module with new version of module to make -- sure all declensions are the same. local test_new_ru_adjective_module = false -- Forward references to functions local tracking_code local categorize local detect_stem_and_accent_type local construct_bare_and_short_stem local decline local canonicalize_override local handle_forms_and_overrides local decline_short local make_table -------------------------------------------------------------------------- -- Main code -- -------------------------------------------------------------------------- -- Implementation of main entry point function export.do_generate_forms(args, old, manual) local orig_args if test_new_ru_adjective_module then orig_args = mw.clone(args) end if args[3] or args[4] or args[5] or args[6] then error("Numbered short forms no longer supported") end if args.shorttailall then track("shorttailall") end local SUBPAGENAME = mw.loadData("Module:headword/data").pagename args.forms = {} args.categories = {} old = old or args.old args.old = old args.suffix = com.split_russian_tr(args.suffix or "", "dopair") args.internal_notes = {} -- Superscript footnote marker at beginning of note, similarly to what's -- done at end of forms. if args.notes then local notes, entry = m_table_tools.get_initial_notes(args.notes) args.notes = notes .. entry end local overall_short_forms_allowed manual = manual or args[2] == "manual" args.manual = manual local decl_types = manual and "$" or args[2] or "" local lemmas = manual and "-" or args[1] or SUBPAGENAME local normal_short_classes, rare_short_classes, dated_short_classes local saw_no_short for _, lemma_and_tr in ipairs(rsplit(lemmas, ",")) do -- reset these for each lemma so we get the short classes of the last -- lemma (doesn't really matter, as they should be the same for all -- lemmas) normal_short_classes = {} rare_short_classes = {} dated_short_classes = {} saw_no_short = false for _, decl_type in ipairs(rsplit(decl_types, ",")) do local lemma, lemmatr = com.split_russian_tr(lemma_and_tr) -- if lemma ends with -ся, strip it and act as if suffix=ся given -- (or rather, prepend ся to suffix) local active_base = rmatch(lemma, "^(.*)ся$") if active_base then lemma = active_base lemmatr = com.strip_tr_ending(lemmatr, "ся") args.refl = true args.real_suffix = com.concat_paired_russian_tr({"ся"}, args.suffix) else args.refl = false args.real_suffix = args.suffix end -- Auto-detect actual decl type, and get short accent and overriding -- short stem, if specified. local stem, stemtr, short_accent, short_stem, short_stemtr, datedrare stem, stemtr, decl_type, short_accent, short_stem, datedrare = detect_stem_and_accent_type(lemma, lemmatr, decl_type, args) if rfind(decl_type, "^[іи]й$") and rfind(stem, "[" .. com.velar .. com.sib .. "]$") then decl_type = "ый" end if datedrare == "none" then saw_no_short = true end local proper_decl = m_table.contains({"proper", "stressed-proper"}, decl_type) -- Use args.real_surname to avoid overwriting args.surname, since we may be overwriting -- `args` multiple times. FIXME: Use a separate data structure for this. args.real_surname = proper_decl or args.surname local short_title if short_accent then short_title = short_accent elseif args.real_surname then short_title = "surname" elseif m_table.contains({"ьій", "ьий", "ьей", "short", "stressed-short", "й-short", "mixed"}, decl_type) then short_title = "possessive" end if short_title then if datedrare == "dated" then m_table.insertIfNot(dated_short_classes, short_title) elseif datedrare == "rare" then m_table.insertIfNot(rare_short_classes, short_title) else m_table.insertIfNot(normal_short_classes, short_title) end end if short_stem then short_stem, short_stemtr = com.split_russian_tr(short_stem) end stem, args.allow_unaccented = rsubb(stem, "^%*", "") if args.allow_unaccented then track("allow-unaccented") end -- Treat suffixes without an accent, and suffixes with an accent on -- the initial hyphen, as if they were preceded with a *, which -- overrides all the logic that normally (a) normalizes the accent, -- and (b) complains about multisyllabic words without an accent. -- Don't do this if lemma is just -, which is used specially in -- manual declension tables. if lemma ~= "-" and (rfind(lemma, "^%-́") or (com.is_unstressed(lemma) and rfind(lemma, "^%-"))) then args.allow_unaccented = true end if not args.allow_unaccented and com.needs_accents(lemma) then -- Technically we don't need accents in -ой adjectives (which are -- always ending-stressed) and in -ый adjectives with a monosyllabic -- stem, such as полный (which are always stem-stressed), but it's -- better to enforce accents in all multisyllabic words, for -- consistency with nouns and verbs. Note that we still don't -- require an accent in monosyllabic adjectives such as злой. error("Lemma must have an accent in it: " .. lemma) end -- Set stem and unstressed version. Also construct end-accented version -- of stem if unstressed; needed for short forms of adjectives of -- type -о́й. We do this here before doing the dereduction -- transformation so that we don't end up stressing an unstressed -- epenthetic vowel, and so that the previous syllable instead ends -- up stressed (in type -о́й adjectives the stem won't have any stress). -- Note that the closest equivalent in nouns is handled in -- attach_unstressed(), which puts the stress onto the final syllable -- if the stress pattern calls for ending stress in the genitive -- plural. This works there because -- (1) It won't stress an unstressed epenthetic vowel because the -- cases where the epenthetic vowel is unstressed are precisely -- those with stem stress in the gen pl, not ending stress; -- (2) There isn't a need to stress the syllable preceding an -- unstressed epenthetic vowel because that syllable should -- already have stress, since we require that the base stem form -- (parameter 2) have stress in it whenever any case form has -- stem stress. This isn't the case here in type -о́й adjectives. -- NOTE: FIXME: I can't actually quote any forms from Zaliznyak -- (at least not from pages 58-60, where this is discussed) that -- seem to require last-stem-syllable-stress, i.e. where the stem is -- multisyllabic. The two examples given are both unusual: дорого́й -- has stress on the initial syllable до́рог etc. and these forms are -- marked with a triangle (indicating an apparent irregularity); and -- голубо́й seems not to have a masculine singular short form, and it's -- accent pattern b, so the remaining forms are all ending-stressed. -- In fact it's possible that these examples don't exist: It appears -- that all the multisyllabic adjectives in -о́й listed in the -- dictionary have a marking next to them consisting of an X inside of -- a square, which is glossed p. 69 to something I don't understand, -- but may be saying that the masculine singular short form is -- missing. If this is regular, we need to implement it. args.stem, args.stemtr = stem, stemtr args.ustem, args.ustemtr = com.make_unstressed_once(stem, stemtr) local accented_stem, accented_stemtr = stem, stemtr if not args.allow_unaccented then if accented_stemtr and com.is_unstressed(accented_stem) ~= com.is_unstressed(accented_stemtr) then error("Stem " .. accented_stem .. " and translit " .. accented_stemtr .. " must have same accent pattern") end if com.is_unstressed(accented_stem) then accented_stem, accented_stemtr = com.make_ending_stressed(accented_stem, accented_stemtr) end end local short_forms_allowed = manual and true or decl_type == "ый" or decl_type == "ой" or decl_type == (old and "ій" or "ий") overall_short_forms_allowed = overall_short_forms_allowed or short_forms_allowed if not short_forms_allowed then -- FIXME: We might want to allow this in case we have a -- reducible short, mixed or proper possessive adjective. But -- in that case we need to reduce rather than dereduce to get -- the stem. if short_accent or short_stem then error("Cannot specify short accent or short stem with declension type " .. decl_type .. ", as short forms aren't allowed") end if args.short_m or args.short_f or args.short_n or args.short_p then error("Cannot specify explicit short forms with declension type " .. decl_type .. ", as short forms aren't allowed") end end local orig_short_accent = short_accent local short_decl_type short_accent, short_decl_type = construct_bare_and_short_stem(args, short_accent, short_stem, short_stemtr, accented_stem, accented_stemtr, old, decl_type) local decls = old and declensions_old or declensions local short_decls = old and short_declensions_old or short_declensions if not decls[decl_type] then error("Unrecognized declension type " .. decl_type) end if short_accent == "" then error("Short accent type cannot be blank, should be omitted or given") end if short_accent and not short_stress_patterns[short_accent] then error("Unrecognized short accent type " .. short_accent) end tracking_code(decl_type, args, orig_short_accent, short_accent, short_stem, datedrare, short_forms_allowed) if not manual and enable_categories then categorize(decl_type, args, orig_short_accent, short_accent, short_stem) end decline(args, decls[decl_type], m_table.contains({"ой", "ьей", "й-short", "stressed-short", "stressed-proper"}, decl_type)) if short_forms_allowed and short_accent then decline_short(args, short_decls[short_decl_type], short_stress_patterns[short_accent], datedrare) end local intable = old and internal_notes_table_old or internal_notes_table local shortintab = old and short_internal_notes_table_old or short_internal_notes_table local internal_note = intable[decl_type] or shortintab[short_decl_type] if internal_note then m_table.insertIfNot(args.internal_notes, internal_note) end end end local short_class_titles = {} local function make_short_class_title(short_classes, datedrare) local sct = table.concat(short_classes, ",") -- short class title if sct == "" then return end if not m_table.contains({"surname", "possessive"}, sct) then -- Convert e.g. a*,a(1) into a*[(1)], either finally or followed by -- comma. sct = rsub(sct, "([abc]'*)%*,%1%*?(%([12]%))$", "%1*[%2]") sct = rsub(sct, "([abc]'*)%*,%1%*?(%([12]%)),", "%1*[%2],") -- Same for a(1),a*. sct = rsub(sct, "([abc]'*)%*?(%([12]%)),%1%*$", "%1*[%2]") sct = rsub(sct, "([abc]'*)%*?(%([12]%)),%1%*,", "%1*[%2],") -- Convert (1), (2) to ①, ②. sct = rsub(sct, "%(1%)", "①") sct = rsub(sct, "%(2%)", "②") -- Add a * before ①, ②, consistent with Zaliznyak. sct = rsub(sct, "([abc]'*)([①②])", "%1*%2") -- Convert ' and '' to ʹ and ʺ, consistent with Zaliznyak. sct = sct:gsub("''", "ʺ"):gsub("'", "ʹ") if args.nofull then sct = "short-form-only class " .. sct else sct = "short class " .. sct end end if datedrare then sct = datedrare .. " " .. sct end table.insert(short_class_titles, sct) end make_short_class_title(normal_short_classes, nil) make_short_class_title(rare_short_classes, "rare") make_short_class_title(dated_short_classes, "dated") if #short_class_titles == 0 then if saw_no_short then args.short_class_title = "no short forms" else args.short_class_title = "unknown short forms" end else args.short_class_title = table.concat(short_class_titles, " / ") end handle_forms_and_overrides(args, overall_short_forms_allowed) -- Test code to compare existing module to new one. if test_new_ru_adjective_module then local m_new_ru_adjective = require("Module:User:Benwing2/ru-adjective") local newargs = m_new_ru_adjective.do_generate_forms(orig_args, old, manual) local difdecl = false for _, case in ipairs(all_cases) do local arg = args[case] local newarg = newargs[case] if not m_table.deepEquals(arg, newarg) then -- Uncomment this to display the particular case and -- differing forms. --error(case .. " " .. (arg and com.concat_forms(arg) or "nil") .. " || " .. (newarg and com.concat_forms(newarg) or "nil")) track("different-decl") difdecl = true end break end if not difdecl then track("same-decl") end end return args end local function process_params(frame, include_form) local boolean = {type = "boolean"} local params = { [1] = true, [2] = true, suffix = true, title = true, special = true, shorttail = true, shorttailall = true, notes = true, old = boolean, noneuter = boolean, nofull = boolean, surname = boolean, pronoun = boolean } if include_form then params.form = {required = true} end for _, case in ipairs(all_cases) do params[case] = true params[case .. "_tail"] = true params[case .. "_tailall"] = true end return require("Module:parameters").process(frame:getParent().args, params) end -- Implementation of main entry point local function do_show(frame, old, manual) local args = export.do_generate_forms(process_params(frame), old, manual) return make_table(args) .. m_utilities.format_categories(args.categories, lang) end -- The main entry point for modern declension tables. function export.show(frame) return do_show(frame, false) end -- The main entry point for old declension tables. function export.show_old(frame) return do_show(frame, true) end -- The main entry point for manual declension tables. function export.show_manual(frame) return do_show(frame, false, "manual") end -- The main entry point for manual old declension tables. function export.show_manual_old(frame) return do_show(frame, true, "manual") end -- Entry point for use in Module:ru-noun. function export.get_nominal_decl(decl, gender, old) local d = old and declensions_old[decl] or declensions[decl] local n = {} if gender == "m" then n.nom_sg = d.nom_m n.gen_sg = d.gen_m n.dat_sg = d.dat_m n.ins_sg = d.ins_m n.pre_sg = d.pre_m elseif gender == "f" then n.nom_sg = d.nom_f n.gen_sg = d.gen_f n.dat_sg = d.dat_f n.acc_sg = d.acc_f n.ins_sg = d.ins_f n.pre_sg = d.pre_f elseif gender == "n" then n.nom_sg = d.nom_n n.gen_sg = d.gen_m n.dat_sg = d.dat_m n.acc_sg = d.acc_n n.ins_sg = d.ins_m n.pre_sg = d.pre_m else assert(false, "Unrecognized gender: " .. gender) end n.nom_pl = d.nom_p n.gen_pl = d.gen_p n.dat_pl = d.dat_p n.ins_pl = d.ins_p n.pre_pl = d.pre_p if gender == "m" and d.nom_mp then n.nom_pl = d.nom_mp end local intable = old and internal_notes_table_old or internal_notes_table local ingenders = old and internal_notes_genders_old or internal_notes_genders -- FIXME, what if there are multiple internal notes? See comment in -- do_generate_forms(). local internal_notes = ingenders[decl] and m_table.contains(ingenders[decl], gender) and intable[decl] return n, internal_notes end local function get_form(forms) local canon_forms = {} for _, form in ipairs(forms) do local ru, tr = form[1], form[2] local ruentry = m_table_tools.get_notes(ru) local trentry, trnotes if tr then trentry, trnotes = m_table_tools.get_notes(tr) end ruentry = m_links.remove_links(ruentry) m_table.insertIfNot(canon_forms, {ruentry, trentry}) end return com.concat_forms(canon_forms) end -- The entry point for 'ru-adj-forms' to generate all adjective forms. function export.generate_forms(frame) local args = export.do_generate_forms(process_params(frame), false) local ins_text = {} for _, case in ipairs(all_cases) do if args[case] then table.insert(ins_text, case .. "=" .. get_form(args[case])) end end return table.concat(ins_text, "|") end -- The entry point for 'ru-adj-form' to generate a particular adjective form. function export.generate_form(frame) local args = process_params(frame, "include form") local form = args.form if not m_table.contains(all_cases, form) then error("Unrecognized form " .. form) end args = export.do_generate_forms(args, false) if not args[form] then return "" else return get_form(args[form]) end end -------------------------------------------------------------------------- -- Tracking and categorization -- -------------------------------------------------------------------------- tracking_code = function(decl_class, args, orig_short_accent, short_accent, short_stem, datedrare) local hint_types = com.get_stem_trailing_letter_type(args.stem) local function dotrack(prefix) if prefix ~= "" then track(prefix) prefix = prefix .. "/" end track(prefix .. decl_class) for _, hint_type in ipairs(hint_types) do track(prefix .. hint_type) track(prefix .. decl_class .. "/" .. hint_type) end end dotrack("") if args.short_m or args.short_f or args.short_n or args.short_p then dotrack("short") end if orig_short_accent then if rfind(orig_short_accent, "%*") then dotrack("reducible") dotrack("reducible/" .. short_accent) end if rfind(orig_short_accent, "%(1%)") then dotrack("special-case-1") dotrack("special-case-1/" .. short_accent) end if rfind(orig_short_accent, "%(2%)") then dotrack("special-case-2") dotrack("special-case-2/" .. short_accent) end end if short_accent then dotrack("short-accent/" .. short_accent) if datedrare == "dated" then dotrack("short-accent-dated") dotrack("short-accent-dated/" .. short_accent) elseif datedrare == "rare" then dotrack("short-accent-rare") dotrack("short-accent-rare/" .. short_accent) end elseif datedrare == "none" then dotrack("short-accent/none") else dotrack("short-accent/unknown") end if short_stem then dotrack("explicit-short-stem") dotrack("explicit-short-stem/" .. short_accent) end for _, case in ipairs(all_cases) do if args[case] then track("irreg/" .. case) -- questionable use: dotrack("irreg/" .. case) end end end -- Insert the category CAT (a string) into list CATEGORIES. String will -- have "Russian " prepended and ~ substituted for the part of speech. local function insert_category(categories, cat, plpos) table.insert(categories, "Russian " .. rsub(cat, "~", plpos)) end categorize = function(decl_type, args, orig_short_accent, short_accent, short_stem) local plpos = args.real_surname and "surnames" or args.pronoun and "pronouns" or "adjectives" -- Insert category CAT into the list of categories in ARGS. local function insert_cat(cat) insert_category(args.categories, cat, plpos) end -- FIXME: For compatibility with old {{temp|ru-adj7}}, {{temp|ru-adj8}}, -- {{temp|ru-adj9}}; maybe there's a better way. if m_table.contains({"ьій", "ьий", "ьей", "short", "stressed-short", "й-short", "mixed", "proper", "stressed-proper"}, decl_type) then insert_cat("possessive ~") end if not args.pronoun then if m_table.contains({"ьій", "ьий", "ьей"}, decl_type) then insert_cat("long possessive ~") elseif m_table.contains({"short", "stressed-short", "й-short"}, decl_type) then insert_cat("short possessive ~") elseif m_table.contains({"mixed"}, decl_type) then insert_cat("mixed possessive ~") elseif m_table.contains({"proper", "stressed-proper"}, decl_type) then -- Don't insert a category like [[:Category:Russian proper-name surnames]]. Instead, rely on -- [[:Category:Russian possessive surnames]] generated above. if not args.real_surname then insert_cat("proper-name ~") end elseif decl_type == "$" then insert_cat("indeclinable ~") else local hint_types = com.get_stem_trailing_letter_type(args.stem) -- insert English version of Zaliznyak stem type local stem_type = m_table.contains(hint_types, "velar") and "velar-stem" or m_table.contains(hint_types, "sibilant") and "sibilant-stem" or m_table.contains(hint_types, "c") and "ц-stem" or m_table.contains(hint_types, "i") and "i-stem" or m_table.contains(hint_types, "vowel") and "vowel-stem" or m_table.contains(hint_types, "soft-cons") and "vowel-stem" or m_table.contains(hint_types, "palatal") and "vowel-stem" or decl_type == "ий" and "soft-stem" or "hard-stem" if stem_type == "soft-stem" or stem_type == "vowel-stem" then insert_cat(stem_type .. " ~") else insert_cat(stem_type .. " " .. (m_table.contains({"ой", "ьей", "й-short"}, decl_type) and "ending-stressed" or "stem-stressed") .. " ~") end end if m_table.contains({"ой", "ьей", "й-short"}, decl_type) then insert_cat("ending-stressed ~") end end local short_forms_allowed = m_table.contains({"ый", "ой", "ій", "ий"}, decl_type) if short_forms_allowed then local override_m = args.short_m local override_f = args.short_f local override_n = args.short_n local override_p = args.short_p local has_short = short_accent or override_m or override_f or override_n or override_p local missing_short = override_m == "-" or override_f == "-" or override_n == "-" or override_p == "-" or not short_accent and (not override_m or not override_f or not override_n or not override_p) if has_short then insert_cat("~ with short forms") if missing_short then insert_cat("~ with missing short forms") end end if short_accent then insert_cat("~ with short accent pattern " .. (short_accent:gsub("''", "ʺ"):gsub("'", "ʹ"))) end if orig_short_accent then if rfind(orig_short_accent, "%*") then insert_cat("~ with reducible short stem") end if rfind(orig_short_accent, "%(1%)") then insert_cat("~ with Zaliznyak short form special case 1") end if rfind(orig_short_accent, "%(2%)") then insert_cat("~ with Zaliznyak short form special case 2") end end if short_stem and short_stem ~= args.stem then insert_cat("~ with irregular short stem") end end for _, case in ipairs(all_cases) do if args[case] and args[case] ~= "-" then local engcase = rsub(case, "^([a-z]*)", { nom="nominative", gen="genitive", dat="dative", acc="accusative", ins="instrumental", pre="prepositional", short="short", }) engcase = rsub(engcase, "(_[a-z]*)$", { _m=" masculine singular", _f=" feminine singular", _n=" neuter singular", _p=" plural", _mp=" masculine plural" }) insert_cat("~ with irregular " .. engcase) end end if args.nofull then insert_cat("short-form-only ~") end end -------------------------------------------------------------------------- -- Autodetection and stem munging -- -------------------------------------------------------------------------- -- Attempt to detect the type of the lemma based on its ending, separating -- off the base and the ending; also extract the accent type for short -- adjectives and optional short stem. DECL is the value passed in, and -- might already specify the ending. Return five values: STEM, STEMTR, DECL, -- SHORT_ACCENT (accent class of short adjective, or nil for no short -- adjectives other than specified through overrides), SHORT_STEM (special -- stem of short adjective, nil if same as long stem). The return value of -- SHORT_STEM is taken directly from the argument and will include any -- manual translit. detect_stem_and_accent_type = function(lemma, tr, decl, args) local datedrare = false -- If it looks like a short decl type, canonicalize. [abc] for types -- a, b, c'', etc.; [d] for dated-*; [r] for rare-*; -- [-] for - (no short forms); [*(] for * and (1) and (2) modifiers, -- which might precede the letter. if rfind(decl, "^[abcdr*(-]") then decl = ":" .. decl end local splitvals = rsplit(decl, ":") if #splitvals > 3 then error("Should be at most three colon-separated parts of a declension spec: " .. decl) end local short_accent, short_stem decl, short_accent, short_stem = splitvals[1], splitvals[2], splitvals[3] -- Check for dated variant of short accent local dated_short = short_accent and rmatch(short_accent, "^dated%-(.*)$") if dated_short then datedrare = "dated" short_accent = dated_short else -- Check for rare variant of short accent local rare_short = short_accent and rmatch(short_accent, "^rare%-(.*)$") if rare_short then datedrare = "rare" short_accent = rare_short end end decl = ine(decl) -- Resolve aliases if decl then decl = rsub(decl, "^ъ%-", "") end if short_accent == "-" then short_accent = nil datedrare = "none" end short_accent = ine(short_accent) short_stem = ine(short_stem) if short_stem and not short_accent then error("With explicit short stem " .. short_stem .. ", must specify short accent") end local base, ending -- The while loop appears to function solely as a way of allowing a -- jump to the end of the loop with a 'break' statement. I don't think -- the loop is ever run more than once. while true do if not decl or decl == "ь" then base, ending = rmatch(lemma, "^(.*)([ыиіое]́?й)$") if base then if ending == "ий" and decl == "ь" then ending = "ьий" elseif ending == "ій" and decl == "ь" then ending = "ьій" elseif ending == "е́й" or (ending == "ей" and com.is_monosyllabic(lemma)) then ending = "ье́й" end tr = com.strip_tr_ending(tr, ending) -- -ий/-ій will be converted to -ый after velars and sibilants -- by the caller decl = com.make_unstressed(ending) break else -- It appears that short, mixed and proper adjectives are always -- either of the -ов/ев/ёв or -ин/ын types. The former type is -- always (or almost always?) short, while the latter can be -- either; apparently mixed is the more "modern" type of -- declension, and short is "older". However, both -ов/ев/ёв or -- -ин/ын are of type "proper" (similar to "short") when -- capitalized. -- -- NOTE: Following regexp is accented base = rmatch(lemma, "^([" .. com.uppercase .. "].*[иы]́н)ъ?$") if base then decl = "stressed-proper" break end base = rmatch(lemma, "^([" .. com.uppercase .. "].*[еёо]́?в)ъ?$") if not base then -- Following regexp is not stressed base = rmatch(lemma, "^([" .. com.uppercase .. "].*[иы]н)ъ?$") end if base then decl = "proper" break end base = rmatch(lemma, "^(.*[еёо]́?в)ъ?$") if base then decl = "short" break end base = rmatch(lemma, "(.*[иы]́н)ъ?$") --accented if base then decl = "stressed-short" break end base = rmatch(lemma, "(.*[иы]н)ъ?$") --unaccented if base then decl = "mixed" break -- error("With -ин/ын adjectives, must specify 'short' or 'mixed':" .. lemma) end error("Cannot determine stem type of adjective: " .. lemma) end elseif m_table.contains({"short", "stressed-short", "mixed", "proper", "stressed-proper"}, decl) then base = rmatch(lemma, "^(.-)ъ?$") assert(base) break elseif decl == "й" or decl == "й-short" then base = rmatch(lemma, "^(.-)й$") assert(base) tr = com.strip_tr_ending(tr, "й") decl = "й-short" break else base = lemma break end end if not datedrare and (args.refl or not (decl == "ый" or decl == "ий" and not rfind(base, "[цс]к$"))) then datedrare = "none" end return base, tr, decl, short_accent, short_stem, datedrare end -- Add a possible suffix to the bare stem, according to the declension and -- value of OLD. This may be -ь, -ъ, -й or nothing. We need to do this here -- because we don't actually attach such a suffix in attach_unstressed() due -- to situations where we don't want the suffix added, e.g. бескра́йний with -- dereduced nom sg бескра́ен without expected -ь. local function add_bare_suffix(bare, baretr, old, decl, dereduced) if decl == "й-short" then return bare .. "й", baretr and baretr .. "j" or nil elseif old and decl ~= "ій" and decl ~= "$" then return bare .. "ъ", baretr elseif decl == "ий" or decl == "ій" then -- This next special case is mentioned in Zaliznyak's 1980 grammatical -- dictionary for adjectives (footnote, p. 60). if dereduced and rfind(bare, "[нН]$") then -- FIXME: What happens in this case old-style? I assume that -- -ъ is added, but this is a guess. return bare .. (old and "ъ" or ""), baretr elseif rfind(bare, "[" .. com.vowel .. "]́?$") then -- This happens with adjectives like длинноше́ий, short masculine -- singular длинноше́й. return bare .. "й", baretr and baretr .. "j" or nil else return bare .. "ь", baretr and baretr .. "ʹ" or nil end else return bare, baretr end end -- Construct and set bare and short form in args, and canonicalize -- short accent spec, handling cases *, (1) and (2). Return canonicalized -- short accent and the short declension, which is usually the same as -- the corresponding long one. construct_bare_and_short_stem = function(args, short_accent, short_stem, short_stemtr, accented_stem, accented_stemtr, old, decl) -- Check if short forms allowed; if not, no short-form params can be given. -- Construct bare version of stem; used for cases where the ending -- is non-syllabic (i.e. short masculine singular of long adjectives, -- and masculine singular of short, mixed and proper adjectives). Comes -- from short masculine or 3rd argument if explicitly given, else from the -- accented stem, possibly with the dereduction transformation applied -- (if * occurs in the short accent spec). local reducible, sc1, sc2 if short_accent then short_accent, reducible = rsubb(short_accent, "%*", "") short_accent, sc1 = rsubb(short_accent, "%(1%)", "") short_accent, sc2 = rsubb(short_accent, "%(2%)", "") end if sc1 or sc2 then -- Reducible isn't compatible with sc1 or sc2, but Zaliznyak's -- dictionary always seems to notate sc1 and sc2 with reducible *, -- so ignore it. reducible = false end if sc1 and sc2 then error("Special cases 1 and 2, i.e. (1) and (2), not compatible") end local short_decl = decl -- Construct short stem. May be explicitly given, else comes from -- end-accented stem. if not short_stem then short_stem, short_stemtr = accented_stem, accented_stemtr end -- Try to accent unaccented short stem (happens only when explicitly given), -- but be conservative -- only if monosyllabic, since otherwise we have -- no idea where stress should end up; after all, the explicit short stem -- is for exceptional cases. if not args.allow_unaccented then if short_stemtr and com.is_unstressed(short_stem) ~= com.is_unstressed(short_stemtr) then error("Explicit short stem " .. short_stem .. " and translit " .. short_stemtr .. " must have same accent pattern") end if com.is_monosyllabic(short_stem) then short_stem, short_stemtr = com.make_ending_stressed(short_stem, short_stemtr) elseif com.needs_accents(short_stem) then error("Explicit short stem " .. short_stem .. " needs an accent") end end if sc2 then if not rfind(short_stem, "нн$") then error("With special case 2, stem needs to end in -нн: " .. short_stem) end short_stem = rsub(short_stem, "нн$", "н") if short_stemtr then if not rfind(short_stemtr, "nn$") then error("With special case 2, stem translit needs to end in -nn: " .. short_stemtr) end short_stemtr = rsub(short_stemtr, "nn$", "n") end end -- Construct bare form, used for short masculine. local bare, baretr if reducible then bare, baretr = com.dereduce_stem(short_stem, short_stemtr, rfind(short_accent, "^b")) if not bare then error("Unable to dereduce stem: " .. short_stem) end bare, baretr = add_bare_suffix(bare, baretr, old, decl, true) else bare, baretr = short_stem, short_stemtr if sc1 then if not rfind(bare, "нн$") then error("With special case 1, stem needs to end in -нн: " .. bare) end bare = rsub(bare, "нн$", "н") if baretr then if not rfind(baretr, "nn$") then error("With special case 1, stem translit needs to end in -nn: " .. baretr) end baretr = rsub(baretr, "nn$", "n") end end -- With special case 1 or 2, we don't ever want -ь added, so treat -- it like a reducible (that may be why these are marked as -- reducible in Zaliznyak). bare, baretr = add_bare_suffix(bare, baretr, old, decl, sc1 or sc2) end args.short_stem, args.short_stemtr = short_stem, short_stemtr args.short_ustem, args.short_ustemtr = com.make_unstressed_once(short_stem, short_stemtr) args.bare, args.baretr = bare, baretr return short_accent, short_decl end -------------------------------------------------------------------------- -- Declensions -- -------------------------------------------------------------------------- declensions["ый"] = { ["nom_m"] = "ый", ["nom_n"] = "ое", ["nom_f"] = "ая", ["nom_p"] = "ые", ["gen_m"] = "ого", ["gen_f"] = "ой", ["gen_p"] = "ых", ["dat_m"] = "ому", ["dat_f"] = "ой", ["dat_p"] = "ым", ["acc_f"] = "ую", ["acc_n"] = "ое", ["ins_m"] = "ым", ["ins_f"] = {"ой", "ою"}, ["ins_p"] = "ыми", ["pre_m"] = "ом", ["pre_f"] = "ой", ["pre_p"] = "ых", } declensions["ий"] = { ["nom_m"] = "ий", ["nom_n"] = "ее", ["nom_f"] = "яя", ["nom_p"] = "ие", ["gen_m"] = "его", ["gen_f"] = "ей", ["gen_p"] = "их", ["dat_m"] = "ему", ["dat_f"] = "ей", ["dat_p"] = "им", ["acc_f"] = "юю", ["acc_n"] = "ее", ["ins_m"] = "им", ["ins_f"] = {"ей", "ею"}, ["ins_p"] = "ими", ["pre_m"] = "ем", ["pre_f"] = "ей", ["pre_p"] = "их", } declensions["ой"] = { ["nom_m"] = "о́й", ["nom_n"] = "о́е", ["nom_f"] = "а́я", ["nom_p"] = "ы́е", ["gen_m"] = "о́го", ["gen_f"] = "о́й", ["gen_p"] = "ы́х", ["dat_m"] = "о́му", ["dat_f"] = "о́й", ["dat_p"] = "ы́м", ["acc_f"] = "у́ю", ["acc_n"] = "о́е", ["ins_m"] = "ы́м", ["ins_f"] = {"о́й", "о́ю"}, ["ins_p"] = "ы́ми", ["pre_m"] = "о́м", ["pre_f"] = "о́й", ["pre_p"] = "ы́х", } -- These can be stressed in ничья́ "draw, tie (in sports)". declensions["ьий"] = { ["nom_m"] = "и́й", ["nom_n"] = "ье́", ["nom_f"] = "ья́", ["nom_p"] = "ьи́", ["gen_m"] = "ье́го", ["gen_f"] = "ье́й", ["gen_p"] = "ьи́х", ["dat_m"] = "ье́му", ["dat_f"] = "ье́й", ["dat_p"] = "ьи́м", ["acc_f"] = "ью́", ["acc_n"] = "ье́", ["ins_m"] = "ьи́м", ["ins_f"] = {"ье́й", "ье́ю"}, ["ins_p"] = "ьи́ми", ["pre_m"] = "ье́м", ["pre_f"] = "ье́й", ["pre_p"] = "ьи́х", } declensions["ьей"] = { ["nom_m"] = "е́й", ["nom_n"] = "ьё", ["nom_f"] = "ья́", ["nom_p"] = "ьи́", ["gen_m"] = "ьего́", ["gen_f"] = "ье́й", ["gen_p"] = "ьи́х", ["dat_m"] = "ьему́", ["dat_f"] = "ье́й", ["dat_p"] = "ьи́м", ["acc_f"] = "ью́", ["acc_n"] = "ьё", ["ins_m"] = "ьи́м", ["ins_f"] = {"ье́й", "ье́ю"}, ["ins_p"] = "ьи́ми", ["pre_m"] = "ьём", ["pre_f"] = "ье́й", ["pre_p"] = "ьи́х", } declensions["short"] = { ["nom_m"] = "", ["nom_n"] = "о́", ["nom_f"] = "а́", ["nom_p"] = "ы́", ["gen_m"] = "а́", ["gen_f"] = "о́й", ["gen_p"] = "ы́х", ["dat_m"] = "у́", ["dat_f"] = "о́й", ["dat_p"] = "ы́м", ["acc_f"] = "у́", ["acc_n"] = "о́", ["ins_m"] = "ы́м", ["ins_f"] = {"о́й", "о́ю"}, ["ins_p"] = "ы́ми", ["pre_m"] = "о́м", ["pre_f"] = "о́й", ["pre_p"] = "ы́х", } declensions["stressed-short"] = mw.clone(declensions["short"]) declensions["stressed-short"]["pre_m"] = "е́" declensions["й-short"] = { ["nom_m"] = "й", ["nom_n"] = "ё", ["nom_f"] = "я́", ["nom_p"] = "и́", ["gen_m"] = "его́", ["gen_f"] = "е́й", ["gen_p"] = "и́х", ["dat_m"] = "ему́", ["dat_f"] = "е́й", ["dat_p"] = "и́м", ["acc_f"] = "ю́", ["acc_n"] = "ё", ["ins_m"] = "и́м", ["ins_f"] = {"е́й", "е́ю"}, ["ins_p"] = "и́ми", ["pre_m"] = "ём", ["pre_f"] = "е́й", ["pre_p"] = "и́х", } declensions["mixed"] = { ["nom_m"] = "", ["nom_n"] = "о", ["nom_f"] = "а", ["nom_p"] = "ы", ["gen_m"] = {"ого", "а2"}, ["gen_f"] = "ой", ["gen_p"] = "ых", ["dat_m"] = {"ому", "у2"}, ["dat_f"] = "ой", ["dat_p"] = "ым", ["acc_f"] = "у", ["acc_n"] = "о", ["ins_m"] = "ым", ["ins_f"] = {"ой", "ою"}, ["ins_p"] = "ыми", ["pre_m"] = "ом", ["pre_f"] = "ой", ["pre_p"] = "ых", } internal_notes_table["mixed"] = "<sup>2</sup> Obsolete." internal_notes_genders["mixed"] = {"m"} internal_notes_table_old["mixed"] = "<sup>2</sup> Obsolete." internal_notes_genders_old["mixed"] = {"m"} declensions["proper"] = { ["nom_m"] = "", ["nom_n"] = nil, ["nom_f"] = "а́", ["nom_p"] = "ы́", ["gen_m"] = "а́", ["gen_f"] = "о́й", ["gen_p"] = "ы́х", ["dat_m"] = "у́", ["dat_f"] = "о́й", ["dat_p"] = "ы́м", ["acc_f"] = "у́", ["acc_n"] = nil, ["ins_m"] = "ы́м", ["ins_f"] = {"о́й", "о́ю1"}, ["ins_p"] = "ы́ми", ["pre_m"] = "е́", ["pre_f"] = "о́й", ["pre_p"] = "ы́х", } declensions["stressed-proper"] = declensions["proper"] for _, decl in ipairs({"proper", "stressed-proper"}) do internal_notes_table[decl] = "<sup>1</sup> Rare." internal_notes_table_old[decl] = "<sup>1</sup> Rare." internal_notes_genders[decl] = {"f"} internal_notes_genders_old[decl] = {"f"} end declensions["$"] = { ["nom_m"] = "", ["nom_n"] = "", ["nom_f"] = "", ["nom_p"] = "", ["gen_m"] = "", ["gen_f"] = "", ["gen_p"] = "", ["dat_m"] = "", ["dat_f"] = "", ["dat_p"] = "", ["acc_f"] = "", -- don't do this; instead we default it to nom_n -- ["acc_n"] = "", ["ins_m"] = "", ["ins_f"] = "", ["ins_p"] = "", ["pre_m"] = "", ["pre_f"] = "", ["pre_p"] = "", -- for old-style templates, два, оба, compounds of два ["nom_mp"] = "", ["nom_fp"] = "", ["gen_mp"] = "", ["gen_fp"] = "", ["dat_mp"] = "", ["dat_fp"] = "", -- don't do this; instead we default them to nom_mp, nom_fp -- ["acc_mp"] = "", -- ["acc_fp"] = "", ["ins_mp"] = "", ["ins_fp"] = "", ["pre_mp"] = "", ["pre_fp"] = "", } declensions_old["ый"] = { ["nom_m"] = "ый", ["nom_n"] = "ое", ["nom_f"] = "ая", ["nom_mp"] = "ые", ["nom_p"] = "ыя", ["gen_m"] = "аго", ["gen_f"] = "ой", ["gen_p"] = "ыхъ", ["dat_m"] = "ому", ["dat_f"] = "ой", ["dat_p"] = "ымъ", ["acc_f"] = "ую", ["acc_n"] = "ое", ["ins_m"] = "ымъ", ["ins_f"] = {"ой", "ою"}, ["ins_p"] = "ыми", ["pre_m"] = "омъ", ["pre_f"] = "ой", ["pre_p"] = "ыхъ", } declensions_old["ій"] = { ["nom_m"] = "ій", ["nom_n"] = "ее", ["nom_f"] = "яя", ["nom_mp"] = "іе", ["nom_p"] = "ія", ["gen_m"] = "яго", ["gen_f"] = "ей", ["gen_p"] = "ихъ", ["dat_m"] = "ему", ["dat_f"] = "ей", ["dat_p"] = "имъ", ["acc_f"] = "юю", ["acc_n"] = "ее", ["ins_m"] = "имъ", ["ins_f"] = {"ей", "ею"}, ["ins_p"] = "ими", ["pre_m"] = "емъ", ["pre_f"] = "ей", ["pre_p"] = "ихъ", } declensions_old["ой"] = { ["nom_m"] = "о́й", ["nom_n"] = "о́е", ["nom_f"] = "а́я", ["nom_mp"] = "ы́е", ["nom_p"] = "ы́я", ["gen_m"] = {"а́го", "о́го"}, ["gen_f"] = "о́й", ["gen_p"] = "ы́хъ", ["dat_m"] = "о́му", ["dat_f"] = "о́й", ["dat_p"] = "ы́мъ", ["acc_f"] = "у́ю", ["acc_n"] = "о́е", ["ins_m"] = "ы́мъ", ["ins_f"] = {"о́й", "о́ю"}, ["ins_p"] = "ы́ми", ["pre_m"] = "о́мъ", ["pre_f"] = "о́й", ["pre_p"] = "ы́хъ", } declensions_old["ьій"] = { ["nom_m"] = "ій", ["nom_n"] = "ье", ["nom_f"] = "ья", ["nom_p"] = "ьи", ["gen_m"] = "ьяго", ["gen_f"] = "ьей", ["gen_p"] = "ьихъ", ["dat_m"] = "ьему", ["dat_f"] = "ьей", ["dat_p"] = "ьимъ", ["acc_f"] = "ью", ["acc_n"] = "ье", ["ins_m"] = "ьимъ", ["ins_f"] = {"ьей", "ьею"}, ["ins_p"] = "ьими", ["pre_m"] = "ьемъ", ["pre_f"] = "ьей", ["pre_p"] = "ьихъ", } declensions_old["ьей"] = { ["nom_m"] = "е́й", ["nom_n"] = "ьё", ["nom_f"] = "ья́", ["nom_p"] = "ьи́", ["gen_m"] = "ьего́", ["gen_f"] = "ье́й", ["gen_p"] = "ьи́хъ", ["dat_m"] = "ьему́", ["dat_f"] = "ье́й", ["dat_p"] = "ьи́мъ", ["acc_f"] = "ью́", ["acc_n"] = "ьё", ["ins_m"] = "ьи́мъ", ["ins_f"] = {"ье́й", "ье́ю"}, ["ins_p"] = "ьи́ми", ["pre_m"] = "ьёмъ", ["pre_f"] = "ье́й", ["pre_p"] = "ьи́хъ", } declensions_old["short"] = { ["nom_m"] = "ъ", ["nom_n"] = "о", ["nom_f"] = "а", ["nom_p"] = "ы", ["gen_m"] = "а", ["gen_f"] = "ой", ["gen_p"] = "ыхъ", ["dat_m"] = "у", ["dat_f"] = "ой", ["dat_p"] = "ымъ", ["acc_f"] = "у", ["acc_n"] = "о", ["ins_m"] = "ымъ", ["ins_f"] = {"ой", "ою"}, ["ins_p"] = "ыми", ["pre_m"] = "омъ", ["pre_f"] = "ой", ["pre_p"] = "ыхъ", } declensions_old["stressed-short"] = mw.clone(declensions_old["short"]) declensions_old["stressed-short"]["pre_m"] = "ѣ́" declensions_old["й-short"] = { ["nom_m"] = "й", ["nom_n"] = "ё", ["nom_f"] = "я́", ["nom_p"] = "и́", ["gen_m"] = "его́", ["gen_f"] = {"е́й", "ея́"}, ["gen_p"] = "и́хъ", ["dat_m"] = "ему́", ["dat_f"] = "е́й", ["dat_p"] = "и́мъ", ["acc_f"] = "ю́", ["acc_n"] = "ё", ["ins_m"] = "и́мъ", ["ins_f"] = {"е́й", "е́ю"}, ["ins_p"] = "и́ми", ["pre_m"] = "ёмъ", ["pre_f"] = "е́й", ["pre_p"] = "и́хъ", } declensions_old["mixed"] = { ["nom_m"] = "ъ", ["nom_n"] = "о", ["nom_f"] = "а", ["nom_p"] = "ы", ["gen_m"] = {"аго", "а1"}, ["gen_f"] = "ой", ["gen_p"] = "ыхъ", ["dat_m"] = {"ому", "у1"}, ["dat_f"] = "ой", ["dat_p"] = "ымъ", ["acc_f"] = "у", ["acc_n"] = "о", ["ins_m"] = "ымъ", ["ins_f"] = {"ой", "ою"}, ["ins_p"] = "ыми", ["pre_m"] = "омъ", ["pre_f"] = "ой", ["pre_p"] = "ыхъ", } declensions_old["proper"] = { ["nom_m"] = "ъ", ["nom_n"] = nil, ["nom_f"] = "а́", ["nom_p"] = "ы́", ["gen_m"] = "а́", ["gen_f"] = "о́й", ["gen_p"] = "ы́хъ", ["dat_m"] = "у́", ["dat_f"] = "о́й", ["dat_p"] = "ы́мъ", ["acc_f"] = "у́", ["acc_n"] = nil, ["ins_m"] = "ы́мъ", ["ins_f"] = {"о́й", "о́ю1"}, ["ins_p"] = "ы́ми", ["pre_m"] = "ѣ́", ["pre_f"] = "о́й", ["pre_p"] = "ы́хъ", } declensions_old["stressed-proper"] = declensions_old["proper"] declensions_old["$"] = declensions["$"] local function frob_genitive_masc(decl) -- signal to combine_stem_and_suffix() to use the special tr_adj() -- function so that -го gets transliterated to -vo if type(decl["gen_m"]) == "table" then local entries = {} for _, entry in ipairs(decl["gen_m"]) do table.insert(entries, rsub(entry, "го$", "го<adj>")) end decl["gen_m"] = entries else decl["gen_m"] = rsub(decl["gen_m"], "го$", "го<adj>") end end -- Frob declensions, adding <adj> to gen_m forms ending in -го. This is -- a signal to add manual translit early on that renders -го as -vo. for _, decl in pairs(declensions_old) do frob_genitive_masc(decl) end for _, decl in pairs(declensions) do frob_genitive_masc(decl) end -------------------------------------------------------------------------- -- Declension functions -- -------------------------------------------------------------------------- local function attach_unstressed(args, suf, short) local stem, stemtr if short then stem, stemtr = args.short_stem, args.short_stemtr else stem, stemtr = args.stem, args.stemtr end if suf == nil then return nil elseif nom.nonsyllabic_suffixes[suf] then if not args.bare then return nil elseif rfind(args.bare, "[йьъ]$") then return {args.bare, args.baretr} elseif suf == "ъ" then return com.concat_russian_tr(args.bare, args.baretr, suf, nil, "dopair") else return {args.bare, args.baretr} end end suf = com.make_unstressed(suf) local rules = nom.unstressed_rules[ulower(usub(stem, -1))] -- The parens around the return value drop all but the first return value return (nom.combine_stem_and_suffix(stem, stemtr, suf, rules, args.old)) end local function attach_stressed(args, suf, short) local ustem, ustemtr if short then ustem, ustemtr = args.short_ustem, args.short_ustemtr else ustem, ustemtr = args.ustem, args.ustemtr end if suf == nil then return nil elseif not rfind(suf, "[ё́]") then -- if suf has no "ё" or accent marks return attach_unstressed(args, suf, short) end local rules = nom.stressed_rules[ulower(usub(ustem, -1))] -- The parens around the return value drop all but the first return value return (nom.combine_stem_and_suffix(ustem, ustemtr, suf, rules, args.old)) end local function attach_both(args, suf, short) local results = {} -- Examples with stems with ё on Zaliznyak p. 61 list the -- ending-stressed forms first, so go with that. -- NOTE: This assumes we get one value returned, not a list of such values. table.insert(results, attach_stressed(args, suf, short)) table.insert(results, attach_unstressed(args, suf, short)) return results end local function attach_with(args, suf, fun, short) if type(suf) == "table" then local all_combineds = {} for _, x in ipairs(suf) do local combineds = attach_with(args, x, fun, short) for _, combined in ipairs(combineds) do table.insert(all_combineds, combined) end end return all_combineds else local funval = fun(args, suf, short) if funval then local tbl = {} assert(type(funval) == "table") if type(funval[1]) ~= "table" then funval = {funval} end for _, x in ipairs(funval) do table.insert(tbl, com.concat_paired_russian_tr(x, args.real_suffix)) end return tbl else return {} end end end local function gen_form(args, decl, case, fun) if not args.forms[case] then args.forms[case] = {} end insert_forms_into_existing_forms(args.forms[case], attach_with(args, decl[case], fun, false)) end decline = function(args, decl, stressed) local attacher = stressed and attach_stressed or attach_unstressed for _, case in ipairs(long_cases) do gen_form(args, decl, case, attacher) end end canonicalize_override = function(args, case) local val = args[case] if not val then return nil end val = rsplit(val, "%s*,%s*") -- auto-accent/check for necessary accents local newvals = {} for _, v in ipairs(val) do local ru, tr = com.split_russian_tr(v) if not args.allow_unaccented then if tr and com.is_unstressed(ru) ~= com.is_unstressed(tr) then error("Override " .. ru .. " and translit " .. tr .. " must have same accent pattern") end -- it's safe to accent monosyllabic stems if com.is_monosyllabic(ru) then ru, tr = com.make_ending_stressed(ru, tr) elseif com.needs_accents(ru) then error("Override " .. ru .. " for case " .. case .. " requires an accent") end end table.insert(newvals, {ru, tr}) end val = newvals return val end handle_forms_and_overrides = function(args, short_forms_allowed) local f = args.forms local function append_note_all(case, value) value = com.split_russian_tr(value, "dopair") if f[case] then for i=1,#f[case] do f[case][i] = com.concat_paired_russian_tr(f[case][i], value) end end end local function append_note_last(case, value, gt_one) value = com.split_russian_tr(value, "dopair") if f[case] then local lastarg = #f[case] if lastarg > (gt_one and 1 or 0) then f[case][lastarg] = com.concat_paired_russian_tr(f[case][lastarg], value) end end end for _, case in ipairs(long_cases) do if args[case .. "_tail"] then append_note_last(case, args[case .. "_tail"]) end if args[case .. "_tailall"] then append_note_all(case, args[case .. "_tailall"]) end args[case] = canonicalize_override(args, case) or f[case] end for _, case in ipairs(short_cases) do if short_forms_allowed then if args[case .. "_tail"] then append_note_last(case, args[case .. "_tail"]) end if args[case .. "_tailall"] then append_note_all(case, args[case .. "_tailall"]) end if args.shorttailall then append_note_all(case, args.shorttailall) end if args.shorttail then append_note_last(case, args.shorttail, ">1") end args[case] = canonicalize_override(args, case) or f[case] else args[case] = nil end end -- Convert an empty list to nil, so that an mdash is inserted. This happens, -- for example, with words like голубой where args.bare is set to nil. for _, case in ipairs(all_cases) do if args[case] and #args[case] == 0 then args[case] = nil end end -- default acc_n to nom_n; applies chiefly in the indeclinable declension -- (used with manual declension tables) if not args.acc_n then args.acc_n = args.nom_n end -- default inanimate/animate accusative variants as appropriate; this is -- almost always correct, but not with e.g. два́дцать оди́н, where the -- masculine animate accusative два́дцать одного́ differs from both the -- masculine nominative два́дцать оди́н and the masculine genitive -- двадцати́ одного́. if not args.acc_m_an then args.acc_m_an = args.gen_m end if not args.acc_m_in then args.acc_m_in = args.nom_m end -- Compounds of два do not have animacy; everything else does. -- Only copy either the with-animacy or without-animacy variants to avoid -- having both forms in {{ru-generate-adj-forms}}. if args.special ~= "cdva" then if not args.acc_p_an then args.acc_p_an = args.gen_p end if not args.acc_p_in then args.acc_p_in = args.nom_p end if not args.acc_mp_an then args.acc_mp_an = args.gen_mp end if not args.acc_mp_in then args.acc_mp_in = args.nom_mp end if not args.acc_fp_an then args.acc_fp_an = args.gen_fp end if not args.acc_fp_in then args.acc_fp_in = args.nom_fp end else if not args.acc_mp then args.acc_mp = args.nom_mp end if not args.acc_fp then args.acc_fp = args.nom_fp end end end -------------------------------------------------------------------------- -- Short adjective declension -- -------------------------------------------------------------------------- short_declensions["ый"] = { m="", f="а́", n="о́", p="ы́" } short_declensions["ой"] = short_declensions["ый"] short_declensions["ий"] = { m="ь", f="я́", n="е́", p="и́" } short_declensions_old["ый"] = { m="ъ", f="а́", n="о́", p="ы́" } short_declensions_old["ой"] = short_declensions_old["ый"] short_declensions_old["ій"] = short_declensions["ий"] -- Short adjective stress patterns: -- "-" = stem-stressed -- "+" = ending-stressed (drawn onto the last syllable of stem in masculine) -- "-+" = both possibilities short_stress_patterns["a"] = { m="-", f="-", n="-", p="-" } short_stress_patterns["a'"] = { m="-", f="-+", n="-", p="-" } short_stress_patterns["b"] = { m="+", f="+", n="+", p="+" } short_stress_patterns["b'"] = { m="+", f="+", n="+", p="-+" } short_stress_patterns["c"] = { m="-", f="+", n="-", p="-" } short_stress_patterns["c'"] = { m="-", f="+", n="-", p="-+" } short_stress_patterns["c''"] = { m="-", f="+", n="-+", p="-+" } local function gen_short_form(args, decl, case, fun, datedrare) if not args.forms["short_" .. case] then args.forms["short_" .. case] = {} end local inserted = insert_forms_into_existing_forms( args.forms["short_" .. case], attach_with(args, decl[case], fun, true), (datedrare == "dated" and datedrare == "rare") and "*" or nil) if datedrare == "dated" and inserted then m_table.insertIfNot(args.internal_notes, "<sup>*</sup> Dated.") elseif datedrare == "rare" and inserted then m_table.insertIfNot(args.internal_notes, "<sup>*</sup> Rare.") end end local attachers = { ["+"] = attach_stressed, ["-"] = attach_unstressed, ["-+"] = attach_both, } decline_short = function(args, decl, stress_pattern, dated) if stress_pattern then for _, case in ipairs({"m", "f", "n", "p"}) do gen_short_form(args, decl, case, attachers[stress_pattern[case]], dated) end end end -------------------------------------------------------------------------- -- Create the table -- -------------------------------------------------------------------------- local title_temp = [=[Declension of <b lang="ru" class="Cyrl">{lemma}</b> ({short_class_title})]=] local old_title_temp = [=[Pre-reform declension of <b lang="ru" class="Cyrl">{lemma}</b> ({short_class_title})]=] local title_temp_no_short_msg = [=[Declension of <b lang="ru" class="Cyrl">{lemma}</b>]=] local old_title_temp_no_short_msg = [=[Pre-reform declension of <b lang="ru" class="Cyrl">{lemma}</b>]=] local template = nil local full_clause = nil local template_no_neuter = nil local full_clause_no_neuter = nil local surname_full_clause = nil local template_mp = nil local full_clause_mp = nil local template_mp_no_neuter = nil local full_clause_mp_no_neuter = nil local surname_full_clause_mp = nil local template_dva = nil local full_clause_dva = nil local full_clause_compound_dva = nil local template_oba = nil local full_clause_oba = nil local short_clause_separator = nil local short_clause = nil local short_clause_no_neuter_separator = nil local short_clause_no_neuter = nil local short_clause_mp_separator = nil local short_clause_mp = nil local short_clause_mp_no_neuter_separator = nil local short_clause_mp_no_neuter = nil local internal_notes_template = nil local notes_template = nil local function get_accel_forms(old, special, has_nom_mp) return { -- used with all variants nom_m = "nom|m|s", nom_f = "nom|f|s", nom_n = "nom|n|s", -- not used with special; applies to all genders normally but only -- feminine and neuter with old=1 nom_p = old and has_nom_mp and "nom|f//n|p" or "nom|p", -- only used with old=1 or special; applies to the masculine and neuter if -- special, but only masculine if old=1 nom_mp = special and "nom|m//n|p" or "nom|m|p", -- only used with special nom_fp = "nom|f|p", -- the remaining singulars and non-gendered plurals used with all variants -- except special == "oba" gen_m = "gen|m//n|s", gen_f = "gen|f|s", gen_p = "gen|p", dat_m = "dat|m//n|s", dat_f = "dat|f|s", dat_p = "dat|p", acc_m_an = "an|acc|m|s", acc_m_in = "in|acc|m|s", acc_f = "acc|f|s", acc_n = "acc|n|s", -- the following two not used with special in ("dva", "oba"); applies to -- all genders normally but only feminine and neuter with old=1 acc_p_an = old and has_nom_mp and "an|acc|f//n|p" or "an|acc|p", acc_p_in = old and has_nom_mp and "in|acc|f//n|p" or "in|acc|p", -- the following two only used with old=1 or special in ("dva|oba"); -- applies to the masculine and neuter if special, but only masculine if -- old=1 acc_mp_an = special and "an|acc|m//n|p" or "an|acc|m|p", acc_mp_in = special and "in|acc|m//n|p" or "in|acc|m|p", -- the following two only used with special in ("dva", "oba") acc_fp_an = "an|acc|f|p", acc_fp_in = "in|acc|f|p", -- the next 6 are used with all variants except special == "oba" ins_m = "ins|m//n|s", ins_f = "ins|f|s", ins_p = "ins|p", pre_m = "pre|m//n|s", pre_f = "pre|f|s", pre_p = "pre|p", -- the following two gendered plurals are only used with special == "cdva" acc_mp = "acc|m//n|p", acc_fp = "acc|f|p", -- the remaining gendered plurals are only used with special == "oba" gen_mp = "gen|m//n|p", gen_fp = "gen|f|p", dat_mp = "dat|m//n|p", dat_fp = "dat|f|p", ins_mp = "ins|m//n|p", ins_fp = "ins|f|p", pre_mp = "pre|m//n|p", pre_fp = "pre|f|p", -- short forms short_m = "short|m|s", short_f = "short|f|s", short_n = "short|n|s", short_p = "short|p", } end -- Make the table make_table = function(args) local lemma_forms = args.special and args.nom_mp or args.nofull and args.short_m or args.nom_m args.lemma = m_links.remove_links(nom.show_form(lemma_forms, true, nil, nil)) args.title = args.title or strutils.format( (args.special or args.manual) and args.old and old_title_temp_no_short_msg or (args.special or args.manual) and title_temp_no_short_msg or args.old and old_title_temp or title_temp, args) local has_nom_mp = args.nom_mp and not (#args.nom_mp == 1 and args.nom_mp[1][1] == "-") local accel_forms = get_accel_forms(args.old, args.special, has_nom_mp) for _, case in ipairs(all_cases) do if args[case] then local accel_form = accel_forms[case] if not accel_form then error("Internal error: Unrecognized case " .. case .. " when looking up accelerator form") end if args.noneuter or args.real_surname then accel_form = rsub(accel_form, "//n", "") end if args.real_surname then accel_form = rsub(accel_form, "an|", "") end args[case] = nom.show_form(args[case], false, accel_form, lemma_forms) else args[case] = nil end end local temp, fullc if args.special == "oba" then temp = template_oba fullc = full_clause_oba elseif args.special == "dva" then temp = template_dva fullc = full_clause_dva elseif args.special == "cdva" then temp = template_dva -- no template_cdva fullc = full_clause_compound_dva elseif args.real_surname and args.old and has_nom_mp then temp = template_mp_no_neuter fullc = surname_full_clause_mp elseif args.real_surname then temp = template_no_neuter fullc = surname_full_clause elseif args.noneuter and args.old and has_nom_mp then temp = template_mp_no_neuter fullc = full_clause_mp_no_neuter elseif args.noneuter then temp = template_no_neuter fullc = full_clause_no_neuter elseif args.old and has_nom_mp then temp = template_mp fullc = full_clause_mp else temp = template fullc = full_clause end if args.old then if has_nom_mp then if args.short_m or args.short_n or args.short_f or args.short_p then args.short_m = args.short_m or "&mdash;" args.short_n = args.short_n or "&mdash;" args.short_f = args.short_f or "&mdash;" args.short_p = args.short_p or "&mdash;" args.shortsep = not args.nofull and ( args.noneuter and short_clause_mp_no_neuter_separator or short_clause_mp_separator ) or "" args.short_clause = strutils.format(args.noneuter and short_clause_mp_no_neuter or short_clause_mp, args) else args.short_clause = "" end else args.short_clause = "" end else if args.short_m or args.short_n or args.short_f or args.short_p then args.short_m = args.short_m or "&mdash;" args.short_n = args.short_n or "&mdash;" args.short_f = args.short_f or "&mdash;" args.short_p = args.short_p or "&mdash;" args.shortsep = not args.nofull and ( args.noneuter and short_clause_no_neuter_separator or short_clause_separator ) or "" args.short_clause = strutils.format(args.noneuter and short_clause_no_neuter or short_clause, args) else args.short_clause = "" end end if not args.nofull then args.full_clause = strutils.format(fullc, args) else args.full_clause = "" end args.internal_notes = table.concat(args.internal_notes, "<br />") args.internal_notes_clause = #args.internal_notes > 0 and strutils.format(internal_notes_template, args) or "" args.notes_clause = args.notes and strutils.format(notes_template, args) or "" return strutils.format(temp, args) end -- Used for new-style templates short_clause_separator = [===[ ! style="height:0.2em;background:var(--wikt-palette-lightblue)" colspan=6 | |- ]===] -- Used for new-style templates short_clause = [===[ {shortsep}! style="background:var(--wikt-palette-lighterblue)" colspan=2 | short form | data-accel-col=1 | {short_m} | data-accel-col=3 | {short_n} | data-accel-col=2 | {short_f} | data-accel-col=4 | {short_p}]===] -- Used for new-style templates short_clause_no_neuter_separator = [===[ ! style="height:0.2em;background:var(--wikt-palette-lightblue)" colspan=5 | |- ]===] -- Used for new-style templates short_clause_no_neuter = [===[ {shortsep}! style="background:var(--wikt-palette-lighterblue)" colspan=2 | short form | data-accel-col=1 | {short_m} | data-accel-col=2 | {short_f} | data-accel-col=4 | {short_p}]===] -- Used for old-style templates short_clause_mp_separator = [===[ ! style="height:0.2em;background:var(--wikt-palette-lightblue)" colspan=7 | |- ]===] -- Used for old-style templates short_clause_mp = [===[ {shortsep}! style="background:var(--wikt-palette-lighterblue)" colspan=2 | short form | data-accel-col=1 | {short_m} | data-accel-col=3 | {short_n} | data-accel-col=2 | {short_f} | data-accel-col=4 colspan=2 | {short_p}]===] -- Used for old-style templates short_clause_mp_no_neuter_separator = [===[ ! style="height:0.2em;background:var(--wikt-palette-lightblue)" colspan=6 | |- ]===] -- Used for old-style templates short_clause_mp_no_neuter = [===[ {shortsep}! style="background:var(--wikt-palette-lighterblue)" colspan=2 | short form | data-accel-col=1 | {short_m} | data-accel-col=2 | {short_f} | data-accel-col=4 colspan=2 | {short_p}]===] -- Used for both new-style and old-style templates notes_template = [===[ <div style="width:100%;text-align:left"> <div style="display:inline-block;text-align:left;padding-left:1em;padding-right:1em"> {notes} </div></div> ]===] -- Used for both new-style and old-style templates internal_notes_template = rsub(notes_template, "notes", "internal_notes") local function template_prelude(min_width) min_width = min_width or "70" return rsub([===[ <div> <div class="NavFrame"> <div class="NavHead" style="background:var(--wikt-palette-lighterblue)">{title}</div> <div class="NavContent"> {\op}| style="background:var(--wikt-palette-paleblue);text-align:center" class="inflection-table" |- ]===], "MINWIDTH", min_width) end local function template_postlude() return [===[{full_clause}|-{short_clause} |{\cl}{internal_notes_clause}{notes_clause}</div></div></div>]===] end -- Used for both new-style and old-style templates template = template_prelude() .. [===[ ! style="width:20%;background:var(--wikt-palette-lightblue)" colspan=2 | ! style="background:var(--wikt-palette-lightblue)" | masculine ! style="background:var(--wikt-palette-lightblue)" | neuter ! style="background:var(--wikt-palette-lightblue)" | feminine ! style="background:var(--wikt-palette-lightblue)" | plural ]===] .. template_postlude() -- Used for both new-style and old-style templates full_clause = [===[ |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | nominative | data-accel-col=1 | {nom_m} | data-accel-col=3 | {nom_n} | data-accel-col=2 | {nom_f} | data-accel-col=4 | {nom_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | genitive | data-accel-col=1 colspan=2 | {gen_m} | data-accel-col=2 | {gen_f} | data-accel-col=4 | {gen_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | dative | data-accel-col=1 colspan=2 | {dat_m} | data-accel-col=2 | {dat_f} | data-accel-col=4 | {dat_p} |- ! style="background:var(--wikt-palette-lighterblue)" rowspan=2 | accusative ! style="background:var(--wikt-palette-lighterblue)" | animate | data-accel-col=1 | {acc_m_an} | data-accel-col=3 rowspan=2 | {acc_n} | data-accel-col=2 rowspan=2 | {acc_f} | data-accel-col=4 | {acc_p_an} |- ! style="background:var(--wikt-palette-lighterblue)" | inanimate | data-accel-col=1 | {acc_m_in} | data-accel-col=4 | {acc_p_in} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | instrumental | data-accel-col=1 colspan=2 | {ins_m} | data-accel-col=2 | {ins_f} | data-accel-col=4 | {ins_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | prepositional | data-accel-col=1 colspan=2 | {pre_m} | data-accel-col=2 | {pre_f} | data-accel-col=4 | {pre_p} ]===] -- Used for both new-style and old-style templates template_no_neuter = template_prelude("55") .. [===[ ! style="width:20%;background:var(--wikt-palette-lightblue)" colspan=2 | ! style="background:var(--wikt-palette-lightblue)" | masculine ! style="background:var(--wikt-palette-lightblue)" | feminine ! style="background:var(--wikt-palette-lightblue)" | plural ]===] .. template_postlude() -- Used for both new-style and old-style templates full_clause_no_neuter = [===[ |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | nominative | data-accel-col=1 | {nom_m} | data-accel-col=2 | {nom_f} | data-accel-col=4 | {nom_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | genitive | data-accel-col=1 | {gen_m} | data-accel-col=2 | {gen_f} | data-accel-col=4 | {gen_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | dative | data-accel-col=1 | {dat_m} | data-accel-col=2 | {dat_f} | data-accel-col=4 | {dat_p} |- ! style="background:var(--wikt-palette-lighterblue)" rowspan=2 | accusative ! style="background:var(--wikt-palette-lighterblue)" | animate | data-accel-col=1 | {acc_m_an} | data-accel-col=2 rowspan=2 | {acc_f} | data-accel-col=4 | {acc_p_an} |- ! style="background:var(--wikt-palette-lighterblue)" | inanimate | data-accel-col=1 | {acc_m_in} | data-accel-col=4 | {acc_p_in} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | instrumental | data-accel-col=1 | {ins_m} | data-accel-col=2 | {ins_f} | data-accel-col=4 | {ins_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | prepositional | data-accel-col=1 | {pre_m} | data-accel-col=2 | {pre_f} | data-accel-col=4 | {pre_p} ]===] -- Used for both new-style and old-style templates surname_full_clause = [===[ |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | nominative | data-accel-col=1 | {nom_m} | data-accel-col=2 | {nom_f} | data-accel-col=4 | {nom_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | genitive | data-accel-col=1 | {gen_m} | data-accel-col=2 | {gen_f} | data-accel-col=4 | {gen_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | dative | data-accel-col=1 | {dat_m} | data-accel-col=2 | {dat_f} | data-accel-col=4 | {dat_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | accusative | data-accel-col=1 | {acc_m_an} | data-accel-col=2 | {acc_f} | data-accel-col=4 | {acc_p_an} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | instrumental | data-accel-col=1 | {ins_m} | data-accel-col=2 | {ins_f} | data-accel-col=4 | {ins_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | prepositional | data-accel-col=1 | {pre_m} | data-accel-col=2 | {pre_f} | data-accel-col=4 | {pre_p} ]===] -- Used for old-style templates template_mp = template_prelude() .. [===[ ! style="width:20%;background:var(--wikt-palette-lightblue)" colspan=2 | ! style="background:var(--wikt-palette-lightblue)" | masculine ! style="background:var(--wikt-palette-lightblue)" | neuter ! style="background:var(--wikt-palette-lightblue)" | feminine ! style="background:var(--wikt-palette-lightblue)" | m. plural ! style="background:var(--wikt-palette-lightblue)" | n./f. plural ]===] .. template_postlude() -- Used for old-style templates full_clause_mp = [===[ |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | nominative | data-accel-col=1 | {nom_m} | data-accel-col=3 | {nom_n} | data-accel-col=2 | {nom_f} | data-accel-col=4 | {nom_mp} | data-accel-col=5 | {nom_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | genitive | data-accel-col=1 colspan=2 | {gen_m} | data-accel-col=2 | {gen_f} | data-accel-col=4 colspan=2 | {gen_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | dative | data-accel-col=1 colspan=2 | {dat_m} | data-accel-col=2 | {dat_f} | data-accel-col=4 colspan=2 | {dat_p} |- ! style="background:var(--wikt-palette-lighterblue)" rowspan=2 | accusative ! style="background:var(--wikt-palette-lighterblue)" | animate | data-accel-col=1 | {acc_m_an} | data-accel-col=3 rowspan=2 | {acc_n} | data-accel-col=2 rowspan=2 | {acc_f} | data-accel-col=4 colspan=2 | {acc_p_an} |- ! style="background:var(--wikt-palette-lighterblue)" | inanimate | data-accel-col=1 | {acc_m_in} | data-accel-col=4 | {acc_mp_in} | data-accel-col=5 | {acc_p_in} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | instrumental | data-accel-col=1 colspan=2 | {ins_m} | data-accel-col=2 | {ins_f} | data-accel-col=4 colspan=2 | {ins_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | prepositional | data-accel-col=1 colspan=2 | {pre_m} | data-accel-col=2 | {pre_f} | data-accel-col=4 colspan=2 | {pre_p} ]===] -- Used for old-style templates template_mp_no_neuter = template_prelude("60") .. [===[ ! style="width:20%;background:var(--wikt-palette-lightblue)" colspan=2 | ! style="background:var(--wikt-palette-lightblue)" | masculine ! style="background:var(--wikt-palette-lightblue)" | feminine ! style="background:var(--wikt-palette-lightblue)" | m. plural ! style="background:var(--wikt-palette-lightblue)" | f. plural ]===] .. template_postlude() -- Used for old-style templates full_clause_mp_no_neuter = [===[ |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | nominative | data-accel-col=1 | {nom_m} | data-accel-col=2 | {nom_f} | data-accel-col=4 | {nom_mp} | data-accel-col=5 | {nom_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | genitive | data-accel-col=1 | {gen_m} | data-accel-col=2 | {gen_f} | data-accel-col=4 colspan=2 | {gen_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | dative | data-accel-col=1 | {dat_m} | data-accel-col=2 | {dat_f} | data-accel-col=4 colspan=2 | {dat_p} |- ! style="background:var(--wikt-palette-lighterblue)" rowspan=2 | accusative ! style="background:var(--wikt-palette-lighterblue)" | animate | data-accel-col=1 | {acc_m_an} | data-accel-col=2 rowspan=2 | {acc_f} | data-accel-col=4 colspan=2 | {acc_p_an} |- ! style="background:var(--wikt-palette-lighterblue)" | inanimate | data-accel-col=1 | {acc_m_in} | data-accel-col=4 | {acc_mp_in} | data-accel-col=5 | {acc_p_in} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | instrumental | data-accel-col=1 | {ins_m} | data-accel-col=2 | {ins_f} | data-accel-col=4 colspan=2 | {ins_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | prepositional | data-accel-col=1 | {pre_m} | data-accel-col=2 | {pre_f} | data-accel-col=4 colspan=2 | {pre_p} ]===] -- Used for some old-style templates surname_full_clause_mp = [===[ |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | nominative | data-accel-col=1 | {nom_m} | data-accel-col=2 | {nom_f} | data-accel-col=4 | {nom_mp} | data-accel-col=5 | {nom_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | genitive | data-accel-col=1 | {gen_m} | data-accel-col=2 | {gen_f} | data-accel-col=4 colspan=2 | {gen_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | dative | data-accel-col=1 | {dat_m} | data-accel-col=2 | {dat_f} | data-accel-col=4 colspan=2 | {dat_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | accusative | data-accel-col=1 | {acc_m_an} | data-accel-col=2 | {acc_f} | data-accel-col=4 colspan=2 | {acc_p_an} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | instrumental | data-accel-col=1 | {ins_m} | data-accel-col=2 | {ins_f} | data-accel-col=4 colspan=2 | {ins_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | prepositional | data-accel-col=1 | {pre_m} | data-accel-col=2 | {pre_f} | data-accel-col=4 colspan=2 | {pre_p} ]===] -- Used for два and compounds template_dva = template_prelude("55") .. [===[ ! style="width:20%;background:var(--wikt-palette-lightblue)" colspan=2 | ! style="background:var(--wikt-palette-lightblue)" | masculine/neuter ! style="background:var(--wikt-palette-lightblue)" | feminine ]===] .. template_postlude() -- Used for both new-style and old-style templates of два (only for два itself, -- which has an animacy distinction; not for compounds) full_clause_dva = [===[ |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | nominative | data-accel-col=4 | {nom_mp} | data-accel-col=5 | {nom_fp} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | genitive | data-accel-col=4 colspan=2 | {gen_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | dative | data-accel-col=4 colspan=2 | {dat_p} |- ! style="background:var(--wikt-palette-lighterblue)" rowspan=2 | accusative ! style="background:var(--wikt-palette-lighterblue)" | animate | data-accel-col=4 colspan=2 | {acc_p_an} |- ! style="background:var(--wikt-palette-lighterblue)" | inanimate | data-accel-col=4 | {acc_mp_in} | data-accel-col=5 | {acc_fp_in} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | instrumental | data-accel-col=4 colspan=2 | {ins_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | prepositional | data-accel-col=4 colspan=2 | {pre_p} ]===] -- Used for both new-style and old-style templates of compounds of два full_clause_compound_dva = [===[ |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | nominative | data-accel-col=4 | {nom_mp} | data-accel-col=5 | {nom_fp} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | genitive | data-accel-col=4 colspan=2 | {gen_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | dative | data-accel-col=4 colspan=2 | {dat_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | accusative | data-accel-col=4 | {acc_mp} | data-accel-col=5 | {acc_fp} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | instrumental | data-accel-col=4 colspan=2 | {ins_p} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | prepositional | data-accel-col=4 colspan=2 | {pre_p} ]===] -- Used for оба template_oba = template_prelude() .. [===[ ! style="width:20%;background:var(--wikt-palette-lightblue)" colspan=2 | ! style="background:var(--wikt-palette-lightblue)" | masculine ! style="background:var(--wikt-palette-lightblue)" | neuter ! style="background:var(--wikt-palette-lightblue)" | feminine ! style="background:var(--wikt-palette-lightblue)" | m./n. plural ! style="background:var(--wikt-palette-lightblue)" | f. plural ]===] .. template_postlude() -- Used for both new-style and old-style templates of оба full_clause_oba = [===[ |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | nominative | data-accel-col=1 | {nom_m} | data-accel-col=3 | {nom_n} | data-accel-col=2 | {nom_f} | data-accel-col=4 | {nom_mp} | data-accel-col=5 | {nom_fp} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | genitive | data-accel-col=1 colspan=2 | {gen_m} | data-accel-col=2 | {gen_f} | data-accel-col=4 | {gen_mp} | data-accel-col=5 | {gen_fp} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | dative | data-accel-col=1 colspan=2 | {dat_m} | data-accel-col=2 | {dat_f} | data-accel-col=4 | {dat_mp} | data-accel-col=5 | {dat_fp} |- ! style="background:var(--wikt-palette-lighterblue)" rowspan=2 | accusative ! style="background:var(--wikt-palette-lighterblue)" | animate | data-accel-col=1 | {acc_m_an} | data-accel-col=3 rowspan=2 | {acc_n} | data-accel-col=2 rowspan=2 | {acc_f} | data-accel-col=4 | {acc_mp_an} | data-accel-col=5 | {acc_fp_an} |- ! style="background:var(--wikt-palette-lighterblue)" | inanimate | data-accel-col=1 | {acc_m_in} | data-accel-col=4 | {acc_mp_in} | data-accel-col=5 | {acc_fp_in} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | instrumental | data-accel-col=1 colspan=2 | {ins_m} | data-accel-col=2 | {ins_f} | data-accel-col=4 | {ins_mp} | data-accel-col=5 | {ins_fp} |- ! style="background:var(--wikt-palette-lighterblue)" colspan=2 | prepositional | data-accel-col=1 colspan=2 | {pre_m} | data-accel-col=2 | {pre_f} | data-accel-col=4 | {pre_mp} | data-accel-col=5 | {pre_fp} ]===] return export ljdyldnivfj65ztu0wa504p25r3fgl3 Modül:User:Benwing2/ru-noun 828 1589239 5669925 2026-06-23T07:33:07Z MustafaCavlak 59368 imported from en 5669925 Scribunto text/plain --[=[ This module contains functions for creating inflection tables for Russian nouns. Author: Benwing, rewritten from early version by Wikitiki89 Form of arguments: One of the following: 1. LEMMA|DECL|PLSTEM (all arguments optional) 2. ACCENT|LEMMA|DECL|PLSTEM (all arguments optional) 3. multiple sets of arguments separated by the literal word "or" Arguments: ACCENT: Accent pattern (a b c d e f b' d' f' f''). Multiple values can be specified, separated by commas. If omitted, defaults to a or b depending on the position of stress on the lemma or explicitly-specified declension. LEMMA: Lemma form (i.e. nom sg or nom pl), with appropriately-placed stress; or the stem, if an explicit declension is specified (in this case, the declension usually looks like an ending, and the stem is the portion of the lemma minus the ending). In the first argument set (i.e. first set of arguments separated by "or"), defaults to page name; in later sets, defaults to lemma of previous set. A plural form can be given, and causes argument n= to default to n=p (plural only). Normally, an accent is required if multisyllabic, and unaccented monosyllables with automatically be stressed; prefix with * to override both behaviors. DECL: Declension field. Normally omitted to autodetect based on the lemma form; see below. PLSTEM: special plural stem (defaults to stem of lemma) Additional named arguments: a: animacy (a/an/anim = animate, i/in/inan = inanimate, b/bi/both/ai = both (listing animate first in the headword), ia = both (listing inanimate first in the headword), otherwise inanimate) n: number restriction (p = plural only, s = singular only, b = both; defaults to both unless the lemma is plural, in which case it defaults to plural only) CASE_NUM or acc_NUM_ANIM or par/loc/voc: override (or multiple values separated by commas) for case/number combination; forms auto-linked; can have raw links in it, can have an ending "note" (*, +, 1, 2, 3, etc.) pltail: Specify something (usually a * or similar) to attach to the end of the last plural form when there's more than one. Used in conjunction with notes= to indicate that alternative plural forms are obsolete, poetic, etc. pltailall: Similar pltail= but attaches to all plural forms. Typically used in conjunction with notes= to make a comment about the plural as a whole (e.g. it's mostly hypothetical, rare and awkward, etc.). sgtail, sgtailall: Same as pltail=, pltailall= but for the singular. obltail, obltailall: Same as pltail=, pltailall= but for oblique cases (not the nominative or accusative). CASE_NUM_tail: Attach the argument to the end of the last form (whether there's one or more than one) for the particular case/number combination. Note that this doesn't work quite like pltail= or sgtail= in that it doesn't skip adding the argument when there's only one form. CASE_NUM_tailall: Attach the argument to the end of all forms specified for the particular case/number combination. Similar to pltailall= or sgtailall=. suffix: Add a suffix such as ся to all forms. prefix: Add a prefix to all forms. plhyp, plhypall, CASE_NUM_hyp, etc.: Same as pltail, pltailall, CASE_NUM_tal, etc. but specify that the marked forms are mostly hypothetical or rare/awkard. Generally you will want plhypall=y to mark the plural as hypothetical. Per word named arguments: All of the above named arguments have per-word variants, e.g. a1, a2, ...; n1, n2, ...; CASE_NUM1, CASE_NUM2, ...; pltail1, pltail2, ...; etc. These apply to the individual words of a form. Case abbreviations: nom: nominative gen: genitive dat: dative acc: accusative ins: instrumental pre: prepositional par: partitive loc: locative voc: vocative Number abbreviations: sg: singular pl: plural Animacy abbreviations: an: animate in: inanimate Declension field: One of the following for regular nouns: (blank) GENDER -VARIANT GENDER-VARIANT $ DECLTYPE DECLTYPE/DECLTYPE (also, can append various special-case markers to any of the above) Or one of the following for adjectival nouns: + +ь +short, +mixed or +proper +DECLTYPE GENDER if present is m, f, n or 3f; for regular nouns, required if the lemma ends in -ь or is plural, ignored otherwise. 3f is the same as f but in the case of a plural lemma in -и, detects a third-declension feminine with singular in -ь rather than a first-declension feminine with singular in -а or -я. VARIANT is one way of requesting variant declensions (see also special case (1) for variant nom pls, and special case (2) for variant gen pls). The currently allowed values are -ья (will select a mixed declension that has some normal declension in its singular and the -ья plural declension); -ин (for animate masculine nouns in -ин with plural in -е -- note that this is autodetected in the majority of cases where the ending in -янин or -анин); -ишко (used for inanimate neuter-form diminutive masculine nouns in -ишко [also сараю́шко] with nom pl -и and colloquial feminine-ending alternants in some singular cases); -ище (similar to -ишко but used for *animate* augmentative masculine neuter-form nouns in -ище). Variants -ишко and -ище must be given with with special case (1). $ is for for invariable nouns. It are principally useful in multiword expressions where some of the words are invariable. DECLTYPE is an explicit declension type. Normally you shouldn't use this, and should instead let the declension type be autodetected based on the ending, supplying the appropriate hint if needed (gender for regular nouns, +ь for adjectives). If provided, the declension type is usually the same as the ending, and if present, the lemma field should be just the stem, without the ending. Possibilities for regular nouns are (blank) or # for hard-consonant declension, а, я, о, е or ё, е́, й, ья, ье or ьё, ь-m, ь-f, ин, ёнок or онок or енок, ёночек or оночек or еночек, мя, -а or #-а, ь-я, й-я, о-и or о-ы, -ья or #-ья, $ (invariable). Old-style (pre-reform) declensions use ъ instead of (blank), ъ-а instead of -а, ъ-ья instead of -ья, and инъ, ёнокъ/онокъ/енокъ, ёночекъ/оночекъ/еночекъ instead of the same without terminating ъ. The declensions can also be written with an accent on them; this chooses the same declension (except for е vs. е́), but causes ACCENT to default to pattern b instead of a. For adjectival nouns, you should normally supply just + and let the ending determine the declension; supply +ь in the case of a possessive adjectival noun in -ий, which have an extra -ь- in most endings compared with normal adjectival nouns in -ий, but which can't be distinguished based on the nominative singular. You can also supply +short, +mixed or +proper, which constrains the declension appropriately but still autodetects the gender-specific and stress-specific variant. If you do supply a specific declension type, as with regular nouns you need to omit the ending from the lemma field and supply just the stem. Possibilities are +ый, +ое, +ая, +ій, +ее, +яя, +ой, +о́е, +а́я, +ьій, +ье, +ья; +-short or +#-short (masc); +о-short, +о-stressed-short or +о́-short; +а-short, +а-stressed-short or +а́-short; and similar for -mixed and -proper (except there aren't any stressed mixed declensions). DECLTYPE/DECLTYPE is used for nouns with one declension in the singular and a different one in the plural, for cases that PLVARIANT and special case (1) below don't cover. Special-case markers: (1) for Zaliznyak-style alternate nominative plural ending: -а or -я for masculine, -и or -ы for neuter (2) for Zaliznyak-style alternate genitive plural ending: -ъ/none for masculine, -ей for feminine, -ов(ъ) for neuter, -ей for plural variant -ья * for reducibles (nom sg or gen pl has an extra vowel before the final consonant as compared with the stem found in other cases) ;ё for Zaliznyak-style alternation between last е in stem and ё TODO: 1. Multi-word issues: -- FIXME: Make sure internal_notes handled correctly; we may run into issues with multiple internal notes from different words, if we're not careful to use different footnote symbols for each type of footnote (which we don't do currently). [NOT DONE, MAY NOT DO] -- Handling default lemma: With multiple words, we should probably split the page name on spaces and default each word in turn [NOT DONE, MAY NOT DO] 2a. FIXME: For -ишко diminutives and -ище augmentatives, should add an appropriate category of some sort (currently marked by colloqfem= in category). 2b. FIXME: Adding a note to dat_sg also adds it to loc_sg when it exists; seems wrong. See луг. 2c. FIXME: When you have both d' and f in feminines and you use sgtail=*, you get two *'s. See User:Benwing2/test-ru-noun-debug. 3. ADJECTIVE FIXMES: 3a. FIXME: Change calls to ru-adj11 to use the new proper name support in ru-adjective. 3b. FIXME: Test that omitting a manual form in ru-adjective leaves the form as a big dash. 3c. FIXME: какой-либо and какой-то display genitives with translit -go instead of -vo. To fix this properly requires implementing real manual translit for adjectives. 3d. FIXME: Implement real manual translit for adjectives. 5. [FIXME: Consider adding an indicator in the header line when the ё/e alternation occurs. This is a bit tricky to calculate: If special case ;ё is given, but also if ё occurs in the stem and the accent pattern is as follows -- for sg-only, b' d' f' f'', also b d f if the noun is masc or 3rd-decl fem (i.e. nom-sg ending is non-syllabic); for pl-only, e f f' f'', also b b' c if the gen pl is non-syllabic; for sg/pl, any but a or b, also b if either nom sg or gen pl is non-syllabic. But it gets more complicated due to overrides. An alternative is to check all forms to see if ё is present in some but not all; but this is tricky also because e.g. reducibles frequently have ё/null alternation, which doesn't count, and some endings have е or ё in them, which also doesn't count. If we were to do it this way, we'd have to (a) count the number of е's in the form(s) with ё and verify that there's at least one form without ё and with one more е than in the form(s) with ё (and it gets trickier if different forms with ё have different numbers of е in them, although that is probably rare); and (b) ignore the appropriate endings (the best way to do this would probably be to look at the actual suffixes that were generated in args.suffixes and chop off any matching ending in the actual form(s), but also chop off final -е/ё, as well as -ев(ъ)/-ёв(ъ)/-ей/-ёй in the gen pl, which is frequently overridden, unless perhaps the stem ends in the same way). It'd probably not possible to do this in a 100% foolproof way but can be "good enough" for nearly all circumstances.] [MIGHT BE TOO MUCH WORK] 6. HEADWORD FIXMES: 6a. FIXME: In ru-headword, create a category for words whose gender doesn't match the form. (This is easy to do for ru-noun+ but harder for ru-noun. We would need to do limited autodetection of the ending: for singulars, -а/я should be feminine, -е/о/ё should be neuter, -ь should be masculine or feminine, anything else should be masculine; for plurals, -и/ы should be masculine or feminine, -а/я should be neuter except that -ія can be feminine or neuter due to old-style adjectival pluralia tantum nouns, anything else can be any gender.) 6b. FIXME: Recognize invariable nouns and indicate as indeclinable. Probably should work by checking the case forms to see if they're the same. 9. FIXME: Change stress-pattern detection and overriding to happen inside of looping over the two parts of a slash decl. Requires that the loop over the two parts happen outside of the loop over stress patterns. Requires that the category code get split into two parts, one to handle combined singular/plural categories that goes outside the two loops, and one to handle everything else that goes inside the two loops. 10. FIXME: override_matches_suffix() had a free variable reference to ARGS in it, which should have triggered an error whenever there was a nom_sg or nom_pl override but didn't. Is there an error causing this never to be called? Check. 11a. FIXME: In a multiword lemma, using loc2=+ causes only the second word to get linked instead of the whole expression. Same for par2=+, voc2=+. 11b. FIXME: Using loc=+ with a multiword lemma should do the right thing, same as if locN=+ is specified for each individual word. Instead it generates the locative as a whole from the dative, which fails e.g. if some of the words are adjectival. Same for par=+, voc=+. 13. Multi-word issues: -- Setting n=pl when auto-detecting a plural lemma. How does that interact with multi-word stuff? (DONE) -- compute_heading() -- what to do with multiple words? I assume we should display info on the first noun (non-invariable, non-adjectival), and on the first adjectival otherwise, and finally on an invariable (DONE) -- args.genders -- it should presumably come from the same word as is used in compute_heading(); but we should allow the overall gender to be overridden, at least in ru-noun+ (DONE) -- Bug in args.suffix: Gets added to every word in attach_with() and then again at the end, after pltail and such. Needs to be added to the last word only, before pltail. Need also suffixN for individual words. (DONE, NEEDS TESTING) -- Should have ..N versions of pltail and variants. (DONE, NEEDS TESTING) -- Need to handle overrides of acc_sg, acc_pl (DONE) -- Overrides of nom_sg/nom_pl should also override acc_sg/acc_pl if it was originally empty and the animacy is inanimate; similarly for gen_sg/gen_pl and animates; this needs to work both for per-word and overall overrides. (DONE) -- do_generate_forms(_multi) need to run part of make_table(), enough to combine all per_word_info into single lists of forms and store back into args[case]. (DONE, NEEDS TESTING) -- In generate_forms, should probably check if a=="i" and only return acc_sg_in as acc_sg=; or if a=="a" and only return acc_sg_an as acc_sg=; in old/new comparison code, do something similar, also when a=="b" check if acc_sg_in==acc_sg_an and make it acc_sg; when a=="b" and the _in and _an variants are different, might need to ignore them or check that acc_sg_in==nom_sg and acc_sg_an==gen_sg; similarly for _pl (DONE, NEEDS TESTING) -- Need to test with multiple words! [DONE] -- Current handling of <adj> won't work properly with multiple words; will need to translate word-by-word in that case (should be solved by manual-translit branch) [DONE] 14. In multiple-words branch, fix ru-decl-noun-multi so it recognizes things like *, (1), (2) and ; without the need for a separator. Consider using semicolon as a separator, since we already use it to separate ё from a previous declension. Maybe use $ or ~ for an invariable word; don't use semicolon. [IMPLEMENTED. NEED TO TEST.] 16. [Consider having ru-noun+ treat par= as a second genitive in the headword, as is done with край] [WON'T DO] 17. [FIXME: Consider removing slash patterns and instead handling them by allowing additional declension flags 'sg' and 'pl'. This simplifies the various special cases caused by slash declensions. It would also be possible to remove the special plural stem, which would get rid of more special cases. On the other hand, it makes it more complicated to support plural variant -ья with all singular types, and the category code that displays things like "Russian nouns with singular -X and plural -Y" also gets more complicated, and there's something convenient and intuitive about plural stems, and slash declensions are also convenient and at least somewhat intuitive. One possibility is to externally allow slash declensions and special plural stems and rewrite them internally to separate stems with 'sg' and 'pl' declension flags; but there are still the two coding issues mentioned above.] 18. [FIXME: Consider redoing slash patterns so they operate at the outer level, i.e. things like special cases apply separately in the singular and plural part of the slash pattern.] 19. In ru-noun, don't recognize -а with m as plural unless (1) or n=pl is also given, because there are masculine words with the feminine ending. Check using the test code whether this changes anything. Also check if there are other similar cases (neuter with -и isn't parallel because -и is always plural). [IMPLEMENTED. NEED TO TEST.] 19a. Internal notes weren't propagated properly from adjectives. [IMPLEMENTED. NEED TO TEST.] 19b. Add support for -ишко and -ище variants (p. 74 of Z), which conversationally and/or colloquially have feminine endings in certain cases. [IMPLEMENTED. NEED TO TEST. MAKE SURE THE INTERNAL NOTES APPEAR.] 19d. For masculine animate neuter-form nouns, the accusative singular ends in -а (-я soft) instead of -о. [IMPLEMENTED. NEED TO TEST. NOTE: Currently this variant only can be selected using new-style arguments where the gender can be given. Perhaps we should consider allowing gender to be specified with old-style explicit declensions.] 21. Put back gender hints for pl adjectival nouns; used by ru-noun+. [IMPLEMENTED. NEED TO TEST.] 23. Mixed and proper-noun adjectives have built-in notes. We need to handle those notes with an "internal_notes" section similar to what is used in the adjective module. [IMPLEMENTED. NEED TO TEST.] 24. Adjective detection code here needs to work the same as for the adjective module, in particular in the handling of short, stressed-short, mixed, proper, stressed-proper. [IMPLEMENTED. NEED TO TEST.] 25. Consider simplifying plural-variant code to only allow -ья as a plural variant [and maybe even change that to be something like (1')]. [IMPLEMENTED REDUCTION OF PLURAL VARIANTS TO -ья; PLURAL-VARIANT CODE STILL COMPLEX, THOUGH. NEED TO TEST.] 26. Automatically superscript *, numbers and similar things at the beginning of a note. Also do this in adjective module. [IMPLEMENTED. NEED TO TEST.] 28. Make the check for multiple stress patterns (categorizing/tracking) smarter, to keep a list of them and check at the end, so we handle multiple stress patterns specified through different arg sets. [IMPLEMENTED; NEED TO TEST.] 29. More sophisticated handling of user-requested plural variant vs. special case (1) vs. plural-detected variant. [IMPLEMENTED. NEED TO TEST FURTHER.] 30. Solution to ambiguous plural involving gender spec "3f". [IMPLEMENTED; NEED TO TEST. Use запчасти, новости.] 33. With pluralia tantum adjectival nouns, we don't know the gender. By default we assume masculine (or feminine for old-style -ія nouns) and currently this goes into the category, but shouldn't. [IMPLEMENTED.] 39. [Eventually: Even with decl type explicitly given, the full stem with ending should be included.] [MAY NEVER IMPLEMENT] 40. [Get error "Unable to dereduce" with strange noun ва́йя, what should happen?] [WILL NOT FIX; USE AN OVERRIDE] 41. In творог, module generates partitive творогу́ when it should copy the dative творогу́,тво́рогу. (DONE) 42. [[груз 200]] doesn't work. Interprets 200 as a footnote symbol. 43. When converting е -> ё not after cons and with translit, we should convert e -> o to avoid double j. (DONE) 44. FIXME: In ро́вня/ровня́, similarly with неровня, marks genitive plural ровня́ as irregular even though it isn't. (NOT OUR ERROR; THE DECLENSIONS OF THESE NOUNS MARKED THE ENDING-STRESSED VARIANTS WITH (2).) ]=]-- local m_utilities = require("Module:utilities") local ut = require("Module:utils") local m_links = require("Module:links") local com = require("Module:ru-common") local nom = require("Module:ru-nominal") local m_ru_adj = require("Module:ru-adjective") local m_ru_translit = require("Module:ru-translit") local strutils = require("Module:string utilities") local scriptutils = require("Module:script utilities") local m_table_tools = require("Module:table tools") local m_debug = require("Module:debug") local export = {} local lang = require("Module:languages").getByCode("ru") local Latn = require("Module:scripts").getByCode("Latn") local u = mw.ustring.char local rfind = mw.ustring.find local rsubn = mw.ustring.gsub local rmatch = mw.ustring.match local rsplit = mw.text.split local ulower = mw.ustring.lower local usub = mw.ustring.sub local ulen = mw.ustring.len local AC = u(0x0301) -- acute = ́ local CFLEX = u(0x0302) -- circumflex = ̂ local IRREGMARKER = "△" local HYPMARKER = "⟐" -- text class to check lowercase arg against to see if Latin text embedded in it local latin_text_class = "[a-zščžěáéíóúýàèìòùỳâêîôûŷạẹịọụỵȧėȯẏ]" -- version of rsubn() that discards all but the first return value local function rsub(term, foo, bar) local retval = rsubn(term, foo, bar) return retval end -- version of rsubn() that returns a 2nd argument boolean indicating whether -- a substitution was made. local function rsubb(term, foo, bar) local retval, nsubs = rsubn(term, foo, bar) return retval, nsubs > 0 end -- version of rfind() that lowercases its string first, for case-insensitive matching local function rlfind(term, foo) return rfind(ulower(term), foo) end local function track(page) m_debug.track("ru-noun/" .. page) return true end -- version of ut.insert_if_not() that makes sure 'false' doesn't get inserted by mistake local function insert_if_not(foo, bar) assert(bar ~= false) ut.insert_if_not(foo, bar) end -- Fancy version of ine() (if-not-empty). Converts empty string to nil, -- but also strips leading/trailing space and then single or double quotes, -- to allow for embedded spaces. local function ine(arg) if not arg then return nil end arg = rsub(arg, "^%s*(.-)%s*$", "%1") if arg == "" then return nil end local inside_quotes = rmatch(arg, '^"(.*)"$') if inside_quotes then return inside_quotes end inside_quotes = rmatch(arg, "^'(.*)'$") if inside_quotes then return inside_quotes end return arg end -- synthesize a frame so that exported functions meant to be called from -- templates can be called from the debug console. local function debug_frame(parargs, args) return {args = args, getParent = function() return {args = parargs} end} end local function rutr_pairs_equal(term1, term2) local ru1, tr1 = term1[1], term1[2] local ru2, tr2 = term2[1], term2[2] local ru1entry, ru1notes = m_table_tools.separate_notes(m_links.remove_links(ru1)) local ru2entry, ru2notes = m_table_tools.separate_notes(m_links.remove_links(ru2)) if ru1entry ~= ru2entry then return false end local tr1entry, tr1notes local tr2entry, tr2notes if tr1 then tr1entry, tr1notes = m_table_tools.separate_notes(tr1) end if tr2 then tr2entry, tr2notes = m_table_tools.separate_notes(tr2) end if tr1entry == tr2entry then return true elseif type(tr1entry) == type(tr2entry) then return false else tr1entry = tr1entry or nom.translit_no_links(ru1entry) tr2entry = tr2entry or nom.translit_no_links(ru2entry) return tr1entry == tr2entry end end local function contains_rutr_pair(list, pair) for _, item in ipairs(list) do if rutr_pairs_equal(item, pair) then return true end end return false end -- Clone parent's args while also assigning nil to empty strings. local function clone_args(frame) local args = {} for pname, param in pairs(frame:getParent().args) do args[pname] = ine(param) end return args end -- Old-style declensions. local declensions_old = {} -- New-style declensions; computed automatically from the old-style ones, -- for the most part. local declensions = {} -- Internal notes for old-style declensions. local internal_notes_table_old = {} -- Same for new-style declensions. local internal_notes_table = {} -- Category and type information corresponding to declensions: These may -- contain the following fields: 'singular', 'plural', 'decl', 'hard', 'g', -- 'suffix', 'gensg', 'irregpl', 'alt_nom_pl', 'cant_reduce', 'ignore_reduce', -- 'stem_suffix'. -- -- 'singular' is used to construct a category of the form -- "Russian nouns SINGULAR". If omitted, a category is constructed of the -- form "Russian nouns ending in -ENDING", where ENDING is the actual -- nom sg ending shorn of its acute accents; or "Russian nouns ending -- in suffix -ENDING", if 'suffix' is true. The value of SINGULAR can be -- one of the following: a single string, a list of strings, or a function, -- which is passed one argument (the value of ENDING that would be used to -- auto-initialize the category), and should return a single string or list -- of strings. Such a category is only constructed if 'gensg' is true. -- -- 'plural' is analogous but used to construct a category of the form -- "Russian nouns with PLURAL", and if omitted, a category is constructed -- of the form "Russian nouns with plural -ENDING", based on the actual -- nom pl ending shorn of its acute accents. Currently no plural category -- is actually constructed. -- -- In addition, a category may normally constructed from the combination of -- 'singular' and 'plural', appropriately defaulted; e.g. if both are present, -- the combined category will be "Russian nouns SINGULAR with PLURAL" and -- if both are missing, the combined category will be -- "Russian nouns ending in -SGENDING with plural -PLENDING" (or -- "Russian nouns ending in suffix -SGENDING with plural -PLENDING" if -- 'suffix' is true). Note that if either singular or plural or both -- specifies a list, looping will occur over all combinations. Such a -- category is constructed only if 'irregpl' or 'alt_nom_pl' or 'suffix' -- is true or if the declension class is a slash class. -- -- 'decl' is "1st", "2nd", "3rd" or "invariable"; 'hard' is "hard", "soft" -- or "none"; 'g' is "m", "f", "n" or "none"; these are all traditional -- declension categories. -- -- If 'suffix' is true, the declension type includes a long suffix -- added to the string that itself undergoes reducibility and such, and so -- reducibility cannot occur in the stem minus the suffix. Categories will -- be created for the suffix. -- -- 'alt_nom_pl' indicates that the declension has an alternative nominative -- plural (corresponding to Zaliznyak's special case 1; compare special case 2 -- for alternative genitive plural). 'irregpl' indicates that the entire -- plural is irregular. -- -- In addition to the above categories, additional more specific categories -- are constructed based on the final letter of the stem, e.g. -- "Russian velar-stem 1st-declension hard nouns". See calls to -- com.get_stem_trailing_letter_type(). 'stem_suffix', if present, is added to -- the end of the stem when get_stem_trailing_letter_type() is called. -- This is the only place that 'stem_suffix' is used. This is for use with -- the '-ья' and '-ье' declension types, so that the trailing letter is -- 'ь' and not whatever precedes it. -- -- 'enable_categories' is a special hack for testing, which disables all -- category insertion if false. Delete this as soon as we've verified the -- working of the category code and created all the necessary categories. local enable_categories = true -- Category/type info corresponding to old-style declensions; see above. local declensions_old_cat = {} -- Category/type info corresponding to new-style declensions. Computed -- automatically from the old-style ones, for the most part. Same format -- as the old-style ones. local declensions_cat = {} -- Table listing aliases of old-style declension classes. local declensions_old_aliases = {} -- Table listing aliases of new-style declension classes; computed -- automatically from the old-style ones. local declensions_aliases = {} local stress_patterns = {} -- Set of patterns with ending-stressed genitive plural. local ending_stressed_gen_pl_patterns = {} -- Set of patterns with ending-stressed prepositional singular. local ending_stressed_pre_sg_patterns = {} -- Set of patterns with ending-stressed dative singular. local ending_stressed_dat_sg_patterns = {} -- Set of patterns with all singular forms ending-stressed. local ending_stressed_sg_patterns = {} -- Set of patterns with all plural forms ending-stressed. local ending_stressed_pl_patterns = {} -- List of all cases, including those that are declined normally -- (nom/gen/dat/acc/ins/pre sg and pl), plus animate/inanimate accusative -- variants (computed automatically as appropriate from the previous cases), -- plus additional overridable cases (loc/par/voc), plus the "linked" lemma -- case variants used in ru-noun+ headwords (nom_sg_linked, nom_pl_linked, -- whose values come from nom_sg and nom_pl but may have additional embedded -- links if they were given in the lemma). local all_cases -- List of all cases that are declined normally. local decl_cases -- List of all cases that can be displayed (includes all cases except -- plain accusatives). local displayable_cases -- List of all cases that can be overridden (includes all cases except the -- "linked" lemma case variants). Also currently the same as the cases -- returned by export.generate_forms(). local overridable_cases -- Type of trailing letter, for tracking purposes local trailing_letter_type -- If enabled, compare this module with new version of module to make -- sure all declensions are the same. Eventually consider removing this; -- but useful as new code is created. local test_new_ru_noun_module = false -- Forward functions local generate_forms_1 local determine_decl local handle_forms_and_overrides local handle_overall_forms_and_overrides local concat_word_forms local make_table local detect_adj_type local detect_stress_pattern local override_stress_pattern local determine_stress_variant local determine_stem_variant local is_reducible local is_dereducible local add_bare_suffix local attach_stressed local do_stress_pattern local canonicalize_override -------------------------------------------------------------------------- -- Tracking and categorization -- -------------------------------------------------------------------------- -- FIXME! Move below the main code -- FIXME!! Consider deleting most of this tracking code once we've enabled -- all the categories. Note that some of the tracking categories aren't -- completely redundant; e.g. we have tracking pages that combine decl and -- stress classes, such as "а/a" or "о-и/d'", which are more or less -- equivalent to stem/gender/stress categories, but we also have the same -- prefixed by "reducible-stem/" for reducible stems. local function tracking_code(stress, orig_decl, decl, args, n, islast) assert(orig_decl) assert(decl) local hint_types = com.get_stem_trailing_letter_type(args.stem) if orig_decl == decl then orig_decl = nil end if args.notes then track("notes") end local function all_pl_irreg() track("irreg") for _, case in ipairs(overridable_cases) do if rfind(case, "_pl") then track("irreg/" .. case) end end end local function track_prefix(prefix) local function dotrack(suf) track(prefix .. suf) end dotrack(stress) dotrack(decl) dotrack(decl .. "/" .. stress) if orig_decl then dotrack(orig_decl) dotrack(orig_decl .. "/" .. stress) end for _, hint_type in ipairs(hint_types) do dotrack(hint_type) dotrack(decl .. "/" .. hint_type) if orig_decl then dotrack(orig_decl .. "/" .. hint_type) end end end track_prefix("") if args.reducible then track("reducible-stem") track_prefix("reducible-stem/") end if rlfind(args.stem, "и́?н$") and (decl == "" or decl == "#") then track("irregular-in") end if args.pltail then track("pltail") end if args.sgtail then track("sgtail") end if args.pltailall then track("pltailall") end if args.sgtailall then track("sgtailall") end if args.pl ~= args.stem then track("irreg-pl-stem") track("irreg") end if args.alt_gen_pl then track("alt-gen-pl") track("irreg") track("irreg/gen_pl") end if args.want_sc1 then track("want-sc1") track("irreg") track("irreg/nom_pl") end if rfind(decl, "-и$") or rfind(decl, "-а$") or rfind(decl, "-я$") then track("irreg") track("irreg/nom_pl") end if rfind(decl, "-ья$") then track("variant-ья") all_pl_irreg() end if rfind(decl, "%(ишк%)$") then track("variant-ишко") end if rfind(decl, "%(ищ%)$") then track("variant-ище") end if args.jo_special then track("jo-special") end if args.manual then track("manual") end if args.explicit_gender then track("explicit-gender") track("explicit-gender/" .. args.explicit_gender) end for _, case in ipairs(overridable_cases) do if args[case .. n] and not args.manual then track("override") track("override/" .. case .. n) track("irreg") track("irreg/" .. case .. n) -- questionable use: track_prefix("irreg/" .. case .. "/") -- questionable use: track_prefix("irreg/" .. case .. n .. "/") end if islast and args[case] and not args.manual then track("override") track("override/" .. case) track("irreg") track("irreg/" .. case) -- questionable use: track_prefix("irreg/" .. case .. "/") end if args[case .. "_tail"] then track("casenum-tail") track("casenum-tail/" .. case) end if args[case .. "_tailall"] then track("casenum-tailall") track("casenum-tailall/" .. case) end end end local gender_to_full = {m="masculine", f="feminine", n="neuter"} local gender_to_short = {m="masc", f="fem", n="neut"} -- Insert the category CAT (a string) into list CATEGORIES. String will -- have "Russian " prepended and ~ substituted for the plural part of speech. local function insert_category(categories, cat, pos, atbeg) if enable_categories then local fullcat = "Russian " .. rsub(cat, "~", pos .. "s") if atbeg then table.insert(categories, 1, fullcat) else table.insert(categories, fullcat) end end end -- Insert categories into ARGS.CATEGORIES corresponding to the specified -- stress and declension classes and to the form of the stem (e.g. velar, -- sibilant, etc.). Also initialize values used to compute the declension -- heading that describes similar information. N is the number of the -- word being processed; ISLAST is true if this is the last word. local function categorize_and_init_heading(stress, decl, args, n, islast) local function cat_to_list(cat) if not cat then return {} elseif type(cat) == "string" then return {cat} else assert(type(cat) == "table") return cat end end -- Insert category CAT into the list of categories in ARGS. -- CAT may be nil, a single string or a list of strings. We call -- insert_category() on each string. The strings will have "Russian " -- prepended and "~" replaced with the plural part of speech. local function insert_cat(cat) for _, c in ipairs(cat_to_list(cat)) do insert_category(args.categories, c, args.pos) end end -- "Resolve" the category spec CATSPEC into the sort of category spec -- accepted by insert_cat(), i.e. nil, a single string or a list of -- strings. CATSPEC may be any of these or a function, which takes one -- argument (SUFFIX) and returns another CATSPEC. local function resolve_cat(catspec, suffix) if type(catspec) == "function" then return resolve_cat(catspec(suffix), suffix) else return catspec end end -- Check whether an override for nom_sg or nom_pl still contains the -- normal suffix (which should already have accents removed) in at least -- one of its entries. If no override then of course we return true. local function override_matches_suffix(args, case, n, suffix) assert(suffix == com.remove_accents(suffix)) -- NOTE: It might not be completely correct to pass in args.forms -- here when n == ""; this is different behavior from -- handle_overall_forms_and_overrides(). But args.forms is only used -- to retrieve the dat_sg for handling loc/par, and we're never -- called with loc or par as the value of CASE, so it doesn't matter. -- We add an assert to make sure of this. assert(case ~= "loc" and case ~= "par") local override = canonicalize_override(args, case, args.forms, n) if not override then return true end for _, x in ipairs(override) do local ru, tr = x[1], x[2] local entry, notes = m_table_tools.separate_notes(ru) entry = com.remove_accents(m_links.remove_links(entry)) if rlfind(entry, suffix .. "$") then return true end end return false end if args.manual then return end local h = args.heading_info assert(decl) local decl_cats = args.old and declensions_old_cat or declensions_cat local sgdecl, pldecl local is_slash_decl = rfind(decl, "/") if is_slash_decl then local indiv_decls = rsplit(decl, "/") sgdecl, pldecl = indiv_decls[1], indiv_decls[2] else sgdecl, pldecl = decl, decl end local sgdc = decl_cats[sgdecl] local pldc = decl_cats[pldecl] assert(sgdc) assert(pldc) local sghint_types = com.get_stem_trailing_letter_type( args.stem .. (sgdc.stem_suffix or "")) -- insert English version of Zaliznyak stem type if sgdc.decl == "invariable" then insert_cat("invariable ~") insert_if_not(h.stemetc, "invar") else local stem_type = sgdc.decl == "3rd" and "3rd-declension" or ut.contains(sghint_types, "velar") and "velar-stem" or ut.contains(sghint_types, "sibilant") and "sibilant-stem" or ut.contains(sghint_types, "c") and "ц-stem" or ut.contains(sghint_types, "i") and "i-stem" or ut.contains(sghint_types, "vowel") and "vowel-stem" or ut.contains(sghint_types, "soft-cons") and "vowel-stem" or ut.contains(sghint_types, "palatal") and "vowel-stem" or sgdc.hard == "soft" and "soft-stem" or "hard-stem" local short_stem_type = stem_type == "3rd-declension" and "3rd-decl" or stem_type if sgdc.adj then -- Don't include gender for pluralia tantum because it's mostly -- indeterminate (certainly when specified using a plural lemma, -- which will be usually; technically it's partly or completely -- determinate in certain old-style adjectives that distinguish -- masculine from feminine/neuter, but this is too rare a case -- to worry about) local gendertext = args.thisn == "p" and "plural-only" or gender_to_full[sgdc.g] insert_if_not(h.adjectival, "yes") if args.thisn ~= "p" then insert_if_not(h.gender, gender_to_short[sgdc.g]) end if sgdc.possadj then insert_cat(sgdc.decl .. " possessive " .. gendertext .. " accent-" .. stress .. " adjectival ~") insert_if_not(h.stemetc, sgdc.decl .. " poss") insert_if_not(h.stress, stress) elseif stem_type == "soft-stem" or stem_type == "vowel-stem" then insert_cat(stem_type .. " " .. gendertext .. " adjectival ~") insert_if_not(h.stemetc, short_stem_type) else insert_cat(stem_type .. " " .. gendertext .. " accent-" .. stress .. " adjectival ~") insert_if_not(h.stemetc, short_stem_type) insert_if_not(h.stress, stress) end else -- NOTE: There are 8 Zaliznyak-style stem types and 3 genders, but -- we don't create a category for masculine-form 3rd-declension -- nouns (there is such a noun, путь, but it mostly behaves -- like a feminine noun), so there are 23. insert_cat(stem_type .. " " .. gender_to_full[sgdc.g] .. "-form ~") -- NOTE: Here we are creating categories for the combination of -- stem, gender and accent. There are 10 accent patterns and 23 -- combinations of stem and gender, which potentially makes for -- 10*23 = 230 such categories, which is a lot. Not all such -- categories should actually exist; there were maybe 75 former -- declension templates, each of which was essentially categorized -- by the same three variables, but some of which dealt with -- ancillary issues like irregular plurals; this amounts to 67 -- actual stem/gender/accent categories, although there are more -- of them in Zaliznyak (FIXME, how many? See generate_cats.py). insert_cat(stem_type .. " " .. gender_to_full[sgdc.g] .. "-form accent-" .. stress .. " ~") insert_if_not(h.adjectival, "no") insert_if_not(h.gender, gender_to_short[sgdc.g]) insert_if_not(h.stemetc, short_stem_type) insert_if_not(h.stress, stress) end insert_cat("~ with accent pattern " .. stress) end local sgsuffix = args.suffixes.nom_sg if sgsuffix then assert(#sgsuffix == 1) -- If this ever fails, then implement a loop sgsuffix = com.remove_accents(sgsuffix[1]) -- If we are plural only or if nom_sg is overridden and has -- an unusual suffix, then don't create category for sg suffix if args.thisn == "p" or not override_matches_suffix(args, "nom_sg", n, sgsuffix) or islast and not override_matches_suffix(args, "nom_sg", "", sgsuffix) then sgsuffix = nil end end local plsuffix = args.suffixes.nom_pl if plsuffix then assert(#plsuffix == 1) -- If this ever fails, then implement a loop plsuffix = com.remove_accents(plsuffix[1]) -- If we are a singulare tantum or if nom_pl is overridden and has -- an unusual suffix, then don't create category for pl suffix if args.thisn == "s" or not override_matches_suffix(args, "nom_pl", n, plsuffix) or islast and not override_matches_suffix(args, "nom_pl", "", plsuffix) then plsuffix = nil end end sgsuffix = sgsuffix and rsub(sgsuffix, "ъ$", "") plsuffix = plsuffix and rsub(plsuffix, "ъ$", "") local sgcat = sgsuffix and (resolve_cat(sgdc.singular, sgsuffix) or "ending in " .. (sgsuffix == "" and "a consonant" or (sgdc.suffix and "suffix " or "") .. "-" .. sgsuffix)) local plcat = plsuffix and (resolve_cat(pldc.plural, suffix) or "plural -" .. plsuffix) if sgcat and sgdc.gensg then for _, cat in ipairs(cat_to_list(sgcat)) do insert_cat("~ " .. cat) end end if sgcat and plcat and (sgdc.suffix or sgdc.alt_nom_pl or sgdc.irregpl or is_slash_decl and plsuffix == "-ья") then for _, scat in ipairs(cat_to_list(sgcat)) do for _, pcat in ipairs(cat_to_list(plcat)) do insert_cat("~ " .. scat .. " with " .. pcat) end end end if args.pl ~= args.stem then insert_cat("~ with irregular plural stem") end if args.reducible and not sgdc.ignore_reduce then insert_cat("~ with reducible stem") insert_if_not(h.reducible, "yes") else insert_if_not(h.reducible, "no") end if args.alt_gen_pl then insert_cat("~ with alternate genitive plural") end if sgdc.adj then insert_cat("adjectival ~") end end local function compute_heading(args) local headings = {} local h = args.heading_info table.insert(headings, args.a == "a" and "anim" or args.a == "i" and "inan" or "bian") table.insert(headings, args.nonumber and "uncountable" or args.n == "s" and "sg-only" or args.n == "p" and "pl-only" or nil) if #h.gender > 0 then table.insert(headings, table.concat(h.gender, "/") .. "-form") end if #h.stemetc > 0 then table.insert(headings, table.concat(h.stemetc, "/")) end if #h.stress > 0 then local stresses = {} for _, stress in ipairs(h.stress) do table.insert(stresses, rsub(stress, "'", "&#39;")) end table.insert(headings, "accent-" .. table.concat(stresses, "/")) end local function handle_bool(boolvals, text, into) into = into or headings if ut.contains(boolvals, "yes") and ut.contains(boolvals, "no") then table.insert(into, "[" .. text .. "]") elseif ut.contains(boolvals, "yes") then table.insert(into, text) end end handle_bool(h.adjectival, "adj") handle_bool(h.reducible, "reduc") return headings end local function compute_overall_heading_categories_and_genders(args) local hinfo = args.per_word_heading_info local index = 0 -- First try for non-adjectival, non-invariable for i=1,#hinfo do if not ut.contains(hinfo[i].stemetc, "invar") and not ut.contains(hinfo[i].adjectival, "yes") then index = i break end end if index == 0 then -- Then just non-invariable for i=1,#hinfo do if not ut.contains(hinfo[i].stemetc, "invar") then index = i break end end end -- Finally, do anything if index == 0 then index = 1 end -- Compute final heading local headings = args.per_word_headings[index] local categories = args.per_word_categories[index] if args.any_irreg then table.insert(headings, "irreg") insert_category(categories, "irregular ~", args.pos) end for _, case in ipairs(overridable_cases) do local is_pl = rfind(case, "_pl") if args.n == "s" and is_pl or args.n == "p" and not is_pl then -- Don't create singular categories when plural-only or vice-versa elseif (case == "loc" or case == "voc" or case == "par") then if args.any_overridden[case] then local engcase = rsub(case, "^([a-z]*)", { par="partitive", loc="locative", voc="vocative" }) insert_category(categories, "~ with " .. engcase, args.pos) end elseif args.any_irreg_case[case] then local engcase = rsub(case, "^([a-z]*)", { nom="nominative", gen="genitive", dat="dative", acc="accusative", ins="instrumental", pre="prepositional", par="partitive", loc="locative", voc="vocative" }) engcase = rsub(engcase, "(_[a-z]*)", { _sg=" singular", _pl=" plural", _an="", _in="", --_an=" animate", _in=" inanimate" }) insert_category(categories, "~ with irregular " .. engcase, args.pos) end end local heading = args.manual and "" or "(<span style=\"font-size: smaller;\">[[Appendix:Russian nouns#Declension tables|" .. table.concat(headings, " ") .. "]]</span>)" args.heading = heading args.categories = categories args.genders = args.per_word_genders[index] end -------------------------------------------------------------------------- -- Main code -- -------------------------------------------------------------------------- -- Used by do_generate_forms(). local function arg1_is_stress(arg1) if not arg1 then return false end for _, arg in ipairs(rsplit(arg1, ",")) do if not rfind(arg, "^[a-f]'?'?$") then return false end end return true end -- Used by do_generate_forms(), handling a word joiner argument -- of the form 'join:JOINER'. local function extract_word_joiner(spec) word_joiner = rmatch(spec, "^join:(.*)$") assert(word_joiner) return nom.split_russian_tr(word_joiner, "dopair") end local function determine_headword_gender(args, sgdc, gender) -- If gender unspecified, use normal gender of declension, except when -- adjectival nouns that are pluralia tantum, where the gender is -- mostly indeterminate (FIXME, not completely with old-style declensions -- but we don't handle that currently). if not gender then if sgdc.adj and args.thisn == "p" then gender = nil else gender = sgdc.g end end -- Determine headword genders gender = gender and gender ~= "none" and gender .. "-" or "" local plsuffix = args.n == "p" and "-p" or "" local hgens if args.a == "a" then hgens = {gender .. "an" .. plsuffix} elseif args.a == "i" then hgens = {gender .. "in" .. plsuffix} elseif args.a == "ai" then hgens = {gender .. "an" .. plsuffix, gender .. "in" .. plsuffix} else hgens = {gender .. "in" .. plsuffix, gender .. "an" .. plsuffix} end -- Insert into list of genders for _, hgen in ipairs(hgens) do insert_if_not(args.genders, hgen) end end function export.do_generate_forms(args, old) old = old or args.old args.old = old args.pos = args.pos or "noun" -- This is a list with each element corresponding to a word and -- consisting of a two-element list, ARG_SETS and JOINER, where ARG_SETS -- is a list of ARG_SET objects, one per alternative stem, and JOINER -- is a string indicating how to join the word to the next one. local per_word_info = {} -- Gather arguments into a list of ARG_SET objects, containing (potentially) -- elements 1, 2, 3, 4, corresponding to accent pattern, stem, declension -- type, pl stem and coming from consecutive numbered parameters. Sets of -- declension parameters are separated by the word "or". local arg_sets = {} -- Find maximum-numbered arg, allowing for holes local max_arg = 0 for k, v in pairs(args) do if type(k) == "number" and k > max_arg then max_arg = k end end -- Now gather the arguments. local offset = 0 local arg_set = {} for i=1,(max_arg + 1) do local end_arg_set = false local end_word = false -- FIXME, is this correct? local word_joiner if i == max_arg + 1 then end_arg_set = true end_word = true word_joiner = {""} elseif args[i] == "_" then end_arg_set = true end_word = true word_joiner = {" "} elseif args[i] == "-" then end_arg_set = true end_word = true word_joiner = {"-"} elseif args[i] and rfind(args[i], "^join:") then end_arg_set = true end_word = true word_joiner = extract_word_joiner(args[i]) elseif args[i] == "or" then end_arg_set = true end if end_arg_set then table.insert(arg_sets, arg_set) arg_set = {} offset = i if end_word then table.insert(per_word_info, {arg_sets, word_joiner}) arg_sets = {} end else -- If the first argument isn't stress, that means all arguments -- have been shifted to the left one. We want to shift them -- back to the right one, so we change the offset so that we -- get the same effect of skipping a slot in the arg set. if i - offset == 1 and not arg1_is_stress(args[i]) then offset = offset - 1 end if i - offset > 4 then error("Too many arguments for argument set: arg " .. i .. " = " .. (args[i] or "(blank)")) end arg_set[i - offset] = args[i] end end return generate_forms_1(args, per_word_info) end function export.do_generate_forms_multi(args, old) old = old or args.old args.old = old args.pos = args.pos or "noun" -- This is a list with each element corresponding to a word and -- consisting of a two-element list, ARG_SET and JOINER, where ARG_SET -- is a list of ARG_SET objects, one per alternative stem, and JOINER -- is a string indicating how to join the word to the next one. local per_word_info = {} -- Find maximum-numbered arg, allowing for holes (FIXME: Is this needed -- here? Will there be holes?) local max_arg = 0 for k, v in pairs(args) do if type(k) == "number" and k > max_arg then max_arg = k end end -- Gather arguments into a list of ARG_SET objects, containing -- (potentially) elements 1, 2, 3, corresponding to accent pattern, -- lemma+declension spec, pl stem, exactly as with do_generate_forms() -- and {{ru-noun-table}} except that the values come from a single argument -- of the form ACCENTPATTERN:LEMMADECL:PL where all but LEMMADECL may (and -- probably will be) omitted and LEMMADECL may be of the following forms: -- LEMMA (for a noun with empty decl spec), -- LEMMADECL (for a noun with non-empty decl spec beginning with a -- *, left paren or semicolon), -- LEMMA^DECL (for a noun with non-empty decl spec), -- LEMMA$ (for an invariable word) -- LEMMA+ (for an adjective with auto-detected decl class) -- LEMMA+DECL (for an adjective with explicit decl class) -- Sets of parameters for the same word are separated by the word "or". local arg_sets = {} local continue_arg_sets = true for i=1,(max_arg + 1) do local end_word = false local word_joiner local process_arg = false if i == max_arg + 1 then end_word = true word_joiner = {""} elseif args[i] == "-" then end_word = true word_joiner = {"-"} continue_arg_sets = true elseif rfind(args[i], "^join:") then end_word = true word_joiner = extract_word_joiner(args[i]) continue_arg_sets = true elseif args[i] == "or" then continue_arg_sets = true else if continue_arg_sets then continue_arg_sets = false else end_word = true word_joiner = {" "} end process_arg = true end if end_word then table.insert(per_word_info, {arg_sets, word_joiner}) arg_sets = {} end if process_arg then local vals = rsplit(args[i], ":") if #vals > 3 then error("Can't specify more than 3 colon-separated params of param set: " .. args[i]) end local arg_set = {} if arg1_is_stress(vals[1]) then arg_set[1] = vals[1] arg_set[2] = vals[2] arg_set[4] = vals[3] else arg_set[2] = vals[1] arg_set[4] = vals[2] end -- recognize invariable local inv_stem = rmatch(arg_set[2], "^(.-)%$$") if inv_stem then arg_set[2] = inv_stem arg_set[3] = "$" else -- recognize adjective local adj_stem, adj_type = rmatch(arg_set[2], "^(.*)(%+.*)$") if adj_stem then arg_set[2] = adj_stem arg_set[3] = adj_type else -- recognize noun with ^ local noun_stem, noun_type = rmatch(arg_set[2], "^(.*)%^(.*)$") if noun_stem then arg_set[2] = noun_stem arg_set[3] = noun_type else -- recognize noun without ^ but with decl spec noun_stem, noun_type = rmatch(arg_set[2], "^(..-)([;*(].*)$") if noun_stem then arg_set[2] = noun_stem arg_set[3] = noun_type else -- noun without ^ or decl spec; nothing to do end end end end table.insert(arg_sets, arg_set) end end return generate_forms_1(args, per_word_info) end -- Implementation of do_generate_forms() and do_generate_forms_multi(), -- which have equivalent functionality but different calling sequence. -- Implementation of do_generate_forms() and do_generate_forms_multi(), -- which have equivalent functionality but different calling sequence, -- as well as show_z() for template {{ru-decl-noun-z}}, which has a -- subset of the functionality of the other two. generate_forms_1 = function(args, per_word_info) local orig_args if test_new_ru_noun_module then orig_args = mw.clone(args) end local SUBPAGENAME = mw.loadData("Module:headword/data").pagename local old = args.old local function verify_animacy_value(val) if not val then return nil end if val == "a" or val == "an" or val == "anim" then return "a" elseif val == "i" or val == "in" or val == "inan" then return "i" elseif val == "b" or val == "bi" or val == "both" or val == "ai" then return "ai" elseif val == "ia" then return "ia" end error("Animacy value " .. val .. " should be empty or a/an/anim (animate), i/in/inan (inanimate), b/bi/both/ai (bianimate, listing animate first), or ia (bianimate, listing inanimate first)") return nil end local function verify_number_value(val, allow_none) if not val then return nil end local short = usub(val, 1, 1) if short == "s" or short == "p" or short == "b" or (allow_none and short == "n" or false) then return short end if allow_none then error("Number value " .. val .. " should be empty or start with 's' (singular), 'p' (plural), 'b' (both) or 'n' (none)") else error("Number value " .. val .. " should be empty or start with 's' (singular), 'p' (plural), or 'b' (both)") end return nil end -- Verify and canonicalize animacy, number, prefix, suffix assert(#per_word_info >= 1) for i=1,#per_word_info do args["a" .. i] = verify_animacy_value(args["a" .. i]) args["n" .. i] = verify_number_value(args["n" .. i]) args["prefix" .. i] = nom.split_russian_tr(args["prefix" .. i] or "", "dopair") args["suffix" .. i] = nom.split_russian_tr(args["suffix" .. i] or "", "dopair") end args.a = verify_animacy_value(args.a) or "i" -- args.ndef, if set, is the default value for args.n; if unset, it defaults -- to "both". It is set to "singular" in ru-proper noun+. We store the value -- of args.n in args.orign before defaulting to args.ndef because we may -- change it to plural-only later on if it was unspecified (this happens if -- an individual word's lemma is plural), and to determine whether it was -- unspecified, we need the original value before defaulting. args.n = verify_number_value(args.n, "allow none") -- treat n=none like n=sg but set a flag so "singular" isn't displayed if args.n == "n" then args.n = "s" args.nonumber = true end args.ndef = verify_number_value(args.ndef) args.orign = args.n args.n = args.n or args.ndef args.prefix = nom.split_russian_tr(args.prefix or "", "dopair") args.suffix = nom.split_russian_tr(args.suffix or "", "dopair") -- Attach overall prefix to first per-word prefix, similarly for suffix args.prefix1 = nom.concat_paired_russian_tr(args.prefix, args.prefix1) args["suffix" .. #per_word_info] = nom.concat_paired_russian_tr( args["suffix" .. #per_word_info], args.suffix) -- Initialize non-word-specific arguments. -- -- The following is a list of WORD_INFO items, one per word, each of -- which is a two element list of WORD_FORMS (a table listing the forms for -- each case) and JOINER (a string, indicating how to join the word with -- the next one). args.per_word_info = {} -- List of HEADING_INFO items, one per word, containing the raw material -- used to generate the header. Initialized from 'args.heading_info', -- which is initialized in categorize_and_init_heading(). args.per_word_heading_info = {} -- List of CATEGORIES items, one per word, containing the categories -- used to initialize the page categories. Initialized from -- 'args.categories', which is initialized in categorize_and_init_heading(). args.per_word_categories = {} -- List of HEADINGS items, one per word, containing the actual words that -- go into the header if we were to use that word to construct the header. -- Comes from compute_heading(). We use this to generate the actual header -- string, which goes into 'args.heading' at the end (done in -- compute_overall_heading_categories_and_genders()). args.per_word_headings = {} -- List of GENDERS items, one per word, containing the headword genders -- for each word (where "headword gender" is in the format used in -- headwords and actually includes animacy and number as well, e.g. -- 'm-in' or 'f-an-p'). We end up selecting one such item and putting -- it into 'args.genders' at the end -- (in compute_overall_heading_categories_and_genders()). args.per_word_genders = {} args.any_overridden = {} args.any_non_nil = {} args.any_irreg = false args.any_irreg_case = {} local function insert_cat(cat) insert_category(args.categories, cat, args.pos) end args.internal_notes = {} -- Superscript footnote marker at beginning of note, similarly to what's -- done at end of forms. if args.notes then local notes, entry = m_table_tools.get_initial_notes(args.notes) args.notes = notes .. entry end local decl_sufs = old and declensions_old or declensions local decl_cats = old and declensions_old_cat or declensions_cat local intable = old and internal_notes_table_old or internal_notes_table -- Default lemma defaults to previous lemma, first one to page name. -- FIXME: With multiple words, we should probably split the page name -- on spaces and default each word in turn local default_lemma local all_stresses_seen -- Made into a function to avoid having to indent a lot of code. -- Process a single arg set of a single word. This inserts the forms -- for the word into args.forms and sets categories and tracking pages. local function do_arg_set(arg_set, n, islast) local stress_arg = arg_set[1] local decl = arg_set[3] or "" local pl, pltr if arg_set[4] then pl, pltr = nom.split_russian_tr(arg_set[4]) end -- Extract special markers from declension class. if decl == "manual" then decl = "$" args.manual = true if #per_word_info > 1 or #per_word_info[1][1] > 1 then error("Can't specify multiple words or argument sets when manual") end if pl then error("Can't specify optional stem parameters when manual") end end decl, args.jo_special = rsubb(decl, "([^/%a])ё$", "%1") if not args.jo_special then decl, args.jo_special = rsubb(decl, "([^/%a])ё([^/%a])", "%1%2") end decl, args.want_sc1 = rsubb(decl, "%(1%)", "") decl, args.alt_gen_pl = rsubb(decl, "%(2%)", "") decl, args.reducible = rsubb(decl, "%*", "") decl = rsub(decl, ";", "") -- Get the lemma. local lemma = args.manual and "-" or arg_set[2] or default_lemma if not lemma then error("Lemma in first argument set must be specified") end default_lemma = lemma local lemmatr lemma, lemmatr = nom.split_russian_tr(lemma) args.thisa = args["a" .. n] or args.a args.thisn = args["n" .. n] or args.n -- Check for explicit allow-unaccented indication. lemma, args.allow_unaccented = rsubb(lemma, "^%*", "") if args.allow_unaccented then track("allow-unaccented") end args.orig_lemma = lemma lemma = m_links.remove_links(lemma) args.lemma_no_links = lemma args.lemmatr = lemmatr -- Treat suffixes without an accent, and suffixes with an accent on the -- initial hyphen, as if they were preceded with a *, which overrides -- all the logic that normally (a) normalizes the accent, and (b) -- complains about multisyllabic words without an accent. Don't do this -- if lemma is just -, which is used specially in manual declension -- tables (e.g. сто, три). if lemma ~= "-" and (rfind(lemma, "^%-́") or (com.is_unstressed(lemma) and rfind(lemma, "^%-"))) then args.allow_unaccented = true end -- Convert lemma and decl arg into stem and canonicalized decl. -- This will autodetect the declension from the lemma if an explicit -- decl isn't given. local stem, tr, gender, was_accented, was_plural, was_autodetected if rfind(decl, "^%+") then stem, tr, decl, gender, was_accented, was_plural, was_autodetected = detect_adj_type(lemma, lemmatr, decl, old) else stem, tr, decl, gender, was_accented, was_plural, was_autodetected = determine_decl(lemma, lemmatr, decl, args) end if was_plural then args.n = args.orign or "p" args.thisn = args["n" .. n] or args.n elseif decl ~= "$" then args.thisn = args.thisn or "b" end args.explicit_gender = gender -- If allow-unaccented not given, maybe check for missing accents. if not args.allow_unaccented and not stress_arg and was_autodetected and com.needs_accents(lemma) then -- If user gave the full word and expects us to determine the -- declension and stress, the word should have an accent on the -- stem or ending. We have a separate check farther below for -- an accent on a multisyllabic stem, after stripping off any -- ending; but this way we get an error if the user e.g. writes -- "гора" without an accent rather than assuming it's stem -- stressed, as would otherwise happen. error("Lemma must have an accent in it: " .. lemma) end -- If stress not given, auto-determine; else validate/canonicalize -- stress arg, override in certain cases and convert to list. if not stress_arg then stress_arg = {detect_stress_pattern(stem, decl, decl_cats, args.reducible, was_plural, was_accented)} else stress_arg = rsplit(stress_arg, ",") for i=1,#stress_arg do local stress = stress_arg[i] stress = override_stress_pattern(decl, stress) if not stress_patterns[stress] then error("Unrecognized accent pattern " .. stress) end stress_arg[i] = stress end end -- parse slash decl to list local sub_decls if rfind(decl, "/") then track("mixed-decl") insert_cat("~ with mixed declension") local indiv_decls = rsplit(decl, "/") -- Should have been caught in canonicalize_decl() assert(#indiv_decls == 2) sub_decls = {{indiv_decls[1], "sg"}, {indiv_decls[2], "pl"}} else sub_decls = {{decl}} end -- Get singular declension and corresponding category. local sgdecl = sub_decls[1][1] local sgdc = decl_cats[sgdecl] assert(sgdc) -- Compute headword gender(s). We base it off the singular declension -- if we have a slash declension -- it's the best we can do. determine_headword_gender(args, sgdc, gender) local original_stem, original_tr = stem, tr local original_pl, original_pltr = pl, pltr -- Loop over accent patterns in case more than one given. for _, stress in ipairs(stress_arg) do args.suffixes = {} stem, tr = original_stem, original_tr local bare, baretr local stem_for_bare, tr_for_bare pl, pltr = original_pl, original_pltr insert_if_not(all_stresses_seen, stress) local stem_was_unstressed = com.is_unstressed(stem) -- If special case ;ё was given and stem is unstressed, -- add ё to the stem now; but don't let this interfere with -- restressing, to handle cases like железа́ with gen pl желёз -- but nom pl же́лезы. if stem_was_unstressed and args.jo_special then -- Beware, Cyrillic еЕ in first rsub, Latin eE in second local new_stem = rsub(stem, "([еЕ])([^еЕ]*)$", function(e, rest) return (e == "Е" and "Ё" or "ё") .. rest end ) if stem == new_stem then error("No е in stem to replace with ё") end stem = new_stem if tr then local subbed -- e after j -> o, e not after j -> jo; don't just convert e -> jo -- and then map jjo -> jo because we want to preserve double j tr, subbed = rsubb(tr, "([jJ])([eE])([^eE]*)$", function(j, e, rest) return j .. (e == "E" and "O" or "o") .. AC .. rest end ) if not subbed then tr = rsub(tr, "([eE])([^eE]*)$", function(e, rest) return (e == "E" and "Jo" or "jo") .. AC .. rest end ) end tr = com.j_correction(tr) end -- This is used to handle железа́ with gen pl желёз and nom pl -- же́лезы. We have two stressed stems, one for the gen pl and -- one for the remaining pl cases, and the variable 'stem' can -- handle only one, so we put the second (gen pl) stem in -- stem_for_bare, which goes into the value of 'bare' (used -- only for gen pl here). stem_for_bare, tr_for_bare = stem, tr end -- Maybe add stress to the stem, depending on whether the -- stem was unstressed and the stress pattern. Stem pattern f -- and variants call for initial stress (голова́ -> го́ловы); -- stem pattern d and variants call for stem-final stress -- (сапожо́к -> сапо́жки). Stem patterns b and b' apparently -- call for stem-final stress as well but it's unlikely to -- make much of a difference (pattern b' only occurs in 3rd-decl -- feminines, which should already have stress in the stem, -- and the only place pattern b gets stem stress is in bare -- forms, i.e. nom sg and/or gen pl depending on the decl type, -- and nom sg stress should already be in the lemma while -- gen pl stress is handled by a different ending-stressing -- mechanism in attach_unstressed(); however, the user is -- free to leave a masc or 3rd-decl fem lemma completely -- unstressed with pattern b, and then the stem-final stress -- *will* make a difference). local function restress_stem(stem, tr, stress, stem_unstressed) -- If the user has indicated they purposely are leaving the -- word unstressed by putting a * at the beginning of the main -- stem, leave it unstressed. This might indicate lack of -- knowledge of the stress or a truly unaccented word -- (e.g. an unaccented suffix). if args.allow_unaccented then return stem, tr end if tr and com.is_unstressed(stem) ~= com.is_unstressed(tr) then error("Stem " .. stem .. " and translit " .. tr .. " must have same accent pattern") end -- it's safe to accent monosyllabic stems if com.is_monosyllabic(stem) then stem, tr = com.make_ending_stressed(stem, tr) -- For those patterns that are ending-stressed in the singular -- nominative (and hence are likely to be expressed without an -- accent on the stem) it's safe to put a particular accent on -- the stem depending on the stress type. Otherwise, give an -- error if no accent. elseif stem_unstressed then if rfind(stress, "^f") then stem, tr = com.make_beginning_stressed(stem, tr) elseif (rfind(stress, "^[bd]") or args.thisn == "p" and ending_stressed_pl_patterns[stress]) then stem, tr = com.make_ending_stressed(stem, tr) elseif com.needs_accents(stem) then error("Stem " .. stem .. " requires an accent") end end return stem, tr end stem, tr = restress_stem(stem, tr, stress, stem_was_unstressed) -- Leave pl unaccented if user wants this; see restress_stem(). if pl and not args.allow_unaccented then if pltr and com.is_unstressed(pl) ~= com.is_unstressed(pltr) then error("Plural stem " .. pl .. " and translit " .. pltr .. " must have same accent pattern") end if com.is_monosyllabic(pl) then pl, pltr = com.make_ending_stressed(pl, pltr) end -- I think this is safe. if com.needs_accents(pl) then if ending_stressed_pl_patterns[stress] then pl, pltr = com.make_ending_stressed(pl, pltr) elseif not args.allow_unaccented then error("Plural stem " .. pl .. " requires an accent") end end end local resolved_bare, resolved_baretr -- Handle (de)reducibles -- FIXME! We are dereducing based on the singular declension. -- In a slash declension things can get weird and we don't -- handle that. We are also computing the bare value from the -- singular stem, and again things can get weird with a plural -- stem. Note that we don't compute a bare value unless we have -- to (either (de)reducible or stress pattern f/f'/f'' combined -- with ё special case); the remaining times we generate the bare -- value directly from the plural stem. if args.reducible and not sgdc.ignore_reduce then -- Zaliznyak treats all nouns in -ье and -ья as being -- reducible. We handle this automatically and don't require -- the user to specify this, but ignore it if so for -- compatibility. if is_reducible(sgdc) then -- If we derived the stem from a nom pl form, then -- it's already reduced, and we need to dereduce it to -- get a bare form; otherwise the stem comes from the -- nom sg and we need to reduce it to get the real stem. if was_plural then resolved_bare, resolved_baretr = export.dereduce_nom_sg_stem(stem, tr, sgdc, stress, old, "error") else resolved_bare, resolved_baretr = stem, tr stem, tr = export.reduce_nom_sg_stem(stem, tr, sgdecl, "error") -- Stem will be unstressed if stress was on elided -- vowel; restress stem the way we did above. (This is -- needed in at least one word, сапожо́к 3*d(2), with -- plural stem probably сапо́жк- and gen pl probably -- сапо́жек.) stem, tr = restress_stem(stem, tr, stress, com.is_unstressed(stem)) if stress ~= "a" and stress ~= "b" and args.alt_gen_pl and not pl then -- Nouns like рожо́к, глазо́к of type 3*d(2) have -- gen pl's ро́жек, гла́зок; to handle this, -- dereduce the reduced stem and store in a -- special place. args.gen_pl_bare, args.gen_pl_baretr = export.dereduce_nom_sg_stem(stem, tr, sgdc, stress, old, "error") end end elseif is_dereducible(sgdc) then resolved_bare, resolved_baretr = export.dereduce_nom_sg_stem(stem, tr, sgdc, stress, old, "error") else error("Declension class " .. sgdecl .. " not (de)reducible") end elseif stem_for_bare and stem ~= stem_for_bare then resolved_bare, resolved_baretr = add_bare_suffix(stem_for_bare, tr_for_bare, old, sgdc, false) end -- Leave unaccented if user wants this; see restress_stem(). -- FIXME, we no longer allow the user to specify the bare value -- so it's unclear if this is needed any more. if resolved_bare and not args.allow_unaccented then if resolved_baretr and com.is_unstressed(resolved_bare) ~= com.is_unstressed(resolved_baretr) then error("Resolved bare stem " .. resolved_bare .. " and translit " .. resolved_baretr .. " must have same accent pattern") end if com.is_monosyllabic(resolved_bare) then resolved_bare, resolved_baretr = com.make_ending_stressed(resolved_bare, resolved_baretr) else if com.needs_accents(resolved_bare) then error("Resolved bare stem " .. resolved_bare .. " requires an accent") end end end args.stem, args.stemtr = stem, tr args.bare, args.baretr = resolved_bare, resolved_baretr args.ustem, args.ustemtr = com.make_unstressed_once(stem, tr) if pl then args.pl, args.pltr = pl, pltr else args.pl, args.pltr = stem, tr end args.upl, args.upltr = com.make_unstressed_once(args.pl, args.pltr) -- Special hack for любо́вь and other reducible 3rd-fem nouns, -- which have the full stem in the ins sg args.ins_sg_stem = sgdecl == "ь-f" and args.reducible and resolved_bare args.ins_sg_tr = sgdecl == "ь-f" and args.reducible and resolved_baretr -- Loop over declension classes (we may have two of them, one for -- singular and one for plural, in the case of a mixed declension -- class of the form SGDECL/PLDECL). for _,decl_spec in ipairs(sub_decls) do local orig_decl = decl_spec[1] local number = decl_spec[2] local real_decl = determine_stress_variant(orig_decl, stress) real_decl = determine_stem_variant(real_decl, number == "pl" and args.pl or args.stem) -- sanity checking; errors should have been caught in -- canonicalize_decl() assert(decl_cats[real_decl], "real_decl " .. real_decl .. " nonexistent") assert(decl_sufs[real_decl], "real_decl " .. real_decl .. " nonexistent") tracking_code(stress, orig_decl, real_decl, args, n, islast) do_stress_pattern(stress, args, real_decl, number, n, islast) -- handle internal notes local internal_note = intable[real_decl] if internal_note then insert_if_not(args.internal_notes, internal_note) end end categorize_and_init_heading(stress, decl, args, n, islast) end end local n = 0 for _, word_info in ipairs(per_word_info) do n = n + 1 local islast = n == #per_word_info local arg_sets, joiner = word_info[1], word_info[2] args.forms = {} args.heading_info = {animacy={}, number={}, gender={}, stress={}, stemetc={}, adjectival={}, reducible={}} args.categories = {} args.genders = {} args.this_any_non_nil = {} if #arg_sets > 1 then track("multiple-arg-sets") insert_cat("~ with multiple argument sets") track("multiple-declensions") insert_cat("~ with multiple declensions") end default_lemma = SUBPAGENAME all_stresses_seen = {} -- Loop over all arg sets. for _, arg_set in ipairs(arg_sets) do do_arg_set(arg_set, n, islast) end if #all_stresses_seen > 1 then track("multiple-accent-patterns") insert_cat("~ with multiple accent patterns") track("multiple-declensions") insert_cat("~ with multiple declensions") end table.insert(args.per_word_heading_info, args.heading_info) table.insert(args.per_word_categories, args.categories) local headings = compute_heading(args) table.insert(args.per_word_headings, headings) table.insert(args.per_word_genders, args.genders) handle_forms_and_overrides(args, n, islast) table.insert(args.per_word_info, {args.forms, joiner}) end handle_overall_forms_and_overrides(args) compute_overall_heading_categories_and_genders(args) for _, case in ipairs(all_cases) do if args[case] then for _, form in ipairs(args[case]) do local ru, tr = form[1], form[2] local ruentry, runotes = m_table_tools.separate_notes(ru) ruentry = m_links.remove_links(ruentry) if rfind(ulower(ruentry), latin_text_class) then --error("Found Latin text " .. ruentry .. " in case " .. case) track("latin-text") track("latin-text/" .. case) end end end end -- Test code to compare existing module to new one. if test_new_ru_noun_module then local m_new_ru_noun = require("Module:User:Benwing2/ru-noun") local newargs = m_new_ru_noun.do_generate_forms(orig_args, old) local difdecl = false for _, case in ipairs(all_cases) do local arg = args[case] local newarg = newargs[case] local is_pl = rfind(case, "_pl") if args.thisn == "s" and is_pl or args.thisn == "p" and not is_pl then -- Don't need to check cases that won't be displayed. elseif not ut.equals(arg, newarg) then local monosyl_accent_diff = false -- Differences only in monosyllabic accents. Enable if we -- change the algorithm for these. --if arg and newarg and #arg == 1 and #newarg == 1 then -- local ru1, tr1 = arg[1][1], arg[1][2] -- local ru2, tr2 = newarg[1][1], newarg[1][2] -- if com.is_monosyllabic(ru1) and com.is_monosyllabic(ru2) then -- ru1, tr1 = com.remove_accents(ru1, tr1) -- ru2, tr2 = com.remove_accents(ru2, tr2) -- if ru1 == ru2 and tr1 == tr2 then -- monosyl_accent_diff = true -- end -- end --end if monosyl_accent_diff then track("monosyl-accent-diff") difdecl = true else -- Uncomment this to display the particular case and -- differing forms. --error(case .. " " .. (arg and nom.concat_forms(arg) or "nil") .. " || " .. (newarg and nom.concat_forms(newarg) or "nil")) track("different-decl") difdecl = true end break end end if not difdecl then track("same-decl") end end return args end -- Implementation of main entry point local function do_show(frame, old) local args = clone_args(frame) local args = export.do_generate_forms(args, old) return make_table(args) .. m_utilities.format_categories(args.categories, lang) end -- The main entry point for modern declension tables. function export.show(frame) return do_show(frame, false) end -- The main entry point for old declension tables. function export.show_old(frame) return do_show(frame, true) end -- Implementation of new entry point, esp. for multiple words local function do_show_multi(frame) local args = clone_args(frame) local args = export.do_generate_forms_multi(args) return make_table(args) .. m_utilities.format_categories(args.categories, lang) end -- The new entry point, esp. for multiple words (but works fine for -- single words). function export.show_multi(frame) return do_show_multi(frame) end local function get_form(forms, preserve_links, raw) local canon_forms = {} for _, form in ipairs(forms) do if raw then local ru, tr = form[1], form[2] ru = rsub(ru, "|", "<!>") if tr then tr = rsub(tr, "|", "<!>") end insert_if_not(canon_forms, {ru, tr}) else local ru, tr = form[1], form[2] local ruentry, runotes = m_table_tools.separate_notes(ru) -- Skip hypothetical forms (but include in the raw versions) if not rfind(runotes, HYPMARKER) then local trentry, trnotes if tr then trentry, trnotes = m_table_tools.separate_notes(tr) end if not preserve_links then ruentry = m_links.remove_links(ruentry) end ruentry = rsub(ruentry, "|", "<!>") if trentry then trentry = rsub(trentry, "|", "<!>") end insert_if_not(canon_forms, {ruentry, trentry}) end end end return nom.concat_forms(canon_forms) end local function case_will_be_displayed(args, forms, case) local ispl = rfind(case, "_pl") local caseok = true if args.n == "p" then caseok = ispl elseif args.n == "s" then caseok = not ispl end if case == "loc" and not args.any_overridden.loc or case == "par" and not args.any_overridden.par or case == "voc" and not args.any_overridden.voc then caseok = false end if args.a == "a" or args.a == "i" then if rfind(case, "_[ai]n") then caseok = false end else -- bianimate -- don't include inanimate/animate variants if combined variant exists -- (typically because inanimate/animate variants are the same); -- FIXME: This could conceivably be different from how the display -- code works, which just checks that the inanimate/animate variants -- are the same when deciding whether to display them, in particular -- if there is an override. Here we are following the algorithm of -- handle_overall_forms_and_overrides(). if (case == "acc_sg_in" or case == "acc_sg_an") and args.acc_sg or (case == "acc_pl_in" or case == "acc_pl_an") and args.acc_pl then caseok = false end end if not forms[case] then caseok = false end return caseok end local function concat_case_args(args, do_all, raw) local ins_text = {} for _, case in ipairs(do_all and all_cases or overridable_cases) do if case_will_be_displayed(args, args, case) then local forms = get_form(args[case], rfind(case, "_linked"), raw) if forms ~= "" then table.insert(ins_text, case .. (raw and "_raw" or "") .. "=" .. forms) end end end return table.concat(ins_text, "|") end -- The entry point for 'ru-noun-forms' to generate all noun forms. -- This returns a single string, with | separating arguments and named -- arguments of the form NAME=VALUE. function export.generate_forms(frame) local args = clone_args(frame) args = export.do_generate_forms(args, false) return concat_case_args(args) end -- The entry point to generate multiple sets of noun forms. This is a hack -- to speed up calling from a bot, where we often want to compare old and new -- argument results to make sure they're the same. Each set of arguments is -- jammed together into a single argument with individual values separated by -- <!>; named arguments are of the form NAME<->VALUE. The return value for -- each set of arguments is as in export.generate_forms(), and the return -- values are concatenated with <!> separating them. NOTE: This will fail if -- the exact sequences <!> or <-> happen to occur in values (which is unlikely, -- esp. as we don't even use the characters <, ! or > for anything) and aren't -- HTML-escaped. function export.generate_multi_forms(frame) local retvals = {} for _, argset in ipairs(frame.args) do local args = {} local i = 0 local argvals = rsplit(argset, "<!>") for _, argval in ipairs(argvals) do local split_arg = rsplit(argval, "<%->") if #split_arg == 1 then i = i + 1 args[i] = ine(split_arg[1]) else assert(#split_arg == 2) args[split_arg[1]] = ine(split_arg[2]) end end args = export.do_generate_forms(args, false) table.insert(retvals, concat_case_args(args)) end return table.concat(retvals, "<!>") end -- The entry point for 'ru-noun-form' to generate a particular noun form. function export.generate_form(frame) local args = clone_args(frame) if not args.form then error("Must specify desired form using form=") end local form = args.form if not ut.contains(all_cases, form) then error("Unrecognized form " .. form) end local args = export.do_generate_forms(args, false) if not args[form] then return "" else return get_form(args[form]) end end -- The entry point for generating arguments of various sorts, including -- the case forms, gender, number and animacy. function export.generate_args(frame) local args = clone_args(frame) args = export.do_generate_forms(args, false) local retargs = {} table.insert(retargs, concat_case_args(args, "doall")) table.insert(retargs, concat_case_args(args, "doall", "raw")) table.insert(retargs, "g=" .. table.concat(args.genders, ",")) -- The following is correct even with ndef because if ndef is -- set we will set it in args.n. table.insert(retargs, "n=" .. (args.n or "b")) table.insert(retargs, "a=" .. (args.a or "i")) return table.concat(retargs, "|") end -- The entry point for compatibility with {{ru-decl-noun-z}}. function export.show_z(frame) local args = clone_args(frame) local stem = args[1] local stress = args[2] local specific = args[4] or "" -- Parse gender/animacy spec local gender, anim = rmatch(args[3], "^([mfn])-([a-z]+)") if not gender then error("Unrecognized gender/anim spec " .. args[3]) end if anim ~= "an" and anim ~= "in" then anim = "both" end args.a = anim -- Handle specific specific = rsub(specific, "ё", ";ё") -- Compute decl; special case for семьянин (perhaps not necessary) local decl = com.make_unstressed_once(stem) == "семьянин" and "#" .. specific or gender .. specific -- Handle overrides args.pre_sg = args.prp_sg args.pre_pl = args.prp_pl args.notes = args.note if args.par then args.par = "+" end if args.loc then if args.loc == "в" then args.loc = "в +" elseif args.loc == "на" then args.loc = "на +" else args.loc = "в +,на +" end end local arg_set = {} table.insert(arg_set, stress) table.insert(arg_set, stem) table.insert(arg_set, decl) local per_word_info = {{{arg_set}, ""}} return generate_forms_1(args, per_word_info) end local stem_expl = { ["velar-stem"] = "a velar (-к, -г or –x)", ["sibilant-stem"] = "a sibilant (-ш, -ж, -ч or -щ)", ["ц-stem"] = "-ц", ["i-stem"] = "-и (old-style -і)", ["vowel-stem"] = "a vowel other than -и or -і, or -й or -ь", ["soft-stem"] = "a soft consonant", ["hard-stem"] = "a hard consonant", } local zaliznyak_stem_type = { ["velar-stem"] = "3", ["sibilant-stem"] = "4", ["ц-stem"] = "5", ["i-stem"] = "7", ["vowel-stem"] = "6", ["soft-stem"] = "2", ["hard-stem"] = "1", ["3rd-declension"] = "8", } local stem_gender_endings = { masculine = { ["hard-stem"] = {"a hard consonant (-ъ old-style)", "-ы"}, ["ц-stem"] = {"-ц (-цъ old-style)", "-ы"}, ["velar-stem"] = {"a velar (plus -ъ old-style)", "-и"}, ["sibilant-stem"] = {"a sibilant (plus -ъ old-style)", "-и"}, ["soft-stem"] = {"-ь", "-и"}, ["i-stem"] = {"-й", "-и"}, ["vowel-stem"] = {"-й", "-и"}, ["3rd-declension"] = {"-ь", "-и"}, }, feminine = { ["hard-stem"] = {"-а", "-ы"}, ["ц-stem"] = {"-а", "-ы"}, ["velar-stem"] = {"-а", "-и"}, ["sibilant-stem"] = {"-а", "-и"}, ["soft-stem"] = {"-я", "-и"}, ["i-stem"] = {"-я", "-и"}, ["vowel-stem"] = {"-я", "-и"}, ["3rd-declension"] = {"-ь", "-и"}, }, neuter = { ["hard-stem"] = {"-о", "-а"}, ["ц-stem"] = {"-е", "-а"}, ["velar-stem"] = {"-о", "-а"}, ["sibilant-stem"] = {"-е", "-а"}, ["soft-stem"] = {"-е", "-я"}, ["i-stem"] = {"-е", "-я"}, ["vowel-stem"] = {"-е", "-я"}, ["3rd-declension"] = {"-мя", "-мена"}, }, } -- Implementation of template 'runouncatboiler'. function export.catboiler(frame) local SUBPAGENAME = mw.loadData("Module:headword/data").pagename local args = clone_args(frame) local cats = {} local function get_stem_gender_text(stem, gender) if not stem_gender_endings[gender] then error("Invalid gender " .. gender) end local endings = stem_gender_endings[gender][stem] if not endings then error("Invalid stem type " .. stem) end local sgending, plending = endings[1], endings[2] local stemtext = stem == "3rd-declension" and "" or " The stem ends in " .. stem_expl[stem] .. " and is Zaliznyak's type " .. zaliznyak_stem_type[stem] .. "." local decltext = stem == "3rd-declension" and "" or " This is traditionally considered to belong to the " .. (gender == "feminine" and "1st" or "2nd") .. " declension." return stem .. ", usually " .. gender .. " ~, normally ending in " .. sgending .. " in the nominative singular " .. " and " .. plending .. " in the nominative plural." .. stemtext .. decltext end local function get_pos() local pos = rmatch(SUBPAGENAME, "^Russian.- ([^ ]*)s ") if not pos then pos = rmatch(SUBPAGENAME, "^Russian.- ([^ ]*)s$") end if not pos then error("Invalid category name, should be e.g. \"Russian nouns with ...\" or \"Russian ... nouns\"") end return pos end local function get_sort_key() local pos, sort_key = rmatch(SUBPAGENAME, "^Russian.- ([^ ]*)s with (.*)$") if sort_key then return sort_key end pos, sort_key = rmatch(SUBPAGENAME, "^Russian ([^ ]*)s (.*)$") if sort_key then return sort_key end return rsub(SUBPAGENAME, "^Russian ", "") end local maintext, pos if args[1] == "stemgenderstress" then local stem, gender, stress stem, gender, stress, pos = rmatch(SUBPAGENAME, "^Russian (.-) (.-)%-form accent%-(.-) (.*)s$") if not stem then error("Invalid category name, should be e.g. \"Russian velar-stem masculine-form accent-a nouns\"") end local stem_gender_text = get_stem_gender_text(stem, gender) local accent_text = " This " .. pos .. " is stressed according to accent pattern " .. rsub(stress, "'", "&#39;") .. "." maintext = stem_gender_text .. accent_text insert_category(cats, "~ by stem type, gender and accent pattern|" .. get_sort_key(), pos) elseif args[1] == "stemgender" then if rfind(SUBPAGENAME, "invariable") then maintext = "invariable (indeclinable) ~, which normally have the same form for all cases and numbers." pos = rmatch(SUBPAGENAME, "^Russian invariable (.*)s$") if not pos then error("Invalid category name, should be e.g. \"Russian invariable nouns\"") end else local stem, gender stem, gender, pos = rmatch(SUBPAGENAME, "^Russian (.-) (.-)%-form (.*)s$") if not stem then error("Invalid category name, should be e.g. \"Russian velar-stem masculine-form nouns\"") end maintext = get_stem_gender_text(stem, gender) end insert_category(cats, "~ by stem type and gender|" .. get_sort_key(), pos) elseif args[1] == "adj" then local stem, gender, stress stem, gender, stress, pos = rmatch(SUBPAGENAME, "^Russian (.*) (.-) accent%-(.-) adjectival (.*)s$") if not stem then stem, gender, pos = rmatch(SUBPAGENAME, "^Russian (.*) (.-) adjectival (.*)s$") end if not stem then error("Invalid category name, should be e.g. \"Russian velar-stem masculine accent-a adjectival nouns\"") end local stemtext if rfind(stem, "possessive") then possessive = "possessive " stem = rsub(stem, " possessive", "") stemtext = "" else if not stem_expl[stem] then error("Invalid stem type " .. stem) end possessive = "" stemtext = " The stem ends in " .. stem_expl[stem] .. " and is Zaliznyak's type " .. zaliznyak_stem_type[stem] .. "." end local stresstext = stress == "a" and "This " .. pos .. " is stressed according to accent pattern a (stress on the stem)." or stress == "b" and "This " .. pos .. " is stressed according to accent pattern b (stress on the ending)." or "All ~ of this class are stressed according to accent pattern a (stress on the stem)." maintext = stem .. " " .. gender .. " ~, with " .. possessive .. "adjectival endings, ending in " .. (gender == "plural-only" and "" or args[2] .. " in the nominative singular and ") .. args[3] .. " in the nominative plural." .. stemtext .. " " .. stresstext insert_category(cats, "~ by stem type, gender and accent pattern|" .. get_sort_key(), pos) else pos = get_pos() if args[1] == "sg" then local sg = rmatch(SUBPAGENAME, "^Russian .* ending in (.*)$") if not sg then error("Invalid category name, should be e.g. \"Russian nouns ending in stressed -е\"") end maintext = "~ ending in " .. (args[2] or sg) .. " in the nominative singular." insert_category(cats, "~ by singular ending", pos) elseif args[1] == "pl" then local pl = rmatch(SUBPAGENAME, "^Russian .* with plural (.*)$") if not pl then error("Invalid category name, should be e.g. \"Russian nouns with plural -е\"") end maintext = "~ ending in " .. (args[2] or pl) .. " in the nominative plural." insert_category(cats, "~ by plural ending|" .. pl, pos) elseif args[1] == "sgpl" then local sg, pl = rmatch(SUBPAGENAME, "^Russian .* ending in (.*) with plural (.*)$") if not sg then error("Invalid category name, should be e.g. \"Russian nouns ending in -о with plural -и\"") end maintext = "~ ending in " .. (args[2] or sg) .. " in the nominative singular, and " .. (args[3] or pl) .. " in the nominative plural." insert_category(cats, "~ by singular and plural ending|" .. sg .. " " .. pl, pos) elseif args[1] == "stress" then local stress = rmatch(SUBPAGENAME, "^Russian .* with accent pattern (.*)$") if not stress then error("Invalid category name, should be e.g. \"Russian nouns with accent pattern d\"") end maintext = "~ with accent pattern " .. rsub(stress, "'", "&#39;") .. "." insert_category(cats, "~ by accent pattern|" .. stress, pos) elseif args[1] == "extracase" then local case = rmatch(SUBPAGENAME, "^Russian .* with (.*)$") if not case then error("Invalid category name, should be e.g. \"Russian nouns with vocative\"") end maintext = "~ with a separate " .. case .. " singular case." insert_category(cats, "~ by case form|" .. case, pos) elseif args[1] == "irregcase" then local case = rmatch(SUBPAGENAME, "^Russian .* with irregular (.*)$") if not case then error("Invalid category name, should be e.g. \"Russian nouns with irregular nominative plural\"") end maintext = "~ with an irregular " .. case .. " case." insert_category(cats, "~ by case form|" .. case, pos) else maintext = "~ " .. args[1] end end insert_category(cats, "~|" .. get_sort_key(), pos, "at beginning") -- format_categories doesn't work in category space so we need to hack our own local categories = {} for _, cat in ipairs(cats) do table.insert(categories, "[[Category:" .. cat .. "]]") end return "This category contains Russian " .. rsub(maintext, "~", pos .. "s") .. "\n" .. mw.getCurrentFrame():expandTemplate{title="ru-categoryTOC", args={}} .. table.concat(categories, "") end -------------------------------------------------------------------------- -- Autodetection and lemma munging -- -------------------------------------------------------------------------- -- Attempt to detect the type of the lemma based on its ending, separating -- off the stem and the ending. GENDER must be present with -ь and plural -- stems, and is otherwise ignored. Return up to three values: The stem -- (lemma minus ending), the singular lemma ending, and if the lemma was -- plural, the plural lemma ending. If the lemma was singular, the singular -- lemma ending will contain any user-given accents; likewise, if the -- lemma was plural, the plural ending will contain such accents. -- VARIANT comes from the declension spec and controls certain declension -- variants. local function detect_lemma_type(lemma, tr, gender, args, variant) local base, ending = rmatch(lemma, "^(.*)([еЕ]́)$") -- accented if base then return base, nom.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*[" .. com.sib_c .. "])([еЕ])$") -- unaccented if base then if variant == "-ище" and not rfind(lemma, "[щЩ][еЕ]$") then error("With declension variant -ище, lemma should end in -ще: " .. lemma) end return base, nom.strip_tr_ending(tr, ending), variant == "-ище" and "(ищ)е-и" or "о" end if variant == "-ишко" then base, ending = rmatch(lemma, "^(.*[шШ][кК])([оО])$") -- unaccented if not base then error("With declension variant -ишко, lemma should end in -шко: " .. lemma) end return base, nom.strip_tr_ending(tr, ending), "(ишк)о-и" end if variant == "-ин" then base, ending = rmatch(lemma, "^(.*)([иИ]́?[нН][ъЪ]?)$") -- maybe accented if not base then error("With declension variant -ин, lemma should end in -ин(ъ): " .. lemma) end return base, nom.strip_tr_ending(tr, ending), ulower(ending) end -- Now autodetect -ин; only animate and in -анин/-янин base, ending = rmatch(lemma, "^(.*[аяАЯ]́?[нН])([иИ]́?[нН][ъЪ]?)$") -- Need to check the animacy to avoid nouns like маиганин, цианин, -- меланин, соланин, etc. if base and args.thisa == "a" then return base, nom.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*)([ёЁ]́?[нН][оО][кК][ъЪ]?)$") if base then return base, nom.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*[" .. com.sib_c .. "])([оО]́[нН][оО][кК][ъЪ]?)$") if base then return base, nom.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*)([ёЁ]́?[нН][оО][чЧ][еЕ][кК][ъЪ]?)$") if base then return base, nom.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*[" .. com.sib_c .. "])([оО]́[нН][оО][чЧ][еЕ][кК][ъЪ]?)$") if base then return base, nom.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*)([мМ][яЯ]́?)$") if base then return base, nom.strip_tr_ending(tr, ending), ulower(ending) end --recognize plural endings if gender == "n" then base, ending = rmatch(lemma, "^(.*)([ьЬ][яЯ]́?)$") if base then -- Don't do this; о/-ья is too rare -- error("Ambiguous plural lemma " .. lemma .. " in -ья, singular could be -о or -ье/-ьё; specify the singular") return base, nom.strip_tr_ending(tr, ending), "ье", ending end base, ending = rmatch(lemma, "^(.*)([аяАЯ]́?)$") if base then return base, nom.strip_tr_ending(tr, ending), rfind(ending, "[аА]") and "о" or "е", ending end base, ending = rmatch(lemma, "^(.*)([ыиЫИ]́?)$") if base then if rfind(ending, "[ыЫ]") or rfind(base, "[" .. com.sib .. com.velar .. "]$") then return base, nom.strip_tr_ending(tr, ending), "о-и", ending else -- FIXME, should we return a slash declension? error("No neuter declension е-и available; use a slash declension") end end end if gender == "f" then base, ending = rmatch(lemma, "^(.*)([ьЬ][иИ]́?)$") if base then return base, nom.strip_tr_ending(tr, ending), "ья", ending end end -- Recognize masculines with irregular plurals, but only if the user -- either explicitly specified that this noun is plural (n=p) or -- specifically requested the irregular plural. This is necessary -- because some masculine nouns have feminine endings, which look -- like irregular plurals. if gender == "m" then if args.thisn == "p" or variant == "-ья" then base, ending = rmatch(lemma, "^(.*)([ьЬ][яЯ]́?)$") if base then return base, nom.strip_tr_ending(tr, ending), (args.old and "ъ-ья" or "-ья"), ending end end if args.thisn == "p" or args.want_sc1 then base, ending = rmatch(lemma, "^(.*)([аА]́?)$") if base then return base, nom.strip_tr_ending(tr, ending), (args.old and "ъ-а" or "-а"), ending end base, ending = rmatch(lemma, "^(.*)([яЯ]́?)$") if base then if rfind(base, "[" .. com.vowel .. "]́?$") then return base, nom.strip_tr_ending(tr, ending), "й-я", ending else return base, nom.strip_tr_ending(tr, ending), "ь-я", ending end end end end if gender == "m" or gender == "f" then base, ending = rmatch(lemma, "^(.*[" .. com.sib .. com.velar .. "])([иИ]́?)$") if not base then base, ending = rmatch(lemma, "^(.*)([ыЫ]́?)$") end if base then return base, nom.strip_tr_ending(tr, ending), gender == "m" and (args.old and "ъ" or "") or "а", ending end base, ending = rmatch(lemma, "^(.*[" .. com.vowel .. "й]́?)([иИ]́?)$") if base then return base, nom.strip_tr_ending(tr, ending), gender == "m" and "й" or "я", ending end base, ending = rmatch(lemma, "^(.*)([иИ]́?)$") if base then return base, nom.strip_tr_ending(tr, ending), gender == "m" and "ь-m" or "я", ending end end if gender == "3f" then base, ending = rmatch(lemma, "^(.*)([иИ]́?)$") if base then return base, nom.strip_tr_ending(tr, ending), "ь-f", ending end end -- end of recognize-plurals code base, ending = rmatch(lemma, "^(.*)([ьЬ][яеёЯЕЁ]́?)$") if base then return base, nom.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*)([йаяеоёъЙАЯЕОЁЪ]́?)$") if base then return base, nom.strip_tr_ending(tr, ending), ulower(ending) end base, ending = rmatch(lemma, "^(.*)([ьЬ])$") if base then if gender == "m" or gender == "f" then return base, nom.strip_tr_ending(tr, ending), "ь-" .. gender elseif gender == "3f" then return base, nom.strip_tr_ending(tr, ending), "ь-f" else error("Need to specify gender m or f with lemma in -ь: ".. lemma) end end if rfind(lemma, "[ыиЫИ]́?$") then error("If this is a plural lemma, gender must be specified: " .. lemma) elseif rfind(lemma, "[уыэюиіѣѵУЫЭЮИІѢѴ]́?$") then error("Don't know how to decline lemma ending in this type of vowel: " .. lemma) end return lemma, tr, "" end local plural_variant_detection_map = { [""] = {["-ья"]="-ья"}, ["ъ"] = {["-ья"]="ъ-ья"}, } local special_case_1_to_plural_variant = { [""] = "-а", ["ъ"] = "ъ-а", ["й"] = "й-я", ["ь-m"] = "ь-я", ["о"] = "о-и", -- these last two are here to avoid getting errors in that checks for -- compatibility with special case 1; the basic variants of these decls -- don't actually exist ["(ишк)о"] = "(ишк)о-и", ["(ищ)е"] = "(ищ)е-и", } local function map_decl(decl, fun) if rfind(decl, "/") then local split_decl = rsplit(decl, "/") if #split_decl ~= 2 then error("Mixed declensional class " .. decl .. "needs exactly two classes, singular and plural") end return fun(split_decl[1]) .. "/" .. fun(split_decl[2]) else return fun(decl) end end -- Canonicalize decl class into non-accented and alias-resolved form; -- but note that some canonical decl class names with an accent in them -- (e.g. е́, not the same as е, whose accented version is ё; and various -- adjective declensions). local function canonicalize_decl(decl, old) local function do_canon(decl) -- remove accents, but not from е́ (for adj decls, accent matters -- as well but we handle that by mapping the accent to a stress pattern -- and then to the accented version in determine_stress_variant()) if decl ~= "е́" then decl = com.remove_accents(decl) end local decl_aliases = old and declensions_old_aliases or declensions_aliases local decl_cats = old and declensions_old_cat or declensions_cat if decl_aliases[decl] then -- If we find an alias, map it. decl = decl_aliases[decl] elseif not decl_cats[decl] then error("Unrecognized declension class " .. decl) end return decl end return map_decl(decl, do_canon) end -- Attempt to determine the actual declension (including plural variants) -- based on a combination of the declension the user specified, what can be -- detected from the lemma, and special case (1), if given in the declension. -- DECL is the value the user passed for the declension field, after -- extraneous annotations (special cases (1) and (2), * for reducible, -- ё for ё/ё alternation and a ; that may precede ё) have been stripped off. -- What's left is one of the following: -- -- 1. Blank, meaning to autodetect the declension from the lemma -- 2. A hyphen followed by a declension variant (-ья, -ин, -ишко, -ище; see -- long comment at top of file) -- 3. A gender (m, f, n, 3f) -- 4. A gender plus declension variant (e.g. f-ья) -- 5. An actual declension, possibly including a plural variant (e.g. о-и) or -- a slash declension (e.g. я/-ья, used for the noun дядя). -- -- Return seven args: stem (lemma minus ending), translit, canonicalized -- declension, explicitly specified gender if any (m, f, n or nil), whether -- the specified declension or detected ending was accented, whether the -- detected ending was pl, and whether the declension was autodetected -- (corresponds to cases where a full word with ending attached is required -- in the lemma field). "Canonicalized" means after autodetection, with -- accents removed, with any aliases mapped to their canonical versions -- and with any requested declension variants applied. The result is either a -- declension that will have a categorization entry (in declensions_cat[] or -- declensions_old_cat[]) or a slash declension where each part similarly has -- a categorization entry. -- -- Note that gender is never required when an explicit declension is given, -- and in connection with stem autodetection is required only when the lemma -- either ends in -ь or is plural. determine_decl = function(lemma, tr, decl, args) -- Assume we're passed a value for DECL of types 1-4 above, and -- fetch gender and requested declension variant. local stem local want_ya_plural, orig_pl_ending, variant local was_autodetected local gender = rmatch(decl, "^(3?[mfn]?)$") if not gender then gender, variant = rmatch(decl, "^(3?[mfn]?)(%-[^%-]+)$") -- But be careful with explicit declensions like -а that look like -- variants without gender (FIXME, eventually we should maybe do -- something about the potential ambiguity). if gender == "" and not ut.contains({"-ья", "-ин", "-ишко", "-ище"}, variant) then gender, variant = nil, nil end end -- If DECL is of type 1-4, handle declension variants and detect -- the actual declension from the lemma. if gender then -- Check for declension variants if variant then if variant == "-ья" then want_ya_plural = "-ья" else -- Sanity-check remaining declension variants, which need -- specific values of animacy, gender and special-case (1) local sc1_needed local animate_needed if variant == "-ишко" then animate_needed = false sc1_needed = true elseif variant == "-ище" then animate_needed = true sc1_needed = true elseif variant == "-ин" then animate_needed = true sc1_needed = false else -- WARNING: If adding another variant, you need to also -- add to the list farther above. error("Unrecognized declension variant " .. variant .. ", should be -ья, -ин, -ишко or -ище") end if sc1_needed and not args.want_sc1 then error("Declension variant " .. variant .. " must be used with special case (1)") elseif sc1_needed == false and args.want_sc1 then error("Declension variant " .. variant .. " must not be used with special case (1)") end if animate_needed and args.thisa ~= "a" then error("Declension variant " .. variant .. " must be specified as animate") elseif animate_needed == false and args.thisa == "a" then error("Declension variant " .. variant .. " must not be specified as animate") end if gender ~= "" and gender ~= "m" then error("Declension variant " .. variant .. " should be used with the masculine gender") end end end stem, tr, decl, orig_pl_ending = detect_lemma_type(lemma, tr, gender, args, variant) was_autodetected = true else stem, tr = lemma, tr end -- Now canonicalize gender if gender == "3f" then gender = "f" elseif gender == "" then gender = nil end -- The ending should be treated as accented if either the original singular -- or plural ending was accented, or if the stem is non-syllabic. local was_accented = com.is_stressed(decl) or orig_pl_ending and com.is_stressed(orig_pl_ending) or com.is_nonsyllabic(stem) local was_plural = not not orig_pl_ending decl = canonicalize_decl(decl, args.old) -- The rest of this code concerns plural variants. It's somewhat -- complicated because there are potentially four sources of plural -- variants (not to mention plural variants constructed using slash -- notation): -- -- 1. A user-requested plural variant in declension types 2 or 4 above -- (currently only -ья) -- 2. An explicit plural variant encoded in an explicit declension of -- type 5 above -- 3. An autodetected plural variant (which will happen in some cases -- when autodetection is performed on a nominative plural) -- 4. A plural variant derived using special case (1). -- -- Up to three actual plural variants might exist (e.g. if the user -- specifies a DECL value or 'm-ья(1)' and a STEM ending in -а, -- although not all three can ever be compatible because -ья and (1) -- are never compatible). We can't have all four because if there's -- an explicit plural variant, there won't be a user-requested or -- autodetected plural variant. -- -- The goal below is to do two things: Check that all available plural -- variants are the same, and generate the actual declension. -- If we have a type-2 or type-3 variant, we already have the actual -- declension; else we need to use a table to map the basic declension -- to the one with the plural variant encoded in it. -- -- NOTE: The code below was written with a more general plural-variant -- system. It probably can be simplified a lot now. -- 1: Handle explicit decl with slash variant if rfind(decl, "/") then if want_ya_plural then -- Don't think this can happen error("Plural variant " .. want_ya_plural .. " not compatible with slash declension " .. decl) end if args.want_sc1 then error("Special case (1) not compatible with slash declension" .. decl) end return stem, tr, decl, gender, was_accented, was_plural, was_autodetected end -- 2: Retrieve explicitly specified or autodetected decl and pl. variant local basic_decl, detected_or_explicit_plural = rmatch(decl, "^(.*)(%-[^mf]+)$") if basic_decl == "ь" then basic_decl = "ь-m" end basic_decl = basic_decl or decl -- 3: Any user-requested plural variant must agree with explicit or -- autodetected variant. if want_ya_plural and detected_or_explicit_plural and want_ya_plural ~= detected_or_explicit_plural then error("Plural variant " .. want_ya_plural .. " requested but plural variant " .. detected_or_explicit_plural .. " detected from plural stem") end -- 4: Handle special case (1). Derive the full declension, make sure its -- plural variant matches any other available plural variants, and -- return the declension. if args.want_sc1 then local sc1_decl = special_case_1_to_plural_variant[basic_decl] or error("Special case (1) not compatible with declension " .. basic_decl) local sc1_plural = rsub(sc1_decl, "^.*%-", "-") local other_plural = want_ya_plural or detected_or_explicit_plural if other_plural and sc1_plural ~= other_plural then error("Plural variant " .. other_plural .. " specified or detected, but special case (1) calls for plural variant " .. sc1_plural) end return stem, tr, sc1_decl, gender, was_accented, was_plural, was_autodetected end -- 5: Handle user-requested plural variant without explicit or detected -- one. (If an explicit or detected one exists, we've already checked -- that it agrees with the user-requested one, and so we already have -- our full declension.) if want_ya_plural and not detected_or_explicit_plural then local variant_decl if plural_variant_detection_map[decl] then variant_decl = plural_variant_detection_map[decl][want_ya_plural] end if variant_decl then return stem, tr, variant_decl, gender, was_accented, was_plural, was_autodetected else return stem, tr, decl .. (args.old and "/ъ-ья" or "/-ья"), gender, was_accented, was_plural, was_autodetected end end -- 6: Just return the full declension, which will include any available -- plural variant in it. return stem, tr, decl, gender, was_accented, was_plural, was_autodetected end -- Convert soft adjectival declensions into hard ones following certain -- stem-final consonants. FIXME: We call this in two places, once -- to handle auto-detection and once to handle explicit declensions; but -- in the former case we end up calling it twice. function determine_adj_stem_variant(decl, stem) local iend = rmatch(decl, "^%+[іи]([йея]?)$") -- Convert ій/ий to ый after velar or sibilant. This is important for -- velars; doesn't really matter one way or the other for sibilants as -- the sibilant rules will convert both sets of endings to the same -- thing (whereas there will be a difference with о vs. е for velars). if iend and rfind(stem, "[" .. com.velar .. com.sib .. "]$") then decl = "+ы" .. iend -- The following is necessary for -ц, unclear if makes sense for -- sibilants. (Would be necessary -- I think -- if we were -- inferring short adjective forms, but we're not.) elseif decl == "+ее" and rfind(stem, "[" .. com.sib_c .. "]$") then decl = "+ое" end return decl end -- Attempt to determine the actual adjective declension based on a -- combination of the declension the user specified and what can be detected -- from the stem. DECL is the value the user passed for the declension field, -- after extraneous annotations have been removed (although none are probably -- relevant here). What's left is one of the following, which always begins -- with +: -- -- 1. +, meaning to autodetect the declension from the stem -- 2. +ь, same as + but selects +ьий instead of +ий if lemma ends in -ий -- 3. +short, +mixed or +proper, with the declension partly specified but -- the particular gender/number-specific short/mixed variant to be -- autodetected -- 4. A gender (+m, +f or +n), used only for detecting the singular of -- plural-form lemmas (this is primarily used in conjunction with template -- ru-noun+, to explicitly specify the gender; for the actual declension, -- it doesn't much matter what singular gender we pick since we're a -- plural only) -- 5. A gender plus short/mixed/proper/ь (e.g. +f-mixed), again with the gender -- used only for detecting the singular of plural-form short/mixed lemmas -- 6. An actual declension, possibly including a slash declension -- (WARNING: Unclear if slash declensions will work, especially those -- that are adjective/noun combinations) -- -- Returns the same seven args as for determine_decl(). The returned -- declension will always begin with +. detect_adj_type = function(lemma, tr, decl, old) local was_autodetected local base, ending local basedecl, g = rmatch(decl, "^(%+)([mfn])$") if not basedecl then g, basedecl = rmatch(decl, "^%+([mfn])%-([a-zь]+)$") if basedecl then basedecl = "+" .. basedecl end end decl = basedecl or decl if decl == "+" or decl == "+ь" then base, ending = rmatch(lemma, "^(.*)([ыиіьаяое]́?[йея])$") if ending == "ий" and decl == "+ь" then decl = "+ьий" elseif ending == "ій" and decl == "+ь" then decl = "+ьій" elseif ending then decl = "+" .. ending else base, ending = rmatch(lemma, "^(.-)([оаыъ]?́?)$") assert(base) local shortmixed = rfind(base, "^[" .. com.uppercase .. "].*[иы]́н$") and "stressed-proper" or -- accented rfind(base, "^[" .. com.uppercase .. "].*[иы]н$") and "proper" or --not accented rlfind(base, "[ёео]́?в$") and "short" or rlfind(base, "[ыи]́н$") and "stressed-short" or -- accented rlfind(base, "[ыи]н$") and "mixed" --not accented if not shortmixed then error("Cannot determine stem type of adjective: " .. lemma) end decl = "+" .. ending .. "-" .. shortmixed end was_autodetected = true elseif ut.contains({"+short", "+mixed", "+proper"}, decl) then base, ending = rmatch(lemma, "^(.-)([оаыъ]?́?)$") assert(base) local shortmixed = usub(decl, 2) if rlfind(base, "[ыи]́н$") then -- accented if shortmixed == "short" then shortmixed = "stressed-short" elseif shortmixed == "proper" then shortmixed = "stressed-proper" end end decl = "+" .. ending .. "-" .. shortmixed was_autodetected = true else base = lemma end if ending and ending ~= "" then tr = nom.strip_tr_ending(tr, ending) end -- Remove any accents from the declension, but not their presence. -- We will convert was_accented into stress pattern b, and convert that -- back to an accented version in determine_stress_variant(). This way -- we end up with the stressed version whether the user placed an accent -- in the ending or decl or specified stress pattern b. -- FIXME, might not work in the presence of slash declensions local was_accented = com.is_stressed(decl) decl = com.remove_accents(decl) decl = map_decl(decl, function(decl) return determine_adj_stem_variant(decl, base) end) local singdecl if decl == "+ые" then singdecl = (g == "m" or not g) and (was_accented and "+ой" or "+ый") or not old and g == "f" and "+ая" or not old and g == "n" and "+ое" elseif decl == "+ыя" and old then singdecl = (g == "f" or not g) and "+ая" or g == "n" and "+ое" elseif decl == "+ие" and not old then singdecl = (g == "m" or not g) and "+ий" or g == "f" and "+яя" or g == "n" and "+ее" elseif decl == "+іе" and old and (g == "m" or not g) then singdecl = "+ій" elseif decl == "+ія" and old then singdecl = (g == "f" or not g) and "+яя" or g == "n" and "+ее" elseif decl == "+ьи" then singdecl = (g == "m" or not g) and (old and "+ьій" or "+ьий") or g == "f" and "+ья" or g == "n" and "+ье" elseif rfind(decl, "^%+ы%-") then -- decl +ы-mixed or similar local beg = (g == "m" or not g) and (old and "ъ" or "") or g == "f" and "а" or g == "n" and "о" singdecl = beg and "+" .. beg .. usub(decl, 3) end if singdecl then was_plural = true decl = singdecl end return base, tr, canonicalize_decl(decl, old), g, was_accented, was_plural, was_autodetected end -- If stress pattern omitted, detect it based on whether ending is stressed -- or the decl class or stem accent calls for inherent stress, defaulting to -- pattern a. This is run after alias resolution and accent removal of DECL; -- WAS_ACCENTED indicates whether the ending was originally stressed. -- FIXME: This is run before splitting slash patterns but should be run after. detect_stress_pattern = function(stem, decl, decl_cats, reducible, was_plural, was_accented) -- ёнок and ёночек always bear stress if rfind(decl, "ёнокъ?") or rfind(decl, "ёночекъ?") then return "b" -- stressed suffix и́н; missing in plural and true endings don't bear stress -- (except for exceptional господи́н) elseif rfind(decl, "инъ?") and was_accented then return "d" -- Adjectival -ой always bears the stress elseif rfind(decl, "%+ой") then return "b" -- Adjectival stressed-short, stressed-proper bears the stress elseif rfind(decl, "^%+.*%-stressed") then return "b" -- Pattern b if ending was accented by user elseif was_accented then return "b" -- Nonsyllabic stem means pattern b elseif com.is_nonsyllabic(stem) then return "b" -- Accent on reducible vowel in masc nom sg (not plural) means pattern b. -- Think about whether we want to enable this. -- elseif reducible and not was_plural then -- -- FIXME hack. Eliminate plural part of slash declension. -- decl = rsub(decl, "/.*", "") -- if decl_cats[decl] and decl_cats[decl].g == "m" then -- if com.is_ending_stressed(stem) or com.is_monosyllabic(stem) then -- return "b" -- end -- end end return "a" end -- In certain special cases, depending on the declension, we override the -- user-specified stress pattern and convert it to something else. -- NOTE: This function is run after alias resolution and accent removal. -- FIXME: It's also run before splitting slash patterns but should be run after. override_stress_pattern = function(decl, stress) -- ёнок and ёночек always bear stress; if user specified a, -- convert to b. Don't do this with slash patterns (see FIXME above). if stress == "a" and (rfind(decl, "^ёнокъ?$") or rfind(decl, "^ёночекъ?$")) then return "b" end return stress end -- Canonicalize an adjectival declension to either the stressed or unstressed -- variant depending on the stress. Ultimately this is what ensures that -- the user's stress mark on an adjectival ending is respected. determine_stress_variant = function(decl, stress) if stress == "b" then if decl == "+ая" then return "+а́я" elseif decl == "+ое" then return "+о́е" else -- Convert +...-short to +...-stressed-short and same for -proper local b, e = rmatch(decl, "^%+(.*)%-(short)$") if not b then b, e = rmatch(decl, "^%+(.*)%-(proper)$") end if b and not rfind(b, "%-stressed") then return "+" .. b .. "-stressed-" .. e end end end return decl end -- Canonicalize a declension based on the final stem consonant, in -- particular converting soft declensions to hard ones after velars and/or -- sibilants. FIXME: We also do this canonicalization earlier on during -- auto-detection (determine_adj_stem_variant() is called by -- detect_adj_type(), and code in detect_lemma_type() does the equivalent -- of the first clause below). Doing it here ensures that explicitly -- specified declensions get handled as well, but it would be nice to not -- do the same thing twice in the auto-detection case. determine_stem_variant = function(decl, stem) if decl == "е" and rfind(stem, "[" .. com.sib_c .. "]$") then return "о" end return determine_adj_stem_variant(decl, stem) end is_reducible = function(decl_cat) if decl_cat.suffix or decl_cat.cant_reduce or decl_cat.adj then return false elseif decl_cat.decl == "3rd" and decl_cat.g == "f" or decl_cat.g == "m" then return true else return false end end -- Reduce nom sg to stem by eliminating the "epenthetic" vowel. Applies to -- masculine 2nd-declension hard and soft, and 3rd-declension feminine in -- -ь. STEM and DECL are after determine_decl(), before converting -- outward-facing declensions to inward ones. function export.reduce_nom_sg_stem(stem, tr, decl, can_err) local full_stem = stem .. (decl == "й" and decl or "") local full_tr = tr and tr .. (decl == "й" and "j" or "") local ret, rettr = com.reduce_stem(full_stem, full_tr) if not ret and can_err then error("Unable to reduce stem " .. stem) end return ret, rettr end is_dereducible = function(decl_cat) if decl_cat.suffix or decl_cat.cant_reduce or decl_cat.adj then return false elseif decl_cat.decl == "1st" or decl_cat.decl == "2nd" and decl_cat.g == "n" then return true else return false end end -- Add a possible suffix to the bare stem, according to the declension and -- value of OLD. This may be -ь, -ъ, -й or nothing. We need to do this here -- because we don't actually attach such a suffix in attach_unstressed() due -- to situations where we don't want the suffix added, e.g. dereducible nouns -- in -ня. add_bare_suffix = function(bare, baretr, old, sgdc, dereduced) if old and sgdc.hard == "hard" then -- Final -ъ isn't transliterated return bare .. "ъ", baretr elseif sgdc.hard == "soft" or sgdc.hard == "palatal" then -- This next clause corresponds to a special case in Vitalik's module. -- It says that nouns in -ня (accent class a) have gen pl without -- trailing -ь. It appears to apply to most nouns in -ня (possibly -- all in -льня), but ку́хня (gen pl ку́хонь) and дерéвня (gen pl -- дереве́нь) is an exception. (Vitalik's module has an extra -- condition here 'stress == "a"' that would exclude дере́вня but I -- don't think this condition is in Zaliznyak, as he indicates -- дере́вня as having an exceptional genitive plural.) if dereduced and rfind(bare, "[нН]$") and sgdc.decl == "1st" then -- FIXME: What happens in this case old-style? I assume that -- -ъ is added, but this is a guess. -- Final -ъ isn't transliterated return bare .. (old and "ъ" or ""), baretr elseif rfind(bare, "[" .. com.vowel .. "]́?$") then return bare .. "й", baretr and (baretr .. "j") else return bare .. "ь", baretr and (baretr .. "ʹ") end else return bare, baretr end end -- Dereduce stem to the form found in the gen pl (and maybe nom sg) by -- inserting an epenthetic vowel. Applies to 1st declension and 2nd -- declension neuter, and to 2nd declension masculine when the stem was -- specified as a plural form (in which case we're deriving the nom sg, -- and also the gen pl in the alt-gen-pl scenario). STEM and DECL are -- after determine_decl(), before converting outward-facing declensions -- to inward ones. STRESS is the stess pattern. function export.dereduce_nom_sg_stem(stem, tr, sgdc, stress, old, can_err) local epenthetic_stress = ending_stressed_gen_pl_patterns[stress] local ret, rettr = com.dereduce_stem(stem, tr, epenthetic_stress) if not ret then if can_err then error("Unable to dereduce stem " .. stem) else return nil, nil end end return add_bare_suffix(ret, rettr, old, sgdc, true) end -------------------------------------------------------------------------- -- Second-declension masculine -- -------------------------------------------------------------------------- ----------------- Masculine hard ------------------- -- Hard-masculine declension, ending in a hard consonant -- (ending in -ъ, old-style). declensions_old["ъ"] = { ["nom_sg"] = "ъ", ["gen_sg"] = "а́", ["dat_sg"] = "у́", ["acc_sg"] = nil, ["ins_sg"] = "о́мъ", ["pre_sg"] = "ѣ́", ["nom_pl"] = "ы́", ["gen_pl"] = function(stem, stress) return nom.sibilant_suffixes[ulower(usub(stem, -1))] and "е́й" or "о́въ" end, ["alt_gen_pl"] = "ъ", ["dat_pl"] = "а́мъ", ["acc_pl"] = nil, ["ins_pl"] = "а́ми", ["pre_pl"] = "а́хъ", } declensions_old_cat["ъ"] = { decl="2nd", hard="hard", g="m" } -- Normal mapping of old ъ would be "" (blank), but we set up "#" as an alias -- so we have a way of referring to it without defaulting if need be and -- distinct from auto-detection (e.g. in the second stem of a word, or to -- override autodetection of -ёнок or -ин -- the latter is necessary in the -- case of семьянин). declensions_aliases["#"] = "" ----------------- Masculine hard, irregular plural ------------------- -- Hard-masculine declension, ending in a hard consonant -- (ending in -ъ, old-style), with irreg nom pl -а. declensions_old["ъ-а"] = mw.clone(declensions_old["ъ"]) declensions_old["ъ-а"]["nom_pl"] = "а́" declensions_old_cat["ъ-а"] = { decl="2nd", hard="hard", g="m", alt_nom_pl=true } declensions_aliases["#-a"] = "-a" -- Hard-masculine declension, ending in a hard consonant -- (ending in -ъ, old-style), with irreg soft pl -ья. -- Differs from the normal declension throughout the plural. declensions_old["ъ-ья"] = { ["nom_sg"] = "ъ", ["gen_sg"] = "а́", ["dat_sg"] = "у́", ["acc_sg"] = nil, ["ins_sg"] = "о́мъ", ["pre_sg"] = "ѣ́", ["nom_pl"] = "ья́", ["gen_pl"] = "ьёвъ", ["alt_gen_pl"] = "е́й", ["dat_pl"] = "ья́мъ", ["acc_pl"] = nil, ["ins_pl"] = "ья́ми", ["pre_pl"] = "ья́хъ", } declensions_old_cat["ъ-ья"] = { decl="2nd", hard="hard", g="m", irregpl=true } declensions_aliases["#-ья"] = "-ья" ----------------- Masculine hard, suffixed, irregular plural ------------------- declensions_old["инъ"] = { ["nom_sg"] = "и́нъ", ["gen_sg"] = "и́на", ["dat_sg"] = "и́ну", ["acc_sg"] = nil, ["ins_sg"] = "и́номъ", ["pre_sg"] = "и́нѣ", ["nom_pl"] = "е́", ["gen_pl"] = "ъ", ["dat_pl"] = "а́мъ", ["acc_pl"] = nil, ["ins_pl"] = "а́ми", ["pre_pl"] = "а́хъ", } declensions_old_cat["инъ"] = { decl="2nd", hard="hard", g="m", suffix=true } declensions_old["ёнокъ"] = { ["nom_sg"] = "ёнокъ", ["gen_sg"] = "ёнка", ["dat_sg"] = "ёнку", ["acc_sg"] = nil, ["ins_sg"] = "ёнкомъ", ["pre_sg"] = "ёнкѣ", ["nom_pl"] = "я́та", ["gen_pl"] = "я́тъ", ["dat_pl"] = "я́тамъ", ["acc_pl"] = nil, ["ins_pl"] = "я́тами", ["pre_pl"] = "я́тахъ", } declensions_old_cat["ёнокъ"] = { decl="2nd", hard="hard", g="m", suffix=true } declensions_old_aliases["онокъ"] = "ёнокъ" declensions_old_aliases["енокъ"] = "ёнокъ" declensions_old["ёночекъ"] = { ["nom_sg"] = "ёночекъ", ["gen_sg"] = "ёночка", ["dat_sg"] = "ёночку", ["acc_sg"] = nil, ["ins_sg"] = "ёночкомъ", ["pre_sg"] = "ёночкѣ", ["nom_pl"] = "я́тки", ["gen_pl"] = "я́токъ", ["dat_pl"] = "я́ткамъ", ["acc_pl"] = nil, ["ins_pl"] = "я́тками", ["pre_pl"] = "я́ткахъ", } declensions_old_cat["ёночекъ"] = { decl="2nd", hard="hard", g="m", suffix=true } declensions_old_aliases["оночекъ"] = "ёночекъ" declensions_old_aliases["еночекъ"] = "ёночекъ" ----------------- Masculine soft ------------------- -- Normal soft-masculine declension in -ь declensions_old["ь-m"] = { ["nom_sg"] = "ь", ["gen_sg"] = "я́", ["dat_sg"] = "ю́", ["acc_sg"] = nil, ["ins_sg"] = "ёмъ", ["pre_sg"] = "ѣ́", ["nom_pl"] = "и́", ["gen_pl"] = "е́й", ["alt_gen_pl"] = "ь", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["ь-m"] = { decl="2nd", hard="soft", g="m" } -- Soft-masculine declension in -ь with irreg nom pl -я declensions_old["ь-я"] = mw.clone(declensions_old["ь-m"]) declensions_old["ь-я"]["nom_pl"] = "я́" declensions_old_cat["ь-я"] = { decl="2nd", hard="soft", g="m", alt_nom_pl=true } ----------------- Masculine palatal ------------------- -- Masculine declension in palatal -й declensions_old["й"] = { ["nom_sg"] = "й", ["gen_sg"] = "я́", ["dat_sg"] = "ю́", ["acc_sg"] = nil, ["ins_sg"] = "ёмъ", ["pre_sg"] = function(stem, stress) return rlfind(stem, "[іи]́?$") and not ending_stressed_pre_sg_patterns[stress] and "и" or "ѣ́" end, ["nom_pl"] = "и́", ["gen_pl"] = "ёвъ", ["alt_gen_pl"] = "й", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["й"] = { decl="2nd", hard="palatal", g="m" } declensions_old["й-я"] = mw.clone(declensions_old["й"]) declensions_old["й-я"]["nom_pl"] = "я́" declensions_old_cat["й-я"] = { decl="2nd", hard="palatal", g="m", alt_nom_pl=true } -------------------------------------------------------------------------- -- First-declension feminine -- -------------------------------------------------------------------------- ----------------- Feminine hard ------------------- -- Hard-feminine declension in -а declensions_old["а"] = { ["nom_sg"] = "а́", ["gen_sg"] = "ы́", ["dat_sg"] = "ѣ́", ["acc_sg"] = "у́", ["ins_sg"] = {"о́й<insa>", "о́ю<insb>"}, -- see concat_word_forms_1() ["pre_sg"] = "ѣ́", ["nom_pl"] = "ы́", ["gen_pl"] = function(stem, stress) return nom.sibilant_suffixes[ulower(usub(stem, -1))] and ending_stressed_gen_pl_patterns[stress] and "е́й" or "ъ" end, ["alt_gen_pl"] = "е́й", ["dat_pl"] = "а́мъ", ["acc_pl"] = nil, ["ins_pl"] = "а́ми", ["pre_pl"] = "а́хъ", } declensions_old_cat["а"] = { decl="1st", hard="hard", g="f" } ----------------- Feminine soft ------------------- -- Soft-feminine declension in -я declensions_old["я"] = { ["nom_sg"] = "я́", ["gen_sg"] = "и́", ["dat_sg"] = function(stem, stress) return rlfind(stem, "[іи]́?$") and not ending_stressed_dat_sg_patterns[stress] and "и" or "ѣ́" end, ["acc_sg"] = "ю́", ["ins_sg"] = {"ёй<insa>", "ёю<insb>"}, -- see concat_word_forms_1() ["pre_sg"] = function(stem, stress) return rlfind(stem, "[іи]́?$") and not ending_stressed_pre_sg_patterns[stress] and "и" or "ѣ́" end, ["nom_pl"] = "и́", ["gen_pl"] = function(stem, stress) return ending_stressed_gen_pl_patterns[stress] and not rlfind(stem, "[" .. com.vowel .. "]́?$") and "е́й" or "й" end, ["alt_gen_pl"] = "е́й", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["я"] = { decl="1st", hard="soft", g="f" } -- Soft-feminine declension in -ья. -- Almost like ь + -я endings except for genitive plural. declensions_old["ья"] = { ["nom_sg"] = "ья́", ["gen_sg"] = "ьи́", ["dat_sg"] = "ьѣ́", ["acc_sg"] = "ью́", ["ins_sg"] = {"ьёй<insa>", "ьёю<insb>"}, -- see concat_word_forms_1() ["pre_sg"] = "ьѣ́", ["nom_pl"] = "ьи́", ["gen_pl"] = function(stem, stress) -- circumflex accent is a signal that forces stress, particularly -- in accent pattern d/d'. return (ending_stressed_gen_pl_patterns[stress] or stress == "d" or stress == "d'") and "е̂й" or "ий" end, ["dat_pl"] = "ья́мъ", ["acc_pl"] = nil, ["ins_pl"] = "ья́ми", ["pre_pl"] = "ья́хъ", } declensions_old_cat["ья"] = { decl="1st", hard="soft", g="f", stem_suffix="ь", gensg=true, ignore_reduce=true -- already has dereduced gen pl } -------------------------------------------------------------------------- -- Second-declension neuter -- -------------------------------------------------------------------------- ----------------- Neuter hard ------------------- -- Normal hard-neuter declension in -о declensions_old["о"] = { ["nom_sg"] = "о́", ["gen_sg"] = "а́", ["dat_sg"] = "у́", ["acc_sg"] = function(stem, stress, args) return not (args.explicit_gender == "m" and args.thisa == "a") and "о́" or nil end, ["ins_sg"] = "о́мъ", ["pre_sg"] = "ѣ́", ["nom_pl"] = "а́", ["gen_pl"] = function(stem, stress) return nom.sibilant_suffixes[ulower(usub(stem, -1))] and ending_stressed_gen_pl_patterns[stress] and "е́й" or "ъ" end, ["alt_gen_pl"] = "о́въ", ["dat_pl"] = "а́мъ", ["acc_pl"] = nil, ["ins_pl"] = "а́ми", ["pre_pl"] = "а́хъ", } declensions_old_cat["о"] = { decl="2nd", hard="hard", g="n" } -- Hard-neuter declension in -о with irreg nom pl -и declensions_old["о-и"] = mw.clone(declensions_old["о"]) declensions_old["о-и"]["nom_pl"] = "ы́" declensions_old_cat["о-и"] = { decl="2nd", hard="hard", g="n", alt_nom_pl=true } declensions_old_aliases["о-ы"] = "о-и" -- Masculine-gender neuter-form declension in -(ишк)о with irreg nom pl -и, -- with colloquial feminine endings in some of the singular cases -- (§5 p. 74 of Zaliznyak) declensions_old["(ишк)о-и"] = mw.clone(declensions_old["о-и"]) declensions_old["(ишк)о-и"]["gen_sg"] = {"а́", "ы́1"} declensions_old["(ишк)о-и"]["dat_sg"] = {"у́", "ѣ́1"} declensions_old["(ишк)о-и"]["ins_sg"] = {"о́мъ", "о́й1"} declensions_old_cat["(ишк)о-и"] = { decl="2nd", hard="hard", g="n", colloqfem=true, alt_nom_pl=true } internal_notes_table_old["(ишк)о-и"] = "<sup>1</sup> Colloquial." -- Masculine-gender animate neuter-form declension in -(ищ)е with irreg -- nom pl -и, with colloquial feminine endings in some of the singular cases -- (§4 p. 74 of Zaliznyak) declensions_old["(ищ)е-и"] = mw.clone(declensions_old["о-и"]) declensions_old["(ищ)е-и"]["acc_sg"] = {"а́", "у́1"} declensions_old["(ищ)е-и"]["gen_sg"] = {"а́", "ы́2"} declensions_old["(ищ)е-и"]["dat_sg"] = {"у́", "ѣ́2"} declensions_old["(ищ)е-и"]["ins_sg"] = {"о́мъ", "о́й2"} declensions_old_cat["(ищ)е-и"] = { decl="2nd", hard="hard", g="n", colloqfem=true, alt_nom_pl=true } internal_notes_table_old["(ищ)е-и"] = "<sup>1</sup> Colloquial.<br /><sup>2</sup> Less common, more colloquial." ----------------- Neuter soft ------------------- -- Soft-neuter declension in -е (stressed -ё) declensions_old["е"] = { ["nom_sg"] = "ё", ["gen_sg"] = "я́", ["dat_sg"] = "ю́", ["acc_sg"] = function(stem, stress, args) return not (args.explicit_gender == "m" and args.thisa == "a") and "ё" or nil end, ["ins_sg"] = "ёмъ", ["pre_sg"] = function(stem, stress) return rlfind(stem, "[іи]́?$") and not ending_stressed_pre_sg_patterns[stress] and "и" or "ѣ́" end, ["nom_pl"] = "я́", ["gen_pl"] = function(stem, stress) return ending_stressed_gen_pl_patterns[stress] and not rlfind(stem, "[" .. com.vowel .. "]́?$") and "е́й" or "й" end, ["alt_gen_pl"] = "ёвъ", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["е"] = { singular = function(suffix) if suffix == "ё" then return "ending in -ё" else return {} end end, decl="2nd", hard="soft", g="n", gensg=true } -- User-facing declension type "ё" = "е" declensions_old_aliases["ё"] = "е" -- Rare soft-neuter declension in stressed -е́ (e.g. муде́, бытие́) declensions_old["е́"] = { ["nom_sg"] = "е́", ["gen_sg"] = "я́", ["dat_sg"] = "ю́", ["acc_sg"] = function(stem, stress, args) return not (args.explicit_gender == "m" and args.thisa == "a") and "е́" or nil end, ["ins_sg"] = "е́мъ", ["pre_sg"] = function(stem, stress) -- FIXME!!! Are we sure about this condition? This is what was -- found in the old template, but the related -е declension has -- -ие prep sg ending -(и)и only when *not* stressed. return rlfind(stem, "[іи]́?$") and "и́" or "ѣ́" end, ["nom_pl"] = "я́", ["gen_pl"] = function(stem, stress) return rlfind(stem, "[" .. com.vowel .. "]́?$") and "й" or "е́й" end, ["alt_gen_pl"] = "ёвъ", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["е́"] = { singular = "ending in stressed -е", decl="2nd", hard="soft", g="n", gensg=true } -- Soft-neuter declension in unstressed -ье (stressed -ьё). declensions_old["ье"] = { ["nom_sg"] = "ьё", ["gen_sg"] = "ья́", ["dat_sg"] = "ью́", ["acc_sg"] = function(stem, stress, args) return not (args.explicit_gender == "m" and args.thisa == "a") and "ьё" or nil end, ["ins_sg"] = "ьёмъ", ["pre_sg"] = "ьѣ́", ["nom_pl"] = "ья́", ["gen_pl"] = function(stem, stress) return ending_stressed_gen_pl_patterns[stress] and "е́й" or "ий" end, ["alt_gen_pl"] = "ьёвъ", ["dat_pl"] = "ья́мъ", ["acc_pl"] = nil, ["ins_pl"] = "ья́ми", ["pre_pl"] = "ья́хъ", } declensions_old_cat["ье"] = { decl="2nd", hard="soft", g="n", stem_suffix="ь", gensg=true, ignore_reduce=true -- already has dereduced gen pl } declensions_old_aliases["ьё"] = "ье" -------------------------------------------------------------------------- -- Third declension -- -------------------------------------------------------------------------- declensions_old["ь-f"] = { ["nom_sg"] = "ь", ["gen_sg"] = "и́", ["dat_sg"] = "и́", ["acc_sg"] = "ь", ["ins_sg"] = "ью́", ["pre_sg"] = "и́", ["nom_pl"] = "и́", ["gen_pl"] = "е́й", ["dat_pl"] = "я́мъ", ["acc_pl"] = nil, ["ins_pl"] = "я́ми", ["pre_pl"] = "я́хъ", } declensions_old_cat["ь-f"] = { decl="3rd", hard="soft", g="f" } declensions_old["мя"] = { ["nom_sg"] = "мя", ["gen_sg"] = "мени", ["dat_sg"] = "мени", ["acc_sg"] = nil, ["ins_sg"] = "менемъ", ["pre_sg"] = "мени", ["nom_pl"] = "мена́", ["gen_pl"] = "мёнъ", ["dat_pl"] = "мена́мъ", ["acc_pl"] = nil, ["ins_pl"] = "мена́ми", ["pre_pl"] = "мена́хъ", } declensions_old_cat["мя"] = { decl="3rd", hard="soft", g="n", cant_reduce=true } -------------------------------------------------------------------------- -- Invariable -- -------------------------------------------------------------------------- -- Invariable declension; no endings. declensions_old["$"] = { ["nom_sg"] = "", ["gen_sg"] = "", ["dat_sg"] = "", ["acc_sg"] = nil, ["ins_sg"] = "", ["pre_sg"] = "", ["nom_pl"] = "", ["gen_pl"] = "", ["dat_pl"] = "", ["acc_pl"] = nil, ["ins_pl"] = "", ["pre_pl"] = "", } declensions_old_cat["$"] = { decl="invariable", hard="none", g="none" } -------------------------------------------------------------------------- -- Adjectival -- -------------------------------------------------------------------------- -- This needs to be up here because it is called just below. local function old_to_new(v) v = rsub(v, "ъ$", "") v = rsub(v, "^ъ", "") v = rsub(v, "(%A)ъ", "%1") v = rsub(v, "ъ(%A)", "%1") v = rsub(v, "і", "и") v = rsub(v, "ѣ", "е") return v end -- Meaning of entry is: -- 1. The declension name in module ru-adjective -- 2. The masculine declension name in this module -- 3. The neuter declension name in this module -- 4. The feminine declension name in this module -- 5. The value of hard= for the declensions_cat entry -- 6. The value of decl= for the declensions_cat entry -- 7. The value of possadj= for the declensions_cat entry (true if possessive -- or similar type of adjective) local adj_decl_map = { {"ый", "ый", "ое", "ая", "hard", "long", false}, {"ій", "ій", "ее", "яя", "soft", "long", false}, {"ой", "ой", "о́е", "а́я", "hard", "long", false}, {"ьій", "ьій", "ье", "ья", "palatal", "long", true}, {"short", "ъ-short", "о-short", "а-short", "hard", "short", true}, {"mixed", "ъ-mixed", "о-mixed", "а-mixed", "hard", "mixed", true}, {"proper", "ъ-proper", "о-proper", "а-proper", "hard", "proper", true}, {"stressed-short", "ъ-stressed-short", "о-stressed-short", "а-stressed-short", "hard", "short", true}, {"stressed-proper", "ъ-stressed-proper", "о-stressed-proper", "а-stressed-proper", "hard", "proper", true}, } local function get_adjectival_decl(adjtype, gender, old) local decl, intnotes = m_ru_adj.get_nominal_decl(adjtype, gender, old) -- hack fem ins_sg to insert <insa>, <insb>; see concat_word_forms_1() if gender == "f" and type(decl["ins_sg"]) == "table" and #decl["ins_sg"] == 2 then decl["ins_sg"][1] = decl["ins_sg"][1] .. "<insa>" decl["ins_sg"][2] = decl["ins_sg"][2] .. "<insb>" end return decl, intnotes end for _, declspec in ipairs(adj_decl_map) do local oadjdecl = declspec[1] local nadjdecl = old_to_new(oadjdecl) local odecl_by_gender = {m="+" .. declspec[2], n="+" .. declspec[3], f="+" .. declspec[4]} local hard = declspec[5] local decltype = declspec[6] local possadj = declspec[7] for _, g in ipairs({"m", "n", "f"}) do local odecl = odecl_by_gender[g] local ndecl = old_to_new(odecl) declensions_old[odecl], internal_notes_table_old[odecl] = get_adjectival_decl(oadjdecl, g, true) declensions[ndecl], internal_notes_table[ndecl] = get_adjectival_decl(nadjdecl, g, false) declensions_old_cat[odecl] = { decl=decltype, hard=hard, g=g, adj=true, possadj=possadj } declensions_cat[ndecl] = { decl=decltype, hard=hard, g=g, adj=true, possadj=possadj } end end -- Set up some aliases. declensions_old_aliases["+о́-short"] = "+о-stressed-short" declensions_old_aliases["+а́-short"] = "+а-stressed-short" declensions_old_aliases["+о́-proper"] = "+о-stressed-proper" declensions_old_aliases["+а́-proper"] = "+а-stressed-proper" declensions_aliases["+#-short"] = "+-short" declensions_aliases["+#-mixed"] = "+-mixed" declensions_aliases["+#-proper"] = "+-proper" declensions_aliases["+#-stressed-short"] = "+-stressed-short" declensions_aliases["+#-stressed-proper"] = "+-stressed-proper" -------------------------------------------------------------------------- -- Populate new from old -- -------------------------------------------------------------------------- -- Function to convert an entry in an old declensions table to new. local function old_decl_entry_to_new(v) if not v then return nil elseif type(v) == "table" then local new_entry = {} for _, i in ipairs(v) do table.insert(new_entry, old_decl_entry_to_new(i)) end return new_entry elseif type(v) == "function" then return function(stem, suffix, args) return old_decl_entry_to_new(v(stem, suffix, args)) end else return old_to_new(v) end end -- Function to convert an old declensions table to new. local function old_decl_to_new(odecl) local ndecl = {} for k, v in pairs(odecl) do ndecl[k] = old_decl_entry_to_new(v) end return ndecl end -- Function to convert an entry in an old declensions_cat table to new. local function old_decl_cat_entry_to_new(odecl_cat_entry) if not odecl_cat_entry then return nil elseif type(odecl_cat_entry) == "function" then return function(suffix) return old_decl_cat_entry_to_new(odecl_cat_entry(suffix)) end elseif type(odecl_cat_entry) == "table" then local ndecl_cat_entry = {} for k, v in pairs(odecl_cat_entry) do ndecl_cat_entry[k] = old_decl_cat_entry_to_new(v) end return ndecl_cat_entry elseif type(odecl_cat_entry) == "boolean" then return odecl_cat_entry else assert(type(odecl_cat_entry) == "string") return old_to_new(odecl_cat_entry) end end -- Function to convert an old declensions_cat table to new. local function old_decl_cat_to_new(odeclcat) local ndeclcat = {} for k, v in pairs(odeclcat) do ndeclcat[k] = old_decl_cat_entry_to_new(v) end return ndeclcat end -- populate declensions[] from declensions_old[] for odecltype, odecl in pairs(declensions_old) do local ndecltype = old_to_new(odecltype) if not declensions[ndecltype] then declensions[ndecltype] = old_decl_to_new(odecl) end end -- populate declensions_cat[] from declensions_old_cat[] for odecltype, odeclcat in pairs(declensions_old_cat) do local ndecltype = old_to_new(odecltype) if not declensions_cat[ndecltype] then declensions_cat[ndecltype] = old_decl_cat_to_new(odeclcat) end end -- populate declensions_aliases[] from declensions_old_aliases[] for ofrom, oto in pairs(declensions_old_aliases) do local from = old_to_new(ofrom) if not declensions_aliases[from] then declensions_aliases[from] = old_to_new(oto) end end -- populate internal_notes_table[] from internal_notes_table_old[] for odecl, note in pairs(internal_notes_table_old) do local ndecl = old_to_new(odecl) if not internal_notes_table[ndecl] then -- FIXME, should we be calling old_to_new() here? internal_notes_table[ndecl] = note end end -------------------------------------------------------------------------- -- Inflection functions -- -------------------------------------------------------------------------- -- Attach the stressed stem (or plural stem, or barestem) out of ARGS -- to the unstressed suffix SUF, modifying the suffix as necessary for the -- last letter of the stem (e.g. if it is velar, sibilant or ц). CASE is -- the case form being created and is used to select the plural stem if -- needed. Returns two values, the combined form and the modified suffix. local function attach_unstressed(args, case, suf, was_stressed) if suf == nil then return nil, nil elseif rfind(suf, CFLEX) then -- if suf has circumflex accent, it forces stressed return attach_stressed(args, case, suf) end local stem, tr if rfind(case, "_pl") then stem, tr = args.pl, args.pltr end if not stem and case == "ins_sg" then stem, tr = args.ins_sg_stem, args.ins_sg_tr end if not stem then stem, tr = args.stem, args.stemtr end if nom.nonsyllabic_suffixes[suf] then -- If gen_pl, use special args.gen_pl_bare if given, else regular -- args.bare if there isn't a plural stem. If nom_sg, always use -- regular args.bare. local barearg, bareargtr if case == "gen_pl" then barearg, bareargtr = args.gen_pl_bare, args.gen_pl_baretr if not barearg and args.pl == args.stem then barearg, bareargtr = args.bare, args.baretr end else barearg, bareargtr = args.bare, args.baretr end local barestem = barearg or stem local barestem, baretr if barearg then barestem, baretr = barearg, bareargtr else barestem, baretr = stem, tr end if was_stressed and case == "gen_pl" then if not barearg then local gen_pl_stem, gen_pl_tr = com.make_ending_stressed(stem, tr) barestem, baretr = gen_pl_stem, gen_pl_tr end end if rlfind(barestem, "[йьъ]$") then suf = "" else if suf == "ъ" then -- OK elseif suf == "й" or suf == "ь" then if barearg and case == "gen_pl" then -- explicit bare or reducible, don't add -ь suf = "" elseif rfind(barestem, "[" .. com.vowel .. "]́?$") then -- not reducible, do add -ь and correct to -й if necessary suf = "й" else suf = "ь" end end end return nom.concat_russian_tr(barestem, baretr, suf, nil, "dopair"), suf end suf = com.make_unstressed(suf) local rules = nom.unstressed_rules[ulower(usub(stem, -1))] return nom.combine_stem_and_suffix(stem, tr, suf, rules, args.old) end -- Analogous to attach_unstressed() but for the unstressed stem and a -- stressed suffix. attach_stressed = function(args, case, suf) if suf == nil then return nil, nil end -- circumflex forces stress even when the accent pattern calls for no stress suf = rsub(suf, "̂", "́") if not rfind(suf, "[ё́]") then -- if suf has no "ё" or accent marks return attach_unstressed(args, case, suf, "was stressed") end local stem, tr if rfind(case, "_pl") then stem, tr = args.upl, args.upltr end if not stem then stem, tr = args.ustem, args.ustemtr end local rules = nom.stressed_rules[ulower(usub(stem, -1))] return nom.combine_stem_and_suffix(stem, tr, suf, rules, args.old) end -- Attach the appropriate stressed or unstressed stem (or plural stem as -- determined by CASE, or barestem) out of ARGS to the suffix SUF, which may -- be a list of alternative suffixes (e.g. in the inst sg of feminine nouns). -- Calls FUN (either attach_stressed() or attach_unstressed()) to do the work -- for an individual suffix. Returns two values, a list of combined forms -- and a list of the real suffixes used (which may be modified from the -- passed-in suffixes, e.g. by removing stress marks or modifying vowels in -- various ways after a stem-final velar, sibilant or ц). Each combined form -- is a two-element list {stem, tr} (or a one-element list if tr is nil). -- IRREG is true if this is an irregular form. We are handling the Nth word; -- ISLAST is true if this is the last one. local function attach_with(args, case, suf, fun, irreg, n, islast) if type(suf) == "table" then local all_combineds = {} local all_realsufs = {} for _, x in ipairs(suf) do local combineds, realsufs = attach_with(args, case, x, fun, irreg, n, islast) for _, combined in ipairs(combineds) do table.insert(all_combineds, combined) end for _, realsuf in ipairs(realsufs) do table.insert(all_realsufs, realsuf) end end return all_combineds, all_realsufs else local combined, realsuf = fun(args, case, suf) local irregsuf = irreg and {IRREGMARKER} or {""} return {combined and nom.concat_paired_russian_tr( nom.concat_paired_russian_tr(args["prefix" .. n], combined), nom.concat_paired_russian_tr(args["suffix" .. n], irregsuf)) or nil}, {realsuf and realsuf .. args["suffix" .. n][1] or nil} end end -- Generate the form(s) and suffix(es) for CASE according to the declension -- table DECL, using the attachment function FUN (one of attach_stressed() -- or attach_unstressed()). IS_SLASH is true if this is a slash declension -- (different declensions for singular and plural). We are handling the Nth -- word; ISLAST is true if this is the last one. local function gen_form(args, decl, case, stress, fun, is_slash, n, islast) local irreg = false if not args.suffixes[case] then args.suffixes[case] = {} end local decl_sufs = args.old and declensions_old or declensions decl_sufs = decl_sufs[decl] local suf = decl_sufs[case] local decl_cats = args.old and declensions_old_cat or declensions_cat local ispl = rfind(case, "_pl") if ispl and (decl_cats[decl].irregpl or args.pl and args.pl ~= args.stem or is_slash) then irreg = true end if case == "nom_pl" and decl_cats[decl].alt_nom_pl then irreg = true end if type(suf) == "function" then suf = suf(ispl and args.pl or args.stem, stress, args) end if case == "gen_pl" and args.alt_gen_pl then suf = decl_sufs.alt_gen_pl irreg = true if not suf then error("No alternate genitive plural available for this declension class") end end local combineds, realsufs = attach_with(args, case, suf, fun, irreg, n, islast) for _, realsuf in ipairs(realsufs) do args.any_non_nil[case] = true args.this_any_non_nil[case] = true insert_if_not(args.suffixes[case], realsuf) end return combineds end local attachers = { ["+"] = attach_stressed, ["-"] = attach_unstressed, } do_stress_pattern = function(stress, args, decl, number, n, islast) local f = {} for _, case in ipairs(decl_cases) do if not number or (number == "sg" and rfind(case, "_sg")) or (number == "pl" and rfind(case, "_pl")) then f[case] = gen_form(args, decl, case, stress, attachers[stress_patterns[stress][case]], not not number, n, islast) -- Turn empty form lists into nil to facilitate computation of -- animate/inanimate accusatives below if f[case] and #f[case] == 0 then f[case] = nil end -- Compute linked versions of potential lemma cases, for use -- in the ru-noun+ headword. We substitute the original lemma -- (before removing links) for forms that are the same as the -- lemma, if the original lemma has links. if f[case] and (case == "nom_sg" or case == "nom_pl") then local linked_forms = {} for _, form in ipairs(f[case]) do -- Return true if FORM is "close enough" to LEMMA that we can substitute the -- linked form of the lemma. Currently this means exactly the same except that -- we ignore acute and grave accent differences in monosyllables, and ignore -- notes that may have been appended (e.g. the triangle marking irregularity). local entry, notes = m_table_tools.separate_notes(form[1]) local lemma = args.lemma_no_links local close_enough_to_lemma = entry == lemma or (com.is_monosyllabic(entry) and com.is_monosyllabic(lemma) and com.remove_accents(entry) == com.remove_accents(lemma)) if close_enough_to_lemma and rfind(args.orig_lemma, "%[%[") then table.insert(linked_forms, {args.orig_lemma .. notes, args.lemmatr and args.lemmatr .. notes}) else table.insert(linked_forms, form) end end f[case .. "_linked"] = linked_forms end end end -- Set acc an/in variants now as appropriate. We used to do this in -- handle_forms_and_overrides(), which simplified the handling of -- nom/gen/acc overrides but caused problems for words like мазло and -- трепло that had a mixture of nil and non-nil accusative forms. local an = args.thisa if not number or number == "sg" then f.acc_sg_an = f.acc_sg_an or f.acc_sg or an == "i" and f.nom_sg or f.gen_sg f.acc_sg_in = f.acc_sg_in or f.acc_sg or an == "a" and f.gen_sg or f.nom_sg end if not number or number == "pl" then f.acc_pl_an = f.acc_pl_an or f.acc_pl or an == "i" and f.nom_pl or f.gen_pl f.acc_pl_in = f.acc_pl_in or f.acc_pl or an == "a" and f.gen_pl or f.nom_pl end for case, forms in pairs(f) do if not args.forms[case] then args.forms[case] = {} end for _, form in ipairs(forms) do insert_if_not(args.forms[case], form) end end end stress_patterns["a"] = { nom_sg="-", gen_sg="-", dat_sg="-", acc_sg="-", ins_sg="-", pre_sg="-", nom_pl="-", gen_pl="-", dat_pl="-", acc_pl="-", ins_pl="-", pre_pl="-", } stress_patterns["b"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="+", ins_sg="+", pre_sg="+", nom_pl="+", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["b'"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="+", ins_sg="-", pre_sg="+", nom_pl="+", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["c"] = { nom_sg="-", gen_sg="-", dat_sg="-", acc_sg="-", ins_sg="-", pre_sg="-", nom_pl="+", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["d"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="+", ins_sg="+", pre_sg="+", nom_pl="-", gen_pl="-", dat_pl="-", acc_pl="-", ins_pl="-", pre_pl="-", } stress_patterns["d'"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="-", ins_sg="+", pre_sg="+", nom_pl="-", gen_pl="-", dat_pl="-", acc_pl="-", ins_pl="-", pre_pl="-", } stress_patterns["e"] = { nom_sg="-", gen_sg="-", dat_sg="-", acc_sg="-", ins_sg="-", pre_sg="-", nom_pl="-", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["f"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="+", ins_sg="+", pre_sg="+", nom_pl="-", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["f'"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="-", ins_sg="+", pre_sg="+", nom_pl="-", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } stress_patterns["f''"] = { nom_sg="+", gen_sg="+", dat_sg="+", acc_sg="+", ins_sg="-", pre_sg="+", nom_pl="-", gen_pl="+", dat_pl="+", acc_pl="+", ins_pl="+", pre_pl="+", } ending_stressed_gen_pl_patterns = ut.list_to_set({"b", "b'", "c", "e", "f", "f'", "f''"}) ending_stressed_pre_sg_patterns = ut.list_to_set({"b", "b'", "d", "d'", "f", "f'", "f''"}) ending_stressed_dat_sg_patterns = ending_stressed_pre_sg_patterns ending_stressed_sg_patterns = ending_stressed_pre_sg_patterns ending_stressed_pl_patterns = ut.list_to_set({"b", "b'", "c"}) local numbers = { ["s"] = "singular", ["p"] = "plural", } local old_title_temp = [=[Pre-reform declension of <b lang="ru" class="Cyrl">{lemma}</b>]=] local title_temp = [=[Declension of <b lang="ru" class="Cyrl">{lemma}</b>]=] local partitive = nil local locative = nil local vocative = nil local internal_notes_template = nil local notes_template = nil local templates = {} -- all cases, period all_cases = { "nom_sg", "gen_sg", "dat_sg", "acc_sg", "ins_sg", "pre_sg", "nom_pl", "gen_pl", "dat_pl", "acc_pl", "ins_pl", "pre_pl", "nom_sg_linked", "nom_pl_linked", "acc_sg_an", "acc_sg_in", "acc_pl_an", "acc_pl_in", "par", "loc", "voc", } -- cases that can be declined normally decl_cases = { "nom_sg", "gen_sg", "dat_sg", "acc_sg", "ins_sg", "pre_sg", "nom_pl", "gen_pl", "dat_pl", "acc_pl", "ins_pl", "pre_pl", } -- all cases displayable displayable_cases = { "nom_sg", "gen_sg", "dat_sg", "ins_sg", "pre_sg", "nom_pl", "gen_pl", "dat_pl", "ins_pl", "pre_pl", "nom_sg_linked", "nom_pl_linked", "acc_sg_an", "acc_sg_in", "acc_pl_an", "acc_pl_in", "par", "loc", "voc", } -- all cases that can be overridden overridable_cases = { "nom_sg", "gen_sg", "dat_sg", "acc_sg", "ins_sg", "pre_sg", "nom_pl", "gen_pl", "dat_pl", "acc_pl", "ins_pl", "pre_pl", "acc_sg_an", "acc_sg_in", "acc_pl_an", "acc_pl_in", "par", "loc", "voc", } -- Convert a raw override into a canonicalized list of individual overrides. -- If input is nil, so is output. Certain junk (e.g. <br/>) is removed, -- and ~ and ~~ are substituted appropriately; ARGS and CASE are required for -- this purpose. FORMS is the list of existing case forms and is currently -- used only to retrieve the dat_sg for handling + in loc/par (this means -- that any overrides of the dat_sg have to be handled before handling -- overrides of loc/par). N is the suffix used to retrieve the override -- -- either a number for word-specific overrides, or an empty string for -- overall overrides. -- -- It will still be necessary to call m_table_tools.separate_notes() to separate -- off any trailing "notes" (asterisks, superscript numbers, etc.), and -- m_links.remove_links() to remove any links to get the raw override form. canonicalize_override = function(args, case, forms, n) local val = args[case .. n] if not val then return nil end -- clean <br /> that's in many multi-form entries and messes up linking val = rsub(val, "<br%s*/>", "") -- substitute ~ and ~~ and split by commas local stem, tr if rfind(case, "_pl") then stem, tr = args.pl, args.pltr end if not stem then stem, tr = args.stem, args.stemtr end local ustem, utr = com.make_unstressed_once(stem, tr) local vals = rsplit(val, "%s*,%s*") local retvals = {} for _, val in ipairs(vals) do local valru, valtr = nom.split_russian_tr(val) valru = rsub(valru, "~~", ustem) valru = rsub(valru, "~", com.is_stressed(val) and ustem or stem) if rfind(valru, "^%*") then valru = rsub(valru, "^%*", "") .. HYPMARKER end if valtr then tr = tr or nom.translit_no_links(stem) utr = utr or nom.translit_no_links(ustem) valtr = rsub(valtr, "~~", utr) valtr = rsub(valtr, "~", com.is_stressed(val) and utr or tr) if rfind(valtr, "^%*") then valtr = rsub(valtr, "^%*", "") .. HYPMARKER end end table.insert(retvals, {valru, valtr}) end vals = retvals -- handle + in loc/par meaning "the expected form"; NOTE: This requires -- that dat_sg has already been processed! if case == "loc" or case == "par" then local new_vals = {} for _, rutr in ipairs(vals) do -- don't just handle + by itself in case the arg has в or на -- or whatever attached to it if rfind(rutr[1], "^%+") or rfind(rutr[1], "[%s%[|]%+") then for _, dat in ipairs(forms["dat_sg"]) do local ru, tr = rutr[1], rutr[2] local datru, dattr = dat[1], dat[2] local valru, valtr -- separate off any footnote symbols (which may have been -- introduced by a *tail or *tailall argument, which we need -- to process before this stage so overrides don't get -- automatically marked with footnote symbols); if par, -- try to preserve them, but with loc this can cause -- problems in case there are multiple dative forms -- (stress variants) and the last one is marked with a -- footnote symbol (occurs in грудь) local datru_entry, datru_notes = m_table_tools.separate_notes(datru) local dattr_entry, dattr_notes if dattr then dattr_entry, dattr_notes = m_table_tools.separate_notes(dattr) end if case == "par" then valru, valtr = datru_entry, dattr_entry else valru, valtr = com.make_ending_stressed(datru_entry, dattr_entry) datru_notes = "" dattr_notes = "" end -- wrap the word in brackets so it's linked; but not if it -- appears to already be linked ru = rsub(ru, "^%+", "[[" .. valru .. "]]") ru = rsub(ru, "([%[|])%+", "%1" .. valru) ru = rsub(ru, "(%s)%+", "%1[[" .. valru .. "]]") ru = ru .. datru_notes -- do the translit; but it shouldn't have brackets in it if tr or valtr then tr = tr or nom.translit_no_links(rutr[1]) valtr = valtr or nom.translit_no_links(valru) tr = rsub(tr, "^%+", valtr) tr = rsub(tr, "(%s)%+", "%1" .. valtr) tr = tr .. dattr_notes end table.insert(new_vals, {ru, tr}) end else table.insert(new_vals, rutr) end end vals = new_vals end -- auto-accent/check for necessary accents local newvals = {} for _, v in ipairs(vals) do local ru, tr = v[1], v[2] if not args.allow_unaccented then if tr and com.is_unstressed(ru) ~= com.is_unstressed(tr) then error("Override " .. ru .. " and translit " .. tr .. " must have same accent pattern") end -- it's safe to accent monosyllabic stems if com.is_monosyllabic(ru) then ru, tr = com.make_ending_stressed(ru, tr) elseif com.needs_accents(ru) then error("Override " .. ru .. " for case " .. case .. n .. " requires an accent") end end table.insert(newvals, {ru, tr}) end vals = newvals return vals end local function process_overrides(args, f, n) local function process_override(case) if args[case .. n] then local overrides = canonicalize_override(args, case, f, n) if not f[case] then f[case] = {} end local new_overrides = {} for _, form in ipairs(overrides) do -- Don't consider overrides of loc/par/voc irregular since -- they're only specified through overrides; FIXME: Theoretically -- we could consider loc/par irregular if they don't follow the -- expected forms; but we'd have to figure out how to eliminate -- the preposition that may be specified if case ~= "loc" and case ~= "par" and case ~= "voc" and not args.manual and not contains_rutr_pair(f[case], form) then local formru, formnotes = m_table_tools.separate_notes(form[1]) if formru ~= "-" then -- don't mark an override of - as irregular, even if -- it has an attached footnote symbol form = nom.concat_paired_russian_tr(form, {IRREGMARKER}) end end table.insert(new_overrides, form) end f[case] = new_overrides args.any_overridden[case] = true end end -- do dative singular first because it will be used by loc/par process_override("dat_sg") -- now do the rest for _, case in ipairs(overridable_cases) do if case ~= "dat_sg" then process_override(case) end end -- if the nominative is overridden, use it to set the linked version unless -- that is also specifically overridden if args["nom_sg" .. n] and not args["nom_sg_linked" .. n] then f.nom_sg_linked = f.nom_sg end if args["nom_pl" .. n] and not args["nom_pl_linked" .. n] then f.nom_pl_linked = f.nom_pl end -- convert empty lists to nil to facilitate computation of accusative -- case variants below. for _, case in ipairs(all_cases) do if f[case] then if type(f[case]) ~= "table" then error("Logic error, args[case] should be nil or table") end if #f[case] == 0 then f[case] = nil end end end end local function process_tail_args(args, f, n) local function case_overridden(case) if args[case .. n] then return true end if case == "acc_sg_an" or case == "acc_sg_in" then return args["acc_sg" .. n] end if case == "acc_pl_an" or case == "acc_pl_in" then return args["acc_pl" .. n] end return false end local function append_note(case, value, singlecase, notetype) value = nom.split_russian_tr(value, "dopair") local function append1(case) -- Don't add tail symbol if case overridden if f[case] and not case_overridden(case) then -- Clone before side-effecting because some cases may be shared -- with others (e.g. acc_* with each other and with nom/gen). f[case] = mw.clone(f[case]) if notetype == "all" then for i=1,#f[case] do f[case][i] = nom.concat_paired_russian_tr(f[case][i], value) end else local lastarg = #f[case] if lastarg > (notetype == ">1" and 1 or 0) then f[case][lastarg] = nom.concat_paired_russian_tr(f[case][lastarg], value) end end end end append1(case) -- A tail symbol specifically for acc_sg or acc_pl should also affect -- the animate and inanimate variants, because those are the ones that -- are displayed. But don't do this if we requested multiple cases at -- once (e.g. pltail=*), because the list of cases frobbed will -- already include all accusative variants and we don't want to add -- the symbol more than once. if singlecase == "single" then if case == "acc_sg" then append1("acc_sg_in") append1("acc_sg_an") elseif case == "acc_pl" then append1("acc_pl_in") append1("acc_pl_an") end end end local function handle_tail_hyp(suf) local arg, val for _, case in ipairs(all_cases) do arg = args[case .. "_" .. suf .. n] if arg then append_note(case, suf == "hyp" and HYPMARKER or arg, "single", "last") end arg = args[case .. "_" .. suf .. "all" .. n] if arg then append_note(case, suf == "hyp" and HYPMARKER or arg, "single", "last") end if not rfind(case, "_pl") then arg = args["sg" .. suf .. "all" .. n] if arg then append_note(case, suf == "hyp" and HYPMARKER or arg, "multi", "all") end arg = args["sg" .. suf .. n] if arg then append_note(case, suf == "hyp" and HYPMARKER or arg, "multi", ">1") end else arg = args["pl" .. suf .. "all" .. n] if arg then append_note(case, suf == "hyp" and HYPMARKER or arg, "multi", "all") end arg = args["pl" .. suf .. n] if arg then append_note(case, suf == "hyp" and HYPMARKER or arg, "multi", ">1") end end if not rfind(case, "nom_") and not rfind(case, "acc_") then arg = args["obl" .. suf .. "all" .. n] if arg then append_note(case, suf == "hyp" and HYPMARKER or arg, "multi", "all") end arg = args["obl" .. suf .. n] if arg then append_note(case, suf == "hyp" and HYPMARKER or arg, "multi", ">1") end end end end handle_tail_hyp("tail") handle_tail_hyp("hyp") end handle_forms_and_overrides = function(args, n, islast) local f = args.forms process_overrides(args, f, n) local an = args.thisa -- Maybe set the value of the animate/inanimate accusative variants based -- on nom/acc/gen overrides. Don't do this if there was a specific override -- of this form. Otherwise, always propagate an accusative singular -- override, and propagate the nom/gen sg if there wasn't a specific -- accusative suffix anywhere (occurs in the fem sg and sometimes the neut -- sg). This logic duplicates logic in handle_overall_forms_and_overrides(); -- see long comment there. We need to duplicate the whole logic here to -- handle words like мать-одиночка, which has an override of acc_sg1. if not args["acc_sg_an" .. n] then if args["acc_sg" .. n] then f.acc_sg_an = f.acc_sg elseif not args.this_any_non_nil.acc_sg then f.acc_sg_an = f.acc_sg or an == "i" and f.nom_sg or f.gen_sg or f.acc_sg_an end end if not args["acc_sg_in" .. n] then if args["acc_sg" .. n] then f.acc_sg_in = f.acc_sg elseif not args.this_any_non_nil.acc_sg then f.acc_sg_in = f.acc_sg or an == "a" and f.gen_sg or f.nom_sg or f.acc_sg_in end end if not args["acc_pl_an" .. n] then if args["acc_pl" .. n] then f.acc_pl_an = f.acc_pl elseif not args.this_any_non_nil.acc_pl then f.acc_pl_an = f.acc_pl or an== "i" and f.nom_pl or f.gen_pl or f.acc_pl_an end end if not args["acc_pl_in" .. n] then if args["acc_pl" .. n] then f.acc_pl_in = f.acc_pl elseif not args.this_any_non_nil.acc_pl then f.acc_pl_in = f.acc_pl or an == "a" and f.gen_pl or f.nom_pl or f.acc_pl_in end end f.loc = f.loc or f.pre_sg f.par = f.par or f.gen_sg f.voc = f.voc or f.nom_sg -- Set these in case we have plural only, in which case the -- singular will also get set to these same values in case we are -- a plural-only word in a singular-only expression. NOTE: It's actually -- unnecessary to do "f.loc_pl = f.loc_pl or f.pre_pl" as f.loc_pl should -- never previously be set. f.loc_pl = f.loc_pl or f.pre_pl f.par_pl = f.par_pl or f.gen_pl f.voc_pl = f.voc_pl or f.nom_pl process_tail_args(args, f, n) local nu = args.thisn -- If we have a singular-only, set the plural forms to the singular forms, -- and vice-versa. This is important so that things work in multi-word -- expressions that combine different number restrictions (e.g. -- singular-only with singular/plural or singular-only with plural-only, -- compare "St. Vincent and the Grenadines" [Сент-Винсент и Гренадины]). if nu == "s" then f.nom_pl_linked = f.nom_sg_linked f.nom_pl = f.nom_sg f.gen_pl = f.gen_sg f.dat_pl = f.dat_sg f.acc_pl = f.acc_sg f.acc_pl_an = f.acc_sg_an f.acc_pl_in = f.acc_sg_in f.ins_pl = f.ins_sg f.pre_pl = f.pre_sg f.nom_pl = f.nom_sg -- Unnecessary because only used below to initialize singular -- loc/par/voc with pluralia tantum. -- f.loc_pl = f.loc -- f.par_pl = f.par -- f.voc_pl = f.voc elseif nu == "p" then f.nom_sg_linked = f.nom_pl_linked f.nom_sg = f.nom_pl f.gen_sg = f.gen_pl f.dat_sg = f.dat_pl f.acc_sg = f.acc_pl f.acc_sg_an = f.acc_pl_an f.acc_sg_in = f.acc_pl_in f.ins_sg = f.ins_pl f.pre_sg = f.pre_pl f.nom_sg = f.nom_pl f.loc = f.loc_pl f.par = f.par_pl f.voc = f.voc_pl end end handle_overall_forms_and_overrides = function(args) local f = {} for _, case in ipairs(displayable_cases) do f[case] = concat_word_forms(args.per_word_info, case) end process_overrides(args, f, "") -- if IRREGMARKER is anywhere in text, remove all instances and put -- at the end before any notes. local function clean_irreg_marker(case, text) if rfind(text, IRREGMARKER) then text = rsub(text, IRREGMARKER, "") local entry, notes = m_table_tools.separate_notes(text) if case_will_be_displayed(args, f, case) then insert_if_not(args.internal_notes, IRREGMARKER .. " Irregular.") args.any_irreg = true args.any_irreg_case[case] = true end return entry .. IRREGMARKER .. notes else return text end end -- set final forms and clean up IRREGMARKER. for _, case in ipairs(all_cases) do if f[case] then local cleaned_forms = {} for _, form in ipairs(f[case]) do local ru, tr = form[1], form[2] ru = clean_irreg_marker(case, ru) if tr then tr = clean_irreg_marker(case, tr) end table.insert(cleaned_forms, {ru, tr}) end f[case] = cleaned_forms end end -- Maybe set the value of the animate/inanimate accusative variants based -- on nom/acc/gen overrides. Don't do this if there was a specific override -- of this form. Otherwise, always propagate an accusative singular -- override, and propagate the nom/gen sg if there wasn't a specific -- accusative suffix anywhere (occurs in the fem sg and sometimes the neut -- sg). We need to do this somewhat complicated procedure to get overrides -- to work correctly, e.g. acc_sg overrides of feminine and neuter nouns -- (such as мать and полслова) and gen_sg/gen_pl overrides of masculine -- animate nouns. We also ran into an issue with words like мазло that are -- neuter-form but can be both masculine animate (in which case the -- acc sg inherits from the genitive) and neuter animate (in which case the -- acc sg has the fixed ending -о, same as nominative, despite the animacy). -- Remember also that the animate/inanimate accusative variants are the ones -- displayed, not the plain acc_sg/acc_pl ones. if not args.any_overridden.acc_sg_an then if args.acc_sg then f.acc_sg_an = f.acc_sg elseif not args.any_non_nil.acc_sg then f.acc_sg_an = f.acc_sg or args.a == "i" and f.nom_sg or f.gen_sg or f.acc_sg_an end end if not args.any_overridden.acc_sg_in then if args.acc_sg then f.acc_sg_in = f.acc_sg elseif not args.any_non_nil.acc_sg then f.acc_sg_in = f.acc_sg or args.a == "a" and f.gen_sg or f.nom_sg or f.acc_sg_in end end if not args.any_overridden.acc_pl_an then if args.acc_pl then f.acc_pl_an = f.acc_pl elseif not args.any_non_nil.acc_pl then f.acc_pl_an = f.acc_pl or args.a == "i" and f.nom_pl or f.gen_pl or f.acc_pl_an end end if not args.any_overridden.acc_pl_in then if args.acc_pl then f.acc_pl_in = f.acc_pl elseif not args.any_non_nil.acc_pl then f.acc_pl_in = f.acc_pl or args.a == "a" and f.gen_pl or f.nom_pl or f.acc_pl_in end end -- Try to set the values of acc_sg and acc_pl. The only time we can't is -- when the noun is bianimate and the anim/inan values are different. -- This is used primarily for generate_forms() and generate_multi_forms(), -- since we don't actually display these forms. if args.a == "a" then f.acc_sg = f.acc_sg or f.acc_sg_an f.acc_pl = f.acc_pl or f.acc_pl_an elseif args.a == "i" then f.acc_sg = f.acc_sg or f.acc_sg_in f.acc_pl = f.acc_pl or f.acc_pl_in else -- bianimate f.acc_sg = f.acc_sg or ut.equals(f.acc_sg_in, f.acc_sg_an) and f.acc_sg_in or nil f.acc_pl = f.acc_pl or ut.equals(f.acc_pl_in, f.acc_pl_an) and f.acc_pl_in or nil end process_tail_args(args, f, "") -- set final args[case]. for _, case in ipairs(all_cases) do args[case] = f[case] end end -- Generate a string to substitute into a particular form in a Wiki-markup -- table. FORMS is the list of forms, generated by concat_word_forms(). -- OLD is true if we're dealing with a pre-reform declension (in this case, -- the page we link to has е in place of ё, for reasons I'm not completely -- sure of). LEMMA is true is we're formatting the entry for use in displaying -- the lemma in the declension table title. In this case, we don't include -- the translit, and remove monosyllabic accents from the Cyrillic (but not -- in multiword expressions). local function show_form(forms, old, lemma) local russianvals = {} local latinvals = {} local lemmavals = {} -- Accumulate separately the Russian and transliteration into -- RUSSIANVALS and LATINVALS, then concatenate each down below. -- However, if LEMMA, we put each transliteration directly -- after the corresponding Russian, in parens, and put the results -- in LEMMAVALS, which get concatenated below. (This is used in the -- title of the declension table.) (Actually, currently we don't -- include the translit in the declension table title.) local is_missing = false for _, form in ipairs(forms) do local ru, tr = form[1], form[2] local ruentry, runotes = m_table_tools.separate_notes(ru) local trentry, trnotes if tr then trentry, trnotes = m_table_tools.separate_notes(tr) trnotes = rsub(trnotes, HYPMARKER, "") end if lemma and com.is_monosyllabic(ruentry) then ruentry = com.remove_accents(ruentry) if trentry then trentry = com.remove_accents(trentry) end end local ishyp = rfind(runotes, HYPMARKER) if ishyp then runotes = rsub(runotes, HYPMARKER, "") end runotes = m_table_tools.superscript_notes(runotes) if trnotes then trnotes = m_table_tools.superscript_notes(trnotes) end local ruspan, trspan if ruentry == "-" and #forms == 1 then ruspan = "&mdash;" is_missing = true elseif ishyp then ruspan = m_links.full_link({lang = lang, term = nil, alt = ruentry, tr = "-"}, "hypothetical") elseif old then ruspan = m_links.full_link({lang = lang, term = com.remove_jo(ruentry), alt = not ruentry:find("[[", 1, true) and ruentry, tr = "-"}) else ruspan = m_links.full_link({lang = lang, term = ruentry, tr = "-"}) end ruspan = ruspan .. runotes if not trentry then trentry = nom.translit_no_links(ruentry) end if not trnotes then trnotes = nom.translit_no_links(runotes) end trspan = m_links.remove_links(trentry) if ishyp then trspan = scriptutils.tag_text(trspan, lang, Latn, "hypothetical") end trspan = scriptutils.tag_translit(trspan .. trnotes, lang, "default", ' style="color: #888"') if lemma then -- insert_if_not(lemmavals, ruspan .. " (" .. trspan .. ")") insert_if_not(lemmavals, ruspan) else insert_if_not(russianvals, ruspan) insert_if_not(latinvals, trspan) end end if lemma then return table.concat(lemmavals, ", ") else local russian_span = table.concat(russianvals, ", ") if is_missing then return russian_span else local latin_span = table.concat(latinvals, ", ") return russian_span .. "<br />" .. latin_span end end end -- Subfunction of concat_word_forms(), used to implement recursively -- generating all combinations of elements from WORD_FORMS (a list, one -- element per word, of a list of the forms for a word, each of which is a -- two-element list of {RUSSIAN, TR}) and TRAILING_FORMS (a list of forms, the -- accumulated suffixes for trailing words so far in the recursion process, -- again where each form is a two-element list {RUSSIAN, TR}). Each time we -- recur we take the last FORMS item off of WORD_FORMS and to each form in -- FORMS we add all elements in TRAILING_FORMS, passing the newly generated -- list of items down the next recursion level with the shorter WORD_FORMS. -- We end up returning a list of concatenated forms, where each list item -- is a two-element list {RUSSIAN, TR}. local function concat_word_forms_1(word_forms, trailing_forms) if #word_forms == 0 then local retforms = {} for _, form in ipairs(trailing_forms) do local ru, tr = form[1], form[2] -- Remove <insa> and <insb> markers; they've served their purpose. ru = rsub(ru, "<ins[ab]>", "") tr = tr and rsub(tr, "<ins[ab]>", "") table.insert(retforms, {ru, tr}) end return retforms else local last_form_info = table.remove(word_forms) local last_forms, joiner = last_form_info[1], last_form_info[2] local new_trailing_forms = {} for _, form in ipairs(last_forms) do for _, trailing_form in ipairs(trailing_forms) do -- If form to prepend is empty, don't add the joiner; this -- is principally used in overall overrides, where we stuff -- the entire override into the last word local full_form = form[1] == "" and trailing_form or nom.concat_paired_russian_tr(form, nom.concat_paired_russian_tr(joiner, trailing_form), "movenotes") if rfind(full_form[1], "<insa>") and rfind(full_form[1], "<insb>") then -- REJECT! So we don't get mixtures of the two feminine -- instrumental singular endings. else table.insert(new_trailing_forms, full_form) end end end return concat_word_forms_1(word_forms, new_trailing_forms) end end -- Generate a list of overall forms by concatenating the per-word forms. -- PER_WORD_INFO comes from args.per_word_info and is a list of -- WORD_INFO items, one per word, each of which a two element list of -- WORD_FORMS (a table listing the forms for each case) and JOINER (a string). -- We loop over all possible combinations of elements from each word's list -- of forms for the given case; this requires recursion. concat_word_forms = function(per_word_info, case) local word_forms = {} -- Gather the appropriate word forms. We have to recreate this anew -- because it will be destructively modified by concat_word_forms_1(). for _, word_info in ipairs(per_word_info) do table.insert(word_forms, {word_info[1][case], word_info[2]}) end -- We need to start the recursion with the second parameter containing -- one blank element rather than no elements, otherwise no elements -- will be propagated to the next recursion level. return concat_word_forms_1(word_forms, {{""}}) end -- Make the table make_table = function(args) local data = {} data.after_title = " " .. args.heading data.number = args.nonumber and "" or numbers[args.n] data.lemma = show_form(args[args.n == "p" and "nom_pl_linked" or "nom_sg_linked"], args.old, "lemma") data.title = args.title or strutils.format(args.old and old_title_temp or title_temp, data) for _, case in ipairs(displayable_cases) do data[case] = show_form(args[case], args.old, false) end local temp = nil if args.n == "s" then data.nom_x = data.nom_sg data.gen_x = data.gen_sg data.dat_x = data.dat_sg data.acc_x_an = data.acc_sg_an data.acc_x_in = data.acc_sg_in data.ins_x = data.ins_sg data.pre_x = data.pre_sg if data.acc_sg_an == data.acc_sg_in then temp = "half" else temp = "half_a" end elseif args.n == "p" then data.nom_x = data.nom_pl data.gen_x = data.gen_pl data.dat_x = data.dat_pl data.acc_x_an = data.acc_pl_an data.acc_x_in = data.acc_pl_in data.ins_x = data.ins_pl data.pre_x = data.pre_pl data.par = nil data.loc = nil data.voc = nil if data.acc_pl_an == data.acc_pl_in then temp = "half" else temp = "half_a" end else if data.acc_pl_an == data.acc_pl_in then temp = "full" elseif data.acc_sg_an == data.acc_sg_in then temp = "full_af" else temp = "full_a" end end data.par_clause = args.any_overridden.par and strutils.format(partitive, data) or "" data.loc_clause = args.any_overridden.loc and strutils.format(locative, data) or "" data.voc_clause = args.any_overridden.voc and strutils.format(vocative, data) or "" data.notes = args.notes data.notes_clause = data.notes and strutils.format(notes_template, data) or "" data.internal_notes = table.concat(args.internal_notes, "<br />") data.internal_notes_clause = #data.internal_notes > 0 and strutils.format(internal_notes_template, data) or "" return strutils.format(templates[temp], data) end partitive = [===[ ! style="background:#eff7ff" | partitive | {par} |-]===] locative = [===[ ! style="background:#eff7ff" | locative | {loc} |-]===] vocative = [===[ ! style="background:#eff7ff" | vocative | {voc} |-]===] notes_template = [===[ <div style="width:100%;text-align:left;background:#d9ebff"> <div style="display:inline-block;text-align:left;padding-left:1em;padding-right:1em"> {notes} </div></div> ]===] internal_notes_template = rsub(notes_template, "notes", "internal_notes") local function template_prelude(min_width) min_width = min_width or "70" return rsub([===[ <div> <div class="NavFrame" style="display:inline-block; min-width:MINWIDTHem"> <div class="NavHead" style="background:#eff7ff;">{title}<span style="font-weight:normal;">{after_title}</span>&nbsp;</div> <div class="NavContent"> {\op}| style="background:#F9F9F9; text-align:center; min-width:MINWIDTHem; width:100%;" class="inflection-table" |- ]===], "MINWIDTH", min_width) end local function template_postlude() return [===[|-{par_clause}{loc_clause}{voc_clause} |{\cl}{internal_notes_clause}{notes_clause}</div></div></div>]===] end templates["full"] = template_prelude("45") .. [===[ ! style="width:10em;background:#d9ebff" | ! style="background:#d9ebff" | singular ! style="background:#d9ebff" | plural |- ! style="background:#eff7ff" | nominative | {nom_sg} | {nom_pl} |- ! style="background:#eff7ff" | genitive | {gen_sg} | {gen_pl} |- ! style="background:#eff7ff" | dative | {dat_sg} | {dat_pl} |- ! style="background:#eff7ff" | accusative | {acc_sg_an} | {acc_pl_an} |- ! style="background:#eff7ff" | instrumental | {ins_sg} | {ins_pl} |- ! style="background:#eff7ff" | prepositional | {pre_sg} | {pre_pl} ]===] .. template_postlude() templates["full_a"] = template_prelude("50") .. [===[ ! style="width:15em;background:#d9ebff" | ! style="background:#d9ebff" | singular ! style="background:#d9ebff" | plural |- ! style="background:#eff7ff" | nominative | {nom_sg} | {nom_pl} |- ! style="background:#eff7ff" | genitive | {gen_sg} | {gen_pl} |- ! style="background:#eff7ff" | dative | {dat_sg} | {dat_pl} |- ! style="background:#eff7ff" rowspan="2" | accusative <span style="padding-left:1em;display:inline-block;vertical-align:middle">animate<br/><br/>inanimate</span> | {acc_sg_an} | {acc_pl_an} |- | {acc_sg_in} | {acc_pl_in} |- ! style="background:#eff7ff" | instrumental | {ins_sg} | {ins_pl} |- ! style="background:#eff7ff" | prepositional | {pre_sg} | {pre_pl} ]===] .. template_postlude() templates["full_af"] = template_prelude("50") .. [===[ ! style="width:15em;background:#d9ebff" | ! style="background:#d9ebff" | singular ! style="background:#d9ebff" | plural |- ! style="background:#eff7ff" | nominative | {nom_sg} | {nom_pl} |- ! style="background:#eff7ff" | genitive | {gen_sg} | {gen_pl} |- ! style="background:#eff7ff" | dative | {dat_sg} | {dat_pl} |- ! style="background:#eff7ff" rowspan="2" | accusative <span style="padding-left:1em;display:inline-block;vertical-align:middle">animate<br/><br/>inanimate</span> | rowspan="2" | {acc_sg_an} | {acc_pl_an} |- | {acc_pl_in} |- ! style="background:#eff7ff" | instrumental | {ins_sg} | {ins_pl} |- ! style="background:#eff7ff" | prepositional | {pre_sg} | {pre_pl} ]===] .. template_postlude() templates["half"] = template_prelude("30") .. [===[ ! style="width:10em;background:#d9ebff" | ! style="background:#d9ebff" | {number} |- ! style="background:#eff7ff" | nominative | {nom_x} |- ! style="background:#eff7ff" | genitive | {gen_x} |- ! style="background:#eff7ff" | dative | {dat_x} |- ! style="background:#eff7ff" | accusative | {acc_x_an} |- ! style="background:#eff7ff" | instrumental | {ins_x} |- ! style="background:#eff7ff" | prepositional | {pre_x} ]===] .. template_postlude() templates["half_a"] = template_prelude("35") .. [===[ ! style="width:15em;background:#d9ebff" | ! style="background:#d9ebff" | {number} |- ! style="background:#eff7ff" | nominative | {nom_x} |- ! style="background:#eff7ff" | genitive | {gen_x} |- ! style="background:#eff7ff" | dative | {dat_x} |- ! style="background:#eff7ff" rowspan="2" | accusative <span style="padding-left:1em;display:inline-block;vertical-align:middle">animate<br/><br/>inanimate</span> | {acc_x_an} |- | {acc_x_in} |- ! style="background:#eff7ff" | instrumental | {ins_x} |- ! style="background:#eff7ff" | prepositional | {pre_x} ]===] .. template_postlude() return export -- For Vim, so we get 4-space tabs -- vim: set ts=4 sw=4 noet: tkqbtd01uc35r4199pm6kcugpdo71aw Şablon:ru-ad-cansız-3-D-a 10 1589240 5669927 2026-06-23T08:03:52Z MustafaCavlak 59368 Yeni sayfa : {{ru-ad-tablo |{{{1}}} |{{#invoke:string|sub|s={{{1}}}|j=-2}}и |{{#invoke:string|sub|s={{{1}}}|j=-2}}и |{{#invoke:string|sub|s={{{1}}}|j=-2}}ей |{{#invoke:string|sub|s={{{1}}}|j=-2}}и |{{#invoke:string|sub|s={{{1}}}|j=-2}}ям |{{{1}}} |{{#invoke:string|sub|s={{{1}}}|j=-2}}и |{{#invoke:string|sub|s={{{1}}}|j=-2}}ью |{{#invoke:string|sub|s={{{1}}}|j=-2}}ями |{{#invoke:string|sub|s={{{1}}}|j=-2}}и |{{#invoke:string|sub|s={{{1}}}|j=-2}}ях }} 5669927 wikitext text/x-wiki {{ru-ad-tablo |{{{1}}} |{{#invoke:string|sub|s={{{1}}}|j=-2}}и |{{#invoke:string|sub|s={{{1}}}|j=-2}}и |{{#invoke:string|sub|s={{{1}}}|j=-2}}ей |{{#invoke:string|sub|s={{{1}}}|j=-2}}и |{{#invoke:string|sub|s={{{1}}}|j=-2}}ям |{{{1}}} |{{#invoke:string|sub|s={{{1}}}|j=-2}}и |{{#invoke:string|sub|s={{{1}}}|j=-2}}ью |{{#invoke:string|sub|s={{{1}}}|j=-2}}ями |{{#invoke:string|sub|s={{{1}}}|j=-2}}и |{{#invoke:string|sub|s={{{1}}}|j=-2}}ях }} k108uqfnzlzv8jwr3sld6msqjkotj0r 5669933 5669927 2026-06-23T08:47:17Z MustafaCavlak 59368 MustafaCavlak, [[Şablon:ru-ad-3da]] sayfasını [[Şablon:ru-ad-cansız-3-D-a]] sayfasına taşıdı 5669927 wikitext text/x-wiki {{ru-ad-tablo |{{{1}}} |{{#invoke:string|sub|s={{{1}}}|j=-2}}и |{{#invoke:string|sub|s={{{1}}}|j=-2}}и |{{#invoke:string|sub|s={{{1}}}|j=-2}}ей |{{#invoke:string|sub|s={{{1}}}|j=-2}}и |{{#invoke:string|sub|s={{{1}}}|j=-2}}ям |{{{1}}} |{{#invoke:string|sub|s={{{1}}}|j=-2}}и |{{#invoke:string|sub|s={{{1}}}|j=-2}}ью |{{#invoke:string|sub|s={{{1}}}|j=-2}}ями |{{#invoke:string|sub|s={{{1}}}|j=-2}}и |{{#invoke:string|sub|s={{{1}}}|j=-2}}ях }} k108uqfnzlzv8jwr3sld6msqjkotj0r Şablon:ru-ad-cansız-s-D-a 10 1589241 5669929 2026-06-23T08:15:25Z MustafaCavlak 59368 Yeni sayfa : {{ru-ad-tablo |{{{1}}} |{{#invoke:string|sub|s={{{1}}}|j=-2}}ы |{{#invoke:string|sub|s={{{1}}}|j=-2}}ы |{{#invoke:string|sub|s={{{1}}}|j=-2}} |{{#invoke:string|sub|s={{{1}}}|j=-2}}е |{{{1}}}м |{{#invoke:string|sub|s={{{1}}}|j=-2}}у |{{#invoke:string|sub|s={{{1}}}|j=-2}}ы |[[{{#invoke:string|sub|s={{{1}}}|j=-2}}ой]]<br>[[{{#invoke:string|sub|s={{{1}}}|j=-2}}ою]] |{{{1}}}ми |{{#invoke:string|sub|s={{{1}}}|j=-2}}е |{{{1}}}х }} 5669929 wikitext text/x-wiki {{ru-ad-tablo |{{{1}}} |{{#invoke:string|sub|s={{{1}}}|j=-2}}ы |{{#invoke:string|sub|s={{{1}}}|j=-2}}ы |{{#invoke:string|sub|s={{{1}}}|j=-2}} |{{#invoke:string|sub|s={{{1}}}|j=-2}}е |{{{1}}}м |{{#invoke:string|sub|s={{{1}}}|j=-2}}у |{{#invoke:string|sub|s={{{1}}}|j=-2}}ы |[[{{#invoke:string|sub|s={{{1}}}|j=-2}}ой]]<br>[[{{#invoke:string|sub|s={{{1}}}|j=-2}}ою]] |{{{1}}}ми |{{#invoke:string|sub|s={{{1}}}|j=-2}}е |{{{1}}}х }} 5hl6kl458uj0bwehz2citfmqso1jlq4 5669935 5669929 2026-06-23T08:48:58Z MustafaCavlak 59368 MustafaCavlak, [[Şablon:ru-ad-sda]] sayfasını [[Şablon:ru-ad-cansız-s-D-a]] sayfasına taşıdı 5669929 wikitext text/x-wiki {{ru-ad-tablo |{{{1}}} |{{#invoke:string|sub|s={{{1}}}|j=-2}}ы |{{#invoke:string|sub|s={{{1}}}|j=-2}}ы |{{#invoke:string|sub|s={{{1}}}|j=-2}} |{{#invoke:string|sub|s={{{1}}}|j=-2}}е |{{{1}}}м |{{#invoke:string|sub|s={{{1}}}|j=-2}}у |{{#invoke:string|sub|s={{{1}}}|j=-2}}ы |[[{{#invoke:string|sub|s={{{1}}}|j=-2}}ой]]<br>[[{{#invoke:string|sub|s={{{1}}}|j=-2}}ою]] |{{{1}}}ми |{{#invoke:string|sub|s={{{1}}}|j=-2}}е |{{{1}}}х }} 5hl6kl458uj0bwehz2citfmqso1jlq4 автомашина 0 1589242 5669930 2026-06-23T08:18:10Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Ad=== {{başlık başı|ru|ad|baş=автомаши́на|c=d}} # [[otomobil]] ====Çekimleme==== {{ru-ad-sda|автомаши́на}} 5669930 wikitext text/x-wiki ==Rusça== ===Ad=== {{başlık başı|ru|ad|baş=автомаши́на|c=d}} # [[otomobil]] ====Çekimleme==== {{ru-ad-sda|автомаши́на}} paqhpa88my8g7i8xwc6swlgrlei3rm2 Şablon:ru-ad-cansız-s-E-a 10 1589243 5669931 2026-06-23T08:24:08Z MustafaCavlak 59368 Yeni sayfa : {{ru-ad-tablo |{{{1}}} |{{{1}}}ы |{{{1}}}а |{{{1}}}ов |{{{1}}}у |{{{1}}}ам |{{{1}}} |{{{1}}}ы |{{{1}}}ом |{{{1}}}ами |{{{1}}}е |{{{1}}}ах }} 5669931 wikitext text/x-wiki {{ru-ad-tablo |{{{1}}} |{{{1}}}ы |{{{1}}}а |{{{1}}}ов |{{{1}}}у |{{{1}}}ам |{{{1}}} |{{{1}}}ы |{{{1}}}ом |{{{1}}}ами |{{{1}}}е |{{{1}}}ах }} shpl2unucsk3t4w7ucthgtf062hch9q 5669937 5669931 2026-06-23T08:49:33Z MustafaCavlak 59368 MustafaCavlak, [[Şablon:ru-ad-sea]] sayfasını [[Şablon:ru-ad-cansız-s-E-a]] sayfasına taşıdı 5669931 wikitext text/x-wiki {{ru-ad-tablo |{{{1}}} |{{{1}}}ы |{{{1}}}а |{{{1}}}ов |{{{1}}}у |{{{1}}}ам |{{{1}}} |{{{1}}}ы |{{{1}}}ом |{{{1}}}ами |{{{1}}}е |{{{1}}}ах }} shpl2unucsk3t4w7ucthgtf062hch9q аванс 0 1589244 5669932 2026-06-23T08:28:52Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Köken=== {{nakledilen|ru|fr|avance}}. ===Ad=== {{başlık başı|ru|ad|baş=ава́нс}} # [[avans]] ====Çekimleme==== {{ru-ad-sea|ава́нс}} 5669932 wikitext text/x-wiki ==Rusça== ===Köken=== {{nakledilen|ru|fr|avance}}. ===Ad=== {{başlık başı|ru|ad|baş=ава́нс}} # [[avans]] ====Çekimleme==== {{ru-ad-sea|ава́нс}} cexfnw8wkfh92n2tgdba8ls26bsmltz Şablon:ru-ad-3da 10 1589245 5669934 2026-06-23T08:47:17Z MustafaCavlak 59368 MustafaCavlak, [[Şablon:ru-ad-3da]] sayfasını [[Şablon:ru-ad-cansız-3-D-a]] sayfasına taşıdı 5669934 wikitext text/x-wiki #YÖNLENDİRME [[Şablon:ru-ad-cansız-3-D-a]] bw9qd33ykz125dyc1ftxrpcff5fa3dt Şablon:ru-ad-sda 10 1589246 5669936 2026-06-23T08:48:58Z MustafaCavlak 59368 MustafaCavlak, [[Şablon:ru-ad-sda]] sayfasını [[Şablon:ru-ad-cansız-s-D-a]] sayfasına taşıdı 5669936 wikitext text/x-wiki #YÖNLENDİRME [[Şablon:ru-ad-cansız-s-D-a]] 130p0ij6rt1duuzee3z60fmyo4y5g09 Şablon:ru-ad-sea 10 1589247 5669938 2026-06-23T08:49:33Z MustafaCavlak 59368 MustafaCavlak, [[Şablon:ru-ad-sea]] sayfasını [[Şablon:ru-ad-cansız-s-E-a]] sayfasına taşıdı 5669938 wikitext text/x-wiki #YÖNLENDİRME [[Şablon:ru-ad-cansız-s-E-a]] 5zqcpj1znxa62iwrv4120jmlx8q83x1 шпинат 0 1589248 5669939 2026-06-23T09:58:08Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Ad=== {{başlık başı|ru|ad|c=e|baş=шпина́т}} # [[ıspanak]] ====Çekimleme==== {{ru-ad-cansız-s-E-a|шпина́т}} 5669939 wikitext text/x-wiki ==Rusça== ===Ad=== {{başlık başı|ru|ad|c=e|baş=шпина́т}} # [[ıspanak]] ====Çekimleme==== {{ru-ad-cansız-s-E-a|шпина́т}} bvs6hsmojpvh5b7i71yf1j9dssuwmxv лавр 0 1589249 5669940 2026-06-23T10:21:35Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== {{vikipedi|dil=ru}} ===Köken 1=== ====Ad==== {{başlık başı|ru|ad|c=e}} # {{t|dil=ru|ağaçlar}} [[defne]] =====Çekimleme===== {{ru-ad-cansız-s-E-a|ла́вр}} ===Köken 2=== ====Ad==== {{başlık başı|ru|çekimli ad}} # {{çekim|dil=ru|ла́вра||gen|ç}} 5669940 wikitext text/x-wiki ==Rusça== {{vikipedi|dil=ru}} ===Köken 1=== ====Ad==== {{başlık başı|ru|ad|c=e}} # {{t|dil=ru|ağaçlar}} [[defne]] =====Çekimleme===== {{ru-ad-cansız-s-E-a|ла́вр}} ===Köken 2=== ====Ad==== {{başlık başı|ru|çekimli ad}} # {{çekim|dil=ru|ла́вра||gen|ç}} rgzyc1dfu8lu7bg5iowz64xugqlrctx նուռն 0 1589250 5669942 2026-06-23T11:12:51Z MustafaCavlak 59368 Yeni sayfa : ==Eski Ermenice== [[Dosya:Pomegranate.jpg|küçükresim|220px]] ===Ad=== {{başlık başı|xcl|ad}} # [[nar]] 5669942 wikitext text/x-wiki ==Eski Ermenice== [[Dosya:Pomegranate.jpg|küçükresim|220px]] ===Ad=== {{başlık başı|xcl|ad}} # [[nar]] foj4pmoe9peg2p9h2pbvw0ywhsw0rei նռնաքար 0 1589251 5669943 2026-06-23T11:16:37Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Ad=== {{hy-ad}} # [[grena]] ====Çekimleme==== {{hy-ad-ի-եր}} 5669943 wikitext text/x-wiki ==Ermenice== ===Ad=== {{hy-ad}} # [[grena]] ====Çekimleme==== {{hy-ad-ի-եր}} j8o1j49o8qklghn99f9ec81xn3xtaz7 գրեյպֆրուտ 0 1589252 5669944 2026-06-23T11:19:07Z MustafaCavlak 59368 Yeni sayfa : ==Ermenice== ===Köken=== {{nakledilen|hy|ru|грейпфру́т}}. ===Ad=== {{hy-ad}} # [[greyfurt]] ====Çekimleme==== {{hy-ad-ի-ներ}} 5669944 wikitext text/x-wiki ==Ermenice== ===Köken=== {{nakledilen|hy|ru|грейпфру́т}}. ===Ad=== {{hy-ad}} # [[greyfurt]] ====Çekimleme==== {{hy-ad-ի-ներ}} 1fntcs5ae0iypvng1fu4a82oefynwfx გრეიპფრუტი 0 1589253 5669946 2026-06-23T11:27:20Z MustafaCavlak 59368 Yeni sayfa : ==Gürcüce== ===Köken=== {{nakledilen|ka|ru|грейпфру́т}}. ===Ad=== {{başlık başı|ka|ad}} # [[greyfurt]] ====Çekimleme==== {{ka-ad-tablo}} 5669946 wikitext text/x-wiki ==Gürcüce== ===Köken=== {{nakledilen|ka|ru|грейпфру́т}}. ===Ad=== {{başlık başı|ka|ad}} # [[greyfurt]] ====Çekimleme==== {{ka-ad-tablo}} l2drnm8c4s7uo0shzautadd7bg7k4bq Республика Корея 0 1589254 5669947 2026-06-23T11:39:56Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Özel ad=== {{başlık başı|ru|özel ad|c=d|baş=[[республика|Респу́блика]] [[Коре́я]]}} # [[Kore Cumhuriyeti]] 5669947 wikitext text/x-wiki ==Rusça== ===Özel ad=== {{başlık başı|ru|özel ad|c=d|baş=[[республика|Респу́блика]] [[Коре́я]]}} # [[Kore Cumhuriyeti]] icjg3t76dp4928o43dk7ck9mpqjsb1g Корея 0 1589255 5669948 2026-06-23T11:40:59Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Özel ad=== {{başlık başı|ru|özel ad|c=d|baş=Коре́я}} # [[Kore]] 5669948 wikitext text/x-wiki ==Rusça== ===Özel ad=== {{başlık başı|ru|özel ad|c=d|baş=Коре́я}} # [[Kore]] 3uwtl6r1js8diofclyhviw9qdjo5bso Корейская Народно-Демократическая Республика 0 1589256 5669949 2026-06-23T11:44:53Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Özel ad=== {{başlık başı|ru|özel ad|baş=[[корейский|Коре́йская]] [[народный|Наро́дно]]-[[демократический|Демократи́ческая]] [[республика|Респу́блика]]|c=d}} # [[Kore Demokratik Halk Cumhuriyeti]] 5669949 wikitext text/x-wiki ==Rusça== ===Özel ad=== {{başlık başı|ru|özel ad|baş=[[корейский|Коре́йская]] [[народный|Наро́дно]]-[[демократический|Демократи́ческая]] [[республика|Респу́блика]]|c=d}} # [[Kore Demokratik Halk Cumhuriyeti]] f2n3dv3p742724ygntrzn3gy8ib8nq1 корейский 0 1589257 5669950 2026-06-23T11:46:36Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Köken=== {{ek|dil=ru|Коре́я|ский}} ===Söyleniş=== * {{ses|dil=ru|Ru-корейский.ogg}} ===Ön ad=== {{ru-ön ad|коре́йский}} # [[Kore]] ile ilgili 5669950 wikitext text/x-wiki ==Rusça== ===Köken=== {{ek|dil=ru|Коре́я|ский}} ===Söyleniş=== * {{ses|dil=ru|Ru-корейский.ogg}} ===Ön ad=== {{ru-ön ad|коре́йский}} # [[Kore]] ile ilgili 7f6sfbl4v0prgsg3zku5guwdxe5vk25 -ский 0 1589258 5669951 2026-06-23T11:48:45Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Son ek=== {{başlık başı|ru|son ek}} # {{şot|İlişkili ön ad yapım eki.}} 5669951 wikitext text/x-wiki ==Rusça== ===Son ek=== {{başlık başı|ru|son ek}} # {{şot|İlişkili ön ad yapım eki.}} 1z6j4x7klzyaiquypoo7xik2g7r0upn 5669952 5669951 2026-06-23T11:49:12Z MustafaCavlak 59368 /* Son ek */ 5669952 wikitext text/x-wiki ==Rusça== ===Son ek=== {{başlık başı|ru|son ek}} # {{şot|İlişkili ön ad yapım eki.}} ====Türetilmiş kavramlar==== * {{sonek-t|ru}} 1oin2ezaw6ep5d3d3vy860s68wb28v2 кореец 0 1589259 5669953 2026-06-23T11:53:59Z MustafaCavlak 59368 Yeni sayfa : {{bak|Кореец}} ==Rusça== ===Köken=== {{ek|dil=ru|Коре́я|ец}} ===Söyleniş=== * {{ses|dil=ru|LL-Q7737 (rus)-DomesticFrog-кореец.wav}} ===Ad=== {{başlık başı|ru|ad|baş=коре́ец|c=e}} # [[Koreli]] 5669953 wikitext text/x-wiki {{bak|Кореец}} ==Rusça== ===Köken=== {{ek|dil=ru|Коре́я|ец}} ===Söyleniş=== * {{ses|dil=ru|LL-Q7737 (rus)-DomesticFrog-кореец.wav}} ===Ad=== {{başlık başı|ru|ad|baş=коре́ец|c=e}} # [[Koreli]] oe9hhy4veadzpwbyz6rkp7w4ozwrbpi Kategori:Rusça -ец son ekiyle oluşmuş sözcükler 14 1589260 5669954 2026-06-23T11:54:34Z MustafaCavlak 59368 Yeni sayfa : {{kategori ek|ru|son|-ец}} 5669954 wikitext text/x-wiki {{kategori ek|ru|son|-ец}} 8onevt7vph873z5x89wtzwng5xf3cwe кореянка 0 1589261 5669955 2026-06-23T11:56:34Z MustafaCavlak 59368 Yeni sayfa : ==Rusça== ===Ad=== {{başlık başı|ru|ad|baş=корея́нка|c=d}} # {{dişil karşılığı|ru|коре́ец}} 5669955 wikitext text/x-wiki ==Rusça== ===Ad=== {{başlık başı|ru|ad|baş=корея́нка|c=d}} # {{dişil karşılığı|ru|коре́ец}} bd2hxypabsgqg6oxlydrm55oy17mw9l