Coverage Report

Created: 2025-11-16 06:51

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.6k
    pub fn new_canonical(iter: I) -> Self {
42
23.6k
        Recompositions {
43
23.6k
            iter: Decompositions::new_canonical(iter),
44
23.6k
            state: self::RecompositionState::Composing,
45
23.6k
            buffer: TinyVec::new(),
46
23.6k
            composee: None,
47
23.6k
            last_ccc: None,
48
23.6k
        }
49
23.6k
    }
<unicode_normalization::recompose::Recompositions<unicode_normalization::stream_safe::StreamSafe<core::str::iter::Chars>>>::new_canonical
Line
Count
Source
41
11.2k
    pub fn new_canonical(iter: I) -> Self {
42
11.2k
        Recompositions {
43
11.2k
            iter: Decompositions::new_canonical(iter),
44
11.2k
            state: self::RecompositionState::Composing,
45
11.2k
            buffer: TinyVec::new(),
46
11.2k
            composee: None,
47
11.2k
            last_ccc: None,
48
11.2k
        }
49
11.2k
    }
<unicode_normalization::recompose::Recompositions<core::str::iter::Chars>>::new_canonical
Line
Count
Source
41
9.05k
    pub fn new_canonical(iter: I) -> Self {
42
9.05k
        Recompositions {
43
9.05k
            iter: Decompositions::new_canonical(iter),
44
9.05k
            state: self::RecompositionState::Composing,
45
9.05k
            buffer: TinyVec::new(),
46
9.05k
            composee: None,
47
9.05k
            last_ccc: None,
48
9.05k
        }
49
9.05k
    }
Unexecuted instantiation: <unicode_normalization::recompose::Recompositions<_>>::new_canonical
<unicode_normalization::recompose::Recompositions<streaming::Counter>>::new_canonical
Line
Count
Source
41
2.38k
    pub fn new_canonical(iter: I) -> Self {
42
2.38k
        Recompositions {
43
2.38k
            iter: Decompositions::new_canonical(iter),
44
2.38k
            state: self::RecompositionState::Composing,
45
2.38k
            buffer: TinyVec::new(),
46
2.38k
            composee: None,
47
2.38k
            last_ccc: None,
48
2.38k
        }
49
2.38k
    }
<unicode_normalization::recompose::Recompositions<core::str::iter::Chars>>::new_canonical
Line
Count
Source
41
978
    pub fn new_canonical(iter: I) -> Self {
42
978
        Recompositions {
43
978
            iter: Decompositions::new_canonical(iter),
44
978
            state: self::RecompositionState::Composing,
45
978
            buffer: TinyVec::new(),
46
978
            composee: None,
47
978
            last_ccc: None,
48
978
        }
49
978
    }
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.1k
    pub fn new_compatible(iter: I) -> Self {
57
10.1k
        Recompositions {
58
10.1k
            iter: Decompositions::new_compatible(iter),
59
10.1k
            state: self::RecompositionState::Composing,
60
10.1k
            buffer: TinyVec::new(),
61
10.1k
            composee: None,
62
10.1k
            last_ccc: None,
63
10.1k
        }
64
10.1k
    }
