Skip to main content

jlabel_question/
position.rs

1//! Structures for position
2
3use std::{fmt::Debug, ops::Range};
4
5use crate::Label;
6
7use super::ParseError;
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12/// Enum that represent all positions
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum AllPosition {
15    /// Phone fields
16    Phone(PhonePosition),
17    /// Signed integer fields
18    SignedRange(SignedRangePosition),
19    /// Unsigned integer fields
20    UnsignedRange(UnsignedRangePosition),
21    /// Boolean fields
22    Boolean(BooleanPosition),
23    /// Numerical categorical fields
24    Category(CategoryPosition),
25    /// Undefined (always `xx`) fields
26    Undefined(UndefinedPotision),
27}
28
29macro_rules! as_ref_map {
30    ($label:ident.$block:ident.$prop:ident) => {
31        $label.$block.as_ref().map(|b| &b.$prop)
32    };
33}
34
35macro_rules! as_ref_and_then {
36    ($label:ident.$block:ident.$prop:ident) => {
37        $label.$block.as_ref().and_then(|b| b.$prop.as_ref())
38    };
39}
40
41/// The trait that Position requires to implement
42pub trait Position {
43    /// The type of match target
44    type Target;
45    /// The type of range
46    type Range;
47
48    /// Parse range strings
49    fn range(&self, ranges: &[&str]) -> Result<Self::Range, ParseError>;
50    /// Get part of [`Label`] this position matches to.
51    fn get<'a>(&self, label: &'a Label) -> Option<&'a Self::Target>;
52    /// Check if the range matches target
53    fn test(&self, range: &Self::Range, target: &Self::Target) -> bool;
54}
55
56/// Positions of phone fields
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
59#[allow(missing_docs)]
60pub enum PhonePosition {
61    P1,
62    P2,
63    P3,
64    P4,
65    P5,
66}
67
68impl Position for PhonePosition {
69    type Target = String;
70    type Range = Vec<String>;
71
72    fn range(&self, ranges: &[&str]) -> Result<Self::Range, ParseError> {
73        Ok(ranges.iter().map(|s| s.to_string()).collect())
74    }
75
76    fn get<'a>(&self, label: &'a Label) -> Option<&'a Self::Target> {
77        match self {
78            Self::P1 => label.phoneme.p2.as_ref(),
79            Self::P2 => label.phoneme.p1.as_ref(),
80            Self::P3 => label.phoneme.c.as_ref(),
81            Self::P4 => label.phoneme.n1.as_ref(),
82            Self::P5 => label.phoneme.n2.as_ref(),
83        }
84    }
85
86    fn test(&self, range: &Self::Range, target: &Self::Target) -> bool {
87        range.contains(target)
88    }
89}
90
91/// Positions with signed integer type
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
94#[allow(missing_docs)]
95pub enum SignedRangePosition {
96    A1,
97}
98
99impl Position for SignedRangePosition {
100    type Target = i8;
101    type Range = Range<i8>;
102
103    fn range(&self, ranges: &[&str]) -> Result<Self::Range, ParseError> {
104        let parsed_ranges = ranges.iter().map(range_i8).collect::<Result<Vec<_>, _>>()?;
105        merge_ranges(parsed_ranges)
106    }
107
108    fn get<'a>(&self, label: &'a Label) -> Option<&'a Self::Target> {
109        match self {
110            Self::A1 => as_ref_map!(label.mora.relative_accent_position),
111        }
112    }
113
114    fn test(&self, range: &Self::Range, target: &Self::Target) -> bool {
115        range.contains(target)
116    }
117}
118
119fn range_i8<S: AsRef<str>>(s: S) -> Result<Range<i8>, ParseError> {
120    let range = match s.as_ref() {
121        "-??" => -99..-9,
122        "-?" => -9..0,
123        "?" => 0..10,
124        s if s.ends_with('?') => {
125            let d = s[..s.len() - 1]
126                .parse::<i8>()
127                .map_err(ParseError::FailWildcard)?;
128            if d >= 0 {
129                d * 10..(d + 1) * 10
130            } else {
131                (d - 1) * 10 + 1..d * 10 + 1
132            }
133        }
134        s => {
135            let d = s.parse::<i8>().map_err(ParseError::FailLiteral)?;
136            d..d + 1
137        }
138    };
139    Ok(range)
140}
141
142/// Positions with unsigned integer type
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
145#[allow(missing_docs)]
146pub enum UnsignedRangePosition {
147    A2,
148    A3,
149
150    E1,
151    E2,
152
153    F1,
154    F2,
155    F5,
156    F6,
157    F7,
158    F8,
159
160    G1,
161    G2,
162
163    H1,
164    H2,
165
166    I1,
167    I2,
168    I3,
169    I4,
170    I5,
171    I6,
172    I7,
173    I8,
174
175    J1,
176    J2,
177
178    K1,
179    K2,
180    K3,
181}
182
183impl Position for UnsignedRangePosition {
184    type Target = u8;
185    type Range = Range<u8>;
186
187    fn range(&self, ranges: &[&str]) -> Result<Self::Range, ParseError> {
188        let parsed_ranges = ranges.iter().map(range_u8).collect::<Result<Vec<_>, _>>()?;
189        merge_ranges(parsed_ranges)
190    }
191
192    fn get<'a>(&self, label: &'a Label) -> Option<&'a Self::Target> {
193        match self {
194            Self::A2 => as_ref_map!(label.mora.position_forward),
195            Self::A3 => as_ref_map!(label.mora.position_backward),
196            Self::E1 => as_ref_map!(label.accent_phrase_prev.mora_count),
197            Self::E2 => as_ref_map!(label.accent_phrase_prev.accent_position),
198            Self::F1 => as_ref_map!(label.accent_phrase_curr.mora_count),
199            Self::F2 => as_ref_map!(label.accent_phrase_curr.accent_position),
200            Self::F5 => as_ref_map!(label.accent_phrase_curr.accent_phrase_position_forward),
201            Self::F6 => as_ref_map!(label.accent_phrase_curr.accent_phrase_position_backward),
202            Self::F7 => as_ref_map!(label.accent_phrase_curr.mora_position_forward),
203            Self::F8 => as_ref_map!(label.accent_phrase_curr.mora_position_backward),
204            Self::G1 => as_ref_map!(label.accent_phrase_next.mora_count),
205            Self::G2 => as_ref_map!(label.accent_phrase_next.accent_position),
206            Self::H1 => as_ref_map!(label.breath_group_prev.accent_phrase_count),
207            Self::H2 => as_ref_map!(label.breath_group_prev.mora_count),
208            Self::I1 => as_ref_map!(label.breath_group_curr.accent_phrase_count),
209            Self::I2 => as_ref_map!(label.breath_group_curr.mora_count),
210            Self::I3 => as_ref_map!(label.breath_group_curr.breath_group_position_forward),
211            Self::I4 => as_ref_map!(label.breath_group_curr.breath_group_position_backward),
212            Self::I5 => as_ref_map!(label.breath_group_curr.accent_phrase_position_forward),
213            Self::I6 => as_ref_map!(label.breath_group_curr.accent_phrase_position_backward),
214            Self::I7 => as_ref_map!(label.breath_group_curr.mora_position_forward),
215            Self::I8 => as_ref_map!(label.breath_group_curr.mora_position_backward),
216            Self::J1 => as_ref_map!(label.breath_group_next.accent_phrase_count),
217            Self::J2 => as_ref_map!(label.breath_group_next.mora_count),
218            Self::K1 => Some(&label.utterance.breath_group_count),
219            Self::K2 => Some(&label.utterance.accent_phrase_count),
220            Self::K3 => Some(&label.utterance.mora_count),
221        }
222    }
223
224    fn test(&self, range: &Self::Range, target: &Self::Target) -> bool {
225        range.contains(target)
226    }
227}
228
229fn range_u8<S: AsRef<str>>(s: S) -> Result<Range<u8>, ParseError> {
230    let range = match s.as_ref() {
231        "?" => 1..10,
232        s if s.ends_with('?') => {
233            let d = s[..s.len() - 1]
234                .parse::<u8>()
235                .map_err(ParseError::FailWildcard)?;
236            d * 10..(d + 1) * 10
237        }
238        s => {
239            let d = s.parse::<u8>().map_err(ParseError::FailLiteral)?;
240            d..d + 1
241        }
242    };
243    Ok(range)
244}
245
246fn merge_ranges<Idx>(mut ranges: Vec<Range<Idx>>) -> Result<Range<Idx>, ParseError>
247where
248    Idx: Ord + Copy,
249{
250    ranges.sort_unstable_by_key(|range| range.start);
251    let merged = ranges
252        .into_iter()
253        .try_fold(None, |acc: Option<Range<Idx>>, curr| match acc {
254            // By sorting, always acc.start <= curr.start
255            // Only need to check curr's start is continuous with acc's end
256            Some(mut acc) if curr.start <= acc.end => {
257                acc.end = acc.end.max(curr.end);
258                Ok(Some(acc))
259            }
260            None => Ok(Some(curr)),
261            _ => Err(ParseError::IncontinuousRange),
262        })?;
263    merged.ok_or(ParseError::Empty)
264}
265
266/// Positions with boolean type
267#[derive(Debug, Clone, Copy, PartialEq, Eq)]
268#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
269#[allow(missing_docs)]
270pub enum BooleanPosition {
271    E3,
272    E5,
273
274    F3,
275
276    G3,
277    G5,
278}
279
280impl Position for BooleanPosition {
281    type Target = bool;
282    type Range = bool;
283
284    fn range(&self, ranges: &[&str]) -> Result<Self::Range, ParseError> {
285        let first = ranges.first().ok_or(ParseError::Empty)?;
286        // E5/G5's logics are inverted
287        let field_false = matches!(self, Self::E5 | Self::G5);
288        match *first {
289            "0" => Ok(field_false),
290            "1" => Ok(!field_false),
291            _ => Err(ParseError::InvalidBoolean(first.to_string())),
292        }
293    }
294
295    fn get<'a>(&self, label: &'a Label) -> Option<&'a Self::Target> {
296        match self {
297            Self::E3 => as_ref_map!(label.accent_phrase_prev.is_interrogative),
298            Self::E5 => as_ref_and_then!(label.accent_phrase_prev.is_pause_insertion),
299            Self::F3 => as_ref_map!(label.accent_phrase_curr.is_interrogative),
300            Self::G3 => as_ref_map!(label.accent_phrase_next.is_interrogative),
301            Self::G5 => as_ref_and_then!(label.accent_phrase_next.is_pause_insertion),
302        }
303    }
304
305    fn test(&self, range: &Self::Range, target: &Self::Target) -> bool {
306        range == target
307    }
308}
309
310/// Positions with numerical representations of categorical value
311#[derive(Debug, Clone, Copy, PartialEq, Eq)]
312#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
313#[allow(missing_docs)]
314pub enum CategoryPosition {
315    B1,
316    B2,
317    B3,
318    C1,
319    C2,
320    C3,
321    D1,
322    D2,
323    D3,
324}
325
326impl Position for CategoryPosition {
327    type Target = u8;
328    type Range = Vec<u8>;
329
330    fn range(&self, ranges: &[&str]) -> Result<Self::Range, ParseError> {
331        ranges
332            .iter()
333            .map(|s| s.parse::<u8>().map_err(ParseError::FailLiteral))
334            .collect()
335    }
336
337    fn get<'a>(&self, label: &'a Label) -> Option<&'a Self::Target> {
338        match self {
339            Self::B1 => as_ref_and_then!(label.word_prev.pos),
340            Self::B2 => as_ref_and_then!(label.word_prev.ctype),
341            Self::B3 => as_ref_and_then!(label.word_prev.cform),
342            Self::C1 => as_ref_and_then!(label.word_curr.pos),
343            Self::C2 => as_ref_and_then!(label.word_curr.ctype),
344            Self::C3 => as_ref_and_then!(label.word_curr.cform),
345            Self::D1 => as_ref_and_then!(label.word_next.pos),
346            Self::D2 => as_ref_and_then!(label.word_next.ctype),
347            Self::D3 => as_ref_and_then!(label.word_next.cform),
348        }
349    }
350
351    fn test(&self, range: &Self::Range, target: &Self::Target) -> bool {
352        range.contains(target)
353    }
354}
355
356/// Positions that are always `xx`
357#[derive(Debug, Clone, Copy, PartialEq, Eq)]
358#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
359#[allow(missing_docs)]
360pub enum UndefinedPotision {
361    E4,
362    F4,
363    G4,
364}
365
366impl Position for UndefinedPotision {
367    type Target = ();
368    type Range = ();
369
370    fn range(&self, _: &[&str]) -> Result<Self::Range, ParseError> {
371        Ok(())
372    }
373
374    fn get<'a>(&self, _: &'a Label) -> Option<&'a Self::Target> {
375        None
376    }
377
378    fn test(&self, _: &Self::Range, _: &Self::Target) -> bool {
379        true
380    }
381}
382
383#[cfg(test)]
384mod tests {
385    use super::*;
386
387    #[test]
388    fn parse_i8_range() {
389        assert_eq!(range_i8("12"), Ok(12..13));
390        assert_eq!(range_i8("1?"), Ok(10..20));
391        assert_eq!(range_i8("?"), Ok(0..10));
392
393        assert_eq!(range_i8("-12"), Ok(-12..-11));
394        assert_eq!(range_i8("-?"), Ok(-9..0));
395        assert_eq!(range_i8("-??"), Ok(-99..-9));
396
397        assert_eq!(range_i8("-1?"), Ok(-19..-9));
398    }
399
400    #[test]
401    fn parse_u8_range() {
402        assert_eq!(range_u8("12"), Ok(12..13));
403        assert_eq!(range_u8("1?"), Ok(10..20));
404        assert_eq!(range_u8("12?"), Ok(120..130));
405        assert_eq!(range_u8("?"), Ok(1..10));
406    }
407
408    #[test]
409    fn range_fail() {
410        use std::num::IntErrorKind;
411        assert!(matches!(
412            range_u8("?2"),
413            Err(ParseError::FailLiteral(e)) if *e.kind() == IntErrorKind::InvalidDigit
414        ));
415        assert!(matches!(
416            range_i8("?2"),
417            Err(ParseError::FailLiteral(e)) if *e.kind() == IntErrorKind::InvalidDigit
418        ));
419
420        assert!(matches!(
421            range_u8("???"),
422            Err(ParseError::FailWildcard(e)) if *e.kind() == IntErrorKind::InvalidDigit
423        ));
424        assert!(matches!(
425            range_i8("???"),
426            Err(ParseError::FailWildcard(e)) if *e.kind() == IntErrorKind::InvalidDigit
427        ));
428    }
429
430    #[test]
431    #[allow(clippy::single_range_in_vec_init)]
432    fn merge_ranges_1() {
433        assert_eq!(merge_ranges(vec![0..1]), Ok(0..1));
434        assert_eq!(merge_ranges(vec![0..1, 1..3]), Ok(0..3));
435        assert_eq!(merge_ranges(vec![1..3, 0..1]), Ok(0..3));
436        assert_eq!(merge_ranges(vec![0..2, 1..3]), Ok(0..3));
437        assert_eq!(merge_ranges(vec![-6..7, 1..3]), Ok(-6..7));
438        assert_eq!(
439            merge_ranges(vec![-6..7, 1..3, 2..6, -8..-7, -8..0]),
440            Ok(-8..7)
441        );
442
443        assert_eq!(merge_ranges::<u8>(vec![]), Err(ParseError::Empty));
444        assert_eq!(
445            merge_ranges(vec![0..1, 5..6]),
446            Err(ParseError::IncontinuousRange)
447        );
448        assert_eq!(
449            merge_ranges(vec![3..6, -1..2]),
450            Err(ParseError::IncontinuousRange)
451        );
452        assert_eq!(
453            merge_ranges(vec![-6..7, 1..3, 2..6, -8..-7]),
454            Err(ParseError::IncontinuousRange)
455        );
456    }
457}