-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add string utilities to make working with string literals easier.
See #3
- Loading branch information
Showing
3 changed files
with
243 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
// | ||
// This file provides a example on how to use strings on an Arduino Uno. | ||
// | ||
|
||
|
||
// Define no_std only for AVR | ||
#![cfg_attr(target_arch = "avr", no_std)] | ||
#![no_main] | ||
// | ||
// To unwrap the Option in const context | ||
#![feature(const_option)] | ||
|
||
|
||
use avr_progmem::progmem; // The macro | ||
use avr_progmem::string::ByteString; // Helper for storing strings | ||
#[cfg(target_arch = "avr")] | ||
use panic_halt as _; // halting panic implementation for AVR | ||
|
||
|
||
progmem! { | ||
/// The static data to be stored in program code section | ||
/// Notice the usage of `ByteString`, to store a string as `[u8;N]`, | ||
/// because you can't store a `str` and storing a `&str` wouldn't have | ||
/// much of an effect. | ||
static progmem SOME_TEXT: ByteString<189> = ByteString::new(" | ||
A long test string literal, that is stored in progmem instead of DRAM. | ||
However, to use it, it needs to be temporarily load into DRAM, | ||
so an individual `ByteString` shouldn't be too long. | ||
").unwrap(); | ||
|
||
/// More data to be stored in program code section | ||
static progmem MORE_TEXT: ByteString<102> = ByteString::new(" | ||
However, you easily store your strings individual, limiting the amount of | ||
temporary DRAM necessary. | ||
").unwrap(); | ||
|
||
/// Unicode works of course as expected | ||
/// | ||
static progmem UNICODE_TEXT: ByteString<137> = ByteString::new( | ||
"dai 大賢者 kenja, Völlerei lässt grüßen, le garçon de théâtre, Ελληνική Δημοκρατία, Слава Україні" | ||
).unwrap(); | ||
} | ||
|
||
// Include a fancy printer supporting Arduino Uno's USB-Serial output as well | ||
// as stdout on non-AVR targets. | ||
mod printer; | ||
use printer::Printer; | ||
|
||
#[no_mangle] | ||
fn main() -> ! { | ||
let mut printer = { | ||
#[cfg(target_arch = "avr")] | ||
{ | ||
// Initialize the USB-Serial output on the Arduino Uno | ||
|
||
let dp = arduino_uno::Peripherals::take().unwrap(); | ||
|
||
let mut pins = arduino_uno::Pins::new(dp.PORTB, dp.PORTC, dp.PORTD); | ||
|
||
let serial = arduino_uno::Serial::new( | ||
dp.USART0, | ||
pins.d0, | ||
pins.d1.into_output(&mut pins.ddr), | ||
9600, | ||
); | ||
Printer(serial) | ||
} | ||
#[cfg(not(target_arch = "avr"))] | ||
{ | ||
// Just use stdout for non-AVR targets | ||
Printer | ||
} | ||
}; | ||
|
||
// Print some introduction text | ||
printer.println("Hello from Arduino!"); | ||
printer.println(""); | ||
printer.println("--------------------------"); | ||
printer.println(""); | ||
|
||
// Scope to limit the lifetime of `text_buffer` | ||
{ | ||
// The temporary DRAM buffer for the string | ||
let text_buffer = SOME_TEXT.load(); | ||
let text: &str = &text_buffer; // Just derefs to `str` | ||
printer.println(text); | ||
} | ||
|
||
// Or just using temporaries | ||
printer.println(&MORE_TEXT.load()); | ||
printer.println(&UNICODE_TEXT.load()); | ||
|
||
// Even more convenient: use a one-off in-place progmem static via `progmem_str` | ||
printer.println(avr_progmem::progmem_str!("Just a lone literal progmem str")); | ||
printer.println(avr_progmem::progmem_str!("And another one")); | ||
|
||
// Print some final lines | ||
printer.println(""); | ||
printer.println("--------------------------"); | ||
printer.println(""); | ||
printer.println("DONE"); | ||
|
||
// It is very convenient to just exit on non-AVR platforms, otherwise users | ||
// might get the impression that the program hangs, whereas it already | ||
// succeeded. | ||
#[cfg(not(target_arch = "avr"))] | ||
std::process::exit(0); | ||
|
||
// Otherwise, that is on AVR, just go into an infinite loop, because on AVR | ||
// we just can't exit! | ||
loop { | ||
// Done, just do nothing | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
use core::ops::Deref; | ||
|
||
|
||
|
||
/// Our own `core::array::TryFromSliceError` | ||
/// | ||
/// Used in [`array_ref_try_from_slice`]. | ||
// We need a local copy of this type, because we need to instantiate it, but it | ||
// has a private field. | ||
struct TryFromSliceError(()); | ||
|
||
/// Const version of `<&[T; N]>::try_from(&[T])` | ||
/// | ||
/// Original Source: | ||
/// https://github.com/rust-lang/rust/blob/eb82facb1626166188d49599a3313fc95201f556/library/core/src/array/mod.rs#L203-L215 | ||
const fn array_ref_try_from_slice<'a, T, const N: usize>( | ||
slice: &[T], | ||
) -> Result<&[T; N], TryFromSliceError> { | ||
if slice.len() == N { | ||
let ptr = slice.as_ptr() as *const [T; N]; | ||
// SAFETY: ok because we just checked that the length fits | ||
unsafe { Ok(&*ptr) } | ||
} else { | ||
Err(TryFromSliceError(())) | ||
} | ||
} | ||
|
||
/// A string stored as byte array. | ||
/// | ||
/// This type is a simple wrapper around a byte array `[u8;N]` and therefore, | ||
/// is stored as such. | ||
/// However, this type primarily is created from `&str` and derefs to `&str`, | ||
/// thus it can be used similar to `String` except that it is not mutable. | ||
/// | ||
/// This type is particularly useful to store string literals in progmem. | ||
/// | ||
/// # Example | ||
/// | ||
/// ```rust | ||
/// #![feature(const_option)] | ||
/// | ||
/// use avr_progmem::progmem; | ||
/// use avr_progmem::string::ByteString; | ||
/// | ||
/// progmem! { | ||
/// // Stores a string as a byte array, i.e. `[u8;19]`, but makes it usable | ||
/// // as `&str` (via `Deref`) | ||
/// static progmem TEXT: ByteString<19> = ByteString::new( | ||
/// "dai 大賢者 kenja" | ||
/// ).unwrap(); | ||
/// } | ||
/// | ||
/// // usage: | ||
/// let text_buffer = TEXT.load(); // The temporary DRAM buffer for `TEXT` | ||
/// let text: &str = &text_buffer; // Just derefs to `str` | ||
/// assert_eq!(text, "dai 大賢者 kenja") | ||
/// ``` | ||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
pub struct ByteString<const N: usize>(pub [u8; N]); | ||
|
||
impl<const N: usize> ByteString<N> { | ||
/// Creates a new byte array from the given string | ||
pub const fn new(s: &str) -> Option<Self> { | ||
Self::from_bytes(s.as_bytes()) | ||
} | ||
|
||
/// Wraps the given byte slice | ||
pub const fn from_bytes(bytes: &[u8]) -> Option<Self> { | ||
let res = array_ref_try_from_slice(bytes); | ||
|
||
match res { | ||
Ok(array) => Some(Self(*array)), | ||
Err(_e) => None, | ||
} | ||
} | ||
} | ||
|
||
impl<const N: usize> Deref for ByteString<N> { | ||
type Target = str; | ||
|
||
fn deref(&self) -> &str { | ||
core::str::from_utf8(&self.0).unwrap() | ||
} | ||
} | ||
|
||
/// Define a string in progmem | ||
/// | ||
/// This is a short-cut macro to create an ad-hoc static storing the given | ||
/// string literal as by [`ByteString`] and load it here from progmem into a | ||
/// temporary and return it as `&str`. | ||
/// | ||
/// This macro allows to conveniently put literal string into progmem exactly, | ||
/// where they are used. However, since they are directly loaded into a | ||
/// temporary you don't get a `&'static str` back, and must use the `&str` | ||
/// immediately (i.e. pass it as a function parameter). | ||
/// You can't even store the returned `&str` in a local `let` assignment. | ||
/// | ||
/// # Example | ||
/// | ||
/// ```rust | ||
/// #![feature(const_option)] | ||
/// | ||
/// use avr_progmem::progmem_str as S; | ||
/// | ||
/// fn print(s: &str) { | ||
/// // -- snip -- | ||
/// # assert_eq!(s, "dai 大賢者 kenja") | ||
/// } | ||
/// | ||
/// // Put the literal as byte array into progmem and load it here as `&str` | ||
/// print(S!("dai 大賢者 kenja")); | ||
/// ``` | ||
#[macro_export] | ||
macro_rules! progmem_str { | ||
($text:literal) => {{ | ||
const TEXT_LEN: usize = <str>::as_bytes($text).len(); | ||
$crate::progmem! { | ||
static progmem TEXT: $crate::string::ByteString<TEXT_LEN> = $crate::string::ByteString::new( | ||
$text | ||
).unwrap(); | ||
} | ||
&*TEXT.load() | ||
}}; | ||
} |