<unicode_normalization::recompose::Recompositions<core::str::iter::Chars>>::new_compatible
Line
Count
Source
56
8.85k
    pub fn new_compatible(iter: I) -> Self {
57
8.85k
        Recompositions {
58
8.85k
            iter: Decompositions::new_compatible(iter),
59
8.85k
            state: self::RecompositionState::Composing,
60
8.85k
            buffer: TinyVec::new(),
61
8.85k
            composee: None,
62
8.85k
            last_ccc: None,
63
8.85k
        }
64
8.85k
    }
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
262M
    fn next(&mut self) -> Option<char> {
72
        use self::RecompositionState::*;
73
74
        loop {
75
264M
            match self.state {
76
                Composing => {
77
273M
                    for ch in self.iter.by_ref() {
78
273M
                        let ch_class = super::char::canonical_combining_class(ch);
79
273M
                        let k = match self.composee {
80
                            None => {
81
5.21M
                                if ch_class != 0 {
82
5.18M
                                    return Some(ch);
83
31.7k
                                }
84
31.7k
                                self.composee = Some(ch);
85
31.7k
                                continue;
86
                            }
87
268M
                            Some(k) => k,
88
                        };
89
268M
                        match self.last_ccc {
90
121M
                            None => match super::char::compose(k, ch) {
91
10.5M
                                Some(r) => {
92
10.5M
                                    self.composee = Some(r);
93
10.5M
                                    continue;
94
                                }
95
                                None => {
96
110M
                                    if ch_class == 0 {
97
108M
                                        self.composee = Some(ch);
98
108M
                                        return Some(k);
99
1.87M
                                    }
100
1.87M
                                    self.buffer.push(ch);
101
1.87M
                                    self.last_ccc = Some(ch_class);
102
                                }
103
                            },
104
146M
                            Some(l_class) => {
105
146M
                                if l_class >= ch_class {
106
                                    // `ch` is blocked from `composee`
107
146M
                                    if ch_class == 0 {
108
1.86M
                                        self.composee = Some(ch);
109
1.86M
                                        self.last_ccc = None;
110
1.86M
                                        self.state = Purging(0);
111
1.86M
                                        return Some(k);
112
144M
                                    }
113
144M
                                    self.buffer.push(ch);
114
144M
                                    self.last_ccc = Some(ch_class);
115
144M
                                    continue;
116
313k
                                }
117
313k
                                match super::char::compose(k, ch) {
118
27.0k
                                    Some(r) => {
119
27.0k
                                        self.composee = Some(r);
120
27.0k
                                        continue;
121
                                    }
122
286k
                                    None => {
123
286k
                                        self.buffer.push(ch);
124
286k
                                        self.last_ccc = Some(ch_class);
125
286k
                                    }
126
                                }
127
                            }
128
                        }
129
                    }
130
33.4k
                    self.state = Finished(0);
131
33.4k
                    if self.composee.is_some() {
132
31.3k
                        return self.composee.take();
133
2.15k
                    }
134
                }
135
111M
                Purging(next) => match self.buffer.get(next).cloned() {
136
1.86M
                    None => {
137
1.86M
                        self.buffer.clear();
138
1.86M
                        self.state = Composing;
139
1.86M
                    }
140
110M
                    s => {
141
110M
                        self.state = Purging(next + 1);
142
110M
                        return s;
143
                    }
144
                },
145
36.7M
                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
36.7M
                    s => {
151
36.7M
                        self.state = Finished(next + 1);
152
36.7M
                        return s;
153
                    }
154
                },
155
            }
156
        }
157
262M
    }
