1use std::borrow::Cow;
4
5use crate::{CharClass, Inpt, InptError, InptStep, RecursionGuard};
6
7#[derive(Inpt, Debug, PartialEq, Eq, Clone, Copy)]
9#[inpt(regex = r"([\S]+)", trim = r"\s")]
10pub struct Spaced<T> {
11 pub inner: T,
12}
13
14#[derive(Inpt, Debug, PartialEq, Eq, Clone, Copy)]
16#[inpt(regex = r"([^\s\p{Punctuation}]+)", trim = r"\s\p{Punctuation}")]
17pub struct Word<T> {
18 pub inner: T,
19}
20
21#[derive(Inpt, Debug, PartialEq, Eq, Clone, Copy)]
23#[inpt(regex = r"([^\n]+)")]
24pub struct Line<T> {
25 pub inner: T,
26}
27
28#[derive(Inpt, Debug, PartialEq, Eq, Clone, Copy)]
30#[inpt(regex = r"((?s).*?)(?:\n\s*\n|$)")]
31pub struct Group<T> {
32 pub inner: T,
33}
34
35pub fn unescape(s: &str) -> Cow<str> {
37 if !s.contains('\\') {
38 return Cow::Borrowed(s);
39 }
40
41 let mut buf = String::with_capacity(s.len());
42 let mut chars = s.chars();
43 while let Some(c) = chars.next() {
44 if c == '\\' {
45 let Some(c) = chars.next() else {
46 unreachable!()
47 };
48 buf.push(match c {
49 '0' => '\u{0}',
50 'a' => '\u{07}',
51 'b' => '\u{08}',
52 'v' => '\u{0B}',
53 'f' => '\u{0C}',
54 'n' => '\n',
55 'r' => '\r',
56 't' => '\t',
57 'e' | 'E' => '\u{1B}',
58 _ => c,
59 });
60 } else {
61 buf.push(c);
62 }
63 }
64 Cow::Owned(buf)
65}
66
67#[derive(Inpt, Debug, PartialEq, Eq, Clone, Copy)]
71#[inpt(regex = r#""((?s:[^\\]|\\.)*?)""#)]
72pub struct Quoted<T> {
73 pub inner: T,
74}
75
76impl<S: AsRef<str>> Quoted<S> {
77 pub fn unescape(&self) -> Cow<str> {
81 unescape(self.inner.as_ref())
82 }
83}
84
85#[derive(Inpt, Debug, PartialEq, Eq, Clone, Copy)]
89#[inpt(regex = r"'((?s:[^\\]|\\.)*?)'")]
90pub struct SingleQuoted<T> {
91 pub inner: T,
92}
93
94impl<S: AsRef<str>> SingleQuoted<S> {
95 pub fn unescape(&self) -> Cow<str> {
99 unescape(self.inner.as_ref())
100 }
101}
102
103#[derive(Debug, PartialEq, Eq, Clone, Copy)]
107pub struct AnyBracketed<const OPEN: char, const CLOSE: char, T> {
108 pub inner: T,
109}
110
111impl<'s, const OPEN: char, const CLOSE: char, T> Inpt<'s> for AnyBracketed<OPEN, CLOSE, T>
112where
113 T: Inpt<'s>,
114{
115 fn step(
116 text: &'s str,
117 end: bool,
118 trimmed: CharClass,
119 guard: &mut RecursionGuard,
120 ) -> crate::InptStep<'s, Self> {
121 guard.check(text, |guard| {
122 if text.starts_with(OPEN) {
123 let mut depth = 0;
124 let mut chars = text.char_indices();
125 let closed = 'matched: loop {
126 let (pos, c) = match chars.next() {
127 Some(c) => c,
128 None => break 'matched Err(InptError::expected_lit_at_end(&CLOSE)),
129 };
130 if c == OPEN {
132 depth += 1;
133 }
134 if c == CLOSE {
135 depth -= 1;
136 }
137 if depth == 0 && (!end || pos + CLOSE.len_utf8() == text.len()) {
138 break Ok(pos);
139 }
140 if let Some(q) = ['"', '\''].iter().find(|q| c == **q) {
142 'quoted: loop {
143 match chars.next() {
144 None => break 'matched Err(InptError::expected_lit_at_end(q)),
146 Some((_, '\\')) => {
148 let _ = chars.next();
149 }
150 Some((_, c)) if c == *q => break 'quoted,
152 _ => (),
154 }
155 }
156 }
157 };
158 let step = match closed {
159 Ok(closed) => crate::InptStep {
160 data: T::step(&text[OPEN.len_utf8()..closed], true, trimmed, guard).data,
161 rest: &text[closed + CLOSE.len_utf8()..],
162 },
163 Err(e) => crate::InptStep {
164 data: Err(e),
165 rest: match text.rfind(CLOSE) {
166 Some(pos) => &text[pos..],
167 None => &text[text.len()..],
168 },
169 },
170 };
171 step.map(|inner| AnyBracketed { inner })
172 } else {
173 InptStep {
174 data: Err(InptError::expected_lit_at_start(&OPEN)),
175 rest: text,
176 }
177 }
178 })
179 }
180}
181
182pub type Parenthetical<T> = AnyBracketed<'(', ')', T>;
184pub type Bracketed<T> = AnyBracketed<'[', ']', T>;
186pub type Braced<T> = AnyBracketed<'{', '}', T>;
188pub type AngleBraced<T> = AnyBracketed<'<', '>', T>;