Skip to main content

yaml_edit/
parse.rs

1//! Parser types and utilities.
2
3use rowan::GreenNode;
4
5/// The result of a parse operation.
6///
7/// Returns the syntax tree even if there are parse errors. Use [`errors()`](Self::errors)
8/// or [`positioned_errors()`](Self::positioned_errors) to check for errors if needed.
9/// This allows for error-resilient tooling that can work with partial or invalid input.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct Parse<T> {
12    green_node: GreenNode,
13    positioned_errors: Vec<crate::PositionedParseError>,
14    _ty: std::marker::PhantomData<fn() -> T>,
15}
16
17impl<T> Parse<T> {
18    pub(crate) fn new(
19        green_node: GreenNode,
20        positioned_errors: Vec<crate::PositionedParseError>,
21    ) -> Self {
22        Parse {
23            green_node,
24            positioned_errors,
25            _ty: std::marker::PhantomData,
26        }
27    }
28
29    /// The parse tree. If there were no parse errors, this is a valid tree.
30    /// If there were parse errors, this tree might be only partially valid.
31    pub fn tree(&self) -> T
32    where
33        T: rowan::ast::AstNode<Language = crate::Lang>,
34    {
35        let syntax_node = rowan::SyntaxNode::new_root_mut(self.green_node.clone());
36        T::cast(syntax_node)
37            .expect("Parse<T> always holds a green node whose root kind matches T::can_cast")
38    }
39
40    /// Positioned parse errors with location information.
41    pub fn positioned_errors(&self) -> &[crate::PositionedParseError] {
42        &self.positioned_errors
43    }
44
45    /// Parse error messages, if any, each prefixed with its `line:column`.
46    pub fn errors(&self) -> Vec<String> {
47        let text = self.green_node.to_string();
48        self.positioned_errors
49            .iter()
50            .map(|e| format!("{}: {}", e.start_position(&text), e.message))
51            .collect()
52    }
53
54    /// Convert parse result to a `Result`, failing with the first error if any.
55    pub fn to_result(self) -> Result<T, crate::YamlError>
56    where
57        T: rowan::ast::AstNode<Language = crate::Lang>,
58    {
59        if let Some(first) = self.positioned_errors.first() {
60            let pos = first.start_position(&self.green_node.to_string());
61            Err(crate::YamlError::Parse {
62                message: first.message.clone(),
63                line: Some(pos.line),
64                column: Some(pos.column),
65            })
66        } else {
67            Ok(self.tree())
68        }
69    }
70
71    /// Whether the parse had any errors.
72    pub fn has_errors(&self) -> bool {
73        !self.positioned_errors.is_empty()
74    }
75
76    /// Whether the parse succeeded without any errors.
77    pub fn ok(&self) -> bool {
78        self.positioned_errors.is_empty()
79    }
80}
81
82impl Parse<crate::YamlFile> {
83    /// Parse YAML text, returning a Parse result
84    pub fn parse_yaml(text: &str) -> Self {
85        let parsed = crate::yaml::parse(text);
86        Parse::new(parsed.green_node, parsed.positioned_errors)
87    }
88}