1pub mod builder;
2pub mod limit;
3
4use std::rc::Rc;
5
6use jlabel::{Label, Phoneme};
7use jpreprocess_core::pronunciation::phoneme::Consonant;
8
9use super::label::*;
10use builder::*;
11
12pub fn utterance_to_features(utterance: &Utterance) -> Vec<Label> {
14 let phoneme_vec = utterance_to_phoneme_vec(utterance);
15 overwrapping_phonemes(phoneme_vec)
16}
17
18pub fn overwrapping_phonemes(phoneme_vec: Vec<(String, FeatureBuilder)>) -> Vec<Label> {
20 (0..phoneme_vec.len())
21 .map(|i| {
22 let (p2, p1) = match i {
23 0 => (None, None),
24 1 => (None, Some(phoneme_vec[0].0.clone())),
25 _ => (
26 Some(phoneme_vec[i - 2].0.clone()),
27 Some(phoneme_vec[i - 1].0.clone()),
28 ),
29 };
30 let (c, n1, n2) = match &phoneme_vec[i..] {
31 [c, n1, n2, ..] => (Some(c.0.clone()), Some(n1.0.clone()), Some(n2.0.clone())),
32 [c, n1] => (Some(c.0.clone()), Some(n1.0.clone()), None),
33 [c] => (Some(c.0.clone()), None, None),
34 _ => unreachable!(),
35 };
36 let phoneme = Phoneme { p2, p1, c, n1, n2 };
37 phoneme_vec[i].1.build(phoneme)
38 })
39 .collect()
40}
41
42pub fn utterance_to_phoneme_vec(utterance: &Utterance) -> Vec<(String, FeatureBuilder)> {
44 let breath_group_count_in_utterance = utterance.breath_groups.len();
45 let accent_phrase_count_in_utterance = utterance.count_accent_phrase();
46 let mora_count_in_utterance = utterance.count_mora();
47 let mut accent_phrase_index_in_utterance = 0;
48 let mut mora_index_in_utterance = 0;
49
50 let mut phonemes = Vec::with_capacity(mora_count_in_utterance);
51
52 let builder_u = FeatureBuilderUtterance::new(utterance.to_k());
53
54 for breath_group_index_in_utterance in 0..breath_group_count_in_utterance {
55 let (breath_group_prev, breath_group, breath_group_next) =
56 get_prev_next(&utterance.breath_groups, breath_group_index_in_utterance);
57
58 let accent_phrase_count_in_breath_group = breath_group.accent_phrases.len();
59 let mora_count_in_breath_group = breath_group.count_mora();
60 let mut mora_index_in_breath_group = 0;
61
62 if let Some(breath_group_prev) = breath_group_prev {
63 phonemes.push((
65 "pau".to_string(),
66 pau_feature(
67 builder_u.clone(),
68 Some(breath_group_prev),
69 Some(breath_group),
70 ),
71 ));
72 } else {
73 let mut builder = pau_feature(builder_u.clone(), None, Some(breath_group));
75 if breath_group_next.is_none() {
76 builder.ignore_d();
77 }
78 phonemes.push(("sil".to_string(), builder));
79 }
80
81 let h = breath_group_prev.map(|bg| bg.to_h());
82 let i = breath_group.to_i(
83 breath_group_count_in_utterance,
84 breath_group_index_in_utterance,
85 accent_phrase_count_in_utterance,
86 accent_phrase_index_in_utterance,
87 mora_count_in_utterance,
88 mora_index_in_utterance,
89 );
90 let j = breath_group_next.map(|bg| bg.to_j());
91
92 let builder_bg = builder_u.with_hij(h, i, j);
93
94 for accent_phrase_index_in_breath_group in 0..accent_phrase_count_in_breath_group {
95 let (accent_phrase_prev, accent_phrase, accent_phrase_next) = {
96 let (accent_phrase_prev, accent_phrase, accent_phrase_next) = get_prev_next(
97 &breath_group.accent_phrases,
98 accent_phrase_index_in_breath_group,
99 );
100 (
101 accent_phrase_prev
102 .or_else(|| breath_group_prev.and_then(|bg| bg.accent_phrases.last())),
103 accent_phrase,
104 accent_phrase_next
105 .or_else(|| breath_group_next.and_then(|bg| bg.accent_phrases.first())),
106 )
107 };
108
109 let e = accent_phrase_prev.map(|ap| {
110 ap.to_e(Some(
111 breath_group_prev.is_some() && accent_phrase_index_in_breath_group == 0,
112 ))
113 });
114 let f = accent_phrase.to_f(
115 accent_phrase_count_in_breath_group,
116 accent_phrase_index_in_breath_group,
117 mora_count_in_breath_group,
118 mora_index_in_breath_group,
119 );
120 let g = accent_phrase_next.map(|ap| {
121 ap.to_g(Some(
122 breath_group_next.is_some()
123 && accent_phrase_index_in_breath_group
124 == accent_phrase_count_in_breath_group - 1,
125 ))
126 });
127
128 let builder_ap = builder_bg.with_efg(e, f, g);
129
130 let mora_a = accent_phrase.generate_mora_a();
131
132 let mora_count_in_accent_phrase = accent_phrase.count_mora();
133 let mut mora_index_in_accent_phrase = 0;
134
135 for word_index_in_accent_phrase in 0..accent_phrase.words.len() {
136 let (word_prev, word, word_next) =
137 get_prev_next(&accent_phrase.words, word_index_in_accent_phrase);
138
139 let b = word_prev
140 .or_else(|| accent_phrase_prev.and_then(|ap| ap.words.last()))
141 .map(|word| word.into());
142 let c = word.into();
143 let d = word_next
144 .or_else(|| accent_phrase_next.and_then(|ap| ap.words.first()))
145 .map(|word| word.into());
146
147 let builder_w = builder_ap.with_bcd(b, c, d);
148
149 for mora in word.moras.moras() {
150 let a = &mora_a[mora_index_in_accent_phrase];
151 let builder = builder_w.with_a(a.to_owned());
152
153 let (consonant, vowel) = mora.phonemes();
154 if let Some(consonant) = consonant {
155 if matches!(&consonant, Consonant::Long) {
156 if let Some((last, _)) = phonemes.last() {
157 phonemes.push((last.to_owned(), builder.clone()));
158 } else {
159 eprintln!("WARN: First mora should not be long vowel symbol.");
160 }
161 } else {
162 phonemes.push((consonant.to_string(), builder.clone()));
163 }
164 }
165 if let Some(vowel) = vowel {
166 phonemes.push((vowel.to_string(), builder));
167 }
168
169 mora_index_in_accent_phrase += 1;
170 }
171 }
172 mora_index_in_breath_group += mora_count_in_accent_phrase;
173 }
174 mora_index_in_utterance += mora_count_in_breath_group;
175 accent_phrase_index_in_utterance += accent_phrase_count_in_breath_group;
176
177 if breath_group_next.is_none() {
178 let mut builder = pau_feature(builder_u.clone(), Some(breath_group), None);
180 if breath_group_prev.is_none() {
181 builder.ignore_b();
182 }
183 phonemes.push(("sil".to_string(), builder));
184 }
185 }
186
187 phonemes
188}
189
190fn get_prev_next<T>(v: &[T], idx: usize) -> (Option<&T>, &T, Option<&T>) {
191 let prev = if idx == 0 { None } else { v.get(idx - 1) };
192 let next = v.get(idx + 1);
193 (prev, &v[idx], next)
194}
195
196fn pau_feature(
197 builder_u: Rc<FeatureBuilderUtterance>,
198 breath_group_prev: Option<&BreathGroup>,
199 breath_group_next: Option<&BreathGroup>,
200) -> FeatureBuilder {
201 let accent_phrase_prev = breath_group_prev.and_then(|bg| bg.accent_phrases.last());
202 let word_prev = accent_phrase_prev.and_then(|ap| ap.words.last());
203
204 let accent_phrase_next = breath_group_next.and_then(|bg| bg.accent_phrases.first());
205 let word_next = accent_phrase_next.and_then(|ap| ap.words.first());
206
207 builder_u
208 .with_hj(
209 breath_group_prev.map(|bg| bg.to_h()),
210 breath_group_next.map(|bg| bg.to_j()),
211 )
212 .with_eg(
213 accent_phrase_prev.map(|ap| ap.to_e(None)),
214 accent_phrase_next.map(|ap| ap.to_g(None)),
215 )
216 .with_bd(word_prev.map(|w| w.into()), word_next.map(|w| w.into()))
217 .without_a()
218}
219
220#[cfg(test)]
221mod tests {
222 use jpreprocess_njd::NJDNode;
223
224 use super::*;
225
226 #[test]
227 fn overwrapping_phonemes_bonsai() {
228 let features = overwrapping_phonemes(
229 ["sil", "b", "o", "N", "s", "a", "i", "sil"]
230 .iter()
231 .map(|phoneme| (phoneme.to_string(), FeatureBuilder::dummy()))
232 .collect(),
233 );
234 let phoneme_answer = [
235 "xx^xx-sil+b=o",
236 "xx^sil-b+o=N",
237 "sil^b-o+N=s",
238 "b^o-N+s=a",
239 "o^N-s+a=i",
240 "N^s-a+i=sil",
241 "s^a-i+sil=xx",
242 "a^i-sil+xx=xx",
243 ];
244 for i in 0..8 {
245 let s = features[i].to_string();
246 let (phoneme, _) = s.split_once('/').unwrap();
247 assert_eq!(phoneme, phoneme_answer[i]);
248 }
249 }
250
251 #[test]
252 fn generate_bonsai() {
253 let njd = vec![NJDNode::new_single(
254 "盆栽,名詞,一般,*,*,*,*,盆栽,ボンサイ,ボンサイ,0/4,C2",
255 )];
256 let utterance = Utterance::from(njd.as_slice());
257 let v = utterance_to_phoneme_vec(&utterance);
258 let phonemes = ["sil", "b", "o", "N", "s", "a", "i", "sil"];
259 let features = [
260 "/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:4_4%0_xx_xx/H:xx_xx/I:xx-xx@xx+xx&xx-xx|xx+xx/J:1_4/K:1+1-4",
261 "/A:-3+1+4/B:xx-xx_xx/C:02_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:4_4#0_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-4@1+1&1-1|1+4/J:xx_xx/K:1+1-4",
262 "/A:-3+1+4/B:xx-xx_xx/C:02_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:4_4#0_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-4@1+1&1-1|1+4/J:xx_xx/K:1+1-4",
263 "/A:-2+2+3/B:xx-xx_xx/C:02_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:4_4#0_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-4@1+1&1-1|1+4/J:xx_xx/K:1+1-4",
264 "/A:-1+3+2/B:xx-xx_xx/C:02_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:4_4#0_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-4@1+1&1-1|1+4/J:xx_xx/K:1+1-4",
265 "/A:-1+3+2/B:xx-xx_xx/C:02_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:4_4#0_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-4@1+1&1-1|1+4/J:xx_xx/K:1+1-4",
266 "/A:0+4+1/B:xx-xx_xx/C:02_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:4_4#0_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-4@1+1&1-1|1+4/J:xx_xx/K:1+1-4",
267 "/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:4_4!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:xx_xx%xx_xx_xx/H:1_4/I:xx-xx@xx+xx&xx-xx|xx+xx/J:xx_xx/K:1+1-4",
268 ];
269 for i in 0..8 {
270 assert_eq!(v[i].0.as_str(), phonemes[i]);
271 assert_eq!(&v[i].1.to_string_without_phoneme(), features[i]);
272 }
273 }
274
275 #[test]
276 fn generate_interrogative_bonsai() {
277 let njd = vec![
278 NJDNode::new_single("盆栽,名詞,一般,*,*,*,*,盆栽,ボンサイ,ボンサイ,0/4,C2"),
279 NJDNode::new_single("?,記号,一般,*,*,*,*,?,?,?,0/0,*,0"),
280 ];
281 let utterance = Utterance::from(njd.as_slice());
282 let v = utterance_to_phoneme_vec(&utterance);
283 let phonemes = ["sil", "b", "o", "N", "s", "a", "i", "sil"];
284 let features = [
285 "/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:4_4%1_xx_xx/H:xx_xx/I:xx-xx@xx+xx&xx-xx|xx+xx/J:1_4/K:1+1-4",
286 "/A:-3+1+4/B:xx-xx_xx/C:02_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:4_4#1_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-4@1+1&1-1|1+4/J:xx_xx/K:1+1-4",
287 "/A:-3+1+4/B:xx-xx_xx/C:02_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:4_4#1_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-4@1+1&1-1|1+4/J:xx_xx/K:1+1-4",
288 "/A:-2+2+3/B:xx-xx_xx/C:02_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:4_4#1_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-4@1+1&1-1|1+4/J:xx_xx/K:1+1-4",
289 "/A:-1+3+2/B:xx-xx_xx/C:02_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:4_4#1_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-4@1+1&1-1|1+4/J:xx_xx/K:1+1-4",
290 "/A:-1+3+2/B:xx-xx_xx/C:02_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:4_4#1_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-4@1+1&1-1|1+4/J:xx_xx/K:1+1-4",
291 "/A:0+4+1/B:xx-xx_xx/C:02_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:4_4#1_xx@1_1|1_4/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-4@1+1&1-1|1+4/J:xx_xx/K:1+1-4",
292 "/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:4_4!1_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:xx_xx%xx_xx_xx/H:1_4/I:xx-xx@xx+xx&xx-xx|xx+xx/J:xx_xx/K:1+1-4",
293 ];
294 for i in 0..8 {
295 assert_eq!(v[i].0.as_str(), phonemes[i]);
296 assert_eq!(&v[i].1.to_string_without_phoneme(), features[i]);
297 }
298 }
299
300 #[test]
301 fn generate_is_this_bonsai() {
302 let njd = vec![
303 NJDNode::new_single("これ,名詞,代名詞,一般,*,*,*,これ,コレ,コレ,0/2,C3,-1"),
304 NJDNode::new_single("は,助詞,係助詞,*,*,*,*,は,ハ,ワ,0/1,名詞%F1/動詞%F2@0/形容詞%F2@0,1"),
305 NJDNode::new_single(",,記号,読点,*,*,*,*,,,、,、,0/0,*,0"),
306 NJDNode::new_single("盆栽,名詞,一般,*,*,*,*,盆栽,ボンサイ,ボンサイ,5/4,C2,0"),
307 NJDNode::new_single("です,助動詞,*,*,*,特殊・デス,基本形,です,デス,デス’,1/2,名詞%F2@1/動詞%F1/形容詞%F2@0,1"),
308 NJDNode::new_single("か,助詞,副助詞/並立助詞/終助詞,*,*,*,*,か,カ,カ,0/1,名詞%F1/動詞%F2@0/形容詞%F2@0,1"),
309 NJDNode::new_single("?,記号,一般,*,*,*,*,?,?,?,0/0,*,0")
310 ];
311 let utterance = Utterance::from(njd.as_slice());
312 let v = utterance_to_phoneme_vec(&utterance);
313 let phonemes = [
314 "sil", "k", "o", "r", "e", "w", "a", "pau", "b", "o", "N", "s", "a", "i", "d", "e",
315 "s", "U", "k", "a", "sil",
316 ];
317 let features = [
318 "/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:04+xx_xx/E:xx_xx!xx_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:3_3%0_xx_xx/H:xx_xx/I:xx-xx@xx+xx&xx-xx|xx+xx/J:1_3/K:2+2-10",
319 "/A:-2+1+3/B:xx-xx_xx/C:04_xx+xx/D:24+xx_xx/E:xx_xx!xx_xx-xx/F:3_3#0_xx@1_1|1_3/G:7_5%1_xx_0/H:xx_xx/I:1-3@1+2&1-2|1+10/J:1_7/K:2+2-10",
320 "/A:-2+1+3/B:xx-xx_xx/C:04_xx+xx/D:24+xx_xx/E:xx_xx!xx_xx-xx/F:3_3#0_xx@1_1|1_3/G:7_5%1_xx_0/H:xx_xx/I:1-3@1+2&1-2|1+10/J:1_7/K:2+2-10",
321 "/A:-1+2+2/B:xx-xx_xx/C:04_xx+xx/D:24+xx_xx/E:xx_xx!xx_xx-xx/F:3_3#0_xx@1_1|1_3/G:7_5%1_xx_0/H:xx_xx/I:1-3@1+2&1-2|1+10/J:1_7/K:2+2-10",
322 "/A:-1+2+2/B:xx-xx_xx/C:04_xx+xx/D:24+xx_xx/E:xx_xx!xx_xx-xx/F:3_3#0_xx@1_1|1_3/G:7_5%1_xx_0/H:xx_xx/I:1-3@1+2&1-2|1+10/J:1_7/K:2+2-10",
323 "/A:0+3+1/B:04-xx_xx/C:24_xx+xx/D:02+xx_xx/E:xx_xx!xx_xx-xx/F:3_3#0_xx@1_1|1_3/G:7_5%1_xx_0/H:xx_xx/I:1-3@1+2&1-2|1+10/J:1_7/K:2+2-10",
324 "/A:0+3+1/B:04-xx_xx/C:24_xx+xx/D:02+xx_xx/E:xx_xx!xx_xx-xx/F:3_3#0_xx@1_1|1_3/G:7_5%1_xx_0/H:xx_xx/I:1-3@1+2&1-2|1+10/J:1_7/K:2+2-10",
325 "/A:xx+xx+xx/B:24-xx_xx/C:xx_xx+xx/D:02+xx_xx/E:3_3!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:7_5%1_xx_xx/H:1_3/I:xx-xx@xx+xx&xx-xx|xx+xx/J:1_7/K:2+2-10",
326 "/A:-4+1+7/B:24-xx_xx/C:02_xx+xx/D:10+7_2/E:3_3!0_xx-0/F:7_5#1_xx@1_1|1_7/G:xx_xx%xx_xx_xx/H:1_3/I:1-7@2+1&2-1|4+7/J:xx_xx/K:2+2-10",
327 "/A:-4+1+7/B:24-xx_xx/C:02_xx+xx/D:10+7_2/E:3_3!0_xx-0/F:7_5#1_xx@1_1|1_7/G:xx_xx%xx_xx_xx/H:1_3/I:1-7@2+1&2-1|4+7/J:xx_xx/K:2+2-10",
328 "/A:-3+2+6/B:24-xx_xx/C:02_xx+xx/D:10+7_2/E:3_3!0_xx-0/F:7_5#1_xx@1_1|1_7/G:xx_xx%xx_xx_xx/H:1_3/I:1-7@2+1&2-1|4+7/J:xx_xx/K:2+2-10",
329 "/A:-2+3+5/B:24-xx_xx/C:02_xx+xx/D:10+7_2/E:3_3!0_xx-0/F:7_5#1_xx@1_1|1_7/G:xx_xx%xx_xx_xx/H:1_3/I:1-7@2+1&2-1|4+7/J:xx_xx/K:2+2-10",
330 "/A:-2+3+5/B:24-xx_xx/C:02_xx+xx/D:10+7_2/E:3_3!0_xx-0/F:7_5#1_xx@1_1|1_7/G:xx_xx%xx_xx_xx/H:1_3/I:1-7@2+1&2-1|4+7/J:xx_xx/K:2+2-10",
331 "/A:-1+4+4/B:24-xx_xx/C:02_xx+xx/D:10+7_2/E:3_3!0_xx-0/F:7_5#1_xx@1_1|1_7/G:xx_xx%xx_xx_xx/H:1_3/I:1-7@2+1&2-1|4+7/J:xx_xx/K:2+2-10",
332 "/A:0+5+3/B:02-xx_xx/C:10_7+2/D:23+xx_xx/E:3_3!0_xx-0/F:7_5#1_xx@1_1|1_7/G:xx_xx%xx_xx_xx/H:1_3/I:1-7@2+1&2-1|4+7/J:xx_xx/K:2+2-10",
333 "/A:0+5+3/B:02-xx_xx/C:10_7+2/D:23+xx_xx/E:3_3!0_xx-0/F:7_5#1_xx@1_1|1_7/G:xx_xx%xx_xx_xx/H:1_3/I:1-7@2+1&2-1|4+7/J:xx_xx/K:2+2-10",
334 "/A:1+6+2/B:02-xx_xx/C:10_7+2/D:23+xx_xx/E:3_3!0_xx-0/F:7_5#1_xx@1_1|1_7/G:xx_xx%xx_xx_xx/H:1_3/I:1-7@2+1&2-1|4+7/J:xx_xx/K:2+2-10",
335 "/A:1+6+2/B:02-xx_xx/C:10_7+2/D:23+xx_xx/E:3_3!0_xx-0/F:7_5#1_xx@1_1|1_7/G:xx_xx%xx_xx_xx/H:1_3/I:1-7@2+1&2-1|4+7/J:xx_xx/K:2+2-10",
336 "/A:2+7+1/B:10-7_2/C:23_xx+xx/D:xx+xx_xx/E:3_3!0_xx-0/F:7_5#1_xx@1_1|1_7/G:xx_xx%xx_xx_xx/H:1_3/I:1-7@2+1&2-1|4+7/J:xx_xx/K:2+2-10",
337 "/A:2+7+1/B:10-7_2/C:23_xx+xx/D:xx+xx_xx/E:3_3!0_xx-0/F:7_5#1_xx@1_1|1_7/G:xx_xx%xx_xx_xx/H:1_3/I:1-7@2+1&2-1|4+7/J:xx_xx/K:2+2-10",
338 "/A:xx+xx+xx/B:23-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:7_5!1_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:xx_xx%xx_xx_xx/H:1_7/I:xx-xx@xx+xx&xx-xx|xx+xx/J:xx_xx/K:2+2-10",
339 ];
340 for i in 0..21 {
341 assert_eq!(v[i].0.as_str(), phonemes[i]);
342 assert_eq!(&v[i].1.to_string_without_phoneme(), features[i]);
343 }
344 }
345
346 #[test]
347 fn generate_no_its_a_smartphone() {
348 let njd = vec![
349 NJDNode::new_single("なに,名詞,代名詞,一般,*,*,*,なに,ナニ,ナニ,1/2,C3,-1"),
350 NJDNode::new_single("を,助詞,格助詞,一般,*,*,*,を,ヲ,ヲ,0/1,動詞%F5/名詞%F1,1"),
351 NJDNode::new_single("言っ,動詞,自立,*,*,五段・ワ行促音便,連用タ接続,言う,イッ,イッ,0/2,*,0"),
352 NJDNode::new_single("て,助詞,接続助詞,*,*,*,*,て,テ,テ,0/1,動詞%F1/形容詞%F1/名詞%F5,1"),
353 NJDNode::new_single("いる,動詞,非自立,*,*,一段,基本形,いる,イル,イル,0/2,動詞%F4@1,0"),
354 NJDNode::new_single("の,名詞,非自立,一般,*,*,*,の,ノ,ノ,2/1,動詞%F2@0/形容詞%F2@-1,0"),
355 NJDNode::new_single("です,助動詞,*,*,*,特殊・デス,基本形,です,デス,デス’,1/2,名詞%F2@1/動詞%F1/形容詞%F2@0,1"),
356 NJDNode::new_single("か,助詞,副助詞/並立助詞/終助詞,*,*,*,*,か,カ,カ,0/1,名詞%F1/動詞%F2@0/形容詞%F2@0,1"),
357 NJDNode::new_single(",,記号,読点,*,*,*,*,,,、,、,0/0,*,0"),
358 NJDNode::new_single("それ,名詞,代名詞,一般,*,*,*,それ,ソレ,ソレ,0/2,C3,0"),
359 NJDNode::new_single("は,助詞,係助詞,*,*,*,*,は,ハ,ワ,0/1,名詞%F1/動詞%F2@0/形容詞%F2@0,1"),
360 NJDNode::new_single("スマホ,名詞,一般,*,*,*,*,スマホ,スマホ,スマホ,4/3,*,0"),
361 NJDNode::new_single("です,助動詞,*,*,*,特殊・デス,基本形,です,デス,デス’,1/2,名詞%F2@1/動詞%F1/形容詞%F2@0,1"),
362 NJDNode::new_single("よ,助詞,終助詞,*,*,*,*,よ,ヨ,ヨ,0/1,名詞%F1/動詞%F1/形容詞%F1,1"),
363 NJDNode::new_single(".,記号,句点,*,*,*,*,.,、,、,0/0,*,0"),
364 ];
365 let utterance = Utterance::from(njd.as_slice());
366 let v = utterance_to_phoneme_vec(&utterance);
367 let phonemes = [
368 "sil", "n", "a", "n", "i", "o", "i", "cl", "t", "e", "i", "r", "u", "n", "o", "d", "e",
369 "s", "U", "k", "a", "pau", "s", "o", "r", "e", "w", "a", "s", "u", "m", "a", "h", "o",
370 "d", "e", "s", "U", "y", "o", "sil",
371 ];
372 let features = [
373 "/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:04+xx_xx/E:xx_xx!xx_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:3_1%0_xx_xx/H:xx_xx/I:xx-xx@xx+xx&xx-xx|xx+xx/J:4_12/K:2+6-21",
374 "/A:0+1+3/B:xx-xx_xx/C:04_xx+xx/D:13+xx_xx/E:xx_xx!xx_xx-xx/F:3_1#0_xx@1_4|1_12/G:3_3%0_xx_1/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
375 "/A:0+1+3/B:xx-xx_xx/C:04_xx+xx/D:13+xx_xx/E:xx_xx!xx_xx-xx/F:3_1#0_xx@1_4|1_12/G:3_3%0_xx_1/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
376 "/A:1+2+2/B:xx-xx_xx/C:04_xx+xx/D:13+xx_xx/E:xx_xx!xx_xx-xx/F:3_1#0_xx@1_4|1_12/G:3_3%0_xx_1/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
377 "/A:1+2+2/B:xx-xx_xx/C:04_xx+xx/D:13+xx_xx/E:xx_xx!xx_xx-xx/F:3_1#0_xx@1_4|1_12/G:3_3%0_xx_1/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
378 "/A:2+3+1/B:04-xx_xx/C:13_xx+xx/D:20+1_1/E:xx_xx!xx_xx-xx/F:3_1#0_xx@1_4|1_12/G:3_3%0_xx_1/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
379 "/A:-2+1+3/B:13-xx_xx/C:20_1+1/D:12+xx_xx/E:3_1!0_xx-1/F:3_3#0_xx@2_3|4_9/G:2_2%0_xx_1/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
380 "/A:-1+2+2/B:13-xx_xx/C:20_1+1/D:12+xx_xx/E:3_1!0_xx-1/F:3_3#0_xx@2_3|4_9/G:2_2%0_xx_1/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
381 "/A:0+3+1/B:20-1_1/C:12_xx+xx/D:17+3_2/E:3_1!0_xx-1/F:3_3#0_xx@2_3|4_9/G:2_2%0_xx_1/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
382 "/A:0+3+1/B:20-1_1/C:12_xx+xx/D:17+3_2/E:3_1!0_xx-1/F:3_3#0_xx@2_3|4_9/G:2_2%0_xx_1/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
383 "/A:-1+1+2/B:12-xx_xx/C:17_3+2/D:22+xx_xx/E:3_3!0_xx-1/F:2_2#0_xx@3_2|7_6/G:4_2%0_xx_1/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
384 "/A:0+2+1/B:12-xx_xx/C:17_3+2/D:22+xx_xx/E:3_3!0_xx-1/F:2_2#0_xx@3_2|7_6/G:4_2%0_xx_1/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
385 "/A:0+2+1/B:12-xx_xx/C:17_3+2/D:22+xx_xx/E:3_3!0_xx-1/F:2_2#0_xx@3_2|7_6/G:4_2%0_xx_1/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
386 "/A:-1+1+4/B:17-3_2/C:22_xx+xx/D:10+7_2/E:2_2!0_xx-1/F:4_2#0_xx@4_1|9_4/G:3_3%0_xx_0/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
387 "/A:-1+1+4/B:17-3_2/C:22_xx+xx/D:10+7_2/E:2_2!0_xx-1/F:4_2#0_xx@4_1|9_4/G:3_3%0_xx_0/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
388 "/A:0+2+3/B:22-xx_xx/C:10_7+2/D:23+xx_xx/E:2_2!0_xx-1/F:4_2#0_xx@4_1|9_4/G:3_3%0_xx_0/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
389 "/A:0+2+3/B:22-xx_xx/C:10_7+2/D:23+xx_xx/E:2_2!0_xx-1/F:4_2#0_xx@4_1|9_4/G:3_3%0_xx_0/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
390 "/A:1+3+2/B:22-xx_xx/C:10_7+2/D:23+xx_xx/E:2_2!0_xx-1/F:4_2#0_xx@4_1|9_4/G:3_3%0_xx_0/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
391 "/A:1+3+2/B:22-xx_xx/C:10_7+2/D:23+xx_xx/E:2_2!0_xx-1/F:4_2#0_xx@4_1|9_4/G:3_3%0_xx_0/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
392 "/A:2+4+1/B:10-7_2/C:23_xx+xx/D:04+xx_xx/E:2_2!0_xx-1/F:4_2#0_xx@4_1|9_4/G:3_3%0_xx_0/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
393 "/A:2+4+1/B:10-7_2/C:23_xx+xx/D:04+xx_xx/E:2_2!0_xx-1/F:4_2#0_xx@4_1|9_4/G:3_3%0_xx_0/H:xx_xx/I:4-12@1+2&1-6|1+21/J:2_9/K:2+6-21",
394 "/A:xx+xx+xx/B:23-xx_xx/C:xx_xx+xx/D:04+xx_xx/E:4_2!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:3_3%0_xx_xx/H:4_12/I:xx-xx@xx+xx&xx-xx|xx+xx/J:2_9/K:2+6-21",
395 "/A:-2+1+3/B:23-xx_xx/C:04_xx+xx/D:24+xx_xx/E:4_2!0_xx-0/F:3_3#0_xx@1_2|1_9/G:6_4%0_xx_1/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
396 "/A:-2+1+3/B:23-xx_xx/C:04_xx+xx/D:24+xx_xx/E:4_2!0_xx-0/F:3_3#0_xx@1_2|1_9/G:6_4%0_xx_1/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
397 "/A:-1+2+2/B:23-xx_xx/C:04_xx+xx/D:24+xx_xx/E:4_2!0_xx-0/F:3_3#0_xx@1_2|1_9/G:6_4%0_xx_1/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
398 "/A:-1+2+2/B:23-xx_xx/C:04_xx+xx/D:24+xx_xx/E:4_2!0_xx-0/F:3_3#0_xx@1_2|1_9/G:6_4%0_xx_1/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
399 "/A:0+3+1/B:04-xx_xx/C:24_xx+xx/D:02+xx_xx/E:4_2!0_xx-0/F:3_3#0_xx@1_2|1_9/G:6_4%0_xx_1/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
400 "/A:0+3+1/B:04-xx_xx/C:24_xx+xx/D:02+xx_xx/E:4_2!0_xx-0/F:3_3#0_xx@1_2|1_9/G:6_4%0_xx_1/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
401 "/A:-3+1+6/B:24-xx_xx/C:02_xx+xx/D:10+7_2/E:3_3!0_xx-1/F:6_4#0_xx@2_1|4_6/G:xx_xx%xx_xx_xx/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
402 "/A:-3+1+6/B:24-xx_xx/C:02_xx+xx/D:10+7_2/E:3_3!0_xx-1/F:6_4#0_xx@2_1|4_6/G:xx_xx%xx_xx_xx/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
403 "/A:-2+2+5/B:24-xx_xx/C:02_xx+xx/D:10+7_2/E:3_3!0_xx-1/F:6_4#0_xx@2_1|4_6/G:xx_xx%xx_xx_xx/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
404 "/A:-2+2+5/B:24-xx_xx/C:02_xx+xx/D:10+7_2/E:3_3!0_xx-1/F:6_4#0_xx@2_1|4_6/G:xx_xx%xx_xx_xx/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
405 "/A:-1+3+4/B:24-xx_xx/C:02_xx+xx/D:10+7_2/E:3_3!0_xx-1/F:6_4#0_xx@2_1|4_6/G:xx_xx%xx_xx_xx/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
406 "/A:-1+3+4/B:24-xx_xx/C:02_xx+xx/D:10+7_2/E:3_3!0_xx-1/F:6_4#0_xx@2_1|4_6/G:xx_xx%xx_xx_xx/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
407 "/A:0+4+3/B:02-xx_xx/C:10_7+2/D:14+xx_xx/E:3_3!0_xx-1/F:6_4#0_xx@2_1|4_6/G:xx_xx%xx_xx_xx/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
408 "/A:0+4+3/B:02-xx_xx/C:10_7+2/D:14+xx_xx/E:3_3!0_xx-1/F:6_4#0_xx@2_1|4_6/G:xx_xx%xx_xx_xx/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
409 "/A:1+5+2/B:02-xx_xx/C:10_7+2/D:14+xx_xx/E:3_3!0_xx-1/F:6_4#0_xx@2_1|4_6/G:xx_xx%xx_xx_xx/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
410 "/A:1+5+2/B:02-xx_xx/C:10_7+2/D:14+xx_xx/E:3_3!0_xx-1/F:6_4#0_xx@2_1|4_6/G:xx_xx%xx_xx_xx/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
411 "/A:2+6+1/B:10-7_2/C:14_xx+xx/D:xx+xx_xx/E:3_3!0_xx-1/F:6_4#0_xx@2_1|4_6/G:xx_xx%xx_xx_xx/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
412 "/A:2+6+1/B:10-7_2/C:14_xx+xx/D:xx+xx_xx/E:3_3!0_xx-1/F:6_4#0_xx@2_1|4_6/G:xx_xx%xx_xx_xx/H:4_12/I:2-9@2+1&5-2|13+9/J:xx_xx/K:2+6-21",
413 "/A:xx+xx+xx/B:14-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:6_4!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:xx_xx%xx_xx_xx/H:2_9/I:xx-xx@xx+xx&xx-xx|xx+xx/J:xx_xx/K:2+6-21",
414 ];
415 for i in 0..41 {
416 assert_eq!(v[i].0.as_str(), phonemes[i]);
417 assert_eq!(&v[i].1.to_string_without_phoneme(), features[i]);
418 }
419 }
420
421 #[test]
422 fn generate_cpp() {
423 let njd = vec![NJDNode::new_single(
425 "C++,名詞,固有名詞,一般,*,*,*,C++,シープラスプラス,シープラス’プラス,6/8,C1,-1",
426 )];
427 let utterance = Utterance::from(njd.as_slice());
428 let v = utterance_to_phoneme_vec(&utterance);
429 let phonemes = [
430 "sil", "sh", "i", "i", "p", "u", "r", "a", "s", "U", "p", "u", "r", "a", "s", "u",
431 "sil",
432 ];
433 let features = [
434 "/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:8_6%0_xx_xx/H:xx_xx/I:xx-xx@xx+xx&xx-xx|xx+xx/J:1_8/K:1+1-8",
435 "/A:-5+1+8/B:xx-xx_xx/C:18_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:8_6#0_xx@1_1|1_8/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-8@1+1&1-1|1+8/J:xx_xx/K:1+1-8",
436 "/A:-5+1+8/B:xx-xx_xx/C:18_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:8_6#0_xx@1_1|1_8/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-8@1+1&1-1|1+8/J:xx_xx/K:1+1-8",
437 "/A:-4+2+7/B:xx-xx_xx/C:18_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:8_6#0_xx@1_1|1_8/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-8@1+1&1-1|1+8/J:xx_xx/K:1+1-8",
438 "/A:-3+3+6/B:xx-xx_xx/C:18_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:8_6#0_xx@1_1|1_8/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-8@1+1&1-1|1+8/J:xx_xx/K:1+1-8",
439 "/A:-3+3+6/B:xx-xx_xx/C:18_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:8_6#0_xx@1_1|1_8/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-8@1+1&1-1|1+8/J:xx_xx/K:1+1-8",
440 "/A:-2+4+5/B:xx-xx_xx/C:18_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:8_6#0_xx@1_1|1_8/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-8@1+1&1-1|1+8/J:xx_xx/K:1+1-8",
441 "/A:-2+4+5/B:xx-xx_xx/C:18_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:8_6#0_xx@1_1|1_8/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-8@1+1&1-1|1+8/J:xx_xx/K:1+1-8",
442 "/A:-1+5+4/B:xx-xx_xx/C:18_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:8_6#0_xx@1_1|1_8/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-8@1+1&1-1|1+8/J:xx_xx/K:1+1-8",
443 "/A:-1+5+4/B:xx-xx_xx/C:18_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:8_6#0_xx@1_1|1_8/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-8@1+1&1-1|1+8/J:xx_xx/K:1+1-8",
444 "/A:0+6+3/B:xx-xx_xx/C:18_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:8_6#0_xx@1_1|1_8/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-8@1+1&1-1|1+8/J:xx_xx/K:1+1-8",
445 "/A:0+6+3/B:xx-xx_xx/C:18_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:8_6#0_xx@1_1|1_8/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-8@1+1&1-1|1+8/J:xx_xx/K:1+1-8",
446 "/A:1+7+2/B:xx-xx_xx/C:18_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:8_6#0_xx@1_1|1_8/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-8@1+1&1-1|1+8/J:xx_xx/K:1+1-8",
447 "/A:1+7+2/B:xx-xx_xx/C:18_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:8_6#0_xx@1_1|1_8/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-8@1+1&1-1|1+8/J:xx_xx/K:1+1-8",
448 "/A:2+8+1/B:xx-xx_xx/C:18_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:8_6#0_xx@1_1|1_8/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-8@1+1&1-1|1+8/J:xx_xx/K:1+1-8",
449 "/A:2+8+1/B:xx-xx_xx/C:18_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:8_6#0_xx@1_1|1_8/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-8@1+1&1-1|1+8/J:xx_xx/K:1+1-8",
450 "/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:8_6!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:xx_xx%xx_xx_xx/H:1_8/I:xx-xx@xx+xx&xx-xx|xx+xx/J:xx_xx/K:1+1-8",
451 ];
452 for i in 0..17 {
453 assert_eq!(v[i].0.as_str(), phonemes[i]);
454 assert_eq!(&v[i].1.to_string_without_phoneme(), features[i]);
455 }
456 }
457
458 #[test]
459 fn generate_weird_sake() {
460 let njd = vec![
461 NJDNode::new_single("――,記号,*,*,*,*,*,――,、,、,0/0,*,-1"),
462 NJDNode::new_single("酒,名詞,接尾,一般,*,*,*,酒,シュ,シュ,0/1,C3,1"),
463 ];
464 let utterance = Utterance::from(njd.as_slice());
465 let v = utterance_to_phoneme_vec(&utterance);
466 let phonemes = ["sil", "sh", "u", "sil"];
467 let features = [
468 "/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:1_1%0_xx_xx/H:xx_xx/I:xx-xx@xx+xx&xx-xx|xx+xx/J:1_1/K:1+1-1",
469 "/A:0+1+1/B:xx-xx_xx/C:15_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:1_1#0_xx@1_1|1_1/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-1@1+1&1-1|1+1/J:xx_xx/K:1+1-1",
470 "/A:0+1+1/B:xx-xx_xx/C:15_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:1_1#0_xx@1_1|1_1/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-1@1+1&1-1|1+1/J:xx_xx/K:1+1-1",
471 "/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:1_1!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:xx_xx%xx_xx_xx/H:1_1/I:xx-xx@xx+xx&xx-xx|xx+xx/J:xx_xx/K:1+1-1",
472 ];
473 for i in 0..4 {
474 assert_eq!(v[i].0.as_str(), phonemes[i]);
475 assert_eq!(&v[i].1.to_string_without_phoneme(), features[i]);
476 }
477 }
478}