diff options
Diffstat (limited to 'corp/russian/predlozhnik/src')
-rw-r--r-- | corp/russian/predlozhnik/src/main.rs | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/corp/russian/predlozhnik/src/main.rs b/corp/russian/predlozhnik/src/main.rs new file mode 100644 index 000000000000..e267c849de83 --- /dev/null +++ b/corp/russian/predlozhnik/src/main.rs @@ -0,0 +1,345 @@ +use yew::html::Scope; +use yew::prelude::*; + +use lazy_static::lazy_static; +use maplit::hashmap; +use std::collections::BTreeSet; +use std::collections::HashMap; + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +enum Падеж { + Именительный, + Родительный, + Дательный, + Винительный, + Творительный, + Предложный, +} + +impl Падеж { + const ВСЕ: [Self; 6] = [ + Self::Именительный, + Self::Родительный, + Self::Дательный, + Self::Винительный, + Self::Творительный, + Self::Предложный, + ]; + + fn вопрос(&self) -> &str { + use Падеж::*; + match self { + Именительный => "кто? Что?", + Родительный => "кого? Чего?", + Дательный => "кому? Чему?", + Винительный => "кого? Что?", + Творительный => "кем? Чем?", + Предложный => "ком? Чём?", + } + } +} + +lazy_static! { + static ref ПО_ПРЕДЛОГУ: HashMap<&'static str, BTreeSet<Падеж>> = { + use Падеж::*; + + hashmap! { + "без" => BTreeSet::from([Родительный]), + "близ" => BTreeSet::from([Родительный]), + "в" => BTreeSet::from([Винительный, Предложный]), + "вместо" => BTreeSet::from([Родительный]), + "вне" => BTreeSet::from([Родительный]), + "внутри" => BTreeSet::from([Родительный]), + "возле" => BTreeSet::from([Родительный]), + "вокруг" => BTreeSet::from([Родительный]), + "вроде" => BTreeSet::from([Родительный]), + "для" => BTreeSet::from([Родительный]), + "до" => BTreeSet::from([Родительный]), + "за" => BTreeSet::from([Винительный, Творительный]), + "из" => BTreeSet::from([Родительный]), + "из-за" => BTreeSet::from([Родительный]), + "из-под" => BTreeSet::from([Родительный]), + "к" => BTreeSet::from([Дательный]), + "кроме" => BTreeSet::from([Родительный]), + "между" => BTreeSet::from([Творительный, Родительный]), + "на" => BTreeSet::from([Винительный, Предложный]), + "над" => BTreeSet::from([Творительный]), + "нет" => BTreeSet::from([Родительный]), + "о" => BTreeSet::from([Винительный, Предложный]), + "около" => BTreeSet::from([Родительный]), + "от" => BTreeSet::from([Родительный]), + "перед" => BTreeSet::from([Творительный]), + "по" => BTreeSet::from([Винительный, Дательный, Предложный]), + "под" => BTreeSet::from([Винительный, Творительный]), + "после" => BTreeSet::from([Родительный]), + "при" => BTreeSet::from([Предложный]), + "про" => BTreeSet::from([Винительный]), + "ради" => BTreeSet::from([Родительный]), + "с" => BTreeSet::from([Родительный, Винительный, Творительный]), + "сквозь" => BTreeSet::from([Винительный]), + "среди" => BTreeSet::from([Родительный]), + "у" => BTreeSet::from([Родительный]), + "через" => BTreeSet::from([Винительный]), + } + }; + static ref ПО_ПАДЕЖУ: HashMap<Падеж, BTreeSet<&'static str>> = { + let mut m = hashmap!(); + + for c in Падеж::ВСЕ { + let mut предлоги: BTreeSet<&'static str> = BTreeSet::new(); + for (k, v) in &*ПО_ПРЕДЛОГУ { + if v.contains(&c) { + предлоги.insert(k); + } + } + + m.insert(c, предлоги); + } + + m + }; + static ref ПАДЕЖИ: BTreeSet<Падеж> = BTreeSet::from(Падеж::ВСЕ); + static ref ПРЕДЛОГИ: BTreeSet<&'static str> = { + let mut s: BTreeSet<&'static str> = BTreeSet::new(); + + for п in ПО_ПРЕДЛОГУ.keys() { + s.insert(п); + } + + s + }; +} + +fn исключение(предлог: &str, падеж: Падеж) -> Option<Html> { + use Падеж::*; + + match (предлог, падеж) { + ("в", Винительный) => Some(html! {"Во что? В кого?"}), + + ("о", Винительный) => Some(html! { + <> + <p>{"О кого? Обо что?"}</p> + <p>{"Редко используется. Например:"}</p> + <ul> + <li>{"Удариться о притолоку."}</li> + <li>{"точить о камень."}</li> + </ul> + </> + }), + + ("между", Родительный) => Some(html! { + <> + <p>{"Между чего?"}</p> + <p>{"Редко используется. Только в идиомах и старой литературе:"}</p> + <ul> + <li>{"Читаю между строк."}</li> + </ul> + </> + }), + + _ => None, + } +} + +enum Сообщение { + ВыбралПадеж(Option<Падеж>), + ВыбралПредлог(Option<&'static str>), +} + +#[derive(Default)] +struct Модель { + падеж: Option<Падеж>, + предлог: Option<&'static str>, +} + +struct Вывод { + доступные_падежи: BTreeSet<Падеж>, + доступные_предлоги: BTreeSet<&'static str>, + объяснение: Option<Html>, +} + +fn объясни(падеж: Падеж, предлог: &str) -> Html { + let иск = match исключение(предлог, падеж) { + Some(exp) => html! { exp }, + None => html! { format!("{} {}", предлог, падеж.вопрос()) }, + }; + + html! { + <div id="obyasnenie"> + <hr/> + <h2>{"Пример:"}</h2> + {иск} + </div> + } +} + +fn ограничить(м: &Модель) -> Вывод { + match (м.падеж, &м.предлог) { + (Some(пж), Some(пл)) => Вывод { + доступные_падежи: (*ПО_ПРЕДЛОГУ)[пл].clone(), + доступные_предлоги: (*ПО_ПАДЕЖУ)[&пж].clone(), + объяснение: Some(объясни(пж, пл)), + }, + + (Some(пж), None) => Вывод { + доступные_падежи: BTreeSet::from([пж]), + доступные_предлоги: (*ПО_ПАДЕЖУ)[&пж].clone(), + объяснение: None, + }, + + (None, Some(пл)) => Вывод { + доступные_падежи: (*ПО_ПРЕДЛОГУ)[пл].clone(), + доступные_предлоги: BTreeSet::from([*пл]), + объяснение: None, + }, + + (None, None) => Вывод { + доступные_падежи: ПАДЕЖИ.clone(), + доступные_предлоги: ПРЕДЛОГИ.clone(), + объяснение: None, + }, + } +} + +fn класс_кнопки(выбран: bool, доступен: bool) -> String { + let класс = "btn ".to_string(); + класс + + match (выбран, доступен) { + (true, _) => "btn-primary", + (false, true) => "btn-ghost btn-primary", + (false, false) => "btn-ghost btn-default", + } +} + +fn покажи_предлог( + link: &Scope<Модель>, + м: &Модель, + вв: &Вывод, + п: &'static str, +) -> Html { + let выбран = м.предлог == Some(п); + let доступен = вв.доступные_предлоги.contains(п); + let класс = класс_кнопки(выбран, доступен); + + html! { + <button class={класс} + onclick={link.callback(move |_| if выбран { + Сообщение::ВыбралПредлог(None) + } else { + Сообщение::ВыбралПредлог(Some(п)) + })} + disabled={!доступен}> + {п} + </button> + } +} + +fn покажи_падеж( + link: &Scope<Модель>, м: &Модель, вв: &Вывод, п: Падеж +) -> Html { + let выбран = м.падеж == Some(п); + let доступен = вв.доступные_падежи.contains(&п); + let класс = класс_кнопки(выбран, доступен); + + html! { + <button class={класс} + onclick={link.callback(move |_| if выбран { + Сообщение::ВыбралПадеж(None) + } else { + Сообщение::ВыбралПадеж(Some(п)) + })} + disabled={!доступен}> + {format!("{:?}", п)} + </button> + } +} + +impl Component for Модель { + type Message = Сообщение; + type Properties = (); + + fn create(_ctx: &Context<Self>) -> Self { + Default::default() + } + + fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool { + match msg { + Сообщение::ВыбралПадеж(пж) => self.падеж = пж, + Сообщение::ВыбралПредлог(пл) => self.предлог = пл, + } + + true + } + + fn view(&self, ctx: &Context<Self>) -> Html { + let вв = ограничить(self); + let link = ctx.link(); + + let кнопки_предлогов = ПРЕДЛОГИ + .iter() + .map(|п| покажи_предлог(link, self, &вв, п)) + .collect::<Html>(); + + let кнопки_падежов = ПАДЕЖИ + .iter() + .map(|п| покажи_падеж(link, self, &вв, *п)) + .collect::<Html>(); + + let объяснение = вв.объяснение.map(|exp| exp).unwrap_or_else(|| html! {}); + + let footer = html! { + <footer> + <hr/> + <p class="footer"> + <a href="https://code.tvl.fyi/tree/users/tazjin/predlozhnik">{"код"}</a> + {" | "} + {"сделано "}<a href="https://tvl.su">{"ООО \"ТВЛ\""}</a> + </p> + </footer> + }; + + let код_рекламы = r#" +window.yaContextCb.push(()=>{ + Ya.Context.AdvManager.render({ + renderTo: 'yandex_rtb_R-A-1773485-1', + blockId: 'R-A-1773485-1' + }) +}) +"#; + + let реклама = html! { + <div id="ad"> + <div id="yandex_rtb_R-A-1773485-1"></div> + <script>{код_рекламы}</script> + </div> + }; + + html! { + <> + <div id="header"> + <h1>{"Предложник"}</h1> + <p>{"... показывает с какими падежами употребляются предлоги в русском языке."}</p> + </div> + + <h2>{"Выбирай предлог:"}</h2> + <div id="predlogi"> + {кнопки_предлогов} + </div> + <hr/> + + <h2>{"Выбирай падеж:"}</h2> + <div id="padezhi"> + {кнопки_падежов} + </div> + + {объяснение} + {footer} + {реклама} + </> + } + } +} + +fn main() { + yew::start_app::<Модель>(); +} |