Skip to main content

one_brackets/
parser.rs

1//! Streaming reader of the bracket format from any [`Read`].
2
3use std::fs::File;
4use std::io::{self, BufRead, BufReader, Read};
5use std::path::Path;
6use std::str::FromStr;
7
8use crate::{Node, ParseError, Position, Value};
9
10type Result<T> = std::result::Result<T, ParseError>;
11
12const BOM: [u8; 3] = [0xEF, 0xBB, 0xBF];
13const BASE64: &[u8] = b"#base64:";
14
15impl Node {
16    /// Parses a single top-level value from any source.
17    ///
18    /// A leading BOM and surrounding whitespace are allowed. Reads byte by byte through a buffer
19    /// without loading the whole input into memory.
20    pub fn from_reader<R: Read>(reader: R) -> Result<Node> {
21        parse(BufReader::new(reader))
22    }
23}
24
25impl TryFrom<&str> for Node {
26    type Error = ParseError;
27
28    fn try_from(text: &str) -> Result<Node> {
29        parse(text.as_bytes())
30    }
31}
32
33impl TryFrom<String> for Node {
34    type Error = ParseError;
35
36    fn try_from(text: String) -> Result<Node> {
37        parse(text.as_bytes())
38    }
39}
40
41impl TryFrom<&[u8]> for Node {
42    type Error = ParseError;
43
44    fn try_from(bytes: &[u8]) -> Result<Node> {
45        parse(bytes)
46    }
47}
48
49impl TryFrom<Vec<u8>> for Node {
50    type Error = ParseError;
51
52    fn try_from(bytes: Vec<u8>) -> Result<Node> {
53        parse(bytes.as_slice())
54    }
55}
56
57impl TryFrom<File> for Node {
58    type Error = ParseError;
59
60    fn try_from(file: File) -> Result<Node> {
61        Node::from_reader(file)
62    }
63}
64
65impl TryFrom<&Path> for Node {
66    type Error = ParseError;
67
68    fn try_from(path: &Path) -> Result<Node> {
69        let file = File::open(path).map_err(|source| ParseError::Open {
70            path: path.to_owned(),
71            source,
72        })?;
73        Node::from_reader(file)
74    }
75}
76
77impl FromStr for Node {
78    type Err = ParseError;
79
80    fn from_str(text: &str) -> Result<Node> {
81        parse(text.as_bytes())
82    }
83}
84
85fn parse<R: BufRead>(reader: R) -> Result<Node> {
86    let mut parser = Parser {
87        input: Input::new(reader),
88    };
89    parser.input.skip_bom()?;
90    parser.input.skip_ws()?;
91    if parser.input.peek()?.is_none() {
92        return Err(ParseError::EmptyInput);
93    }
94    let node = parser.value()?;
95    parser.input.skip_ws()?;
96    let at = parser.input.pos;
97    match parser.input.peek()? {
98        None => Ok(node),
99        Some(b'}') => Err(ParseError::UnmatchedClose { at }),
100        Some(_) => Err(ParseError::TrailingData { at }),
101    }
102}
103
104/// Comma-separated top-level values read one at a time, e.g. records of an event log `.lgp`.
105///
106/// The input is streamed: only the current value is kept in memory. Text lines before
107/// the values (a file header) can be read with [`read_line`](Values::read_line);
108/// positions in errors and nodes still count from the start of the input.
109/// A trailing comma is allowed: a log being written ends with one.
110/// The iterator stops after the first error.
111///
112/// ```
113/// use one_brackets::Values;
114///
115/// # fn main() -> Result<(), one_brackets::ParseError> {
116/// let mut values = Values::new("HEADER\r\n{1,a},\r\n{2,b},".as_bytes());
117/// assert_eq!(values.read_line()?.as_deref(), Some("HEADER"));
118/// let records = values.collect::<Result<Vec<_>, _>>()?;
119/// assert_eq!(records.len(), 2);
120/// assert_eq!(records[1].position().unwrap().line, 3);
121/// # Ok(())
122/// # }
123/// ```
124pub struct Values<R> {
125    parser: Parser<BufReader<R>>,
126    state: State,
127}
128
129#[derive(Clone, Copy, PartialEq, Eq)]
130enum State {
131    /// The BOM has not been checked yet.
132    Fresh,
133    BeforeValue,
134    AfterValue,
135    Done,
136}
137
138impl<R: Read> Values<R> {
139    pub fn new(reader: R) -> Self {
140        Values {
141            parser: Parser {
142                input: Input::new(BufReader::new(reader)),
143            },
144            state: State::Fresh,
145        }
146    }
147
148    /// Reads one text line before the values; `None` at the end of input.
149    ///
150    /// The line ending (`\n` or `\r\n`) is not included.
151    pub fn read_line(&mut self) -> Result<Option<String>> {
152        self.start()?;
153        let input = &mut self.parser.input;
154        let at = input.pos;
155        if input.peek()?.is_none() {
156            return Ok(None);
157        }
158        let mut bytes = Vec::new();
159        if input.scan(|b| b == b'\n', Some(&mut bytes))?.is_some() {
160            input.bump()?;
161        }
162        if bytes.last() == Some(&b'\r') {
163            bytes.pop();
164        }
165        String::from_utf8(bytes)
166            .map(Some)
167            .map_err(|_| ParseError::InvalidUtf8 {
168                context: "line",
169                at,
170            })
171    }
172
173    /// Position of the next unread byte.
174    pub fn position(&self) -> Position {
175        self.parser.input.pos
176    }
177
178    fn start(&mut self) -> Result<()> {
179        if self.state == State::Fresh {
180            self.parser.input.skip_bom()?;
181            self.state = State::BeforeValue;
182        }
183        Ok(())
184    }
185
186    fn read_value(&mut self) -> Result<Option<Node>> {
187        self.start()?;
188        let input = &mut self.parser.input;
189        input.skip_ws()?;
190        if self.state == State::AfterValue {
191            let at = input.pos;
192            match input.peek()? {
193                None => return Ok(None),
194                Some(b',') => {
195                    input.bump()?;
196                    input.skip_ws()?;
197                }
198                Some(b'}') => return Err(ParseError::UnmatchedClose { at }),
199                Some(_) => {
200                    return Err(ParseError::UnexpectedChar {
201                        found: self.parser.char_at_cursor()?,
202                        at,
203                    });
204                }
205            }
206        }
207        if self.parser.input.peek()?.is_none() {
208            return Ok(None);
209        }
210        let node = self.parser.value()?;
211        self.state = State::AfterValue;
212        Ok(Some(node))
213    }
214}
215
216impl<R: Read> Iterator for Values<R> {
217    type Item = Result<Node>;
218
219    fn next(&mut self) -> Option<Result<Node>> {
220        if self.state == State::Done {
221            return None;
222        }
223        let result = self.read_value();
224        if !matches!(result, Ok(Some(_))) {
225            self.state = State::Done;
226        }
227        result.transpose()
228    }
229}
230
231impl<R: Read> std::iter::FusedIterator for Values<R> {}
232
233/// Byte cursor over [`BufRead`] that tracks the position.
234struct Input<R> {
235    inner: R,
236    /// Bytes read while checking for a BOM and pushed back.
237    pending: Vec<u8>,
238    pos: Position,
239}
240
241impl<R: BufRead> Input<R> {
242    fn new(inner: R) -> Self {
243        Self {
244            inner,
245            pending: Vec::new(),
246            pos: Position::START,
247        }
248    }
249
250    fn peek_inner(&mut self) -> Result<Option<u8>> {
251        loop {
252            match self.inner.fill_buf() {
253                Ok(buf) => return Ok(buf.first().copied()),
254                Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
255                Err(e) => return Err(e.into()),
256            }
257        }
258    }
259
260    fn peek(&mut self) -> Result<Option<u8>> {
261        match self.pending.first() {
262            Some(&b) => Ok(Some(b)),
263            None => self.peek_inner(),
264        }
265    }
266
267    fn bump(&mut self) -> Result<Option<u8>> {
268        let byte = if self.pending.is_empty() {
269            let byte = self.peek_inner()?;
270            if byte.is_some() {
271                self.inner.consume(1);
272            }
273            byte
274        } else {
275            Some(self.pending.remove(0))
276        };
277        if let Some(b) = byte {
278            advance(&mut self.pos, &[b]);
279        }
280        Ok(byte)
281    }
282
283    fn skip_bom(&mut self) -> Result<()> {
284        let mut seen = Vec::new();
285        for expected in BOM {
286            match self.peek_inner()? {
287                Some(b) if b == expected => {
288                    self.inner.consume(1);
289                    seen.push(b);
290                }
291                _ => break,
292            }
293        }
294        if seen.len() == BOM.len() {
295            self.pos.offset += BOM.len() as u64;
296        } else {
297            self.pending = seen;
298        }
299        Ok(())
300    }
301
302    fn skip_ws(&mut self) -> Result<()> {
303        self.scan(|b| !b.is_ascii_whitespace(), None)?;
304        Ok(())
305    }
306
307    /// Moves over bytes until `stop` matches, a whole buffer at a time, copying them to `out`.
308    ///
309    /// Returns the stop byte, which stays unread, or `None` at the end of input.
310    fn scan(
311        &mut self,
312        stop: impl Fn(u8) -> bool,
313        mut out: Option<&mut Vec<u8>>,
314    ) -> Result<Option<u8>> {
315        // Pushed-back bytes of a partial BOM: at most two, one by one.
316        while let Some(&b) = self.pending.first() {
317            if stop(b) {
318                return Ok(Some(b));
319            }
320            self.bump()?;
321            if let Some(out) = out.as_deref_mut() {
322                out.push(b);
323            }
324        }
325        loop {
326            let buf = loop {
327                match self.inner.fill_buf() {
328                    Ok(buf) => break buf,
329                    Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
330                    Err(e) => return Err(e.into()),
331                }
332            };
333            if buf.is_empty() {
334                return Ok(None);
335            }
336            let (len, found) = match buf.iter().position(|&b| stop(b)) {
337                Some(i) => (i, Some(buf[i])),
338                None => (buf.len(), None),
339            };
340            let chunk = &buf[..len];
341            if let Some(out) = out.as_deref_mut() {
342                out.extend_from_slice(chunk);
343            }
344            advance(&mut self.pos, chunk);
345            self.inner.consume(len);
346            if found.is_some() {
347                return Ok(found);
348            }
349        }
350    }
351}
352
353struct Parser<R> {
354    input: Input<R>,
355}
356
357impl<R: BufRead> Parser<R> {
358    fn value(&mut self) -> Result<Node> {
359        match self.input.peek()? {
360            Some(b'{') => self.list(),
361            _ => self.scalar(),
362        }
363    }
364
365    /// Lists are parsed without recursion: forms can be deeply nested.
366    fn list(&mut self) -> Result<Node> {
367        // (position of `{`, collected items)
368        let mut stack: Vec<(Position, Vec<Node>)> = Vec::new();
369        loop {
370            // The cursor is always on `{` here.
371            let opened = self.input.pos;
372            self.input.bump()?;
373            self.input.skip_ws()?;
374            stack.push((opened, Vec::new()));
375            match self.input.peek()? {
376                // Empty list `{}`; the shared closing code follows.
377                Some(b'}') => {}
378                Some(b'{') => continue,
379                _ => {
380                    let item = self.scalar()?;
381                    push(&mut stack, item);
382                }
383            }
384            // Separators and closing brackets until a new nested list starts.
385            loop {
386                self.input.skip_ws()?;
387                let at = self.input.pos;
388                match self.input.peek()? {
389                    Some(b',') => {
390                        self.input.bump()?;
391                        self.input.skip_ws()?;
392                        if self.input.peek()? == Some(b'{') {
393                            break;
394                        }
395                        let item = self.scalar()?;
396                        push(&mut stack, item);
397                    }
398                    Some(b'}') => {
399                        self.input.bump()?;
400                        let (opened, items) = stack.pop().expect("list stack is not empty");
401                        let done = Node::parsed(Value::List(items), opened);
402                        match stack.last_mut() {
403                            Some((_, parent)) => parent.push(done),
404                            None => return Ok(done),
405                        }
406                    }
407                    Some(_) => {
408                        return Err(ParseError::UnexpectedChar {
409                            found: self.char_at_cursor()?,
410                            at,
411                        });
412                    }
413                    None => {
414                        let (opened, _) = stack.last().expect("list stack is not empty");
415                        return Err(ParseError::UnclosedList {
416                            opened: *opened,
417                            at,
418                        });
419                    }
420                }
421            }
422        }
423    }
424
425    fn scalar(&mut self) -> Result<Node> {
426        if self.input.peek()? == Some(b'"') {
427            self.string()
428        } else {
429            self.atom()
430        }
431    }
432
433    fn string(&mut self) -> Result<Node> {
434        let start = self.input.pos;
435        self.input.bump()?;
436        let mut bytes = Vec::new();
437        loop {
438            if self.input.scan(|b| b == b'"', Some(&mut bytes))?.is_none() {
439                return Err(ParseError::UnclosedString { start });
440            }
441            self.input.bump()?;
442            // A doubled quote is an escaped one; a single quote ends the string.
443            if self.input.peek()? != Some(b'"') {
444                break;
445            }
446            self.input.bump()?;
447            bytes.push(b'"');
448        }
449        let value = String::from_utf8(bytes).map_err(|_| ParseError::InvalidUtf8 {
450            context: "quoted string",
451            at: start,
452        })?;
453        Ok(Node::parsed(Value::Str(value), start))
454    }
455
456    fn atom(&mut self) -> Result<Node> {
457        let start = self.input.pos;
458        let mut bytes = Vec::new();
459        let delimiter = |b: u8| matches!(b, b',' | b'{' | b'}');
460        let stop = self.input.scan(
461            |b| delimiter(b) || b.is_ascii_whitespace(),
462            Some(&mut bytes),
463        )?;
464        // Base64 is wrapped with `\r\r\n`: whitespace is part of the value.
465        if stop.is_some_and(|b| b.is_ascii_whitespace()) && bytes.starts_with(BASE64) {
466            self.input.scan(delimiter, Some(&mut bytes))?;
467        }
468        let value = String::from_utf8(bytes).map_err(|_| ParseError::InvalidUtf8 {
469            context: "value",
470            at: start,
471        })?;
472        Ok(Node::parsed(Value::Atom(value), start))
473    }
474
475    /// Character under the cursor (for error messages); may span several bytes.
476    fn char_at_cursor(&mut self) -> Result<char> {
477        let mut bytes = Vec::with_capacity(4);
478        while let Some(b) = self.input.peek()? {
479            if !bytes.is_empty() && b & 0xC0 != 0x80 || bytes.len() == 4 {
480                break;
481            }
482            bytes.push(b);
483            self.input.bump()?;
484        }
485        Ok(std::str::from_utf8(&bytes)
486            .ok()
487            .and_then(|s| s.chars().next())
488            .unwrap_or(char::REPLACEMENT_CHARACTER))
489    }
490}
491
492/// Moves the position over bytes already read.
493fn advance(pos: &mut Position, bytes: &[u8]) {
494    // UTF-8 continuation bytes do not advance the column.
495    let chars = |bytes: &[u8]| bytes.iter().filter(|&&b| b & 0xC0 != 0x80).count() as u64;
496    pos.offset += bytes.len() as u64;
497    match bytes.iter().rposition(|&b| b == b'\n') {
498        Some(last) => {
499            pos.line += bytes.iter().filter(|&&b| b == b'\n').count() as u64;
500            pos.column = 1 + chars(&bytes[last + 1..]);
501        }
502        None => pos.column += chars(bytes),
503    }
504}
505
506fn push(stack: &mut [(Position, Vec<Node>)], item: Node) {
507    stack
508        .last_mut()
509        .expect("list stack is not empty")
510        .1
511        .push(item);
512}
513
514#[cfg(test)]
515mod tests {
516    use super::*;
517
518    fn atom(s: &str) -> Node {
519        Node::atom(s)
520    }
521
522    /// Source that yields one byte per `read` call.
523    struct ByteByByte<'a>(&'a [u8]);
524
525    impl Read for ByteByByte<'_> {
526        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
527            match (self.0.split_first(), buf.first_mut()) {
528                (Some((&b, rest)), Some(slot)) => {
529                    *slot = b;
530                    self.0 = rest;
531                    Ok(1)
532                }
533                _ => Ok(0),
534            }
535        }
536    }
537
538    #[test]
539    fn parses_values() {
540        let node: Node = "\u{feff}{1,{\"a \"\"b\"\"\",x},{},}".parse().unwrap();
541        assert_eq!(
542            node,
543            Node::list([
544                atom("1"),
545                Node::list([Node::string("a \"b\""), atom("x")]),
546                Node::list([]),
547                atom(""),
548            ])
549        );
550    }
551
552    #[test]
553    fn keeps_base64_with_line_breaks() {
554        let node = Node::try_from("{1,\r\n{#base64:QUJD\r\r\nREVG\r\r\n},2}").unwrap();
555        assert_eq!(
556            node.get_path(&[1, 0]).and_then(Node::as_str),
557            Some("#base64:QUJD\r\r\nREVG\r\r\n")
558        );
559        assert_eq!(node[2].as_i64(), Some(2));
560    }
561
562    #[test]
563    fn deep_nesting_without_recursion() {
564        let depth = 5_000;
565        let text = format!("{}{}", "{".repeat(depth), "}".repeat(depth));
566        assert!(Node::try_from(text).is_ok());
567    }
568
569    #[test]
570    fn reads_byte_by_byte() {
571        let text = "\u{feff}{1,\r\n{\"ру\"\"с\",#base64:AB\r\r\nC},Ж,\"\"}";
572        let expected = Node::try_from(text).unwrap();
573        let node = Node::from_reader(ByteByByte(text.as_bytes())).unwrap();
574        assert_eq!(node, expected);
575    }
576
577    #[test]
578    fn partial_bom_is_part_of_value() {
579        // U+F000 starts with byte 0xEF like a BOM: the bytes read are pushed back.
580        let node = Node::try_from("\u{f000}").unwrap();
581        assert_eq!(node, atom("\u{f000}"));
582    }
583
584    #[test]
585    fn propagates_io_errors() {
586        struct Failing;
587        impl Read for Failing {
588            fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
589                Err(io::Error::other("disk is on fire"))
590            }
591        }
592        let err = Node::from_reader(Failing).unwrap_err();
593        assert!(matches!(err, ParseError::Io(_)));
594        insta::assert_snapshot!(err, @"I/O error while reading input: disk is on fire");
595    }
596
597    #[test]
598    fn rejects_broken_input() {
599        assert!(Node::try_from("{1,2").is_err());
600        assert!(Node::try_from("{\"abc}").is_err());
601        assert!(Node::try_from("{1}}").is_err());
602    }
603
604    #[test]
605    fn reads_value_sequences() {
606        let text = "\u{feff}HEAD\r\n\r\n{1},\r\n{2,\"x\"},\r\nz,\r\n";
607        let mut values = Values::new(ByteByByte(text.as_bytes()));
608        assert_eq!(values.read_line().unwrap().as_deref(), Some("HEAD"));
609        assert_eq!(values.read_line().unwrap().as_deref(), Some(""));
610        let nodes: Vec<Node> = values.map(Result::unwrap).collect();
611        assert_eq!(
612            nodes,
613            [
614                Node::list([atom("1")]),
615                Node::list([atom("2"), Node::string("x")]),
616                atom("z")
617            ]
618        );
619        assert_eq!(nodes[1].position().unwrap().line, 4);
620
621        assert_eq!(Values::new("".as_bytes()).count(), 0);
622        let mut values = Values::new("{1}\r\n{2}".as_bytes());
623        assert!(values.next().unwrap().is_ok());
624        insta::assert_snapshot!(values.next().unwrap().unwrap_err(), @"unexpected character '{' at line 2, column 1: expected ',' or '}'");
625        assert!(values.next().is_none());
626        let err = Values::new("{1},{2".as_bytes())
627            .nth(1)
628            .unwrap()
629            .unwrap_err();
630        insta::assert_snapshot!(err, @"unexpected end of input at line 1, column 7: list opened at line 1, column 5 is not closed");
631    }
632
633    #[test]
634    fn reports_positions() {
635        let err = Node::try_from("{1,\r\n{2,\r\n  3").unwrap_err();
636        insta::assert_snapshot!(err, @"unexpected end of input at line 3, column 4: list opened at line 2, column 1 is not closed");
637        let err = Node::try_from("{\"ёж\" x}").unwrap_err();
638        insta::assert_snapshot!(err, @"unexpected character 'x' at line 1, column 7: expected ',' or '}'");
639        assert_eq!(err.position().map(|p| p.offset), Some(8));
640    }
641}