diff --git a/vidyut-prakriya/src/angasya.rs b/vidyut-prakriya/src/angasya.rs index bdab646..542bae1 100644 --- a/vidyut-prakriya/src/angasya.rs +++ b/vidyut-prakriya/src/angasya.rs @@ -55,8 +55,11 @@ fn add_num(t: &mut Term) { /// Runs rules that lengthen a vowel in the anga. fn try_do_dirgha(p: &mut Prakriya, i_anga: usize) -> Option<()> { + p.debug(format!("TRY DO HALAH {i_anga}")); let anga = p.get_if(i_anga, |t| t.is_anga())?; - let n = p.get_if(i_anga + 1, |t| t.is_pratyaya())?; + // Also include yAsut-Agama for ji + yAs + t --> jIyAt. + // TODO: extend? + let n = p.get_if(i_anga + 1, |t| t.is_pratyaya() || t.has_u("yAsu~w"))?; // Exclude tin -- otherwise, we get "daDAntaH" instead of "daDantaH". // "kvisāhacaryeṇa tiṅbhinnasyaiva jhalādestatra grahaṇāt" -- Balamanorama on 6.4.48. diff --git a/vidyut-prakriya/src/samprasarana.rs b/vidyut-prakriya/src/samprasarana.rs index 174ae4d..a8db8b0 100644 --- a/vidyut-prakriya/src/samprasarana.rs +++ b/vidyut-prakriya/src/samprasarana.rs @@ -7,6 +7,7 @@ use crate::core::{Code, Prakriya}; /// - Must follow atidesha so that suffixes have the kit/Nit annotations necessary to cause /// samprasanara. use crate::dhatu_gana as gana; +use crate::sounds; fn is_vaci_svapi(t: &Term) -> bool { t.is_dhatu() @@ -64,11 +65,11 @@ fn find_samprasarana_match(p: &Prakriya, i: usize) -> Option<&'static str> { ]; const AFTER: &[&str] = &[ // vaci-svapi - "uc", "uc", "sup", "ij", "up", "uh", "us", "u", "vI", "hU", "ud", "SU", + "uac", "uac", "suap", "iaj", "uap", "uah", "uas", "ue", "vie", "hue", "uad", "Sui", // grahi-jyA - "gfh", "ji", "uy", "uy", "viD", "uS", "vic", "vfsc", "pfC", "Bfsj", + "gfah", "jiA", "uay", "uay", "viaD", "uaS", "viac", "vfasc", "pfaC", "Bfasj", // other rules - "sim", "sim", + "siam", "siam", ]; debug_assert!(BEFORE.len() == AFTER.len()); @@ -80,15 +81,32 @@ fn find_samprasarana_match(p: &Prakriya, i: usize) -> Option<&'static str> { } } +// Deletes the vowel that follows the samprasarana vowel. +// +// Example: "uac" --> "uc" +fn run_samprasaranac_ca(p: &mut Prakriya, i_dhatu: usize) -> Option<()> { + // The code here is inelegant, but it works. + let dhatu = p.get(i_dhatu)?; + for i in 0..dhatu.len() - 1 { + if let (Some(x), Some(y)) = (dhatu.get_at(i), dhatu.get_at(i + 1)) { + if sounds::is_ac(x) && sounds::is_ac(y) { + p.run_at("6.1.108", i_dhatu, |t| t.set_at(i + 1, "")); + return Some(()); + } + } + } + + Some(()) +} + /// Runs a hacky version of samprasarana that runs 6.1.108 (samprasAraNAcca) immediately. -/// -/// TODO: properly annotate 6.1.108 and related rules here. -fn do_samprasarana(rule: Code, p: &mut Prakriya, i_dhatu: usize) -> Option<()> { - let after = find_samprasarana_match(p, i_dhatu)?; +fn do_samprasarana_for_dhatu(rule: Code, p: &mut Prakriya, i_dhatu: usize) -> Option<()> { + let new_text = find_samprasarana_match(p, i_dhatu)?; p.run_at(rule, i_dhatu, |t| { - t.set_text(after); + t.set_text(new_text); t.add_tag(T::FlagSamprasarana); }); + run_samprasaranac_ca(p, i_dhatu); Some(()) } @@ -99,6 +117,7 @@ fn do_samprasarana_for_abhyasa(rule: Code, p: &mut Prakriya, i_abhyasa: usize) - t.set_text(after); t.add_tag(T::FlagSamprasarana); }); + run_samprasaranac_ca(p, i_dhatu); Some(()) } @@ -156,10 +175,10 @@ pub fn run_for_dhatu_after_atidesha(p: &mut Prakriya) -> Option<()> { let is_ve = dhatu.has_u("ve\\Y"); if dhatu.has_u("Yizva\\pa~") && n.has_u("Ric") && p.has(i_n + 1, |t| t.has_u("caN")) { // asUzupat - do_samprasarana("6.1.18", p, i); + do_samprasarana_for_dhatu("6.1.18", p, i); } else if dhatu.has_u_in(&["Yizva\\pa~", "syamu~", "vye\\Y"]) && n_is_yan { // sozupyate, sesimyate, vevIyate - do_samprasarana("6.1.19", p, i); + do_samprasarana_for_dhatu("6.1.19", p, i); } else if dhatu.has_u("vaSa~") && n_is_yan { // vAvaSyate (exception to grahi-jyA-...) p.step("6.1.20"); @@ -203,12 +222,12 @@ pub fn run_for_dhatu_after_atidesha(p: &mut Prakriya) -> Option<()> { } else { // General rules if is_vaci_svapi(dhatu) && n.has_tag(T::kit) { - do_samprasarana("6.1.15", p, i); + do_samprasarana_for_dhatu("6.1.15", p, i); } else if is_grahi_jya(dhatu) && n.is_knit() { if dhatu.has_u("pra\\Ca~") && n.has_u("naN") { // Per ashtadhyayi.com, skip samprasarana for praC + naN. } else { - do_samprasarana("6.1.16", p, i); + do_samprasarana_for_dhatu("6.1.16", p, i); if p.has(i, |t| t.has_text("uy") && t.has_u("vayi~")) { optional_set_text("6.1.39", p, "uv"); } diff --git a/vidyut-prakriya/test_utils/src/lib.rs b/vidyut-prakriya/test_utils/src/lib.rs index 94afd01..122e8a5 100644 --- a/vidyut-prakriya/test_utils/src/lib.rs +++ b/vidyut-prakriya/test_utils/src/lib.rs @@ -996,23 +996,27 @@ fn debug_text(rule: Rule) -> String { /// Nicely prints out the given prakriyas. pub fn print_all_prakriyas(prakriyas: &[Prakriya]) { for p in prakriyas { - for step in p.history() { - let mut result = String::new(); - for (i, t) in step.result().iter().enumerate() { - if i != 0 { - result += " + "; - } - if t.was_changed() { - result += &format!("[{}]", t.text()); - } else { - result += t.text(); - } + print_prakriya(p); + } +} + +pub fn print_prakriya(p: &Prakriya) { + for step in p.history() { + let mut result = String::new(); + for (i, t) in step.result().iter().enumerate() { + if i != 0 { + result += " + "; + } + if t.was_changed() { + result += &format!("[{}]", t.text()); + } else { + result += t.text(); } - println!("{} --> {}", debug_text(step.rule()), result); } - println!("{:?}", p.rule_choices()); - println!(); + println!("{} --> {}", debug_text(step.rule()), result); } + println!("{:?}", p.rule_choices()); + println!(); } // Heavy assert helpers @@ -1038,3 +1042,36 @@ pub fn assert_has_results(prakriyas: Vec, expected: &[&str]) { assert_eq!(expected, actuals); } + +pub fn assert_matches_prakriya(p: &Prakriya, expected: &[(Rule, Vec<&str>)]) { + let mut i_start = 0; + for (e_rule, e_result) in expected { + let i = p.history()[i_start..] + .iter() + .enumerate() + .find(|(_, x)| x.rule() == *e_rule) + .map(|x| x.0); + + if i.is_none() { + print_prakriya(p); + assert!( + false, + "Could not find expected rule {:?} in prakriya.", + e_rule + ); + } + + i_start += i.unwrap(); + + let step = &p.history()[i_start]; + let items: Vec<_> = step.result().iter().map(|x| x.text()).collect(); + if &items != e_result { + print_prakriya(p); + assert!( + false, + "Mismatch for prakriya on code {:?}, {}.", + e_rule, i_start + ); + } + } +} diff --git a/vidyut-prakriya/tests/prakriyas.rs b/vidyut-prakriya/tests/prakriyas.rs new file mode 100644 index 0000000..94ca2e7 --- /dev/null +++ b/vidyut-prakriya/tests/prakriyas.rs @@ -0,0 +1,37 @@ +//! Tests that verify that a prakriya has a specific format. +//! +//! TODO: add tests from the पाणिनीयव्याकरणोदाहरणकोषः +extern crate test_utils; +use test_utils::*; +use vidyut_prakriya::args::Gana::*; +use vidyut_prakriya::args::*; +use vidyut_prakriya::Rule; + +#[test] +fn bhavadi() { + let bhu = d("BU", Bhvadi); + let args = Tinanta::builder() + .dhatu(bhu) + .prayoga(Prayoga::Kartari) + .purusha(Purusha::Prathama) + .vacana(Vacana::Eka) + .lakara(Lakara::Lat) + .build() + .unwrap(); + let t = Tester::default(); + let ps = t.derive_tinantas(&args); + let p = ps.iter().find(|p| p.text() == "Bavati").unwrap(); + + use Rule::Ashtadhyayi as A; + + assert_matches_prakriya( + p, + &[ + (A("1.3.1"), vec!["BU"]), + (A("3.4.78"), vec!["BU", "tip"]), + (A("3.1.68"), vec!["BU", "Sap", "ti"]), + (A("7.3.84"), vec!["Bo", "a", "ti"]), + (A("6.1.78"), vec!["Bav", "a", "ti"]), + ], + ); +} diff --git a/vidyut-prakriya/tests/regressions.rs b/vidyut-prakriya/tests/regressions.rs index 3564b71..0a5cc87 100644 --- a/vidyut-prakriya/tests/regressions.rs +++ b/vidyut-prakriya/tests/regressions.rs @@ -3,6 +3,8 @@ extern crate test_utils; use test_utils::*; use vidyut_prakriya::args::Gana::*; use vidyut_prakriya::args::Lakara::*; +use vidyut_prakriya::args::*; +use vidyut_prakriya::Rule; #[test] fn ambibat() { @@ -41,3 +43,38 @@ fn irshy_san_lan() { &["Erzyiyizat", "Erzyizizat"], ); } + +/// Fixes https://github.com/ambuda-org/vidyut/issues/118 +/// +/// This test verifies the following: +/// - We correctly apply 6.4.108. +/// - We lengthen the dhatu's vowel with 6.4.2. +#[test] +fn jiyat_prakriya() { + let jya = d("jyA\\", Kryadi); + let args = Tinanta::builder() + .dhatu(jya) + .prayoga(Prayoga::Kartari) + .purusha(Purusha::Prathama) + .vacana(Vacana::Eka) + .lakara(Lakara::AshirLin) + .build() + .unwrap(); + let t = Tester::default(); + let ps = t.derive_tinantas(&args); + let p = ps.iter().find(|p| p.text() == "jIyAt").unwrap(); + + use Rule::Ashtadhyayi as A; + + assert_matches_prakriya( + p, + &[ + (A("1.3.1"), vec!["jyA\\"]), + (A("3.4.116"), vec!["jyA", "ti"]), + (A("6.1.16"), vec!["jiA", "yAs", "st"]), + (A("6.1.108"), vec!["ji", "yAs", "st"]), + (A("6.4.2"), vec!["jI", "yAs", "st"]), + (A("8.4.56"), vec!["jI", "yA", "t"]), + ], + ); +}