<unicode_normalization::recompose::Recompositions<unicode_normalization::stream_safe::StreamSafe<core::str::iter::Chars>> as core::iter::traits::iterator::Iterator>::next
Line
Count
Source
71
73.5M
    fn next(&mut self) -> Option<char> {
72
        use self::RecompositionState::*;
73
74
        loop {
75
75.1M
            match self.state {
76
                Composing => {
77
77.6M
                    for ch in self.iter.by_ref() {
78
77.6M
                        let ch_class = super::char::canonical_combining_class(ch);
79
77.6M
                        let k = match self.composee {
80
                            None => {
81
35.2k
                                if ch_class != 0 {
82
24.4k
                                    return Some(ch);
83
10.7k
                                }
84
10.7k
                                self.composee = Some(ch);
85
10.7k
                                continue;
86
                            }
87
77.5M
                            Some(k) => k,
88
                        };
89
77.5M
                        match self.last_ccc {
90
31.3M
                            None => match super::char::compose(k, ch) {
91
4.02M
                                Some(r) => {
92
4.02M
                                    self.composee = Some(r);
93
4.02M
                                    continue;
94
                                }
95
                                None => {
96
27.3M
                                    if ch_class == 0 {
97
25.7M
                                        self.composee = Some(ch);
98
25.7M
                                        return Some(k);
99
1.56M
                                    }
100
1.56M
                                    self.buffer.push(ch);
101
1.56M
                                    self.last_ccc = Some(ch_class);
102
                                }
103
                            },
104
46.2M
                            Some(l_class) => {
105
46.2M
                                if l_class >= ch_class {
106
                                    // `ch` is blocked from `composee`
107
46.0M
                                    if ch_class == 0 {
108
1.55M
                                        self.composee = Some(ch);
109
1.55M
                                        self.last_ccc = None;
110
1.55M
                                        self.state = Purging(0);
111
1.55M
                                        return Some(k);
112
44.5M
                                    }
113
44.5M
                                    self.buffer.push(ch);
114
44.5M
                                    self.last_ccc = Some(ch_class);
115
44.5M
                                    continue;
116
133k
                                }
117
133k
                                match super::char::compose(k, ch) {
118
7.50k
                                    Some(r) => {
119
7.50k
                                        self.composee = Some(r);
120
7.50k
                                        continue;
121
                                    }
122
125k
                                    None => {
123
125k
                                        self.buffer.push(ch);
124
125k
                                        self.last_ccc = Some(ch_class);
125
125k
                                    }
126
                                }
127
                            }
128
                        }
129
                    }
130
11.1k
                    self.state = Finished(0);
131
11.1k
                    if self.composee.is_some() {
132
10.6k
                        return self.composee.take();
133
474
                    }
134
                }
135
47.7M
                Purging(next) => match self.buffer.get(next).cloned() {
136
1.55M
                    None => {
137
1.55M
                        self.buffer.clear();
138
1.55M
                        self.state = Composing;
139
1.55M
                    }
140
46.1M
                    s => {
141
46.1M
                        self.state = Purging(next + 1);
142
46.1M
                        return s;
143
                    }
144
                },
145
42.6k
                Finished(next) => match self.buffer.get(next).cloned() {
146
                    None => {
147
11.0k
                        self.buffer.clear();
148
11.0k
                        return self.composee.take();
149
                    }
150
31.5k
                    s => {
151
31.5k
                        self.state = Finished(next + 1);
152
31.5k
                        return s;
153
                    }
154
                },
155
            }
156
        }
157
73.5M
    }
