makefile_lossless/
parse.rs

1//! Parse wrapper type following rust-analyzer's pattern for thread-safe storage in Salsa.
2
3use crate::lossless::{Error, ErrorInfo, Makefile, ParseError, Rule};
4use rowan::ast::AstNode;
5use rowan::{GreenNode, SyntaxNode};
6use std::marker::PhantomData;
7
8/// The result of parsing: a syntax tree and a collection of errors.
9///
10/// This type is designed to be stored in Salsa databases as it contains
11/// the thread-safe `GreenNode` instead of the non-thread-safe `SyntaxNode`.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Parse<T> {
14    green: GreenNode,
15    errors: Vec<ErrorInfo>,
16    _ty: PhantomData<T>,
17}
18
19impl<T> Parse<T> {
20    /// Create a new Parse result from a GreenNode and errors
21    pub fn new(green: GreenNode, errors: Vec<ErrorInfo>) -> Self {
22        Parse {
23            green,
24            errors,
25            _ty: PhantomData,
26        }
27    }
28
29    /// Get the green node (thread-safe representation)
30    pub fn green(&self) -> &GreenNode {
31        &self.green
32    }
33
34    /// Get the syntax errors
35    pub fn errors(&self) -> &[ErrorInfo] {
36        &self.errors
37    }
38
39    /// Check if there are any errors
40    pub fn ok(&self) -> bool {
41        self.errors.is_empty()
42    }
43
44    /// Convert to a Result, returning the tree if there are no errors
45    pub fn to_result(self) -> Result<T, Error>
46    where
47        T: AstNode<Language = crate::lossless::Lang>,
48    {
49        if self.errors.is_empty() {
50            let node = SyntaxNode::new_root_mut(self.green);
51            Ok(T::cast(node).expect("root node has wrong type"))
52        } else {
53            Err(Error::Parse(ParseError {
54                errors: self.errors,
55            }))
56        }
57    }
58
59    /// Get the parsed syntax tree, panicking if there are errors
60    pub fn tree(&self) -> T
61    where
62        T: AstNode<Language = crate::lossless::Lang>,
63    {
64        assert!(
65            self.errors.is_empty(),
66            "tried to get tree with errors: {:?}",
67            self.errors
68        );
69        let node = SyntaxNode::new_root_mut(self.green.clone());
70        T::cast(node).expect("root node has wrong type")
71    }
72
73    /// Get the syntax node
74    pub fn syntax_node(&self) -> SyntaxNode<crate::lossless::Lang> {
75        SyntaxNode::new_root(self.green.clone())
76    }
77}
78
79// Implement Send + Sync since GreenNode is thread-safe
80unsafe impl<T> Send for Parse<T> {}
81unsafe impl<T> Sync for Parse<T> {}
82
83impl Parse<Makefile> {
84    /// Parse makefile text, returning a Parse result
85    pub fn parse_makefile(text: &str) -> Self {
86        let parsed = crate::lossless::parse(text);
87        Parse::new(parsed.green_node, parsed.errors)
88    }
89}
90
91impl Parse<Rule> {
92    /// Parse rule text, returning a Parse result
93    pub fn parse_rule(text: &str) -> Self {
94        let parsed = crate::lossless::parse(text);
95        Parse::new(parsed.green_node, parsed.errors)
96    }
97
98    /// Convert to a Result, extracting a single rule from the makefile
99    pub fn to_rule_result(self) -> Result<Rule, Error> {
100        if !self.errors.is_empty() {
101            return Err(Error::Parse(ParseError {
102                errors: self.errors,
103            }));
104        }
105
106        let makefile =
107            Makefile::cast(SyntaxNode::new_root_mut(self.green)).expect("root node has wrong type");
108        let rules: Vec<_> = makefile.rules().collect();
109
110        if rules.len() == 1 {
111            Ok(rules.into_iter().next().unwrap())
112        } else {
113            Err(Error::Parse(ParseError {
114                errors: vec![ErrorInfo {
115                    message: "expected a single rule".to_string(),
116                    line: 1,
117                    context: "".to_string(),
118                }],
119            }))
120        }
121    }
122}