Skip to content

Commit

Permalink
Add string utilities to make working with string literals easier.
Browse files Browse the repository at this point in the history
See #3
  • Loading branch information
Cryptjar committed Apr 2, 2022
1 parent 1ebc117 commit 3976ba2
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 0 deletions.
114 changes: 114 additions & 0 deletions examples/uno-string.rs
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
}
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
// However, it seems in more recent Rust version there is no more `llvm_asm`.
// And docs.rs uses the latest Rust version.
#![cfg_attr(not(doc), feature(llvm_asm))]
//
// For string support, we need to convert from slice to array in const context.
#![cfg_attr(not(doc), feature(const_raw_ptr_deref))]

//!
//! Progmem utilities for the AVR architectures.
Expand Down Expand Up @@ -187,6 +190,7 @@


pub mod raw;
pub mod string;
mod wrapper;

pub use wrapper::ProgMem;
125 changes: 125 additions & 0 deletions src/string.rs
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()
}};
}

0 comments on commit 3976ba2

Please sign in to comment.