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
14pub use crate::SyntaxError;
15
16/// A parsed `.bib` file: the green tree plus any syntax errors gathered
17/// alongside it. Errors never abort the parse.
18#[derive(Debug, Clone)]
19pub struct Parse {
20    pub green: GreenNode,
21    pub errors: Vec<SyntaxError>,
22}
23
24impl Parse {
25    /// Materialize a fresh red-tree cursor over the parsed file. Cheap (an atomic
26    /// clone of the green node).
27    pub fn syntax(&self) -> SyntaxNode {
28        SyntaxNode::new_root(self.green.clone())
29    }
30}
31
32/// Parse BibTeX/BibLaTeX source into a lossless CST.
33pub fn parse(input: &str) -> Parse {
34    let tokens = lex(input);
35    let (events, errors) = grammar::parse(&tokens);
36    let green = build_tree(&tokens, &events);
37    Parse { green, errors }
38}
39
40/// Parse `input` and render the CST back to source. By the losslessness
41/// invariant this always equals `input`.
42pub fn reconstruct(input: &str) -> String {
43    parse(input).syntax().to_string()
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use crate::bib::syntax::SyntaxKind;
50
51    #[test]
52    fn reconstruct_is_identity() {
53        let input = "@article{key,\n  title = {Hi},\n  year = 2020,\n}\n";
54        assert_eq!(reconstruct(input), input);
55    }
56
57    #[test]
58    fn entry_has_key_and_fields() {
59        let parse = parse("@article{key, title = {Hi}}");
60        let entry = parse
61            .syntax()
62            .descendants()
63            .find(|n| n.kind() == SyntaxKind::ENTRY)
64            .expect("an ENTRY node");
65        assert!(
66            entry.children().any(|n| n.kind() == SyntaxKind::KEY),
67            "the entry should have a KEY node"
68        );
69        assert!(
70            entry.children().any(|n| n.kind() == SyntaxKind::FIELD),
71            "the entry should have a FIELD node"
72        );
73        assert!(parse.errors.is_empty());
74    }
75}