bindu 2.0.3 → 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,28 @@
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
+
16
+ 2026.10.16, v2.0.4
17
+
18
+ fix:
19
+ - d19c008 sandhi: normalise test prefixes to bindu: sandhi: <plugin>:
20
+ - 4ec54ef sandhi: add isSequenceExpression guard to prevent double-remove crash
21
+
22
+ feature:
23
+ - 5ba9ed0 apply-vriddhi: add a+ai a+au support, import VRIDDHI from pratyahara
24
+ - f76ffe2 apply-guna: add ḷ and ā prior-vowel support via pratyahara
25
+
1
26
  2026.10.15, v2.0.3
2
27
 
3
28
  feature:
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
@@ -14,6 +14,38 @@ const ac = [
14
14
  ...ec,
15
15
  ];
16
16
 
17
+ const ik = [
18
+ 'i',
19
+ 'I',
20
+ 'u',
21
+ 'U',
22
+ 'f',
23
+ 'F',
24
+ 'x',
25
+ ];
26
+
27
+ const en = [
28
+ 'e',
29
+ 'o',
30
+ ];
31
+
32
+ const EN = [
33
+ 'E',
34
+ 'O',
35
+ ];
36
+
37
+ const ak = [
38
+ 'a',
39
+ 'A',
40
+ 'i',
41
+ 'I',
42
+ 'u',
43
+ 'U',
44
+ 'f',
45
+ 'F',
46
+ 'x',
47
+ ];
48
+
17
49
  const khar = [
18
50
  'k',
19
51
  'c',
@@ -26,6 +58,183 @@ const khar = [
26
58
 
27
59
  const createAsti = (a) => (b) => a.includes(b.toLowerCase());
28
60
 
61
+ export const GUNA = {
62
+ i: 'e',
63
+ I: 'e',
64
+ u: 'o',
65
+ U: 'o',
66
+ f: 'ar',
67
+ F: 'ar',
68
+ x: 'al',
69
+ };
70
+
71
+ export const VRIDDHI = {
72
+ e: 'E',
73
+ o: 'O',
74
+ E: 'E',
75
+ O: 'O',
76
+ };
77
+
78
+ export const YAN_MAP = {
79
+ i: 'y',
80
+ I: 'y',
81
+ u: 'v',
82
+ U: 'v',
83
+ f: 'r',
84
+ F: 'r',
85
+ x: 'l',
86
+ };
87
+
88
+ export const DIRGHA_MAP = {
89
+ a: 'A',
90
+ A: 'A',
91
+ i: 'I',
92
+ I: 'I',
93
+ u: 'U',
94
+ U: 'U',
95
+ f: 'F',
96
+ F: 'F',
97
+ x: 'x',
98
+ };
99
+
29
100
  export const astiAc = createAsti(ac);
30
101
  export const astiEc = createAsti(ec);
31
102
  export const astiKhar = createAsti(khar);
103
+ export const astiIk = createAsti(ik);
104
+ export const astiEn = createAsti(en);
105
+ export const astiEN = createAsti(EN);
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);
@@ -1,10 +1,27 @@
1
1
  import {operator} from 'putout';
2
+ import {astiEc} from '#pratyahara';
2
3
  import {avagraha} from '../../../symbols.js';
3
4
 
4
5
  const {remove} = operator;
5
6
 
6
7
  export const report = () => `ayādayaḥ`;
7
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
+
8
25
  export const fix = ({path, next, name, nextName}) => {
9
26
  remove(next);
10
27
 
@@ -33,37 +50,3 @@ export const fix = ({path, next, name, nextName}) => {
33
50
  return;
34
51
  }
35
52
  };
36
-
37
- export const traverse = ({push}) => ({
38
- Identifier(path) {
39
- const next = path.getNextSibling();
40
-
41
- if (!next.node)
42
- return;
43
-
44
- const {name} = path.node;
45
- const nextName = next.node.name;
46
-
47
- if (!/([eo]|a[iu])$/.test(name))
48
- return;
49
-
50
- if (name.endsWith('e') && !/^[aueio]/.test(nextName))
51
- return;
52
-
53
- if (name.endsWith('o') && !/^[aiu]/.test(nextName))
54
- return;
55
-
56
- if (name.endsWith('ai') && !/^[ai]/.test(nextName))
57
- return;
58
-
59
- if (name.endsWith('au') && !/^[ai]/.test(nextName))
60
- return;
61
-
62
- push({
63
- path,
64
- next,
65
- name,
66
- nextName,
67
- });
68
- },
69
- });
@@ -1,47 +1,28 @@
1
1
  import {operator} from 'putout';
2
2
  import {avyayamAsti} from '#avyaya';
3
+ import {astiAk} from '#pratyahara';
3
4
 
4
5
  const {remove} = operator;
5
6
 
6
7
  export const report = () => `akaḥ savarṇe dīrghaḥ`;
7
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
+
8
25
  export const fix = ({path, next, name, nextName, dirgha}) => {
9
26
  path.node.name = `${name.slice(0, -1)}${dirgha}${nextName.slice(1)}`;
10
27
  remove(next);
11
28
  };
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
- if (avyayamAsti(nextName))
24
- return;
25
-
26
- const last = name
27
- .at(-1)
28
- .toLowerCase();
29
-
30
- const first = nextName
31
- .at(0)
32
- .toLowerCase();
33
-
34
- if (last !== first)
35
- return;
36
-
37
- const dirgha = first.toUpperCase();
38
-
39
- push({
40
- path,
41
- next,
42
- dirgha,
43
- name,
44
- nextName,
45
- });
46
- },
47
- });
@@ -1,11 +1,26 @@
1
1
  import {operator} from 'putout';
