bindu 2.0.4 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ChangeLog CHANGED
@@ -1,3 +1,18 @@
1
+ 2026.10.19, v2.1.0
2
+
3
+ fix:
4
+ - c8c3154 sandhi: normalise test prefixes to bindu: sandhi: <plugin>:
5
+ - b3d5cd0 sandhi: add isSequenceExpression guard to prevent double-remove crash
6
+
7
+ feature:
8
+ - 2c6bb51 sandhi: visarga+hal: barrel update
9
+ - 796a1bf pratyahara: add VOICED_HAL, SIBILANT, DENTAL_STOP, PALATAL_MAP, SHORT_VOWEL
10
+ - 7cbf7e0 createSandhi: add
11
+ - a044f29 sandhi: hal: add
12
+ - 0cbeb54 coverage
13
+ - e419d2a apply-vriddhi: add a+ai a+au support, import VRIDDHI from pratyahara
14
+ - 93777f4 apply-guna: add ḷ and ā prior-vowel support via pratyahara
15
+
1
16
  2026.10.16, v2.0.4
2
17
 
3
18
  fix:
package/README.md CHANGED
@@ -62,6 +62,40 @@ places[0].message;
62
62
  - ✅[`visarga/apply-rori`](https://putout.cloudcmd.io/#/gist/839c4d49726a2f869dc2854a110001d9/f443ecb1522b2b0f8d18a86eaaf8bdfe9325eaf6): [रो रि।](https://www.learnsanskrit.org/vyakarana/sounds/hal-sandhi/#:~:text=rule%208.3.15%20below%3A-,%E0%A4%B0%E0%A5%8B%20%E0%A4%B0%E0%A4%BF%E0%A5%A4%20%E0%A5%AE.%E0%A5%A9.%E0%A5%A7%E0%A5%AA,-ro%20ri%20);
63
63
  - ✅[`visarga/apply-bhobhago`](https://putout.cloudcmd.io/#/gist/839c4d49726a2f869dc2854a110001d9/f443ecb1522b2b0f8d18a86eaaf8bdfe9325eaf6): [भोभगोअघोअपूर्वस्य योऽशि](https://ashtadhyayi.com/sutraani/8/3/17);
64
64
  - ✅[`visarga/apply-kharovasana`](https://putout.cloudcmd.io/#/gist/425cfcba3ba6dd2bb4a6692fc6fb6959/bc3fb244c2bbe03597e3ae2ab85473ae46a5f22b):[खरवसानयोर्विसर्जनीयः](https://ashtadhyayi.com/sutraani/8/3/15);
65
+ - ✅[`visarga/apply-visarga-voiced`](...): [अतो रोरप्लुतादप्लुते](https://ashtadhyayi.com/sutraani/8/3/15);
66
+ - ✅[`visarga/apply-visarga-vowel`](...): [हशि च](https://ashtadhyayi.com/sutraani/8/3/15);
67
+ - ✅[`visarga/apply-sassa`](...): [विसर्जनीयस्य सः](https://ashtadhyayi.com/sutraani/8/3/34);
68
+
69
+ #### hal
70
+
71
+ - ✅[`hal/apply-anusvara`](...): [अनुस्वारस्य ययि परसवर्णः](https://ashtadhyayi.com/sutraani/8/3/23);
72
+ - ✅[`hal/apply-jashtva`](...): [झलां जशोऽन्ते](https://ashtadhyayi.com/sutraani/8/4/53);
73
+ - ✅[`hal/apply-shchtva`](...): [ष्टुना ष्टुः](https://ashtadhyayi.com/sutraani/8/4/40);
74
+ - ✅[`hal/apply-cch`](...): [छे च](https://ashtadhyayi.com/sutraani/6/1/73);
75
+
76
+ #### apply-anusvara
77
+
78
+ ```
79
+ सम् + गच्छति → संगच्छति
80
+ saM + gacCati → saNgacCati
81
+ saṃ + gacchati → saṅgacchati
82
+
83
+ सम् + योग → संयोग
84
+ saM + yoga → saYyoga
85
+ saṃ + yoga → sañyoga
86
+ ```
87
+
88
+ #### apply-jashtva
89
+
90
+ ```
91
+ वाक् + देव → वाग्देव
92
+ vAk + deva → vAgdeva
93
+ vāk + deva → vāgdeva
94
+
95
+ तत् + अपि → तदपि
96
+ tat + api → tadapi
97
+ tata + api → tadapi
98
+ ```
65
99
 
66
100
  #### apply-padantad
67
101
 
package/lib/bindu.js CHANGED
@@ -2,6 +2,7 @@ import putout from 'putout';
2
2
  import Sanscript from '@indic-transliteration/sanscript';
3
3
  import * as acSandhi from './sandhi/ac/index.js';
4
4
  import * as visargaSandhi from './sandhi/visarga/index.js';
5
+ import * as halSandhi from './sandhi/hal/index.js';
5
6
  import {postProcess, preProcess} from './process.js';
6
7
 
7
8
  export const toSLP1 = (a) => Sanscript.t(a, 'devanagari', 'slp1');
@@ -17,6 +18,7 @@ export const bindu = (text, overrides = {}) => {
17
18
  plugins: [
18
19
  ['sandhi/ac', acSandhi],
19
20
  ['sandhi/visarga', visargaSandhi],
21
+ ['sandhi/hal', halSandhi],
20
22
  ],
21
23
  });
22
24
 
package/lib/pratyahara.js CHANGED
@@ -104,3 +104,137 @@ export const astiIk = createAsti(ik);
104
104
  export const astiEn = createAsti(en);
105
105
  export const astiEN = createAsti(EN);
106
106
  export const astiAk = createAsti(ak);
107
+
108
+ export const HAL = 'kKgGNcCjJYwWqQRtTdDnpPbBmyrlvzSsh'.split('');
109
+
110
+ export const CAR = [
111
+ 'k',
112
+ 'K',
113
+ 'c',
114
+ 'C',
115
+ 'w',
116
+ 'W',
117
+ 't',
118
+ 'T',
119
+ 'p',
120
+ 'P',
121
+ 'z',
122
+ 'S',
123
+ 's',
124
+ ];
125
+
126
+ export const JASH_MAP = {
127
+ k: 'g',
128
+ K: 'G',
129
+ c: 'j',
130
+ C: 'J',
131
+ w: 'q',
132
+ W: 'Q',
133
+ t: 'd',
134
+ T: 'D',
135
+ p: 'b',
136
+ P: 'B',
137
+ z: 'j',
138
+ S: 'q',
139
+ s: 'd',
140
+ };
141
+
142
+ export const VARGA_NASAL = {
143
+ k: 'N',
144
+ K: 'N',
145
+ g: 'N',
146
+ G: 'N',
147
+ N: 'N',
148
+ c: 'Y',
149
+ C: 'Y',
150
+ j: 'Y',
151
+ J: 'Y',
152
+ Y: 'Y',
153
+ w: 'R',
154
+ W: 'R',
155
+ q: 'R',
156
+ Q: 'R',
157
+ R: 'R',
158
+ t: 'n',
159
+ T: 'n',
160
+ d: 'n',
161
+ D: 'n',
162
+ n: 'n',
163
+ p: 'm',
164
+ P: 'm',
165
+ b: 'm',
166
+ B: 'm',
167
+ m: 'm',
168
+ y: 'Y',
169
+ v: 'm',
170
+ r: 'R',
171
+ l: 'n',
172
+ z: 'Y',
173
+ S: 'R',
174
+ s: 'n',
175
+ h: 'N',
176
+ };
177
+
178
+ export const astiHal = createAsti(HAL);
179
+ export const astiCar = (c) => CAR.includes(c);
180
+
181
+ export const VOICED_HAL = [
182
+ 'g',
183
+ 'G',
184
+ 'j',
185
+ 'J',
186
+ 'q',
187
+ 'Q',
188
+ 'd',
189
+ 'D',
190
+ 'b',
191
+ 'B',
192
+ 'n',
193
+ 'N',
194
+ 'm',
195
+ 'Y',
196
+ 'R',
197
+ 'y',
198
+ 'r',
199
+ 'l',
200
+ 'v',
201
+ 'h',
202
+ ];
203
+
204
+ export const SIBILANT = ['s', 'S', 'z'];
205
+
206
+ export const DENTAL_STOP = [
207
+ 't',
208
+ 'T',
209
+ 'd',
210
+ 'D',
211
+ 'n',
212
+ ];
213
+
214
+ export const PALATAL_STOP = [
215
+ 'c',
216
+ 'C',
217
+ 'j',
218
+ 'J',
219
+ 'Y',
220
+ ];
221
+
222
+ export const PALATAL_MAP = {
223
+ t: 'c',
224
+ T: 'C',
225
+ d: 'j',
226
+ D: 'J',
227
+ n: 'Y',
228
+ };
229
+
230
+ export const SHORT_VOWEL = [
231
+ 'a',
232
+ 'i',
233
+ 'u',
234
+ 'f',
235
+ 'x',
236
+ ];
237
+
238
+ export const astiVoicedHal = (c) => VOICED_HAL.includes(c);
239
+ export const astiSibilant = (c) => SIBILANT.includes(c);
240
+ export const astiShortVowel = (c) => SHORT_VOWEL.includes(c);
@@ -6,6 +6,22 @@ const {remove} = operator;
6
6
 
7
7
  export const report = () => `ayādayaḥ`;
8
8
 
9
+ export const when = ({last, preLast, name, nextName}) => {
10
+ if (!astiEc(last) && !astiEc(preLast + last))
11
+ return false;
12
+
13
+ if (name.endsWith('e') && !/^[aueio]/.test(nextName))
14
+ return false;
15
+
16
+ if (name.endsWith('o') && !/^[aiu]/.test(nextName))
17
+ return false;
18
+
19
+ if (name.endsWith('ai') && !/^[ai]/.test(nextName))
20
+ return false;
21
+
22
+ return !name.endsWith('au') || /^[ai]/.test(nextName);
23
+ };
24
+
9
25
  export const fix = ({path, next, name, nextName}) => {
10
26
  remove(next);
11
27
 
@@ -34,43 +50,3 @@ export const fix = ({path, next, name, nextName}) => {
34
50
  return;
35
51
  }
36
52
  };
37
-
38
- export const traverse = ({push}) => ({
39
- Identifier(path) {
40
- const next = path.getNextSibling();
41
-
42
- if (!path.parentPath.isSequenceExpression())
43
- return;
44
-
45
- if (!next.node)
46
- return;
47
-
48
- const {name} = path.node;
49
- const nextName = next.node.name;
50
-
51
- const last = name.at(-1);
52
- const preLast = name.at(-2);
53
-
54
- if (!astiEc(last) && !astiEc(preLast + last))
55
- return;
56
-
57
- if (name.endsWith('e') && !/^[aueio]/.test(nextName))
58
- return;
59
-
60
- if (name.endsWith('o') && !/^[aiu]/.test(nextName))
61
- return;
62
-
63
- if (name.endsWith('ai') && !/^[ai]/.test(nextName))
64
- return;
65
-
66
- if (name.endsWith('au') && !/^[ai]/.test(nextName))
67
- return;
68
-
69
- push({
70
- path,
71
- next,
72
- name,
73
- nextName,
74
- });
75
- },
76
- });
@@ -6,49 +6,23 @@ const {remove} = operator;
6
6
 
7
7
  export const report = () => `akaḥ savarṇe dīrghaḥ`;
8
8
 
9
+ export const when = ({last, first, nextName}) => {
10
+ if (avyayamAsti(nextName))
11
+ return false;
12
+
13
+ const lastLow = last.toLowerCase();
14
+ const firstLow = first.toLowerCase();
15
+
16
+ return astiAk(lastLow) && lastLow === firstLow;
17
+ };
18
+
19
+ export const then = ({last}) => ({
20
+ dirgha: last
21
+ .toLowerCase()
22
+ .toUpperCase(),
23
+ });
24
+
9
25
  export const fix = ({path, next, name, nextName, dirgha}) => {
10
26
  path.node.name = `${name.slice(0, -1)}${dirgha}${nextName.slice(1)}`;
11
27
  remove(next);
12
28
  };
13
-
14
- export const traverse = ({push}) => ({
15
- Identifier(path) {
16
- const next = path.getNextSibling();
17
-
18
- if (!path.parentPath.isSequenceExpression())
19
- return;
20
-
21
- if (!next.node)
22
- return;
23
-
24
- const {name} = path.node;
25
- const nextName = next.node.name;
26
-
27
- if (avyayamAsti(nextName))
28
- return;
29
-
30
- const last = name
31
- .at(-1)
32
- .toLowerCase();
33
-
34
- const first = nextName
35
- .at(0)
36
- .toLowerCase();
37
-
38
- if (!astiAk(last))
39
- return;
40
-
41
- if (last !== first)
42
- return;
43
-
44
- const dirgha = first.toUpperCase();
45
-
46
- push({
47
- path,
48
- next,
49
- dirgha,
50
- name,
51
- nextName,
52
- });
53
- },
54
- });
@@ -6,6 +6,21 @@ const {remove} = operator;
6
6
 
7
7
  export const report = () => `eco yavāyāvaḥ`;
8
8
 
9
+ export const when = ({last, preLast, first, name}) => {
10
+ if (!astiEc(last) && !astiEc(preLast + last))
11
+ return false;
12
+
13
+ if (!astiAc(first))
14
+ return false;
15
+
16
+ if (first === 'e' && first === last)
17
+ return false;
18
+
19
+ const stem = praptaPratapadika(name);
20
+
21
+ return !(stem && stem !== name);
22
+ };
23
+
9
24
  export const fix = ({path, next, name, nextName}) => {
10
25
  if (name.endsWith('e')) {
11
26
  path.node.name = `${name.slice(0, -1)}ay${nextName}`;
@@ -35,43 +50,3 @@ export const fix = ({path, next, name, nextName}) => {
35
50
  return;
36
51
  }
37
52
  };
38
-
39
- export const traverse = ({push}) => ({
40
- Identifier(path) {
41
- const next = path.getNextSibling();
42
-
43
- if (!path.parentPath.isSequenceExpression())
44
- return;
45
-
46
- if (!next.node)
47
- return;
48
-
49
- const {name} = path.node;
50
-
51
- const nextName = next.node.name;
52
- const first = nextName.at(0);
53
- const last = name.at(-1);
54
- const preLast = name.at(-2);
55
-
56
- if (!astiEc(last) && !astiEc(preLast + last))
57
- return;
58
-
59
- if (!astiAc(first))
60
- return;
61
-
62
- if (first === 'e' && first === last)
63
- return;
64
-
65
- const stem = praptaPratapadika(name);
66
-
67
- if (stem && stem !== name)
68
- return;
69
-
70
- push({
71
- path,
72
- next,
73
- name,
74
- nextName,
75
- });
76
- },
77
- });
@@ -5,42 +5,13 @@ const {remove} = operator;
5
5
 
6
6
  export const report = () => `ād‑guṇaḥ`;
7
7
 
8
+ export const when = ({last, first}) => (last === 'a' || last === 'A') && astiIk(first);
9
+
10
+ export const then = ({first}) => ({
11
+ guna: GUNA[first],
12
+ });
13
+
8
14
  export const fix = ({path, next, name, nextName, guna}) => {
9
15
  path.node.name = `${name.slice(0, -1)}${guna}${nextName.slice(1)}`;
10
16
  remove(next);
11
17
  };
12
-
13
- export const traverse = ({push}) => ({
14
- Identifier(path) {
15
- const next = path.getNextSibling();
16
-
17
- if (!path.parentPath.isSequenceExpression())
18
- return;
19
-
20
- if (!next.node)
21
- return;
22
-
23
- const {name} = path.node;
24
- const nextName = next.node.name;
25
-
26
- const last = name.at(-1);
27
-
28
- const first = nextName.at(0);
29
-
30
- if (last !== 'a' && last !== 'A')
31
- return;
32
-
33
- if (!astiIk(first))
34
- return;
35
-
36
- const guna = GUNA[first];
37
-
38
- push({
39
- path,
40
- next,
41
- guna,
42
- name,
43
- nextName,
44
- });
45
- },
46
- });
@@ -5,34 +5,9 @@ const {remove} = operator;
5
5
 
6
6
  export const report = () => `eṅaḥ padāntād ati`;
7
7
 
8
+ export const when = ({name, first}) => first === 'a' && /(a[ui]|[eo])$/.test(name);
9
+
8
10
  export const fix = ({path, next, name, nextName}) => {
9
11
  path.node.name = `${name}${avagraha.code}${nextName.slice(1)}`;
10
12
  remove(next);
11
13
  };
12
-
13
- export const traverse = ({push}) => ({
14
- Identifier(path) {
15
- const next = path.getNextSibling();
16
-
17
- if (!next.node)
18
- return;
19
-
20
- const {name} = path.node;
21
- const nextName = next.node.name;
22
-
23
- const first = nextName.at(0);
24
-
25
- if (first !== 'a')
26
- return;
27
-
28
- if (!/(a[ui]|[eo])$/.test(name))
29
- return;
30
-
31
- push({
32
- path,
33
- next,
34
- name,
35
- nextName,
36
- });
37
- },
38
- });
@@ -3,6 +3,38 @@ import {DIRGHA_MAP, YAN_MAP} from '#pratyahara';
3
3
 
4
4
  export const report = () => `ekaḥ pūrvaparayoḥ`;
5
5
 
6
+ export const when = ({last, first, nextName}) => {
7
+ if (!avyayamAsti(nextName))
8
+ return false;
9
+
10
+ if (!/[aiufxAIU]/.test(last))
11
+ return false;
12
+
13
+ const lastLow = last.toLowerCase();
14
+ const firstLow = first.toLowerCase();
15
+
16
+ if (lastLow === firstLow && DIRGHA_MAP[last])
17
+ return true;
18
+
19
+ return YAN_MAP[last] && /[aie]/.test(firstLow);
20
+ };
21
+
22
+ export const then = ({last, first}) => {
23
+ const lastLow = last.toLowerCase();
24
+ const firstLow = first.toLowerCase();
25
+
26
+ if (lastLow === firstLow)
27
+ return {
28
+ kind: 'dirgha',
29
+ dirgha: DIRGHA_MAP[last],
30
+ };
31
+
32
+ return {
33
+ kind: 'yan',
34
+ yan: YAN_MAP[last],
35
+ };
36
+ };
37
+
6
38
  export const fix = ({path, name, nextName, kind, yan, dirgha}) => {
7
39
  if (kind === 'dirgha') {
8
40
  path.node.name = `${name.slice(0, -1)}${dirgha}${nextName.slice(1)}`;
@@ -18,55 +50,3 @@ export const fix = ({path, name, nextName, kind, yan, dirgha}) => {
18
50
  return;
19
51
  }
20
52
  };
21
-
22
- export const traverse = ({push}) => ({
23
- Identifier(path) {
24
- const next = path.getNextSibling();
25
-
26
- if (!path.parentPath.isSequenceExpression())
27
- return;
28
-
29
- if (!next.node)
30
- return;
31
-
32
- const {name} = path.node;
33
- const nextName = next.node.name;
34
-
35
- if (!avyayamAsti(nextName))
36
- return;
37
-
38
- const last = name.at(-1);
39
- const lastLow = last.toLowerCase();
40
-
41
- const first = nextName
42
- .at(0)
43
- .toLowerCase();
44
-
45
- if (!/[aiufxAIU]/.test(last))
46
- return;
47
-
48
- if (lastLow === first && DIRGHA_MAP[last]) {
49
- push({
50
- path,
51
- next,
52
- name,
53
- nextName,
54
- kind: 'dirgha',
55
- dirgha: DIRGHA_MAP[last],
56
- });
57
- return;
58
- }
59
-
60
- if (YAN_MAP[last] && /[aie]/.test(first)) {
61
- push({
62
- path,
63
- next,
64
- name,
65
- nextName,
66
- kind: 'yan',
67
- yan: YAN_MAP[last],
68
- });
69
- return;
70
- }
71
- },
72
- });
@@ -9,6 +9,8 @@ const {remove} = operator;
9
9
 
10
10
  export const report = () => `vṛddhireci`;
11
11
 
12
+ export const when = ({last, first, nextName}) => (last === 'a' || last === 'A') && (astiEn(first) || astiEN(first) || /^a[iu]/.test(nextName));
13
+
12
14
  export const fix = ({path, next, name, nextName}) => {
13
15
  const first = nextName.at(0);
14
16
  let joint;
@@ -23,35 +25,3 @@ export const fix = ({path, next, name, nextName}) => {
23
25
  path.node.name = `${name}${joint}${nextName.slice(1)}`;
24
26
  remove(next);
25
27
  };
26
-
27
- export const traverse = ({push}) => ({
28
- Identifier(path) {
29
- const next = path.getNextSibling();
30
-
31
- if (!path.parentPath.isSequenceExpression())
32
- return;
33
-
34
- if (!next.node)
35
- return;
36
-
37
- const {name} = path.node;
38
- const nextName = next.node.name;
39
-
40
- const last = name.at(-1);
41
-
42
- const first = nextName.at(0);
43
-
44
- if (last !== 'a' && last !== 'A')
45
- return;
46
-
47
- if (!astiEn(first) && !astiEN(first) && !/^a[iu]/.test(nextName))
48
- return;
49
-
50
- push({
51
- path,
52
- next,
53
- name,
54
- nextName,
55
- });
56
- },
57
- });
@@ -9,42 +9,14 @@ const {remove} = operator;
9
9
 
10
10
  export const report = () => `iko yaṇaci`;
11
11
 
12
+ export const when = ({last, first}) => astiIk(last) && astiAc(first.toLowerCase());
13
+
14
+ export const then = ({last}) => ({
15
+ yan: YAN_MAP[last],
16
+ });
17
+
12
18
  export const fix = ({path, next, name, nextName, yan}) => {
13
19
  path.node.name = `${name.slice(0, -1)}${yan}${nextName}`;
14
20
 
15
21
  remove(next);
16
22
  };
17
-
18
- export const traverse = ({push}) => ({
19
- Identifier(path) {
20
- const next = path.getNextSibling();
21
-
22
- if (!path.parentPath.isSequenceExpression())
23
- return;
24
-
25
- if (!next.node)
26
- return;
27
-
28
- const {name} = path.node;
29
- const nextName = next.node.name;
30
-
31
- const last = name.at(-1);
32
-
33
- const first = nextName
34
- .at(0)
35
- .toLowerCase();
36
-
37
- if (!astiIk(last) || !astiAc(first))
38
- return;
39
-
40
- const yan = YAN_MAP[last];
41
-
42
- push({
43
- path,
44
- next,
45
- name,
46
- nextName,
47
- yan,
48
- });
49
- },
50
- });
@@ -1,3 +1,4 @@
1
+ import {createSandhi} from '#sandhi';
1
2
  import * as applyEcoYavayava from './apply-eco-yavayava/index.js';
2
3
  import * as applyPadantad from './apply-padantad/index.js';
3
4
  import * as applyAyadaya from './apply-ayadaya/index.js';
@@ -8,12 +9,12 @@ import * as applyYan from './apply-yan/index.js';
8
9
  import * as applyDirgha from './apply-dirgha/index.js';
9
10
 
10
11
  export const rules = {
11
- 'apply-dirgha': applyDirgha,
12
- 'apply-yan': applyYan,
13
- 'apply-guna': applyGuna,
14
- 'apply-vriddhi': applyVriddhi,
15
- 'apply-purvarupa': applyPurvarupa,
16
- 'apply-ayadaya': applyAyadaya,
17
- 'apply-padantad': applyPadantad,
18
- 'apply-eco-yavayava': applyEcoYavayava,
12
+ 'apply-dirgha': createSandhi(applyDirgha),
13
+ 'apply-yan': createSandhi(applyYan),
14
+ 'apply-guna': createSandhi(applyGuna),
15
+ 'apply-vriddhi': createSandhi(applyVriddhi),
16
+ 'apply-purvarupa': createSandhi(applyPurvarupa),
17
+ 'apply-ayadaya': createSandhi(applyAyadaya),
18
+ 'apply-padantad': createSandhi(applyPadantad),
19
+ 'apply-eco-yavayava': createSandhi(applyEcoYavayava),
19
20
  };
@@ -0,0 +1,41 @@
1
+ export const createSandhi = ({report, when, then, fix}) => ({
2
+ report,
3
+ fix,
4
+ traverse: ({push}) => ({
5
+ Identifier(path) {
6
+ const next = path.getNextSibling();
7
+
8
+ if (!path.parentPath.isSequenceExpression())
9
+ return;
10
+
11
+ if (!next.node)
12
+ return;
13
+
14
+ const {name} = path.node;
15
+ const nextName = next.node.name;
16
+ const last = name.at(-1);
17
+ const first = nextName.at(0);
18
+ const preLast = name.at(-2);
19
+
20
+ const ctx = {
21
+ name,
22
+ nextName,
23
+ last,
24
+ first,
25
+ preLast,
26
+ };
27
+
28
+ if (!when(ctx))
29
+ return;
30
+
31
+ const result = then ? then(ctx) : {};
32
+
33
+ push({
34
+ path,
35
+ next,
36
+ ...ctx,
37
+ ...result,
38
+ });
39
+ },
40
+ }),
41
+ });
@@ -0,0 +1,17 @@
1
+ import {operator} from 'putout';
2
+ import {astiHal, VARGA_NASAL} from '#pratyahara';
3
+
4
+ const {remove} = operator;
5
+
6
+ export const report = () => `anusvārasya yayi parasavarṇaḥ`;
7
+
8
+ export const when = ({last, first}) => (last === 'M' || last === 'm') && astiHal(first);
9
+
10
+ export const then = ({first}) => ({
11
+ vargaNasal: VARGA_NASAL[first.toLowerCase()],
12
+ });
13
+
14
+ export const fix = ({path, next, name, nextName, vargaNasal}) => {
15
+ path.node.name = `${name.slice(0, -1)}${vargaNasal}${nextName}`;
16
+ remove(next);
17
+ };
@@ -0,0 +1,13 @@
1
+ import {operator} from 'putout';
2
+ import {astiShortVowel} from '#pratyahara';
3
+
4
+ const {remove} = operator;
5
+
6
+ export const report = () => 'che ca';
7
+
8
+ export const when = ({last, first}) => astiShortVowel(last) && first === 'C';
9
+
10
+ export const fix = ({path, next, name, nextName}) => {
11
+ path.node.name = `${name}c${nextName}`;
12
+ remove(next);
13
+ };
@@ -0,0 +1,17 @@
1
+ import {operator} from 'putout';
2
+ import {CAR, JASH_MAP} from '#pratyahara';
3
+
4
+ const {remove} = operator;
5
+
6
+ export const report = () => `jhalāṃ jaśo'nte`;
7
+
8
+ export const when = ({last, first}) => CAR.includes(last) && !CAR.includes(first) && first !== 'H';
9
+
10
+ export const then = ({last}) => ({
11
+ voiced: JASH_MAP[last],
12
+ });
13
+
14
+ export const fix = ({path, next, name, nextName, voiced}) => {
15
+ path.node.name = `${name.slice(0, -1)}${voiced}${nextName}`;
16
+ remove(next);
17
+ };
@@ -0,0 +1,21 @@
1
+ import {operator} from 'putout';
2
+ import {
3
+ DENTAL_STOP,
4
+ PALATAL_STOP,
5
+ PALATAL_MAP,
6
+ } from '#pratyahara';
7
+
8
+ const {remove} = operator;
9
+
10
+ export const report = () => 'ṣṭunā ṣṭuḥ';
11
+
12
+ export const when = ({last, first}) => DENTAL_STOP.includes(last) && PALATAL_STOP.includes(first);
13
+
14
+ export const then = ({last}) => ({
15
+ palatal: PALATAL_MAP[last],
16
+ });
17
+
18
+ export const fix = ({path, next, name, nextName, palatal}) => {
19
+ path.node.name = `${name.slice(0, -1)}${palatal}${nextName}`;
20
+ remove(next);
21
+ };
@@ -0,0 +1,12 @@
1
+ import {createSandhi} from '#sandhi';
2
+ import * as applyAnusvara from './apply-anusvara/index.js';
3
+ import * as applyJashtva from './apply-jashtva/index.js';
4
+ import * as applyShchtva from './apply-shchtva/index.js';
5
+ import * as applyCch from './apply-cch/index.js';
6
+
7
+ export const rules = {
8
+ 'apply-anusvara': createSandhi(applyAnusvara),
9
+ 'apply-jashtva': createSandhi(applyJashtva),
10
+ 'apply-shchtva': createSandhi(applyShchtva),
11
+ 'apply-cch': createSandhi(applyCch),
12
+ };
@@ -11,40 +11,9 @@ const purvasya = [
11
11
 
12
12
  export const report = () => `bhobhago’gho’pūrvasya yo’śi`;
13
13
 
14
+ export const when = ({name, first}) => purvasya.includes(name) && astiAc(first.toLowerCase());
15
+
14
16
  export const fix = ({path, next, name, nextName}) => {
15
17
  path.node.name = `${name.slice(0, -2)}o'${nextName}`;
16
18
  remove(next);
17
19
  };
18
-
19
- export const traverse = ({push}) => ({
20
- Identifier(path) {
21
- const next = path.getNextSibling();
22
-
23
- if (!path.parentPath.isSequenceExpression())
24
- return;
25
-
26
- if (!next.node)
27
- return;
28
-
29
- const {name} = path.node;
30
-
31
- if (!purvasya.includes(name))
32
- return;
33
-
34
- const nextName = next.node.name;
35
-
36
- const first = nextName
37
- .at(0)
38
- .toLowerCase();
39
-
40
- if (!astiAc(first))
41
- return;
42
-
43
- push({
44
- path,
45
- next,
46
- name,
47
- nextName,
48
- });
49
- },
50
- });
@@ -2,37 +2,8 @@ import {astiKhar} from '../../../pratyahara.js';
2
2
 
3
3
  export const report = () => `kharavasānayor visarjanīyaḥ`;
4
4
 
5
+ export const when = ({last, first}) => /[sr]/.test(last) && astiKhar(first);
6
+
5
7
  export const fix = ({path, name}) => {
6
8
  path.node.name = `${name.slice(0, -1)}H`;
7
9
  };
8
-
9
- export const traverse = ({push}) => ({
10
- Identifier(path) {
11
- const next = path.getNextSibling();
12
-
13
- if (!path.parentPath.isSequenceExpression())
14
- return;
15
-
16
- if (!next.node)
17
- return;
18
-
19
- const {name} = path.node;
20
-
21
- const nextName = next.node.name;
22
- const first = nextName.at(0);
23
- const last = name.at(-1);
24
-
25
- if (!/[sr]/.test(last))
26
- return;
27
-
28
- if (!astiKhar(first))
29
- return;
30
-
31
- push({
32
- path,
33
- next,
34
- name,
35
- nextName,
36
- });
37
- },
38
- });
@@ -5,38 +5,9 @@ const {remove} = operator;
5
5
 
6
6
  export const report = () => `ro ri`;
7
7
 
8
+ export const when = ({last, first}) => last === 'H' && astiAc(first.toLowerCase());
9
+
8
10
  export const fix = ({path, next, name, nextName}) => {
9
11
  path.node.name = `${name.slice(0, -1)}r${nextName}`;
10
12
  remove(next);
11
13
  };
12
-
13
- export const traverse = ({push}) => ({
14
- Identifier(path) {
15
- const next = path.getNextSibling();
16
-
17
- if (!path.parentPath.isSequenceExpression())
18
- return;
19
-
20
- if (!next.node)
21
- return;
22
-
23
- const {name} = path.node;
24
- const nextName = next.node.name;
25
-
26
- const first = nextName
27
- .at(0)
28
- .toLowerCase();
29
-
30
- const last = name.at(-1);
31
-
32
- if (last !== 'H' || !astiAc(first))
33
- return;
34
-
35
- push({
36
- path,
37
- next,
38
- name,
39
- nextName,
40
- });
41
- },
42
- });
@@ -0,0 +1,19 @@
1
+ import {operator} from 'putout';
2
+ import {astiSibilant} from '#pratyahara';
3
+
4
+ const {remove} = operator;
5
+
6
+ export const report = () => 'visarjanīyasya saḥ';
7
+
8
+ export const when = ({last, first}) => last === 'H' && astiSibilant(first);
9
+
10
+ export const then = ({first}) => ({
11
+ sibilant: first,
12
+ });
13
+
14
+ export const fix = ({path, next, name, nextName, sibilant}) => {
15
+ const rest = sibilant === 'z' ? nextName.slice(1) : nextName;
16
+
17
+ path.node.name = `${name.slice(0, -1)}${sibilant}${rest}`;
18
+ remove(next);
19
+ };
@@ -0,0 +1,9 @@
1
+ import {astiVoicedHal} from '#pratyahara';
2
+
3
+ export const report = () => 'ato rora plutādaplute';
4
+
5
+ export const when = ({last, first, name}) => last === 'H' && name.endsWith('aH') && astiVoicedHal(first);
6
+
7
+ export const fix = ({path, name}) => {
8
+ path.node.name = `${name.slice(0, -2)}o`;
9
+ };
@@ -0,0 +1,14 @@
1
+ import {operator} from 'putout';
2
+ import {astiAc} from '#pratyahara';
3
+ import {avagraha} from '../../../symbols.js';
4
+
5
+ const {remove} = operator;
6
+
7
+ export const report = () => 'haśi ca';
8
+
9
+ export const when = ({last, first, name}) => last === 'H' && name.endsWith('aH') && astiAc(first.toLowerCase());
10
+
11
+ export const fix = ({path, next, name, nextName}) => {
12
+ path.node.name = `${name.slice(0, -2)}o${avagraha.code}${nextName.slice(1)}`;
13
+ remove(next);
14
+ };
@@ -1,9 +1,16 @@
1
+ import {createSandhi} from '#sandhi';
2
+ import * as applyVisargaVoiced from './apply-visarga-voiced/index.js';
3
+ import * as applyVisargaVowel from './apply-visarga-vowel/index.js';
1
4
  import * as applyKharovasana from './apply-kharovasana/index.js';
2
5
  import * as applyBhobhago from './apply-bhobhago/index.js';
3
6
  import * as applyRori from './apply-rori/index.js';
7
+ import * as applySassa from './apply-sassa/index.js';
4
8
 
5
9
  export const rules = {
6
- 'apply-bhobhago': applyBhobhago,
7
- 'apply-rori': applyRori,
8
- 'apply-kharovasana': applyKharovasana,
10
+ 'apply-bhobhago': createSandhi(applyBhobhago),
11
+ 'apply-rori': createSandhi(applyRori),
12
+ 'apply-kharovasana': createSandhi(applyKharovasana),
13
+ 'apply-visarga-voiced': createSandhi(applyVisargaVoiced),
14
+ 'apply-visarga-vowel': createSandhi(applyVisargaVowel),
15
+ 'apply-sassa': createSandhi(applySassa),
9
16
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bindu",
3
- "version": "2.0.4",
3
+ "version": "2.1.0",
4
4
  "description": "Pāṇini Aṣṭādhyāyī",
5
5
  "main": "lib/bindu.js",
6
6
  "bin": {
@@ -40,7 +40,9 @@
40
40
  },
41
41
  "imports": {
42
42
  "#avyaya": "./lib/avyaya/index.js",
43
- "#pratyahara": "./lib/pratyahara.js"
43
+ "#pratyahara": "./lib/pratyahara.js",
44
+ "#sandhi": "./lib/sandhi/create-sandhi.js",
45
+ "#sandhi-test": "./test/create-sandhi-test.js"
44
46
  },
45
47
  "dependencies": {
46
48
  "@indic-transliteration/sanscript": "^1.3.3",