#![no_std]
#![doc(html_root_url = "https://docs.rs/compile-fmt/0.1.0")]
#![warn(missing_debug_implementations, missing_docs, bare_trait_objects)]
#![warn(clippy::all, clippy::pedantic)]
#![allow(
clippy::missing_errors_doc,
clippy::must_use_candidate,
clippy::module_name_repetitions
)]
use core::{fmt, slice, str};
#[cfg(test)]
extern crate std;
mod argument;
mod format;
mod macros;
#[cfg(test)]
mod tests;
mod utils;
#[doc(hidden)]
pub use crate::argument::{Argument, ArgumentWrapper};
pub use crate::{
argument::Ascii,
format::{clip, clip_ascii, fmt, Fmt, FormatArgument, MaxLength, StrLength},
};
use crate::{format::StrFormat, utils::ClippedStr};
#[derive(Debug)]
pub struct CompileArgs<const CAP: usize> {
buffer: [u8; CAP],
len: usize,
}
impl<const CAP: usize> fmt::Display for CompileArgs<CAP> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl<const CAP: usize> AsRef<str> for CompileArgs<CAP> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<const CAP: usize> CompileArgs<CAP> {
pub const CAPACITY: usize = CAP;
#[doc(hidden)] #[track_caller]
pub const fn assert_capacity(required_capacity: usize) {
compile_assert!(
CAP >= required_capacity,
"Insufficient capacity (", CAP => fmt::<usize>(), " bytes) provided \
for `compile_args` macro; it requires at least ", required_capacity => fmt::<usize>(), " bytes"
);
}
const fn new() -> Self {
Self {
buffer: [0_u8; CAP],
len: 0,
}
}
const fn write_str(self, s: &str, fmt: Option<StrFormat>) -> Self {
match fmt {
Some(StrFormat { clip_at, using }) => {
let clipped = ClippedStr::new(s, clip_at);
match clipped {
ClippedStr::Full(bytes) => self.write_str_bytes(bytes),
ClippedStr::Clipped(bytes) => self
.write_str_bytes(bytes)
.write_str_bytes(using.as_bytes()),
}
}
_ => self.write_str_bytes(s.as_bytes()),
}
}
const fn write_str_bytes(self, s_bytes: &[u8]) -> Self {
let new_len = self.len + s_bytes.len();
let mut buffer = self.buffer;
let mut pos = self.len;
while pos < new_len {
buffer[pos] = s_bytes[pos - self.len];
pos += 1;
}
Self {
buffer,
len: new_len,
}
}
#[allow(clippy::cast_possible_truncation)] const fn write_char(self, c: char) -> Self {
const TAG_CONT: u8 = 0b_1000_0000;
const TAG_TWO_BYTES: u8 = 0b_1100_0000;
const TAG_THREE_BYTES: u8 = 0b_1110_0000;
const TAG_FOUR_BYTES: u8 = 0b_1111_0000;
let new_len = self.len + c.len_utf8();
let mut buffer = self.buffer;
let pos = self.len;
let code = c as u32;
match c.len_utf8() {
1 => {
buffer[pos] = code as u8;
}
2 => {
buffer[pos] = (code >> 6 & 0x_1f) as u8 | TAG_TWO_BYTES;
buffer[pos + 1] = (code & 0x_3f) as u8 | TAG_CONT;
}
3 => {
buffer[pos] = (code >> 12 & 0x_0f) as u8 | TAG_THREE_BYTES;
buffer[pos + 1] = (code >> 6 & 0x_3f) as u8 | TAG_CONT;
buffer[pos + 2] = (code & 0x_3f) as u8 | TAG_CONT;
}
4 => {
buffer[pos] = (code >> 18 & 0x_07) as u8 | TAG_FOUR_BYTES;
buffer[pos + 1] = (code >> 12 & 0x_3f) as u8 | TAG_CONT;
buffer[pos + 2] = (code >> 6 & 0x_3f) as u8 | TAG_CONT;
buffer[pos + 3] = (code & 0x_3f) as u8 | TAG_CONT;
}
_ => unreachable!(),
}
Self {
buffer,
len: new_len,
}
}
#[doc(hidden)] pub const fn format(arguments: &[Argument]) -> Self {
let mut this = Self::new();
let mut arg_i = 0;
while arg_i < arguments.len() {
this = this.format_arg(arguments[arg_i]);
arg_i += 1;
}
this
}
pub const fn as_str(&self) -> &str {
unsafe {
let written_slice = slice::from_raw_parts(self.buffer.as_ptr(), self.len);
str::from_utf8_unchecked(written_slice)
}
}
}
impl<const CAP: usize> FormatArgument for &CompileArgs<CAP> {
type Details = ();
const MAX_BYTES_PER_CHAR: usize = 4;
}
impl<const CAP: usize> MaxLength for &CompileArgs<CAP> {
const MAX_LENGTH: StrLength = StrLength::both(CAP);
}
#[cfg(doctest)]
doc_comment::doctest!("../README.md");