use core::ffi::CStr;
use core::fmt::{self, Write};
pub const MESSAGE_CAPACITY: usize = 256;
const TRUNCATION_MARKER: &str = "...";
struct Message {
bytes: [u8; MESSAGE_CAPACITY + 1],
len: usize,
truncated: bool,
}
impl Message {
const fn new() -> Self {
Self {
bytes: [0; MESSAGE_CAPACITY + 1],
len: 0,
truncated: false,
}
}
fn append(&mut self, s: &str) {
let room = MESSAGE_CAPACITY - self.len;
let end = if s.len() <= room {
s.len()
} else {
self.truncated = true;
floor_char_boundary(s.as_bytes(), room)
};
self.bytes[self.len..self.len + end].copy_from_slice(&s.as_bytes()[..end]);
self.len += end;
}
fn finish(&mut self, newline: bool) -> &CStr {
let newline_len = usize::from(newline);
if self.len + newline_len > MESSAGE_CAPACITY {
self.truncated = true;
}
let tail = if self.truncated {
TRUNCATION_MARKER.len() + newline_len
} else {
newline_len
};
if self.len + tail > MESSAGE_CAPACITY {
self.len = floor_char_boundary(&self.bytes[..self.len], MESSAGE_CAPACITY - tail);
}
if self.truncated {
self.push(TRUNCATION_MARKER.as_bytes());
}
if newline {
self.push(b"\n");
}
self.bytes[self.len] = 0;
self.as_c_str()
}
fn push(&mut self, bytes: &[u8]) {
self.bytes[self.len..self.len + bytes.len()].copy_from_slice(bytes);
self.len += bytes.len();
}
fn as_c_str(&self) -> &CStr {
CStr::from_bytes_until_nul(&self.bytes).unwrap_or(c"")
}
}
impl Write for Message {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.append(s);
if self.truncated {
Err(fmt::Error)
} else {
Ok(())
}
}
}
fn floor_char_boundary(bytes: &[u8], index: usize) -> usize {
let mut index = index;
while index > 0 && index < bytes.len() && bytes[index] & 0b1100_0000 == 0b1000_0000 {
index -= 1;
}
index
}
pub fn print_args(args: fmt::Arguments<'_>) {
emit(args, false);
}
pub fn println_args(args: fmt::Arguments<'_>) {
emit(args, true);
}
fn emit(args: fmt::Arguments<'_>, newline: bool) {
let mut message = Message::new();
let _ = message.write_fmt(args);
write_c_str(message.finish(newline));
}
#[cfg(bela_device)]
fn write_c_str(message: &CStr) {
let _ = unsafe { bela_sys::Bela_printf(c"%s".as_ptr(), message.as_ptr()) };
}
#[cfg(not(bela_device))]
fn write_c_str(message: &CStr) {
use std::io::{Write as _, stdout};
let mut out = stdout();
let _ = out.write_all(message.to_bytes());
let _ = out.flush();
}
#[macro_export]
macro_rules! rt_print {
($($arg:tt)*) => {
$crate::print_args(::core::format_args!($($arg)*))
};
}
#[macro_export]
macro_rules! rt_println {
() => {
$crate::println_args(::core::format_args!(""))
};
($($arg:tt)*) => {
$crate::println_args(::core::format_args!($($arg)*))
};
}
#[cfg(test)]
mod tests {
use super::*;
fn format(args: fmt::Arguments<'_>, newline: bool) -> String {
let mut message = Message::new();
let _ = message.write_fmt(args);
String::from_utf8(message.finish(newline).to_bytes().to_vec())
.expect("messages are truncated on char boundaries, so they stay valid UTF-8")
}
#[test]
fn formats_arguments() {
assert_eq!(format(format_args!("x = {}", 42), false), "x = 42");
}
#[test]
fn appends_a_newline_when_asked() {
assert_eq!(format(format_args!("done"), true), "done\n");
}
#[test]
fn a_message_of_exactly_the_capacity_is_left_alone() {
let text = "a".repeat(MESSAGE_CAPACITY);
assert_eq!(format(format_args!("{text}"), false), text);
}
#[test]
fn a_message_over_the_capacity_is_marked_as_truncated() {
let formatted = format(format_args!("{}", "a".repeat(MESSAGE_CAPACITY + 10)), false);
assert_eq!(formatted.len(), MESSAGE_CAPACITY);
assert!(
formatted.ends_with(TRUNCATION_MARKER),
"truncation should be visible: {formatted}"
);
}
#[test]
fn a_truncated_line_still_ends_with_a_newline() {
let formatted = format(format_args!("{}", "a".repeat(MESSAGE_CAPACITY + 10)), true);
assert_eq!(formatted.len(), MESSAGE_CAPACITY);
assert!(
formatted.ends_with("...\n"),
"a truncated line should end with the marker and a newline: {formatted}"
);
}
#[test]
fn a_newline_that_does_not_fit_truncates_the_text() {
let formatted = format(format_args!("{}", "a".repeat(MESSAGE_CAPACITY)), true);
assert_eq!(formatted.len(), MESSAGE_CAPACITY);
assert!(
formatted.ends_with("...\n"),
"the newline has to displace text: {formatted}"
);
}
#[test]
fn multi_byte_characters_are_not_split() {
let text = "あ".repeat(MESSAGE_CAPACITY / 3 + 1);
let formatted = format(format_args!("{text}"), false);
assert!(
formatted.ends_with(TRUNCATION_MARKER),
"expected a truncated message: {formatted}"
);
let characters = formatted
.trim_end_matches(TRUNCATION_MARKER)
.chars()
.count();
assert_eq!(characters * 3, formatted.len() - TRUNCATION_MARKER.len());
}
#[test]
fn formatting_stops_once_the_buffer_is_full() {
struct Counting<'a> {
message: &'a mut Message,
calls: usize,
}
impl Write for Counting<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.calls += 1;
self.message.write_str(s)
}
}
let mut message = Message::new();
let mut counting = Counting {
message: &mut message,
calls: 0,
};
let _ = counting.write_fmt(format_args!("{:width$}", "", width = 65_535));
let calls = counting.calls;
assert!(
calls < 2 * MESSAGE_CAPACITY,
"the work should be bounded by the capacity, not by the width, but took {calls} writes"
);
let formatted = message.finish(false).to_bytes();
assert_eq!(formatted.len(), MESSAGE_CAPACITY);
assert!(formatted.ends_with(TRUNCATION_MARKER.as_bytes()));
}
#[test]
fn a_message_ends_at_an_interior_nul() {
assert_eq!(format(format_args!("before\0after"), false), "before");
}
#[test]
fn percent_signs_are_not_format_specifiers() {
assert_eq!(format(format_args!("100%s of it"), false), "100%s of it");
}
}