abhedya_chhandas/lib.rs
1
2// Basic mappings for Sanskrit Phonemes to integers
3// Consonants (Vyambjana) - 33 Standard
4const CONSONANTS: [&str; 33] = [
5 "k", "kh", "g", "gh", "ṅ",
6 "c", "ch", "j", "jh", "ñ",
7 "ṭ", "ṭh", "ḍ", "ḍh", "ṇ",
8 "t", "th", "d", "dh", "n",
9 "p", "ph", "b", "bh", "m",
10 "y", "r", "l", "v",
11 "ś", "ṣ", "s", "h"
12];
13
14// Matras (Svara) - 16 Standard
15// Short (Laghu): a, i, u, ṛ, ḷ
16// Long (Guru): ā, ī, ū, ṝ, ḹ, e, ai, o, au, aṃ, aḥ
17const MATRAS: [&str; 16] = [
18 "a", "ā", "i", "ī", "u", "ū", "ṛ", "ṝ",
19 "ḷ", "ḹ", "e", "ai", "o", "au", "aṃ", "aḥ"
20];
21
22#[derive(Debug, Clone, Copy, PartialEq)]
23pub enum MatraWeight {
24 Laghu, // Short
25 Guru, // Long
26}
27
28
29
30#[derive(Debug, Clone, PartialEq)]
31pub struct Syllable {
32 pub value: u16, // The integer value in Z_q
33}
34
35impl Syllable {
36 pub fn new(val: u16) -> self::Syllable {
37 Syllable { value: val }
38 }
39
40 pub fn to_sanskrit(&self) -> String {
41 let v = self.value as usize;
42 let c_idx = v / 16;
43 let m_idx = v % 16;
44
45 // Use modulo to wrap around for larger Q values to maintain uniform distribution across consonants
46 let c = CONSONANTS[c_idx % 33];
47
48 // Balanced Mapping for Crypto Uniformity:
49 // Indices 0..8 (8 total) -> Map to Laghu (size 5)
50 // Indices 8..16 (8 total) -> Map to Guru (size 11)
51 let m_str = if m_idx < 8 {
52 // Weights (0-7): Laghu
53 // Cycle through the 5 Laghus: a, i, u, r, l (indices 0, 2, 4, 6, 8 in original array)
54 let lags = [0, 2, 4, 6, 8];
55 MATRAS[lags[m_idx % 5]]
56 } else {
57 // Weights (8-15): Guru
58 // Cycle through the 11 Gurus
59 let gurs = [1, 3, 5, 7, 9, 10, 11, 12, 13, 14, 15];
60 MATRAS[gurs[(m_idx - 8) % 11]]
61 };
62
63 format!("{}{}", c, m_str)
64 }
65}
66
67pub fn get_matra_weight(idx: usize) -> MatraWeight {
68 if idx < 8 {
69 MatraWeight::Laghu
70 } else {
71 MatraWeight::Guru
72 }
73}
74
75pub fn encode_vector(vec: &[u16], use_meter: bool) -> String {
76 let mut result = Vec::new();
77 // In Chhandas (Meter) mode, we use the 'break' (Yati) as a dimension separator.
78 // Standard Anushtubh meter has 4 quarters (padas) of 8 syllables.
79 // For PoC, we can insert a danda '।' every 8 syllables if meter is enabled.
80
81 for (i, val) in vec.iter().enumerate() {
82 result.push(Syllable::new(*val).to_sanskrit());
83 if use_meter && (i + 1) % 8 == 0 {
84 result.push("।".to_string());
85 }
86 }
87 result.join(" ")
88}
89
90pub fn decode_verse(verse: &str) -> Vec<u16> {
91 // This is a naive decoder for the PoC
92 // It assumes space separation and exact matches
93 // In a real system, we'd have a robust parser
94
95 verse.split_whitespace()
96 .map(|s| {
97 // Reverse lookup fallback
98 // This is O(N) basic search, okay for PoC
99 let mut val = 0;
100 // Try to match easiest way: iterate all combos
101 for c in 0..33 {
102 for m_idx in 0..16 {
103 let candidate_val = (c * 16 + m_idx) as u16;
104 let built = Syllable::new(candidate_val).to_sanskrit();
105 if built == s {
106 return candidate_val;
107 }
108 }
109 }
110 0 // Fail safe
111 })
112 .collect()
113}