Skip to main content

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, PositionedParseError, 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    positioned_errors: Vec<PositionedParseError>,
17    _ty: PhantomData<T>,
18}
19
20impl<T> Parse<T> {
21    /// Create a new Parse result from a GreenNode, errors, and positioned errors
22    pub fn new(
23        green: GreenNode,
24        errors: Vec<ErrorInfo>,
25        positioned_errors: Vec<PositionedParseError>,
26    ) -> Self {
27        Parse {
28            green,
29            errors,
30            positioned_errors,
31            _ty: PhantomData,
32        }
33    }
34
35    /// Get the green node (thread-safe representation)
36    pub fn green(&self) -> &GreenNode {
37        &self.green
38    }
39
40    /// Get the syntax errors
41    pub fn errors(&self) -> &[ErrorInfo] {
42        &self.errors
43    }
44
45    /// Get parse errors with position information
46    pub fn positioned_errors(&self) -> &[PositionedParseError] {
47        &self.positioned_errors
48    }
49
50    /// Check if there are any errors
51    pub fn ok(&self) -> bool {
52        self.errors.is_empty()
53    }
54
55    /// Convert to a Result, returning the tree if there are no errors
56    pub fn to_result(self) -> Result<T, Error>
57    where
58        T: AstNode<Language = crate::lossless::Lang>,
59    {
60        if self.errors.is_empty() {
61            let node = SyntaxNode::new_root_mut(self.green);
62            Ok(T::cast(node).expect("root node has wrong type"))
63        } else {
64            Err(Error::Parse(ParseError {
65                errors: self.errors,
66            }))
67        }
68    }
69
70    /// Get the parsed syntax tree
71    ///
72    /// Returns the tree even if there are parse errors. Use `errors()`,
73    /// `positioned_errors()`, or `ok()` to check for errors separately if needed.
74    /// This allows for error-resilient tooling that can work with partial/invalid input.
75    pub fn tree(&self) -> T
76    where
77        T: AstNode<Language = crate::lossless::Lang>,
78    {
79        let node = SyntaxNode::new_root_mut(self.green.clone());
80        T::cast(node).expect("root node has wrong type")
81    }
82
83    /// Get the syntax node
84    pub fn syntax_node(&self) -> SyntaxNode<crate::lossless::Lang> {
85        SyntaxNode::new_root(self.green.clone())
86    }
87}
88
89// Implement Send + Sync since GreenNode is thread-safe
90unsafe impl<T> Send for Parse<T> {}
91unsafe impl<T> Sync for Parse<T> {}
92
93impl Parse<Makefile> {
94    /// Parse makefile text, returning a Parse result
95    pub fn parse_makefile(text: &str) -> Self {
96        let parsed = crate::lossless::parse(text, None);
97        Parse::new(parsed.green_node, parsed.errors, parsed.positioned_errors)
98    }
99}
100
101impl Parse<Rule> {
102    /// Parse rule text, returning a Parse result
103    pub fn parse_rule(text: &str) -> Self {
104        let parsed = crate::lossless::parse(text, None);
105        Parse::new(parsed.green_node, parsed.errors, parsed.positioned_errors)
106    }
107
108    /// Convert to a Result, extracting a single rule from the makefile
109    pub fn to_rule_result(self) -> Result<Rule, Error> {
110        if !self.errors.is_empty() {
111            return Err(Error::Parse(ParseError {
112                errors: self.errors,
113            }));
114        }
115
116        let makefile =
117            Makefile::cast(SyntaxNode::new_root_mut(self.green)).expect("root node has wrong type");
118        let rules: Vec<_> = makefile.rules().collect();
119
120        if rules.len() == 1 {
121            Ok(rules.into_iter().next().unwrap())
122        } else {
123            Err(Error::Parse(ParseError {
124                errors: vec![ErrorInfo {
125                    message: "expected a single rule".to_string(),
126                    line: 1,
127                    context: "".to_string(),
128                }],
129            }))
130        }
131    }
132}