constr/lerox: remove
This commit is contained in:
parent
5c4c8bcb80
commit
d1b5c48aac
@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
members = ["libconlang", "lerox", "constr"]
|
||||
members = ["libconlang"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
|
@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "constr"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
@ -1,255 +0,0 @@
|
||||
//! [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));)*
|
||||
}
|
||||
)*}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "lerox"
|
||||
authors.workspace = true
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
@ -1,73 +0,0 @@
|
||||
//! Lerox is a stupid, simple combinator library, part of the Conlang project
|
||||
|
||||
/// The core trait of Lerox, [Combinator], generically implements a handful of simple
|
||||
/// combinators for your type based on two required functions:
|
||||
/// - [`is_alright(&self) -> bool`](Combinator::is_alright) performs some unspecified *evaluation*
|
||||
/// on `self`, and returns true if `self` is valid for your problem domain.
|
||||
/// - [`into_alright(self) -> Self`](Combinator::into_alright) performs some unspecified
|
||||
/// *transformation* of `self` into an alright version of `self` (that is, one which satisfies
|
||||
/// [`self.is_alright()`](Combinator::is_alright) == `true`)
|
||||
pub trait Combinator: Sized {
|
||||
/// Evaluates the [Combinator], and returns whether it is alright.
|
||||
///
|
||||
/// This is the basis for [Combinator]'s [and*](Combinator::and())/[or*](Combinator::or())
|
||||
/// behaviors, and is like a generic form of [Option::is_some()] or [Result::is_ok()]
|
||||
fn is_alright(&self) -> bool;
|
||||
|
||||
/// Does something to make this [Combinator] [alright](Combinator::is_alright())
|
||||
fn into_alright(self) -> Self;
|
||||
|
||||
/// If self is alright, runs f(self)
|
||||
fn and(self, f: impl Fn(Self) -> Self) -> Self {
|
||||
match self.is_alright() {
|
||||
true => f(self),
|
||||
false => self,
|
||||
}
|
||||
}
|
||||
|
||||
/// If self is not alright, runs f(self) as if it were alright
|
||||
fn or(self, f: impl FnOnce(Self) -> Self) -> Self {
|
||||
match self.is_alright() {
|
||||
true => self,
|
||||
false => f(self.into_alright()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Repeats the function f on self until it would fail
|
||||
fn and_any(self, f: impl Fn(Self) -> Self) -> Self {
|
||||
self.and(|mut this| {
|
||||
while this.is_alright() {
|
||||
this = this.and(&f)
|
||||
}
|
||||
this.into_alright()
|
||||
})
|
||||
}
|
||||
|
||||
/// Repeats the function f on self at least once, then until it fails.
|
||||
fn and_many(self, f: impl Fn(Self) -> Self) -> Self {
|
||||
self.and(&f).and_any(f)
|
||||
}
|
||||
|
||||
/// Returns the result of running f on self, or if it fails, the original self
|
||||
fn and_maybe(self, f: impl Fn(Self) -> Self) -> Self
|
||||
where Self: Clone {
|
||||
self.and_either(f, |g| g)
|
||||
}
|
||||
|
||||
/// Returns the result of running f on self, or if it fails, runs g on self
|
||||
fn and_either(self, f: impl Fn(Self) -> Self, g: impl Fn(Self) -> Self) -> Self
|
||||
where Self: Clone {
|
||||
self.clone().and(f).or(|_| g(self))
|
||||
}
|
||||
|
||||
/// Returns the result of the first f that succeeds, or self
|
||||
fn and_one_of(mut self, f: &[&dyn Fn(Self) -> Self]) -> Self {
|
||||
for &f in f {
|
||||
self = self.into_alright().and(f);
|
||||
if self.is_alright() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user