<unicode_normalization::recompose::Recompositions<core::str::iter::Chars> as core::iter::traits::iterator::Iterator>::next
Line
Count
Source
71
123M
    fn next(&mut self) -> Option<char> {
72
        use self::RecompositionState::*;
73
74
        loop {
75
123M
            match self.state {
76
                Composing => {
77
129M
                    for ch in self.iter.by_ref() {
78
129M
                        let ch_class = super::char::canonical_combining_class(ch);
79
129M
                        let k = match self.composee {
80
                            None => {
81
151k
                                if ch_class != 0 {
82
135k
                                    return Some(ch);
83
16.7k
                                }
84
16.7k
                                self.composee = Some(ch);
85
16.7k
                                continue;
86
                            }
87
129M
                            Some(k) => k,
88
                        };
89
129M
                        match self.last_ccc {
90
68.0M
                            None => match super::char::compose(k, ch) {
91
6.48M
                                Some(r) => {
92
6.48M
                                    self.composee = Some(r);
93
6.48M
                                    continue;
94
                                }
95
                                None => {
96
61.5M
                                    if ch_class == 0 {
97
61.5M
                                        self.composee = Some(ch);
98
61.5M
                                        return Some(k);
99
34.5k
                                    }
100
34.5k
                                    self.buffer.push(ch);
101
34.5k
                                    self.last_ccc = Some(ch_class);
102
                                }
103
                            },
104
61.6M
                            Some(l_class) => {
105
61.6M
                                if l_class >= ch_class {
106
                                    // `ch` is blocked from `composee`
107
61.5M
                                    if ch_class == 0 {
108
29.1k
                                        self.composee = Some(ch);
109
29.1k
                                        self.last_ccc = None;
110
29.1k
                                        self.state = Purging(0);
111
29.1k
                                        return Some(k);
112
61.5M
                                    }
113
61.5M
                                    self.buffer.push(ch);
114
61.5M
                                    self.last_ccc = Some(ch_class);
115
61.5M
                                    continue;
116
56.9k
                                }
117
56.9k
                                match super::char::compose(k, ch) {
118
11.1k
                                    Some(r) => {
119
11.1k
                                        self.composee = Some(r);
120
11.1k
                                        continue;
121
                                    }
122
45.8k
                                    None => {
123
45.8k
                                        self.buffer.push(ch);
124
45.8k
                                        self.last_ccc = Some(ch_class);
125
45.8k
                                    }
126
                                }
127
                            }
128
                        }
129
                    }
130
17.6k
                    self.state = Finished(0);
131
17.6k
                    if self.composee.is_some() {
132
16.5k
                        return self.composee.take();
133
1.13k
                    }
134
                }
135
41.8M
                Purging(next) => match self.buffer.get(next).cloned() {
136
28.9k
                    None => {
137
28.9k
                        self.buffer.clear();
138
28.9k
                        self.state = Composing;
139
28.9k
                    }
140
41.8M
                    s => {
141
41.8M
                        self.state = Purging(next + 1);
142
41.8M
                        return s;
143
                    }
144
                },
145
19.7M
                Finished(next) => match self.buffer.get(next).cloned() {
146
                    None => {
147
17.5k
                        self.buffer.clear();
148
17.5k
                        return self.composee.take();
149
                    }
150
19.7M
                    s => {
151
19.7M
                        self.state = Finished(next + 1);
152
19.7M
                        return s;
153
                    }
154
                },
155
            }
156
        }
157
123M
    }
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
16.1M
    fn next(&mut self) -> Option<char> {
72
        use self::RecompositionState::*;
73
74
        loop {
75
16.3M
            match self.state {
76
                Composing => {
77
16.1M
                    for ch in self.iter.by_ref() {
78
16.1M
                        let ch_class = super::char::canonical_combining_class(ch);
79
16.1M
                        let k = match self.composee {
80
                            None => {
81
9.69k
                                if ch_class != 0 {
82
7.51k
                                    return Some(ch);
83
2.18k
                                }
84
2.18k
                                self.composee = Some(ch);
85
2.18k
                                continue;
86
                            }
87
16.1M
                            Some(k) => k,
88
                        };
89
16.1M
                        match self.last_ccc {
90
8.33M
                            None => match super::char::compose(k, ch) {
91
13.2k
                                Some(r) => {
92
13.2k
                                    self.composee = Some(r);
93
13.2k
                                    continue;
94
                                }
95
                                None => {
96
8.32M
                                    if ch_class == 0 {
97
8.05M
                                        self.composee = Some(ch);
98
8.05M
                                        return Some(k);
99
271k
                                    }
100
271k
                                    self.buffer.push(ch);
101
271k
                                    self.last_ccc = Some(ch_class);
102
                                }
103
                            },
104
7.79M
                            Some(l_class) => {
105
7.79M
                                if l_class >= ch_class {
106
                                    // `ch` is blocked from `composee`
107
7.68M
                                    if ch_class == 0 {
108
270k
                                        self.composee = Some(ch);
109
270k
                                        self.last_ccc = None;
110
270k
                                        self.state = Purging(0);
111
270k
                                        return Some(k);
112
7.41M
                                    }
113
7.41M
                                    self.buffer.push(ch);
114
7.41M
                                    self.last_ccc = Some(ch_class);
115
7.41M
                                    continue;
116
105k
                                }
117
105k
                                match super::char::compose(k, ch) {
118
6.78k
                                    Some(r) => {
119
6.78k
                                        self.composee = Some(r);
120
6.78k
                                        continue;
121
                                    }
122
98.5k
                                    None => {
123
98.5k
                                        self.buffer.push(ch);
124
98.5k
                                        self.last_ccc = Some(ch_class);
125
98.5k
                                    }
126
                                }
127
                            }
128
                        }
129
                    }
130
2.38k
                    self.state = Finished(0);
131
2.38k
                    if self.composee.is_some() {
132
2.18k
                        return self.composee.take();
133
201
                    }
134
                }
135
8.04M
                Purging(next) => match self.buffer.get(next).cloned() {
136
270k
                    None => {
137
270k
                        self.buffer.clear();
138
270k
                        self.state = Composing;
139
270k
                    }
140
7.77M
                    s => {
141
7.77M
                        self.state = Purging(next + 1);
142
7.77M
                        return s;
143
                    }
144
                },
145
9.96k
                Finished(next) => match self.buffer.get(next).cloned() {
146
                    None => {
147
2.38k
                        self.buffer.clear();
148
2.38k
                        return self.composee.take();
149
                    }
150
7.58k
                    s => {
151
7.58k
                        self.state = Finished(next + 1);
152
7.58k
                        return s;
153
                    }
154
                },
155
            }
156
        }
157
16.1M
    }
