morse_codec/
message.rs

1//! Message struct to hold decoded message or message to be encoded.
2//!
3//! Client code can use this to access and manipulate the
4//! internal message of MorseDecoder or MorseEncoder:
5//!
6//! ```ignore
7//! // Get a decoded message
8//! let decoded_message = decoder.message.as_str();
9//! let decoded_message_chars = decoder.message.as_charray();
10//! // ...Do something with the message...
11//!
12//! // Clear the message
13//! decoder.message.clear();
14//!
15//! // Set message to something different
16//! // and continue decoding from the end
17//! decoder.message.set_message("SOME INITIAL MESSAGE", true);
18//!
19//! // We continue sending signals
20//! decoder.signal_event(125, true);
21//! decoder.signal_event(200, false);
22//! ....
23//!
24//! // To show an editing cursor on the screen
25//! let editing_position = decoder.message.get_edit_pos();
26//! ```
27
28use crate::{
29    FILLER,
30    FILLER_CHAR,
31    Character,
32};
33
34#[cfg(feature = "utf8")]
35use core::fmt::Display;
36
37#[cfg(feature = "utf8")]
38#[derive(Debug)]
39/// When "utf8" feature is enabled, instead of &str
40/// we return this new type struct as a placeholder for &str,
41/// because it's still hard to use arithmetic operations in
42/// const expressions. In the future if this PR gets merged:
43/// <https://github.com/rust-lang/rust/issues/76560>
44/// We might update the code to do stuff like:
45/// let chars: [0; MSG_MAX * 4] = ...
46pub struct Utf8Charray<'a>(&'a [char]);
47
48#[cfg(feature = "utf8")]
49impl Display for Utf8Charray<'_> {
50    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
51        for ch in self.0 {
52            write!(f, "{}", ch)?;
53        }
54
55        Ok(())
56    }
57}
58
59#[cfg(feature = "utf8")]
60impl PartialEq<&str> for Utf8Charray<'_> {
61    fn eq(&self, other: &&str) -> bool {
62        let mut other_chars = other.chars();
63        for &ch in self.0.iter() {
64            let other_char = other_chars.next();
65            if other_char.is_none() || ch != other_char.unwrap() {
66                return false;
67            }
68        }
69
70        true
71    }
72}
73
74#[cfg(feature = "utf8")]
75impl Utf8Charray<'_> {
76    pub fn iter(&self) -> impl Iterator<Item = &char> {
77        self.0.iter()
78    }
79}
80
81/// This struct holds the message in human readable format.
82///
83/// It also provides functions to do edit position manipulation,
84/// getting or setting characters at index positions.
85pub struct Message<const MSG_MAX: usize> {
86    chars: [Character; MSG_MAX],
87    edit_pos: usize,
88    last_change_index: usize,
89    clamp_edit_pos: bool,
90}
91
92impl<const MSG_MAX: usize> Default for Message<MSG_MAX> {
93    fn default() -> Self {
94        Self {
95            chars: [FILLER; MSG_MAX],
96            edit_pos: 0,
97            last_change_index: 0,
98            clamp_edit_pos: false,
99        }
100    }
101}
102
103// Constructor with a starter string
104impl<const MSG_MAX: usize> Message<MSG_MAX> {
105    /// Maximum index editing position can be at
106    pub const POS_MAX: usize = MSG_MAX - 1;
107
108    /// Get an instance of Message starting from an &str.
109    ///
110    /// edit_pos_end means client code wants to continue editing this
111    /// text at the end.
112    pub fn new(message_str: &str, edit_pos_end: bool, clamp_edit_pos: bool) -> Self {
113        let mut new_self = Self {
114            chars: Self::str_to_chars(message_str),
115            clamp_edit_pos,
116            ..Self::default()
117        };
118
119        if edit_pos_end {
120            new_self.edit_pos = new_self.len().clamp(0, Self::POS_MAX);
121        }
122
123        new_self
124    }
125
126    #[cfg(not(feature = "utf8"))]
127    // Static member utility function to convert an &str to character array internal format.
128    fn str_to_chars(str: &str) -> [u8; MSG_MAX] {
129        let mut str_iter = str.chars()
130            .take(MSG_MAX)
131            .filter(|ch| ch.is_ascii());
132
133        core::array::from_fn(|_|
134            str_iter.next()
135                .unwrap_or(FILLER_CHAR)
136                .to_ascii_uppercase() as u8
137        )
138    }
139
140    #[cfg(feature = "utf8")]
141    // Static member utility function to convert an &str to charray internal format.
142    fn str_to_chars(str: &str) -> [Character; MSG_MAX] {
143        let mut str_iter = str.chars()
144            .take(MSG_MAX);
145
146        core::array::from_fn(|_|
147            str_iter.next()
148                .unwrap_or(FILLER_CHAR)
149                .to_uppercase()
150                .next()
151                .unwrap()
152        )
153    }
154}
155
156// Private stuff
157impl<const MSG_MAX: usize> Message<MSG_MAX> {
158    // Index of last character before the last FILLERs
159    fn last_char_index(&self) -> Option<usize> {
160        self.chars.iter().rposition(|ch| *ch != FILLER)
161    }
162
163    // Check if any FILLER characters are between normal chars
164    // and convert them to ' ' space characters.
165    fn update_empty_chars(&mut self) {
166        if let Some(last_index) = self.last_char_index() {
167            self.chars.iter_mut().enumerate().for_each(|(index, ch)| {
168                if *ch == FILLER && index < last_index {
169                    *ch = ' ' as Character;
170                }
171            });
172        }
173    }
174}
175
176// Public API
177impl<const MSG_MAX: usize> Message<MSG_MAX> {
178    /// Get an iterator to the message chars contained within.
179    pub fn iter(&self) -> MessageIterator<MSG_MAX> {
180        MessageIterator {
181            message: self,
182            index: 0,
183        }
184    }
185
186    /// Sets current editing position to given value.
187    pub fn set_edit_pos(&mut self, pos: usize) {
188        self.edit_pos = pos.clamp(0, Self::POS_MAX);
189    }
190
191    /// Change the clamping behaviour of the edit position to
192    /// wrapping (default) or clamping.
193    ///
194    /// With clamping set, when edit position is shifted to left or right,
195    /// it won't cycle forward to maximum position or revert back to zero position,
196    /// effectively remaining within the limits of the message no matter current position is.
197    pub fn set_edit_position_clamp(&mut self, clamp: bool) {
198        self.clamp_edit_pos = clamp;
199    }
200
201    /// Returns if edit position movement is clamping to the ends of the message
202    pub fn is_edit_clamped(&self) -> bool {
203        self.clamp_edit_pos
204    }
205
206    /// Returns current editing position.
207    pub fn get_edit_pos(&self) -> usize {
208        self.edit_pos
209    }
210
211    /// Returns index of last added character
212    pub fn get_last_changed_index(&self) -> usize {
213        self.last_change_index
214    }
215
216    /// Returns the character at the index of last change
217    pub fn get_last_changed_char(&self) -> Character {
218        self.chars[self.last_change_index]
219    }
220
221    /// Move editing position to the left.
222    /// By default it will wrap to the end if position is 0
223    pub fn shift_edit_left(&mut self) {
224        self.edit_pos = match self.edit_pos {
225            0 => if self.clamp_edit_pos { 0 } else { Self::POS_MAX },
226            p => p - 1,
227        }
228    }
229
230    /// Move editing position to the right.
231    /// By default it will wrap to the beginning if position is POS_MAX
232    pub fn shift_edit_right(&mut self) {
233        self.edit_pos = match self.edit_pos {
234            p if p == Self::POS_MAX => if self.clamp_edit_pos { Self::POS_MAX } else { 0 },
235            p => p + 1,
236        }
237    }
238
239    /// Insert character at the editing position.
240    ///
241    /// If any characters before the character are [FILLER]s
242    /// They'll automatically be converted to empty characters ' '
243    /// which means the user wants some space between words.
244    pub fn add_char(&mut self, ch: Character) {
245        self.chars[self.edit_pos] = ch;
246        // This is only necessary if client code sets edit position
247        // manually and adds a character after it, but hey.
248        self.update_empty_chars();
249        self.last_change_index = self.edit_pos;
250    }
251
252    /// Insert character at index.
253    ///
254    /// If any characters before the character are [FILLER]s
255    /// They'll automatically be converted to empty characters ' '
256    /// which means the user wants some space between words.
257    pub fn put_char_at(&mut self, index: usize, ch: Character) -> Result<(), &str> {
258        if index < MSG_MAX {
259            self.chars[index] = ch;
260            self.update_empty_chars();
261            self.last_change_index = index;
262
263            Ok(())
264        } else {
265            Err("Put char index doesn't fit into message length")
266        }
267    }
268
269    /// Returns character at an index
270    pub fn char_at(&self, index: usize) -> Character {
271        self.chars[index]
272    }
273
274    /// Returns current length of the message discarding empty FILLER characters at the end.
275    ///
276    /// This is useful for creating ranged loops of actual characters decoded or can be encoded.
277    pub fn len(&self) -> usize {
278        let index = self.last_char_index();
279        match index {
280            Some(i) if i < MSG_MAX => i + 1,
281            Some(i) if i == MSG_MAX => MSG_MAX,
282            _ => 0,
283        }
284    }
285
286    /// Returns true if the message is empty, false otherwise.
287    ///
288    /// This method discards FILLER characters and only takes
289    /// normal characters into account.
290    pub fn is_empty(&self) -> bool {
291        self.last_char_index().is_none()
292    }
293
294    /// Manually set the message from an &str.
295    ///
296    /// edit_pos_end flag means we'll continue from the end of this string when
297    /// we continue decoding or encoding.
298    pub fn set_message(&mut self, message_str: &str, edit_pos_end: bool) -> Result<(), &str> {
299        if message_str.len() > MSG_MAX {
300            Err("Message string can't be longer than MSG_MAX.")
301        } else {
302            self.chars = Self::str_to_chars(message_str);
303
304            if edit_pos_end {
305                self.edit_pos = self.len().clamp(0, Self::POS_MAX);
306            } else {
307                self.edit_pos = 0;
308            }
309
310            self.last_change_index = self.edit_pos;
311
312            Ok(())
313        }
314    }
315
316    /// Returns the message as it is now in a character array format.
317    ///
318    /// Note that this also includes 'empty' [FILLER] characters.
319    /// Client code can use return value of len() which is the actual length
320    /// to loop through it or filter the fillers manually in a loop or iterator.
321    pub fn as_charray(&self) -> [Character; MSG_MAX] {
322        self.chars
323    }
324
325    /// Returns the message as it is now as &str slice.
326    /// Or as a [Utf8Charray] if "utf8" feature is enabled.
327    ///
328    /// Note that this *does not* include empty [FILLER] characters.
329    #[cfg(not(feature = "utf8"))]
330    pub fn as_str(&self) -> &str {
331        core::str::from_utf8(self.chars[0..self.len()].as_ref()).unwrap()
332    }
333
334    #[cfg(feature = "utf8")]
335    pub fn as_str(&self) -> Utf8Charray {
336        // Fixme: Update the code to use buffer copy,
337        // after const generic expressions become stable in Rust.
338        // https://github.com/rust-lang/rust/issues/76560
339        //
340        // let mut buffer = [0u8; MSG_MAX * 4];
341        // let mut pos: usize = 0;
342        // for ch in self.chars {
343        //      pos += ch.encode_utf8(&mut buffer).len();
344        // }
345        //
346        // core::str::from_utf8(buffer[0..pos].asref()).unwrap()
347
348        Utf8Charray(self.chars[..self.len()].as_ref())
349    }
350
351    /// Pop the last character from the message and return it
352    /// deleting it from the message in the process.
353    pub fn pop(&mut self) -> Option<Character> {
354        if self.len() > 0 {
355            let last = self.chars[self.len() - 1];
356            self.chars[self.len() - 1] = FILLER;
357
358            Some(last)
359        } else {
360            None
361        }
362    }
363
364    /// Clear the message and start over.
365    pub fn clear(&mut self) {
366        self.chars = [FILLER; MSG_MAX];
367        self.edit_pos = 0;
368    }
369}
370
371/// Message iterator provides a convenient way to iterate over
372/// message characters. This doesn't include empty FILLER chars.
373pub struct MessageIterator<'a, const MSG_MAX: usize> {
374    message: &'a Message<MSG_MAX>,
375    index: usize,
376}
377
378impl<'a, const MSG_MAX: usize> Iterator for MessageIterator<'a, MSG_MAX> {
379    type Item = &'a Character;
380
381    fn next(&mut self) -> Option<Self::Item> {
382        if self.index < self.message.len() {
383            let result = Some(&self.message.chars[self.index]);
384            self.index += 1;
385
386            result
387        } else {
388            None
389        }
390    }
391}