Conlang/constr/src/lib.rs

256 lines
8.8 KiB
Rust

//! [String] tools for Conlang
//#![warn(clippy::all)]
#![feature(decl_macro, const_trait_impl)]
impl<T: Iterator> ConstrTools for T {}
pub trait ConstrTools {
/// Unescapes string escape sequences
fn unescape(self) -> UnescapeString<Self>
where Self: Iterator<Item = char> + Sized {
UnescapeString::new(self)
}
/// Parse an integer
fn parse_int<O>(self) -> ParseInt<Self, O>
where Self: Iterator<Item = char> + Sized {
ParseInt::new(self)
}
}
pub use unescape_string::UnescapeString;
pub mod unescape_string {
//! TODO: Write the module-level documentation
pub struct UnescapeString<I: Iterator<Item = char>> {
inner: I,
}
impl<I: Iterator<Item = char>> Iterator for UnescapeString<I> {
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
self.unescape()
}
}
impl<I: Iterator<Item = char>> UnescapeString<I> {
pub fn new(inner: I) -> Self {
Self { inner }
}
/// Consumes an escape sequence. See the [module level documentation](self).
pub fn unescape(&mut self) -> Option<char> {
match self.inner.next()? {
'\\' => (),
other => return Some(other),
}
Some(match self.inner.next()? {
'a' => '\x07',
'b' => '\x08',
'f' => '\x0c',
'n' => '\n',
'r' => '\r',
't' => '\t',
'x' => self.hex_digits::<2>()?,
'u' => self.unicode()?,
'0' => '\0',
byte => byte,
})
}
fn unicode(&mut self) -> Option<char> {
let mut out = 0;
let Some('{') = self.inner.next() else {
return None;
};
for c in self.inner.by_ref() {
match c {
'}' => return char::from_u32(out),
_ => out = (out << 4) + super::base::<16>(c)? as u32,
}
}
None
}
fn hex_digits<const DIGITS: u32>(&mut self) -> Option<char> {
let mut out = 0;
for _ in 0..DIGITS {
out = (out << 4) + self.hex_digit()? as u32;
}
char::from_u32(out)
}
fn hex_digit(&mut self) -> Option<u8> {
super::base::<16>(self.inner.next()?)
}
}
}
pub use parse_int::ParseInt;
pub mod parse_int {
use std::marker::PhantomData;
pub struct ParseInt<I: Iterator<Item = char>, O> {
inner: I,
_data: PhantomData<O>,
}
impl<I: Iterator<Item = char>, O> ParseInt<I, O> {
pub fn new(inner: I) -> Self {
Self { inner, _data: Default::default() }
}
fn digit<const B: u8>(&mut self) -> Option<u8> {
let next = loop {
match self.inner.next()? {
'_' => continue,
c => break c,
}
};
super::base::<B>(next)
}
}
parse_int_impl!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128);
macro parse_int_impl($($T:ty),*$(,)?) {$(
impl<I: Iterator<Item = char>> ParseInt<I, $T> {
fn digits<const B: u8>(&mut self, init: Option<u8>) -> Option<$T> {
let mut out = match init {
Some(digit) => digit,
None => self.digit::<B>()?,
} as $T;
while let Some(digit) = self.digit::<B>() {
out = out.checked_mul(B as $T)?.checked_add(digit as $T)?
}
Some(out)
}
fn base(&mut self) -> Option<$T> {
match self.inner.next()? {
'b' => self.digits::<2>(None),
'd' => self.digits::<10>(None),
'o' => self.digits::<8>(None),
'x' => self.digits::<16>(None),
c => self.digits::<10>(Some(super::base::<10>(c)?)),
}
}
}
impl<I: Iterator<Item = char>> Iterator for ParseInt<I, $T> {
type Item = $T;
fn next(&mut self) -> Option<Self::Item> {
match self.digit::<10>()? {
0 => self.base(),
c if (0..=9).contains(&c) => self.digits::<10>(Some(c)),
_ => None,
}
}
}
)*}
}
/// Converts a single char [0-9A-Za-z] to their [base B](base::<B>) equivalent.
///
/// # May Panic
/// Panics in debug mode when B > 36
pub const fn base<const B: u8>(c: char) -> Option<u8> {
// TODO: Wait for a way to limit const generics at compile time
debug_assert!(B <= 36);
// Can't use Ord::min in const context yet :(
// This function also relies on wrapping arithmetic
macro wrap ($c:ident - $b:literal $(+ $ten:literal)? $(< $B:ident.min($min:literal))?) {
$c.wrapping_sub($b)$(.wrapping_add($ten))? $(< if $B < $min {$B} else {$min})?
}
let c = c as u8;
match c {
c if wrap!(c - b'0' < B.min(10)) => Some(wrap!(c - b'0')),
_ if B <= 10 => None, // cuts base<1..=10> to 4 instructions on x86 :^)
c if wrap!(c - b'A' + 10 < B.min(36)) => Some(wrap!(c - b'A' + 10)),
c if wrap!(c - b'a' + 10 < B.min(36)) => Some(wrap!(c - b'a' + 10)),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
mod unescape_string {
use super::*;
test_unescape! {
empty = ["" => ""];
n_newline = ["\\n" => "\n", "This is a\\ntest" => "This is a\ntest"];
a_bell = ["\\a" => "\x07", "Ring the \\abell" => "Ring the \x07bell"];
b_backspace = ["\\b" => "\x08"];
f_feed = ["\\f" => "\x0c"];
r_return = ["\\r" => "\r"];
t_tab = ["\\t" => "\t"];
_0_nul = ["\\0" => "\0"];
x_hex = [
"\\x41\\x41\\x41\\x41" => "AAAA",
"\x00" => "\0",
"\\x7f" => "\x7f",
"\\x80" => "\u{80}",
"\\xD0" => "\u{D0}",
];
u_unicode = [
"\\u{41}" => "A",
"\\u{1f988}" => "🦈",
];
}
macro test_unescape ($($f:ident = [$($test:expr => $expect:expr),*$(,)?];)*) {$(
#[test] fn $f () {
$(assert_eq!($test.chars().unescape().collect::<String>(), dbg!($expect));)*
}
)*}
}
mod parse_int {
use super::*;
#[test]
#[should_panic]
fn base_37_panics() {
base::<37>('a');
}
test_parse! {
parse_u8: u8 = [
"0xc5" => Some(0xc5),
"0xc_____________________5" => Some(0xc5),
"0x7d" => Some(0x7d),
"0b10" => Some(0b10),
"0o10" => Some(0o10),
"0x10" => Some(0x10),
"0d10" => Some(10),
"10" => Some(10),
];
parse_u16: u16 = [
"0xc5c5" => Some(0xc5c5),
"0x1234" => Some(0x1234),
"0x5678" => Some(0x5678),
"0x9abc" => Some(0x9abc),
"0xdef0" => Some(0xdef0),
"0xg" => None,
"0b10" => Some(0b10),
"0o10" => Some(0o10),
"0x10" => Some(0x10),
"0d10" => Some(10),
"10" => Some(10),
];
parse_u32: u32 = [
"0xc5c5c5c5" => Some(0xc5c5c5c5),
"0xc5_c5_c5_c5" => Some(0xc5c5c5c5),
"1_234_567____" => Some(1234567),
"4294967295" => Some(4294967295),
"4294967296" => None,
"🦈" => None,
];
parse_u64: u64 = [
"0xffffffffffffffff" => Some(0xffffffffffffffff),
"0x10000000000000000" => None,
"0xc5c5c5c5c5c5c5c5" => Some(0xc5c5c5c5c5c5c5c5),
"0x123456789abcdef0" => Some(1311768467463790320),
"0x123456789abcdefg" => Some(81985529216486895),
"0d1234567890" => Some(1234567890),
"0o12345670" => Some(2739128),
"0b10" => Some(2),
];
parse_u128: u128 = [
"0x10000000000000000" => Some(0x10000000000000000),
"0xc5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5" => Some(0xc5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5),
"0o77777777777777777777777777777777" => Some(0o77777777777777777777777777777777),
];
}
macro test_parse ($($f:ident : $T:ty = [$($test:expr => $expect:expr),*$(,)?];)*) {$(
#[test] fn $f () {
type Test = $T;
$(assert_eq!(($test.chars().parse_int() as ParseInt<_, Test>).next(), dbg!($expect));)*
}
)*}
}
}