Skip to main content

scute_core/
parser.rs

1use tree_sitter::{Language, Tree};
2
3/// A source code parser that produces syntax trees.
4///
5/// Implementations are stateful and mutable: a single instance should
6/// be reused across multiple files so it can recycle internal buffers.
7///
8/// The signature currently uses [`tree_sitter::Language`] and
9/// [`tree_sitter::Tree`] directly to avoid a wrapper layer that would
10/// add no value today. Implementors must depend on the same
11/// `tree_sitter` version.
12pub trait AstParser {
13    /// Parse source code with the given language grammar.
14    ///
15    /// # Errors
16    ///
17    /// Returns [`ParseError::LanguageSetup`] if the grammar can't be loaded,
18    /// or [`ParseError::ParseFailed`] if the parser produces no tree.
19    fn parse(&mut self, source: &str, language: &Language) -> Result<Tree, ParseError>;
20}
21
22#[derive(Debug)]
23pub enum ParseError {
24    /// The grammar could not be loaded (ABI version mismatch).
25    LanguageSetup,
26    /// The parser failed to produce a syntax tree.
27    ParseFailed,
28}
29
30impl std::fmt::Display for ParseError {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            Self::LanguageSetup => write!(f, "failed to load grammar (ABI mismatch)"),
34            Self::ParseFailed => write!(f, "parser failed to produce a syntax tree"),
35        }
36    }
37}
38
39impl std::error::Error for ParseError {}
40
41/// [`AstParser`] backed by tree-sitter.
42pub struct TreeSitterParser(tree_sitter::Parser);
43
44impl TreeSitterParser {
45    #[must_use]
46    pub fn new() -> Self {
47        Self(tree_sitter::Parser::new())
48    }
49}
50
51impl Default for TreeSitterParser {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57impl AstParser for TreeSitterParser {
58    fn parse(&mut self, source: &str, language: &Language) -> Result<Tree, ParseError> {
59        self.0
60            .set_language(language)
61            .map_err(|_| ParseError::LanguageSetup)?;
62        self.0.parse(source, None).ok_or(ParseError::ParseFailed)
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn parses_valid_rust_source() {
72        let mut parser = TreeSitterParser::new();
73
74        let tree = parser.parse("fn main() {}", &tree_sitter_rust::LANGUAGE.into());
75
76        assert!(tree.is_ok());
77    }
78
79    #[test]
80    fn switches_language_between_calls() {
81        let mut parser = TreeSitterParser::new();
82
83        let rust = parser.parse("fn main() {}", &tree_sitter_rust::LANGUAGE.into());
84        let ts = parser.parse(
85            "function main() {}",
86            &tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
87        );
88
89        assert!(rust.is_ok());
90        assert!(ts.is_ok());
91    }
92}