2
- import {astiAc, astiEc} from '../../../pratyahara.js';
2
+ import {astiEc, astiAc} from '#pratyahara';
3
3
  import {praptaPratapadika} from '../../../pratipadika.js';
4
4
 
5
5
  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,40 +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 (!next.node)
44
- return;
45
-
46
- const {name} = path.node;
47
-
48
- const nextName = next.node.name;
49
- const first = nextName.at(0);
50
- const last = name.at(-1);
51
- const preLast = name.at(-2);
52
-
53
- if (!astiEc(last) && !astiEc(preLast + last))
54
- return;
55
-
56
- if (!astiAc(first))
57
- return;
58
-
59
- if (first === 'e' && first === last)
60
- return;
61
-
62
- const stem = praptaPratapadika(name);
63
-
64
- if (stem && stem !== name)
65
- return;
66
-
67
- push({
68
- path,
69
- next,
70
- name,
71
- nextName,
72
- });
73
- },
74
- });
@@ -1,50 +1,17 @@
1
1
  import {operator} from 'putout';
2
+ import {GUNA, astiIk} from '#pratyahara';
2
3
 
3
4
  const {remove} = operator;
4
- const SANDHI = {
5
- i: 'e',
6
- u: 'o',
7
- f: 'ar',
8
- };
9
-
10
- const isSandhiVowel = (c) => Object.hasOwn(SANDHI, c);
11
5
 
12
6
  export const report = () => `ād‑guṇaḥ`;
13
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
+
14
14
  export const fix = ({path, next, name, nextName, guna}) => {
15
15
  path.node.name = `${name.slice(0, -1)}${guna}${nextName.slice(1)}`;
16
16
  remove(next);
17
17
  };
18
-
19
- export const traverse = ({push}) => ({
20
- Identifier(path) {
21
- const next = path.getNextSibling();
22
-
23
- if (!next.node)
24
- return;
25
-
26
- const {name} = path.node;
27
- const nextName = next.node.name;
28
-
29
- const last = name
30
- .at(-1)
31
- .toLowerCase();
32
-
33
- const first = nextName
34
- .at(0)
35
- .toLowerCase();
36
-
37
- if (last !== 'a' || !isSandhiVowel(first))
38
- return;
39
-
40
- const guna = SANDHI[first];
41
-
42
- push({
43
- path,
44
- next,
45
- guna,
46
- name,
47
- nextName,
48
- });
49
- },
50
- });
@@ -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
- });
@@ -1,25 +1,38 @@
1
1
  import {avyayamAsti} from '#avyaya';
