1use super::error::ParseError;
44
45pub struct Cursor<'a> {
50 input: &'a str,
51 rest: &'a str,
52}
53
54pub fn parse_with<T>(
57 input: &str,
58 grammar: impl FnOnce(&mut Cursor<'_>) -> Result<T, ParseError>,
59) -> Result<T, ParseError> {
60 let mut cursor = Cursor { input, rest: input };
61 let parsed = grammar(&mut cursor)?;
62
63 match cursor.at_end() {
64 true => Ok(parsed),
65 false => Err(cursor.fail("trailing input")),
66 }
67}
68
69impl<'a> Cursor<'a> {
70 pub fn at(&self) -> usize {
72 self.input.len() - self.rest.len()
73 }
74
75 pub fn rest(&self) -> &'a str {
77 self.rest
78 }
79
80 pub fn at_end(&self) -> bool {
81 self.rest.is_empty()
82 }
83
84 pub fn peek(&self) -> Option<char> {
85 self.rest.chars().next()
86 }
87
88 pub fn starts_with(&self, text: &str) -> bool {
89 self.rest.starts_with(text)
90 }
91
92 pub fn fail(&self, message: impl Into<String>) -> ParseError {
94 ParseError::new(self.input, self.at(), message)
95 }
96
97 pub fn eat(&mut self, text: &str) -> bool {
99 match self.rest.strip_prefix(text) {
100 Some(rest) => {
101 self.rest = rest;
102 true
103 }
104 None => false,
105 }
106 }
107
108 pub fn lit(&mut self, text: &str) -> Result<(), ParseError> {
110 match self.eat(text) {
111 true => Ok(()),
112 false => Err(self.fail(format!("expected {text:?}"))),
113 }
114 }
115
116 pub fn take_while(&mut self, accept: impl Fn(char) -> bool) -> &'a str {
118 let end = self
119 .rest
120 .find(|c: char| !accept(c))
121 .unwrap_or(self.rest.len());
122 let (taken, rest) = self.rest.split_at(end);
123
124 self.rest = rest;
125 taken
126 }
127
128 pub fn ws0(&mut self) {
130 self.take_while(|c| c == ' ' || c == '\t' || c == '\n');
131 }
132
133 pub fn word(&mut self, stops: &[char]) -> Result<String, ParseError> {
137 let mut out = match self.quoted()? {
138 Some(text) => text,
139 None => self.bare(stops)?,
140 };
141
142 loop {
143 let snapshot = self.rest;
144 match self.segment(stops) {
145 Ok(Some(text)) => out.push_str(&text),
146 Ok(None) | Err(_) => {
147 self.rest = snapshot;
148 break;
149 }
150 }
151 }
152 Ok(out)
153 }
154
155 fn advance(&mut self, c: char) {
156 self.rest = &self.rest[c.len_utf8()..];
157 }
158
159 fn segment(&mut self, stops: &[char]) -> Result<Option<String>, ParseError> {
162 if let Some(text) = self.quoted()? {
163 return Ok(Some(text));
164 }
165
166 match self.peek() {
167 Some('\\') => self.bare(stops).map(Some),
168 Some(c) if !stops.contains(&c) => self.bare(stops).map(Some),
169 _ => Ok(None),
170 }
171 }
172
173 fn quoted(&mut self) -> Result<Option<String>, ParseError> {
175 if self.eat("$'") {
176 return self.ansi_c().map(Some);
177 }
178 if self.starts_with("'") {
179 return self.single().map(Some);
180 }
181 if self.starts_with("\"") {
182 return self.double().map(Some);
183 }
184 if self.starts_with("$\"") {
185 self.advance('$');
186 return self.double().map(Some);
187 }
188 Ok(None)
189 }
190
191 fn single(&mut self) -> Result<String, ParseError> {
192 self.lit("'")?;
193
194 let body = self.take_while(|c| c != '\'').to_string();
195 if !self.eat("'") {
196 return Err(self.fail("unterminated ' quote"));
197 }
198 Ok(body)
199 }
200
201 fn double(&mut self) -> Result<String, ParseError> {
202 self.lit("\"")?;
203
204 let mut out = String::new();
205 loop {
206 let Some(c) = self.peek() else {
209 return Err(self.fail("unterminated \" quote"));
210 };
211
212 if c == '"' {
213 self.advance(c);
214 return Ok(out);
215 }
216 if c == '\\' {
217 self.advance(c);
218 match self.peek() {
219 Some(escaped @ ('$' | '"' | '\\' | '`')) => {
220 out.push(escaped);
221 self.advance(escaped);
222 }
223 Some('\n') => self.advance('\n'),
224 Some(escaped) => {
225 out.push('\\');
226 out.push(escaped);
227 self.advance(escaped);
228 }
229 None => return Err(self.fail("a backslash at the end of the input")),
230 }
231 continue;
232 }
233 out.push(c);
234 self.advance(c);
235 }
236 }
237
238 fn ansi_c(&mut self) -> Result<String, ParseError> {
239 let mut out = String::new();
240 loop {
241 let Some(c) = self.peek() else {
242 return Err(self.fail("unterminated $' quote"));
243 };
244
245 if c == '\'' {
246 self.advance(c);
247 return Ok(out);
248 }
249 if c == '\\' {
250 self.advance(c);
251 let Some(escaped) = self.peek() else {
252 return Err(self.fail("a backslash at the end of the input"));
253 };
254
255 self.advance(escaped);
256 self.escape(escaped, &mut out)?;
257 continue;
258 }
259 out.push(c);
260 self.advance(c);
261 }
262 }
263
264 fn escape(&mut self, c: char, out: &mut String) -> Result<(), ParseError> {
265 match c {
266 'a' => out.push('\x07'),
267 'b' => out.push('\x08'),
268 'e' | 'E' => out.push('\x1B'),
269 'f' => out.push('\x0C'),
270 'n' => out.push('\n'),
271 'r' => out.push('\r'),
272 't' => out.push('\t'),
273 'v' => out.push('\x0B'),
274 '\\' => out.push('\\'),
275 '\'' => out.push('\''),
276 '"' => out.push('"'),
277 '?' => out.push('?'),
278 'c' => {
279 let control = self
280 .peek()
281 .ok_or_else(|| self.fail(r"\c with nothing after it"))?;
282
283 self.advance(control);
284 out.push(((control as u32) & 0x1F) as u8 as char);
285 }
286 'x' => {
287 let digits = self.hex(2);
288 let byte = u8::from_str_radix(&digits, 16).map_err(|_| self.fail(r"\x wants one or two hex digits"))?;
289
290 out.push(byte as char);
291 }
292 'u' => self.unicode(4, out)?,
293 'U' => self.unicode(8, out)?,
294 first if is_octal(first) => {
295 let mut octal = String::from(first);
296 for _ in 0..2 {
297 match self.peek() {
298 Some(digit) if is_octal(digit) => {
299 octal.push(digit);
300 self.advance(digit);
301 }
302 _ => break,
303 }
304 }
305
306 let byte = u8::from_str_radix(&octal, 8).map_err(|_| {
309 self.fail(format!(
310 r"octal escape \{octal} is above \377"
311 ))
312 })?;
313
314 out.push(byte as char);
315 }
316 other => {
317 out.push('\\');
318 out.push(other);
319 }
320 }
321 Ok(())
322 }
323
324 fn unicode(&mut self, width: usize, out: &mut String) -> Result<(), ParseError> {
325 let digits = self.hex(width);
326 let point = u32::from_str_radix(&digits, 16).map_err(|_| self.fail("a unicode escape wants hex digits"))?;
327
328 out.push(char::from_u32(point).ok_or_else(|| self.fail("not a Unicode scalar value"))?);
329 Ok(())
330 }
331
332 fn hex(&mut self, width: usize) -> String {
333 let mut out = String::new();
334 for _ in 0..width {
335 match self.peek() {
336 Some(c) if c.is_ascii_hexdigit() => {
337 out.push(c);
338 self.advance(c);
339 }
340 _ => break,
341 }
342 }
343 out
344 }
345
346 fn bare(&mut self, stops: &[char]) -> Result<String, ParseError> {
351 let mut out = String::new();
352
353 while let Some(c) = self.peek() {
354 if c == '\'' || c == '"' || c == '$' {
355 break;
356 }
357 if c == '\\' {
358 let after = &self.rest[1..];
359 match after.chars().next() {
360 Some('\n') => self.rest = &after[1..],
361 Some(escaped) => {
362 out.push(escaped);
363 self.rest = &after[escaped.len_utf8()..];
364 }
365 None => break,
367 }
368 continue;
369 }
370 if stops.contains(&c) {
371 break;
372 }
373 out.push(c);
374 self.advance(c);
375 }
376
377 match out.is_empty() {
378 true => Err(self.fail("expected a word")),
379 false => Ok(out),
380 }
381 }
382}
383
384fn is_octal(c: char) -> bool {
385 c.is_ascii_digit() && c != '8' && c != '9'
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[test]
395 fn a_grammar_over_other_syntax_builds_on_the_word_rules() {
396 const STOPS: &[char] = &['=', ';', ' '];
397
398 let settings = parse_with(
399 r#"a='one two';b=$'x\ty';c=bare\ word"#,
400 |c| {
401 let mut out = Vec::new();
402 loop {
403 c.ws0();
404 let key = c.word(STOPS)?;
405 c.lit("=")?;
406 out.push((key, c.word(STOPS)?));
407 if !c.eat(";") {
408 return Ok(out);
409 }
410 }
411 },
412 )
413 .unwrap();
414
415 assert_eq!(
416 settings,
417 [
418 ("a".to_string(), "one two".to_string()),
419 ("b".to_string(), "x\ty".to_string()),
420 ("c".to_string(), "bare word".to_string()),
421 ]
422 );
423 }
424
425 #[test]
426 fn a_grammar_that_stops_early_is_an_error() {
427 let failed = parse_with("abc def", |c| c.word(&[' '])).expect_err("trailing input");
428
429 assert_eq!(failed.at, 3);
430 assert!(
431 failed.message.contains("trailing"),
432 "{failed}"
433 );
434 }
435
436 #[test]
438 fn a_refusal_names_the_position_it_reached() {
439 let failed = parse_with("'a' 'b", |c| {
440 c.word(&[' '])?;
441 c.lit(" ")?;
442 c.word(&[' '])
443 })
444 .expect_err("unterminated");
445
446 assert_eq!(
447 failed.at, 6,
448 "the end of the input, inside the open quote"
449 );
450 assert!(
451 failed.message.contains("unterminated"),
452 "{failed}"
453 );
454 }
455
456 #[test]
458 fn an_octal_escape_leaves_what_it_does_not_take() {
459 assert_eq!(
460 parse_with(r"$'\1234'", |c| c.word(&[])).unwrap(),
461 "\u{53}4"
462 );
463 }
464}