Skip to main content

shuck_parser/parser/
result.rs

1use shuck_ast::{File, Span};
2
3use crate::error::Error;
4
5/// Overall outcome of a parse attempt.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ParseStatus {
8    /// The parse completed without recovery diagnostics.
9    Clean,
10    /// The parse completed, but required recovery diagnostics.
11    Recovered,
12    /// The parse failed with a terminal error.
13    Fatal,
14}
15
16/// One branch separator recognized inside a zsh `case` pattern group.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct ZshCaseGroupPart {
19    /// Index of the owning pattern part within the parsed pattern.
20    pub pattern_part_index: usize,
21    /// Source span covering the separator syntax.
22    pub span: Span,
23}
24
25/// Additional parser-owned facts that are useful to downstream consumers.
26#[derive(Debug, Clone, Default, PartialEq, Eq)]
27pub struct SyntaxFacts {
28    /// Spans of zsh brace-style `if` bodies.
29    pub zsh_brace_if_spans: Vec<Span>,
30    /// Spans of zsh `always` clauses.
31    pub zsh_always_spans: Vec<Span>,
32    /// Pattern-group separators collected from zsh `case` items.
33    pub zsh_case_group_parts: Vec<ZshCaseGroupPart>,
34}
35
36/// A parser diagnostic emitted while recovering from invalid input.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct ParseDiagnostic {
39    /// Human-readable diagnostic message.
40    pub message: String,
41    /// Source span associated with the diagnostic.
42    pub span: Span,
43}
44
45/// The result of parsing a script, including any recovery diagnostics and
46/// syntax facts collected along the way.
47#[derive(Debug, Clone)]
48pub struct ParseResult {
49    /// Parsed syntax tree for the file.
50    pub file: File,
51    /// Recovery diagnostics emitted while producing the AST.
52    pub diagnostics: Vec<ParseDiagnostic>,
53    /// High-level parse status.
54    pub status: ParseStatus,
55    /// Terminal parse error, when recovery could not continue.
56    pub terminal_error: Option<Error>,
57    /// Additional syntax facts collected during parsing.
58    pub syntax_facts: SyntaxFacts,
59}
60
61impl ParseResult {
62    /// Returns `true` when the parse completed without recovery diagnostics.
63    pub fn is_ok(&self) -> bool {
64        self.status == ParseStatus::Clean
65    }
66
67    /// Returns `true` when the parse produced recovery diagnostics or a terminal error.
68    pub fn is_err(&self) -> bool {
69        !self.is_ok()
70    }
71
72    /// Convert this result into a strict parse error.
73    ///
74    /// If recovery diagnostics exist but no terminal error was recorded, the first recovery
75    /// diagnostic is converted into an [`Error`].
76    pub fn strict_error(&self) -> Error {
77        self.terminal_error.clone().unwrap_or_else(|| {
78            let Some(diagnostic) = self.diagnostics.first() else {
79                panic!("non-clean parse result should include a diagnostic or terminal error");
80            };
81            Error::parse_at(
82                diagnostic.message.clone(),
83                diagnostic.span.start.line,
84                diagnostic.span.start.column,
85            )
86        })
87    }
88
89    /// Return the parse result when it is clean, otherwise panic with the strict error.
90    pub fn unwrap(self) -> Self {
91        if self.is_ok() {
92            self
93        } else {
94            panic!(
95                "called `ParseResult::unwrap()` on a non-clean parse: {}",
96                self.strict_error()
97            )
98        }
99    }
100
101    /// Return the parse result when it is clean, otherwise panic with `message`.
102    pub fn expect(self, message: &str) -> Self {
103        if self.is_ok() {
104            self
105        } else {
106            panic!("{message}: {}", self.strict_error())
107        }
108    }
109
110    /// Return the strict parse error when the result is not clean, otherwise panic.
111    pub fn unwrap_err(self) -> Error {
112        if self.is_err() {
113            self.strict_error()
114        } else {
115            panic!("called `ParseResult::unwrap_err()` on a clean parse")
116        }
117    }
118
119    /// Return the strict parse error when the result is not clean, otherwise panic with
120    /// `message`.
121    pub fn expect_err(self, message: &str) -> Error {
122        if self.is_err() {
123            self.strict_error()
124        } else {
125            panic!("{message}")
126        }
127    }
128}