Coverage Report

Created: 2025-12-14 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/unicode-normalization/src/recompose.rs
Line
Count
Source
1
// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
2
// file at the top-level directory of this distribution and at
3
// http://rust-lang.org/COPYRIGHT.
4
//
5
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8
// option. This file may not be copied, modified, or distributed
9
// except according to those terms.
10
11
use crate::decompose::Decompositions;
12
use core::{
13
    fmt::{self, Write},
14
    iter::FusedIterator,
15
};
16
use tinyvec::TinyVec;
17
18
#[derive(Clone)]
19
enum RecompositionState {
20
    Composing,
21
    Purging(usize),
22
    Finished(usize),
23
}
24
25
/// External iterator for a string recomposition's characters.
26
#[derive(Clone)]
27
pub struct Recompositions<I> {
28
    iter: Decompositions<I>,
29
    state: RecompositionState,
30
    buffer: TinyVec<[char; 4]>,
31
    composee: Option<char>,
32
    last_ccc: Option<u8>,
33
}
34
35
impl<I: Iterator<Item = char>> Recompositions<I> {
36
    /// Create a new recomposition iterator for canonical compositions (NFC)
37
    ///
38
    /// Note that this iterator can also be obtained by directly calling [`.nfc()`](crate::UnicodeNormalization::nfc)
39
    /// on the iterator.
40
    #[inline]
41
23.5k
    pub fn new_canonical(iter: I) -> Self {
42
23.5k
        Recompositions {
43
23.5k
            iter: Decompositions::new_canonical(iter),
44
23.5k
            state: self::RecompositionState::Composing,
45
23.5k
            buffer: TinyVec::new(),
46
23.5k
            composee: None,
47
23.5k
            last_ccc: None,
48
23.5k
        }
49
23.5k
    }
<unicode_normalization::recompose::Recompositions<unicode_normalization::stream_safe::StreamSafe<core::str::iter::Chars>>>::new_canonical
Line
Count
Source
41
11.3k
    pub fn new_canonical(iter: I) -> Self {
42
11.3k
        Recompositions {
43
11.3k
            iter: Decompositions::new_canonical(iter),
44
11.3k
            state: self::RecompositionState::Composing,
45
11.3k
            buffer: TinyVec::new(),
46
11.3k
            composee: None,
47
11.3k
            last_ccc: None,
48
11.3k
        }
49
11.3k
    }
<unicode_normalization::recompose::Recompositions<core::str::iter::Chars>>::new_canonical
Line
Count
Source
41
9.12k
    pub fn new_canonical(iter: I) -> Self {
42
9.12k
        Recompositions {
43
9.12k
            iter: Decompositions::new_canonical(iter),
44
9.12k
            state: self::RecompositionState::Composing,
45
9.12k
            buffer: TinyVec::new(),
46
9.12k
            composee: None,
47
9.12k
            last_ccc: None,
48
9.12k
        }
49
9.12k
    }
Unexecuted instantiation: <unicode_normalization::recompose::Recompositions<_>>::new_canonical
<unicode_normalization::recompose::Recompositions<streaming::Counter>>::new_canonical
Line
Count
Source
41
2.17k
    pub fn new_canonical(iter: I) -> Self {
42
2.17k
        Recompositions {
43
2.17k
            iter: Decompositions::new_canonical(iter),
44
2.17k
            state: self::RecompositionState::Composing,
45
2.17k
            buffer: TinyVec::new(),
46
2.17k
            composee: None,
47
2.17k
            last_ccc: None,
48
2.17k
        }
49
2.17k
    }
<unicode_normalization::recompose::Recompositions<core::str::iter::Chars>>::new_canonical
Line
Count
Source
41
843
    pub fn new_canonical(iter: I) -> Self {
42
843
        Recompositions {
43
843
            iter: Decompositions::new_canonical(iter),
44
843
            state: self::RecompositionState::Composing,
45
843
            buffer: TinyVec::new(),
46
843
            composee: None,
47
843
            last_ccc: None,
48
843
        }
49
843
    }
50
51
    /// Create a new recomposition iterator for compatability compositions (NFkC)
52
    ///
53
    /// Note that this iterator can also be obtained by directly calling [`.nfkc()`](crate::UnicodeNormalization::nfkc)
54
    /// on the iterator.
55
    #[inline]
56
10.2k
    pub fn new_compatible(iter: I) -> Self {
57
10.2k
        Recompositions {
58
10.2k
            iter: Decompositions::new_compatible(iter),
59
10.2k
            state: self::RecompositionState::Composing,
60
10.2k
            buffer: TinyVec::new(),
61
10.2k
            composee: None,
62
10.2k
            last_ccc: None,
63
10.2k
        }
64
10.2k
    }
<unicode_normalization::recompose::Recompositions<core::str::iter::Chars>>::new_compatible
Line
Count
Source
56
8.93k
    pub fn new_compatible(iter: I) -> Self {
57
8.93k
        Recompositions {
58
8.93k
            iter: Decompositions::new_compatible(iter),
59
8.93k
            state: self::RecompositionState::Composing,
60
8.93k
            buffer: TinyVec::new(),
61
8.93k
            composee: None,
62
8.93k
            last_ccc: None,
63
8.93k
        }
64
8.93k
    }
Unexecuted instantiation: <unicode_normalization::recompose::Recompositions<_>>::new_compatible
<unicode_normalization::recompose::Recompositions<core::str::iter::Chars>>::new_compatible
Line
Count
Source
56
1.34k
    pub fn new_compatible(iter: I) -> Self {
57
1.34k
        Recompositions {
58
1.34k
            iter: Decompositions::new_compatible(iter),
59
1.34k
            state: self::RecompositionState::Composing,
60
1.34k
            buffer: TinyVec::new(),
61
1.34k
            composee: None,
62
1.34k
            last_ccc: None,
63
1.34k
        }
64
1.34k
    }
65
}
66
67
impl<I: Iterator<Item = char>> Iterator for Recompositions<I> {
68
    type Item = char;
69
70
    #[inline]
71
303M
    fn next(&mut self) -> Option<char> {
72
        use self::RecompositionState::*;
73
74
        loop {
75
305M
            match self.state {
76
                Composing => {
77
311M
                    for ch in self.iter.by_ref() {
78
311M
                        let ch_class = super::char::canonical_combining_class(ch);
79
311M
                        let k = match self.composee {
80
                            None => {
81
4.35M
                                if ch_class != 0 {
82
4.32M
                                    return Some(ch);
83
31.6k
                                }
84
31.6k
                                self.composee = Some(ch);
85
31.6k
                                continue;
86
                            }
87
307M
                            Some(k) => k,
88
                        };
89
307M
                        match self.last_ccc {
90
112M
                            None => match super::char::compose(k, ch) {
91
8.57M
                                Some(r) => {
92
8.57M
                                    self.composee = Some(r);
93
8.57M
                                    continue;
94
                                }
95
                                None => {
96
104M
                                    if ch_class == 0 {
97
101M
                                        self.composee = Some(ch);
98
101M
                                        return Some(k);
99
2.50M
                                    }
100
2.50M
                                    self.buffer.push(ch);
101
2.50M
                                    self.last_ccc = Some(ch_class);
102
                                }
103
                            },
104
194M
                            Some(l_class) => {
105
194M
                                if l_class >= ch_class {
106
                                    // `ch` is blocked from `composee`
107
194M
                                    if ch_class == 0 {
108
2.49M
                                        self.composee = Some(ch);
109
2.49M
                                        self.last_ccc = None;
110
2.49M
                                        self.state = Purging(0);
111
2.49M
                                        return Some(k);
112
191M
                                    }
113
191M
                                    self.buffer.push(ch);
114
191M
                                    self.last_ccc = Some(ch_class);
115
191M
                                    continue;
116
462k
                                }
117
462k
                                match super::char::compose(k, ch) {
118
27.0k
                                    Some(r) => {
119
27.0k
                                        self.composee = Some(r);
120
27.0k
                                        continue;
121
                                    }
122
435k
                                    None => {
123
435k
                                        self.buffer.push(ch);
124
435k
                                        self.last_ccc = Some(ch_class);
125
435k
                                    }
126
                                }
127
                            }
128
                        }
129
                    }
130
33.4k
                    self.state = Finished(0);
131
33.4k
                    if self.composee.is_some() {
132
31.2k
                        return self.composee.take();
133
2.19k
                    }
134
                }
135
150M
                Purging(next) => match self.buffer.get(next).cloned() {
136
2.49M
                    None => {
137
2.49M
                        self.buffer.clear();
138
2.49M
                        self.state = Composing;
139
2.49M
                    }
140
148M
                    s => {
141
148M
                        self.state = Purging(next + 1);
142
148M
                        return s;
143
                    }
144
                },
145
46.3M
                Finished(next) => match self.buffer.get(next).cloned() {
146
                    None => {
147
33.2k
                        self.buffer.clear();
148
33.2k
                        return self.composee.take();
149
                    }
150
46.3M
                    s => {
151
46.3M
                        self.state = Finished(next + 1);
152
46.3M
                        return s;
153
                    }
154
                },
155
            }
156
        }
157
303M
    }
<unicode_normalization::recompose::Recompositions<unicode_normalization::stream_safe::StreamSafe<core::str::iter::Chars>> as core::iter::traits::iterator::Iterator>::next
Line
Count
Source
71
88.8M
    fn next(&mut self) -> Option<char> {
72
        use self::RecompositionState::*;
73
74
        loop {
75
91.1M
            match self.state {
76
                Composing => {
77
92.2M
                    for ch in self.iter.by_ref() {
78
92.2M
                        let ch_class = super::char::canonical_combining_class(ch);
79
92.2M
                        let k = match self.composee {
80
                            None => {
81
35.6k
                                if ch_class != 0 {
82
24.7k
                                    return Some(ch);
83
10.8k
                                }
84
10.8k
                                self.composee = Some(ch);
85
10.8k
                                continue;
86
                            }
87
92.1M
                            Some(k) => k,
88
                        };
89
92.1M
                        match self.last_ccc {
90
26.5M
                            None => match super::char::compose(k, ch) {
91
3.31M
                                Some(r) => {
92
3.31M
                                    self.composee = Some(r);
93
3.31M
                                    continue;
94
                                }
95
                                None => {
96
23.2M
                                    if ch_class == 0 {
97
21.0M
                                        self.composee = Some(ch);
98
21.0M
                                        return Some(k);
99
2.21M
                                    }
100
2.21M
                                    self.buffer.push(ch);
101
2.21M
                                    self.last_ccc = Some(ch_class);
102
                                }
103
                            },
104
65.6M
                            Some(l_class) => {
105
65.6M
                                if l_class >= ch_class {
106
                                    // `ch` is blocked from `composee`
107
65.3M
                                    if ch_class == 0 {
108
2.21M
                                        self.composee = Some(ch);
109
2.21M
                                        self.last_ccc = None;
110
2.21M
                                        self.state = Purging(0);
111
2.21M
                                        return Some(k);
112
63.1M
                                    }
113
63.1M
                                    self.buffer.push(ch);
114
63.1M
                                    self.last_ccc = Some(ch_class);
115
63.1M
                                    continue;
116
271k
                                }
117
271k
                                match super::char::compose(k, ch) {
118
7.78k
                                    Some(r) => {
119
7.78k
                                        self.composee = Some(r);
120
7.78k
                                        continue;
121
                                    }
122
264k
                                    None => {
123
264k
                                        self.buffer.push(ch);
124
264k
                                        self.last_ccc = Some(ch_class);
125
264k
                                    }
126
                                }
127
                            }
128
                        }
129
                    }
130
11.2k
                    self.state = Finished(0);
131
11.2k
                    if self.composee.is_some() {
132
10.7k
                        return self.composee.take();
133
503
                    }
134
                }
135
67.8M
                Purging(next) => match self.buffer.get(next).cloned() {
136
2.21M
                    None => {
137
2.21M
                        self.buffer.clear();
138
2.21M
                        self.state = Composing;
139
2.21M
                    }
140
65.5M
                    s => {
141
65.5M
                        self.state = Purging(next + 1);
142
65.5M
                        return s;
143
                    }
144
                },
145
44.3k
                Finished(next) => match self.buffer.get(next).cloned() {
146
                    None => {
147
11.1k
                        self.buffer.clear();
148
11.1k
                        return self.composee.take();
149
                    }
150
33.1k
                    s => {
151
33.1k
                        self.state = Finished(next + 1);
152
33.1k
                        return s;
153
                    }
154
                },
155
            }
156
        }
157
88.8M
    }
<unicode_normalization::recompose::Recompositions<core::str::iter::Chars> as core::iter::traits::iterator::Iterator>::next
Line
Count
Source
71
147M
    fn next(&mut self) -> Option<char> {
72
        use self::RecompositionState::*;
73
74
        loop {
75
147M
            match self.state {
76
                Composing => {
77
152M
                    for ch in self.iter.by_ref() {
78
152M
                        let ch_class = super::char::canonical_combining_class(ch);
79
152M
                        let k = match self.composee {
80
                            None => {
81
95.1k
                                if ch_class != 0 {
82
78.2k
                                    return Some(ch);
83
16.9k
                                }
84
16.9k
                                self.composee = Some(ch);
85
16.9k
                                continue;
86
                            }
87
152M
                            Some(k) => k,
88
                        };
89
152M
                        match self.last_ccc {
90
64.9M
                            None => match super::char::compose(k, ch) {
91
5.23M
                                Some(r) => {
92
5.23M
                                    self.composee = Some(r);
93
5.23M
                                    continue;
94
                                }
95
                                None => {
96
59.6M
                                    if ch_class == 0 {
97
59.6M
                                        self.composee = Some(ch);
98
59.6M
                                        return Some(k);
99
36.0k
                                    }
100
36.0k
                                    self.buffer.push(ch);
101
36.0k
                                    self.last_ccc = Some(ch_class);
102
                                }
103
                            },
104
87.5M
                            Some(l_class) => {
105
87.5M
                                if l_class >= ch_class {
106
                                    // `ch` is blocked from `composee`
107
87.4M
                                    if ch_class == 0 {
108
30.6k
                                        self.composee = Some(ch);
109
30.6k
                                        self.last_ccc = None;
110
30.6k
                                        self.state = Purging(0);
111
30.6k
                                        return Some(k);
112
87.4M
                                    }
113
87.4M
                                    self.buffer.push(ch);
114
87.4M
                                    self.last_ccc = Some(ch_class);
115
87.4M
                                    continue;
116
57.2k
                                }
117
57.2k
                                match super::char::compose(k, ch) {
118
11.6k
                                    Some(r) => {
119
11.6k
                                        self.composee = Some(r);
120
11.6k
                                        continue;
121
                                    }
122
45.6k
                                    None => {
123
45.6k
                                        self.buffer.push(ch);
124
45.6k
                                        self.last_ccc = Some(ch_class);
125
45.6k
                                    }
126
                                }
127
                            }
128
                        }
129
                    }
130
17.7k
                    self.state = Finished(0);
131
17.7k
                    if self.composee.is_some() {
132
16.6k
                        return self.composee.take();
133
1.15k
                    }
134
                }
135
59.7M
                Purging(next) => match self.buffer.get(next).cloned() {
136
30.4k
                    None => {
137
30.4k
                        self.buffer.clear();
138
30.4k
                        self.state = Composing;
139
30.4k
                    }
140
59.7M
                    s => {
141
59.7M
                        self.state = Purging(next + 1);
142
59.7M
                        return s;
143
                    }
144
                },
145
27.8M
                Finished(next) => match self.buffer.get(next).cloned() {
146
                    None => {
147
17.6k
                        self.buffer.clear();
148
17.6k
                        return self.composee.take();
149
                    }
150
27.7M
                    s => {
151
27.7M
                        self.state = Finished(next + 1);
152
27.7M
                        return s;
153
                    }
154
                },
155
            }
156
        }
157
147M
    }
Unexecuted instantiation: <unicode_normalization::recompose::Recompositions<_> as core::iter::traits::iterator::Iterator>::next
<unicode_normalization::recompose::Recompositions<streaming::Counter> as core::iter::traits::iterator::Iterator>::next
Line
Count
Source
71
14.8M
    fn next(&mut self) -> Option<char> {
72
        use self::RecompositionState::*;
73
74
        loop {
75
15.0M
            match self.state {
76
                Composing => {
77
14.8M
                    for ch in self.iter.by_ref() {
78
14.8M
                        let ch_class = super::char::canonical_combining_class(ch);
79
14.8M
                        let k = match self.composee {
80
                            None => {
81
9.20k
                                if ch_class != 0 {
82
7.21k
                                    return Some(ch);
83
1.98k
                                }
84
1.98k
                                self.composee = Some(ch);
85
1.98k
                                continue;
86
                            }
87
14.8M
                            Some(k) => k,
88
                        };
89
14.8M
                        match self.last_ccc {
90
7.75M
                            None => match super::char::compose(k, ch) {
91
16.9k
                                Some(r) => {
92
16.9k
                                    self.composee = Some(r);
93
16.9k
                                    continue;
94
                                }
95
                                None => {
96
7.73M
                                    if ch_class == 0 {
97
7.48M
                                        self.composee = Some(ch);
98
7.48M
                                        return Some(k);
99
252k
                                    }
100
252k
                                    self.buffer.push(ch);
101
252k
                                    self.last_ccc = Some(ch_class);
102
                                }
103
                            },
104
7.09M
                            Some(l_class) => {
105
7.09M
                                if l_class >= ch_class {
106
                                    // `ch` is blocked from `composee`
107
6.98M
                                    if ch_class == 0 {
108
252k
                                        self.composee = Some(ch);
109
252k
                                        self.last_ccc = None;
110
252k
                                        self.state = Purging(0);
111
252k
                                        return Some(k);
112
6.73M
                                    }
113
6.73M
                                    self.buffer.push(ch);
114
6.73M
                                    self.last_ccc = Some(ch_class);
115
6.73M
                                    continue;
116
114k
                                }
117
114k
                                match super::char::compose(k, ch) {
118
6.04k
                                    Some(r) => {
119
6.04k
                                        self.composee = Some(r);
120
6.04k
                                        continue;
121
                                    }
122
108k
                                    None => {
123
108k
                                        self.buffer.push(ch);
124
108k
                                        self.last_ccc = Some(ch_class);
125
108k
                                    }
126
                                }
127
                            }
128
                        }
129
                    }
130
2.17k
                    self.state = Finished(0);
131
2.17k
                    if self.composee.is_some() {
132
1.98k
                        return self.composee.take();
133
191
                    }
134
                }
135
7.33M
                Purging(next) => match self.buffer.get(next).cloned() {
136
252k
                    None => {
137
252k
                        self.buffer.clear();
138
252k
                        self.state = Composing;
139
252k
                    }
140
7.08M
                    s => {
141
7.08M
                        self.state = Purging(next + 1);
142
7.08M
                        return s;
143
                    }
144
                },
145
9.74k
                Finished(next) => match self.buffer.get(next).cloned() {
146
                    None => {
147
2.17k
                        self.buffer.clear();
148
2.17k
                        return self.composee.take();
149
                    }
150
7.56k
                    s => {
151
7.56k
                        self.state = Finished(next + 1);
152
7.56k
                        return s;
153
                    }
154
                },
155
            }
156
        }
157
14.8M
    }
<unicode_normalization::recompose::Recompositions<core::str::iter::Chars> as core::iter::traits::iterator::Iterator>::next
Line
Count
Source
71
52.1M
    fn next(&mut self) -> Option<char> {
72
        use self::RecompositionState::*;
73
74
        loop {
75
52.1M
            match self.state {
76
                Composing => {
77
52.1M
                    for ch in self.iter.by_ref() {
78
52.1M
                        let ch_class = super::char::canonical_combining_class(ch);
79
52.1M
                        let k = match self.composee {
80
                            None => {
81
4.21M
                                if ch_class != 0 {
82
4.21M
                                    return Some(ch);
83
1.84k
                                }
84
1.84k
                                self.composee = Some(ch);
85
1.84k
                                continue;
86
                            }
87
47.8M
                            Some(k) => k,
88
                        };
89
47.8M
                        match self.last_ccc {
90
13.5M
                            None => match super::char::compose(k, ch) {
91
6.29k
                                Some(r) => {
92
6.29k
                                    self.composee = Some(r);
93
6.29k
                                    continue;
94
                                }
95
                                None => {
96
13.5M
                                    if ch_class == 0 {
97
13.5M
                                        self.composee = Some(ch);
98
13.5M
                                        return Some(k);
99
5.45k
                                    }
100
5.45k
                                    self.buffer.push(ch);
101
5.45k
                                    self.last_ccc = Some(ch_class);
102
                                }
103
                            },
104
34.3M
                            Some(l_class) => {
105
34.3M
                                if l_class >= ch_class {
106
                                    // `ch` is blocked from `composee`
107
34.3M
                                    if ch_class == 0 {
108
4.64k
                                        self.composee = Some(ch);
109
4.64k
                                        self.last_ccc = None;
110
4.64k
                                        self.state = Purging(0);
111
4.64k
                                        return Some(k);
112
34.3M
                                    }
113
34.3M
                                    self.buffer.push(ch);
114
34.3M
                                    self.last_ccc = Some(ch_class);
115
34.3M
                                    continue;
116
18.0k
                                }
117
18.0k
                                match super::char::compose(k, ch) {
118
1.60k
                                    Some(r) => {
119
1.60k
                                        self.composee = Some(r);
120
1.60k
                                        continue;
121
                                    }
122
16.4k
                                    None => {
123
16.4k
                                        self.buffer.push(ch);
124
16.4k
                                        self.last_ccc = Some(ch_class);
125
16.4k
                                    }
126
                                }
127
                            }
128
                        }
129
                    }
130
2.19k
                    self.state = Finished(0);
131
2.19k
                    if self.composee.is_some() {
132
1.84k
                        return self.composee.take();
133
344
                    }
134
                }
135
15.8M
                Purging(next) => match self.buffer.get(next).cloned() {
136
4.64k
                    None => {
137
4.64k
                        self.buffer.clear();
138
4.64k
                        self.state = Composing;
139
4.64k
                    }
140
15.8M
                    s => {
141
15.8M
                        self.state = Purging(next + 1);
142
15.8M
                        return s;
143
                    }
144
                },
145
18.5M
                Finished(next) => match self.buffer.get(next).cloned() {
146
                    None => {
147
2.19k
                        self.buffer.clear();
148
2.19k
                        return self.composee.take();
149
                    }
150
18.5M
                    s => {
151
18.5M
                        self.state = Finished(next + 1);
152
18.5M
                        return s;
153
                    }
154
                },
155
            }
156
        }
157
52.1M
    }
158
}
159
160
impl<I: Iterator<Item = char> + FusedIterator> FusedIterator for Recompositions<I> {}
161
162
impl<I: Iterator<Item = char> + Clone> fmt::Display for Recompositions<I> {
163
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
164
0
        for c in self.clone() {
165
0
            f.write_char(c)?;
166
        }
167
0
        Ok(())
168
0
    }
169
}