Skip to main content

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}