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