Skip to main content

deb822_lossless/
parse.rs

1//! Parse wrapper type following rust-analyzer's pattern for thread-safe storage in Salsa.
2
3use crate::lossless::{Deb822, ParseError, PositionedParseError};
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<String>,
16    positioned_errors: Vec<PositionedParseError>,
17    _ty: PhantomData<T>,
18}
19
20impl<T> Parse<T> {
21    /// Create a new Parse result from a GreenNode and errors
22    pub fn new(green: GreenNode, errors: Vec<String>) -> Self {
23        Parse {
24            green,
25            errors,
26            positioned_errors: Vec::new(),
27            _ty: PhantomData,
28        }
29    }
30
31    /// Create a new Parse result from a GreenNode, errors, and positioned errors
32    pub fn new_with_positioned_errors(
33        green: GreenNode,
34        errors: Vec<String>,
35        positioned_errors: Vec<PositionedParseError>,
36    ) -> Self {
37        Parse {
38            green,
39            errors,
40            positioned_errors,
41            _ty: PhantomData,
42        }
43    }
44
45    /// Get the green node (thread-safe representation)
46    pub fn green(&self) -> &GreenNode {
47        &self.green
48    }
49
50    /// Get the syntax errors
51    pub fn errors(&self) -> &[String] {
52        &self.errors
53    }
54
55    /// Get parse errors with position information
56    pub fn positioned_errors(&self) -> &[PositionedParseError] {
57        &self.positioned_errors
58    }
59
60    /// Get parse errors as strings (for backward compatibility if needed)
61    pub fn error_messages(&self) -> Vec<String> {
62        self.positioned_errors
63            .iter()
64            .map(|e| e.message.clone())
65            .collect()
66    }
67
68    /// Check if there are any errors
69    pub fn ok(&self) -> bool {
70        self.errors.is_empty()
71    }
72
73    /// Convert to a Result, returning the tree if there are no errors
74    pub fn to_result(self) -> Result<T, ParseError>
75    where
76        T: AstNode<Language = crate::lossless::Lang>,
77    {
78        if self.errors.is_empty() {
79            let node = SyntaxNode::new_root_mut(self.green);
80            Ok(T::cast(node).expect("root node has wrong type"))
81        } else {
82            Err(ParseError(self.errors))
83        }
84    }
85
86    /// Get the parsed syntax tree, panicking if there are errors
87    pub fn tree(&self) -> T
88    where
89        T: AstNode<Language = crate::lossless::Lang>,
90    {
91        assert!(
92            self.errors.is_empty(),
93            "tried to get tree with errors: {:?}",
94            self.errors
95        );
96        let node = SyntaxNode::new_root_mut(self.green.clone());
97        T::cast(node).expect("root node has wrong type")
98    }
99
100    /// Get the syntax node
101    pub fn syntax_node(&self) -> SyntaxNode<crate::lossless::Lang> {
102        SyntaxNode::new_root(self.green.clone())
103    }
104}
105
106// Implement Send + Sync since GreenNode is thread-safe
107unsafe impl<T> Send for Parse<T> {}
108unsafe impl<T> Sync for Parse<T> {}
109
110impl Parse<Deb822> {
111    /// Parse deb822 text, returning a Parse result
112    pub fn parse_deb822(text: &str) -> Self {
113        let parsed = crate::lossless::parse(text);
114        Parse::new_with_positioned_errors(
115            parsed.green_node,
116            parsed.errors,
117            parsed.positioned_errors,
118        )
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_positioned_errors_api() {
128        let input = "Invalid field without colon\nBroken: field: extra colon\n";
129        let parsed = Parse::<Deb822>::parse_deb822(input);
130
131        // Should have positioned errors
132        let positioned_errors = parsed.positioned_errors();
133        assert!(!positioned_errors.is_empty());
134
135        // Should still have string errors for backward compatibility
136        let string_errors = parsed.errors();
137        assert!(!string_errors.is_empty());
138
139        // Should be able to get error messages
140        let error_messages = parsed.error_messages();
141        assert_eq!(error_messages.len(), positioned_errors.len());
142
143        for (i, positioned_error) in positioned_errors.iter().enumerate() {
144            assert_eq!(positioned_error.message, error_messages[i]);
145            assert!(positioned_error.range.start() <= positioned_error.range.end());
146        }
147    }
148
149    #[test]
150    fn test_positioned_errors_example() {
151        // Example from the requirements document
152        let input = "Invalid: field\nBroken field without colon";
153        let parsed = Parse::<Deb822>::parse_deb822(input);
154
155        let positioned_errors = parsed.positioned_errors();
156        assert!(!positioned_errors.is_empty());
157
158        // Example usage like in a language server
159        for error in positioned_errors {
160            let start_offset: u32 = error.range.start().into();
161            let end_offset: u32 = error.range.end().into();
162            println!(
163                "Error at {:?} ({}..{}): {}",
164                error.range, start_offset, end_offset, error.message
165            );
166
167            // Verify we can extract the problematic text
168            if end_offset <= input.len() as u32 {
169                let error_text = &input[start_offset as usize..end_offset as usize];
170                assert!(!error_text.is_empty());
171            }
172        }
173    }
174}