avr_progmem/string.rs
1//! String utilities
2//!
3//! This module offers further utilities base on [`ProgMem`] to make working
4//! with strings in progmem more convenient.
5//!
6//! The difficulty with strings is that normally they are either heap allocated
7//! (as with the std Rust `String`) or dynamically sized (as with `str`).
8//! To store a string in progmem, one needs first of all a fixed-sized storage
9//! variant of a `str`.
10//! One option is to use byte string literals (e.g. `b"foobar"`), however,
11//! for some reason those only accept ASCII and no Unicode.
12//! At some day, one might be able to to convert arbitrary string literals to
13//! byte arrays like this:
14//!
15//! ```ignore
16//! # use std::convert::TryInto;
17//! // Dose not compile as of 1.51, because `try_into` is not a const fn, yet
18//! static WIKIPEDIA: [u8; 12] = "维基百科".as_bytes().try_into().unwrap();
19//! ```
20//!
21//! However, for the time being, this module offers as a convenient workaround:
22//! * [`LoadedString`] a simple UTF-8 encoded sized byte array
23//! * [`PmString`] a UTF-8 encoded sized byte array in progmem similar to [`ProgMem`].
24//!
25//!
26//! # Working with Strings
27//!
28//! To work with strings in progmem, this crate offers two APIs and two modes
29//! of operation, each with their own little tradeoff.
30//!
31//! ## Operation Modes
32//!
33//! When you want to use strings from progmem you have two options:
34//!
35//! * you can load them as whole from progmem into RAM and work with them
36//! essentially like stack-allocated `&str`s
37//! * or, you choose to load the strings `char` after `char` from progmem and work
38//! with them using a `char`-iterator.
39//!
40//! The first mode of operation obviously allows you to use them everywhere,
41//! where you can use a `&str`, giving you high compatibility with other APIs.
42//! On the other hand, this comes at the cost of high RAM usage.
43//! So you must leave enough free RAM to fit all your string, thus the bigger,
44//! your biggest string is, the less RAM you must use statically.
45//! So, you might have to split your strings somehow to make them manageable.
46//!
47//! The alternative is to only load just one `char` at a time.
48//! This obviously limits the amount of RAM that you need, independently of
49//! how big your strings are, allowing you to work with really huge strings.
50//! However, you no longer get a `&str`, any you have make do with a `char`
51//! iterator.
52//!
53//! However, if you only need your strings to be printed in some way,
54//! the [`Display`](fmt::Display) and [`ufmt::uDisplay`] traits implementations
55//! (the latter only if the `ufmt` crate feature is enabled) of [`PmString`],
56//! might become very handy.
57//! These trait implementations only need the `char`-iterator so they are very
58//! economic with respect to RAM usage.
59//!
60//! ## APIs
61//!
62//! API-wise you can either:
63//!
64//! * define progmem `static`s via the [`progmem`](crate::progmem) macro
65//! and use them all over your program,
66//! * or, you create single-use progmem strings via the
67//! [`progmem_str`](crate::progmem_str) and
68//! [`progmem_display`](crate::progmem_display) macro
69//!
70//! The single-use macros are the most concise option, but also a rather
71//! special-case solution.
72//! [`progmem_str`](crate::progmem_str) gives you are very temporary `&str`
73//! to an ad-hoc loaded
74//! progmem string, so you can only pass it to a function call and you need
75//! enough RAM to store it.
76//! On the other hand, [`progmem_display`](crate::progmem_display) gives you
77//! just something that is `impl Display + uDisplay`, so you can just print it,
78//! but it has minimal RAM usage.
79//!
80//! If need anything more flexible or fancy, you are probably best served
81//! creating a `static` via [`progmem`](crate::progmem) macro.
82//!
83//!
84//! # Examples
85//!
86//! Using [`PmString`] directly via the [`progmem`](crate::progmem) macro:
87//!
88//! ```rust
89//! # use std::iter::FromIterator;
90//! use avr_progmem::progmem;
91//! use avr_progmem::string::LoadedString;
92//!
93//! progmem! {
94//! // A simple Unicode string in progmem, internally stored as fix-sized
95//! // byte array, i.e. a `PmString<18>`.
96//! static progmem string TEXT = "Hello 大賢者";
97//! }
98//!
99//! // You can load it all at once (like a `ProgMem`)
100//! let buffer: LoadedString<15> = TEXT.load();
101//! // and use that as `&str`
102//! assert_eq!("Hello 大賢者", &*buffer);
103//!
104//! // Or you load it one char at a time (limits RAM usage)
105//! let chars_iter = TEXT.chars(); // impl Iterator<Item=char>
106//! let exp = ['H', 'e', 'l', 'l', 'o', ' ', '大', '賢', '者'];
107//! assert_eq!(&exp, &*Vec::from_iter(chars_iter));
108//!
109//! // Or you use the `Display`/`uDisplay` impl on `PmString`
110//! fn foo<W: ufmt::uWrite>(writer: &mut W) {
111//! #[cfg(feature = "ufmt")] // requires the `ufmt` crate feature
112//! ufmt::uwrite!(writer, "{}", TEXT);
113//! }
114//! #
115//! # struct MyWriter(String);
116//! # impl ufmt::uWrite for MyWriter {
117//! # type Error = ();
118//! # fn write_str(&mut self, s: &str) -> Result<(),()> {
119//! # self.0.push_str(s);
120//! # Ok(())
121//! # }
122//! # }
123//! # let mut writer = MyWriter(String::new());
124//! # foo(&mut writer);
125//! # #[cfg(feature = "ufmt")] // will be still empty otherwise
126//! # assert_eq!("Hello 大賢者", writer.0);
127//! ```
128//!
129//! Using the special literal in-line string macros [`progmem_str`](crate::progmem_str)
130//! (yielding a `&str`) and [`progmem_display`](crate::progmem_display)
131//! (yielding some `impl Display + uDisplay`):
132//!
133//! ```rust
134//! use avr_progmem::progmem_str as F;
135//! use avr_progmem::progmem_display as D;
136//!
137//! fn foo<W: ufmt::uWrite>(writer: &mut W) {
138//! // In-line string as temporary `&str`
139//! writer.write_str(F!("Hello 大賢者"));
140//!
141//! // In-line string as some `impl Display + uDisplay`
142//! #[cfg(feature = "ufmt")] // requires the `ufmt` crate feature
143//! ufmt::uwrite!(writer, "{}", D!("Hello 大賢者"));
144//! }
145//! #
146//! # use ufmt::uWrite;
147//! # struct MyWriter;
148//! # impl ufmt::uWrite for MyWriter {
149//! # type Error = ();
150//! # fn write_str(&mut self, _s: &str) -> Result<(),()> {
151//! # Ok(()) // ignore input
152//! # }
153//! # }
154//! # let mut writer = MyWriter;
155//! # foo(&mut writer);
156//! ```
157//!
158//! You can also use arbitrary `&str`-yielding expression, including loading
159//! huge strings from files, just don't use `PmString::load` nor `progmem_str`
160//! with huge strings (because if it is bigger than 255 bytes, it will panic).
161//!
162//! ```rust
163//! use avr_progmem::progmem;
164//! use avr_progmem::progmem_display as D;
165//!
166//! progmem! {
167//! // Text too large to fit in the RAM of a Arduino Uno
168//! static progmem string HUGE_TEXT = include_str!("../examples/test_text.txt");
169//! }
170//! println!("{}", HUGE_TEXT);
171//!
172//! // In-line a huge string from a file
173//! println!("{}", D!(include_str!("../examples/test_text.txt")));
174//! ```
175//!
176
177
178use core::convert::TryFrom;
179use core::fmt;
180use core::ops::Deref;
181
182use crate::wrapper::PmIter;
183use crate::wrapper::ProgMem;
184
185
186pub(crate) mod from_slice;
187mod validations;
188
189
190
191/// Indicates that static type size does not match the dynamic str length.
192#[derive(Debug, Clone)]
193pub struct InvalidLengthError;
194
195
196/// A string stored as byte array.
197///
198/// This type is a simple wrapper around a byte array `[u8;N]` and therefore,
199/// is stored as such.
200/// However, this type primarily is created from `&str` and derefs to `&str`,
201/// thus it can be used similar to `String` except that it is not mutable.
202///
203/// This type is particularly useful to store string literals in progmem.
204///
205///
206/// # Safety
207///
208/// The wrapped byte array must contain valid UTF-8.
209///
210#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
211#[non_exhaustive] // SAFETY: must not be publicly constructible
212pub struct LoadedString<const N: usize> {
213 /// The inner UTF-8 string as byte array
214 ///
215 /// # Safety
216 ///
217 /// Must be valid UTF-8.
218 utf8_array: [u8; N],
219}
220
221impl<const N: usize> LoadedString<N> {
222 /// Creates a new byte array from the given string
223 ///
224 /// # Error
225 ///
226 /// If the byte size of `str` is not exactly `N`, `None` is returned.
227 ///
228 pub const fn new(s: &str) -> Option<Self> {
229 let bytes: &[u8] = s.as_bytes();
230 unsafe {
231 // SAFETY: we got a `str` thus it must already contain valid UTF-8
232 Self::from_bytes(bytes)
233 }
234 }
235
236 /// Wraps the given byte slice
237 ///
238 /// # Safety
239 ///
240 /// The give byte slice must contain valid UTF-8.
241 ///
242 /// # Error
243 ///
244 /// If the size of the given byte slice is not exactly `N`, `None` is
245 /// returned.
246 ///
247 pub const unsafe fn from_bytes(bytes: &[u8]) -> Option<Self> {
248 // Cast slice into an array
249 let res = from_slice::array_ref_try_from_slice(bytes);
250
251 match res {
252 Ok(array) => {
253 let array = *array;
254 unsafe {
255 // SAFETY: the caller ensures that the bytes are valid
256 // UTF-8
257 Some(Self::from_array(array))
258 }
259 },
260 Err(_e) => None,
261 }
262 }
263
264 /// Wraps the given byte array
265 ///
266 /// # Safety
267 ///
268 /// The give byte array must contain valid UTF-8.
269 ///
270 pub const unsafe fn from_array(array: [u8; N]) -> Self {
271 /* TODO: Use this once it becomes const fn
272 match core::str::from_utf8(bytes) {
273 Ok(_) => (),
274 Err(_) => panic!("Not UTF-8"),
275 };
276 */
277
278 // SAFETY: The caller ensures that `array` is indeed UTF-8
279 Self {
280 utf8_array: array,
281 }
282 }
283
284 /// Returns the underlying byte array.
285 pub fn as_bytes(&self) -> &[u8; N] {
286 &self.utf8_array
287 }
288}
289
290impl<const N: usize> TryFrom<&str> for LoadedString<N> {
291 type Error = InvalidLengthError;
292
293 fn try_from(s: &str) -> Result<Self, Self::Error> {
294 match LoadedString::new(s) {
295 Some(bs) => Ok(bs),
296 None => Err(InvalidLengthError),
297 }
298 }
299}
300
301impl<const N: usize> Deref for LoadedString<N> {
302 type Target = str;
303
304 fn deref(&self) -> &str {
305 unsafe {
306 // SAFETY: by the contract of this struct, `utf8_array` must be
307 // valid UTF-8
308 core::str::from_utf8_unchecked(&self.utf8_array)
309 }
310 }
311}
312
313impl<const N: usize> fmt::Display for LoadedString<N> {
314 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
315 write!(fmt, "{}", self.deref())
316 }
317}
318
319#[cfg(feature = "ufmt")]
320impl<const N: usize> ufmt::uDisplay for LoadedString<N> {
321 fn fmt<W: ?Sized>(&self, fmt: &mut ufmt::Formatter<W>) -> Result<(), W::Error>
322 where
323 W: ufmt::uWrite,
324 {
325 ufmt::uwrite!(fmt, "{}", self.deref())
326 }
327}
328
329
330/// A byte string in progmem
331///
332/// Not to be confused with a [`LoadedString`].
333/// A `LoadedString` is a simple wrapper around a byte array (`[u8;N]`) that
334/// derefs to `str`, and should be used in RAM.
335/// A `PmString` on the other hand, is a wrapper around a byte array in progmem
336/// aka around a `ProgMem<[u8;N]>`, and thus must always be progmem.
337/// Similar to `ProgMem`, `PmString` offers a [`load`](PmString::load) method to
338/// load its entire content into RAM.
339/// The loaded content will be a `LoadedString`, hence the name.
340///
341/// Besides loading the entire string at once into RAM, `PmString` also offers
342/// a lazy [`chars`](PmString::chars) iterator method, that will load just one
343/// char at a time.
344/// This allows `chars` to be used on very large strings that do not fit into
345/// the RAM as whole.
346///
347///
348/// # Safety
349///
350/// This type is a wrapper around [`ProgMem`], thus it any value of this type
351/// must be placed in program memory.
352/// See the [`ProgMem`] safety section for more details.
353///
354/// Additionally to the [`ProgMem`] contract, the byte array wrapped by this
355/// struct must be valid UTF-8.
356///
357///
358/// # Example
359///
360/// ```rust
361/// use avr_progmem::progmem;
362/// use avr_progmem::string::PmString;
363/// use avr_progmem::string::LoadedString;
364///
365/// progmem! {
366/// // Stores a string as a byte array, i.e. `[u8;19]`, but makes it usable
367/// // as `&str` (via `Deref`)
368/// static progmem string TEXT = "dai 大賢者 kenja";
369/// }
370///
371/// // The static has type `PmString`
372/// let text: &PmString<19> = &TEXT;
373/// // The loaded RAM string has type `LoadedString`
374/// let loaded: LoadedString<19> = text.load();
375/// // Which derefs to `&str`
376/// assert_eq!("dai 大賢者 kenja", &*loaded)
377/// ```
378///
379//
380//
381// SAFETY: this struct must not be publicly constructible
382#[non_exhaustive]
383//
384// Its just a pointer type, thus copy, clone & debug are fine (none of them
385// will access the progmem, that's what `Display` is for).
386#[derive(Copy, Clone, Debug)]
387// Also impl `uDebug` if enabled.
388#[cfg_attr(feature = "ufmt", derive(ufmt::derive::uDebug))]
389pub struct PmString<const N: usize> {
390 /// The inner UTF-8 string as byte array in progmem.
391 ///
392 /// # Safety
393 ///
394 /// Must be valid UTF-8.
395 pm_utf8_array: ProgMem<[u8; N]>,
396}
397
398impl<const N: usize> PmString<N> {
399 /// Creates a new byte array from the given string
400 ///
401 /// You are encouraged to use the [`progmem`](crate::progmem) macro instead.
402 ///
403 /// # Safety
404 ///
405 /// This function is only sound to call, if the value is
406 /// is a valid `ProgMem`, and the underlying byte array contains valid UTF-8.
407 pub const unsafe fn new(pm: ProgMem<[u8; N]>) -> Self {
408 // SAFETY: the caller ensures that the bytes are valid UTF-8
409 Self {
410 pm_utf8_array: pm,
411 }
412 }
413
414 /// Loads the entire string into RAM
415 ///
416 /// # Panics
417 ///
418 /// This method panics, if the size of the value (i.e. `N`) is beyond 255
419 /// bytes.
420 /// However, this is currently just a implementation limitation, which may
421 /// be lifted in the future.
422 ///
423 /// If you have a very large string, consider using the lazy
424 /// [`chars`](Self::chars) iterator that accesses the string by one char at
425 /// a time and thus does not have such a limitation.
426 ///
427 pub fn load(&self) -> LoadedString<N> {
428 let array = self.load_bytes();
429
430 let bs_opt = unsafe {
431 // SAFETY: The contract on `Self` guarantees us that we have UTF-8
432 LoadedString::from_bytes(&array)
433 };
434
435 bs_opt.unwrap()
436 }
437
438 /// Loads the entire string as byte array into RAM
439 ///
440 /// # Panics
441 ///
442 /// This method panics, if the size of the value (i.e. `[u8; N]`) is beyond
443 /// 255 bytes.
444 /// However, this is currently just a implementation limitation, which may
445 /// be lifted in the future.
446 ///
447 /// If you have a very large string, consider using the lazy
448 /// [`chars`](Self::chars) iterator or the respective byte iterator
449 /// (via `as_bytes().iter()`).
450 pub fn load_bytes(&self) -> [u8; N] {
451 self.as_bytes().load()
452 }
453
454 /// Returns the underlying progmem byte array.
455 pub fn as_bytes(&self) -> &ProgMem<[u8; N]> {
456 &self.pm_utf8_array
457 }
458
459 /// Lazily iterate over the `char`s of the string.
460 ///
461 /// This function is analog to [`ProgMem::iter`], except it performs UTF-8
462 /// parsing and returns the `char`s of this string, thus it is more similar
463 /// to [`str::chars`].
464 pub fn chars(&self) -> PmChars<N> {
465 PmChars::new(self)
466 }
467}
468
469impl<const N: usize> fmt::Display for PmString<N> {
470 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
471 for c in self.chars() {
472 write!(fmt, "{}", c)?
473 }
474 Ok(())
475 }
476}
477
478#[cfg(feature = "ufmt")]
479impl<const N: usize> ufmt::uDisplay for PmString<N> {
480 fn fmt<W: ?Sized>(&self, fmt: &mut ufmt::Formatter<W>) -> Result<(), W::Error>
481 where
482 W: ufmt::uWrite,
483 {
484 for c in self.chars() {
485 ufmt::uwrite!(fmt, "{}", c)?
486 }
487 Ok(())
488 }
489}
490
491
492/// An iterator over a [`PmString`]
493///
494/// # Safety
495///
496/// The inner byte iterator of this struct must yield valid UTF-8 sequence.
497#[non_exhaustive] // SAFETY: this struct must not be publicly constructible
498pub struct PmChars<'a, const N: usize> {
499 /// The inner byte iterator
500 ///
501 /// # Safety
502 ///
503 /// Must yield valid UTF-8 sequences.
504 bytes: PmIter<'a, u8, N>,
505}
506
507impl<'a, const N: usize> PmChars<'a, N> {
508 pub fn new(pm: &'a PmString<N>) -> Self {
509 // SAFETY: the contract on PmString guarantees us that it wraps
510 // valid UTF-8, thus its byte iterator will yield valid UTF-8
511 PmChars {
512 bytes: pm.pm_utf8_array.iter(),
513 }
514 }
515}
516
517impl<'a, const N: usize> Iterator for PmChars<'a, N> {
518 type Item = char;
519
520 fn next(&mut self) -> Option<Self::Item> {
521 unsafe {
522 // SAFETY: the contract on `Self` struct guarantees us that we only
523 // get valid UTF-8 sequences
524 validations::next_code_point(&mut self.bytes)
525 }
526 .map(|u| core::char::from_u32(u).unwrap())
527 }
528}
529
530
531
532/// Define a single-use string in progmem usable as temporary `&str`
533///
534/// This is a short-cut macro to create an ad-hoc static storing the given
535/// string literal as by [`LoadedString`] and load it here from progmem into a
536/// temporary and return it as `&str`.
537/// This is similar to the `F` macro available in Arduino.
538///
539/// Similar to the C marco, this will load the full string into RAM at once
540/// and thus the string should be of limited size, to not exceed the space
541/// available in RAM.
542/// Also see the [progmem_display](crate::progmem_display) macro which does not have this limitation.
543///
544/// This macro allows to conveniently put a literal string into progmem
545/// right where it is used.
546/// However, since they are directly loaded into a temporary you don't get a
547/// `&'static str` back, and must use the `&str` immediately (i.e. pass it as a
548/// function parameter).
549/// You can't even store the returned `&str` in a local `let` assignment.
550///
551///
552/// # Example
553///
554/// ```rust
555/// use avr_progmem::progmem_str as F;
556/// use ufmt::uWrite;
557///
558/// # struct MyWriter;
559/// # impl uWrite for MyWriter {
560/// # type Error = ();
561/// # fn write_str(&mut self, _s: &str) -> Result<(),()> {
562/// # Ok(()) // ignore input
563/// # }
564/// # }
565/// #
566/// let mut writer = // impl uWrite
567/// # MyWriter;
568/// /* SNIP */;
569///
570/// // Put the literal `str` into progmem and load it here as `&str`
571/// writer.write_str(F!("dai 大賢者 kenja"));
572/// ```
573///
574#[macro_export]
575macro_rules! progmem_str {
576 ($text:expr) => {{
577 $crate::progmem! {
578 static progmem string TEXT = $text;
579 }
580 &*TEXT.load()
581 }};
582}
583
584
585/// Define a single-use string in progmem usable as `impl Display + uDisplay`
586///
587/// This is a short-cut macro to create an ad-hoc static storing the given
588/// string literal as a [`PmString`] and return it.
589/// This is somewhat similar to the `F` macro available in Arduino IDE, but
590/// different.
591/// For a macro more in line with the `F` macro, see [progmem_str].
592///
593/// Unlike the `F` macro, this macro neither loads the string here, nor, can
594/// it be use as a `&str`.
595/// However, the returned value implements [Display](fmt::Display) as well as
596/// [ufmt::uDisplay] (if the `ufmt` crate feature is enabled).
597///
598/// This macro allows to conveniently put a literal string into progmem
599/// right where it is used.
600/// However, since it is not loaded (yet) into RAM it is not a `&str`, it only
601/// exposes a [Display](fmt::Display) and [ufmt::uDisplay] (if the `ufmt` crate
602/// feature is enabled) implementation,
603/// which will load it char-by-char when used, thus limiting the RAM usage,
604/// and allowing arbitrarily large strings to be wrapped.
605///
606///
607/// # Example
608///
609/// ```rust
610/// use avr_progmem::progmem_display as D;
611/// use ufmt::uWrite;
612///
613/// # struct MyWriter;
614/// # impl uWrite for MyWriter {
615/// # type Error = ();
616/// # fn write_str(&mut self, _s: &str) -> Result<(),()> {
617/// # Ok(()) // ignore input
618/// # }
619/// # }
620/// #
621/// let mut writer = // impl uWrite
622/// # MyWriter;
623/// /* SNIP */;
624///
625/// // Put the literal `str` into progmem and use it as `impl uDisplay`
626/// #[cfg(feature = "ufmt")] // requires the `ufmt` crate feature
627/// ufmt::uwrite!(&mut writer, "{}", D!("dai 大賢者 kenja"));
628///
629/// // Huge strings are fine
630/// #[cfg(feature = "ufmt")] // requires the `ufmt` crate feature
631/// ufmt::uwrite!(&mut writer, "{}", D!(include_str!("../examples/test_text.txt")));
632/// ```
633///
634#[macro_export]
635macro_rules! progmem_display {
636 ($text:expr) => {{
637 $crate::progmem! {
638 static progmem string TEXT = $text;
639 }
640 &TEXT
641 }};
642}