Skip to main content

badness_parser/bib/
core.rs

1//! The BibTeX parser entry point and its output type.
2//!
3//! `parse` runs the pipeline: [`lex`] → [`grammar::parse`] (the recursive
4//! descent, which emits events + errors) → [`build_tree`] (the green tree).
5//! Syntax errors ride a side channel and never abort the parse.
6
7use rowan::GreenNode;
8
9use crate::bib::grammar;
10use crate::bib::lexer::lex;
11use crate::bib::syntax::SyntaxNode;
12use crate::bib::tree_builder::build_tree;
13
14/// A parsed `.bib` file: the green tree plus any syntax errors gathered
15/// alongside it. Errors never abort the parse.
16#[derive(Debug, Clone)]
17pub struct Parse {
18    pub green: GreenNode,
19    pub errors: Vec<SyntaxError>,
20}
21
22impl Parse {
23    /// Materialize a fresh red-tree cursor over the parsed file. Cheap (an atomic
24    /// clone of the green node).
25    pub fn syntax(&self) -> SyntaxNode {
26        SyntaxNode::new_root(self.green.clone())
27    }
28}
29
30/// A syntax error, carried on a side channel keyed by byte range.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct SyntaxError {
33    pub message: String,
34    pub start: usize,
35    pub end: usize,
36}
37
38/// Parse BibTeX/BibLaTeX source into a lossless CST.
39pub fn parse(input: &str) -> Parse {
40    let tokens = lex(input);
41    let (events, errors) = grammar::parse(&tokens);
42    let green = build_tree(&tokens, &events);
43    Parse { green, errors }
44}
45
46/// Parse `input` and render the CST back to source. By the losslessness
47/// invariant this always equals `input`.
48pub fn reconstruct(input: &str) -> String {
49    parse(input).syntax().to_string()
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use crate::bib::syntax::SyntaxKind;
56
57    #[test]
58    fn reconstruct_is_identity() {
59        let input = "@article{key,\n  title = {Hi},\n  year = 2020,\n}\n";
60        assert_eq!(reconstruct(input), input);
61    }
62
63    #[test]
64    fn entry_has_key_and_fields() {
65        let parse = parse("@article{key, title = {Hi}}");
66        let entry = parse
67            .syntax()
68            .descendants()
69            .find(|n| n.kind() == SyntaxKind::ENTRY)
70            .expect("an ENTRY node");
71        assert!(
72            entry.children().any(|n| n.kind() == SyntaxKind::KEY),
73            "the entry should have a KEY node"
74        );
75        assert!(
76            entry.children().any(|n| n.kind() == SyntaxKind::FIELD),
77            "the entry should have a FIELD node"
78        );
79        assert!(parse.errors.is_empty());
80    }
81}