2
+ import {DIRGHA_MAP, YAN_MAP} from '#pratyahara';
2
3
 
3
4
  export const report = () => `ekaḥ pūrvaparayoḥ`;
4
5
 
5
- const DIRGHA = {
6
- a: 'A',
7
- A: 'A',
8
- i: 'I',
9
- I: 'I',
10
- u: 'U',
11
- U: 'U',
12
- f: 'F',
13
- x: 'X',
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);
14
20
  };
15
21
 
16
- const YAN = {
17
- i: 'y',
18
- I: 'y',
19
- u: 'v',
20
- U: 'v',
21
- f: 'r',
22
- x: 'l',
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
+ };
23
36
  };
24
37
 
25
38
  export const fix = ({path, name, nextName, kind, yan, dirgha}) => {
@@ -37,55 +50,3 @@ export const fix = ({path, name, nextName, kind, yan, dirgha}) => {
37
50
  return;
38
51
  }
39
52
  };
40
-
41
- export const traverse = ({push}) => ({
42
- Identifier(path) {
43
- const next = path.getNextSibling();
44
-
45
- if (!path.parentPath.isSequenceExpression())
46
- return;
47
-
48
- if (!next.node)
49
- return;
50
-
51
- const {name} = path.node;
52
- const nextName = next.node.name;
53
-
54
- if (!avyayamAsti(nextName))
55
- return;
56
-
57
- const last = name.at(-1);
58
- const lastLow = last.toLowerCase();
59
-
60
- const first = nextName
61
- .at(0)
62
- .toLowerCase();
63
-
64
- if (!/[aiufxAIU]/.test(last))
65
- return;
66
-
67
- if (lastLow === first && DIRGHA[last]) {
68
- push({
69
- path,
70
- next,
71
- name,
72
- nextName,
73
- kind: 'dirgha',
74
- dirgha: DIRGHA[last],
75
- });
76
- return;
77
- }
78
-
79
- if (YAN[last] && /[aie]/.test(first)) {
80
- push({
81
- path,
82
- next,
83
- name,
84
- nextName,
85
- kind: 'yan',
86
- yan: YAN[last],
87
- });
88
- return;
89
- }
90
- },
91
- });
@@ -1,59 +1,27 @@
1
1
  import {operator} from 'putout';
2
+ import {
3
+ VRIDDHI,
4
+ astiEn,
5
+ astiEN,
6
+ } from '#pratyahara';
2
7
 
3
8
  const {remove} = operator;
4
9
 
5
10
  export const report = () => `vṛddhireci`;
6
11
 
12
+ export const when = ({last, first, nextName}) => (last === 'a' || last === 'A') && (astiEn(first) || astiEN(first) || /^a[iu]/.test(nextName));
13
+
7
14
  export const fix = ({path, next, name, nextName}) => {
8
- const joint = getJoint(nextName);
15
+ const first = nextName.at(0);
16
+ let joint;
17
+
18
+ if (first === 'e')
19
+ joint = 'i';
20
+ else if (first === 'o')
21
+ joint = 'u';
22
+ else
23
+ joint = VRIDDHI[first] || '';
9
24
 
10
25
  path.node.name = `${name}${joint}${nextName.slice(1)}`;
11
26
  remove(next);
12
27
  };
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
- const last = name
28
- .at(-1)
29
- .toLowerCase();
30
-
31
- const [first] = nextName;
32
-
33
- if (last !== 'a')
34
- return;
35
-
36
- if (!/[oe]/.test(first) && !/^a[iu]/.test(nextName))
37
- return;
38
-
39
- push({
40
- path,
41
- next,
42
- name,
43
- nextName,
44
- });
45
- },
46
- });
47
-
48
- function getJoint([first, second]) {
49
- if (first === 'e')
50
- return 'i';
51
-
52
- if (second === 'i')
53
- return '';
54
-
55
- if (second === 'u')
56
- return '';
57
-
58
- return 'u';
59
- }
@@ -1,67 +1,22 @@
1
1
  import {operator} from 'putout';
2
+ import {
3
+ YAN_MAP,
4
+ astiIk,
5
+ astiAc,
6
+ } from '#pratyahara';
2
7
 
