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
60    ///
61    /// Returns the tree even if there are parse errors. Use `errors()` or `ok()` to check
62    /// for errors separately if needed.
63    pub fn tree(&self) -> T
64    where
65        T: AstNode<Language = crate::lossless::Lang>,
66    {
67        let node = SyntaxNode::new_root_mut(self.green.clone());
68        T::cast(node).expect("root node has wrong type")
69    }
70
71    /// Get the syntax node
72    pub fn syntax_node(&self) -> SyntaxNode<crate::lossless::Lang> {
73        SyntaxNode::new_root(self.green.clone())
74    }
75}
76
77// Implement Send + Sync since GreenNode is thread-safe
78unsafe impl<T> Send for Parse<T> {}
79unsafe impl<T> Sync for Parse<T> {}
80
81impl Parse<Makefile> {
82    /// Parse makefile text, returning a Parse result
83    pub fn parse_makefile(text: &str) -> Self {
84        let parsed = crate::lossless::parse(text, None);
85        Parse::new(parsed.green_node, parsed.errors)
86    }
87}
88
89impl Parse<Rule> {
90    /// Parse rule text, returning a Parse result
91    pub fn parse_rule(text: &str) -> Self {
92        let parsed = crate::lossless::parse(text, None);
93        Parse::new(parsed.green_node, parsed.errors)
94    }
95
96    /// Convert to a Result, extracting a single rule from the makefile
97    pub fn to_rule_result(self) -> Result<Rule, Error> {
98        if !self.errors.is_empty() {
99            return Err(Error::Parse(ParseError {
100                errors: self.errors,
101            }));
102        }
103
104        let makefile =
105            Makefile::cast(SyntaxNode::new_root_mut(self.green)).expect("root node has wrong type");
106        let rules: Vec<_> = makefile.rules().collect();
107
108        if rules.len() == 1 {
109            Ok(rules.into_iter().next().unwrap())
110        } else {
111            Err(Error::Parse(ParseError {
112                errors: vec![ErrorInfo {
113                    message: "expected a single rule".to_string(),
114                    line: 1,
115                    context: "".to_string(),
116                }],
117            }))
118        }
119    }
120}