diff --git a/Makefile b/Makefile index 6856586..d8e6c8b 100644 --- a/Makefile +++ b/Makefile @@ -28,5 +28,5 @@ docs: cargo doc --all --no-deps --open --document-private-items # Runs documentation tests for all crates in the repository. -test_doc: +test_docs: cargo test --all --doc diff --git a/README.md b/README.md index e4ee092..72558f1 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,17 @@

विद्युत्

+

Reliable infrastructure for Sanskrit software

-Vidyut provides reliable infrastructure for Sanskrit software. - -Specifically, Vidyut aims to provide performant and high-quality solutions for the -common problems that Sanskrit programmers face. Some of these problems include: - -- *Transliteration*, or conversion of Sanskrit text from one script to another. (भू → bhū) +Vidyut aims to provide performant and high-quality solutions for the common problems +that Sanskrit programmers face. Some of these problems include: - *Word generation*, or converting bases and suffixes into complete words. (भू → भवति) - *Word lookup*, or mapping a complete word back to its bases and suffixes. (भवति → भू) +- *Transliteration*, or conversion of Sanskrit text from one script to another. (भू → bhū) + - *Metrical analysis*, or understanding the meter used by a piece of Sanskrit text. - *Sandhi changes*, or applying and undoing the sound changes that occur between pieces of @@ -100,20 +99,25 @@ Python project. Once your setup is ready, you can install the `vidyut` package: ```shell -# With pip -$ pip install vidyut - # With uv $ uv add vidyut + +# With pip +$ pip install vidyut ```` You can also install directly from this repository. Doing so compiles the repository from scratch and might take several minutes, so we strongly suggest using our latest [PyPI release][pypi] instead. -``` -# The command is very slow, so pass `--verbose` to monitor its status. -pip install -e "git+https://github.com/ambuda-org/vidyut.git#egg=vidyut&subdirectory=bindings-python" --verbose +```shell +# Building from scratch is slow, so we pass `--verbose` to monitor its status. + +# With uv +$ uv add "git+https://github.com/ambuda-org/vidyut.git#subdirectory=bindings-python" --verbose + +# With pip +$ pip install -e "git+https://github.com/ambuda-org/vidyut.git#egg=vidyut&subdirectory=bindings-python" --verbose ``` We recommend using our pre-built linguistic data, which is available as a ZIP file diff --git a/bindings-python/src/prakriya/args.rs b/bindings-python/src/prakriya/args.rs index 445a378..a1aaac9 100644 --- a/bindings-python/src/prakriya/args.rs +++ b/bindings-python/src/prakriya/args.rs @@ -1193,15 +1193,15 @@ impl PyPratipadika { impl PyPratipadika { pub fn __repr__(&self) -> String { match &self.pratipadika { - Pratipadika::Basic(_) => format!( - "Pratipadika(text='{}', is_avyaya={})", - self.text, + Pratipadika::Basic(_) => { if self.pratipadika.is_avyaya() { - "True" + format!("Pratipadika(text='{}', is_avyaya=True)", self.text) + } else if self.pratipadika.is_nyap() { + format!("Pratipadika(text='{}', is_nyap=True)", self.text) } else { - "False" + format!("Pratipadika(text='{}')", self.text) } - ), + } _ => "Pratipadika(...)".to_string(), } } @@ -1230,6 +1230,26 @@ impl PyPratipadika { }) } + /// Create a new pratipadika that is treated as ending in a nyAp-pratyaya. + /// + /// `text` should be an SLP1 string. + #[staticmethod] + #[pyo3(signature = (text))] + pub fn nyap(text: String) -> PyResult { + let safe = match Slp1String::from(text.clone()) { + Ok(s) => s, + Err(_) => { + return Err(PyValueError::new_err(format!( + "{text} must be an SLP1 string." + ))) + } + }; + Ok(Self { + pratipadika: Pratipadika::nyap(safe), + text, + }) + } + /// Create a new pratipadika that is a krdanta. #[staticmethod] #[pyo3(signature = (dhatu, krt))] @@ -1266,6 +1286,12 @@ impl PyPratipadika { pub fn is_avyaya(&self) -> bool { self.pratipadika.is_avyaya() } + + /// Whether or not this pratipadika should be treated as a *nyAp-anta*. + #[getter] + pub fn is_nyap(&self) -> bool { + self.pratipadika.is_nyap() + } } impl From for PyPratipadika { diff --git a/bindings-python/test/unit/kosha/test_entries.py b/bindings-python/test/unit/kosha/test_entries.py index c182d18..eb4d510 100644 --- a/bindings-python/test/unit/kosha/test_entries.py +++ b/bindings-python/test/unit/kosha/test_entries.py @@ -95,7 +95,7 @@ def test_pratipadika_entry__dunders(): # __repr__ assert repr(rama_entry) == ( - "PratipadikaEntry.Basic(pratipadika=Pratipadika(text='rAma', is_avyaya=False), " + "PratipadikaEntry.Basic(pratipadika=Pratipadika(text='rAma'), " "lingas=[Linga.Pum])" ) @@ -198,7 +198,7 @@ def test_pada_entry__dunders(): assert repr(rama_pada) == ( "PadaEntry.Subanta(" "pratipadika_entry=PratipadikaEntry.Basic(" - "pratipadika=Pratipadika(text='rAma', is_avyaya=False), lingas=[Linga.Pum]), " + "pratipadika=Pratipadika(text='rAma'), lingas=[Linga.Pum]), " "linga=Linga.Pum, vibhakti=Vibhakti.Prathama, vacana=Vacana.Eka)" ) diff --git a/bindings-python/test/unit/prakriya/test_args.py b/bindings-python/test/unit/prakriya/test_args.py index 4383997..8ba0f93 100644 --- a/bindings-python/test/unit/prakriya/test_args.py +++ b/bindings-python/test/unit/prakriya/test_args.py @@ -129,7 +129,7 @@ def test_dhatu__dunders(): def test_pratipadika_new(): p = Pratipadika.basic("deva") - assert repr(p) == "Pratipadika(text='deva', is_avyaya=False)" + assert repr(p) == "Pratipadika(text='deva')" def test_pratipadika_new__fails_if_no_args(): @@ -156,7 +156,7 @@ def test_pratipadika__dunders(): _ = sorted([deva, eva]) # __repr__ - assert repr(deva) == "Pratipadika(text='deva', is_avyaya=False)" + assert repr(deva) == "Pratipadika(text='deva')" def test_subanta(): diff --git a/bindings-python/test/unit/prakriya/test_lib.py b/bindings-python/test/unit/prakriya/test_lib.py index 45956e1..7e7c929 100644 --- a/bindings-python/test/unit/prakriya/test_lib.py +++ b/bindings-python/test/unit/prakriya/test_lib.py @@ -229,6 +229,18 @@ def test_derive_subantas(): assert expected == actual +def test_derive_subantas_with_nyap(): + v = Vyakarana() + prakriyas = v.derive( + Pada.Subanta( + Pratipadika.nyap("nadI"), Linga.Stri, Vibhakti.Prathama, Vacana.Eka + ) + ) + expected = {"nadI"} + actual = {x.text for x in prakriyas} + assert expected == actual + + @pytest.mark.parametrize( "code,expected", [ diff --git a/vidyut-prakriya/README.md b/vidyut-prakriya/README.md index 217d848..aaacf67 100644 --- a/vidyut-prakriya/README.md +++ b/vidyut-prakriya/README.md @@ -8,9 +8,10 @@ [paper]: https://iscls.github.io/assets/files/proceedings/2024.iscls.7.pdf `vidyut-prakriya` generates Sanskrit words with their prakriyās (derivations) -according to the rules of Paninian grammar and currently implements around -2,000 rules. Our long-term goal is to provide a complete implementation of the -Ashtadhyayi. +according to the rules of traditional Sanskrit grammar. It currently implements +more than 2,000 rules from the *Aṣṭādhyāyī*, the core text of the grammatical +tradition. Our long-term goal is to provide a complete implementation of the +*Aṣṭādhyāyī*. This [crate][crate] is under active development as part of the [Ambuda][ambuda] project. If you enjoy our work and wish to contribute to it, please see the @@ -18,7 +19,8 @@ project. If you enjoy our work and wish to contribute to it, please see the Discord server][discord], where you can meet other Sanskrit programmers and enthusiasts. -An online demo is available [here][demo]. +An online demo, which also demonstrates this crate's WebAssembly bindings, is +available [here][demo]. - [Overview](#overview) - [Usage](#usage) @@ -39,30 +41,31 @@ Overview `vidyut-prakriya` has three distinguishing qualities: 1. *Fidelity*. We follow the rules of Paninian grammar as closely as possible. - Each word we return can optionally include a prakriyā that lists each rule + Each word we return can optionally include a *prakriyā* that lists each rule that was used as well as its result. -2. *Speed*. On my laptop (a 2.4GHz 8-core CPU with 64 GB of DDR4 RAM), this - crate generates almost 50,000 words per second on a single thread. All else - equal, a fast program is easier to run and test, which means that we can - produce a larger word list at a higher standard of quality. +2. *Speed*. We have paid special attention to overall performance, especially + by caching partial results. These kinds of changes make `vidyut-prakriya` + several orders of magnitude faster than other word generators. 3. *Portability*. This crate compiles to fast native code and can be bound to - most other progamming languages with a bit of effort. In particular, this - crate can be compiled to WebAssembly, which means that it can run in a - modern web browser. + most other progamming languages with a bit of effort. We provide first-class + support for Python bindings through our [vidyut][vidyut-py] Python package, + and we also maintain bindings for WebAssembly. `vidyut-prakriya` has excellent support for Sanskrit's basic word types, including *subanta*s, *tiṅanta*s, *kṛdanta*s, and *taddhitānta*s. It has moderate support for *samāsa*s and weak support for accent rules. +[vidyut-py]: https://vidyut.readthedocs.io/en/latest/ + Usage ----- `vidyut-prakriya` supports two modes of use: -### Command-line use +### As a binary The first way to use `vidyut-prakriya` is as a command-line tool for generating Sanskrit words. For example, you can generate all basic *tiṅanta*s in *kartari @@ -80,10 +83,10 @@ compile and complete within a few seconds. You can find other example commands by exploring the `Makefile` and in particular the various invocations in `create_test_files`. -### Programmatic use +### As a library -The second way to use `vidyut-prakriya` is programmatically. For example, we -can generate simple verbs like so: +The second way to use `vidyut-prakriya` is as a library in your own binaries. +For example, we can generate simple verbs like so: ```rust use vidyut_prakriya::Vyakarana; diff --git a/vidyut-prakriya/src/args/krt.rs b/vidyut-prakriya/src/args/krt.rs index b502d42..658545b 100644 --- a/vidyut-prakriya/src/args/krt.rs +++ b/vidyut-prakriya/src/args/krt.rs @@ -668,6 +668,18 @@ impl Krdanta { self.require.as_ref() } + /// Sets the prayoga to use with this krdanta. + pub fn with_prayoga(mut self, prayoga: Prayoga) -> Self { + self.prayoga = Some(prayoga); + self + } + + /// Sets the lakara to use with this krdanta. + pub fn with_lakara(mut self, lakara: Lakara) -> Self { + self.lakara = Some(lakara); + self + } + /// Sets the required value for this krdanta. pub fn with_require(mut self, s: impl AsRef) -> Self { self.require = Some(s.as_ref().to_string()); diff --git a/vidyut-prakriya/src/args/pratipadika.rs b/vidyut-prakriya/src/args/pratipadika.rs index 99acf9e..e28c7ec 100644 --- a/vidyut-prakriya/src/args/pratipadika.rs +++ b/vidyut-prakriya/src/args/pratipadika.rs @@ -23,6 +23,11 @@ impl BasicPratipadika { pub fn is_avyaya(&self) -> bool { self.is_avyaya } + + /// Returns whether this pratipadika should be treated as ending in a *nyAp pratyaya*. + pub fn is_nyap(&self) -> bool { + self.is_nyap + } } /// A nominal stem. @@ -85,6 +90,15 @@ impl Pratipadika { _ => false, } } + + /// Returns whether the pratipadika describes an avyaya. + pub fn is_nyap(&self) -> bool { + match self { + Self::Basic(b) => b.is_nyap(), + Self::Krdanta(_) => false, + _ => false, + } + } } impl TryFrom<&str> for Pratipadika { diff --git a/vidyut-prakriya/src/dhatupatha.rs b/vidyut-prakriya/src/dhatupatha.rs index a01a336..efe8afc 100644 --- a/vidyut-prakriya/src/dhatupatha.rs +++ b/vidyut-prakriya/src/dhatupatha.rs @@ -1,7 +1,5 @@ -/*! -Utility functions for working with the Dhatupatha file included in this crate. For details, see the -comments on the `Dhatupatha` struct. -*/ +//! Utility functions for working with the Dhatupatha file included in this crate. +//! For details, see the comments on the `Dhatupatha` struct. use crate::args::{Antargana, Dhatu, Gana}; use crate::core::errors::*; @@ -119,7 +117,7 @@ pub fn create_dhatu(aupadeshika: impl AsRef, gana: Gana, number: u16) -> Re } impl Dhatupatha { - /// Loads a dhatupatha from the provided TSV. + /// Loads a dhatupatha from a TSV file. /// /// This function expects a TSV with headers and at least two columns. The first column is a /// short numeric code associated with the dhatu (e.g. `"01.0001"`), and the second column is @@ -139,7 +137,7 @@ impl Dhatupatha { Self::from_text(&content) } - /// Loads a dhatupatha from the input text string. + /// Loads a dhatupatha from a TSV string. /// /// This function is best suited for environments that don't have access to an underlying file /// system, such as when running with WebAssembly. @@ -196,7 +194,7 @@ impl Dhatupatha { } } - /// Returns an iterator over this dhatupatha's contents. + /// Returns an iterator over all dhatus in the Dhatupatha. pub fn iter(&self) -> std::slice::Iter { self.0.iter() } diff --git a/vidyut-prakriya/src/vyakarana.rs b/vidyut-prakriya/src/vyakarana.rs index a8cd266..4e220a4 100644 --- a/vidyut-prakriya/src/vyakarana.rs +++ b/vidyut-prakriya/src/vyakarana.rs @@ -231,68 +231,6 @@ impl Vyakarana { /// .prayoga(Prayoga::Kartari) /// .purusha(Purusha::Prathama) /// .vacana(Vacana::Eka) - /// .build() - /// .unwrap(); - /// let prakriyas = v.derive_tinantas(&args); - /// assert_eq!(prakriyas[0].text(), "aBiBavati"); - /// # Ok::<(), Error>(()) - /// ``` - /// - /// A *tiṅanta* whose *dhātu* has one or more *sanādi-pratyaya*s: - /// - /// ``` - /// # use vidyut_prakriya::*; - /// # use vidyut_prakriya::args::*; - /// # let v = Vyakarana::new(); - /// let bobhuya = Dhatu::mula(Slp1String::from("BU")?, Gana::Bhvadi).with_sanadi(&[Sanadi::yaN]); - /// let args = Tinanta::builder() - /// .dhatu(bobhuya) - /// .lakara(Lakara::Lat) - /// .prayoga(Prayoga::Kartari) - /// .purusha(Purusha::Prathama) - /// .vacana(Vacana::Eka) - /// .build() - /// .unwrap(); - /// let prakriyas = v.derive_tinantas(&args); - /// assert_eq!(prakriyas[0].text(), "boBUyate"); - /// # Ok::<(), Error>(()) - /// ``` - /// - /// A *tiṅanta* that must use *ātmanepada*. If the *dhātu* cannot support the requested *pada*, - /// this method returns no results: - /// - /// ``` - /// # use vidyut_prakriya::*; - /// # use vidyut_prakriya::args::*; - /// # let v = Vyakarana::new(); - /// let kr = Dhatu::mula(Slp1String::from("qukf\\Y")?, Gana::Tanadi); - /// let args = Tinanta::builder() - /// .dhatu(kr) - /// .lakara(Lakara::Lat) - /// .prayoga(Prayoga::Kartari) - /// .purusha(Purusha::Prathama) - /// .vacana(Vacana::Eka) - /// .pada(DhatuPada::Atmanepada) - /// .build() - /// .unwrap(); - /// let prakriyas = v.derive_tinantas(&args); - /// assert_eq!(prakriyas[0].text(), "kurute"); - /// # Ok::<(), Error>(()) - /// ``` - /// - /// A *tiṅanta* with one or more *upasarga*s: - /// - /// ``` - /// # use vidyut_prakriya::*; - /// # use vidyut_prakriya::args::*; - /// # let v = Vyakarana::new(); - /// let abhibhu = Dhatu::mula(Slp1String::from("BU")?, Gana::Bhvadi).with_prefixes(&["aBi"]); - /// let args = Tinanta::builder() - /// .dhatu(abhibhu) - /// .lakara(Lakara::Lat) - /// .prayoga(Prayoga::Kartari) - /// .purusha(Purusha::Prathama) - /// .vacana(Vacana::Eka) /// .build()?; /// let prakriyas = v.derive_tinantas(&args); /// assert_eq!(prakriyas[0].text(), "aBiBavati"); @@ -367,6 +305,8 @@ impl Vyakarana { /// /// ### Example /// + /// A basic subanta: + /// /// ``` /// # use vidyut_prakriya::*; /// # use vidyut_prakriya::args::*; @@ -392,7 +332,7 @@ impl Vyakarana { /// /// ### Example /// - /// Using a basic *kṛt pratyaya*: + /// A basic *kṛt pratyaya*: /// /// ``` /// # use vidyut_prakriya::*; @@ -405,7 +345,22 @@ impl Vyakarana { /// # Ok::<(), Error>(()) /// ``` /// - /// Using an *uṇādi pratyaya*: + /// A basic *kṛt pratyaya* with a specific *prayoga* and *lakāra*: + /// + /// ``` + /// # use vidyut_prakriya::*; + /// # use vidyut_prakriya::args::*; + /// let v = Vyakarana::new(); + /// let dhatu = Dhatu::mula(Slp1String::from("BU")?, Gana::Bhvadi); + /// let args = Krdanta::new(dhatu, BaseKrt::Satf) + /// .with_prayoga(Prayoga::Kartari) + /// .with_lakara(Lakara::Lat); + /// let prakriyas = v.derive_krdantas(&args); + /// assert_eq!(prakriyas[0].text(), "Bavat"); + /// # Ok::<(), Error>(()) + /// ``` + /// + /// An *uṇādi pratyaya*: /// /// ``` /// # use vidyut_prakriya::*;