<unicode_normalization::recompose::Recompositions<core::str::iter::Chars> as core::iter::traits::iterator::Iterator>::next
Line
Count
Source
71
49.8M
    fn next(&mut self) -> Option<char> {
72
        use self::RecompositionState::*;
73
74
        loop {
75
49.8M
            match self.state {
76
                Composing => {
77
49.8M
                    for ch in self.iter.by_ref() {
78
49.8M
                        let ch_class = super::char::canonical_combining_class(ch);
79
49.8M
                        let k = match self.composee {
80
                            None => {
81
5.01M
                                if ch_class != 0 {
82
5.01M
                                    return Some(ch);
83
1.97k
                                }
84
1.97k
                                self.composee = Some(ch);
85
1.97k
                                continue;
86
                            }
87
44.8M
                            Some(k) => k,
88
                        };
89
44.8M
                        match self.last_ccc {
90
13.5M
                            None => match super::char::compose(k, ch) {
91
6.86k
                                Some(r) => {
92
6.86k
                                    self.composee = Some(r);
93
6.86k
                                    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.38k
                                    }
100
5.38k
                                    self.buffer.push(ch);
101
5.38k
                                    self.last_ccc = Some(ch_class);
102
                                }
103
                            },
104
31.2M
                            Some(l_class) => {
105
31.2M
                                if l_class >= ch_class {
106
                                    // `ch` is blocked from `composee`
107
31.2M
                                    if ch_class == 0 {
108
4.58k
                                        self.composee = Some(ch);
109
4.58k
                                        self.last_ccc = None;
110
4.58k
                                        self.state = Purging(0);
111
4.58k
                                        return Some(k);
112
31.2M
                                    }
113
31.2M
                                    self.buffer.push(ch);
114
31.2M
                                    self.last_ccc = Some(ch_class);
115
31.2M
                                    continue;
116
17.8k
                                }
117
17.8k
                                match super::char::compose(k, ch) {
118
1.63k
                                    Some(r) => {
119
1.63k
                                        self.composee = Some(r);
120
1.63k
                                        continue;
121
                                    }
122
16.2k
                                    None => {
123
16.2k
                                        self.buffer.push(ch);
124
16.2k
                                        self.last_ccc = Some(ch_class);
125
16.2k
                                    }
126
                                }
127
                            }
128
                        }
129
                    }
130
2.32k
                    self.state = Finished(0);
131
2.32k
                    if self.composee.is_some() {
132
1.97k
                        return self.composee.take();
133
352
                    }
134
                }
135
14.3M
                Purging(next) => match self.buffer.get(next).cloned() {
136
4.58k
                    None => {
137
4.58k
                        self.buffer.clear();
138
4.58k
                        self.state = Composing;
139
4.58k
                    }
140
14.2M
                    s => {
141
14.2M
                        self.state = Purging(next + 1);
142
14.2M
                        return s;
143
                    }
144
                },
145
16.9M
                Finished(next) => match self.buffer.get(next).cloned() {
146
                    None => {
147
2.32k
                        self.buffer.clear();
148
2.32k
                        return self.composee.take();
149
                    }
150
16.9M
                    s => {
151
16.9M
                        self.state = Finished(next + 1);
152
16.9M
                        return s;
153
                    }
154
                },
155
            }
156
        }
157
49.8M
    }
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
}