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::*;