devela/text/unicode/grapheme/scanner/machine/define.rs
1// devela/src/text/unicode/grapheme/scanner/machine/define.rs
2//
3//! Defines [`GraphemeMachine`], [`GraphemeBoundary`].
4//
5
6use super::GraphemeMachineState;
7use crate::{GraphemeProps, GraphemeScanner, Mem, charu, impl_trait};
8
9#[doc = crate::_tags!(text)]
10#[doc = concat!["Streaming ", crate::_ABBR_EGC!(), " boundary detector."]]
11/// Streaming grapheme cluster boundary detector.
12#[doc = crate::_doc_meta!{
13 location("text/unicode/grapheme", struct GraphemeMachine),
14 test_size_of(GraphemeMachine = 3|24; niche Option),
15}]
16/// Sequentially processes Unicode code points,
17/// returning whether each starts a new cluster or continues the current one.
18///
19/// Internally tracks only previous code point properties
20/// and a small state machine for efficient segmentation.
21///
22#[doc = crate::_doc_vendor!("grapheme_machine")]
23#[derive(Clone, Copy, Debug, Default, Eq)]
24pub struct GraphemeMachine {
25 state: GraphemeMachineState,
26 prev: Option<GraphemeProps>,
27}
28
29impl_trait! { PartialEq for GraphemeMachine |self, other| Self::eq(*self, *other) }
30impl_trait! { Hash for GraphemeMachine |self, state| {
31 self.state.hash(state); self.prev.hash(state);
32} }
33
34impl GraphemeMachine {
35 /// Creates a new grapheme machine in the initial state.
36 pub const fn new() -> Self {
37 GraphemeMachine { state: GraphemeMachineState::Base, prev: None }
38 }
39
40 /// Advances the state machine with the given code point properties.
41 ///
42 /// Returns [`GraphemeBoundary::Split`] if the code point starts a new cluster,
43 /// or [`GraphemeBoundary::Continue`] if it extends the current cluster.
44 ///
45 /// At start of input (no previous code point), always returns `Split`.
46 pub const fn next_char_properties(&mut self, next: GraphemeProps) -> GraphemeBoundary {
47 let (boundary, next_state) = self.state.transition(self.prev, next);
48 self.state = next_state;
49 self.prev = Some(next);
50 if boundary { GraphemeBoundary::Split } else { GraphemeBoundary::Continue }
51 }
52
53 /// Advances the state machine with a [`charu`] scalar.
54 ///
55 /// See [`Self::next_char_properties`] for result interpretation.
56 pub const fn next_charu(&mut self, c: charu) -> GraphemeBoundary {
57 let props = GraphemeProps::for_charu(c);
58 self.next_char_properties(props)
59 }
60
61 /// Advances the state machine with a [`char`] scalar.
62 ///
63 /// See [`Self::next_char_properties`] for result interpretation.
64 ///
65 /// Note: [`GraphemeProps`] lookup is optimized for [`charu`].
66 /// Use [`Self::next_charu`] if you already have `charu` to avoid conversion.
67 pub const fn next_char(&mut self, c: char) -> GraphemeBoundary {
68 let props = GraphemeProps::for_char(c);
69 self.next_char_properties(props)
70 }
71
72 /// Returns an iterator over [`charu`] scalars in `s` with their cluster actions.
73 ///
74 /// The iterator processes code points from `s` using [`Self::next_charu`].
75 /// For consistent state tracking, consume the entire iterator.
76 ///
77 /// Does not call [`Self::end_of_input`] automatically, supporting streaming across buffers.
78 pub const fn next_charu_from_str<'a>(&'a mut self, s: &'a str) -> GraphemeScanner<'a, charu> {
79 GraphemeScanner::<charu>::new(self, s)
80 }
81
82 /// Like [`Self::next_charu_from_str`] but converts to [`char`] for convenience.
83 pub const fn next_char_from_str<'a>(&'a mut self, s: &'a str) -> GraphemeScanner<'a, char> {
84 GraphemeScanner::<char>::new(self, s)
85 }
86
87 /// Resets the state machine to initial "start of input" state.
88 ///
89 /// Use when the input stream ends or at non-text boundaries (e.g., markup tags).
90 /// Always returns [`GraphemeBoundary::Split`] to mark the end of the final cluster.
91 pub const fn end_of_input(&mut self) -> GraphemeBoundary {
92 self.state = GraphemeMachineState::Base;
93 self.prev = None;
94 GraphemeBoundary::Split
95 }
96
97 /// Const-compatible `Eq`.
98 pub const fn eq(self, other: Self) -> bool {
99 self.state.eq(other.state)
100 && match (self.prev, other.prev) {
101 (Some(prev), Some(other_prev)) => prev.eq(other_prev),
102 (None, None) => true,
103 _ => false,
104 }
105 }
106}
107
108#[doc = crate::_tags!(text)]
109/// Indicates how to handle a code point when detecting grapheme cluster boundaries.
110#[doc = crate::_doc_meta!{
111 location("text/unicode/grapheme", enum GraphemeBoundary),
112 test_size_of(GraphemeBoundary = 1|8; niche Option),
113}]
114/// Returned by [`GraphemeMachine`] for each code point processed, indicating
115/// whether the code point continues the current grapheme cluster or starts a new one.
116///
117#[doc = crate::_doc_vendor!("grapheme_machine")]
118#[derive(Debug, Clone, Copy, Eq)]
119pub enum GraphemeBoundary {
120 /// Add this code point to the current grapheme cluster and continue.
121 ///
122 /// The code point extends the current cluster without creating a boundary.
123 Continue,
124
125 /// Finalize the current grapheme cluster and start a new one with this code point.
126 ///
127 /// The current cluster is complete before this code point.
128 /// This code point becomes the first code point of the next cluster.
129 Split,
130}
131
132impl_trait! { PartialEq for GraphemeBoundary |self, other| Self::eq(*self, *other) }
133impl_trait! { Hash for GraphemeBoundary |self, state| { Mem::discriminant(self).hash(state); } }
134
135impl GraphemeBoundary {
136 /// Const-compatible `Eq`.
137 pub const fn eq(self, other: Self) -> bool {
138 matches!((self, other), (Self::Continue, Self::Continue) | (Self::Split, Self::Split))
139 }
140}