use std::borrow::Cow;
use crate::{CharClass, Inpt, InptError, InptStep, RecursionGuard};
#[derive(Inpt, Debug, PartialEq, Eq, Clone, Copy)]
#[inpt(regex = r"([\S]+)", trim = r"\s")]
pub struct Spaced<T> {
pub inner: T,
}
#[derive(Inpt, Debug, PartialEq, Eq, Clone, Copy)]
#[inpt(regex = r"([^\s\p{Punctuation}]+)", trim = r"\s\p{Punctuation}")]
pub struct Word<T> {
pub inner: T,
}
#[derive(Inpt, Debug, PartialEq, Eq, Clone, Copy)]
#[inpt(regex = r"([^\n]+)")]
pub struct Line<T> {
pub inner: T,
}
#[derive(Inpt, Debug, PartialEq, Eq, Clone, Copy)]
#[inpt(regex = r"((?s).*?)(?:\n\s*\n|$)")]
pub struct Group<T> {
pub inner: T,
}
pub fn unescape(s: &str) -> Cow<str> {
if !s.contains('\\') {
return Cow::Borrowed(s);
}
let mut buf = String::with_capacity(s.len());
let mut chars = s.chars();
while let Some(c) = chars.next() {
if c == '\\' {
let Some(c) = chars.next() else {
unreachable!()
};
buf.push(match c {
'0' => '\u{0}',
'a' => '\u{07}',
'b' => '\u{08}',
'v' => '\u{0B}',
'f' => '\u{0C}',
'n' => '\n',
'r' => '\r',
't' => '\t',
'e' | 'E' => '\u{1B}',
_ => c,
});
} else {
buf.push(c);
}
}
Cow::Owned(buf)
}
#[derive(Inpt, Debug, PartialEq, Eq, Clone, Copy)]
#[inpt(regex = r#""((?s:[^\\]|\\.)*?)""#)]
pub struct Quoted<T> {
pub inner: T,
}
impl<S: AsRef<str>> Quoted<S> {
pub fn unescape(&self) -> Cow<str> {
unescape(self.inner.as_ref())
}
}
#[derive(Inpt, Debug, PartialEq, Eq, Clone, Copy)]
#[inpt(regex = r"'((?s:[^\\]|\\.)*?)'")]
pub struct SingleQuoted<T> {
pub inner: T,
}
impl<S: AsRef<str>> SingleQuoted<S> {
pub fn unescape(&self) -> Cow<str> {
unescape(self.inner.as_ref())
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct AnyBracketed<const OPEN: char, const CLOSE: char, T> {
pub inner: T,
}
impl<'s, const OPEN: char, const CLOSE: char, T> Inpt<'s> for AnyBracketed<OPEN, CLOSE, T>
where
T: Inpt<'s>,
{
fn step(
text: &'s str,
end: bool,
trimmed: CharClass,
guard: &mut RecursionGuard,
) -> crate::InptStep<'s, Self> {
guard.check(text, |guard| {
if text.starts_with(OPEN) {
let mut depth = 0;
let mut chars = text.char_indices();
let closed = 'matched: loop {
let (pos, c) = match chars.next() {
Some(c) => c,
None => break 'matched Err(InptError::expected_lit_at_end(&CLOSE)),
};
if c == OPEN {
depth += 1;
}
if c == CLOSE {
depth -= 1;
}
if depth == 0 && (!end || pos + CLOSE.len_utf8() == text.len()) {
break Ok(pos);
}
if let Some(q) = ['"', '\''].iter().find(|q| c == **q) {
'quoted: loop {
match chars.next() {
None => break 'matched Err(InptError::expected_lit_at_end(q)),
Some((_, '\\')) => {
let _ = chars.next();
}
Some((_, c)) if c == *q => break 'quoted,
_ => (),
}
}
}
};
let step = match closed {
Ok(closed) => crate::InptStep {
data: T::step(&text[OPEN.len_utf8()..closed], true, trimmed, guard).data,
rest: &text[closed + CLOSE.len_utf8()..],
},
Err(e) => crate::InptStep {
data: Err(e),
rest: match text.rfind(CLOSE) {
Some(pos) => &text[pos..],
None => &text[text.len()..],
},
},
};
step.map(|inner| AnyBracketed { inner })
} else {
InptStep {
data: Err(InptError::expected_lit_at_start(&OPEN)),
rest: text,
}
}
})
}
}
pub type Parenthetical<T> = AnyBracketed<'(', ')', T>;
pub type Bracketed<T> = AnyBracketed<'[', ']', T>;
pub type Braced<T> = AnyBracketed<'{', '}', T>;
pub type AngleBraced<T> = AnyBracketed<'<', '>', T>;