3
8
  const {remove} = operator;
4
- const {keys} = Object;
5
9
 
6
- const SANDHI = {
7
- i: 'y',
8
- u: 'v',
9
- f: 'r',
10
- x: 'l',
11
- };
12
-
13
- const YAN = keys(SANDHI);
10
+ export const report = () => `iko yaṇaci`;
14
11
 
15
- const SVARA = [
16
- 'a',
17
- 'i',
18
- 'u',
19
- 'f',
20
- 'x',
21
- 'e',
22
- 'o',
23
- ];
12
+ export const when = ({last, first}) => astiIk(last) && astiAc(first.toLowerCase());
24
13
 
25
- export const report = () => `iko yaṇaci`;
14
+ export const then = ({last}) => ({
15
+ yan: YAN_MAP[last],
16
+ });
26
17
 
27
18
  export const fix = ({path, next, name, nextName, yan}) => {
28
19
  path.node.name = `${name.slice(0, -1)}${yan}${nextName}`;
29
20
 
30
21
  remove(next);
31
22
  };
32
-
33
- export const traverse = ({push}) => ({
34
- Identifier(path) {
35
- const next = path.getNextSibling();
36
-
37
- if (!next.node)
38
- return;
39
-
40
- const {name} = path.node;
41
- const nextName = next.node.name;
42
-
43
- const last = name
44
- .at(-1)
45
- .toLowerCase();
46
-
47
- const first = nextName
48
- .at(0)
49
- .toLowerCase();
50
-
51
- if (!SVARA.includes(first))
52
- return;
53
-
54
- if (!YAN.includes(last))
55
- return;
56
-
57
- const yan = SANDHI[last];
58
-
59
- push({
60
- path,
61
- next,
62
- name,
63
- nextName,
64
- yan,
65
- });
66
- },
67
- });
@@ -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,37 +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 (!next.node)
24
- return;
25
-
26
- const {name} = path.node;
27
-
28
- if (!purvasya.includes(name))
29
- return;
30
-
31
- const nextName = next.node.name;
32
-
33
- const first = nextName
34
- .at(0)
35
- .toLowerCase();
36
-
37
- if (!astiAc(first))
38
- return;
39
-
40
- push({
41
- path,
42
- next,
43
- name,
44
- nextName,
45
- });
46
- },
47
- });
@@ -2,34 +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 (!next.node)
14
- return;
15
-
16
- const {name} = path.node;
17
-
18
- const nextName = next.node.name;
19
- const first = nextName.at(0);
20
- const last = name.at(-1);
21
-
22
- if (!/[sr]/.test(last))
23
- return;
24
-
25
- if (!astiKhar(first))
26
- return;
27
-
28
- push({
29
- path,
30
- next,
31
- name,
32
- nextName,
33
- });
34
- },
35
- });
@@ -5,35 +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 (!next.node)
18
- return;
19
-
20
- const {name} = path.node;
21
- const nextName = next.node.name;
22
-
23
- const first = nextName
24
- .at(0)
25
- .toLowerCase();
26
-
27
- const last = name.at(-1);
28
-
29
- if (last !== 'H' || !astiAc(first))
30
- return;
31
-
32
- push({
33
- path,
34
- next,
35
- name,
36
- nextName,
37
- });
38
- },
39
- });
@@ -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.3",
3
+ "version": "2.1.0",
4
4
  "description": "Pāṇini Aṣṭādhyāyī",
5
5
  "main": "lib/bindu.js",
6
6
  "bin": {
@@ -39,7 +39,10 @@
39
39
  "node": ">=22"
40
40
  },
41
41
  "imports": {
42
- "#avyaya": "./lib/avyaya/index.js"
42
+ "#avyaya": "./lib/avyaya/index.js",
43
+ "#pratyahara": "./lib/pratyahara.js",
44
+ "#sandhi": "./lib/sandhi/create-sandhi.js",
45
+ "#sandhi-test": "./test/create-sandhi-test.js"
43
46
  },
44
47
  "dependencies": {
45
48
  "@indic-transliteration/sanscript": "^1.3.3",