diff --git a/README.md b/README.md index e91ed15..cc9df9a 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,12 @@ Installation ------------ Vidyut is implemented in [Rust][rust], which provides low-level control with -high-level ergonomics. We also provide first-class support for Python bindings -through the [vidyut][vidyut-py] Python package. This section describes how to -use Vidyut either through Rust or through Python. +high-level ergonomics. For your convenience, we also provide first-class support +for Python bindings through the [vidyut][vidyut-py] Python package. This section +describes how to use Vidyut either through Rust or through Python. -[pypi]: https://vidyut.readthedocs.io/en/latest/ [rust]: https://www.rust-lang.org/ +[vidyut-py]: https://vidyut.readthedocs.io/en/latest/ ### Through Rust @@ -84,7 +84,7 @@ Python project. Once your setup is ready, you can install the `vidyut` package: ```shell -# With Pip +# With pip $ pip install vidyut # With uv @@ -108,6 +108,8 @@ We recommend using our pre-built linguistic data, which is available as a ZIP fi For more information, see our [Python documentation][rtd]. +[rtd]: https://vidyut.readthedocs.io/en/latest/ + Building from source -------------------- @@ -137,6 +139,7 @@ be much faster. We recommend using our pre-built linguistic data, which is available as a ZIP file [here][zip]. Or if you prefer, you can build this data for yourself: +[nextest]: https://nexte.st/ [zip]: https://github.com/ambuda-org/vidyut-py/releases/download/0.3.0/data-0.3.0.zip ```shell @@ -245,9 +248,21 @@ For details, see the [vidyut-sandhi README][vidyut-sandhi]. Documentation ------------- -To view documentation for all crates (including private modules and structs), -run `make docs`. This command will generate Rust's standard documentation and -open it in your default web browser. +Our Rust documentation is available [on docs.rs][docs-rs], and our Python +documentation is available on [readthedocs.org][vidyut-rtd]. You can also build +our documentation from scratch: + +- (Rust) To view documentation for all crates (including private modules and + structs), run `make docs` from the repository root. This command will + generate Rust's standard documentation and open it in your default web + browser. + +- (Python) To view the latest build of our Python documentation, run `make docs` + from the `bindings-python` directory. This command will write our Python docs + to local HTML files, which you should then open manually. + +[docs-rs]: https://docs.rs/releases/search?query=vidyut +[vidyut-rtd]: https://vidyut.readthedocs.io/en/latest/ Contributing diff --git a/bindings-python/CHANGES.rst b/bindings-python/CHANGES.rst index 009ca30..f1feecb 100644 --- a/bindings-python/CHANGES.rst +++ b/bindings-python/CHANGES.rst @@ -9,8 +9,8 @@ releases. That is, versions 0.x.a and 0.x.b will be able to use the same data. Released 2025-01-12. -- Fix a `PanicException` when making many call to `transliterate`. -- Fix a `PanicException` when using the `YiWa` pratyaya. +- Fix a `PanicException` when making many calls to `transliterate`. +- Fix a `PanicException` when using the ``YiWa`` pratyaya. - Add `Kosha.dhatus` and `Kosha.pratipadikas`. - Update all `vidyut.prakriya` enums to use SLP1 strings for `as_str`. - Update Python enums to use SLP1 strings for `__str__`. diff --git a/bindings-python/src/kosha/entries.rs b/bindings-python/src/kosha/entries.rs index a1d0c7d..920ed21 100644 --- a/bindings-python/src/kosha/entries.rs +++ b/bindings-python/src/kosha/entries.rs @@ -53,7 +53,7 @@ impl PyDhatuEntry { self.dhatu.clone() } - /// Convert this entry to a `Dhatu`. + /// Convert this entry to a :class:`~vidyut.prakriya.Dhatu`. pub fn to_prakriya_args(&self) -> PyDhatu { self.dhatu.clone() } @@ -140,7 +140,7 @@ impl PyPratipadikaEntry { } } - /// Convert this entry to a `Pratipadika`. + /// Convert this entry to a :class:`~vidyut.prakriya.Pratipadika`. pub fn to_prakriya_args(&self) -> PyPratipadika { use PyPratipadikaEntry as PE; match self { @@ -269,7 +269,7 @@ pub enum PyPadaEntry { impl PyPadaEntry { /// The lemma used by this *pada*. /// - /// The lemma is either aa *dhātu* or a simple *prātipadika*. + /// The lemma is either a *dhātu* or a simple *prātipadika*. #[getter] pub fn lemma(&self) -> Option { match self { @@ -325,7 +325,7 @@ impl PyPadaEntry { } } - /// Convert this entry to a `Pada`. + /// Convert this entry to a :class:`~vidyut.prakriya.Pada`. pub fn to_prakriya_args(&self) -> PyResult { PyPada::try_from(self) } diff --git a/bindings-python/src/macro_utils.rs b/bindings-python/src/macro_utils.rs index e95169f..478b9ce 100644 --- a/bindings-python/src/macro_utils.rs +++ b/bindings-python/src/macro_utils.rs @@ -22,6 +22,188 @@ /// /// For enum compat, see: https://github.com/PyO3/pyo3/issues/2887 macro_rules! py_enum { + ($Py:ident, $Rust:ident, [$( $variant:ident ),*]) => { + impl From<$Rust> for $Py { + fn from(val: $Rust) -> Self { + match val { + $( + $Rust::$variant => $Py::$variant, + )* + } + } + } + + impl From<$Py> for $Rust { + fn from(val: $Py) -> Self { + match val { + $( + $Py::$variant => $Rust::$variant, + )* + } + } + } + + impl std::fmt::Display for $Py { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", $Rust::from(*self).as_str()) + } + } + + #[pymethods] + impl $Py { + #[new] + fn __new__(val: String) -> PyResult<$Py> { + // O(n), but this is a rare method not on a hot path, so it's fine for now. + for choice in Self::choices() { + if val == choice.__str__() { + return Ok(choice) + } + } + Err(pyo3::exceptions::PyValueError::new_err( + format!("{:?} is not a valid {}", val, stringify!($Rust)))) + } + + /* + fn __format__(&self, spec: String) -> String { + format!("{{{}:{}}}", + $Rust::from(*self).as_str(), + spec + ) + } + */ + + fn __getitem__(&self, val: String) -> PyResult<$Py> { + // O(n), but this is a rare method not on a hot path, so it's fine for now. + for choice in Self::choices() { + if val == choice.name() { + return Ok(choice) + } + } + Err(pyo3::exceptions::PyKeyError::new_err(String::new())) + } + + fn __hash__(&self) -> u64 { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + self.hash(&mut hasher); + hasher.finish() + } + + pub(crate) fn __repr__(&self) -> String { + format!("{}.{}", stringify!{$Rust}, self.name()) + } + + /// The string representation of this enum. + /// + /// `__str__` returns a valid SLP1 string. This string might include + /// accent marks (``\\``, ``^``) or nasality markers (``~``). + fn __str__(&self) -> String { + self.value() + } + + // Enum built-ins + // ------------------- + + /// The name used to define the `Enum` member. + #[getter] + fn name(&self) -> String { + let ret = match self { + $( + $Py::$variant => stringify!($variant), + )* + }; + ret.to_string() + } + + /// The name used to define the `Enum` member. This is identical + /// to `name`. + /// + /// (For compatibility with Python `Enum`). + #[getter] + fn _name_(&self) -> String { + self.name() + } + + /// The name used to define the `Enum` member. + /// + /// The returned value is guaranteed to be identical to the output of + /// `__str__`. + #[getter] + fn value(&self) -> String { + $Rust::from(*self).as_str().to_string() + } + + /// The value associated with this variant. This is identical + /// to `value`. + /// + /// (For compatibility with Python `Enum`). + #[getter] + fn _value_(&self) -> String { + self.value() + } + + /// Return all possible values for this enum. + #[staticmethod] + fn choices() -> Vec<$Py> { + const CHOICES: &[$Py] = &[ + $( + $Py::$variant, + )* + ]; + CHOICES.to_vec() + } + + /// DEPRECATED -- prefer `__new__` instead. + /// + /// Create an enum value from the given string. + /// + /// This is the inverse of `__str__`. Performance is currently linear + /// in the number of enum options. + #[staticmethod] + fn from_string(val: &str) -> PyResult { + // O(n), but this is a rare method not on a hot path, so it's fine for now. + for choice in Self::choices() { + if val == choice.__str__() { + return Ok(choice) + } + } + Err(pyo3::exceptions::PyValueError::new_err(format!("Could not parse {val}"))) + } + + } + } +} + +/// Helper macro for pratyayas (Krt, Taddhita, Sanadi) +/// +/// Generates the following boilerplate methods: +/// +/// - `From for PyEnum` +/// - `From for RustEnum` +/// - `__new__` +/// - `__format__` +/// - `__hash__` +/// - `__repr__` +/// - `__str__` +/// - `choices` +/// +/// And the following boilerplate attributes: +/// - `drshya` +/// - `anubandhas` +/// - `name` +/// - `value` +/// - `_name_` +/// - `_value_` +/// +/// `__iter__` is not possible since it requires accessing the metaclass. +/// See https://github.com/PyO3/pyo3/issues/906 and related issues. +/// +/// Requirements: +/// - Enum must derive `Hash` +/// +/// For enum compat, see: https://github.com/PyO3/pyo3/issues/2887 +macro_rules! py_pratyaya { ($Py:ident, $Rust:ident, [$( $variant:ident ),*]) => { impl From<$Rust> for $Py { fn from(val: $Rust) -> Self { @@ -167,6 +349,19 @@ macro_rules! py_enum { } Err(pyo3::exceptions::PyValueError::new_err(format!("Could not parse {val}"))) } + + // Pratyaya methods + // ---------------- + + fn drshya(&self) -> String { + let rust = $Rust::from(*self); + rust.drshya().to_string() + } + + fn anubandhas(&self) -> Vec { + let rust = $Rust::from(*self); + rust.anubandhas().iter().map(|x| (*x).into()).collect() + } } } } @@ -274,3 +469,4 @@ macro_rules! py_only_enum { pub(crate) use py_enum; pub(crate) use py_only_enum; +pub(crate) use py_pratyaya; diff --git a/bindings-python/src/prakriya/args.rs b/bindings-python/src/prakriya/args.rs index 289bacc..15ddf95 100644 --- a/bindings-python/src/prakriya/args.rs +++ b/bindings-python/src/prakriya/args.rs @@ -4,11 +4,127 @@ Wrappers for vidyut-prakriya arguments. Pyo3 doesn't allow us to annotate existing enums, and using a wrapping struct has poor ergonomics for callers. So instead, redefine our enums of interest. */ -use crate::macro_utils::py_enum; +use crate::macro_utils::{py_enum, py_pratyaya}; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use vidyut_prakriya::args::{BaseKrt as Krt, *}; +/// One of the "indicatory" letters attached to an *aupadeśika*. +#[pyclass(name = "Anubandha", module = "prakriya", eq, eq_int, ord)] +#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd)] +#[allow(non_camel_case_types)] +pub enum PyAnubandha { + /// Placeholder *it* with no specific meaning. + adit, + /// (pratyaya) prevents it-agama for nisthA pratyayas per 7.2.16 but allows it optionally in + /// non-kartari usage per 7.2.17. + Adit, + /// (dhatu) indicates the mandatory use of num-Agama (vidi~ -> vind). + idit, + /// (pratyaya) prevents it-Agama for nisthA pratyayas per 7.2.14. + Idit, + /// (pratyaya) optionally allows it-agama for ktvA-pratyaya per 7.2.56. + udit, + /// (pratyaya) optionally allows it-agama per 7.2.44. + Udit, + /// (dhatu) prevents shortening of the dhatu vowel when followed by Ni + caN per 7.4.2. + fdit, + /// (dhatu) indicates the use of aN-pratyaya in luN-lakAra per 3.1.55. (gamx~ -> agamat) + xdit, + /// (dhatu) prevents vrddhi in luN-lakara when followed by it-Agama per 7.2.5 + edit, + /// (dhatu) indicates replacement of the "t" of a nistha-pratyaya with "n" per 8.2.45 (lagta -> + /// lagna). + odit, + /// (krt) prevents guna and vrddhi. Causes samprasarana for vac-Adi roots (vac -> ukta) per + /// 6.1.15 and grah-Adi roots (grah -> gfhIta) per 6.1.16. + /// + /// (taddhita) causes vrddhi per 7.2.118. Indicates antodAtta per 6.1.165. + /// + /// (agama) indicates that the Agama should be added after the term, per 1.1.46. + kit, + /// (taddhita) replaced with "In" per 7.1.2. + Kit, + /// (pratyaya) causes a term's final cavarga sound to shift to kavarga per 7.3.52 (yuj -> + /// yoga). + Git, + /// (pratyaya) prevents guna and vrddhi. Causes samprasarana for grah-Adi roots (grah -> + /// gfhIta) per 6.1.15. + /// + /// (dhatu) marks the dhAtu as taking only Atmanepada endings per 1.3.12. + Nit, + /// (pratyaya) indicates that the last syllable of the stem is udAtta per 6.1.153. + cit, + /// (taddhita) replaced with "Iy" per 7.1.2. + Cit, + /// (pratyaya) used to give distinct names to certain pratyayas, such as `jas`, `jus`, ... + jit, + /// (pratyaya) first letter of the bahuvacana-prathama-parasmaipada tinanta suffix. It is + /// replaced with "ant" or similar options per 7.1.3 - 7.1.5 and with "jus" by 3.4.108 - + /// 3.4.112. + Jit, + /// (dhatu) marks the dhAtu as taking either parasamaipada or Atmanepada endings per 1.3.72. + /// + /// (pratyaya) causes vrddhi per 7.2.115. + Yit, + /// (pratyaya) in a lakAra-pratyaya, indicates various transformations such as 3.4.79 and + /// 3.4.80. + wit, + /// (taddhita) replaced by "ik". + Wit, + /// (adesha) indicates replacement of the "Ti" section of the previous term per 6.4.143. + qit, + /// (taddhita) replaced with "ey" per 7.1.2. + Qit, + /// (pratyaya) causes vrddhi per 7.2.115. + Rit, + /// (pratyaya) causes *svarita*. + tit, + /// (pratyaya) + nit, + /// (pratyaya) indicates anudatta accent per 3.1.4. For sarvadhatuka pratyayas, allows guna and + /// vrddhi; all other sarvadhatuka pratyayas are marked as `Nit` per 1.2.4 and are thus blocked + /// from causing guna and vrddhi changes per 1.1.5. + pit, + /// (taddhita) replaced with "Ayan" per 7.1.2. + Pit, + /// (adesha) indicates insertion after the term's last vowel per 1.1.47. + /// + /// (dhatu) indicates shortening of the dhatu's penultimate vowel when followed by a + /// `RI`-pratyaya per 6.4.92. + mit, + /// (pratyaya) + rit, + /// (pratyaya) + lit, + /// (adesha) indicates a total replacement per 1.1.55. + /// + /// (pratyaya) marks the pratyaya as sArvadhAtuka per 3.4.113. + Sit, + /// (pratyaya) uses NIz-pratyaya in strI-linga per 4.1.41. + zit, + /// (pratyaya) indicates that the previous term should be called `pada` per 1.4.16. + sit, + /// (dhatu) indicates the optional use of aN-pratyaya in luN-lakAra per 3.1.57. + irit, + /// (dhatu) indicates that kta-pratyaya denotes the present tense as opposed to the past tense. + YIt, + /// (dhatu) allows the krt-pratyaya "Tuc" per 3.1.90. + wvit, + /// (dhatu) allows the krt-pratyaya "ktri" per 3.1.89. + qvit, +} + +py_enum!( + PyAnubandha, + Anubandha, + [ + adit, Adit, idit, Idit, udit, Udit, fdit, xdit, edit, odit, kit, Kit, Git, Nit, cit, Cit, + jit, Jit, Yit, wit, Wit, qit, Qit, Rit, tit, nit, pit, Pit, mit, rit, lit, Sit, zit, sit, + irit, YIt, wvit, qvit + ] +); + #[pyclass(name = "Gana", module = "prakriya", eq, eq_int, ord)] #[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd)] pub enum PyGana { @@ -271,6 +387,8 @@ pub enum PyKrt { /// -man manin, /// -ya + ya, + /// -ya yat, /// -ana yuc, @@ -318,7 +436,7 @@ pub enum PyKrt { sen, } -py_enum!( +py_pratyaya!( PyKrt, Krt, [ @@ -328,7 +446,7 @@ py_enum!( knu, kmarac, kyap, kru, krukan, klukan, kvanip, kvarap, kvasu, ksnu, kvin, kvip, Kac, KaS, Kal, KizRuc, KukaY, Kyun, Ga, GaY, GinuR, Gurac, Nvanip, cAnaS, Yyuw, wa, wak, qa, qara, qu, Ra, Ramul, Rini, Ryat, Ryuw, Rvi, Rvuc, Rvul, taveN, taven, tavE, tavya, tavyat, tumun, - tfc, tfn, tosun, Takan, naN, najiN, nan, ni, manin, yat, yuc, ra, ru, lyu, lyuw, vanip, + tfc, tfn, tosun, Takan, naN, najiN, nan, ni, manin, ya, yat, yuc, ra, ru, lyu, lyuw, vanip, varac, vic, viw, vuY, vun, zAkan, zwran, zvun, Sa, Satf, SaDyE, SaDyEn, SAnac, SAnan, se, sen ] @@ -693,7 +811,7 @@ pub enum PyTaddhita { ha, } -py_enum!( +py_pratyaya!( PyTaddhita, Taddhita, [ @@ -831,7 +949,7 @@ pub enum PySanadi { kyac, } -py_enum!( +py_pratyaya!( PySanadi, Sanadi, [san, yaN, yaNluk, Ric, kAmyac, kyaN, kyac] diff --git a/bindings-python/vidyut/docs/source/api.rst b/bindings-python/vidyut/docs/source/api.rst index d019e75..853a3da 100644 --- a/bindings-python/vidyut/docs/source/api.rst +++ b/bindings-python/vidyut/docs/source/api.rst @@ -1,5 +1,5 @@ -API -=== +API reference +============= This page defines an API reference for the `vidyut` package. diff --git a/bindings-python/vidyut/docs/source/introduction.rst b/bindings-python/vidyut/docs/source/introduction.rst index dba1fce..5ef9a76 100644 --- a/bindings-python/vidyut/docs/source/introduction.rst +++ b/bindings-python/vidyut/docs/source/introduction.rst @@ -61,8 +61,8 @@ and display your output results in Kannada or some other scheme that your users Although we haven't run a formal analysis, `vidyut.lipi` compares favorably with standard Python transliterators like Aksharamukha and indic_transliteration and even handles a few -cases that these transliterations miss. But its main feature is that with a small bit of -work, you can use the same high-quality transliteration engine almost anywhere on your +cases that these transliterators miss. But its main feature is that with a small bit of +work, you can use the same high-quality transliteration engine almost anywhere in your software stack. For example, `here`_ you can see `vidyut-lipi` running entirely in the browser via a WebAssembly build. diff --git a/bindings-python/vidyut/docs/source/prakriya.rst b/bindings-python/vidyut/docs/source/prakriya.rst index de7f533..d7bba24 100644 --- a/bindings-python/vidyut/docs/source/prakriya.rst +++ b/bindings-python/vidyut/docs/source/prakriya.rst @@ -349,7 +349,7 @@ Generate a single prakriya with its sutras from vidyut.prakriya import * data = Data("/path/to/prakriya/data") - code_to_sutra = {(s.source, s.code: s.text) for s in data.load_sutras()} + code_to_sutra = {(s.source, s.code): s.text for s in data.load_sutras()} v = Vyakarana() prakriyas = v.derive(Pada.Tinanta( diff --git a/vidyut-prakriya/src/args/dhatu.rs b/vidyut-prakriya/src/args/dhatu.rs index dee4456..cbcd337 100644 --- a/vidyut-prakriya/src/args/dhatu.rs +++ b/vidyut-prakriya/src/args/dhatu.rs @@ -161,24 +161,36 @@ pub enum Sanadi { impl Sanadi { /// Returns whether this *pratyaya* can be added only after subantas. - pub fn is_namadhatu(&self) -> bool { + pub fn is_namadhatu(self) -> bool { use Sanadi::*; matches!(self, kAmyac | kyaN | kyac) } /// Returns the *aupadeśika* form of this *pratyaya*. - pub(crate) fn aupadeshika(&self) -> &'static str { + pub(crate) fn aupadeshika(self) -> &'static str { self.as_str() } /// Returns the anubandhas used by this pratyaya. - pub fn anubandhas(&self) -> Vec { - if *self == Self::yaNluk { + pub fn anubandhas(self) -> Vec { + if self == Self::yaNluk { it_samjna::anubandhas_for_term((Self::yaN).into()) } else { - it_samjna::anubandhas_for_term((*self).into()) + it_samjna::anubandhas_for_term(self.into()) } } + + /// Returns the *dr̥śya* form of this *pratyaya*. + pub fn drshya(self) -> &'static str { + let term = if self == Self::yaNluk { + Sanadi::yaN.into() + } else { + self.into() + }; + let (start, end) = it_samjna::text_without_anubandhas(term); + let slice = &self.as_str()[start..end]; + slice + } } sanskrit_enum!(Sanadi, { @@ -651,6 +663,19 @@ impl DhatuBuilder { mod tests { use super::*; + #[test] + fn sanadi_drshya() { + // Test that nothing panics. + for sanadi in Sanadi::iter() { + println!("{}", sanadi.drshya()); + } + + assert_eq!(Sanadi::san.drshya(), "sa"); + assert_eq!(Sanadi::yaN.drshya(), "ya"); + assert_eq!(Sanadi::yaNluk.drshya(), "ya"); + assert_eq!(Sanadi::kAmyac.drshya(), "kAmya"); + } + #[test] fn sanadi_anubandhas() { // Tests that nothing panics. diff --git a/vidyut-prakriya/src/args/krt.rs b/vidyut-prakriya/src/args/krt.rs index e2d57f4..4aea1a2 100644 --- a/vidyut-prakriya/src/args/krt.rs +++ b/vidyut-prakriya/src/args/krt.rs @@ -216,6 +216,8 @@ pub enum BaseKrt { /// -man manin, /// -ya + ya, + /// -ya yat, /// -ana yuc, @@ -362,6 +364,7 @@ sanskrit_enum!(BaseKrt, { nan => "nan", ni => "ni", manin => "mani~n", + ya => "ya", yat => "yat", yuc => "yu~c", ra => "ra", @@ -389,16 +392,35 @@ sanskrit_enum!(BaseKrt, { impl BaseKrt { /// Returns the *aupadeśika* form of this *pratyaya*. - pub fn aupadeshika(&self) -> &'static str { + pub fn aupadeshika(self) -> &'static str { self.as_str() } + /// Returns the *dr̥śya* form of this *pratyaya*. + pub fn drshya(self) -> &'static str { + let term = Krt::Base(self).to_term(); + let (start, end) = it_samjna::text_without_anubandhas(term); + let slice = &self.as_str()[start..end]; + + if slice == "yu~" { + "ana" + } else if slice == "vu~" { + "aka" + } else if slice == "wra" { + "tra" + } else if slice == "v" { + "" + } else { + slice + } + } + /// Returns whether this krt pratyaya creates an *avyaya*. /// /// This is a convenience function for programs that generate Sanskrit words. If a *krt /// pratyaya* creates *avyaya*s, then we don't need to try creating subantas for various /// combinations of vibhakti and vacana. - pub fn is_avyaya(&self) -> bool { + pub fn is_avyaya(self) -> bool { use BaseKrt::*; matches!( self, @@ -429,7 +451,7 @@ impl BaseKrt { /// /// Specifically, two pratyayas are near duplicates if they always produce the same results, /// with the exception af accent. - pub fn is_duplicate(&self) -> bool { + pub fn is_duplicate(self) -> bool { use BaseKrt::*; matches!( self, @@ -444,8 +466,8 @@ impl BaseKrt { } /// Returns the anubandhas used by this pratyaya. - pub fn anubandhas(&self) -> Vec { - let term = Krt::Base(*self).to_term(); + pub fn anubandhas(self) -> Vec { + let term = Krt::Base(self).to_term(); it_samjna::anubandhas_for_term(term) } } @@ -478,7 +500,7 @@ impl Krt { /// We must track this explicitly so that we can "look ahead" and potentially add `-Aya` or /// other *pratyaya*s for certain *dhātu*s. For details, see the implementation of rules 3.1.28 /// - 3.1.31. - pub fn is_ardhadhatuka(&self) -> bool { + pub fn is_ardhadhatuka(self) -> bool { use BaseKrt::*; match self { Krt::Base(k) => !matches!(k, Sa | Satf | SAnac | SAnan | cAnaS | KaS), @@ -491,7 +513,7 @@ impl Krt { /// This is a convenience function for programs that generate Sanskrit words. If a *krt /// pratyaya* creates *avyaya*s, then we don't need to try creating subantas for various /// combinations of vibhakti and vacana. - pub fn is_avyaya(&self) -> bool { + pub fn is_avyaya(self) -> bool { match self { Krt::Base(b) => b.is_avyaya(), _ => false, @@ -502,15 +524,15 @@ impl Krt { /// /// This mapping is not reversible. This is because some pratyayas are in both `Base` and /// `Unadi`. - pub fn as_str(&self) -> &'static str { + pub fn as_str(self) -> &'static str { self.aupadeshika() } /// Returns the *aupadesika* form of this pratyaya. - pub fn aupadeshika(&self) -> &'static str { + pub fn aupadeshika(self) -> &'static str { match self { Krt::Base(b) => b.aupadeshika(), - Krt::Unadi(u) => u.aupadeshika(), + Krt::Unadi(unadi) => unadi.aupadeshika(), } } } @@ -724,6 +746,20 @@ impl KrdantaBuilder { mod tests { use super::*; + #[test] + fn drshya() { + // Test that nothing panics. + for krt in BaseKrt::iter() { + println!("{}", krt.drshya()); + } + + assert_eq!(BaseKrt::GaY.drshya(), "a"); + assert_eq!(BaseKrt::kasun.drshya(), "as"); + assert_eq!(BaseKrt::Rvul.drshya(), "aka"); + assert_eq!(BaseKrt::lyuw.drshya(), "ana"); + assert_eq!(BaseKrt::kvip.drshya(), ""); + } + #[test] fn anubandhas() { // Test that nothing panics. diff --git a/vidyut-prakriya/src/args/taddhita.rs b/vidyut-prakriya/src/args/taddhita.rs index b3704d3..fbd6723 100644 --- a/vidyut-prakriya/src/args/taddhita.rs +++ b/vidyut-prakriya/src/args/taddhita.rs @@ -549,13 +549,41 @@ sanskrit_enum!(Taddhita, { impl Taddhita { /// Returns the *aupadeśika* form of this *pratyaya*. - pub fn aupadeshika(&self) -> &'static str { + pub fn aupadeshika(self) -> &'static str { self.as_str() } + /// Returns the *dr̥śya* form of this *pratyaya*. + pub fn drshya(self) -> &'static str { + use Taddhita::*; + + match self { + // "āyanēyīnīyiyaḥ phaḍhakhacchaghāṁ pratyayādīnām" + Pak | PaY => "Ayana", + PiY => "Ayani", + Qak | Qa | QaY => "eya", + QakaY => "eyaka", + Qinuk => "eyin", + Qrak => "eyra", + Ka | KaY => "Ina", + Ca | CaR | Cas => "Iya", + Ga | Gac | Gan | Gas => "ina", + // ṭhasyēkaḥ + Wak | Wac | WaY | Wan | Wap | YiWa => "ika", + zwarac => "tara", + _ => { + let term = self.into(); + let (start, end) = it_samjna::text_without_anubandhas(term); + let slice = &self.as_str()[start..end]; + + slice + } + } + } + /// Returns the anubandhas used by this pratyaya. - pub fn anubandhas(&self) -> Vec { - it_samjna::anubandhas_for_term((*self).into()) + pub fn anubandhas(self) -> Vec { + it_samjna::anubandhas_for_term((self).into()) } } @@ -980,6 +1008,25 @@ mod tests { assert!(!TasyaApatyam.is_type_of(Gotra)); } + #[test] + fn drshya() { + // Test that nothing panics. + for taddhita in Taddhita::iter() { + println!("{}", taddhita.drshya()); + } + + use Taddhita as T; + assert_eq!(T::tamap.drshya(), "tama"); + assert_eq!(T::jAtIyar.drshya(), "jAtIya"); + + assert_eq!(T::Pak.drshya(), "Ayana"); + assert_eq!(T::Qa.drshya(), "eya"); + assert_eq!(T::Ka.drshya(), "Ina"); + assert_eq!(T::CaR.drshya(), "Iya"); + assert_eq!(T::Gac.drshya(), "ina"); + assert_eq!(T::Wak.drshya(), "ika"); + } + #[test] fn anubandhas() { // Tests that nothing panics. diff --git a/vidyut-prakriya/src/args/unadi.rs b/vidyut-prakriya/src/args/unadi.rs index 6b277cd..aca8569 100644 --- a/vidyut-prakriya/src/args/unadi.rs +++ b/vidyut-prakriya/src/args/unadi.rs @@ -960,7 +960,7 @@ sanskrit_enum!(Unadi, { impl Unadi { /// Returns the *aupadesika* form of this pratyaya. - pub fn aupadeshika(&self) -> &'static str { + pub fn aupadeshika(self) -> &'static str { self.as_str() } } diff --git a/vidyut-prakriya/src/ganapatha.rs b/vidyut-prakriya/src/ganapatha.rs index 3221a52..308acf4 100644 --- a/vidyut-prakriya/src/ganapatha.rs +++ b/vidyut-prakriya/src/ganapatha.rs @@ -13,6 +13,8 @@ impl Ganasutra { } } +// pub const SARVADI: GanaEntry = GanaEntry::new("sarvAdi", "1.1.27", 1, &[]); + /// 1.1.27 sarvAdIni sarvanAmAni (1) pub const SARVA_ADI: Ganasutra = Ganasutra(&[ "sarva", "viSva", "uBa", "uBaya", "qatara", "qatama", "anya", "anyatara", "itara", "tvat", diff --git a/vidyut-prakriya/src/it_samjna.rs b/vidyut-prakriya/src/it_samjna.rs index c9b231c..6e141be 100644 --- a/vidyut-prakriya/src/it_samjna.rs +++ b/vidyut-prakriya/src/it_samjna.rs @@ -343,6 +343,51 @@ pub(crate) fn anubandhas_for_term(term: Term) -> Vec { ret } +/// Helper function for public APIs that return anubandhas set on a dhatu, krt, etc. +pub(crate) fn text_without_anubandhas(term: Term) -> (usize, usize) { + let text = get_aupadeshika(&term).expect("ok"); + + let mut start = 0; + let mut end = text.len(); + let adi = text.chars().next().expect("present"); + let antya = text.chars().last().expect("present"); + + if term.is_pratyaya() { + if adi == 'z' { + // 1.3.6 + start += 1; + } else if CU_TU.contains(adi) && !is_exempt_from_cutu(&term) { + // 1.3.7 + start += 1; + } else if !term.is_taddhita() && LA_SHA_KU.contains(adi) && !is_exempt_from_lakshaku(&term) + { + // 1.3.8 + start += 1; + } + } + + // 1.3.3 + if HAL.contains(antya) { + end -= 1; + } + + // 1.3.2 + let slice = &text[start..end]; + if !matches!(slice, "yu~" | "vu~") { + for (i, c) in slice.char_indices() { + if c == '~' { + if i == 1 { + start += 2; + } else { + end -= 2; + } + } + } + } + + (start, end) +} + #[cfg(test)] mod tests { use super::*;