ipa-core 1.2.1 → 1.3.1
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/README.md +0 -0
- package/core.js +1 -1
- package/dictionary.js +105 -0
- package/fill-orthography.js +2 -2
- package/generate-config.js +37 -15
- package/generate-conlang.js +37 -0
- package/get-definitions.js +41 -0
- package/index.js +5 -1
- package/modifiers.js +39 -52
- package/package.json +1 -1
- package/parser.js +102 -23
- package/rules.js +82 -24
- package/test.js +60 -14
package/README.md
CHANGED
|
Binary file
|
package/core.js
CHANGED
|
@@ -95,7 +95,7 @@ const core = {
|
|
|
95
95
|
'ʢ': { type: "consonant", features: { manner: "fricative/approximant", place: "epiglottal", voicing: "voiced" }, airstream: "pulmonic" },
|
|
96
96
|
'ʡ': { type: "consonant", features: { manner: "plosive", place: "epiglottal", voicing: "voiceless" }, airstream: "pulmonic" },
|
|
97
97
|
//coarticulated
|
|
98
|
-
'ɧ': { type: "consonant", coarticulated: true, components: ["ʃ", "x"] },
|
|
98
|
+
// 'ɧ': { type: "consonant", coarticulated: true, components: ["ʃ", "x"] },
|
|
99
99
|
//vowels
|
|
100
100
|
//front
|
|
101
101
|
'i': { type: "vowel", features: { height: "close", backness: "front", rounding: "unrounded" }, airstream: "pulmonic" },
|
package/dictionary.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const dictionary = {
|
|
2
|
+
// AIRSTREAM
|
|
3
|
+
pulmonic: "Use airflow from the lungs to produce the sound",
|
|
4
|
+
"glottalic ingressive": "Draw air inward using the glottis mechanism",
|
|
5
|
+
"lingual ingressive": "Draw air inward using the tongue (click mechanism)",
|
|
6
|
+
ejective: "Release a burst of air using a glottalic upward push",
|
|
7
|
+
|
|
8
|
+
// MANNER
|
|
9
|
+
plosive: "Completely block airflow, build pressure, then release it in a burst",
|
|
10
|
+
nasal: "Lower the velum so air flows through the nose",
|
|
11
|
+
trill: "Allow an articulator to vibrate rapidly in the airstream",
|
|
12
|
+
"tap/flap": "Make a single quick strike of an articulator against its contact point",
|
|
13
|
+
fricative: "Narrow the airflow channel to create friction noise",
|
|
14
|
+
approximant: "Shape the mouth for sound without creating turbulent airflow",
|
|
15
|
+
"lateral fricative": "Direct airflow along the sides of the tongue while creating friction",
|
|
16
|
+
"lateral approximant": "Allow air to flow smoothly along the sides of the tongue",
|
|
17
|
+
click: "Create a suction pocket in the mouth and release it sharply",
|
|
18
|
+
|
|
19
|
+
// PLACE
|
|
20
|
+
bilabial: "Bring both lips together",
|
|
21
|
+
labiodental: "Place lower lip against upper teeth",
|
|
22
|
+
dental: "Place the tongue against the teeth",
|
|
23
|
+
alveolar: "Place the tongue at the alveolar ridge just behind the teeth",
|
|
24
|
+
postalveolar: "Place the tongue just behind the alveolar ridge",
|
|
25
|
+
retroflex: "Curl the tongue tip backward toward the palate",
|
|
26
|
+
palatal: "Raise the middle of the tongue to the hard palate",
|
|
27
|
+
velar: "Raise the back of the tongue to the soft palate",
|
|
28
|
+
uvular: "Raise the back of the tongue toward the uvula",
|
|
29
|
+
pharyngeal: "Constrict the throat in the pharynx area",
|
|
30
|
+
glottal: "Use the vocal folds as the place of articulation",
|
|
31
|
+
epiglottal: "Use the epiglottis to constrict airflow",
|
|
32
|
+
|
|
33
|
+
// COMPLEX PLACE TYPES
|
|
34
|
+
"alveolo-palatal": "Position the tongue between the alveolar ridge and hard palate",
|
|
35
|
+
"labial-velar": "Simultaneously use the lips and the soft palate",
|
|
36
|
+
|
|
37
|
+
// VOICING
|
|
38
|
+
voiceless: "Do not vibrate the vocal cords",
|
|
39
|
+
voiced: "Vibrate the vocal cords",
|
|
40
|
+
|
|
41
|
+
// VOWEL HEIGHT
|
|
42
|
+
close: "Raise the tongue high in the mouth",
|
|
43
|
+
"near-close": "Raise the tongue slightly lower than close vowels",
|
|
44
|
+
"close-mid": "Position the tongue between close and mid height",
|
|
45
|
+
mid: "Keep the tongue in a neutral height position",
|
|
46
|
+
"open-mid": "Lower the tongue between mid and open",
|
|
47
|
+
"near-open": "Lower the tongue slightly above fully open",
|
|
48
|
+
open: "Lower the tongue as far as possible",
|
|
49
|
+
|
|
50
|
+
// VOWEL BACKNESS
|
|
51
|
+
front: "Position the tongue toward the front of the mouth",
|
|
52
|
+
central: "Position the tongue in the center of the mouth",
|
|
53
|
+
back: "Position the tongue toward the back of the mouth",
|
|
54
|
+
|
|
55
|
+
// ROUNDING
|
|
56
|
+
rounded: "Round the lips",
|
|
57
|
+
unrounded: "Keep the lips relaxed and unrounded",
|
|
58
|
+
|
|
59
|
+
// LENGTH
|
|
60
|
+
"extra-short": "Produce the sound with very short duration",
|
|
61
|
+
"half-long": "Hold the sound slightly longer than normal",
|
|
62
|
+
long: "Hold the sound for an extended duration",
|
|
63
|
+
|
|
64
|
+
// TONE
|
|
65
|
+
high: "Produce the sound with a high pitch",
|
|
66
|
+
low: "Produce the sound with a low pitch",
|
|
67
|
+
rising: "Start low and move to a higher pitch",
|
|
68
|
+
falling: "Start high and move to a lower pitch",
|
|
69
|
+
"extra-high": "Produce the sound with a very high pitch",
|
|
70
|
+
"extra-low": "Produce the sound with a very low pitch",
|
|
71
|
+
|
|
72
|
+
// TONE MODIFIERS
|
|
73
|
+
downstep: "Lower pitch relative to the previous tone",
|
|
74
|
+
upstep: "Raise pitch relative to the previous tone",
|
|
75
|
+
|
|
76
|
+
// RELEASE
|
|
77
|
+
none: "Do not release the closure audibly",
|
|
78
|
+
|
|
79
|
+
// SECONDARY ARTICULATION
|
|
80
|
+
aspirated: "Release the sound with a strong burst of air",
|
|
81
|
+
palatalized: "Raise the tongue toward the hard palate during articulation",
|
|
82
|
+
velarized: "Raise the back of the tongue toward the soft palate",
|
|
83
|
+
labialized: "Round the lips during articulation",
|
|
84
|
+
pharyngealized: "Constrict the pharynx during articulation",
|
|
85
|
+
|
|
86
|
+
// PHONATION
|
|
87
|
+
creaky_voice: "Use irregular vocal fold vibration",
|
|
88
|
+
breathy_voice: "Allow extra airflow through loosely vibrating vocal folds",
|
|
89
|
+
|
|
90
|
+
// NASALIZATION / RHOTICITY
|
|
91
|
+
nasalized: "Lower the velum so air escapes through the nose during articulation",
|
|
92
|
+
rhoticity: "Add an r-colored quality to the sound",
|
|
93
|
+
|
|
94
|
+
// CONSONANT/VOWEL TYPES
|
|
95
|
+
consonant: "Produce a sound with significant constriction of airflow",
|
|
96
|
+
vowel: "Produce a sound without significant obstruction of airflow",
|
|
97
|
+
|
|
98
|
+
// SEQUENCE TYPES
|
|
99
|
+
affricate: "Begin with a complete stop, then release into a fricative",
|
|
100
|
+
diphthong: "Move smoothly from one vowel position to another within a single syllable",
|
|
101
|
+
syllable: "Produce a single rhythmic unit centered around a vowel",
|
|
102
|
+
coarticulation: "Overlap articulations between adjacent sounds"
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
module.exports = dictionary;
|
package/fill-orthography.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// fill-orthography.
|
|
2
|
+
// fill-orthography.cjs
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
@@ -34,7 +34,7 @@ if (!Array.isArray(alphabet) || alphabet.length === 0) {
|
|
|
34
34
|
// -----------------------------
|
|
35
35
|
// GENERATE orthography OBJECT STRING
|
|
36
36
|
// -----------------------------
|
|
37
|
-
const orthographyLines = alphabet.map(letter => `
|
|
37
|
+
const orthographyLines = alphabet.map(letter => ` // "${letter}": ["", []],`);
|
|
38
38
|
const orthographyString = `const orthography = {\n${orthographyLines.join('\n')}\n};`;
|
|
39
39
|
|
|
40
40
|
// -----------------------------
|
package/generate-config.js
CHANGED
|
@@ -38,9 +38,9 @@ function generateIPASection(core) {
|
|
|
38
38
|
const grouped = groupByType(core);
|
|
39
39
|
const sections = Object.entries(grouped).map(([type, symbols]) => {
|
|
40
40
|
symbols.sort((a, b) => a.localeCompare(b));
|
|
41
|
-
return [`// ${type.toUpperCase()}`, wrapSymbols(symbols)
|
|
41
|
+
return [`// ${type.toUpperCase()}`, wrapSymbols(symbols)].join('\n');
|
|
42
42
|
});
|
|
43
|
-
return ['/**', ' * IPA SYMBOL REFERENCE', ' * Copy/paste symbols as needed', '', ...sections].join('\n');
|
|
43
|
+
return ['/**', ' * IPA SYMBOL REFERENCE', ' * Copy/paste symbols as needed', ' */', ...sections].join('\n');
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// -----------------------------
|
|
@@ -73,7 +73,26 @@ function generateModifierSection(modifiers) {
|
|
|
73
73
|
const appliesTo = Array.isArray(data.appliesTo) ? data.appliesTo.join(', ') : data.appliesTo || '';
|
|
74
74
|
return `${key}(${appliesTo})`;
|
|
75
75
|
});
|
|
76
|
-
return ['/**', ' * MODIFIER REFERENCE', ' * modifier(appliesTo)', ' */',
|
|
76
|
+
return ['/**', ' * MODIFIER REFERENCE', ' * modifier(appliesTo)', ' */', wrapModifiers(formatted)].join('\n');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// -----------------------------
|
|
80
|
+
// SEQUENCE MODIFIER SECTION
|
|
81
|
+
// -----------------------------
|
|
82
|
+
function generateSequenceModifierSection(modifiers) {
|
|
83
|
+
const sequenceModifiers = Object.entries(modifiers)
|
|
84
|
+
.filter(([key, data]) => {
|
|
85
|
+
const appliesTo = data.appliesTo;
|
|
86
|
+
return Array.isArray(appliesTo) ? appliesTo.includes("sequence") : appliesTo === "sequence";
|
|
87
|
+
})
|
|
88
|
+
.map(([key]) => key);
|
|
89
|
+
|
|
90
|
+
if (sequenceModifiers.length === 0) {
|
|
91
|
+
return '';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const lines = sequenceModifiers.map(m => `// ${m}`);
|
|
95
|
+
return ['/**', ' * SEQUENCE MODIFIERS', ' * Add as last element in array', ' */', ...lines].join('\n');
|
|
77
96
|
}
|
|
78
97
|
|
|
79
98
|
// -----------------------------
|
|
@@ -85,7 +104,8 @@ function generateOrthographyExport() {
|
|
|
85
104
|
'// a: ["ɑ", ["more_rounded"]],',
|
|
86
105
|
'// x̱: [',
|
|
87
106
|
'// ["k", ["ejective", "aspirated"]],',
|
|
88
|
-
'// ["x", []]',
|
|
107
|
+
'// ["x", []],',
|
|
108
|
+
'// "affricate"',
|
|
89
109
|
'// ],',
|
|
90
110
|
'// l: ["ɬ"]'
|
|
91
111
|
].join('\n');
|
|
@@ -98,14 +118,13 @@ function generateOrthographyExport() {
|
|
|
98
118
|
' * Fill in your orthography below; examples are commented out.',
|
|
99
119
|
' */',
|
|
100
120
|
'',
|
|
101
|
-
"const { parseConfig } = require('./parser.
|
|
121
|
+
"const { parseConfig } = require('./parser.js');",
|
|
102
122
|
'',
|
|
103
123
|
'const orthography = {',
|
|
104
124
|
commentedOrthography,
|
|
105
125
|
'};',
|
|
106
126
|
'',
|
|
107
|
-
'module.exports = parseConfig(orthography);'
|
|
108
|
-
''
|
|
127
|
+
'module.exports = parseConfig(orthography);'
|
|
109
128
|
].join('\n');
|
|
110
129
|
}
|
|
111
130
|
|
|
@@ -113,13 +132,14 @@ function generateOrthographyExport() {
|
|
|
113
132
|
// MAIN GENERATOR
|
|
114
133
|
// -----------------------------
|
|
115
134
|
function generateConfig() {
|
|
116
|
-
const
|
|
135
|
+
const sections = [
|
|
117
136
|
generateIPASection(core),
|
|
118
|
-
'',
|
|
119
137
|
generateModifierSection(modifiers),
|
|
120
|
-
|
|
138
|
+
generateSequenceModifierSection(modifiers),
|
|
121
139
|
generateOrthographyExport()
|
|
122
|
-
].
|
|
140
|
+
].filter(s => s.length > 0);
|
|
141
|
+
|
|
142
|
+
const content = sections.join('\n\n');
|
|
123
143
|
|
|
124
144
|
// Ensure 'ipa' folder exists
|
|
125
145
|
const ipaDir = path.join(process.cwd(), 'ipa');
|
|
@@ -133,11 +153,13 @@ function generateConfig() {
|
|
|
133
153
|
|
|
134
154
|
console.log('✅ ipa/ipa.config.js generated with parseConfig() export');
|
|
135
155
|
|
|
136
|
-
// Write alphabet.js
|
|
156
|
+
// Write alphabet.js if it doesn't exist
|
|
137
157
|
const alphabetPath = path.join(ipaDir, 'alphabet.js');
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
158
|
+
if (!fs.existsSync(alphabetPath)) {
|
|
159
|
+
const alphabetContent = 'const alphabet = [];\nmodule.exports = alphabet;\n';
|
|
160
|
+
fs.writeFileSync(alphabetPath, alphabetContent, 'utf-8');
|
|
161
|
+
console.log('✅ ipa/alphabet.js generated with empty alphabet array');
|
|
162
|
+
}
|
|
141
163
|
}
|
|
142
164
|
|
|
143
165
|
// RUN
|
package/generate-conlang.js
CHANGED
|
@@ -86,11 +86,48 @@ function generateConlang({ alphabet, mod1Chance, mod2Chance, affricateChance })
|
|
|
86
86
|
orthography[letter] = [
|
|
87
87
|
[ipaKey, modifiersList],
|
|
88
88
|
[fricKey, []],
|
|
89
|
+
"affricate"
|
|
89
90
|
];
|
|
90
91
|
return;
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
// Diphthong (vowel + vowel sequence)
|
|
96
|
+
if (baseObj?.type === 'vowel' && Math.random() < 0.3) {
|
|
97
|
+
const vowels = Object.keys(core).filter(k => core[k].type === 'vowel');
|
|
98
|
+
const secondVowel = pickRandom(vowels);
|
|
99
|
+
orthography[letter] = [
|
|
100
|
+
[ipaKey, modifiersList],
|
|
101
|
+
[secondVowel, []],
|
|
102
|
+
"diphthong"
|
|
103
|
+
];
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Syllable (consonant + vowel)
|
|
108
|
+
if (baseObj?.type === 'consonant' && Math.random() < 0.3) {
|
|
109
|
+
const vowels = Object.keys(core).filter(k => core[k].type === 'vowel');
|
|
110
|
+
const vowel = pickRandom(vowels);
|
|
111
|
+
orthography[letter] = [
|
|
112
|
+
[ipaKey, modifiersList],
|
|
113
|
+
[vowel, []],
|
|
114
|
+
"syllable"
|
|
115
|
+
];
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Coarticulation (consonant + consonant)
|
|
120
|
+
if (baseObj?.type === 'consonant' && Math.random() < 0.15) {
|
|
121
|
+
const consonants = Object.keys(core).filter(k => core[k].type === 'consonant');
|
|
122
|
+
const secondConsonant = pickRandom(consonants);
|
|
123
|
+
orthography[letter] = [
|
|
124
|
+
[ipaKey, modifiersList],
|
|
125
|
+
[secondConsonant, []],
|
|
126
|
+
"coarticulation"
|
|
127
|
+
];
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
94
131
|
orthography[letter] = [ipaKey, modifiersList];
|
|
95
132
|
});
|
|
96
133
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const dictionary = require('./dictionary');
|
|
2
|
+
|
|
3
|
+
function getDefinitions(phoneme) {
|
|
4
|
+
const steps = [];
|
|
5
|
+
|
|
6
|
+
function collect(p) {
|
|
7
|
+
if (p.features?.place && dictionary[p.features.place]) {
|
|
8
|
+
steps.push(dictionary[p.features.place]);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (p.features?.manner && dictionary[p.features.manner]) {
|
|
12
|
+
steps.push(dictionary[p.features.manner]);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (p.features?.voicing && dictionary[p.features.voicing]) {
|
|
16
|
+
steps.push(dictionary[p.features.voicing]);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (p.airstream && dictionary[p.airstream]) {
|
|
20
|
+
steps.push(dictionary[p.airstream]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (p.modifiersApplied) {
|
|
24
|
+
for (const mod of p.modifiersApplied) {
|
|
25
|
+
if (dictionary[mod]) steps.push(dictionary[mod]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (p.components) {
|
|
30
|
+
for (const comp of p.components) {
|
|
31
|
+
collect(comp); // IMPORTANT: no string recursion
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
collect(phoneme);
|
|
37
|
+
|
|
38
|
+
return steps.join(". ") + ".";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { getDefinitions, dictionary };
|
package/index.js
CHANGED
|
@@ -3,6 +3,8 @@ const core = require("./core.js"); // base consonants & vowels
|
|
|
3
3
|
const modifiers = require("./modifiers.js"); // diacritics, tones, suprasegmentals
|
|
4
4
|
const rules = require("./rules.js"); // modifier compatibility rules
|
|
5
5
|
const parser = require("./parser.js"); // parser (parsePhoneme, parseUnit, parseConfig)
|
|
6
|
+
const dictionary = require("./dictionary.js"); //instructional definitions
|
|
7
|
+
const getDefinitions = require("./get-definitions.js"); //compiles and formats instructions
|
|
6
8
|
|
|
7
9
|
// Export the layers so users can access them if needed
|
|
8
10
|
const { parsePhoneme, parseUnit, parseConfig } = parser;
|
|
@@ -14,5 +16,7 @@ module.exports = {
|
|
|
14
16
|
parser,
|
|
15
17
|
parsePhoneme,
|
|
16
18
|
parseUnit,
|
|
17
|
-
parseConfig
|
|
19
|
+
parseConfig,
|
|
20
|
+
dictionary,
|
|
21
|
+
getDefinitions
|
|
18
22
|
};
|
package/modifiers.js
CHANGED
|
@@ -20,34 +20,47 @@ const modifiers = {
|
|
|
20
20
|
more_rounded: {
|
|
21
21
|
symbol: "̹",
|
|
22
22
|
appliesTo: "vowel",
|
|
23
|
-
effects: {
|
|
23
|
+
effects: { roundingModifier: "more" }
|
|
24
24
|
},
|
|
25
25
|
less_rounded: {
|
|
26
26
|
symbol: "̜",
|
|
27
27
|
appliesTo: "vowel",
|
|
28
|
-
effects: {
|
|
28
|
+
effects: { roundingModifier: "less" }
|
|
29
29
|
},
|
|
30
30
|
|
|
31
|
+
// PLACE: PRIMARY OVERRIDES
|
|
32
|
+
dental: {
|
|
33
|
+
symbol: "̪",
|
|
34
|
+
appliesTo: "consonant",
|
|
35
|
+
effects: { place: "dental" }
|
|
36
|
+
},
|
|
37
|
+
linguolabial: {
|
|
38
|
+
symbol: "̼",
|
|
39
|
+
appliesTo: "consonant",
|
|
40
|
+
effects: { place: "linguolabial" }
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
// PLACE: SHIFTS (DO NOT OVERRIDE)
|
|
31
44
|
advanced: {
|
|
32
45
|
symbol: "̟",
|
|
33
46
|
appliesTo: "consonant",
|
|
34
|
-
effects: {
|
|
47
|
+
effects: { placeShift: "advanced" }
|
|
35
48
|
},
|
|
36
49
|
retracted: {
|
|
37
50
|
symbol: "̠",
|
|
38
51
|
appliesTo: "consonant",
|
|
39
|
-
effects: {
|
|
52
|
+
effects: { placeShift: "retracted" }
|
|
40
53
|
},
|
|
41
54
|
|
|
42
55
|
centralized: {
|
|
43
56
|
symbol: "̈",
|
|
44
57
|
appliesTo: "vowel",
|
|
45
|
-
effects: {
|
|
58
|
+
effects: { centralization: "centralized" }
|
|
46
59
|
},
|
|
47
60
|
mid_centralized: {
|
|
48
61
|
symbol: "̽",
|
|
49
62
|
appliesTo: "vowel",
|
|
50
|
-
effects: {
|
|
63
|
+
effects: { centralization: "mid" }
|
|
51
64
|
},
|
|
52
65
|
|
|
53
66
|
syllabic: {
|
|
@@ -61,29 +74,20 @@ const modifiers = {
|
|
|
61
74
|
effects: { syllabic: false }
|
|
62
75
|
},
|
|
63
76
|
|
|
64
|
-
dental: {
|
|
65
|
-
symbol: "̪",
|
|
66
|
-
appliesTo: "consonant",
|
|
67
|
-
effects: { placeModifier: "dental" }
|
|
68
|
-
},
|
|
69
77
|
apical: {
|
|
70
78
|
symbol: "̺",
|
|
71
79
|
appliesTo: "consonant",
|
|
72
|
-
effects: {
|
|
80
|
+
effects: { tonguePart: "apical" }
|
|
73
81
|
},
|
|
74
82
|
laminal: {
|
|
75
83
|
symbol: "̻",
|
|
76
84
|
appliesTo: "consonant",
|
|
77
|
-
effects: {
|
|
85
|
+
effects: { tonguePart: "laminal" }
|
|
78
86
|
},
|
|
79
87
|
|
|
80
|
-
|
|
81
|
-
symbol: "ˠ",
|
|
82
|
-
appliesTo: "consonant",
|
|
83
|
-
effects: { secondaryArticulation: "velarized_or_pharyngealized" }
|
|
84
|
-
},
|
|
88
|
+
// SECONDARY ARTICULATION
|
|
85
89
|
velarized: {
|
|
86
|
-
symbol: "
|
|
90
|
+
symbol: "ˠ",
|
|
87
91
|
appliesTo: "consonant",
|
|
88
92
|
effects: { secondaryArticulation: "velarized" }
|
|
89
93
|
},
|
|
@@ -115,6 +119,7 @@ const modifiers = {
|
|
|
115
119
|
effects: { rhotic: true }
|
|
116
120
|
},
|
|
117
121
|
|
|
122
|
+
// LENGTH
|
|
118
123
|
length_half_long: {
|
|
119
124
|
symbol: "ˑ",
|
|
120
125
|
appliesTo: ["vowel", "consonant"],
|
|
@@ -131,15 +136,7 @@ const modifiers = {
|
|
|
131
136
|
effects: { length: "extra-short" }
|
|
132
137
|
},
|
|
133
138
|
|
|
134
|
-
|
|
135
|
-
symbol: "̥̆",
|
|
136
|
-
appliesTo: "consonant",
|
|
137
|
-
effects: {
|
|
138
|
-
voicing: "voiceless",
|
|
139
|
-
mannerModifier: "flap"
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
|
|
139
|
+
// PHONATION
|
|
143
140
|
creaky_voice: {
|
|
144
141
|
symbol: "̰",
|
|
145
142
|
appliesTo: ["vowel", "consonant"],
|
|
@@ -151,12 +148,7 @@ const modifiers = {
|
|
|
151
148
|
effects: { phonation: "breathy" }
|
|
152
149
|
},
|
|
153
150
|
|
|
154
|
-
|
|
155
|
-
symbol: "̼",
|
|
156
|
-
appliesTo: "consonant",
|
|
157
|
-
effects: { placeModifier: "linguolabial" }
|
|
158
|
-
},
|
|
159
|
-
|
|
151
|
+
// HEIGHT MODIFICATION
|
|
160
152
|
raised: {
|
|
161
153
|
symbol: "̝",
|
|
162
154
|
appliesTo: ["vowel", "consonant"],
|
|
@@ -168,6 +160,7 @@ const modifiers = {
|
|
|
168
160
|
effects: { heightModifier: "lowered" }
|
|
169
161
|
},
|
|
170
162
|
|
|
163
|
+
// TONGUE ROOT
|
|
171
164
|
advanced_tongue_root: {
|
|
172
165
|
symbol: "̘",
|
|
173
166
|
appliesTo: "vowel",
|
|
@@ -179,8 +172,7 @@ const modifiers = {
|
|
|
179
172
|
effects: { tongueRoot: "retracted" }
|
|
180
173
|
},
|
|
181
174
|
|
|
182
|
-
//
|
|
183
|
-
|
|
175
|
+
// TONE (unified — removed pitch duplication)
|
|
184
176
|
high_tone: {
|
|
185
177
|
symbol: "́",
|
|
186
178
|
appliesTo: ["vowel", "consonant"],
|
|
@@ -212,17 +204,6 @@ const modifiers = {
|
|
|
212
204
|
effects: { tone: "extra-low" }
|
|
213
205
|
},
|
|
214
206
|
|
|
215
|
-
extra_high_pitch: {
|
|
216
|
-
symbol: "᷄",
|
|
217
|
-
appliesTo: ["vowel", "consonant"],
|
|
218
|
-
effects: { pitch: "extra-high" }
|
|
219
|
-
},
|
|
220
|
-
extra_low_pitch: {
|
|
221
|
-
symbol: "᷅",
|
|
222
|
-
appliesTo: ["vowel", "consonant"],
|
|
223
|
-
effects: { pitch: "extra-low" }
|
|
224
|
-
},
|
|
225
|
-
|
|
226
207
|
downstep: {
|
|
227
208
|
symbol: "ꜜ",
|
|
228
209
|
appliesTo: ["vowel", "consonant"],
|
|
@@ -239,12 +220,18 @@ const modifiers = {
|
|
|
239
220
|
appliesTo: "consonant",
|
|
240
221
|
effects: { release: "none" }
|
|
241
222
|
},
|
|
242
|
-
|
|
223
|
+
|
|
243
224
|
ejective: {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
225
|
+
symbol: "ʼ",
|
|
226
|
+
appliesTo: "consonant",
|
|
227
|
+
effects: { airstream: "egressive_glottalic" }
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
// SEQUENCE MODIFIERS
|
|
231
|
+
diphthong: { appliesTo: "sequence", effects: { type: "diphthong" } },
|
|
232
|
+
affricate: { appliesTo: "sequence", effects: { type: "affricate" } },
|
|
233
|
+
syllable: { appliesTo: "sequence", effects: { type: "syllable" } },
|
|
234
|
+
coarticulation: { appliesTo: "sequence", effects: { type: "coarticulation" } }
|
|
248
235
|
};
|
|
249
236
|
|
|
250
237
|
module.exports = modifiers;
|
package/package.json
CHANGED
package/parser.js
CHANGED
|
@@ -16,8 +16,14 @@ function parsePhoneme(base, appliedModifiers = []) {
|
|
|
16
16
|
throw new Error(`Unknown base phoneme: ${base}`);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
// Start with type and airstream from core
|
|
20
|
+
let phoneme = {
|
|
21
|
+
base,
|
|
22
|
+
type: baseObj.type, // consonant, vowel, etc.
|
|
23
|
+
airstream: baseObj.airstream, // pulmonic, egressive_glottalic, etc.
|
|
24
|
+
features: { ...baseObj.features },
|
|
25
|
+
modifiersApplied: []
|
|
26
|
+
};
|
|
21
27
|
|
|
22
28
|
for (const modKey of appliedModifiers) {
|
|
23
29
|
const mod = modifiers[modKey];
|
|
@@ -26,29 +32,25 @@ function parsePhoneme(base, appliedModifiers = []) {
|
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
const incompatible = rules[modKey]?.incompatibleWith || [];
|
|
29
|
-
const conflict = modifiersApplied.find(applied => incompatible.includes(applied));
|
|
35
|
+
const conflict = phoneme.modifiersApplied.find(applied => incompatible.includes(applied));
|
|
30
36
|
if (conflict) {
|
|
31
37
|
console.warn(`Modifier "${modKey}" is incompatible with already applied "${conflict}". Skipping.`);
|
|
32
38
|
continue;
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
if (mod.
|
|
36
|
-
features = { ...features, ...mod.
|
|
41
|
+
if (mod.effects) {
|
|
42
|
+
phoneme.features = { ...phoneme.features, ...mod.effects };
|
|
37
43
|
} else {
|
|
38
|
-
features[modKey] = true;
|
|
44
|
+
phoneme.features[modKey] = true;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
|
-
modifiersApplied.push(modKey);
|
|
47
|
+
phoneme.modifiersApplied.push(modKey);
|
|
42
48
|
}
|
|
43
49
|
|
|
44
|
-
return
|
|
45
|
-
base,
|
|
46
|
-
symbol: baseObj.symbol || base,
|
|
47
|
-
features,
|
|
48
|
-
modifiersApplied
|
|
49
|
-
};
|
|
50
|
+
return phoneme;
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
|
|
52
54
|
/**
|
|
53
55
|
* Helper to create a phoneme unit for orthography config.
|
|
54
56
|
* Can be a single phoneme or a sequence object.
|
|
@@ -56,10 +58,11 @@ function parsePhoneme(base, appliedModifiers = []) {
|
|
|
56
58
|
* @param {string[]} appliedModifiers - Modifiers for single phoneme (ignored if sequence)
|
|
57
59
|
* @returns {object}
|
|
58
60
|
*/
|
|
59
|
-
function parseUnit(unit, appliedModifiers =
|
|
61
|
+
function parseUnit(unit, appliedModifiers = null) {
|
|
60
62
|
// if unit is a string, treat as single phoneme
|
|
61
63
|
if (typeof unit === "string") {
|
|
62
|
-
|
|
64
|
+
const mods = Array.isArray(appliedModifiers) ? appliedModifiers : [];
|
|
65
|
+
return { type: "single", base: unit, modifiers: mods };
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
// if unit is an object with multiple phonemes, treat as sequence
|
|
@@ -69,12 +72,79 @@ function parseUnit(unit, appliedModifiers = []) {
|
|
|
69
72
|
const mods = unit[base] || [];
|
|
70
73
|
sequence.push({ base, modifiers: mods });
|
|
71
74
|
}
|
|
72
|
-
|
|
75
|
+
const type = (appliedModifiers && typeof appliedModifiers === "string") ? appliedModifiers : "sequence";
|
|
76
|
+
return { type, sequence };
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
throw new Error(`Invalid unit: ${unit}`);
|
|
76
80
|
}
|
|
77
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Validates a sequence against its rules.
|
|
84
|
+
* @param {object} sequenceObj - { type, components }
|
|
85
|
+
* @returns {object} - validated sequence or throws error
|
|
86
|
+
*/
|
|
87
|
+
function validateSequence(sequenceObj) {
|
|
88
|
+
const { type, components } = sequenceObj;
|
|
89
|
+
const rule = rules[type];
|
|
90
|
+
|
|
91
|
+
if (!rule) {
|
|
92
|
+
return sequenceObj;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (rule.length && components.length !== rule.length) {
|
|
96
|
+
throw new Error(`${type} requires exactly ${rule.length} components, got ${components.length}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (rule.minLength && components.length < rule.minLength) {
|
|
100
|
+
throw new Error(`${type} requires at least ${rule.minLength} components, got ${components.length}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (rule.requires) {
|
|
104
|
+
for (const req of rule.requires) {
|
|
105
|
+
const component = components[req.position];
|
|
106
|
+
if (!component) {
|
|
107
|
+
throw new Error(`${type} requires component at position ${req.position}`);
|
|
108
|
+
}
|
|
109
|
+
if (req.type && component.type !== req.type) {
|
|
110
|
+
throw new Error(`${type} requires position ${req.position} to be type "${req.type}", got "${component.type}"`);
|
|
111
|
+
}
|
|
112
|
+
if (req.features) {
|
|
113
|
+
for (const [key, value] of Object.entries(req.features)) {
|
|
114
|
+
if (component.features[key] !== value) {
|
|
115
|
+
throw new Error(`${type} requires position ${req.position} to have features.${key}="${value}", got "${component.features[key]}"`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (rule.requiresAll) {
|
|
123
|
+
for (const req of rule.requiresAll) {
|
|
124
|
+
const invalid = components.find(c => req.type && c.type !== req.type);
|
|
125
|
+
if (invalid) {
|
|
126
|
+
throw new Error(`${type} requires all components to be type "${req.type}", but found "${invalid.type}"`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (rule.requiresFromPosition) {
|
|
132
|
+
for (const [pos, reqs] of Object.entries(rule.requiresFromPosition)) {
|
|
133
|
+
const component = components[parseInt(pos)];
|
|
134
|
+
if (!component) {
|
|
135
|
+
throw new Error(`${type} requires component at position ${pos}`);
|
|
136
|
+
}
|
|
137
|
+
for (const req of reqs) {
|
|
138
|
+
if (req.type && component.type !== req.type) {
|
|
139
|
+
throw new Error(`${type} requires position ${pos} to be type "${req.type}", got "${component.type}"`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return sequenceObj;
|
|
146
|
+
}
|
|
147
|
+
|
|
78
148
|
/**
|
|
79
149
|
* Parses a unit (single or sequence) into full feature objects
|
|
80
150
|
* @param {object} unitObj
|
|
@@ -85,8 +155,14 @@ function parseUnitObj(unitObj) {
|
|
|
85
155
|
return parsePhoneme(unitObj.base, unitObj.modifiers);
|
|
86
156
|
}
|
|
87
157
|
|
|
88
|
-
if (unitObj.type === "sequence") {
|
|
89
|
-
|
|
158
|
+
if (unitObj.type === "sequence" || unitObj.type === "affricate" || unitObj.type === "diphthong" || unitObj.type === "syllable" || unitObj.type === "coarticulation") {
|
|
159
|
+
const components = unitObj.sequence.map((u, index) => {
|
|
160
|
+
const parsed = parsePhoneme(u.base, u.modifiers);
|
|
161
|
+
parsed.position = index;
|
|
162
|
+
return parsed;
|
|
163
|
+
});
|
|
164
|
+
const sequenceObj = { type: unitObj.type, components };
|
|
165
|
+
return validateSequence(sequenceObj);
|
|
90
166
|
}
|
|
91
167
|
|
|
92
168
|
throw new Error(`Unknown unit type: ${unitObj.type}`);
|
|
@@ -110,7 +186,7 @@ function parseConfig(config) {
|
|
|
110
186
|
let unitObj;
|
|
111
187
|
|
|
112
188
|
// Already a parseUnit object
|
|
113
|
-
if (val && typeof val === "object" && val.type && (val.type === "single" || val.
|
|
189
|
+
if (val && typeof val === "object" && val.type && (val.type === "single" || val.sequence)) {
|
|
114
190
|
unitObj = val;
|
|
115
191
|
}
|
|
116
192
|
// Shorthand single phoneme: ["base", ["modifiers"]]
|
|
@@ -119,10 +195,13 @@ function parseConfig(config) {
|
|
|
119
195
|
const mods = Array.isArray(val[1]) ? val[1] : [];
|
|
120
196
|
unitObj = { type: "single", base, modifiers: mods };
|
|
121
197
|
}
|
|
122
|
-
// Shorthand sequence: [["base1", ["mods1"]], ["base2", ["mods2"]],
|
|
123
|
-
else if (Array.isArray(val) && val.every(el => Array.isArray(el)
|
|
124
|
-
const
|
|
125
|
-
|
|
198
|
+
// Shorthand sequence: [["base1", ["mods1"]], ["base2", ["mods2"]], ..., "affricate"]
|
|
199
|
+
else if (Array.isArray(val) && val.every(el => Array.isArray(el) || typeof el === "string")) {
|
|
200
|
+
const stringElements = val.filter(el => typeof el === "string");
|
|
201
|
+
const sequenceModifier = stringElements[0] || null;
|
|
202
|
+
const arrayElements = val.filter(el => Array.isArray(el));
|
|
203
|
+
const sequence = arrayElements.map(([base, mods]) => ({ base, modifiers: Array.isArray(mods) ? mods : [] }));
|
|
204
|
+
unitObj = { type: sequenceModifier || "sequence", sequence };
|
|
126
205
|
}
|
|
127
206
|
else {
|
|
128
207
|
throw new Error(`Invalid orthography entry for "${letter}": ${JSON.stringify(val)}`);
|
package/rules.js
CHANGED
|
@@ -1,38 +1,45 @@
|
|
|
1
|
-
//modifier
|
|
1
|
+
// modifier compatibility
|
|
2
2
|
const rules = {
|
|
3
|
-
//
|
|
3
|
+
// VOICING / PHONATION
|
|
4
4
|
voiceless: { incompatibleWith: ["voiced", "creaky_voice", "breathy_voice"] },
|
|
5
|
-
voiced: { incompatibleWith: ["voiceless"
|
|
6
|
-
voiceless_flap: { incompatibleWith: ["voiced", "aspirated"] },
|
|
5
|
+
voiced: { incompatibleWith: ["voiceless"] },
|
|
7
6
|
creaky_voice: { incompatibleWith: ["voiceless"] },
|
|
8
7
|
breathy_voice: { incompatibleWith: ["voiceless"] },
|
|
9
8
|
|
|
10
|
-
//
|
|
11
|
-
advanced: { incompatibleWith: ["retracted"
|
|
12
|
-
retracted: { incompatibleWith: ["advanced"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
linguolabial: { incompatibleWith: [] },
|
|
9
|
+
// PLACE: SHIFTS (mutually exclusive)
|
|
10
|
+
advanced: { incompatibleWith: ["retracted"] },
|
|
11
|
+
retracted: { incompatibleWith: ["advanced"] },
|
|
12
|
+
|
|
13
|
+
// PLACE: PRIMARY OVERRIDES
|
|
14
|
+
// (not inherently incompatible — last one wins in processing)
|
|
17
15
|
dental: { incompatibleWith: [] },
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
linguolabial: { incompatibleWith: [] },
|
|
17
|
+
|
|
18
|
+
// TONGUE PART (mutually exclusive)
|
|
19
|
+
apical: { incompatibleWith: ["laminal"] },
|
|
20
|
+
laminal: { incompatibleWith: ["apical"] },
|
|
21
21
|
|
|
22
|
-
//
|
|
22
|
+
// SECONDARY ARTICULATION
|
|
23
|
+
// These can stack in real phonetics, but we constrain for clarity
|
|
24
|
+
palatalized: { incompatibleWith: ["velarized", "pharyngealized"] },
|
|
25
|
+
velarized: { incompatibleWith: ["palatalized"] },
|
|
26
|
+
pharyngealized: { incompatibleWith: ["palatalized"] },
|
|
27
|
+
labialized: { incompatibleWith: [] },
|
|
28
|
+
|
|
29
|
+
// SYLLABICITY
|
|
23
30
|
syllabic: { incompatibleWith: ["non_syllabic"] },
|
|
24
31
|
non_syllabic: { incompatibleWith: ["syllabic"] },
|
|
25
32
|
|
|
26
|
-
//
|
|
33
|
+
// NASALIZATION / RHOTICITY
|
|
27
34
|
nasalized: { incompatibleWith: [] },
|
|
28
35
|
rhoticity: { incompatibleWith: [] },
|
|
29
36
|
|
|
30
|
-
//
|
|
37
|
+
// LENGTH / DURATION
|
|
31
38
|
extra_short: { incompatibleWith: ["length_half_long", "length_long"] },
|
|
32
39
|
length_half_long: { incompatibleWith: ["extra_short", "length_long"] },
|
|
33
40
|
length_long: { incompatibleWith: ["extra_short", "length_half_long"] },
|
|
34
41
|
|
|
35
|
-
//
|
|
42
|
+
// TONE
|
|
36
43
|
high_tone: { incompatibleWith: ["low_tone", "falling_tone", "extra_low_tone"] },
|
|
37
44
|
low_tone: { incompatibleWith: ["high_tone", "rising_tone", "extra_high_tone"] },
|
|
38
45
|
rising_tone: { incompatibleWith: ["falling_tone", "low_tone"] },
|
|
@@ -41,12 +48,63 @@ const rules = {
|
|
|
41
48
|
extra_low_tone: { incompatibleWith: ["extra_high_tone", "high_tone"] },
|
|
42
49
|
downstep: { incompatibleWith: [] },
|
|
43
50
|
upstep: { incompatibleWith: [] },
|
|
44
|
-
extra_high_pitch: { incompatibleWith: ["extra_low_pitch"] },
|
|
45
|
-
extra_low_pitch: { incompatibleWith: ["extra_high_pitch"] },
|
|
46
51
|
|
|
47
|
-
//
|
|
48
|
-
aspirated: { incompatibleWith: ["
|
|
49
|
-
no_audible_release: { incompatibleWith: ["aspirated"] }
|
|
52
|
+
// RELEASE / ASPIRATION
|
|
53
|
+
aspirated: { incompatibleWith: ["no_audible_release"] },
|
|
54
|
+
no_audible_release: { incompatibleWith: ["aspirated"] },
|
|
55
|
+
|
|
56
|
+
// VOWEL QUALITY (non-conflicting dimensions)
|
|
57
|
+
more_rounded: { incompatibleWith: [] },
|
|
58
|
+
less_rounded: { incompatibleWith: [] },
|
|
59
|
+
|
|
60
|
+
centralized: { incompatibleWith: ["mid_centralized"] },
|
|
61
|
+
mid_centralized: { incompatibleWith: ["centralized"] },
|
|
62
|
+
|
|
63
|
+
raised: { incompatibleWith: ["lowered"] },
|
|
64
|
+
lowered: { incompatibleWith: ["raised"] },
|
|
65
|
+
|
|
66
|
+
// TONGUE ROOT (single axis, but isolated from others)
|
|
67
|
+
advanced_tongue_root: { incompatibleWith: ["retracted_tongue_root"] },
|
|
68
|
+
retracted_tongue_root: { incompatibleWith: ["advanced_tongue_root"] },
|
|
69
|
+
|
|
70
|
+
// AIRSTREAM MECHANISM (independent feature class)
|
|
71
|
+
ejective: { incompatibleWith: [] },
|
|
72
|
+
|
|
73
|
+
// SEQUENCE-LEVEL RULES
|
|
74
|
+
|
|
75
|
+
affricate: {
|
|
76
|
+
requires: [
|
|
77
|
+
{ position: 0, features: { manner: "plosive" } },
|
|
78
|
+
{ position: 1, features: { manner: "fricative" } }
|
|
79
|
+
],
|
|
80
|
+
length: 2
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
diphthong: {
|
|
84
|
+
requiresAll: [
|
|
85
|
+
{ type: "vowel" }
|
|
86
|
+
],
|
|
87
|
+
minLength: 2
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
coarticulation: {
|
|
91
|
+
requiresAll: [
|
|
92
|
+
{ type: "consonant" }
|
|
93
|
+
],
|
|
94
|
+
minLength: 2
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
syllable: {
|
|
98
|
+
requires: [
|
|
99
|
+
{ position: 0, type: "consonant" }
|
|
100
|
+
],
|
|
101
|
+
requiresFromPosition: {
|
|
102
|
+
1: [
|
|
103
|
+
{ type: "vowel" }
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
minLength: 2
|
|
107
|
+
}
|
|
50
108
|
};
|
|
51
109
|
|
|
52
|
-
module.exports = rules
|
|
110
|
+
module.exports = rules;
|
package/test.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// test.js
|
|
2
2
|
|
|
3
3
|
const { parseUnit, parseConfig } = require("./parser");
|
|
4
|
+
const { getDefinitions } = require("./get-definitions");
|
|
4
5
|
|
|
5
6
|
// Sample orthography config
|
|
6
7
|
const orthography = {
|
|
@@ -14,25 +15,57 @@ const orthography = {
|
|
|
14
15
|
x̱: parseUnit({
|
|
15
16
|
k: ["ejective", "aspirated"],
|
|
16
17
|
x: []
|
|
17
|
-
}),
|
|
18
|
+
}, "affricate"),
|
|
18
19
|
|
|
19
20
|
// Another single consonant
|
|
20
|
-
l: parseUnit("ɬ")
|
|
21
|
+
l: parseUnit("ɬ"),
|
|
22
|
+
|
|
23
|
+
// Consonant with place modifiers
|
|
24
|
+
t̪: parseUnit("t", ["dental"]),
|
|
25
|
+
s̻: parseUnit("s", ["laminal"]),
|
|
26
|
+
k̠: parseUnit("k", ["retracted"])
|
|
21
27
|
};
|
|
22
28
|
|
|
23
29
|
// Another sample orthography config using shorthand
|
|
24
30
|
const shorthandOrthography = {
|
|
25
|
-
// A single consonant
|
|
26
|
-
k: ["k"
|
|
31
|
+
// A single consonant
|
|
32
|
+
k: ["k"],
|
|
27
33
|
|
|
28
34
|
// A single vowel with a modifier
|
|
29
35
|
a: ["ɑ", ["more_rounded"]],
|
|
30
36
|
|
|
31
|
-
// A consonant sequence (affricate)
|
|
32
|
-
x̱: [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
// A consonant sequence (affricate) with new string shorthand
|
|
38
|
+
x̱: [
|
|
39
|
+
["k", ["ejective", "aspirated"]],
|
|
40
|
+
["x", []],
|
|
41
|
+
"affricate"
|
|
42
|
+
],
|
|
43
|
+
|
|
44
|
+
// Diphthong example
|
|
45
|
+
ai: [
|
|
46
|
+
["ɑ"],
|
|
47
|
+
["i"],
|
|
48
|
+
"diphthong"
|
|
49
|
+
],
|
|
50
|
+
|
|
51
|
+
// Syllable example (e.g., CV - consonant followed by vowel)
|
|
52
|
+
na: [
|
|
53
|
+
["n"],
|
|
54
|
+
["ɑ"],
|
|
55
|
+
"syllable"
|
|
56
|
+
],
|
|
57
|
+
|
|
58
|
+
// Coarticulation example (e.g., labial-velar)
|
|
59
|
+
w: [
|
|
60
|
+
["ʍ"],
|
|
61
|
+
"coarticulation"
|
|
62
|
+
],
|
|
63
|
+
|
|
64
|
+
// Plain sequence (no sequence modifier)
|
|
65
|
+
kl: [
|
|
66
|
+
["k"],
|
|
67
|
+
["l"]
|
|
68
|
+
],
|
|
36
69
|
|
|
37
70
|
// Another single consonant
|
|
38
71
|
l: ["ɬ"]
|
|
@@ -48,13 +81,26 @@ console.log(JSON.stringify(parsed, null, 2));
|
|
|
48
81
|
// Optional: inspect a single phoneme
|
|
49
82
|
const singlePhoneme = parsed.k;
|
|
50
83
|
console.log("\n=== Single Phoneme 'k' Features ===");
|
|
51
|
-
console.log(singlePhoneme);
|
|
84
|
+
console.log(JSON.stringify(singlePhoneme, null, 2));
|
|
52
85
|
|
|
53
86
|
// Optional: inspect a sequence
|
|
54
87
|
const affricateSequence = parsed.x̱;
|
|
55
88
|
console.log("\n=== Affricate Sequence 'x̱' Features ===");
|
|
56
|
-
console.log(affricateSequence);
|
|
89
|
+
console.log(JSON.stringify(affricateSequence, null, 2));
|
|
90
|
+
|
|
91
|
+
// const parsedShorthand = parseConfig(shorthandOrthography);
|
|
92
|
+
// console.log("\n=== Parsed Output2 ===");
|
|
93
|
+
// console.log(JSON.stringify(parsedShorthand, null, 2));
|
|
94
|
+
|
|
95
|
+
// Definitions
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
console.log("\n=== Definitions for 'k' ===");
|
|
99
|
+
console.log(getDefinitions(parsed.k));
|
|
100
|
+
|
|
101
|
+
console.log("\n=== Definitions for 'x̱' (affricate) ===");
|
|
102
|
+
console.log(getDefinitions(parsed.x̱));
|
|
103
|
+
|
|
104
|
+
console.log("\n=== Definitions for 'a' (vowel) ===");
|
|
105
|
+
console.log(getDefinitions(parsed.a));
|
|
57
106
|
|
|
58
|
-
const parsedShorthand = parseConfig(shorthandOrthography);
|
|
59
|
-
console.log("\n=== Parsed Output2 ===");
|
|
60
|
-
console.log(JSON.stringify(parsedShorthand, null, 2));
|